From 23387d6a5c8094521ec9e3bed1e455a63c267cd9 Mon Sep 17 00:00:00 2001 From: pinky <> Date: Sun, 7 Jan 2007 21:24:58 +0000 Subject: [PATCH] Removed reportlab -> 2.0 Better report on timesheets bzr revid: pinky-f7e72504718a6a358300c76ac783f3eb37406dbd --- bin/osv/orm.py | 1 + bin/report/render/rml2pdf/trml2pdf.py | 2 +- bin/reportlab/__init__.py | 23 - bin/reportlab/extensions/README | 25 - bin/reportlab/extensions/__init__.py | 7 - bin/reportlab/fonts/00readme.txt | 8 - bin/reportlab/fonts/Dustismo_Roman.ttf | Bin 105340 -> 0 bytes bin/reportlab/fonts/PenguinAttack.ttf | Bin 70356 -> 0 bytes bin/reportlab/fonts/Wargames.afm | 471 --- bin/reportlab/fonts/Wargames.pfb | Bin 29611 -> 0 bytes bin/reportlab/graphics/__init__.py | 4 - bin/reportlab/graphics/charts/__init__.py | 4 - bin/reportlab/graphics/charts/areas.py | 92 - bin/reportlab/graphics/charts/axes.py | 1958 ------------- bin/reportlab/graphics/charts/barcharts.py | 1943 ------------- bin/reportlab/graphics/charts/dotbox.py | 165 -- bin/reportlab/graphics/charts/doughnut.py | 372 --- bin/reportlab/graphics/charts/legends.py | 486 ---- bin/reportlab/graphics/charts/linecharts.py | 656 ----- bin/reportlab/graphics/charts/lineplots.py | 1096 ------- bin/reportlab/graphics/charts/markers.py | 81 - bin/reportlab/graphics/charts/piecharts.py | 863 ------ bin/reportlab/graphics/charts/slidebox.py | 186 -- bin/reportlab/graphics/charts/spider.py | 353 --- bin/reportlab/graphics/charts/textlabels.py | 440 --- bin/reportlab/graphics/charts/utils.py | 191 -- bin/reportlab/graphics/charts/utils3d.py | 233 -- bin/reportlab/graphics/renderPDF.py | 361 --- bin/reportlab/graphics/renderPM.py | 631 ---- bin/reportlab/graphics/renderPS.py | 859 ------ bin/reportlab/graphics/renderSVG.py | 813 ------ bin/reportlab/graphics/renderbase.py | 315 -- bin/reportlab/graphics/samples/__init__.py | 0 bin/reportlab/graphics/samples/bubble.py | 73 - .../graphics/samples/clustered_bar.py | 84 - .../graphics/samples/clustered_column.py | 83 - bin/reportlab/graphics/samples/excelcolors.py | 45 - .../graphics/samples/exploded_pie.py | 65 - .../graphics/samples/filled_radar.py | 54 - bin/reportlab/graphics/samples/line_chart.py | 83 - .../samples/linechart_with_markers.py | 94 - bin/reportlab/graphics/samples/radar.py | 66 - bin/reportlab/graphics/samples/runall.py | 59 - bin/reportlab/graphics/samples/scatter.py | 71 - .../graphics/samples/scatter_lines.py | 82 - .../graphics/samples/scatter_lines_markers.py | 72 - bin/reportlab/graphics/samples/simple_pie.py | 61 - bin/reportlab/graphics/samples/stacked_bar.py | 85 - .../graphics/samples/stacked_column.py | 84 - bin/reportlab/graphics/shapes.py | 1244 -------- bin/reportlab/graphics/testdrawings.py | 294 -- bin/reportlab/graphics/testshapes.py | 547 ---- bin/reportlab/graphics/widgetbase.py | 490 ---- bin/reportlab/graphics/widgets/__init__.py | 4 - bin/reportlab/graphics/widgets/eventcal.py | 303 -- bin/reportlab/graphics/widgets/flags.py | 879 ------ bin/reportlab/graphics/widgets/grids.py | 504 ---- bin/reportlab/graphics/widgets/markers.py | 228 -- .../graphics/widgets/signsandsymbols.py | 919 ------ bin/reportlab/lib/PyFontify.py | 155 - bin/reportlab/lib/__init__.py | 7 - bin/reportlab/lib/abag.py | 44 - bin/reportlab/lib/attrmap.py | 132 - bin/reportlab/lib/codecharts.py | 340 --- bin/reportlab/lib/colors.py | 564 ---- bin/reportlab/lib/corp.py | 443 --- bin/reportlab/lib/enums.py | 11 - bin/reportlab/lib/extformat.py | 81 - bin/reportlab/lib/fonts.py | 89 - bin/reportlab/lib/formatters.py | 100 - bin/reportlab/lib/logger.py | 61 - bin/reportlab/lib/normalDate.py | 603 ---- bin/reportlab/lib/pagesizes.py | 55 - bin/reportlab/lib/randomtext.py | 348 --- bin/reportlab/lib/rparsexml.py | 440 --- bin/reportlab/lib/sequencer.py | 284 -- bin/reportlab/lib/set_ops.py | 38 - bin/reportlab/lib/styles.py | 256 -- bin/reportlab/lib/tocindex.py | 294 -- bin/reportlab/lib/units.py | 23 - bin/reportlab/lib/utils.py | 776 ----- bin/reportlab/lib/validators.py | 260 -- bin/reportlab/lib/xmllib.py | 770 ----- bin/reportlab/lib/yaml.py | 188 -- bin/reportlab/pdfbase/__init__.py | 6 - bin/reportlab/pdfbase/_cidfontdata.py | 452 --- bin/reportlab/pdfbase/_fontdata.py | 2590 ----------------- bin/reportlab/pdfbase/cidfonts.py | 399 --- bin/reportlab/pdfbase/pdfdoc.py | 1855 ------------ bin/reportlab/pdfbase/pdfform.py | 632 ---- bin/reportlab/pdfbase/pdfmetrics.py | 773 ----- bin/reportlab/pdfbase/pdfpattern.py | 59 - bin/reportlab/pdfbase/pdfutils.py | 453 --- bin/reportlab/pdfbase/ttfonts.py | 1048 ------- bin/reportlab/pdfgen/__init__.py | 5 - bin/reportlab/pdfgen/canvas.py | 1487 ---------- bin/reportlab/pdfgen/pathobject.py | 93 - bin/reportlab/pdfgen/pdfgeom.py | 77 - bin/reportlab/pdfgen/pdfimages.py | 186 -- bin/reportlab/pdfgen/pycanvas.py | 309 -- bin/reportlab/pdfgen/textobject.py | 360 --- bin/reportlab/platypus/__init__.py | 14 - bin/reportlab/platypus/doctemplate.py | 897 ------ bin/reportlab/platypus/figures.py | 372 --- bin/reportlab/platypus/flowables.py | 693 ----- bin/reportlab/platypus/frames.py | 195 -- bin/reportlab/platypus/para.py | 2369 --------------- bin/reportlab/platypus/paragraph.py | 944 ------ bin/reportlab/platypus/paraparser.py | 938 ------ bin/reportlab/platypus/tableofcontents.py | 329 --- bin/reportlab/platypus/tables.py | 1184 -------- bin/reportlab/platypus/xpreformatted.py | 316 -- bin/reportlab/rl_config.py | 132 - bin/reportlab/tools/README | 10 - bin/reportlab/tools/__init__.py | 3 - bin/reportlab/tools/docco/README | 8 - bin/reportlab/tools/docco/__init__.py | 3 - bin/reportlab/tools/docco/codegrab.py | 228 -- bin/reportlab/tools/docco/docpy.py | 1247 -------- bin/reportlab/tools/docco/examples.py | 851 ------ bin/reportlab/tools/docco/graphdocpy.py | 980 ------- bin/reportlab/tools/docco/rl_doc_utils.py | 411 --- bin/reportlab/tools/docco/rltemplate.py | 140 - bin/reportlab/tools/docco/stylesheet.py | 147 - bin/reportlab/tools/docco/t_parse.py | 247 -- bin/reportlab/tools/docco/yaml.py | 201 -- bin/reportlab/tools/docco/yaml2pdf.py | 104 - bin/reportlab/tools/py2pdf/README | 8 - bin/reportlab/tools/py2pdf/__init__.py | 3 - bin/reportlab/tools/py2pdf/demo-config.txt | 11 - bin/reportlab/tools/py2pdf/demo.py | 198 -- bin/reportlab/tools/py2pdf/idle_print.py | 56 - bin/reportlab/tools/py2pdf/py2pdf.py | 1567 ---------- bin/reportlab/tools/py2pdf/vertpython.jpg | Bin 22872 -> 0 bytes bin/reportlab/tools/pythonpoint/README | 29 - bin/reportlab/tools/pythonpoint/__init__.py | 3 - .../tools/pythonpoint/customshapes.py | 298 -- bin/reportlab/tools/pythonpoint/demos/htu.xml | 96 - .../tools/pythonpoint/demos/leftlogo.a85 | 53 - .../tools/pythonpoint/demos/leftlogo.gif | Bin 2198 -> 0 bytes .../tools/pythonpoint/demos/lj8100.jpg | Bin 12463 -> 0 bytes .../tools/pythonpoint/demos/monterey.xml | 306 -- .../tools/pythonpoint/demos/outline.gif | Bin 12918 -> 0 bytes .../tools/pythonpoint/demos/pplogo.gif | Bin 3429 -> 0 bytes .../tools/pythonpoint/demos/python.gif | Bin 125666 -> 0 bytes .../tools/pythonpoint/demos/pythonpoint.xml | 1051 ------- .../tools/pythonpoint/demos/spectrum.png | Bin 1855 -> 0 bytes .../tools/pythonpoint/demos/vertpython.gif | Bin 20910 -> 0 bytes .../tools/pythonpoint/pythonpoint.dtd | 275 -- .../tools/pythonpoint/pythonpoint.py | 1129 ------- bin/reportlab/tools/pythonpoint/stdparser.py | 834 ------ .../tools/pythonpoint/styles/__init__.py | 3 - .../tools/pythonpoint/styles/horrible.py | 101 - bin/reportlab/tools/pythonpoint/styles/htu.py | 158 - .../tools/pythonpoint/styles/modern.py | 120 - .../tools/pythonpoint/styles/projection.py | 106 - .../tools/pythonpoint/styles/standard.py | 132 - 157 files changed, 2 insertions(+), 55856 deletions(-) delete mode 100644 bin/reportlab/__init__.py delete mode 100644 bin/reportlab/extensions/README delete mode 100644 bin/reportlab/extensions/__init__.py delete mode 100644 bin/reportlab/fonts/00readme.txt delete mode 100644 bin/reportlab/fonts/Dustismo_Roman.ttf delete mode 100644 bin/reportlab/fonts/PenguinAttack.ttf delete mode 100644 bin/reportlab/fonts/Wargames.afm delete mode 100644 bin/reportlab/fonts/Wargames.pfb delete mode 100644 bin/reportlab/graphics/__init__.py delete mode 100644 bin/reportlab/graphics/charts/__init__.py delete mode 100644 bin/reportlab/graphics/charts/areas.py delete mode 100644 bin/reportlab/graphics/charts/axes.py delete mode 100644 bin/reportlab/graphics/charts/barcharts.py delete mode 100644 bin/reportlab/graphics/charts/dotbox.py delete mode 100644 bin/reportlab/graphics/charts/doughnut.py delete mode 100644 bin/reportlab/graphics/charts/legends.py delete mode 100644 bin/reportlab/graphics/charts/linecharts.py delete mode 100644 bin/reportlab/graphics/charts/lineplots.py delete mode 100644 bin/reportlab/graphics/charts/markers.py delete mode 100644 bin/reportlab/graphics/charts/piecharts.py delete mode 100644 bin/reportlab/graphics/charts/slidebox.py delete mode 100644 bin/reportlab/graphics/charts/spider.py delete mode 100644 bin/reportlab/graphics/charts/textlabels.py delete mode 100644 bin/reportlab/graphics/charts/utils.py delete mode 100644 bin/reportlab/graphics/charts/utils3d.py delete mode 100644 bin/reportlab/graphics/renderPDF.py delete mode 100644 bin/reportlab/graphics/renderPM.py delete mode 100644 bin/reportlab/graphics/renderPS.py delete mode 100644 bin/reportlab/graphics/renderSVG.py delete mode 100644 bin/reportlab/graphics/renderbase.py delete mode 100644 bin/reportlab/graphics/samples/__init__.py delete mode 100644 bin/reportlab/graphics/samples/bubble.py delete mode 100644 bin/reportlab/graphics/samples/clustered_bar.py delete mode 100644 bin/reportlab/graphics/samples/clustered_column.py delete mode 100644 bin/reportlab/graphics/samples/excelcolors.py delete mode 100644 bin/reportlab/graphics/samples/exploded_pie.py delete mode 100644 bin/reportlab/graphics/samples/filled_radar.py delete mode 100644 bin/reportlab/graphics/samples/line_chart.py delete mode 100644 bin/reportlab/graphics/samples/linechart_with_markers.py delete mode 100644 bin/reportlab/graphics/samples/radar.py delete mode 100644 bin/reportlab/graphics/samples/runall.py delete mode 100644 bin/reportlab/graphics/samples/scatter.py delete mode 100644 bin/reportlab/graphics/samples/scatter_lines.py delete mode 100644 bin/reportlab/graphics/samples/scatter_lines_markers.py delete mode 100644 bin/reportlab/graphics/samples/simple_pie.py delete mode 100644 bin/reportlab/graphics/samples/stacked_bar.py delete mode 100644 bin/reportlab/graphics/samples/stacked_column.py delete mode 100644 bin/reportlab/graphics/shapes.py delete mode 100755 bin/reportlab/graphics/testdrawings.py delete mode 100755 bin/reportlab/graphics/testshapes.py delete mode 100644 bin/reportlab/graphics/widgetbase.py delete mode 100644 bin/reportlab/graphics/widgets/__init__.py delete mode 100644 bin/reportlab/graphics/widgets/eventcal.py delete mode 100644 bin/reportlab/graphics/widgets/flags.py delete mode 100644 bin/reportlab/graphics/widgets/grids.py delete mode 100644 bin/reportlab/graphics/widgets/markers.py delete mode 100644 bin/reportlab/graphics/widgets/signsandsymbols.py delete mode 100644 bin/reportlab/lib/PyFontify.py delete mode 100755 bin/reportlab/lib/__init__.py delete mode 100644 bin/reportlab/lib/abag.py delete mode 100644 bin/reportlab/lib/attrmap.py delete mode 100644 bin/reportlab/lib/codecharts.py delete mode 100644 bin/reportlab/lib/colors.py delete mode 100755 bin/reportlab/lib/corp.py delete mode 100644 bin/reportlab/lib/enums.py delete mode 100644 bin/reportlab/lib/extformat.py delete mode 100755 bin/reportlab/lib/fonts.py delete mode 100755 bin/reportlab/lib/formatters.py delete mode 100755 bin/reportlab/lib/logger.py delete mode 100755 bin/reportlab/lib/normalDate.py delete mode 100755 bin/reportlab/lib/pagesizes.py delete mode 100755 bin/reportlab/lib/randomtext.py delete mode 100644 bin/reportlab/lib/rparsexml.py delete mode 100644 bin/reportlab/lib/sequencer.py delete mode 100755 bin/reportlab/lib/set_ops.py delete mode 100644 bin/reportlab/lib/styles.py delete mode 100644 bin/reportlab/lib/tocindex.py delete mode 100755 bin/reportlab/lib/units.py delete mode 100644 bin/reportlab/lib/utils.py delete mode 100644 bin/reportlab/lib/validators.py delete mode 100644 bin/reportlab/lib/xmllib.py delete mode 100644 bin/reportlab/lib/yaml.py delete mode 100644 bin/reportlab/pdfbase/__init__.py delete mode 100644 bin/reportlab/pdfbase/_cidfontdata.py delete mode 100644 bin/reportlab/pdfbase/_fontdata.py delete mode 100644 bin/reportlab/pdfbase/cidfonts.py delete mode 100644 bin/reportlab/pdfbase/pdfdoc.py delete mode 100644 bin/reportlab/pdfbase/pdfform.py delete mode 100644 bin/reportlab/pdfbase/pdfmetrics.py delete mode 100644 bin/reportlab/pdfbase/pdfpattern.py delete mode 100644 bin/reportlab/pdfbase/pdfutils.py delete mode 100644 bin/reportlab/pdfbase/ttfonts.py delete mode 100644 bin/reportlab/pdfgen/__init__.py delete mode 100644 bin/reportlab/pdfgen/canvas.py delete mode 100644 bin/reportlab/pdfgen/pathobject.py delete mode 100644 bin/reportlab/pdfgen/pdfgeom.py delete mode 100644 bin/reportlab/pdfgen/pdfimages.py delete mode 100644 bin/reportlab/pdfgen/pycanvas.py delete mode 100644 bin/reportlab/pdfgen/textobject.py delete mode 100644 bin/reportlab/platypus/__init__.py delete mode 100644 bin/reportlab/platypus/doctemplate.py delete mode 100644 bin/reportlab/platypus/figures.py delete mode 100644 bin/reportlab/platypus/flowables.py delete mode 100644 bin/reportlab/platypus/frames.py delete mode 100644 bin/reportlab/platypus/para.py delete mode 100644 bin/reportlab/platypus/paragraph.py delete mode 100644 bin/reportlab/platypus/paraparser.py delete mode 100644 bin/reportlab/platypus/tableofcontents.py delete mode 100644 bin/reportlab/platypus/tables.py delete mode 100644 bin/reportlab/platypus/xpreformatted.py delete mode 100644 bin/reportlab/rl_config.py delete mode 100644 bin/reportlab/tools/README delete mode 100644 bin/reportlab/tools/__init__.py delete mode 100644 bin/reportlab/tools/docco/README delete mode 100644 bin/reportlab/tools/docco/__init__.py delete mode 100644 bin/reportlab/tools/docco/codegrab.py delete mode 100755 bin/reportlab/tools/docco/docpy.py delete mode 100644 bin/reportlab/tools/docco/examples.py delete mode 100755 bin/reportlab/tools/docco/graphdocpy.py delete mode 100755 bin/reportlab/tools/docco/rl_doc_utils.py delete mode 100644 bin/reportlab/tools/docco/rltemplate.py delete mode 100644 bin/reportlab/tools/docco/stylesheet.py delete mode 100644 bin/reportlab/tools/docco/t_parse.py delete mode 100644 bin/reportlab/tools/docco/yaml.py delete mode 100644 bin/reportlab/tools/docco/yaml2pdf.py delete mode 100644 bin/reportlab/tools/py2pdf/README delete mode 100644 bin/reportlab/tools/py2pdf/__init__.py delete mode 100644 bin/reportlab/tools/py2pdf/demo-config.txt delete mode 100755 bin/reportlab/tools/py2pdf/demo.py delete mode 100644 bin/reportlab/tools/py2pdf/idle_print.py delete mode 100755 bin/reportlab/tools/py2pdf/py2pdf.py delete mode 100644 bin/reportlab/tools/py2pdf/vertpython.jpg delete mode 100644 bin/reportlab/tools/pythonpoint/README delete mode 100644 bin/reportlab/tools/pythonpoint/__init__.py delete mode 100644 bin/reportlab/tools/pythonpoint/customshapes.py delete mode 100644 bin/reportlab/tools/pythonpoint/demos/htu.xml delete mode 100644 bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 delete mode 100644 bin/reportlab/tools/pythonpoint/demos/leftlogo.gif delete mode 100644 bin/reportlab/tools/pythonpoint/demos/lj8100.jpg delete mode 100644 bin/reportlab/tools/pythonpoint/demos/monterey.xml delete mode 100644 bin/reportlab/tools/pythonpoint/demos/outline.gif delete mode 100644 bin/reportlab/tools/pythonpoint/demos/pplogo.gif delete mode 100644 bin/reportlab/tools/pythonpoint/demos/python.gif delete mode 100644 bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml delete mode 100644 bin/reportlab/tools/pythonpoint/demos/spectrum.png delete mode 100644 bin/reportlab/tools/pythonpoint/demos/vertpython.gif delete mode 100644 bin/reportlab/tools/pythonpoint/pythonpoint.dtd delete mode 100755 bin/reportlab/tools/pythonpoint/pythonpoint.py delete mode 100644 bin/reportlab/tools/pythonpoint/stdparser.py delete mode 100644 bin/reportlab/tools/pythonpoint/styles/__init__.py delete mode 100644 bin/reportlab/tools/pythonpoint/styles/horrible.py delete mode 100644 bin/reportlab/tools/pythonpoint/styles/htu.py delete mode 100644 bin/reportlab/tools/pythonpoint/styles/modern.py delete mode 100644 bin/reportlab/tools/pythonpoint/styles/projection.py delete mode 100644 bin/reportlab/tools/pythonpoint/styles/standard.py diff --git a/bin/osv/orm.py b/bin/osv/orm.py index c1486330354..2fbb2428731 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1244,6 +1244,7 @@ class orm(object): 'relate': resrelate } + print result return result # TODO: ameliorer avec NULL diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index dea3be7a5bb..08e707dc1ca 100755 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -37,7 +37,7 @@ import os # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support # # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46 -encoding = 'cp1252' +encoding = 'utf-8' def str2xml(s): return s.replace('&', '&').replace('<', '<').replace('>', '>') diff --git a/bin/reportlab/__init__.py b/bin/reportlab/__init__.py deleted file mode 100644 index f961fb448a4..00000000000 --- a/bin/reportlab/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#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/__init__.py -__version__=''' $Id$ ''' -__doc__="""The Reportlab PDF generation library.""" -Version = "1.20" - -def getStory(context): - if context.target == 'UserGuide': - # parse some local file - import os - myDir = os.path.split(__file__)[0] - import yaml - return yaml.parseFile(myDir + os.sep + 'mydocs.yaml') - else: - # this signals that it should revert to default processing - return None - - -def getMonitor(): - import reportlab.monitor - mon = reportlab.monitor.ReportLabToolkitMonitor() - return mon diff --git a/bin/reportlab/extensions/README b/bin/reportlab/extensions/README deleted file mode 100644 index a0d9fc5ca28..00000000000 --- a/bin/reportlab/extensions/README +++ /dev/null @@ -1,25 +0,0 @@ -This directory is intended to act as a placeholder for extending ReportLab -with extra extensions. It has been packagised with an empty __init__.py. - -So a typical extender should add his package extension as - - reportlab/extensions/great_extension/__init__.py - /dingo.py - /etc etc - -and single modules as - - reportlab/extensions/my_module.py - -Then client code can do - - from reportlab.extensions.great_extension import dingo - -if you extend with just a single module it might be simpler to add that -into extensions so that you could do - - from reportlab.extensions import my_module. - - -ReportLab can take no responsibility for name clashes and problems caused by -modules and packages in reportlab/extensions. diff --git a/bin/reportlab/extensions/__init__.py b/bin/reportlab/extensions/__init__.py deleted file mode 100644 index f98a66cae04..00000000000 --- a/bin/reportlab/extensions/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#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/extensions/__init__.py -__version__=''' $Id$ ''' -__doc__=""" -""" -# No usable content. Do not add to this file! diff --git a/bin/reportlab/fonts/00readme.txt b/bin/reportlab/fonts/00readme.txt deleted file mode 100644 index 08d545a8666..00000000000 --- a/bin/reportlab/fonts/00readme.txt +++ /dev/null @@ -1,8 +0,0 @@ -This directory is a convenient place to put -fonts where Reportlab will find them. If running -in a server environment, this is a good place to -put Type 1 fonts needed by your application. If -in a desktop environment, you might prefer to add -your font directories to the T1SearchPath in -reportlab/rl_config.py instead. - diff --git a/bin/reportlab/fonts/Dustismo_Roman.ttf b/bin/reportlab/fonts/Dustismo_Roman.ttf deleted file mode 100644 index 3a13d893049f3a082f3b9f9145ba02a022ecf73d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105340 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

f1_<2c*`$Wy);$nLheI0MVU|)c)ItQm6AAMsS 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: - valueStep = T[1]-T[0] - else: - oVS = self.valueStep - self.valueStep = None - T = self._calcTickPositions() - self.valueStep = oVS - if len(T)>1: - valueStep = T[1]-T[0] - else: - valueStep = self._valueStep - r = cache[K] = valueStep, T, valueStep*1e-8 - return r - - def _setRange(self, dataSeries): - """Set minimum and maximum axis values. - - The dataSeries argument is assumed to be a list of data - vectors. Each vector is itself a list or tuple of numbers. - - Returns a min, max tuple. - """ - - oMin = valueMin = self.valueMin - oMax = valueMax = self.valueMax - rangeRound = self.rangeRound - if valueMin is None: valueMin = self._cValueMin = _findMin(dataSeries,self._dataIndex,0) - if valueMax is None: valueMax = self._cValueMax = _findMax(dataSeries,self._dataIndex,0) - if valueMin == valueMax: - if valueMax==0: - if oMin is None and oMax is None: - zrp = getattr(self,'zrangePref',0) - if zrp>0: - valueMax = zrp - valueMin = 0 - elif zrp<0: - valueMax = 0 - valueMin = zrp - else: - valueMax = 0.01 - valueMin = -0.01 - elif self.valueMin is None: - valueMin = -0.01 - else: - valueMax = 0.01 - else: - if valueMax>0: - valueMax = 1.2*valueMax - valueMin = 0.0 - else: - valueMax = 0.0 - valueMin = 1.2*valueMin - - if getattr(self,'_bubblePlot',None): - bubbleMax = float(_findMax(dataSeries,2,0)) - frac=.25 - bubbleV=frac*(valueMax-valueMin) - self._bubbleV = bubbleV - self._bubbleMax = bubbleMax - self._bubbleRadius = frac*self._length - def special(T,x,func,bubbleV=bubbleV,bubbleMax=bubbleMax): - try: - v = T[2] - except IndexError: - v = bubbleMAx*0.1 - bubbleV *= (v/bubbleMax)**0.5 - return func(T[x]+bubbleV,T[x]-bubbleV) - if oMin is None: valueMin = self._cValueMin = _findMin(dataSeries,self._dataIndex,0,special=special) - if oMax is None: valueMax = self._cValueMax = _findMax(dataSeries,self._dataIndex,0,special=special) - - forceZero = self.forceZero - if forceZero: - if forceZero=='near': - forceZero = min(abs(valueMin),abs(valueMax)) <= 5*(valueMax-valueMin) - if forceZero: - if valueMax<0: valueMax=0 - elif valueMin>0: valueMin = 0 - - abf = self.avoidBoundFrac - do_rr = not getattr(self,'valueSteps',None) - do_abf = abf and do_rr - if type(abf) not in (TupleType,ListType): - abf = abf, abf - do_rr = rangeRound is not 'none' and do_rr - if do_rr: - rrn = rangeRound in ['both','floor'] - rrx = rangeRound in ['both','ceiling'] - else: - rrn = rrx = 0 - - go = do_rr or do_abf - cache = {} - cMin = valueMin - cMax = valueMax - while go: - go = 0 - if do_abf: - valueStep, T, fuzz = self._getValueStepAndTicks(valueMin, valueMax, cache) - fuzz = 1e-8*valueStep - i0 = valueStep*abf[0] - i1 = valueStep*abf[1] - if rrn: v = T[0] - else: v = valueMin - d = v - (cMin-i0) - if abs(v)>fuzz and fuzz < d: - valueMin = valueMin - (d+fuzz) - go = 1 - if rrn: v = T[-1] - else: v = valueMax - d = i1 + cMax - v - if abs(v)>fuzz and fuzz < d: - valueMax = valueMax + (d+fuzz) - go = 1 - - if do_rr: - valueStep, T, fuzz = self._getValueStepAndTicks(valueMin, valueMax, cache) - if rangeRound in ['both','floor'] and valueMinT[-1]+fuzz: - valueMax = T[-1]+valueStep - go = 1 - - self._valueMin, self._valueMax = valueMin, valueMax - self._rangeAdjust() - - def _rangeAdjust(self): - """Override this if you want to alter the calculated range. - - E.g. if want a minumamum range of 30% or don't want 100% - as the first point. - """ - pass - - def _adjustAxisTicks(self): - '''Override if you want to put slack at the ends of the axis - eg if you don't want the last tick to be at the bottom etc - ''' - pass - - def _calcScaleFactor(self): - """Calculate the axis' scale factor. - This should be called only *after* the axis' range is set. - Returns a number. - """ - self._scaleFactor = self._length / float(self._valueMax - self._valueMin) - return self._scaleFactor - - def _calcTickPositions(self): - self._calcValueStep() - P = [] - valueMin, valueMax, valueStep = self._valueMin, self._valueMax, self._valueStep - fuzz = 1e-8*valueStep - t = int((valueMin-fuzz)/valueStep) * valueStep - if t >= valueMin-fuzz: P.append(t) - t = t + valueStep - while t <= valueMax+fuzz: - P.append(t) - t = t + valueStep - return P - - def _calcTickmarkPositions(self): - """Calculate a list of tick positions on the axis. Returns a list of numbers.""" - self._tickValues = getattr(self,'valueSteps',None) - if self._tickValues: return self._tickValues - self._tickValues = self._calcTickPositions() - self._adjustAxisTicks() - return self._tickValues - - def _calcValueStep(self): - '''Calculate _valueStep for the axis or get from valueStep.''' - if self.valueStep is None: - rawRange = self._valueMax - self._valueMin - rawInterval = rawRange / min(float(self.maximumTicks-1),(float(self._length)/self.minimumTickSpacing)) - self._valueStep = nextRoundNumber(rawInterval) - else: - self._valueStep = self.valueStep - - def _allIntTicks(self): - for tick in self._tickValues: - try: - if int(tick)!=tick: return 0 - except: - return 0 - return 1 - - def makeTickLabels(self): - g = Group() - if not self.visibleLabels: return g - - f = self._labelTextFormat # perhaps someone already set it - if f is None: - f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str) - elif f is str and self._allIntTicks(): f = '%.0f' - post = self.labelTextPostFormat - scl = self.labelTextScale - pos = [self._x, self._y] - d = self._dataIndex - labels = self.labels - - i = 0 - for tick in self._tickValues: - if f and labels[i].visible: - v = self.scale(tick) - if scl is not None: - t = tick*scl - else: - t = tick - if type(f) is StringType: txt = f % t - elif type(f) in (TupleType,ListType): - #it's a list, use as many items as we get - if i < len(f): - txt = f[i] - else: - txt = '' - elif callable(f): - txt = f(t) - else: - raise ValueError, 'Invalid labelTextFormat %s' % f - if post: txt = post % txt - label = labels[i] - pos[d] = v - apply(label.setOrigin,pos) - label.setText(txt) - g.add(label) - i = i + 1 - - return g - - def draw(self): - g = Group() - - if not self.visible: - return g - - g.add(self.makeAxis()) - g.add(self.makeTicks()) - g.add(self.makeTickLabels()) - - return g - - -class XValueAxis(ValueAxis): - "X/value axis" - - _attrMap = AttrMap(BASE=ValueAxis, - tickUp = AttrMapValue(isNumber, - desc='Tick length up the axis.'), - tickDown = AttrMapValue(isNumber, - desc='Tick length down the axis.'), - joinAxis = AttrMapValue(None, - desc='Join both axes if true.'), - joinAxisMode = AttrMapValue(OneOf('bottom', 'top', 'value', 'points', None), - desc="Mode used for connecting axis ('bottom', 'top', 'value', 'points', None)."), - joinAxisPos = AttrMapValue(isNumberOrNone, - desc='Position at which to join with other axis.'), - ) - - # Indicate the dimension of the data we're interested in. - _dataIndex = 0 - - def __init__(self): - ValueAxis.__init__(self) - - self.labels.boxAnchor = 'n' - self.labels.dx = 0 - self.labels.dy = -5 - - self.tickUp = 0 - self.tickDown = 5 - - self.joinAxis = None - self.joinAxisMode = None - self.joinAxisPos = None - - - def demo(self): - self.setPosition(20, 50, 150) - self.configure([(10,20,30,40,50)]) - - d = Drawing(200, 100) - d.add(self) - return d - - - def joinToAxis(self, yAxis, mode='bottom', pos=None): - "Join with y-axis using some mode." - _assertYAxis(yAxis) - if mode == 'bottom': - self._x = yAxis._x * 1.0 - self._y = yAxis._y * 1.0 - elif mode == 'top': - self._x = yAxis._x * 1.0 - self._y = (yAxis._y + yAxis._length) * 1.0 - elif mode == 'value': - self._x = yAxis._x * 1.0 - self._y = yAxis.scale(pos) * 1.0 - elif mode == 'points': - self._x = yAxis._x * 1.0 - self._y = pos * 1.0 - - def scale(self, value): - """Converts a numeric value to a Y position. - - The chart first configures the axis, then asks it to - work out the x value for each point when plotting - lines or bars. You could override this to do - logarithmic axes. - """ - - msg = "Axis cannot scale numbers before it is configured" - assert self._configured, msg - if value is None: - value = 0 - return self._x + self._scaleFactor * (value - self._valueMin) - - - def makeAxis(self): - g = Group() - - if not self.visibleAxis: - return g - - ja = self.joinAxis - if ja: - jam = self.joinAxisMode - jap = self.joinAxisPos - jta = self.joinToAxis - if jam in ('bottom', 'top'): - jta(ja, mode=jam) - elif jam in ('value', 'points'): - jta(ja, mode=jam, pos=jap) - - axis = Line(self._x, self._y, self._x + self._length, self._y) - axis.strokeColor = self.strokeColor - axis.strokeWidth = self.strokeWidth - axis.strokeDashArray = self.strokeDashArray - g.add(axis) - - return g - - def makeTicks(self): - g = Group() - if self.visibleTicks and (self.tickUp or self.tickDown): - self._makeLines(g,-self.tickDown,self.tickUp,self.strokeColor,self.strokeWidth,self.strokeDashArray) - return g - -class NormalDateXValueAxis(XValueAxis): - """An X axis applying additional rules. - - Depending on the data and some built-in rules, the axis - displays normalDate values as nicely formatted dates. - - The client chart should have NormalDate X values. - """ - - _attrMap = AttrMap(BASE = XValueAxis, - bottomAxisLabelSlack = AttrMapValue(isNumber, desc="Fractional amount used to adjust label spacing"), - niceMonth = AttrMapValue(isBoolean, desc="Flag for displaying months 'nicely'."), - forceEndDate = AttrMapValue(isBoolean, desc='Flag for enforced displaying of last date value.'), - forceFirstDate = AttrMapValue(isBoolean, desc='Flag for enforced displaying of first date value.'), - xLabelFormat = AttrMapValue(None, desc="Label format string (e.g. '{mm}/{yy}') or function."), - dayOfWeekName = AttrMapValue(SequenceOf(isString,emptyOK=0,lo=7,hi=7), desc='Weekday names.'), - monthName = AttrMapValue(SequenceOf(isString,emptyOK=0,lo=12,hi=12), desc='Month names.'), - dailyFreq = AttrMapValue(isBoolean, desc='True if we are to assume daily data to be ticked at end of month.'), - ) - - _valueClass = normalDate.ND - - def __init__(self, **kw): - apply(XValueAxis.__init__, (self,)) - - # some global variables still used... - self.bottomAxisLabelSlack = 0.1 - self.niceMonth = 1 - self.forceEndDate = 0 - self.forceFirstDate = 0 - self.dailyFreq = 0 - self.xLabelFormat = "{mm}/{yy}" - self.dayOfWeekName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] - self.monthName = ['January', 'February', 'March', 'April', 'May', 'June', 'July', - 'August', 'September', 'October', 'November', 'December'] - self.valueSteps = None - - def _scalar2ND(self, x): - "Convert a scalar to a NormalDate value." - d = self._valueClass() - d.normalize(x) - return d - - def _dateFormatter(self, v): - "Create a formatted label for some value." - if not isinstance(v,normalDate.NormalDate): - v = self._scalar2ND(v) - d, m = normalDate._dayOfWeekName, normalDate._monthName - try: - normalDate._dayOfWeekName, normalDate._monthName = self.dayOfWeekName, self.monthName - return v.formatMS(self.xLabelFormat) - finally: - normalDate._dayOfWeekName, normalDate._monthName = d, m - - def _xAxisTicker(self, xVals): - """Complex stuff... - - Needs explanation... - """ - axisLength = self._length - formatter = self._dateFormatter - labels = self.labels - fontName, fontSize, leading = labels.fontName, labels.fontSize, labels.leading - textAnchor, boxAnchor, angle = labels.textAnchor, labels.boxAnchor, labels.angle - RBL = _textBoxLimits(string.split(formatter(xVals[0]),'\n'),fontName, - fontSize,leading or 1.2*fontSize,textAnchor,boxAnchor) - RBL = _rotatedBoxLimits(RBL[0],RBL[1],RBL[2],RBL[3], angle) - xLabelW = RBL[1]-RBL[0] - xLabelH = RBL[3]-RBL[2] - w = max(xLabelW,labels.width,self.minimumTickSpacing) - - W = w+w*self.bottomAxisLabelSlack - n = len(xVals) - ticks = [] - labels = [] - maximumTicks = self.maximumTicks - - def addTick(i, xVals=xVals, formatter=formatter, ticks=ticks, labels=labels): - ticks.insert(0,xVals[i]) - labels.insert(0,formatter(xVals[i])) - - for d in (1,2,3,6,12,24,60,120): - k = n/d - if k<=maximumTicks and k*W <= axisLength: - i = n-1 - if self.niceMonth: - j = xVals[-1].month() % (d<=12 and d or 12) - if j: - if self.forceEndDate: addTick(i) - i = i - j - - #weird first date ie not at end of month - try: - wfd = xVals[0].month() == xVals[1].month() - except: - wfd = 0 - - while i>=wfd: - addTick(i) - i = i - d - - if self.forceFirstDate and ticks[0] != xVals[0]: - addTick(0) - if (axisLength/(ticks[-1]-ticks[0]))*(ticks[1]-ticks[0])<=w: - del ticks[1], labels[1] - if self.forceEndDate and self.niceMonth and j: - if (axisLength/(ticks[-1]-ticks[0]))*(ticks[-1]-ticks[-2])<=w: - del ticks[-2], labels[-2] - try: - if labels[0] and labels[0]==labels[1]: - del ticks[1], labels[1] - except IndexError: - pass - - return ticks, labels - - def _convertXV(self,data): - '''Convert all XValues to a standard normalDate type''' - - VC = self._valueClass - for D in data: - for i in xrange(len(D)): - x, y = D[i] - if not isinstance(x,VC): - D[i] = (VC(x),y) - - def _getStepsAndLabels(self,xVals): - if self.dailyFreq: - xEOM = [] - pm = 0 - px = xVals[0] - for x in xVals: - m = x.month() - if pm!=m: - if pm: xEOM.append(px) - pm = m - px = x - px = xVals[-1] - if xEOM[-1]!=x: xEOM.append(px) - steps, labels = self._xAxisTicker(xEOM) - else: - steps, labels = self._xAxisTicker(xVals) - return steps, labels - - def configure(self, data): - self._convertXV(data) - from reportlab.lib.set_ops import union - xVals = reduce(union,map(lambda x: map(lambda dv: dv[0],x),data),[]) - xVals.sort() - steps,labels = self._getStepsAndLabels(xVals) - valueMin, valueMax = self.valueMin, self.valueMax - if valueMin is None: valueMin = xVals[0] - if valueMax is None: valueMax = xVals[-1] - self._valueMin, self._valueMax = valueMin, valueMax - self._tickValues = steps - self._labelTextFormat = labels - - self._scaleFactor = self._length / float(valueMax - valueMin) - self._tickValues = steps - self._configured = 1 - -class YValueAxis(ValueAxis): - "Y/value axis" - - _attrMap = AttrMap(BASE=ValueAxis, - tickLeft = AttrMapValue(isNumber, - desc='Tick length left of the axis.'), - tickRight = AttrMapValue(isNumber, - desc='Tick length right of the axis.'), - joinAxis = AttrMapValue(None, - desc='Join both axes if true.'), - joinAxisMode = AttrMapValue(OneOf(('left', 'right', 'value', 'points', None)), - desc="Mode used for connecting axis ('left', 'right', 'value', 'points', None)."), - joinAxisPos = AttrMapValue(isNumberOrNone, - desc='Position at which to join with other axis.'), - ) - - # Indicate the dimension of the data we're interested in. - _dataIndex = 1 - - def __init__(self): - ValueAxis.__init__(self) - - self.labels.boxAnchor = 'e' - self.labels.dx = -5 - self.labels.dy = 0 - - self.tickRight = 0 - self.tickLeft = 5 - - self.joinAxis = None - self.joinAxisMode = None - self.joinAxisPos = None - - - def demo(self): - data = [(10, 20, 30, 42)] - self.setPosition(100, 10, 80) - self.configure(data) - - drawing = Drawing(200, 100) - drawing.add(self) - return drawing - - - - def joinToAxis(self, xAxis, mode='left', pos=None): - "Join with x-axis using some mode." - _assertXAxis(xAxis) - if mode == 'left': - self._x = xAxis._x * 1.0 - self._y = xAxis._y * 1.0 - elif mode == 'right': - self._x = (xAxis._x + xAxis._length) * 1.0 - self._y = xAxis._y * 1.0 - elif mode == 'value': - self._x = xAxis.scale(pos) * 1.0 - self._y = xAxis._y * 1.0 - elif mode == 'points': - self._x = pos * 1.0 - self._y = xAxis._y * 1.0 - - def scale(self, value): - """Converts a numeric value to a Y position. - - The chart first configures the axis, then asks it to - work out the x value for each point when plotting - lines or bars. You could override this to do - logarithmic axes. - """ - - msg = "Axis cannot scale numbers before it is configured" - assert self._configured, msg - - if value is None: - value = 0 - return self._y + self._scaleFactor * (value - self._valueMin) - - - def makeAxis(self): - g = Group() - - if not self.visibleAxis: - return g - - ja = self.joinAxis - if ja: - jam = self.joinAxisMode - jap = self.joinAxisPos - jta = self.joinToAxis - if jam in ('left', 'right'): - jta(ja, mode=jam) - elif jam in ('value', 'points'): - jta(ja, mode=jam, pos=jap) - - axis = Line(self._x, self._y, self._x, self._y + self._length) - axis.strokeColor = self.strokeColor - axis.strokeWidth = self.strokeWidth - axis.strokeDashArray = self.strokeDashArray - g.add(axis) - - return g - - def makeTicks(self): - g = Group() - if self.visibleTicks and (self.tickLeft or self.tickRight): - self._makeLines(g,-self.tickLeft,self.tickRight,self.strokeColor,self.strokeWidth,self.strokeDashArray) - return g - -class AdjYValueAxis(YValueAxis): - """A Y-axis applying additional rules. - - Depending on the data and some built-in rules, the axis - may choose to adjust its range and origin. - """ - _attrMap = AttrMap(BASE = YValueAxis, - requiredRange = AttrMapValue(isNumberOrNone, desc='Minimum required value range.'), - leftAxisPercent = AttrMapValue(isBoolean, desc='When true add percent sign to label values.'), - leftAxisOrigShiftIPC = AttrMapValue(isNumber, desc='Lowest label shift interval ratio.'), - leftAxisOrigShiftMin = AttrMapValue(isNumber, desc='Minimum amount to shift.'), - leftAxisSkipLL0 = AttrMapValue(EitherOr((isBoolean,isListOfNumbers)), desc='Skip/Keep lowest tick label when true/false.\nOr skiplist') - ) - - def __init__(self): - apply(YValueAxis.__init__, (self,)) - self.requiredRange = 30 - self.leftAxisPercent = 1 - self.leftAxisOrigShiftIPC = 0.15 - self.leftAxisOrigShiftMin = 12 - self.leftAxisSkipLL0 = 0 - self.valueSteps = None - - def _rangeAdjust(self): - "Adjusts the value range of the axis." - - from reportlab.graphics.charts.utils import find_good_grid, ticks - y_min, y_max = self._valueMin, self._valueMax - m = self.maximumTicks - n = filter(lambda x,m=m: x<=m,[4,5,6,7,8,9]) - if not n: n = [m] - - valueStep, requiredRange = self.valueStep, self.requiredRange - if requiredRange and y_max - y_min < requiredRange: - y1, y2 = find_good_grid(y_min, y_max,n=n,grid=valueStep)[:2] - if y2 - y1 < requiredRange: - ym = (y1+y2)*0.5 - y1 = min(ym-requiredRange*0.5,y_min) - y2 = max(ym+requiredRange*0.5,y_max) - if y_min>=100 and y1<100: - y2 = y2 + 100 - y1 - y1 = 100 - elif y_min>=0 and y1<0: - y2 = y2 - y1 - y1 = 0 - self._valueMin, self._valueMax = y1, y2 - - T, L = ticks(self._valueMin, self._valueMax, split=1, n=n, percent=self.leftAxisPercent,grid=valueStep) - abf = self.avoidBoundFrac - if abf: - i1 = (T[1]-T[0]) - if type(abf) not in (TupleType,ListType): - i0 = i1 = i1*abf - else: - i0 = i1*abf[0] - i1 = i1*abf[1] - _n = getattr(self,'_cValueMin',T[0]) - _x = getattr(self,'_cValueMax',T[-1]) - if _n - T[0] < i0: self._valueMin = self._valueMin - i0 - if T[-1]-_x < i1: self._valueMax = self._valueMax + i1 - T, L = ticks(self._valueMin, self._valueMax, split=1, n=n, percent=self.leftAxisPercent,grid=valueStep) - - self._valueMin = T[0] - self._valueMax = T[-1] - self._tickValues = self.valueSteps = T - if self.labelTextFormat is None: - self._labelTextFormat = L - else: - self._labelTextFormat = self.labelTextFormat - - if abs(self._valueMin-100)<1e-6: - self._calcValueStep() - vMax, vMin = self._valueMax, self._valueMin - m = max(self.leftAxisOrigShiftIPC*self._valueStep, - (vMax-vMin)*self.leftAxisOrigShiftMin/self._length) - self._valueMin = self._valueMin - m - - if self.leftAxisSkipLL0: - if type(self.leftAxisSkipLL0) in (ListType,TupleType): - for x in self.leftAxisSkipLL0: - try: - L[x] = '' - except IndexError: - pass - L[0] = '' - -# Sample functions. -def sample0a(): - "Sample drawing with one xcat axis and two buckets." - - drawing = Drawing(400, 200) - - data = [(10, 20)] - - xAxis = XCategoryAxis() - xAxis.setPosition(75, 75, 300) - xAxis.configure(data) - xAxis.categoryNames = ['Ying', 'Yang'] - xAxis.labels.boxAnchor = 'n' - - drawing.add(xAxis) - - return drawing - - -def sample0b(): - "Sample drawing with one xcat axis and one bucket only." - - drawing = Drawing(400, 200) - - data = [(10,)] - - xAxis = XCategoryAxis() - xAxis.setPosition(75, 75, 300) - xAxis.configure(data) - xAxis.categoryNames = ['Ying'] - xAxis.labels.boxAnchor = 'n' - - drawing.add(xAxis) - - return drawing - - -def sample1(): - "Sample drawing containing two unconnected axes." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XCategoryAxis() - xAxis.setPosition(75, 75, 300) - xAxis.configure(data) - xAxis.categoryNames = ['Beer','Wine','Meat','Cannelloni'] - xAxis.labels.boxAnchor = 'n' - xAxis.labels[3].dy = -15 - xAxis.labels[3].angle = 30 - xAxis.labels[3].fontName = 'Times-Bold' - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -##def sample2a(): -## "Make sample drawing with two axes, x connected at top of y." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## -## xAxis = XCategoryAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.joinToAxis(yAxis, mode='top') -## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] -## xAxis.labels.boxAnchor = 'n' -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing -## -## -##def sample2b(): -## "Make two axes, x connected at bottom of y." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## -## xAxis = XCategoryAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.joinToAxis(yAxis, mode='bottom') -## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] -## xAxis.labels.boxAnchor = 'n' -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing -## -## -##def sample2c(): -## "Make two axes, x connected at fixed value (in points) of y." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## -## xAxis = XCategoryAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.joinToAxis(yAxis, mode='points', pos=100) -## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] -## xAxis.labels.boxAnchor = 'n' -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing -## -## -##def sample2d(): -## "Make two axes, x connected at fixed value (of y-axes) of y." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## -## xAxis = XCategoryAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.joinToAxis(yAxis, mode='value', pos=20) -## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] -## xAxis.labels.boxAnchor = 'n' -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing -## -## -##def sample3a(): -## "Make sample drawing with two axes, y connected at left of x." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## xAxis = XCategoryAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] -## xAxis.labels.boxAnchor = 'n' -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## yAxis.joinToAxis(xAxis, mode='left') -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing -## -## -##def sample3b(): -## "Make sample drawing with two axes, y connected at right of x." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## xAxis = XCategoryAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] -## xAxis.labels.boxAnchor = 'n' -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## yAxis.joinToAxis(xAxis, mode='right') -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing -## -## -##def sample3c(): -## "Make two axes, y connected at fixed value (in points) of x." -## -## drawing = Drawing(400, 200) -## -## data = [(10, 20, 30, 42)] -## -## yAxis = YValueAxis() -## yAxis.setPosition(50, 50, 125) -## yAxis.configure(data) -## -## xAxis = XValueAxis() -## xAxis._length = 300 -## xAxis.configure(data) -## xAxis.joinToAxis(yAxis, mode='points', pos=100) -## -## drawing.add(xAxis) -## drawing.add(yAxis) -## -## return drawing - - -def sample4a(): - "Sample drawing, xvalue/yvalue axes, y connected at 100 pts to x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'points' - xAxis.joinAxisPos = 100 - xAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample4b(): - "Sample drawing, xvalue/yvalue axes, y connected at value 35 of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'value' - xAxis.joinAxisPos = 35 - xAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample4c(): - "Sample drawing, xvalue/yvalue axes, y connected to bottom of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'bottom' - xAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample4c1(): - "xvalue/yvalue axes, without drawing axis lines/ticks." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - yAxis.visibleAxis = 0 - yAxis.visibleTicks = 0 - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'bottom' - xAxis.configure(data) - xAxis.visibleAxis = 0 - xAxis.visibleTicks = 0 - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample4d(): - "Sample drawing, xvalue/yvalue axes, y connected to top of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'top' - xAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample5a(): - "Sample drawing, xvalue/yvalue axes, y connected at 100 pts to x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis.setPosition(50, 50, 300) - xAxis.configure(data) - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'points' - yAxis.joinAxisPos = 100 - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample5b(): - "Sample drawing, xvalue/yvalue axes, y connected at value 35 of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis.setPosition(50, 50, 300) - xAxis.configure(data) - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'value' - yAxis.joinAxisPos = 35 - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample5c(): - "Sample drawing, xvalue/yvalue axes, y connected at right of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis.setPosition(50, 50, 300) - xAxis.configure(data) - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'right' - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample5d(): - "Sample drawing, xvalue/yvalue axes, y connected at left of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis.setPosition(50, 50, 300) - xAxis.configure(data) - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'left' - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample6a(): - "Sample drawing, xcat/yvalue axes, x connected at top of y." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XCategoryAxis() - xAxis._length = 300 - xAxis.configure(data) - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'top' - xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - xAxis.labels.boxAnchor = 'n' - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample6b(): - "Sample drawing, xcat/yvalue axes, x connected at bottom of y." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XCategoryAxis() - xAxis._length = 300 - xAxis.configure(data) - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'bottom' - xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - xAxis.labels.boxAnchor = 'n' - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample6c(): - "Sample drawing, xcat/yvalue axes, x connected at 100 pts to y." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XCategoryAxis() - xAxis._length = 300 - xAxis.configure(data) - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'points' - xAxis.joinAxisPos = 100 - xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - xAxis.labels.boxAnchor = 'n' - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample6d(): - "Sample drawing, xcat/yvalue axes, x connected at value 20 of y." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - yAxis = YValueAxis() - yAxis.setPosition(50, 50, 125) - yAxis.configure(data) - - xAxis = XCategoryAxis() - xAxis._length = 300 - xAxis.configure(data) - xAxis.joinAxis = yAxis - xAxis.joinAxisMode = 'value' - xAxis.joinAxisPos = 20 - xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - xAxis.labels.boxAnchor = 'n' - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample7a(): - "Sample drawing, xvalue/ycat axes, y connected at right of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.configure(data) - - yAxis = YCategoryAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'right' - yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - yAxis.labels.boxAnchor = 'e' - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample7b(): - "Sample drawing, xvalue/ycat axes, y connected at left of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.configure(data) - - yAxis = YCategoryAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'left' - yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - yAxis.labels.boxAnchor = 'e' - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample7c(): - "Sample drawing, xvalue/ycat axes, y connected at value 30 of x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.configure(data) - - yAxis = YCategoryAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'value' - yAxis.joinAxisPos = 30 - yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - yAxis.labels.boxAnchor = 'e' - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing - - -def sample7d(): - "Sample drawing, xvalue/ycat axes, y connected at 200 pts to x." - - drawing = Drawing(400, 200) - - data = [(10, 20, 30, 42)] - - xAxis = XValueAxis() - xAxis._length = 300 - xAxis.configure(data) - - yAxis = YCategoryAxis() - yAxis.setPosition(50, 50, 125) - yAxis.joinAxis = xAxis - yAxis.joinAxisMode = 'points' - yAxis.joinAxisPos = 200 - yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni'] - yAxis.labels.boxAnchor = 'e' - yAxis.configure(data) - - drawing.add(xAxis) - drawing.add(yAxis) - - return drawing diff --git a/bin/reportlab/graphics/charts/barcharts.py b/bin/reportlab/graphics/charts/barcharts.py deleted file mode 100644 index 5c57184060c..00000000000 --- a/bin/reportlab/graphics/charts/barcharts.py +++ /dev/null @@ -1,1943 +0,0 @@ -#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/graphics/charts/barcharts.py -"""This module defines a variety of Bar Chart components. - -The basic flavors are Side-by-side, available in horizontal and -vertical versions. - -Stacked and percentile bar charts to follow... -""" -__version__=''' $Id$ ''' - -import string, copy -from types import FunctionType, StringType - -from reportlab.lib import colors -from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isListOfStrings, SequenceOf, isBoolean, isNoneOrShape -from reportlab.lib.formatters import Formatter -from reportlab.lib.attrmap import AttrMap, AttrMapValue -from reportlab.pdfbase.pdfmetrics import stringWidth -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder -from reportlab.graphics.shapes import Line, Rect, Group, Drawing, NotImplementedError -from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis -from reportlab.graphics.charts.axes import YCategoryAxis, XValueAxis -from reportlab.graphics.charts.textlabels import BarChartLabel, NA_Label, NoneOrInstanceOfNA_Label -from reportlab.graphics.charts.areas import PlotArea - -class BarChartProperties(PropHolder): - _attrMap = AttrMap( - strokeColor = AttrMapValue(isColorOrNone, desc='Color of the bar border.'), - fillColor = AttrMapValue(isColorOrNone, desc='Color of the bar interior area.'), - strokeWidth = AttrMapValue(isNumber, desc='Width of the bar border.'), - symbol = AttrMapValue(None, desc='A widget to be used instead of a normal bar.'), - ) - - def __init__(self): - self.strokeColor = None - self.fillColor = colors.blue - self.strokeWidth = 0.5 - self.symbol = None - -# Bar chart classes. -class BarChart(PlotArea): - "Abstract base class, unusable by itself." - - _attrMap = AttrMap(BASE=PlotArea, - useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.'), - barWidth = AttrMapValue(isNumber, desc='The width of an individual bar.'), - groupSpacing = AttrMapValue(isNumber, desc='Width between groups of bars.'), - barSpacing = AttrMapValue(isNumber, desc='Width between individual bars.'), - bars = AttrMapValue(None, desc='Handle of the individual bars.'), - valueAxis = AttrMapValue(None, desc='Handle of the value axis.'), - categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'), - data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'), - barLabels = AttrMapValue(None, desc='Handle to the list of bar labels.'), - barLabelFormat = AttrMapValue(None, desc='Formatting string or function used for bar labels.'), - barLabelCallOut = AttrMapValue(None, desc='Callout function(label)\nlabel._callOutInfo = (self,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0)'), - barLabelArray = AttrMapValue(None, desc='explicit array of bar label values, must match size of data if present.'), - reversePlotOrder = AttrMapValue(isBoolean, desc='If true, reverse common category plot order.'), - naLabel = AttrMapValue(NoneOrInstanceOfNA_Label, desc='Label to use for N/A values.'), - annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'), - ) - - def __init__(self): - assert self.__class__.__name__ not in ('BarChart','BarChart3D'), 'Abstract Class %s Instantiated' % self.__class__.__name__ - - if self._flipXY: - self.categoryAxis = YCategoryAxis() - self.valueAxis = XValueAxis() - else: - self.categoryAxis = XCategoryAxis() - self.valueAxis = YValueAxis() - - PlotArea.__init__(self) - self.barSpacing = 0 - self.reversePlotOrder = 0 - - - # this defines two series of 3 points. Just an example. - self.data = [(100,110,120,130), - (70, 80, 85, 90)] - - # control bar spacing. is useAbsolute = 1 then - # the next parameters are in points; otherwise - # they are 'proportions' and are normalized to - # fit the available space. Half a barSpacing - # is allocated at the beginning and end of the - # chart. - self.useAbsolute = 0 #- not done yet - self.barWidth = 10 - self.groupSpacing = 5 - self.barSpacing = 0 - - self.barLabels = TypedPropertyCollection(BarChartLabel) - self.barLabels.boxAnchor = 'c' - self.barLabels.textAnchor = 'middle' - self.barLabelFormat = None - self.barLabelArray = None - # this says whether the origin is inside or outside - # the bar - +10 means put the origin ten points - # above the tip of the bar if value > 0, or ten - # points inside if bar value < 0. This is different - # to label dx/dy which are not dependent on the - # sign of the data. - self.barLabels.nudge = 0 - - # if you have multiple series, by default they butt - # together. - - # we really need some well-designed default lists of - # colors e.g. from Tufte. These will be used in a - # cycle to set the fill color of each series. - self.bars = TypedPropertyCollection(BarChartProperties) -## self.bars.symbol = None - self.bars.strokeWidth = 1 - self.bars.strokeColor = colors.black - - self.bars[0].fillColor = colors.red - self.bars[1].fillColor = colors.green - self.bars[2].fillColor = colors.blue - self.naLabel = None#NA_Label() - - - def demo(self): - """Shows basic use of a bar chart""" - if self.__class__.__name__=='BarChart': - raise NotImplementedError, 'Abstract Class BarChart has no demo' - drawing = Drawing(200, 100) - bc = self.__class__() - drawing.add(bc) - return drawing - - def _getConfigureData(self): - cA = self.categoryAxis - data = self.data - if cA.style not in ('parallel','parallel_3d'): - _data = data - data = max(map(len,_data))*[0] - for d in _data: - for i in xrange(len(d)): - data[i] = data[i] + (d[i] or 0) - data = list(_data) + [data] - self._configureData = data - - def _getMinMax(self): - '''Attempt to return the data range''' - self._getConfigureData() - self.valueAxis._setRange(self._configureData) - return self.valueAxis._valueMin, self.valueAxis._valueMax - - def _drawBegin(self,org,length): - '''Position and configure value axis, return crossing value''' - vA = self.valueAxis - vA.setPosition(self.x, self.y, length) - self._getConfigureData() - vA.configure(self._configureData) - - # if zero is in chart, put the other axis there, otherwise use low - crossesAt = vA.scale(0) - if crossesAt > org+length or crossesAt=0 and 1 or -1) * label.nudge, y + 0.5*height - else: - return x + 0.5*width, y + height + (height>=0 and 1 or -1) * label.nudge - - def _addBarLabel(self, g, rowNo, colNo, x, y, width, height): - text = self._getLabelText(rowNo,colNo) - if text: - self._addLabel(text, self.barLabels[(rowNo, colNo)], g, rowNo, colNo, x, y, width, height) - - def _addNABarLabel(self, g, rowNo, colNo, x, y, width, height): - na = self.naLabel - if na and na.text: - na = copy.copy(na) - v = self.valueAxis._valueMax<=0 and -1e-8 or 1e-8 - if width is None: width = v - if height is None: height = v - self._addLabel(na.text, na, g, rowNo, colNo, x, y, width, height) - - def _addLabel(self, text, label, g, rowNo, colNo, x, y, width, height): - if label.visible: - labelWidth = stringWidth(text, label.fontName, label.fontSize) - x0, y0 = self._labelXY(label,x,y,width,height) - flipXY = self._flipXY - if flipXY: - pm = width - else: - pm = height - label._pmv = pm #the plus minus val - fixedEnd = getattr(label,'fixedEnd', None) - if fixedEnd is not None: - v = fixedEnd._getValue(self,pm) - x00, y00 = x0, y0 - if flipXY: - x0 = v - else: - y0 = v - else: - if flipXY: - x00 = x0 - y00 = y+height/2.0 - else: - x00 = x+width/2.0 - y00 = y0 - fixedStart = getattr(label,'fixedStart', None) - if fixedStart is not None: - v = fixedStart._getValue(self,pm) - if flipXY: - x00 = v - else: - y00 = v - - if pm<0: - if flipXY: - dx = -2*label.dx - dy = 0 - else: - dy = -2*label.dy - dx = 0 - else: - dy = dx = 0 - label.setOrigin(x0+dx, y0+dy) - label.setText(text) - sC, sW = label.lineStrokeColor, label.lineStrokeWidth - if sC and sW: g.insert(0,Line(x00,y00,x0,y0, strokeColor=sC, strokeWidth=sW)) - g.add(label) - alx = getattr(self,'barLabelCallOut',None) - if alx: - label._callOutInfo = (self,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0) - alx(label) - del label._callOutInfo - - def _makeBar(self,g,x,y,width,height,rowNo,style): - r = Rect(x, y, width, height) - r.strokeWidth = style.strokeWidth - r.fillColor = style.fillColor - r.strokeColor = style.strokeColor - g.add(r) - - def _makeBars(self,g,lg): - lenData = len(self.data) - bars = self.bars - for rowNo in range(lenData): - row = self._barPositions[rowNo] - styleCount = len(bars) - styleIdx = rowNo % styleCount - rowStyle = bars[styleIdx] - for colNo in range(len(row)): - barPos = row[colNo] - style = bars.has_key((styleIdx,colNo)) and bars[(styleIdx,colNo)] or rowStyle - (x, y, width, height) = barPos - if None in (width,height): - self._addNABarLabel(lg,rowNo,colNo,x,y,width,height) - continue - - # Draw a rectangular symbol for each data item, - # or a normal colored rectangle. - symbol = None - if hasattr(style, 'symbol'): - symbol = copy.deepcopy(style.symbol) - elif hasattr(self.bars, 'symbol'): - symbol = self.bars.symbol - - if symbol: - symbol.x = x - symbol.y = y - symbol.width = width - symbol.height = height - g.add(symbol) - elif abs(width)>1e-7 and abs(height)>=1e-7 and (style.fillColor is not None or style.strokeColor is not None): - self._makeBar(g,x,y,width,height,rowNo,style) - - self._addBarLabel(lg,rowNo,colNo,x,y,width,height) - - def makeBars(self): - g = Group() - lg = Group() - self._makeBars(g,lg) - g.add(lg) - return g - - def _desiredCategoryAxisLength(self): - '''for dynamically computing the desired category axis length''' - style = self.categoryAxis.style - data = self.data - n = len(data) - m = max(map(len,data)) - if style=='parallel': - groupWidth = (n-1)*self.barSpacing+n*self.barWidth - else: - groupWidth = self.barWidth - return m*(self.groupSpacing+groupWidth) - - def draw(self): - cA, vA = self.categoryAxis, self.valueAxis - if vA: ovAjA, vA.joinAxis = vA.joinAxis, cA - if cA: ocAjA, cA.joinAxis = cA.joinAxis, vA - if self._flipXY: - cA.setPosition(self._drawBegin(self.x,self.width), self.y, self.height) - else: - cA.setPosition(self.x, self._drawBegin(self.y,self.height), self.width) - return self._drawFinish() - -class VerticalBarChart(BarChart): - "Vertical bar chart with multiple side-by-side bars." - _flipXY = 0 - -class HorizontalBarChart(BarChart): - "Horizontal bar chart with multiple side-by-side bars." - _flipXY = 1 - -class _FakeGroup: - def __init__(self, cmp=None): - self._data = [] - self._cmp = cmp - - def add(self,what): - self._data.append(what) - - def value(self): - return self._data - - def sort(self): - self._data.sort(self._cmp) - -class BarChart3D(BarChart): - _attrMap = AttrMap(BASE=BarChart, - theta_x = AttrMapValue(isNumber, desc='dx/dz'), - theta_y = AttrMapValue(isNumber, desc='dy/dz'), - zDepth = AttrMapValue(isNumber, desc='depth of an individual series'), - zSpace = AttrMapValue(isNumber, desc='z gap around series'), - ) - theta_x = .5 - theta_y = .5 - zDepth = None - zSpace = None - - def calcBarPositions(self): - BarChart.calcBarPositions(self) - seriesCount = self._seriesCount - zDepth = self.zDepth - if zDepth is None: zDepth = self.barWidth - zSpace = self.zSpace - if zSpace is None: zSpace = self.barSpacing - if self.categoryAxis.style=='parallel_3d': - _3d_depth = seriesCount*zDepth+(seriesCount+1)*zSpace - else: - _3d_depth = zDepth + 2*zSpace - _3d_depth *= self._normFactor - self._3d_dx = self.theta_x*_3d_depth - self._3d_dy = self.theta_y*_3d_depth - - def _calc_z0(self,rowNo): - zDepth = self.zDepth - if zDepth is None: zDepth = self.barWidth - zSpace = self.zSpace - if zSpace is None: zSpace = self.barSpacing - if self.categoryAxis.style=='parallel_3d': - z0 = self._normFactor*(rowNo*(zDepth+zSpace)+zSpace) - else: - z0 = self._normFactor*zSpace - return z0 - - def _makeBar(self,g,x,y,width,height,rowNo,style): - zDepth = self.zDepth - if zDepth is None: zDepth = self.barWidth - zSpace = self.zSpace - if zSpace is None: zSpace = self.barSpacing - z0 = self._calc_z0(rowNo) - z1 = z0 + zDepth*self._normFactor - if width<0: - x += width - width = -width - x += z0*self.theta_x - y += z0*self.theta_y - if self._flipXY: - y += zSpace - else: - x += zSpace - g.add((0,z0,z1,x,y,width,height,rowNo,style)) - - def _addBarLabel(self, g, rowNo, colNo, x, y, width, height): - z0 = self._calc_z0(rowNo) - zSpace = self.zSpace - if zSpace is None: zSpace = self.barSpacing - z1 = z0 - x += z0*self.theta_x - y += z0*self.theta_y - if self._flipXY: - y += zSpace - else: - x += zSpace - g.add((1,z0,z1,x,y,width,height,rowNo,colNo)) - - def makeBars(self): - from utils3d import _draw_3d_bar - fg = _FakeGroup(cmp=self._cmpZ) - self._makeBars(fg,fg) - fg.sort() - g = Group() - theta_x = self.theta_x - theta_y = self.theta_y - for t in fg.value(): - if t[0]==1: - z0,z1,x,y,width,height,rowNo,colNo = t[1:] - BarChart._addBarLabel(self,g,rowNo,colNo,x,y,width,height) - elif t[0]==0: - z0,z1,x,y,width,height,rowNo,style = t[1:] - dz = z1 - z0 - _draw_3d_bar(g, x, x+width, y, y+height, dz*theta_x, dz*theta_y, - fillColor=style.fillColor, fillColorShaded=None, - strokeColor=style.strokeColor, strokeWidth=style.strokeWidth, - shading=0.45) - return g - -class VerticalBarChart3D(BarChart3D,VerticalBarChart): - _cmpZ=lambda self,a,b:cmp((-a[1],a[3],a[0],-a[4]),(-b[1],b[3],b[0],-b[4])) - -class HorizontalBarChart3D(BarChart3D,HorizontalBarChart): - _cmpZ = lambda self,a,b: cmp((-a[1],a[4],a[0],-a[3]),(-b[1],b[4],b[0],-b[3])) #t, z0, z1, x, y = a[:5] - -# Vertical samples. -def sampleV0a(): - "A slightly pathologic bar chart with only TWO data items." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - 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 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV0b(): - "A pathologic bar chart with only ONE data item." - - drawing = Drawing(400, 200) - - data = [(42,)] - - 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 = 50 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'ne' - bc.categoryAxis.labels.dx = 8 - bc.categoryAxis.labels.dy = -2 - bc.categoryAxis.labels.angle = 30 - bc.categoryAxis.categoryNames = ['Jan-99'] - - drawing.add(bc) - - return drawing - - -def sampleV0c(): - "A really pathologic bar chart with NO data items at all!" - - drawing = Drawing(400, 200) - - data = [()] - - 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.categoryNames = [] - - drawing.add(bc) - - return drawing - - -def sampleV1(): - "Sample of multi-series bar chart." - - drawing = Drawing(400, 200) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (14, 6, 21, 23, 38, 46, 20, 5) - ] - - 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 sampleV2a(): - "Sample of multi-series bar chart." - - data = [(2.4, -5.7, 2, 5, 9.2), - (0.6, -4.9, -3, 4, 6.8) - ] - - labels = ("Q3 2000", "Year to Date", "12 months", - "Annualised\n3 years", "Since 07.10.99") - - drawing = Drawing(400, 200) - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 120 - bc.width = 300 - bc.data = data - - bc.barSpacing = 0 - bc.groupSpacing = 10 - bc.barWidth = 10 - - bc.valueAxis.valueMin = -15 - bc.valueAxis.valueMax = +15 - bc.valueAxis.valueStep = 5 - bc.valueAxis.labels.fontName = 'Helvetica' - bc.valueAxis.labels.fontSize = 8 - bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c') - bc.valueAxis.labels.textAnchor = 'middle' - - bc.categoryAxis.categoryNames = labels - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 8 - bc.categoryAxis.labels.dy = -60 - - drawing.add(bc) - - return drawing - - -def sampleV2b(): - "Sample of multi-series bar chart." - - data = [(2.4, -5.7, 2, 5, 9.2), - (0.6, -4.9, -3, 4, 6.8) - ] - - labels = ("Q3 2000", "Year to Date", "12 months", - "Annualised\n3 years", "Since 07.10.99") - - drawing = Drawing(400, 200) - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 120 - bc.width = 300 - bc.data = data - - bc.barSpacing = 5 - bc.groupSpacing = 10 - bc.barWidth = 10 - - bc.valueAxis.valueMin = -15 - bc.valueAxis.valueMax = +15 - bc.valueAxis.valueStep = 5 - bc.valueAxis.labels.fontName = 'Helvetica' - bc.valueAxis.labels.fontSize = 8 - bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c') - bc.valueAxis.labels.textAnchor = 'middle' - - bc.categoryAxis.categoryNames = labels - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 8 - bc.categoryAxis.labels.dy = -60 - - drawing.add(bc) - - return drawing - - -def sampleV2c(): - "Sample of multi-series bar chart." - - data = [(2.4, -5.7, 2, 5, 9.99), - (0.6, -4.9, -3, 4, 9.99) - ] - - labels = ("Q3 2000", "Year to Date", "12 months", - "Annualised\n3 years", "Since 07.10.99") - - drawing = Drawing(400, 200) - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 120 - bc.width = 300 - bc.data = data - - bc.barSpacing = 2 - bc.groupSpacing = 10 - bc.barWidth = 10 - - bc.valueAxis.valueMin = -15 - bc.valueAxis.valueMax = +15 - bc.valueAxis.valueStep = 5 - bc.valueAxis.labels.fontName = 'Helvetica' - bc.valueAxis.labels.fontSize = 8 - - bc.categoryAxis.categoryNames = labels - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 8 - bc.valueAxis.labels.boxAnchor = 'n' - bc.valueAxis.labels.textAnchor = 'middle' - bc.categoryAxis.labels.dy = -60 - - bc.barLabels.nudge = 10 - - bc.barLabelFormat = '%0.2f' - bc.barLabels.dx = 0 - bc.barLabels.dy = 0 - bc.barLabels.boxAnchor = 'n' # irrelevant (becomes 'c') - bc.barLabels.fontName = 'Helvetica' - bc.barLabels.fontSize = 6 - - drawing.add(bc) - - return drawing - - -def sampleV3(): - "Faked horizontal bar chart using a vertical real one (deprecated)." - - names = ("UK Equities", "US Equities", "European Equities", "Japanese Equities", - "Pacific (ex Japan) Equities", "Emerging Markets Equities", - "UK Bonds", "Overseas Bonds", "UK Index-Linked", "Cash") - - series1 = (-1.5, 0.3, 0.5, 1.0, 0.8, 0.7, 0.4, 0.1, 1.0, 0.3) - series2 = (0.0, 0.33, 0.55, 1.1, 0.88, 0.77, 0.44, 0.11, 1.10, 0.33) - - assert len(names) == len(series1), "bad data" - assert len(names) == len(series2), "bad data" - - drawing = Drawing(400, 200) - - bc = VerticalBarChart() - bc.x = 0 - bc.y = 0 - bc.height = 100 - bc.width = 150 - bc.data = (series1,) - bc.bars.fillColor = colors.green - - bc.barLabelFormat = '%0.2f' - bc.barLabels.dx = 0 - bc.barLabels.dy = 0 - bc.barLabels.boxAnchor = 'w' # irrelevant (becomes 'c') - bc.barLabels.angle = 90 - bc.barLabels.fontName = 'Helvetica' - bc.barLabels.fontSize = 6 - bc.barLabels.nudge = 10 - - bc.valueAxis.visible = 0 - bc.valueAxis.valueMin = -2 - bc.valueAxis.valueMax = +2 - bc.valueAxis.valueStep = 1 - - bc.categoryAxis.tickUp = 0 - bc.categoryAxis.tickDown = 0 - bc.categoryAxis.categoryNames = names - bc.categoryAxis.labels.angle = 90 - bc.categoryAxis.labels.boxAnchor = 'w' - bc.categoryAxis.labels.dx = 0 - bc.categoryAxis.labels.dy = -125 - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 6 - - g = Group(bc) - g.translate(100, 175) - g.rotate(-90) - - drawing.add(g) - - return drawing - - -def sampleV4a(): - "A bar chart showing value axis region starting at *exactly* zero." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - 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 = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV4b(): - "A bar chart showing value axis region starting *below* zero." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - - bc.strokeColor = colors.black - - bc.valueAxis.valueMin = -10 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV4c(): - "A bar chart showing value axis region staring *above* zero." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - - bc.strokeColor = colors.black - - bc.valueAxis.valueMin = 10 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV4d(): - "A bar chart showing value axis region entirely *below* zero." - - drawing = Drawing(400, 200) - - data = [(-13, -20)] - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - - bc.strokeColor = colors.black - - bc.valueAxis.valueMin = -30 - bc.valueAxis.valueMax = -10 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -### - -##dataSample5 = [(10, 20), (20, 30), (30, 40), (40, 50), (50, 60)] -##dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30), (50, 20)] -dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30)] - -def sampleV5a(): - "A simple bar chart with no expressed spacing attributes." - - drawing = Drawing(400, 200) - - data = dataSample5 - - 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 = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV5b(): - "A simple bar chart with proportional spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 0 - bc.barWidth = 40 - bc.groupSpacing = 20 - bc.barSpacing = 10 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV5c1(): - "Make sampe simple bar chart but with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 40 - bc.groupSpacing = 0 - bc.barSpacing = 0 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV5c2(): - "Make sampe simple bar chart but with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 40 - bc.groupSpacing = 20 - bc.barSpacing = 0 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV5c3(): - "Make sampe simple bar chart but with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 40 - bc.groupSpacing = 0 - bc.barSpacing = 10 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleV5c4(): - "Make sampe simple bar chart but with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 40 - bc.groupSpacing = 20 - bc.barSpacing = 10 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'n' - bc.categoryAxis.labels.dy = -5 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -# Horizontal samples - -def sampleH0a(): - "Make a slightly pathologic bar chart with only TWO data items." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - bc = HorizontalBarChart() - 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 = 'se' - bc.categoryAxis.labels.angle = 30 - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH0b(): - "Make a pathologic bar chart with only ONE data item." - - drawing = Drawing(400, 200) - - data = [(42,)] - - bc = HorizontalBarChart() - 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 = 50 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'se' - bc.categoryAxis.labels.angle = 30 - bc.categoryAxis.categoryNames = ['Jan-99'] - - drawing.add(bc) - - return drawing - - -def sampleH0c(): - "Make a really pathologic bar chart with NO data items at all!" - - drawing = Drawing(400, 200) - - data = [()] - - bc = HorizontalBarChart() - 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 = 'se' - bc.categoryAxis.labels.angle = 30 - bc.categoryAxis.categoryNames = [] - - drawing.add(bc) - - return drawing - - -def sampleH1(): - "Sample of multi-series bar chart." - - drawing = Drawing(400, 200) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (14, 6, 21, 23, 38, 46, 20, 5) - ] - - bc = HorizontalBarChart() - 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 = 'e' - 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, 'barchart') - - return drawing - - -def sampleH2a(): - "Sample of multi-series bar chart." - - data = [(2.4, -5.7, 2, 5, 9.2), - (0.6, -4.9, -3, 4, 6.8) - ] - - labels = ("Q3 2000", "Year to Date", "12 months", - "Annualised\n3 years", "Since 07.10.99") - - drawing = Drawing(400, 200) - - bc = HorizontalBarChart() - bc.x = 80 - bc.y = 50 - bc.height = 120 - bc.width = 300 - bc.data = data - - bc.barSpacing = 0 - bc.groupSpacing = 10 - bc.barWidth = 10 - - bc.valueAxis.valueMin = -15 - bc.valueAxis.valueMax = +15 - bc.valueAxis.valueStep = 5 - bc.valueAxis.labels.fontName = 'Helvetica' - bc.valueAxis.labels.fontSize = 8 - bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c') - bc.valueAxis.labels.textAnchor = 'middle' - bc.valueAxis.configure(bc.data) - - bc.categoryAxis.categoryNames = labels - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 8 - bc.categoryAxis.labels.dx = -150 - - drawing.add(bc) - - return drawing - - -def sampleH2b(): - "Sample of multi-series bar chart." - - data = [(2.4, -5.7, 2, 5, 9.2), - (0.6, -4.9, -3, 4, 6.8) - ] - - labels = ("Q3 2000", "Year to Date", "12 months", - "Annualised\n3 years", "Since 07.10.99") - - drawing = Drawing(400, 200) - - bc = HorizontalBarChart() - bc.x = 80 - bc.y = 50 - bc.height = 120 - bc.width = 300 - bc.data = data - - bc.barSpacing = 5 - bc.groupSpacing = 10 - bc.barWidth = 10 - - bc.valueAxis.valueMin = -15 - bc.valueAxis.valueMax = +15 - bc.valueAxis.valueStep = 5 - bc.valueAxis.labels.fontName = 'Helvetica' - bc.valueAxis.labels.fontSize = 8 - bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c') - bc.valueAxis.labels.textAnchor = 'middle' - - bc.categoryAxis.categoryNames = labels - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 8 - bc.categoryAxis.labels.dx = -150 - - drawing.add(bc) - - return drawing - - -def sampleH2c(): - "Sample of multi-series bar chart." - - data = [(2.4, -5.7, 2, 5, 9.99), - (0.6, -4.9, -3, 4, 9.99) - ] - - labels = ("Q3 2000", "Year to Date", "12 months", - "Annualised\n3 years", "Since 07.10.99") - - drawing = Drawing(400, 200) - - bc = HorizontalBarChart() - bc.x = 80 - bc.y = 50 - bc.height = 120 - bc.width = 300 - bc.data = data - - bc.barSpacing = 2 - bc.groupSpacing = 10 - bc.barWidth = 10 - - bc.valueAxis.valueMin = -15 - bc.valueAxis.valueMax = +15 - bc.valueAxis.valueStep = 5 - bc.valueAxis.labels.fontName = 'Helvetica' - bc.valueAxis.labels.fontSize = 8 - bc.valueAxis.labels.boxAnchor = 'n' - bc.valueAxis.labels.textAnchor = 'middle' - - bc.categoryAxis.categoryNames = labels - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 8 - bc.categoryAxis.labels.dx = -150 - - bc.barLabels.nudge = 10 - - bc.barLabelFormat = '%0.2f' - bc.barLabels.dx = 0 - bc.barLabels.dy = 0 - bc.barLabels.boxAnchor = 'n' # irrelevant (becomes 'c') - bc.barLabels.fontName = 'Helvetica' - bc.barLabels.fontSize = 6 - - drawing.add(bc) - - return drawing - - -def sampleH3(): - "A really horizontal bar chart (compared to the equivalent faked one)." - - names = ("UK Equities", "US Equities", "European Equities", "Japanese Equities", - "Pacific (ex Japan) Equities", "Emerging Markets Equities", - "UK Bonds", "Overseas Bonds", "UK Index-Linked", "Cash") - - series1 = (-1.5, 0.3, 0.5, 1.0, 0.8, 0.7, 0.4, 0.1, 1.0, 0.3) - series2 = (0.0, 0.33, 0.55, 1.1, 0.88, 0.77, 0.44, 0.11, 1.10, 0.33) - - assert len(names) == len(series1), "bad data" - assert len(names) == len(series2), "bad data" - - drawing = Drawing(400, 200) - - bc = HorizontalBarChart() - bc.x = 100 - bc.y = 20 - bc.height = 150 - bc.width = 250 - bc.data = (series1,) - bc.bars.fillColor = colors.green - - bc.barLabelFormat = '%0.2f' - bc.barLabels.dx = 0 - bc.barLabels.dy = 0 - bc.barLabels.boxAnchor = 'w' # irrelevant (becomes 'c') - bc.barLabels.fontName = 'Helvetica' - bc.barLabels.fontSize = 6 - bc.barLabels.nudge = 10 - - bc.valueAxis.visible = 0 - bc.valueAxis.valueMin = -2 - bc.valueAxis.valueMax = +2 - bc.valueAxis.valueStep = 1 - - bc.categoryAxis.tickLeft = 0 - bc.categoryAxis.tickRight = 0 - bc.categoryAxis.categoryNames = names - bc.categoryAxis.labels.boxAnchor = 'w' - bc.categoryAxis.labels.dx = -170 - bc.categoryAxis.labels.fontName = 'Helvetica' - bc.categoryAxis.labels.fontSize = 6 - - g = Group(bc) - drawing.add(g) - - return drawing - - -def sampleH4a(): - "A bar chart showing value axis region starting at *exactly* zero." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - bc = HorizontalBarChart() - 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 = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH4b(): - "A bar chart showing value axis region starting *below* zero." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - - bc.strokeColor = colors.black - - bc.valueAxis.valueMin = -10 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH4c(): - "A bar chart showing value axis region starting *above* zero." - - drawing = Drawing(400, 200) - - data = [(13, 20)] - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - - bc.strokeColor = colors.black - - bc.valueAxis.valueMin = 10 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH4d(): - "A bar chart showing value axis region entirely *below* zero." - - drawing = Drawing(400, 200) - - data = [(-13, -20)] - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - - bc.strokeColor = colors.black - - bc.valueAxis.valueMin = -30 - bc.valueAxis.valueMax = -10 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30)] - -def sampleH5a(): - "A simple bar chart with no expressed spacing attributes." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = HorizontalBarChart() - 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 = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH5b(): - "A simple bar chart with proportional spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 0 - bc.barWidth = 40 - bc.groupSpacing = 20 - bc.barSpacing = 10 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH5c1(): - "A simple bar chart with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 10 - bc.groupSpacing = 0 - bc.barSpacing = 0 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH5c2(): - "Simple bar chart with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 10 - bc.groupSpacing = 20 - bc.barSpacing = 0 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH5c3(): - "Simple bar chart with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 20 - bc.height = 155 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 10 - bc.groupSpacing = 0 - bc.barSpacing = 2 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - - -def sampleH5c4(): - "Simple bar chart with absolute spacing." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 10 - bc.groupSpacing = 20 - bc.barSpacing = 10 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - drawing.add(bc) - - return drawing - -def sampleSymbol1(): - "Simple bar chart using symbol attribute." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.barWidth = 10 - bc.groupSpacing = 15 - bc.barSpacing = 3 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - from reportlab.graphics.widgets.grids import ShadedRect - sym1 = ShadedRect() - sym1.fillColorStart = colors.black - sym1.fillColorEnd = colors.blue - sym1.orientation = 'horizontal' - sym1.strokeWidth = 0 - - sym2 = ShadedRect() - sym2.fillColorStart = colors.black - sym2.fillColorEnd = colors.pink - sym2.orientation = 'horizontal' - sym2.strokeWidth = 0 - - sym3 = ShadedRect() - sym3.fillColorStart = colors.blue - sym3.fillColorEnd = colors.white - sym3.orientation = 'vertical' - sym3.cylinderMode = 1 - sym3.strokeWidth = 0 - - bc.bars.symbol = sym1 - bc.bars[2].symbol = sym2 - bc.bars[3].symbol = sym3 - - drawing.add(bc) - - return drawing - -def sampleStacked1(): - "Simple bar chart using symbol attribute." - - drawing = Drawing(400, 200) - - data = dataSample5 - - bc = VerticalBarChart() - bc.categoryAxis.style = 'stacked' - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = data - bc.strokeColor = colors.black - - bc.barWidth = 10 - bc.groupSpacing = 15 - bc.valueAxis.valueMin = 0 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - from reportlab.graphics.widgets.grids import ShadedRect - bc.bars.symbol = ShadedRect() - bc.bars.symbol.fillColorStart = colors.red - bc.bars.symbol.fillColorEnd = colors.white - bc.bars.symbol.orientation = 'vertical' - bc.bars.symbol.cylinderMode = 1 - bc.bars.symbol.strokeWidth = 0 - - bc.bars[1].symbol = ShadedRect() - bc.bars[1].symbol.fillColorStart = colors.magenta - bc.bars[1].symbol.fillColorEnd = colors.white - bc.bars[1].symbol.orientation = 'vertical' - bc.bars[1].symbol.cylinderMode = 1 - bc.bars[1].symbol.strokeWidth = 0 - - bc.bars[2].symbol = ShadedRect() - bc.bars[2].symbol.fillColorStart = colors.green - bc.bars[2].symbol.fillColorEnd = colors.white - bc.bars[2].symbol.orientation = 'vertical' - bc.bars[2].symbol.cylinderMode = 1 - bc.bars[2].symbol.strokeWidth = 0 - - bc.bars[3].symbol = ShadedRect() - bc.bars[3].symbol.fillColorStart = colors.blue - bc.bars[3].symbol.fillColorEnd = colors.white - bc.bars[3].symbol.orientation = 'vertical' - bc.bars[3].symbol.cylinderMode = 1 - bc.bars[3].symbol.strokeWidth = 0 - - drawing.add(bc) - - return drawing - -#class version of function sampleH5c4 above -class SampleH5c4(Drawing): - "Simple bar chart with absolute spacing." - - def __init__(self,width=400,height=200,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - bc = HorizontalBarChart() - bc.x = 50 - bc.y = 50 - bc.height = 125 - bc.width = 300 - bc.data = dataSample5 - bc.strokeColor = colors.black - - bc.useAbsolute = 1 - bc.barWidth = 10 - bc.groupSpacing = 20 - bc.barSpacing = 10 - - bc.valueAxis.valueMin = 0 - bc.valueAxis.valueMax = 60 - bc.valueAxis.valueStep = 15 - - bc.categoryAxis.labels.boxAnchor = 'e' - bc.categoryAxis.categoryNames = ['Ying', 'Yang'] - - self.add(bc,name='HBC') diff --git a/bin/reportlab/graphics/charts/dotbox.py b/bin/reportlab/graphics/charts/dotbox.py deleted file mode 100644 index 6962d278822..00000000000 --- a/bin/reportlab/graphics/charts/dotbox.py +++ /dev/null @@ -1,165 +0,0 @@ -from reportlab.lib.colors import blue, _PCMYK_black -from reportlab.graphics.charts.textlabels import Label -from reportlab.graphics.shapes import Circle, Drawing, Group, Line, Rect, String -from reportlab.graphics.widgetbase import Widget -from reportlab.lib.attrmap import * -from reportlab.lib.validators import * -from reportlab.lib.units import cm -from reportlab.pdfbase.pdfmetrics import getFont -from reportlab.graphics.charts.lineplots import _maxWidth - -class DotBox(Widget): - """Returns a dotbox widget.""" - - #Doesn't use TypedPropertyCollection for labels - this can be a later improvement - _attrMap = AttrMap( - xlabels = AttrMapValue(isNoneOrListOfNoneOrStrings, - desc="List of text labels for boxes on left hand side"), - ylabels = AttrMapValue(isNoneOrListOfNoneOrStrings, - desc="Text label for second box on left hand side"), - labelFontName = AttrMapValue(isString, - desc="Name of font used for the labels"), - labelFontSize = AttrMapValue(isNumber, - desc="Size of font used for the labels"), - labelOffset = AttrMapValue(isNumber, - desc="Space between label text and grid edge"), - strokeWidth = AttrMapValue(isNumber, - desc='Width of the grid and dot outline'), - gridDivWidth = AttrMapValue(isNumber, - desc="Width of each 'box'"), - gridColor = AttrMapValue(isColor, - desc='Colour for the box and gridding'), - dotDiameter = AttrMapValue(isNumber, - desc="Diameter of the circle used for the 'dot'"), - dotColor = AttrMapValue(isColor, - desc='Colour of the circle on the box'), - dotXPosition = AttrMapValue(isNumber, - desc='X Position of the circle'), - dotYPosition = AttrMapValue(isNumber, - desc='X Position of the circle'), - x = AttrMapValue(isNumber, - desc='X Position of dotbox'), - y = AttrMapValue(isNumber, - desc='Y Position of dotbox'), - ) - - def __init__(self): - self.xlabels=["Value", "Blend", "Growth"] - self.ylabels=["Small", "Medium", "Large"] - self.labelFontName = "Helvetica" - self.labelFontSize = 6 - self.labelOffset = 5 - self.strokeWidth = 0.5 - self.gridDivWidth=0.5*cm - self.gridColor=colors.Color(25/255.0,77/255.0,135/255.0) - self.dotDiameter=0.4*cm - self.dotColor=colors.Color(232/255.0,224/255.0,119/255.0) - self.dotXPosition = 1 - self.dotYPosition = 1 - self.x = 30 - self.y = 5 - - - def _getDrawingDimensions(self): - leftPadding=rightPadding=topPadding=bottomPadding=5 - #find width of grid - tx=len(self.xlabels)*self.gridDivWidth - #add padding (and offset) - tx=tx+leftPadding+rightPadding+self.labelOffset - #add in maximum width of text - tx=tx+_maxWidth(self.xlabels, self.labelFontName, self.labelFontSize) - #find height of grid - ty=len(self.ylabels)*self.gridDivWidth - #add padding (and offset) - ty=ty+topPadding+bottomPadding+self.labelOffset - #add in maximum width of text - ty=ty+_maxWidth(self.ylabels, self.labelFontName, self.labelFontSize) - #print (tx, ty) - return (tx,ty) - - def demo(self,drawing=None): - if not drawing: - tx,ty=self._getDrawingDimensions() - drawing = Drawing(tx,ty) - drawing.add(self.draw()) - return drawing - - def draw(self): - g = Group() - - #box - g.add(Rect(self.x,self.y,len(self.xlabels)*self.gridDivWidth,len(self.ylabels)*self.gridDivWidth, - strokeColor=self.gridColor, - strokeWidth=self.strokeWidth, - fillColor=None)) - - #internal gridding - for f in range (1,len(self.ylabels)): - #horizontal - g.add(Line(strokeColor=self.gridColor, - strokeWidth=self.strokeWidth, - x1 = self.x, - y1 = self.y+f*self.gridDivWidth, - x2 = self.x+len(self.xlabels)*self.gridDivWidth, - y2 = self.y+f*self.gridDivWidth)) - for f in range (1,len(self.xlabels)): - #vertical - g.add(Line(strokeColor=self.gridColor, - strokeWidth=self.strokeWidth, - x1 = self.x+f*self.gridDivWidth, - y1 = self.y, - x2 = self.x+f*self.gridDivWidth, - y2 = self.y+len(self.ylabels)*self.gridDivWidth)) - - # draw the 'dot' - g.add(Circle(strokeColor=self.gridColor, - strokeWidth=self.strokeWidth, - fillColor=self.dotColor, - cx = self.x+(self.dotXPosition*self.gridDivWidth), - cy = self.y+(self.dotYPosition*self.gridDivWidth), - r = self.dotDiameter/2.0)) - - #used for centering y-labels (below) - ascent=getFont(self.labelFontName).face.ascent - if ascent==0: - ascent=0.718 # default (from helvetica) - ascent=ascent*self.labelFontSize # normalize - - #do y-labels - if self.ylabels != None: - for f in range (len(self.ylabels)-1,-1,-1): - if self.ylabels[f]!= None: - g.add(String(strokeColor=self.gridColor, - text = self.ylabels[f], - fontName = self.labelFontName, - fontSize = self.labelFontSize, - fillColor=_PCMYK_black, - x = self.x-self.labelOffset, - y = self.y+(f*self.gridDivWidth+(self.gridDivWidth-ascent)/2.0), - textAnchor = 'end')) - - #do x-labels - if self.xlabels != None: - for f in range (0,len(self.xlabels)): - if self.xlabels[f]!= None: - l=Label() - l.x=self.x+(f*self.gridDivWidth)+(self.gridDivWidth+ascent)/2.0 - l.y=self.y+(len(self.ylabels)*self.gridDivWidth)+self.labelOffset - l.angle=90 - l.textAnchor='start' - l.fontName = self.labelFontName - l.fontSize = self.labelFontSize - l.fillColor = _PCMYK_black - l.setText(self.xlabels[f]) - l.boxAnchor = 'sw' - l.draw() - g.add(l) - - return g - - - - -if __name__ == "__main__": - d = DotBox() - d.demo().save(fnRoot="dotbox") \ No newline at end of file diff --git a/bin/reportlab/graphics/charts/doughnut.py b/bin/reportlab/graphics/charts/doughnut.py deleted file mode 100644 index e8829b29996..00000000000 --- a/bin/reportlab/graphics/charts/doughnut.py +++ /dev/null @@ -1,372 +0,0 @@ -#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/graphics/charts/doughnut.py -# doughnut chart - -"""Doughnut chart - -Produces a circular chart like the doughnut charts produced by Excel. -Can handle multiple series (which produce concentric 'rings' in the chart). - -""" -__version__=''' $Id$ ''' - -import copy -from math import sin, cos, pi -from types import ListType, TupleType -from reportlab.lib import colors -from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\ - isListOfNumbers, isColorOrNone, isString,\ - isListOfStringsOrNone, OneOf, SequenceOf,\ - isBoolean, isListOfColors,\ - isNoneOrListOfNoneOrStrings,\ - isNoneOrListOfNoneOrNumbers,\ - isNumberOrNone -from reportlab.lib.attrmap import * -from reportlab.pdfgen.canvas import Canvas -from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, Ellipse, \ - Wedge, String, SolidShape, UserNode, STATE_DEFAULTS -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder -from reportlab.graphics.charts.areas import PlotArea -from reportlab.graphics.charts.textlabels import Label -from reportlab.graphics.widgets.markers import Marker - -class SectorProperties(PropHolder): - """This holds descriptive information about the sectors in a doughnut chart. - - It is not to be confused with the 'sector itself'; this just holds - a recipe for how to format one, and does not allow you to hack the - angles. It can format a genuine Sector object for you with its - format method. - """ - - _attrMap = AttrMap( - strokeWidth = AttrMapValue(isNumber), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone), - popout = AttrMapValue(isNumber), - fontName = AttrMapValue(isString), - fontSize = AttrMapValue(isNumber), - fontColor = AttrMapValue(isColorOrNone), - labelRadius = AttrMapValue(isNumber), - ) - - def __init__(self): - self.strokeWidth = 0 - self.fillColor = None - self.strokeColor = STATE_DEFAULTS["strokeColor"] - self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] - self.popout = 0 - self.fontName = STATE_DEFAULTS["fontName"] - self.fontSize = STATE_DEFAULTS["fontSize"] - self.fontColor = STATE_DEFAULTS["fillColor"] - self.labelRadius = 1.2 - - -class Doughnut(Widget): - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc='X position of the chart within its container.'), - y = AttrMapValue(isNumber, desc='Y position of the chart within its container.'), - width = AttrMapValue(isNumber, desc='width of doughnut bounding box. Need not be same as width.'), - height = AttrMapValue(isNumber, desc='height of doughnut bounding box. Need not be same as height.'), - data = AttrMapValue(None, desc='list of numbers defining sector sizes; need not sum to 1'), - labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"), - startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"), - direction = AttrMapValue(OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"), - slices = AttrMapValue(None, desc="collection of sector descriptor objects"), - ) - - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 100 - self.height = 100 - self.data = [1,1] - self.labels = None # or list of strings - self.startAngle = 90 - self.direction = "clockwise" - - self.slices = TypedPropertyCollection(SectorProperties) - self.slices[0].fillColor = colors.darkcyan - self.slices[1].fillColor = colors.blueviolet - self.slices[2].fillColor = colors.blue - self.slices[3].fillColor = colors.cyan - - def demo(self): - d = Drawing(200, 100) - - dn = Doughnut() - dn.x = 50 - dn.y = 10 - dn.width = 100 - dn.height = 80 - dn.data = [10,20,30,40,50,60] - dn.labels = ['a','b','c','d','e','f'] - - dn.slices.strokeWidth=0.5 - dn.slices[3].popout = 10 - dn.slices[3].strokeWidth = 2 - dn.slices[3].strokeDashArray = [2,2] - dn.slices[3].labelRadius = 1.75 - dn.slices[3].fontColor = colors.red - dn.slices[0].fillColor = colors.darkcyan - dn.slices[1].fillColor = colors.blueviolet - dn.slices[2].fillColor = colors.blue - dn.slices[3].fillColor = colors.cyan - dn.slices[4].fillColor = colors.aquamarine - dn.slices[5].fillColor = colors.cadetblue - dn.slices[6].fillColor = colors.lightcoral - - d.add(dn) - return d - - def normalizeData(self, data=None): - from operator import add - sum = float(reduce(add,data,0)) - return abs(sum)>=1e-8 and map(lambda x,f=360./sum: f*x, data) or len(data)*[0] - - def makeSectors(self): - # normalize slice data - if type(self.data) in (ListType, TupleType) and type(self.data[0]) in (ListType, TupleType): - #it's a nested list, more than one sequence - normData = [] - n = [] - for l in self.data: - t = self.normalizeData(l) - normData.append(t) - n.append(len(t)) - else: - normData = self.normalizeData(self.data) - n = len(normData) - - #labels - if self.labels is None: - labels = [] - if type(n) not in (ListType,TupleType): - labels = [''] * n - else: - for m in n: - labels = list(labels) + [''] * m - else: - labels = self.labels - #there's no point in raising errors for less than enough errors if - #we silently create all for the extreme case of no labels. - if type(n) not in (ListType,TupleType): - i = n-len(labels) - if i>0: - labels = list(labels) + [''] * i - else: - tlab = 0 - for m in n: - tlab = tlab+m - i = tlab-len(labels) - if i>0: - labels = list(labels) + [''] * i - - xradius = self.width/2.0 - yradius = self.height/2.0 - centerx = self.x + xradius - centery = self.y + yradius - - if self.direction == "anticlockwise": - whichWay = 1 - else: - whichWay = -1 - - g = Group() - i = 0 - sn = 0 - - startAngle = self.startAngle #% 360 - if type(self.data[0]) in (ListType, TupleType): - #multi-series doughnut - styleCount = len(self.slices) - iradius = (self.height/5.0)/len(self.data) - for series in normData: - for angle in series: - endAngle = (startAngle + (angle * whichWay)) #% 360 - if abs(startAngle-endAngle)>=1e-5: - if startAngle < endAngle: - a1 = startAngle - a2 = endAngle - else: - a1 = endAngle - a2 = startAngle - - #if we didn't use %stylecount here we'd end up with the later sectors - #all having the default style - sectorStyle = self.slices[i%styleCount] - - # is it a popout? - cx, cy = centerx, centery - if sectorStyle.popout != 0: - # pop out the sector - averageAngle = (a1+a2)/2.0 - aveAngleRadians = averageAngle * pi/180.0 - popdistance = sectorStyle.popout - cx = centerx + popdistance * cos(aveAngleRadians) - cy = centery + popdistance * sin(aveAngleRadians) - - if type(n) in (ListType,TupleType): - theSector = Wedge(cx, cy, xradius+(sn*iradius)-iradius, a1, a2, yradius=yradius+(sn*iradius)-iradius, radius1=yradius+(sn*iradius)-(2*iradius)) - else: - theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=iradius) - - theSector.fillColor = sectorStyle.fillColor - theSector.strokeColor = sectorStyle.strokeColor - theSector.strokeWidth = sectorStyle.strokeWidth - theSector.strokeDashArray = sectorStyle.strokeDashArray - - g.add(theSector) - startAngle = endAngle - - if labels[i] != "": - averageAngle = (a1+a2)/2.0 - aveAngleRadians = averageAngle*pi/180.0 - labelRadius = sectorStyle.labelRadius - labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius) - labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius) - - theLabel = String(labelX, labelY, labels[i]) - theLabel.textAnchor = "middle" - theLabel.fontSize = sectorStyle.fontSize - theLabel.fontName = sectorStyle.fontName - theLabel.fillColor = sectorStyle.fontColor - g.add(theLabel) - i = i + 1 - sn = sn + 1 - - else: - #single series doughnut - styleCount = len(self.slices) - iradius = self.height/5.0 - for angle in normData: - endAngle = (startAngle + (angle * whichWay)) #% 360 - if abs(startAngle-endAngle)>=1e-5: - if startAngle < endAngle: - a1 = startAngle - a2 = endAngle - else: - a1 = endAngle - a2 = startAngle - - #if we didn't use %stylecount here we'd end up with the later sectors - #all having the default style - sectorStyle = self.slices[i%styleCount] - - # is it a popout? - cx, cy = centerx, centery - if sectorStyle.popout != 0: - # pop out the sector - averageAngle = (a1+a2)/2.0 - aveAngleRadians = averageAngle * pi/180.0 - popdistance = sectorStyle.popout - cx = centerx + popdistance * cos(aveAngleRadians) - cy = centery + popdistance * sin(aveAngleRadians) - - if n > 1: - theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, radius1=iradius) - elif n==1: - theSector = Wedge(cx, cy, xradius, a1, a2, yradius=yradius, iradius=iradius) - - theSector.fillColor = sectorStyle.fillColor - theSector.strokeColor = sectorStyle.strokeColor - theSector.strokeWidth = sectorStyle.strokeWidth - theSector.strokeDashArray = sectorStyle.strokeDashArray - - g.add(theSector) - - # now draw a label - if labels[i] != "": - averageAngle = (a1+a2)/2.0 - aveAngleRadians = averageAngle*pi/180.0 - labelRadius = sectorStyle.labelRadius - labelX = centerx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius) - labelY = centery + (0.5 * self.height * sin(aveAngleRadians) * labelRadius) - - theLabel = String(labelX, labelY, labels[i]) - theLabel.textAnchor = "middle" - theLabel.fontSize = sectorStyle.fontSize - theLabel.fontName = sectorStyle.fontName - theLabel.fillColor = sectorStyle.fontColor - - g.add(theLabel) - - startAngle = endAngle - i = i + 1 - - return g - - def draw(self): - g = Group() - g.add(self.makeSectors()) - return g - - -def sample1(): - "Make up something from the individual Sectors" - - d = Drawing(400, 400) - g = Group() - - s1 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=0, endangledegrees=120, radius1=100) - s1.fillColor=colors.red - s1.strokeColor=None - d.add(s1) - s2 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=120, endangledegrees=240, radius1=100) - s2.fillColor=colors.green - s2.strokeColor=None - d.add(s2) - s3 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=240, endangledegrees=260, radius1=100) - s3.fillColor=colors.blue - s3.strokeColor=None - d.add(s3) - s4 = Wedge(centerx=200, centery=200, radius=150, startangledegrees=260, endangledegrees=360, radius1=100) - s4.fillColor=colors.gray - s4.strokeColor=None - d.add(s4) - - return d - -def sample2(): - "Make a simple demo" - - d = Drawing(400, 400) - - dn = Doughnut() - dn.x = 50 - dn.y = 50 - dn.width = 300 - dn.height = 300 - dn.data = [10,20,30,40,50,60] - - d.add(dn) - - return d - -def sample3(): - "Make a more complex demo" - - d = Drawing(400, 400) - dn = Doughnut() - dn.x = 50 - dn.y = 50 - dn.width = 300 - dn.height = 300 - dn.data = [[10,20,30,40,50,60], [10,20,30,40]] - dn.labels = ['a','b','c','d','e','f'] - - d.add(dn) - - return d - -if __name__=='__main__': - - from reportlab.graphics.renderPDF import drawToFile - d = sample1() - drawToFile(d, 'doughnut1.pdf') - d = sample2() - drawToFile(d, 'doughnut2.pdf') - d = sample3() - drawToFile(d, 'doughnut3.pdf') diff --git a/bin/reportlab/graphics/charts/legends.py b/bin/reportlab/graphics/charts/legends.py deleted file mode 100644 index f991bd7ed7c..00000000000 --- a/bin/reportlab/graphics/charts/legends.py +++ /dev/null @@ -1,486 +0,0 @@ -#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/graphics/charts/legends.py -"""This will be a collection of legends to be used with charts. -""" -__version__=''' $Id$ ''' - -import string, copy - -from reportlab.lib import colors -from reportlab.lib.validators import isNumber, OneOf, isString, isColorOrNone, isNumberOrNone, isListOfNumbersOrNone -from reportlab.lib.attrmap import * -from reportlab.pdfbase.pdfmetrics import stringWidth, getFont -from reportlab.graphics.widgetbase import Widget -from reportlab.graphics.shapes import Drawing, Group, String, Rect, Line, STATE_DEFAULTS - - -class Legend(Widget): - """A simple legend containing rectangular swatches and strings. - - The swatches are filled rectangles whenever the respective - color object in 'colorNamePairs' is a subclass of Color in - reportlab.lib.colors. Otherwise the object passed instead is - assumed to have 'x', 'y', 'width' and 'height' attributes. - A legend then tries to set them or catches any error. This - lets you plug-in any widget you like as a replacement for - the default rectangular swatches. - - Strings can be nicely aligned left or right to the swatches. - """ - - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc="x-coordinate of upper-left reference point"), - y = AttrMapValue(isNumber, desc="y-coordinate of upper-left reference point"), - deltax = AttrMapValue(isNumberOrNone, desc="x-distance between neighbouring swatches"), - deltay = AttrMapValue(isNumberOrNone, desc="y-distance between neighbouring swatches"), - dxTextSpace = AttrMapValue(isNumber, desc="Distance between swatch rectangle and text"), - autoXPadding = AttrMapValue(isNumber, desc="x Padding between columns if deltax=None"), - autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"), - dx = AttrMapValue(isNumber, desc="Width of swatch rectangle"), - dy = AttrMapValue(isNumber, desc="Height of swatch rectangle"), - columnMaximum = AttrMapValue(isNumber, desc="Max. number of items per column"), - alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text with respect to swatches"), - colorNamePairs = AttrMapValue(None, desc="List of color/name tuples (color can also be widget)"), - fontName = AttrMapValue(isString, desc="Font name of the strings"), - fontSize = AttrMapValue(isNumber, desc="Font size of the strings"), - fillColor = AttrMapValue(isColorOrNone, desc=""), - strokeColor = AttrMapValue(isColorOrNone, desc="Border color of the swatches"), - strokeWidth = AttrMapValue(isNumber, desc="Width of the border color of the swatches"), - callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"), - ) - - def __init__(self): - # Upper-left reference point. - self.x = 0 - self.y = 0 - - # Alginment of text with respect to swatches. - self.alignment = "left" - - # x- and y-distances between neighbouring swatches. - self.deltax = 75 - self.deltay = 20 - self.autoXPadding = 5 - self.autoYPadding = 2 - - # Size of swatch rectangle. - self.dx = 10 - self.dy = 10 - - # Distance between swatch rectangle and text. - self.dxTextSpace = 10 - - # Max. number of items per column. - self.columnMaximum = 3 - - # Color/name pairs. - self.colorNamePairs = [ (colors.red, "red"), - (colors.blue, "blue"), - (colors.green, "green"), - (colors.pink, "pink"), - (colors.yellow, "yellow") ] - - # Font name and size of the labels. - self.fontName = STATE_DEFAULTS['fontName'] - self.fontSize = STATE_DEFAULTS['fontSize'] - self.fillColor = STATE_DEFAULTS['fillColor'] - self.strokeColor = STATE_DEFAULTS['strokeColor'] - self.strokeWidth = STATE_DEFAULTS['strokeWidth'] - - def _calculateMaxWidth(self, colorNamePairs): - "Calculate the maximum width of some given strings." - m = 0 - for t in map(lambda p:str(p[1]),colorNamePairs): - if t: - for s in string.split(t,'\n'): - m = max(m,stringWidth(s, self.fontName, self.fontSize)) - return m - - - def _calcHeight(self): - deltay = self.deltay - dy = self.dy - thisy = upperlefty = self.y - dy - ascent=getFont(self.fontName).face.ascent/1000. - if ascent==0: ascent=0.718 # default (from helvetica) - leading = self.fontSize*1.2 - columnCount = 0 - count = 0 - lowy = upperlefty - for unused, name in colorNamePairs: - T = string.split(name and str(name) or '','\n') - S = [] - # thisy+dy/2 = y+leading/2 - y = thisy+(dy-ascent)*0.5-leading - newy = thisy-max(deltay,len(S)*leading) - lowy = min(y,newy) - if count == columnMaximum-1: - count = 0 - thisy = upperlefty - columnCount = columnCount + 1 - else: - thisy = newy - count = count+1 - return upperlefty - lowy - - def draw(self): - g = Group() - colorNamePairs = self.colorNamePairs - thisx = upperleftx = self.x - thisy = upperlefty = self.y - self.dy - dx, dy, alignment, columnMaximum = self.dx, self.dy, self.alignment, self.columnMaximum - deltax, deltay, dxTextSpace = self.deltax, self.deltay, self.dxTextSpace - fontName, fontSize, fillColor = self.fontName, self.fontSize, self.fillColor - strokeWidth, strokeColor = self.strokeWidth, self.strokeColor - leading = fontSize*1.2 - if not deltay: - deltay = max(dy,leading)+self.autoYPadding - if not deltax: - maxWidth = self._calculateMaxWidth(colorNamePairs) - deltax = maxWidth+dx+dxTextSpace+self.autoXPadding - else: - if alignment=='left': maxWidth = self._calculateMaxWidth(colorNamePairs) - - def gAdd(t,g=g,fontName=fontName,fontSize=fontSize,fillColor=fillColor): - t.fontName = fontName - t.fontSize = fontSize - t.fillColor = fillColor - return g.add(t) - - ascent=getFont(fontName).face.ascent/1000. - if ascent==0: ascent=0.718 # default (from helvetica) - ascent=ascent*fontSize # normalize - - columnCount = 0 - count = 0 - callout = getattr(self,'callout',None) - for col, name in colorNamePairs: - T = string.split(name and str(name) or '','\n') - S = [] - # thisy+dy/2 = y+leading/2 - y = thisy+(dy-ascent)*0.5 - if callout: callout(self,g,thisx,y,colorNamePairs[count]) - if alignment == "left": - for t in T: - # align text to left - s = String(thisx+maxWidth,y,t) - s.textAnchor = "end" - S.append(s) - y = y-leading - x = thisx+maxWidth+dxTextSpace - elif alignment == "right": - for t in T: - # align text to right - s = String(thisx+dx+dxTextSpace, y, t) - s.textAnchor = "start" - S.append(s) - y = y-leading - x = thisx - else: - raise ValueError, "bad alignment" - - # Make a 'normal' color swatch... - if isinstance(col, colors.Color): - r = Rect(x, thisy, dx, dy) - r.fillColor = col - r.strokeColor = strokeColor - r.strokeWidth = strokeWidth - g.add(r) - else: - #try and see if we should do better. - try: - c = copy.deepcopy(col) - c.x = x - c.y = thisy - c.width = dx - c.height = dy - g.add(c) - except: - pass - - map(gAdd,S) - - if count%columnMaximum == columnMaximum-1: - thisx = thisx+deltax - thisy = upperlefty - columnCount = columnCount + 1 - else: - thisy = thisy-max(deltay,len(S)*leading) - count = count+1 - - return g - - -class LineSwatch(Widget): - """basically a Line with properties added so it can be used in a LineLegend""" - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc="x-coordinate for swatch line start point"), - y = AttrMapValue(isNumber, desc="y-coordinate for swatch line start point"), - width = AttrMapValue(isNumber, desc="length of swatch line"), - height = AttrMapValue(isNumber, desc="used for line strokeWidth"), - strokeColor = AttrMapValue(isColorOrNone, desc="color of swatch line"), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc="dash array for swatch line"), - ) - - def __init__(self): - from reportlab.lib.colors import red - from reportlab.graphics.shapes import Line - self.x = 0 - self.y = 0 - self.width = 20 - self.height = 1 - self.strokeColor = red - self.strokeDashArray = None - - def draw(self): - l = Line(self.x,self.y,self.x+self.width,self.y) - l.strokeColor = self.strokeColor - l.strokeDashArray = self.strokeDashArray - l.strokeWidth = self.height - return l - -class LineLegend(Legend): - """A subclass of Legend for drawing legends with lines as the - swatches rather than rectangles. Useful for lineCharts and - linePlots. Should be similar in all other ways the the standard - Legend class. - """ - - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc="x-coordinate of upper-left reference point"), - y = AttrMapValue(isNumber, desc="y-coordinate of upper-left reference point"), - deltax = AttrMapValue(isNumberOrNone, desc="x-distance between neighbouring line-swatches"), - deltay = AttrMapValue(isNumberOrNone, desc="y-distance between neighbouring line-swatches"), - dxTextSpace = AttrMapValue(isNumber, desc="Distance between line-swatches and text"), - autoXPadding = AttrMapValue(isNumber, desc="x Padding between columns if deltax=None"), - autoYPadding = AttrMapValue(isNumber, desc="y Padding between rows if deltay=None"), - dx = AttrMapValue(isNumber, desc="Width of line-swatch - ie length of the line"), - dy = AttrMapValue(isNumber, desc="Height of line-swatch - ie strokeWidth to be used for the line"), - columnMaximum = AttrMapValue(isNumber, desc="Max. number of items per column"), - alignment = AttrMapValue(OneOf("left", "right"), desc="Alignment of text with respect to line-swatches"), - colorNamePairs = AttrMapValue(None, desc="List of color/name tuples (color can also be widget)"), - fontName = AttrMapValue(isString, desc="Font name of the strings"), - fontSize = AttrMapValue(isNumber, desc="Font size of the strings"), - fillColor = AttrMapValue(isColorOrNone, desc=""), - strokeColor = AttrMapValue(isColorOrNone, desc="Stroke color of the line-swatches"), - strokeWidth = AttrMapValue(isNumber, desc="Width of the line-swatches"), - callout = AttrMapValue(None, desc="a user callout(self,g,x,y,(color,text))"), - ) - - def __init__(self): - Legend.__init__(self) - - # Size of swatch rectangle. - self.dx = 10 #width of line - self.dy = 2 #strokeWidth for line - - # Color/name pairs. - self.colorNamePairs = [] - for col, colName in [ (colors.red, "red"), - (colors.blue, "blue"), - (colors.green, "green"), - (colors.pink, "pink"), - (colors.yellow, "yellow") ]: - l = LineSwatch() - l.strokeColor = col - self.colorNamePairs.append((l, colName)) - - # Font name and size of the labels. - self.fillColor = STATE_DEFAULTS['fillColor'] - self.strokeColor = STATE_DEFAULTS['strokeColor'] - self.strokeWidth = STATE_DEFAULTS['strokeWidth'] - - def draw(self): - g = Group() - colorNamePairs = self.colorNamePairs - thisx = upperleftx = self.x - thisy = upperlefty = self.y - self.dy - dx, dy, alignment, columnMaximum = self.dx, self.dy, self.alignment, self.columnMaximum - deltax, deltay, dxTextSpace = self.deltax, self.deltay, self.dxTextSpace - fontName, fontSize, fillColor = self.fontName, self.fontSize, self.fillColor - strokeWidth, strokeColor = self.strokeWidth, self.strokeColor - leading = fontSize*1.2 - if not deltay: - deltay = max(dy,leading)+self.autoYPadding - if not deltax: - maxWidth = self._calculateMaxWidth(colorNamePairs) - deltax = maxWidth+dx+dxTextSpace+self.autoXPadding - else: - if alignment=='left': maxWidth = self._calculateMaxWidth(colorNamePairs) - - def gAdd(t,g=g,fontName=fontName,fontSize=fontSize,fillColor=fillColor): - t.fontName = fontName - t.fontSize = fontSize - t.fillColor = fillColor - return g.add(t) - - ascent=getFont(fontName).face.ascent/1000. - if ascent==0: ascent=0.718 # default (from helvetica) - ascent=ascent*fontSize # normalize - - columnCount = 0 - count = 0 - callout = getattr(self,'callout',None) - for col, name in colorNamePairs: - T = string.split(name and str(name) or '','\n') - S = [] - # thisy+dy/2 = y+leading/2 - y = thisy+(dy-ascent)*0.5 - if callout: callout(self,g,thisx,y,colorNamePairs[count]) - if alignment == "left": - for t in T: - # align text to left - s = String(thisx+maxWidth,y,t) - s.textAnchor = "end" - S.append(s) - y = y-leading - x = thisx+maxWidth+dxTextSpace - elif alignment == "right": - for t in T: - # align text to right - s = String(thisx+dx+dxTextSpace, y, t) - s.textAnchor = "start" - S.append(s) - y = y-leading - x = thisx - else: - raise ValueError, "bad alignment" - - # Make a 'normal' color line-swatch... - if isinstance(col, colors.Color): - l = LineSwatch() - l.x = x - l.y = thisy - l.width = dx - l.height = dy - l.strokeColor = col - g.add(l) - else: - #try and see if we should do better. - try: - c = copy.deepcopy(col) - c.x = x - c.y = thisy - c.width = dx - c.height = dy - g.add(c) - except: - pass - - map(gAdd,S) - - if count%columnMaximum == columnMaximum-1: - thisx = thisx+deltax - thisy = upperlefty - columnCount = columnCount + 1 - else: - thisy = thisy-max(deltay,len(S)*leading) - count = count+1 - - return g - - - - def demo(self): - "Make sample legend." - - d = Drawing(200, 100) - - legend = Legend() - legend.alignment = 'left' - legend.x = 0 - legend.y = 100 - legend.dxTextSpace = 5 - items = string.split('red green blue yellow pink black white', ' ') - items = map(lambda i:(getattr(colors, i), i), items) - legend.colorNamePairs = items - - d.add(legend, 'legend') - - return d - - -def sample1c(): - "Make sample legend." - - d = Drawing(200, 100) - - legend = Legend() - legend.alignment = 'right' - legend.x = 0 - legend.y = 100 - legend.dxTextSpace = 5 - items = string.split('red green blue yellow pink black white', ' ') - items = map(lambda i:(getattr(colors, i), i), items) - legend.colorNamePairs = items - - d.add(legend, 'legend') - - return d - - -def sample2c(): - "Make sample legend." - - d = Drawing(200, 100) - - legend = Legend() - legend.alignment = 'right' - legend.x = 20 - legend.y = 90 - legend.deltax = 60 - legend.dxTextSpace = 10 - legend.columnMaximum = 4 - items = string.split('red green blue yellow pink black white', ' ') - items = map(lambda i:(getattr(colors, i), i), items) - legend.colorNamePairs = items - - d.add(legend, 'legend') - - return d - -def sample3(): - "Make sample legend with line swatches." - - d = Drawing(200, 100) - - legend = LineLegend() - legend.alignment = 'right' - legend.x = 20 - legend.y = 90 - legend.deltax = 60 - legend.dxTextSpace = 10 - legend.columnMaximum = 4 - items = string.split('red green blue yellow pink black white', ' ') - items = map(lambda i:(getattr(colors, i), i), items) - legend.colorNamePairs = items - d.add(legend, 'legend') - - return d - - -def sample3a(): - "Make sample legend with line swatches and dasharrays on the lines." - - d = Drawing(200, 100) - - legend = LineLegend() - legend.alignment = 'right' - legend.x = 20 - legend.y = 90 - legend.deltax = 60 - legend.dxTextSpace = 10 - legend.columnMaximum = 4 - items = string.split('red green blue yellow pink black white', ' ') - darrays = ([2,1], [2,5], [2,2,5,5], [1,2,3,4], [4,2,3,4], [1,2,3,4,5,6], [1]) - cnp = [] - for i in range(0, len(items)): - l = LineSwatch() - l.strokeColor = getattr(colors, items[i]) - l.strokeDashArray = darrays[i] - cnp.append((l, items[i])) - legend.colorNamePairs = cnp - d.add(legend, 'legend') - - return d diff --git a/bin/reportlab/graphics/charts/linecharts.py b/bin/reportlab/graphics/charts/linecharts.py deleted file mode 100644 index c98c05c05ed..00000000000 --- a/bin/reportlab/graphics/charts/linecharts.py +++ /dev/null @@ -1,656 +0,0 @@ -#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/graphics/charts/linecharts.py -""" -This modules defines a very preliminary Line Chart example. -""" -__version__=''' $Id$ ''' - -import string -from types import FunctionType, StringType - -from reportlab.lib import colors -from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isListOfStrings, \ - isListOfStringsOrNone, SequenceOf, isBoolean, NoneOr, \ - isListOfNumbersOrNone -from reportlab.lib.attrmap import * -from reportlab.lib.formatters import Formatter -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder -from reportlab.graphics.shapes import Line, Rect, Group, Drawing, Polygon, PolyLine -from reportlab.graphics.widgets.signsandsymbols import NoEntry -from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis -from reportlab.graphics.charts.textlabels import Label -from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol, makeMarker -from reportlab.graphics.charts.areas import PlotArea - -class LineChartProperties(PropHolder): - _attrMap = AttrMap( - strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'), - strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line.'), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'), - symbol = AttrMapValue(NoneOr(isSymbol), desc='Widget placed at data points.'), - ) - -class LineChart(PlotArea): - pass - -# This is conceptually similar to the VerticalBarChart. -# Still it is better named HorizontalLineChart... :-/ - -class HorizontalLineChart(LineChart): - """Line chart with multiple lines. - - A line chart is assumed to have one category and one value axis. - Despite its generic name this particular line chart class has - a vertical value axis and a horizontal category one. It may - evolve into individual horizontal and vertical variants (like - with the existing bar charts). - - Available attributes are: - - x: x-position of lower-left chart origin - y: y-position of lower-left chart origin - width: chart width - height: chart height - - useAbsolute: disables auto-scaling of chart elements (?) - lineLabelNudge: distance of data labels to data points - lineLabels: labels associated with data values - lineLabelFormat: format string or callback function - groupSpacing: space between categories - - joinedLines: enables drawing of lines - - strokeColor: color of chart lines (?) - fillColor: color for chart background (?) - lines: style list, used cyclically for data series - - valueAxis: value axis object - categoryAxis: category axis object - categoryNames: category names - - data: chart data, a list of data series of equal length - """ - - _attrMap = AttrMap(BASE=LineChart, - useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.'), - lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.'), - lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'), - lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'), - lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'), - groupSpacing = AttrMapValue(isNumber, desc='? - Likely to disappear.'), - joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'), - lines = AttrMapValue(None, desc='Handle of the lines.'), - valueAxis = AttrMapValue(None, desc='Handle of the value axis.'), - categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'), - categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'), - data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'), - inFill = AttrMapValue(isBoolean, desc='Whether infilling should be done.'), - reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.'), - annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'), - ) - - def __init__(self): - LineChart.__init__(self) - - # Allow for a bounding rectangle. - self.strokeColor = None - self.fillColor = None - - # Named so we have less recoding for the horizontal one :-) - self.categoryAxis = XCategoryAxis() - self.valueAxis = YValueAxis() - - # This defines two series of 3 points. Just an example. - self.data = [(100,110,120,130), - (70, 80, 80, 90)] - self.categoryNames = ('North','South','East','West') - - self.lines = TypedPropertyCollection(LineChartProperties) - self.lines.strokeWidth = 1 - self.lines[0].strokeColor = colors.red - self.lines[1].strokeColor = colors.green - self.lines[2].strokeColor = colors.blue - - # control spacing. if useAbsolute = 1 then - # the next parameters are in points; otherwise - # they are 'proportions' and are normalized to - # fit the available space. - self.useAbsolute = 0 #- not done yet - self.groupSpacing = 1 #5 - - self.lineLabels = TypedPropertyCollection(Label) - self.lineLabelFormat = None - self.lineLabelArray = None - - # This says whether the origin is above or below - # the data point. +10 means put the origin ten points - # above the data point if value > 0, or ten - # points below if data value < 0. This is different - # to label dx/dy which are not dependent on the - # sign of the data. - self.lineLabelNudge = 10 - # If you have multiple series, by default they butt - # together. - - # New line chart attributes. - self.joinedLines = 1 # Connect items with straight lines. - self.inFill = 0 - self.reversePlotOrder = 0 - - - def demo(self): - """Shows basic use of a line chart.""" - - drawing = Drawing(200, 100) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (14, 10, 21, 28, 38, 46, 25, 5) - ] - - lc = HorizontalLineChart() - - lc.x = 20 - lc.y = 10 - lc.height = 85 - lc.width = 170 - lc.data = data - lc.lines.symbol = makeMarker('Circle') - - drawing.add(lc) - - return drawing - - - def calcPositions(self): - """Works out where they go. - - Sets an attribute _positions which is a list of - lists of (x, y) matching the data. - """ - - self._seriesCount = len(self.data) - self._rowLength = max(map(len,self.data)) - - if self.useAbsolute: - # Dimensions are absolute. - normFactor = 1.0 - else: - # Dimensions are normalized to fit. - normWidth = self.groupSpacing - availWidth = self.categoryAxis.scale(0)[1] - normFactor = availWidth / normWidth - - self._positions = [] - for rowNo in range(len(self.data)): - lineRow = [] - for colNo in range(len(self.data[rowNo])): - datum = self.data[rowNo][colNo] - if datum is not None: - (groupX, groupWidth) = self.categoryAxis.scale(colNo) - x = groupX + (0.5 * self.groupSpacing * normFactor) - y = self.valueAxis.scale(0) - height = self.valueAxis.scale(datum) - y - lineRow.append((x, y+height)) - self._positions.append(lineRow) - - - def _innerDrawLabel(self, rowNo, colNo, x, y): - "Draw a label for a given item in the list." - - labelFmt = self.lineLabelFormat - labelValue = self.data[rowNo][colNo] - - if labelFmt is None: - labelText = None - elif type(labelFmt) is StringType: - if labelFmt == 'values': - labelText = self.lineLabelArray[rowNo][colNo] - else: - labelText = labelFmt % labelValue - elif type(labelFmt) is FunctionType: - labelText = labelFmt(labelValue) - elif isinstance(labelFmt, Formatter): - labelText = labelFmt(labelValue) - else: - msg = "Unknown formatter type %s, expected string or function" - raise Exception, msg % labelFmt - - if labelText: - label = self.lineLabels[(rowNo, colNo)] - # Make sure labels are some distance off the data point. - if y > 0: - label.setOrigin(x, y + self.lineLabelNudge) - else: - label.setOrigin(x, y - self.lineLabelNudge) - label.setText(labelText) - else: - label = None - return label - - def drawLabel(self, G, rowNo, colNo, x, y): - '''Draw a label for a given item in the list. - G must have an add method''' - G.add(self._innerDrawLabel(rowNo,colNo,x,y)) - - def makeLines(self): - g = Group() - - labelFmt = self.lineLabelFormat - P = range(len(self._positions)) - if self.reversePlotOrder: P.reverse() - inFill = self.inFill - if inFill: - inFillY = self.categoryAxis._y - inFillX0 = self.valueAxis._x - inFillX1 = inFillX0 + self.categoryAxis._length - inFillG = getattr(self,'_inFillG',g) - - # Iterate over data rows. - for rowNo in P: - row = self._positions[rowNo] - styleCount = len(self.lines) - styleIdx = rowNo % styleCount - rowStyle = self.lines[styleIdx] - rowColor = rowStyle.strokeColor - dash = getattr(rowStyle, 'strokeDashArray', None) - - if hasattr(self.lines[styleIdx], 'strokeWidth'): - strokeWidth = self.lines[styleIdx].strokeWidth - elif hasattr(self.lines, 'strokeWidth'): - strokeWidth = self.lines.strokeWidth - else: - strokeWidth = None - - # Iterate over data columns. - if self.joinedLines: - points = [] - for colNo in range(len(row)): - points += row[colNo] - if inFill: - points = points + [inFillX1,inFillY,inFillX0,inFillY] - inFillG.add(Polygon(points,fillColor=rowColor,strokeColor=rowColor,strokeWidth=0.1)) - else: - line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1) - if strokeWidth: - line.strokeWidth = strokeWidth - if dash: - line.strokeDashArray = dash - g.add(line) - - if hasattr(self.lines[styleIdx], 'symbol'): - uSymbol = self.lines[styleIdx].symbol - elif hasattr(self.lines, 'symbol'): - uSymbol = self.lines.symbol - else: - uSymbol = None - - if uSymbol: - for colNo in range(len(row)): - x1, y1 = row[colNo] - symbol = uSymbol2Symbol(uSymbol,x1,y1,rowStyle.strokeColor) - if symbol: g.add(symbol) - - # Draw item labels. - for colNo in range(len(row)): - x1, y1 = row[colNo] - self.drawLabel(g, rowNo, colNo, x1, y1) - - return g - - def draw(self): - "Draws itself." - - vA, cA = self.valueAxis, self.categoryAxis - vA.setPosition(self.x, self.y, self.height) - if vA: vA.joinAxis = cA - if cA: cA.joinAxis = vA - vA.configure(self.data) - - # If zero is in chart, put x axis there, otherwise - # use bottom. - xAxisCrossesAt = vA.scale(0) - if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): - y = self.y - else: - y = xAxisCrossesAt - - cA.setPosition(self.x, y, self.width) - cA.configure(self.data) - - self.calcPositions() - - g = Group() - g.add(self.makeBackground()) - if self.inFill: - self._inFillG = Group() - g.add(self._inFillG) - - g.add(cA) - g.add(vA) - vA.gridStart = cA._x - vA.gridEnd = cA._x+cA._length - cA.gridStart = vA._y - cA.gridEnd = vA._y+vA._length - cA.makeGrid(g,parent=self) - vA.makeGrid(g,parent=self) - g.add(self.makeLines()) - for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale)) - return g - -def _cmpFakeItem(a,b): - '''t, z0, z1, x, y = a[:5]''' - return cmp((-a[1],a[3],a[0],-a[4]),(-b[1],b[3],b[0],-b[4])) - -class _FakeGroup: - def __init__(self): - self._data = [] - - def add(self,what): - if what: self._data.append(what) - - def value(self): - return self._data - - def sort(self): - self._data.sort(_cmpFakeItem) - #for t in self._data: print t - -class HorizontalLineChart3D(HorizontalLineChart): - _attrMap = AttrMap(BASE=HorizontalLineChart, - theta_x = AttrMapValue(isNumber, desc='dx/dz'), - theta_y = AttrMapValue(isNumber, desc='dy/dz'), - zDepth = AttrMapValue(isNumber, desc='depth of an individual series'), - zSpace = AttrMapValue(isNumber, desc='z gap around series'), - ) - theta_x = .5 - theta_y = .5 - zDepth = 10 - zSpace = 3 - - def calcPositions(self): - HorizontalLineChart.calcPositions(self) - nSeries = self._seriesCount - zSpace = self.zSpace - zDepth = self.zDepth - if self.categoryAxis.style=='parallel_3d': - _3d_depth = nSeries*zDepth+(nSeries+1)*zSpace - else: - _3d_depth = zDepth + 2*zSpace - self._3d_dx = self.theta_x*_3d_depth - self._3d_dy = self.theta_y*_3d_depth - - def _calc_z0(self,rowNo): - zSpace = self.zSpace - if self.categoryAxis.style=='parallel_3d': - z0 = rowNo*(self.zDepth+zSpace)+zSpace - else: - z0 = zSpace - return z0 - - def _zadjust(self,x,y,z): - return x+z*self.theta_x, y+z*self.theta_y - - def makeLines(self): - labelFmt = self.lineLabelFormat - P = range(len(self._positions)) - if self.reversePlotOrder: P.reverse() - inFill = self.inFill - assert not inFill, "inFill not supported for 3d yet" - #if inFill: - #inFillY = self.categoryAxis._y - #inFillX0 = self.valueAxis._x - #inFillX1 = inFillX0 + self.categoryAxis._length - #inFillG = getattr(self,'_inFillG',g) - zDepth = self.zDepth - _zadjust = self._zadjust - theta_x = self.theta_x - theta_y = self.theta_y - F = _FakeGroup() - from utils3d import _make_3d_line_info - tileWidth = getattr(self,'_3d_tilewidth',None) - if not tileWidth and self.categoryAxis.style!='parallel_3d': tileWidth = 1 - - # Iterate over data rows. - for rowNo in P: - row = self._positions[rowNo] - n = len(row) - styleCount = len(self.lines) - styleIdx = rowNo % styleCount - rowStyle = self.lines[styleIdx] - rowColor = rowStyle.strokeColor - dash = getattr(rowStyle, 'strokeDashArray', None) - z0 = self._calc_z0(rowNo) - z1 = z0 + zDepth - - if hasattr(self.lines[styleIdx], 'strokeWidth'): - strokeWidth = self.lines[styleIdx].strokeWidth - elif hasattr(self.lines, 'strokeWidth'): - strokeWidth = self.lines.strokeWidth - else: - strokeWidth = None - - # Iterate over data columns. - if self.joinedLines: - if n: - x0, y0 = row[0] - for colNo in xrange(1,n): - x1, y1 = row[colNo] - _make_3d_line_info( F, x0, x1, y0, y1, z0, z1, - theta_x, theta_y, - rowColor, fillColorShaded=None, tileWidth=tileWidth, - strokeColor=None, strokeWidth=None, strokeDashArray=None, - shading=0.1) - x0, y0 = x1, y1 - - if hasattr(self.lines[styleIdx], 'symbol'): - uSymbol = self.lines[styleIdx].symbol - elif hasattr(self.lines, 'symbol'): - uSymbol = self.lines.symbol - else: - uSymbol = None - - if uSymbol: - for colNo in xrange(n): - x1, y1 = row[colNo] - x1, y1 = _zadjust(x1,y1,z0) - symbol = uSymbol2Symbol(uSymbol,x1,y1,rowColor) - if symbol: F.add((2,z0,z0,x1,y1,symbol)) - - # Draw item labels. - for colNo in xrange(n): - x1, y1 = row[colNo] - x1, y1 = _zadjust(x1,y1,z0) - L = self._innerDrawLabel(rowNo, colNo, x1, y1) - if L: F.add((2,z0,z0,x1,y1,L)) - - F.sort() - g = Group() - map(lambda x,a=g.add: a(x[-1]),F.value()) - return g - -class VerticalLineChart(LineChart): - pass - - -def sample1(): - drawing = Drawing(400, 200) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (5, 20, 46, 38, 23, 21, 6, 14) - ] - - lc = HorizontalLineChart() - - lc.x = 50 - lc.y = 50 - lc.height = 125 - lc.width = 300 - lc.data = data - lc.joinedLines = 1 - lc.lines.symbol = makeMarker('FilledDiamond') - lc.lineLabelFormat = '%2.0f' - - catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ') - lc.categoryAxis.categoryNames = catNames - lc.categoryAxis.labels.boxAnchor = 'n' - - lc.valueAxis.valueMin = 0 - lc.valueAxis.valueMax = 60 - lc.valueAxis.valueStep = 15 - - drawing.add(lc) - - return drawing - - -class SampleHorizontalLineChart(HorizontalLineChart): - "Sample class overwriting one method to draw additional horizontal lines." - - def demo(self): - """Shows basic use of a line chart.""" - - drawing = Drawing(200, 100) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (14, 10, 21, 28, 38, 46, 25, 5) - ] - - lc = SampleHorizontalLineChart() - - lc.x = 20 - lc.y = 10 - lc.height = 85 - lc.width = 170 - lc.data = data - lc.strokeColor = colors.white - lc.fillColor = colors.HexColor(0xCCCCCC) - - drawing.add(lc) - - return drawing - - - def makeBackground(self): - g = Group() - - g.add(HorizontalLineChart.makeBackground(self)) - - valAxis = self.valueAxis - valTickPositions = valAxis._tickValues - - for y in valTickPositions: - y = valAxis.scale(y) - g.add(Line(self.x, y, self.x+self.width, y, - strokeColor = self.strokeColor)) - - return g - - - -def sample1a(): - drawing = Drawing(400, 200) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (5, 20, 46, 38, 23, 21, 6, 14) - ] - - lc = SampleHorizontalLineChart() - - lc.x = 50 - lc.y = 50 - lc.height = 125 - lc.width = 300 - lc.data = data - lc.joinedLines = 1 - lc.strokeColor = colors.white - lc.fillColor = colors.HexColor(0xCCCCCC) - lc.lines.symbol = makeMarker('FilledDiamond') - lc.lineLabelFormat = '%2.0f' - - catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ') - lc.categoryAxis.categoryNames = catNames - lc.categoryAxis.labels.boxAnchor = 'n' - - lc.valueAxis.valueMin = 0 - lc.valueAxis.valueMax = 60 - lc.valueAxis.valueStep = 15 - - drawing.add(lc) - - return drawing - - -def sample2(): - drawing = Drawing(400, 200) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (5, 20, 46, 38, 23, 21, 6, 14) - ] - - lc = HorizontalLineChart() - - lc.x = 50 - lc.y = 50 - lc.height = 125 - lc.width = 300 - lc.data = data - lc.joinedLines = 1 - lc.lines.symbol = makeMarker('Smiley') - lc.lineLabelFormat = '%2.0f' - lc.strokeColor = colors.black - lc.fillColor = colors.lightblue - - catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ') - lc.categoryAxis.categoryNames = catNames - lc.categoryAxis.labels.boxAnchor = 'n' - - lc.valueAxis.valueMin = 0 - lc.valueAxis.valueMax = 60 - lc.valueAxis.valueStep = 15 - - drawing.add(lc) - - return drawing - - -def sample3(): - drawing = Drawing(400, 200) - - data = [ - (13, 5, 20, 22, 37, 45, 19, 4), - (5, 20, 46, 38, 23, 21, 6, 14) - ] - - lc = HorizontalLineChart() - - lc.x = 50 - lc.y = 50 - lc.height = 125 - lc.width = 300 - lc.data = data - lc.joinedLines = 1 - lc.lineLabelFormat = '%2.0f' - lc.strokeColor = colors.black - - lc.lines[0].symbol = makeMarker('Smiley') - lc.lines[1].symbol = NoEntry - lc.lines[0].strokeWidth = 2 - lc.lines[1].strokeWidth = 4 - - catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ') - lc.categoryAxis.categoryNames = catNames - lc.categoryAxis.labels.boxAnchor = 'n' - - lc.valueAxis.valueMin = 0 - lc.valueAxis.valueMax = 60 - lc.valueAxis.valueStep = 15 - - drawing.add(lc) - - return drawing diff --git a/bin/reportlab/graphics/charts/lineplots.py b/bin/reportlab/graphics/charts/lineplots.py deleted file mode 100644 index 7ada465a380..00000000000 --- a/bin/reportlab/graphics/charts/lineplots.py +++ /dev/null @@ -1,1096 +0,0 @@ -#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/graphics/charts/lineplots.py -"""This module defines a very preliminary Line Plot example. -""" -__version__=''' $Id$ ''' - -import string, time -from types import FunctionType - -from reportlab.lib import colors -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * -from reportlab.graphics.shapes import Drawing, Group, Rect, Line, PolyLine, Polygon, _SetKeyWordArgs -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder -from reportlab.graphics.charts.textlabels import Label -from reportlab.graphics.charts.axes import XValueAxis, YValueAxis, AdjYValueAxis, NormalDateXValueAxis -from reportlab.graphics.charts.utils import * -from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol, makeMarker -from reportlab.graphics.widgets.grids import Grid, DoubleGrid, ShadedRect, ShadedPolygon -from reportlab.pdfbase.pdfmetrics import stringWidth, getFont -from reportlab.graphics.charts.areas import PlotArea - -# This might be moved again from here... -class LinePlotProperties(PropHolder): - _attrMap = AttrMap( - strokeWidth = AttrMapValue(isNumber, desc='Width of a line.'), - strokeColor = AttrMapValue(isColorOrNone, desc='Color of a line.'), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'), - symbol = AttrMapValue(None, desc='Widget placed at data points.'), - shader = AttrMapValue(None, desc='Shader Class.'), - filler = AttrMapValue(None, desc='Filler Class.'), - ) - -class Shader(_SetKeyWordArgs): - _attrMap = AttrMap(BASE=PlotArea, - vertical = AttrMapValue(isBoolean, desc='If true shade to x axis'), - colors = AttrMapValue(SequenceOf(isColorOrNone,lo=2,hi=2), desc='(AxisColor, LineColor)'), - ) - - def shade(self, lp, g, rowNo, rowColor, row): - c = [None,None] - c = getattr(self,'colors',c) or c - if not c[0]: c[0] = getattr(lp,'fillColor',colors.white) - if not c[1]: c[1] = rowColor - -class Filler: - '''mixin providing simple polygon fill''' - def fill(self, lp, g, rowNo, rowColor, points): - self.points[:] = points - g.add(self) - -class ShadedPolyFiller(Filler,ShadedPolygon): - pass - -class PolyFiller(Filler,Polygon): - pass - -class LinePlot(PlotArea): - """Line plot with multiple lines. - - Both x- and y-axis are value axis (so there are no seperate - X and Y versions of this class). - """ - _attrMap = AttrMap(BASE=PlotArea, - reversePlotOrder = AttrMapValue(isBoolean, desc='If true reverse plot order.'), - lineLabelNudge = AttrMapValue(isNumber, desc='Distance between a data point and its label.'), - lineLabels = AttrMapValue(None, desc='Handle to the list of data point labels.'), - lineLabelFormat = AttrMapValue(None, desc='Formatting string or function used for data point labels.'), - lineLabelArray = AttrMapValue(None, desc='explicit array of line label values, must match size of data if present.'), - joinedLines = AttrMapValue(isNumber, desc='Display data points joined with lines if true.'), - strokeColor = AttrMapValue(isColorOrNone, desc='Color used for background border of plot area.'), - fillColor = AttrMapValue(isColorOrNone, desc='Color used for background interior of plot area.'), - lines = AttrMapValue(None, desc='Handle of the lines.'), - xValueAxis = AttrMapValue(None, desc='Handle of the x axis.'), - yValueAxis = AttrMapValue(None, desc='Handle of the y axis.'), - data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) x/y tuples.'), - annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'), - ) - - def __init__(self): - PlotArea.__init__(self) - self.reversePlotOrder = 0 - - self.xValueAxis = XValueAxis() - self.yValueAxis = YValueAxis() - - # this defines two series of 3 points. Just an example. - self.data = [ - ((1,1), (2,2), (2.5,1), (3,3), (4,5)), - ((1,2), (2,3), (2.5,2), (3,4), (4,6)) - ] - - self.lines = TypedPropertyCollection(LinePlotProperties) - self.lines.strokeWidth = 1 - self.lines[0].strokeColor = colors.red - self.lines[1].strokeColor = colors.blue - - self.lineLabels = TypedPropertyCollection(Label) - self.lineLabelFormat = None - self.lineLabelArray = None - - # this says whether the origin is inside or outside - # the bar - +10 means put the origin ten points - # above the tip of the bar if value > 0, or ten - # points inside if bar value < 0. This is different - # to label dx/dy which are not dependent on the - # sign of the data. - self.lineLabelNudge = 10 - # if you have multiple series, by default they butt - # together. - - # New line chart attributes. - self.joinedLines = 1 # Connect items with straight lines. - - #private attributes - self._inFill = None - - def demo(self): - """Shows basic use of a line chart.""" - - drawing = Drawing(400, 200) - - data = [ - ((1,1), (2,2), (2.5,1), (3,3), (4,5)), - ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) - ] - - lp = LinePlot() - - lp.x = 50 - lp.y = 50 - lp.height = 125 - lp.width = 300 - lp.data = data - lp.joinedLines = 1 - lp.lineLabelFormat = '%2.0f' - lp.strokeColor = colors.black - - lp.lines[0].strokeColor = colors.red - lp.lines[0].symbol = makeMarker('FilledCircle') - lp.lines[1].strokeColor = colors.blue - lp.lines[1].symbol = makeMarker('FilledDiamond') - - lp.xValueAxis.valueMin = 0 - lp.xValueAxis.valueMax = 5 - lp.xValueAxis.valueStep = 1 - - lp.yValueAxis.valueMin = 0 - lp.yValueAxis.valueMax = 7 - lp.yValueAxis.valueStep = 1 - - drawing.add(lp) - - return drawing - - - def calcPositions(self): - """Works out where they go. - - Sets an attribute _positions which is a list of - lists of (x, y) matching the data. - """ - - self._seriesCount = len(self.data) - self._rowLength = max(map(len,self.data)) - - self._positions = [] - for rowNo in range(len(self.data)): - line = [] - for colNo in range(len(self.data[rowNo])): - datum = self.data[rowNo][colNo] # x,y value - if type(datum[0]) == type(''): - x = self.xValueAxis.scale(mktime(mkTimeTuple(datum[0]))) - else: - x = self.xValueAxis.scale(datum[0]) - y = self.yValueAxis.scale(datum[1]) - line.append((x, y)) - self._positions.append(line) - - def _innerDrawLabel(self, rowNo, colNo, x, y): - "Draw a label for a given item in the list." - - labelFmt = self.lineLabelFormat - labelValue = self.data[rowNo][colNo][1] ### - - if labelFmt is None: - labelText = None - elif type(labelFmt) is StringType: - if labelFmt == 'values': - labelText = self.lineLabelArray[rowNo][colNo] - else: - labelText = labelFmt % labelValue - elif type(labelFmt) is FunctionType: - labelText = labelFmt(labelValue) - elif isinstance(labelFmt, Formatter): - labelText = labelFmt(labelValue) - else: - msg = "Unknown formatter type %s, expected string or function" - raise Exception, msg % labelFmt - - if labelText: - label = self.lineLabels[(rowNo, colNo)] - #hack to make sure labels are outside the bar - if y > 0: - label.setOrigin(x, y + self.lineLabelNudge) - else: - label.setOrigin(x, y - self.lineLabelNudge) - label.setText(labelText) - else: - label = None - return label - - def drawLabel(self, G, rowNo, colNo, x, y): - '''Draw a label for a given item in the list. - G must have an add method''' - G.add(self._innerDrawLabel(rowNo,colNo,x,y)) - - def makeLines(self): - g = Group() - bubblePlot = getattr(self,'_bubblePlot',None) - if bubblePlot: - yA = self.yValueAxis - xA = self.xValueAxis - bubbleR = min(yA._bubbleRadius,xA._bubbleRadius) - bubbleMax = xA._bubbleMax - - labelFmt = self.lineLabelFormat - - P = range(len(self._positions)) - if self.reversePlotOrder: P.reverse() - inFill = getattr(self,'_inFill',None) - if inFill: - inFillY = self.xValueAxis._y - inFillX0 = self.yValueAxis._x - inFillX1 = inFillX0 + self.xValueAxis._length - inFillG = getattr(self,'_inFillG',g) - # Iterate over data rows. - styleCount = len(self.lines) - for rowNo in P: - row = self._positions[rowNo] - rowStyle = self.lines[rowNo % styleCount] - rowColor = rowStyle.strokeColor - dash = getattr(rowStyle, 'strokeDashArray', None) - - if hasattr(rowStyle, 'strokeWidth'): - width = rowStyle.strokeWidth - elif hasattr(self.lines, 'strokeWidth'): - width = self.lines.strokeWidth - else: - width = None - - # Iterate over data columns. - if self.joinedLines: - points = [] - for xy in row: - points = points + [xy[0], xy[1]] - if inFill: - points = points + [inFillX1,inFillY,inFillX0,inFillY] - filler = getattr(rowStyle, 'filler', None) - if filler: - filler.fill(self,inFillG,rowNo,rowColor,points) - else: - inFillG.add(Polygon(points,fillColor=rowColor,strokeColor=rowColor,strokeWidth=0.1)) - else: - line = PolyLine(points,strokeColor=rowColor,strokeLineCap=0,strokeLineJoin=1) - if width: - line.strokeWidth = width - if dash: - line.strokeDashArray = dash - g.add(line) - - if hasattr(rowStyle, 'symbol'): - uSymbol = rowStyle.symbol - elif hasattr(self.lines, 'symbol'): - uSymbol = self.lines.symbol - else: - uSymbol = None - - if uSymbol: - j = -1 - if bubblePlot: drow = self.data[rowNo] - for xy in row: - j += 1 - symbol = uSymbol2Symbol(uSymbol,xy[0],xy[1],rowColor) - if symbol: - if bubblePlot: - symbol.size = bubbleR*(drow[j][2]/bubbleMax)**0.5 - g.add(symbol) - - # Draw data labels. - for colNo in range(len(row)): - x1, y1 = row[colNo] - self.drawLabel(g, rowNo, colNo, x1, y1) - - shader = getattr(rowStyle, 'shader', None) - if shader: shader.shade(self,g,rowNo,rowColor,row) - - - return g - - def makeSwatchSample(self,rowNo, x, y, width, height): - styleCount = len(self.lines) - styleIdx = rowNo % styleCount - rowColor = self.lines[styleIdx].strokeColor - - if self.joinedLines: - dash = getattr(self.lines[styleIdx], 'strokeDashArray', getattr(self.lines,'strokeDashArray',None)) - strokeWidth= getattr(self.lines[styleIdx], 'strokeWidth', getattr(self.lines[styleIdx], 'strokeWidth',None)) - L = Line(x,y,x+width,y+height,strokeColor=rowColor,strokeLineCap=0) - if strokeWidth: L.strokeWidth = strokeWidth - if dash: L.strokeDashArray = dash - else: - L = None - - if hasattr(self.lines[styleIdx], 'symbol'): - S = self.lines[styleIdx].symbol - elif hasattr(self.lines, 'symbol'): - S = self.lines.symbol - else: - S = None - - if S: S = uSymbol2Symbol(S,x+width/2.,y+height/2.,rowColor) - if S and L: - g = Group() - g.add(S) - g.add(L) - return g - return S or L - - def draw(self): - yA = self.yValueAxis - xA = self.xValueAxis - if getattr(self,'_bubblePlot',None): - yA._bubblePlot = xA._bubblePlot = 1 - yA.setPosition(self.x, self.y, self.height) - if yA: yA.joinAxis = xA - if xA: xA.joinAxis = yA - yA.configure(self.data) - - # if zero is in chart, put x axis there, otherwise use bottom. - xAxisCrossesAt = yA.scale(0) - if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): - y = self.y - else: - y = xAxisCrossesAt - - xA.setPosition(self.x, y, self.width) - xA.configure(self.data) - self.calcPositions() - g = Group() - g.add(self.makeBackground()) - if self._inFill: - self._inFillG = Group() - g.add(self._inFillG) - g.add(xA) - g.add(yA) - yA.gridStart = xA._x - yA.gridEnd = xA._x+xA._length - xA.gridStart = yA._y - xA.gridEnd = yA._y+yA._length - xA.makeGrid(g,parent=self) - yA.makeGrid(g,parent=self) - g.add(self.makeLines()) - for a in getattr(self,'annotations',()): g.add(a(self,xA.scale,yA.scale)) - return g - -class LinePlot3D(LinePlot): - _attrMap = AttrMap(BASE=LinePlot, - theta_x = AttrMapValue(isNumber, desc='dx/dz'), - theta_y = AttrMapValue(isNumber, desc='dy/dz'), - zDepth = AttrMapValue(isNumber, desc='depth of an individual series'), - zSpace = AttrMapValue(isNumber, desc='z gap around series'), - ) - theta_x = .5 - theta_y = .5 - zDepth = 10 - zSpace = 3 - - def calcPositions(self): - LinePlot.calcPositions(self) - nSeries = self._seriesCount - zSpace = self.zSpace - zDepth = self.zDepth - if self.xValueAxis.style=='parallel_3d': - _3d_depth = nSeries*zDepth+(nSeries+1)*zSpace - else: - _3d_depth = zDepth + 2*zSpace - self._3d_dx = self.theta_x*_3d_depth - self._3d_dy = self.theta_y*_3d_depth - - def _calc_z0(self,rowNo): - zSpace = self.zSpace - if self.xValueAxis.style=='parallel_3d': - z0 = rowNo*(self.zDepth+zSpace)+zSpace - else: - z0 = zSpace - return z0 - - def _zadjust(self,x,y,z): - return x+z*self.theta_x, y+z*self.theta_y - - def makeLines(self): - bubblePlot = getattr(self,'_bubblePlot',None) - assert not bubblePlot, "_bubblePlot not supported for 3d yet" - #if bubblePlot: - # yA = self.yValueAxis - # xA = self.xValueAxis - # bubbleR = min(yA._bubbleRadius,xA._bubbleRadius) - # bubbleMax = xA._bubbleMax - - labelFmt = self.lineLabelFormat - positions = self._positions - - P = range(len(positions)) - if self.reversePlotOrder: P.reverse() - inFill = getattr(self,'_inFill',None) - assert not inFill, "inFill not supported for 3d yet" - #if inFill: - # inFillY = self.xValueAxis._y - # inFillX0 = self.yValueAxis._x - # inFillX1 = inFillX0 + self.xValueAxis._length - # inFillG = getattr(self,'_inFillG',g) - zDepth = self.zDepth - _zadjust = self._zadjust - theta_x = self.theta_x - theta_y = self.theta_y - from linecharts import _FakeGroup - F = _FakeGroup() - - from utils3d import _make_3d_line_info, find_intersections - if self.xValueAxis.style!='parallel_3d': - tileWidth = getattr(self,'_3d_tilewidth',1) - if getattr(self,'_find_intersections',None): - from copy import copy - fpositions = map(copy,positions) - I = find_intersections(fpositions,small=tileWidth) - ic = None - for i,j,x,y in I: - if ic!=i: - ic = i - jc = 0 - else: - jc+=1 - fpositions[i].insert(j+jc,(x,y)) - tileWidth = None - else: - fpositions = positions - else: - tileWidth = None - fpositions = positions - - # Iterate over data rows. - styleCount = len(self.lines) - for rowNo in P: - row = positions[rowNo] - n = len(row) - rowStyle = self.lines[rowNo % styleCount] - rowColor = rowStyle.strokeColor - dash = getattr(rowStyle, 'strokeDashArray', None) - z0 = self._calc_z0(rowNo) - z1 = z0 + zDepth - - if hasattr(rowStyle, 'strokeWidth'): - width = rowStyle.strokeWidth - elif hasattr(self.lines, 'strokeWidth'): - width = self.lines.strokeWidth - else: - width = None - - # Iterate over data columns. - if self.joinedLines: - if n: - frow = fpositions[rowNo] - x0, y0 = frow[0] - for colNo in xrange(1,len(frow)): - x1, y1 = frow[colNo] - _make_3d_line_info( F, x0, x1, y0, y1, z0, z1, - theta_x, theta_y, - rowColor, fillColorShaded=None, tileWidth=tileWidth, - strokeColor=None, strokeWidth=None, strokeDashArray=None, - shading=0.1) - x0, y0 = x1, y1 - - if hasattr(rowStyle, 'symbol'): - uSymbol = rowStyle.symbol - elif hasattr(self.lines, 'symbol'): - uSymbol = self.lines.symbol - else: - uSymbol = None - - if uSymbol: - for xy in row: - x1, y1 = row[colNo] - x1, y1 = _zadjust(x1,y1,z0) - symbol = uSymbol2Symbol(uSymbol,xy[0],xy[1],rowColor) - if symbol: F.add((1,z0,z0,x1,y1,symbol)) - - # Draw data labels. - for colNo in xrange(n): - x1, y1 = row[colNo] - x1, y1 = _zadjust(x1,y1,z0) - L = self._innerDrawLabel(rowNo, colNo, x1, y1) - if L: F.add((2,z0,z0,x1,y1,L)) - - F.sort() - g = Group() - map(lambda x,a=g.add: a(x[-1]),F.value()) - return g - -_monthlyIndexData = [[(19971202, 100.0), - (19971231, 100.1704367), - (19980131, 101.5639577), - (19980228, 102.1879927), - (19980331, 101.6337257), - (19980430, 102.7640446), - (19980531, 102.9198038), - (19980630, 103.25938789999999), - (19980731, 103.2516421), - (19980831, 105.4744329), - (19980930, 109.3242705), - (19981031, 111.9859291), - (19981130, 110.9184642), - (19981231, 110.9184642), - (19990131, 111.9882532), - (19990228, 109.7912614), - (19990331, 110.24189629999999), - (19990430, 110.4279321), - (19990531, 109.33955469999999), - (19990630, 108.2341748), - (19990731, 110.21294469999999), - (19990831, 110.9683062), - (19990930, 112.4425371), - (19991031, 112.7314032), - (19991130, 112.3509645), - (19991231, 112.3660659), - (20000131, 110.9255248), - (20000229, 110.5266306), - (20000331, 113.3116101), - (20000430, 111.0449133), - (20000531, 111.702717), - (20000630, 113.5832178)], - [(19971202, 100.0), - (19971231, 100.0), - (19980131, 100.8), - (19980228, 102.0), - (19980331, 101.9), - (19980430, 103.0), - (19980531, 103.0), - (19980630, 103.1), - (19980731, 103.1), - (19980831, 102.8), - (19980930, 105.6), - (19981031, 108.3), - (19981130, 108.1), - (19981231, 111.9), - (19990131, 113.1), - (19990228, 110.2), - (19990331, 111.8), - (19990430, 112.3), - (19990531, 110.1), - (19990630, 109.3), - (19990731, 111.2), - (19990831, 111.7), - (19990930, 112.6), - (19991031, 113.2), - (19991130, 113.9), - (19991231, 115.4), - (20000131, 112.7), - (20000229, 113.9), - (20000331, 115.8), - (20000430, 112.2), - (20000531, 112.6), - (20000630, 114.6)]] - -class GridLinePlot(LinePlot): - """A customized version of LinePlot. - It uses NormalDateXValueAxis() and AdjYValueAxis() for the X and Y axes. - The chart has a default grid background with thin horizontal lines - aligned with the tickmarks (and labels). You can change the back- - ground to be any Grid or ShadedRect, or scale the whole chart. - If you do provide a background, you can specify the colours of the - stripes with 'background.stripeColors'. - """ - - _attrMap = AttrMap(BASE=LinePlot, - background = AttrMapValue(None, desc='Background for chart area (now Grid or ShadedRect).'), - scaleFactor = AttrMapValue(isNumberOrNone, desc='Scalefactor to apply to whole drawing.'), - ) - - def __init__(self): - from reportlab.lib import colors - LinePlot.__init__(self) - self.xValueAxis = NormalDateXValueAxis() - self.yValueAxis = AdjYValueAxis() - self.scaleFactor = None - self.background = Grid() - self.background.orientation = 'horizontal' - self.background.useRects = 0 - self.background.useLines = 1 - self.background.strokeWidth = 0.5 - self.background.strokeColor = colors.black - self.data = _monthlyIndexData - - def demo(self,drawing=None): - from reportlab.lib import colors - if not drawing: - drawing = Drawing(400, 200) - lp = AdjLinePlot() - lp.x = 50 - lp.y = 50 - lp.height = 125 - lp.width = 300 - lp.data = _monthlyIndexData - lp.joinedLines = 1 - lp.strokeColor = colors.black - c0 = colors.PCMYKColor(100,65,0,30, spotName='PANTONE 288 CV', density=100) - lp.lines[0].strokeColor = c0 - lp.lines[0].strokeWidth = 2 - lp.lines[0].strokeDashArray = None - c1 = colors.PCMYKColor(0,79,91,0, spotName='PANTONE Wm Red CV', density=100) - lp.lines[1].strokeColor = c1 - lp.lines[1].strokeWidth = 1 - lp.lines[1].strokeDashArray = [3,1] - lp.xValueAxis.labels.fontSize = 10 - lp.xValueAxis.labels.textAnchor = 'start' - lp.xValueAxis.labels.boxAnchor = 'w' - lp.xValueAxis.labels.angle = -45 - lp.xValueAxis.labels.dx = 0 - lp.xValueAxis.labels.dy = -8 - lp.xValueAxis.xLabelFormat = '{mm}/{yy}' - lp.yValueAxis.labelTextFormat = '%5d%% ' - lp.yValueAxis.tickLeft = 5 - lp.yValueAxis.labels.fontSize = 10 - lp.background = Grid() - lp.background.stripeColors = [colors.pink, colors.lightblue] - lp.background.orientation = 'vertical' - drawing.add(lp,'plot') - return drawing - - def draw(self): - xva, yva = self.xValueAxis, self.yValueAxis - if xva: xva.joinAxis = yva - if yva: yva.joinAxis = xva - - yva.setPosition(self.x, self.y, self.height) - yva.configure(self.data) - - # if zero is in chart, put x axis there, otherwise - # use bottom. - xAxisCrossesAt = yva.scale(0) - if ((xAxisCrossesAt > self.y + self.height) or (xAxisCrossesAt < self.y)): - y = self.y - else: - y = xAxisCrossesAt - - xva.setPosition(self.x, y, self.width) - xva.configure(self.data) - - back = self.background - if isinstance(back, Grid): - if back.orientation == 'vertical' and xva._tickValues: - xpos = map(xva.scale, [xva._valueMin] + xva._tickValues) - steps = [] - for i in range(len(xpos)-1): - steps.append(xpos[i+1] - xpos[i]) - back.deltaSteps = steps - elif back.orientation == 'horizontal' and yva._tickValues: - ypos = map(yva.scale, [yva._valueMin] + yva._tickValues) - steps = [] - for i in range(len(ypos)-1): - steps.append(ypos[i+1] - ypos[i]) - back.deltaSteps = steps - elif isinstance(back, DoubleGrid): - # Ideally, these lines would not be needed... - back.grid0.x = self.x - back.grid0.y = self.y - back.grid0.width = self.width - back.grid0.height = self.height - back.grid1.x = self.x - back.grid1.y = self.y - back.grid1.width = self.width - back.grid1.height = self.height - - # some room left for optimization... - if back.grid0.orientation == 'vertical' and xva._tickValues: - xpos = map(xva.scale, [xva._valueMin] + xva._tickValues) - steps = [] - for i in range(len(xpos)-1): - steps.append(xpos[i+1] - xpos[i]) - back.grid0.deltaSteps = steps - elif back.grid0.orientation == 'horizontal' and yva._tickValues: - ypos = map(yva.scale, [yva._valueMin] + yva._tickValues) - steps = [] - for i in range(len(ypos)-1): - steps.append(ypos[i+1] - ypos[i]) - back.grid0.deltaSteps = steps - if back.grid1.orientation == 'vertical' and xva._tickValues: - xpos = map(xva.scale, [xva._valueMin] + xva._tickValues) - steps = [] - for i in range(len(xpos)-1): - steps.append(xpos[i+1] - xpos[i]) - back.grid1.deltaSteps = steps - elif back.grid1.orientation == 'horizontal' and yva._tickValues: - ypos = map(yva.scale, [yva._valueMin] + yva._tickValues) - steps = [] - for i in range(len(ypos)-1): - steps.append(ypos[i+1] - ypos[i]) - back.grid1.deltaSteps = steps - - self.calcPositions() - - width, height, scaleFactor = self.width, self.height, self.scaleFactor - if scaleFactor and scaleFactor!=1: - #g = Drawing(scaleFactor*width, scaleFactor*height) - g.transform = (scaleFactor, 0, 0, scaleFactor,0,0) - else: - g = Group() - - g.add(self.makeBackground()) - g.add(self.xValueAxis) - g.add(self.yValueAxis) - g.add(self.makeLines()) - - return g - -class AreaLinePlot(LinePlot): - '''we're given data in the form [(X1,Y11,..Y1M)....(Xn,Yn1,...YnM)]'''#' - def __init__(self): - LinePlot.__init__(self) - self._inFill = 1 - self.reversePlotOrder = 1 - self.data = [(1,20,100,30),(2,11,50,15),(3,15,70,40)] - - def draw(self): - try: - odata = self.data - n = len(odata) - m = len(odata[0]) - S = n*[0] - self.data = [] - for i in xrange(1,m): - D = [] - for j in xrange(n): - S[j] = S[j] + odata[j][i] - D.append((odata[j][0],S[j])) - self.data.append(D) - return LinePlot.draw(self) - finally: - self.data = odata - -class SplitLinePlot(AreaLinePlot): - def __init__(self): - AreaLinePlot.__init__(self) - self.xValueAxis = NormalDateXValueAxis() - self.yValueAxis = AdjYValueAxis() - self.data=[(20030601,0.95,0.05,0.0),(20030701,0.95,0.05,0.0),(20030801,0.95,0.05,0.0),(20030901,0.95,0.05,0.0),(20031001,0.95,0.05,0.0),(20031101,0.95,0.05,0.0),(20031201,0.95,0.05,0.0),(20040101,0.95,0.05,0.0),(20040201,0.95,0.05,0.0),(20040301,0.95,0.05,0.0),(20040401,0.95,0.05,0.0),(20040501,0.95,0.05,0.0),(20040601,0.95,0.05,0.0),(20040701,0.95,0.05,0.0),(20040801,0.95,0.05,0.0),(20040901,0.95,0.05,0.0),(20041001,0.95,0.05,0.0),(20041101,0.95,0.05,0.0),(20041201,0.95,0.05,0.0),(20050101,0.95,0.05,0.0),(20050201,0.95,0.05,0.0),(20050301,0.95,0.05,0.0),(20050401,0.95,0.05,0.0),(20050501,0.95,0.05,0.0),(20050601,0.95,0.05,0.0),(20050701,0.95,0.05,0.0),(20050801,0.95,0.05,0.0),(20050901,0.95,0.05,0.0),(20051001,0.95,0.05,0.0),(20051101,0.95,0.05,0.0),(20051201,0.95,0.05,0.0),(20060101,0.95,0.05,0.0),(20060201,0.95,0.05,0.0),(20060301,0.95,0.05,0.0),(20060401,0.95,0.05,0.0),(20060501,0.95,0.05,0.0),(20060601,0.95,0.05,0.0),(20060701,0.95,0.05,0.0),(20060801,0.95,0.05,0.0),(20060901,0.95,0.05,0.0),(20061001,0.95,0.05,0.0),(20061101,0.95,0.05,0.0),(20061201,0.95,0.05,0.0),(20070101,0.95,0.05,0.0),(20070201,0.95,0.05,0.0),(20070301,0.95,0.05,0.0),(20070401,0.95,0.05,0.0),(20070501,0.95,0.05,0.0),(20070601,0.95,0.05,0.0),(20070701,0.95,0.05,0.0),(20070801,0.95,0.05,0.0),(20070901,0.95,0.05,0.0),(20071001,0.95,0.05,0.0),(20071101,0.95,0.05,0.0),(20071201,0.95,0.05,0.0),(20080101,0.95,0.05,0.0),(20080201,0.95,0.05,0.0),(20080301,0.95,0.05,0.0),(20080401,0.95,0.05,0.0),(20080501,0.95,0.05,0.0),(20080601,0.95,0.05,0.0),(20080701,0.95,0.05,0.0),(20080801,0.95,0.05,0.0),(20080901,0.95,0.05,0.0),(20081001,0.95,0.05,0.0),(20081101,0.95,0.05,0.0),(20081201,0.95,0.05,0.0),(20090101,0.95,0.05,0.0),(20090201,0.91,0.09,0.0),(20090301,0.91,0.09,0.0),(20090401,0.91,0.09,0.0),(20090501,0.91,0.09,0.0),(20090601,0.91,0.09,0.0),(20090701,0.91,0.09,0.0),(20090801,0.91,0.09,0.0),(20090901,0.91,0.09,0.0),(20091001,0.91,0.09,0.0),(20091101,0.91,0.09,0.0),(20091201,0.91,0.09,0.0),(20100101,0.91,0.09,0.0),(20100201,0.81,0.19,0.0),(20100301,0.81,0.19,0.0),(20100401,0.81,0.19,0.0),(20100501,0.81,0.19,0.0),(20100601,0.81,0.19,0.0),(20100701,0.81,0.19,0.0),(20100801,0.81,0.19,0.0),(20100901,0.81,0.19,0.0),(20101001,0.81,0.19,0.0),(20101101,0.81,0.19,0.0),(20101201,0.81,0.19,0.0),(20110101,0.81,0.19,0.0),(20110201,0.72,0.28,0.0),(20110301,0.72,0.28,0.0),(20110401,0.72,0.28,0.0),(20110501,0.72,0.28,0.0),(20110601,0.72,0.28,0.0),(20110701,0.72,0.28,0.0),(20110801,0.72,0.28,0.0),(20110901,0.72,0.28,0.0),(20111001,0.72,0.28,0.0),(20111101,0.72,0.28,0.0),(20111201,0.72,0.28,0.0),(20120101,0.72,0.28,0.0),(20120201,0.53,0.47,0.0),(20120301,0.53,0.47,0.0),(20120401,0.53,0.47,0.0),(20120501,0.53,0.47,0.0),(20120601,0.53,0.47,0.0),(20120701,0.53,0.47,0.0),(20120801,0.53,0.47,0.0),(20120901,0.53,0.47,0.0),(20121001,0.53,0.47,0.0),(20121101,0.53,0.47,0.0),(20121201,0.53,0.47,0.0),(20130101,0.53,0.47,0.0),(20130201,0.44,0.56,0.0),(20130301,0.44,0.56,0.0),(20130401,0.44,0.56,0.0),(20130501,0.44,0.56,0.0),(20130601,0.44,0.56,0.0),(20130701,0.44,0.56,0.0),(20130801,0.44,0.56,0.0),(20130901,0.44,0.56,0.0),(20131001,0.44,0.56,0.0),(20131101,0.44,0.56,0.0),(20131201,0.44,0.56,0.0),(20140101,0.44,0.56,0.0),(20140201,0.36,0.5,0.14),(20140301,0.36,0.5,0.14),(20140401,0.36,0.5,0.14),(20140501,0.36,0.5,0.14),(20140601,0.36,0.5,0.14),(20140701,0.36,0.5,0.14),(20140801,0.36,0.5,0.14),(20140901,0.36,0.5,0.14),(20141001,0.36,0.5,0.14),(20141101,0.36,0.5,0.14),(20141201,0.36,0.5,0.14),(20150101,0.36,0.5,0.14),(20150201,0.3,0.41,0.29),(20150301,0.3,0.41,0.29),(20150401,0.3,0.41,0.29),(20150501,0.3,0.41,0.29),(20150601,0.3,0.41,0.29),(20150701,0.3,0.41,0.29),(20150801,0.3,0.41,0.29),(20150901,0.3,0.41,0.29),(20151001,0.3,0.41,0.29),(20151101,0.3,0.41,0.29),(20151201,0.3,0.41,0.29),(20160101,0.3,0.41,0.29),(20160201,0.26,0.36,0.38),(20160301,0.26,0.36,0.38),(20160401,0.26,0.36,0.38),(20160501,0.26,0.36,0.38),(20160601,0.26,0.36,0.38),(20160701,0.26,0.36,0.38),(20160801,0.26,0.36,0.38),(20160901,0.26,0.36,0.38),(20161001,0.26,0.36,0.38),(20161101,0.26,0.36,0.38),(20161201,0.26,0.36,0.38),(20170101,0.26,0.36,0.38),(20170201,0.2,0.3,0.5),(20170301,0.2,0.3,0.5),(20170401,0.2,0.3,0.5),(20170501,0.2,0.3,0.5),(20170601,0.2,0.3,0.5),(20170701,0.2,0.3,0.5),(20170801,0.2,0.3,0.5),(20170901,0.2,0.3,0.5),(20171001,0.2,0.3,0.5),(20171101,0.2,0.3,0.5),(20171201,0.2,0.3,0.5),(20180101,0.2,0.3,0.5),(20180201,0.13,0.37,0.5),(20180301,0.13,0.37,0.5),(20180401,0.13,0.37,0.5),(20180501,0.13,0.37,0.5),(20180601,0.13,0.37,0.5),(20180701,0.13,0.37,0.5),(20180801,0.13,0.37,0.5),(20180901,0.13,0.37,0.5),(20181001,0.13,0.37,0.5),(20181101,0.13,0.37,0.5),(20181201,0.13,0.37,0.5),(20190101,0.13,0.37,0.5),(20190201,0.1,0.4,0.5),(20190301,0.1,0.4,0.5),(20190401,0.1,0.4,0.5),(20190501,0.1,0.4,0.5),(20190601,0.1,0.4,0.5),(20190701,0.1,0.4,0.5),(20190801,0.1,0.4,0.5),(20190901,0.1,0.4,0.5),(20191001,0.1,0.4,0.5),(20191101,0.1,0.4,0.5),(20191201,0.1,0.4,0.5),(20200101,0.1,0.4,0.5)] - self.yValueAxis.requiredRange = None - self.yValueAxis.leftAxisPercent = 0 - self.yValueAxis.leftAxisOrigShiftMin = 0 - self.yValueAxis.leftAxisOrigShiftIPC = 0 - self.lines[0].strokeColor = colors.toColor(0x0033cc) - self.lines[1].strokeColor = colors.toColor(0x99c3ff) - self.lines[2].strokeColor = colors.toColor(0xCC0033) - -def _maxWidth(T, fontName, fontSize): - '''return max stringWidth for the list of strings T''' - if type(T) not in (type(()),type([])): T = (T,) - T = filter(None,T) - return T and max(map(lambda t,sW=stringWidth,fN=fontName, fS=fontSize: sW(t,fN,fS),T)) or 0 - -class ScatterPlot(LinePlot): - """A scatter plot widget""" - - _attrMap = AttrMap(BASE=LinePlot, - width = AttrMapValue(isNumber, desc="Width of the area inside the axes"), - height = AttrMapValue(isNumber, desc="Height of the area inside the axes"), - outerBorderOn = AttrMapValue(isBoolean, desc="Is there an outer border (continuation of axes)"), - outerBorderColor = AttrMapValue(isColorOrNone, desc="Color of outer border (if any)"), - background = AttrMapValue(isColorOrNone, desc="Background color (if any)"), - labelOffset = AttrMapValue(isNumber, desc="Space between label and Axis (or other labels)"), - axisTickLengths = AttrMapValue(isNumber, desc="Lenth of the ticks on both axes"), - axisStrokeWidth = AttrMapValue(isNumber, desc="Stroke width for both axes"), - xLabel = AttrMapValue(isString, desc="Label for the whole X-Axis"), - yLabel = AttrMapValue(isString, desc="Label for the whole Y-Axis"), - data = AttrMapValue(isAnything, desc='Data points - a list of x/y tuples.'), - strokeColor = AttrMapValue(isColorOrNone, desc='Color used for border of plot area.'), - fillColor = AttrMapValue(isColorOrNone, desc='Color used for background interior of plot area.'), - leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'), - rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'), - topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'), - bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'), - ) - - def __init__(self): - LinePlot.__init__(self) - self.width = 142 - self.height = 77 - self.outerBorderOn = 1 - self.outerBorderColor = colors.black - self.background = None - - _labelOffset = 3 - _axisTickLengths = 2 - _axisStrokeWidth = 0.5 - - self.yValueAxis.valueMin = None - self.yValueAxis.valueMax = None - self.yValueAxis.valueStep = None - self.yValueAxis.labelTextFormat = '%s' - - self.xLabel="X Lable" - self.xValueAxis.labels.fontSize = 6 - - self.yLabel="Y Lable" - self.yValueAxis.labels.fontSize = 6 - - self.data =[((0.030, 62.73), - (0.074, 54.363), - (1.216, 17.964)), - - ((1.360, 11.621), - (1.387, 50.011), - (1.428, 68.953)), - - ((1.444, 86.888), - (1.754, 35.58), - (1.766, 36.05))] - - #values for lineplot - self.joinedLines = 0 - self.fillColor = self.background - - self.leftPadding=5 - self.rightPadding=10 - self.topPadding=5 - self.bottomPadding=5 - - self.x = self.leftPadding+_axisTickLengths+(_labelOffset*2) - self.x=self.x+_maxWidth(str(self.yValueAxis.valueMax), self.yValueAxis.labels.fontName, self.yValueAxis.labels.fontSize) - self.y = self.bottomPadding+_axisTickLengths+_labelOffset+self.xValueAxis.labels.fontSize - - self.xValueAxis.labels.dy = -_labelOffset - self.xValueAxis.tickDown = _axisTickLengths - self.xValueAxis.strokeWidth = _axisStrokeWidth - self.xValueAxis.rangeRound='both' - self.yValueAxis.labels.dx = -_labelOffset - self.yValueAxis.tickLeft = _axisTickLengths - self.yValueAxis.strokeWidth = _axisStrokeWidth - self.yValueAxis.rangeRound='both' - - self.lineLabelFormat="%.2f" - self.lineLabels.fontSize = 5 - self.lineLabels.boxAnchor = 'e' - self.lineLabels.dx = -2 - self.lineLabelNudge = 0 - self.lines.symbol=makeMarker('FilledCircle',size=3) - self.lines[1].symbol=makeMarker('FilledDiamond',size=3) - self.lines[2].symbol=makeMarker('FilledSquare',size=3) - self.lines[2].strokeColor = colors.green - - def _getDrawingDimensions(self): - tx = self.leftPadding+self.yValueAxis.tickLeft+(self.yValueAxis.labels.dx*2)+self.xValueAxis.labels.fontSize - tx=tx+(5*_maxWidth(str(self.yValueAxis.valueMax), self.yValueAxis.labels.fontName, self.yValueAxis.labels.fontSize)) - tx=tx+self.width+self.rightPadding - t=('%.2f%%'%self.xValueAxis.valueMax) - tx=tx+(_maxWidth(t, self.yValueAxis.labels.fontName, self.yValueAxis.labels.fontSize)) - ty = self.bottomPadding+self.xValueAxis.tickDown+(self.xValueAxis.labels.dy*2)+(self.xValueAxis.labels.fontSize*2) - ty=ty+self.yValueAxis.labels.fontSize+self.height+self.topPadding - #print (tx, ty) - return (tx,ty) - - def demo(self,drawing=None): - if not drawing: - tx,ty=self._getDrawingDimensions() - drawing = Drawing(tx,ty) - drawing.add(self.draw()) - return drawing - - def draw(self): - ascent=getFont(self.xValueAxis.labels.fontName).face.ascent - if ascent==0: - ascent=0.718 # default (from helvetica) - ascent=ascent*self.xValueAxis.labels.fontSize # normalize - - #basic LinePlot - does the Axes, Ticks etc - lp = LinePlot.draw(self) - - xLabel = self.xLabel - if xLabel: #Overall label for the X-axis - xl=Label() - xl.x = (self.x+self.width)/2.0 - xl.y = 0 - xl.fontName = self.xValueAxis.labels.fontName - xl.fontSize = self.xValueAxis.labels.fontSize - xl.setText(xLabel) - lp.add(xl) - - yLabel = self.yLabel - if yLabel: #Overall label for the Y-axis - yl=Label() - yl.angle = 90 - yl.x = 0 - yl.y = (self.y+self.height/2.0) - yl.fontName = self.yValueAxis.labels.fontName - yl.fontSize = self.yValueAxis.labels.fontSize - yl.setText(yLabel) - lp.add(yl) - - # do a bounding box - in the same style as the axes - if self.outerBorderOn: - lp.add(Rect(self.x, self.y, self.width, self.height, - strokeColor = self.outerBorderColor, - strokeWidth = self.yValueAxis.strokeWidth, - fillColor = None)) - - lp.shift(self.leftPadding, self.bottomPadding) - - return lp - -def sample1a(): - "A line plot with non-equidistant points in x-axis." - - drawing = Drawing(400, 200) - - data = [ - ((1,1), (2,2), (2.5,1), (3,3), (4,5)), - ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) - ] - - lp = LinePlot() - - lp.x = 50 - lp.y = 50 - lp.height = 125 - lp.width = 300 - lp.data = data - lp.joinedLines = 1 - lp.strokeColor = colors.black - - lp.lines.symbol = makeMarker('UK_Flag') - - lp.lines[0].strokeWidth = 2 - lp.lines[1].strokeWidth = 4 - - lp.xValueAxis.valueMin = 0 - lp.xValueAxis.valueMax = 5 - lp.xValueAxis.valueStep = 1 - - lp.yValueAxis.valueMin = 0 - lp.yValueAxis.valueMax = 7 - lp.yValueAxis.valueStep = 1 - - drawing.add(lp) - - return drawing - - -def sample1b(): - "A line plot with non-equidistant points in x-axis." - - drawing = Drawing(400, 200) - - data = [ - ((1,1), (2,2), (2.5,1), (3,3), (4,5)), - ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) - ] - - lp = LinePlot() - - lp.x = 50 - lp.y = 50 - lp.height = 125 - lp.width = 300 - lp.data = data - lp.joinedLines = 1 - lp.lines.symbol = makeMarker('Circle') - lp.lineLabelFormat = '%2.0f' - lp.strokeColor = colors.black - - lp.xValueAxis.valueMin = 0 - lp.xValueAxis.valueMax = 5 - lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5] - lp.xValueAxis.labelTextFormat = '%2.1f' - - lp.yValueAxis.valueMin = 0 - lp.yValueAxis.valueMax = 7 - lp.yValueAxis.valueStep = 1 - - drawing.add(lp) - - return drawing - - -def sample1c(): - "A line plot with non-equidistant points in x-axis." - - drawing = Drawing(400, 200) - - data = [ - ((1,1), (2,2), (2.5,1), (3,3), (4,5)), - ((1,2), (2,3), (2.5,2), (3.5,5), (4,6)) - ] - - lp = LinePlot() - - lp.x = 50 - lp.y = 50 - lp.height = 125 - lp.width = 300 - lp.data = data - lp.joinedLines = 1 - lp.lines[0].symbol = makeMarker('FilledCircle') - lp.lines[1].symbol = makeMarker('Circle') - lp.lineLabelFormat = '%2.0f' - lp.strokeColor = colors.black - - lp.xValueAxis.valueMin = 0 - lp.xValueAxis.valueMax = 5 - lp.xValueAxis.valueSteps = [1, 2, 2.5, 3, 4, 5] - lp.xValueAxis.labelTextFormat = '%2.1f' - - lp.yValueAxis.valueMin = 0 - lp.yValueAxis.valueMax = 7 - lp.yValueAxis.valueSteps = [1, 2, 3, 5, 6] - - drawing.add(lp) - - return drawing - - -def preprocessData(series): - "Convert date strings into seconds and multiply values by 100." - - return map(lambda x: (str2seconds(x[0]), x[1]*100), series) - - -def sample2(): - "A line plot with non-equidistant points in x-axis." - - drawing = Drawing(400, 200) - - data = [ - (('25/11/1991',1), - ('30/11/1991',1.000933333), - ('31/12/1991',1.0062), - ('31/01/1992',1.0112), - ('29/02/1992',1.0158), - ('31/03/1992',1.020733333), - ('30/04/1992',1.026133333), - ('31/05/1992',1.030266667), - ('30/06/1992',1.034466667), - ('31/07/1992',1.038733333), - ('31/08/1992',1.0422), - ('30/09/1992',1.045533333), - ('31/10/1992',1.049866667), - ('30/11/1992',1.054733333), - ('31/12/1992',1.061), - ), - ] - - data[0] = preprocessData(data[0]) - - lp = LinePlot() - - lp.x = 50 - lp.y = 50 - lp.height = 125 - lp.width = 300 - lp.data = data - lp.joinedLines = 1 - lp.lines.symbol = makeMarker('FilledDiamond') - lp.strokeColor = colors.black - - start = mktime(mkTimeTuple('25/11/1991')) - t0 = mktime(mkTimeTuple('30/11/1991')) - t1 = mktime(mkTimeTuple('31/12/1991')) - t2 = mktime(mkTimeTuple('31/03/1992')) - t3 = mktime(mkTimeTuple('30/06/1992')) - t4 = mktime(mkTimeTuple('30/09/1992')) - end = mktime(mkTimeTuple('31/12/1992')) - lp.xValueAxis.valueMin = start - lp.xValueAxis.valueMax = end - lp.xValueAxis.valueSteps = [start, t0, t1, t2, t3, t4, end] - lp.xValueAxis.labelTextFormat = seconds2str - lp.xValueAxis.labels[1].dy = -20 - lp.xValueAxis.labels[2].dy = -35 - - lp.yValueAxis.labelTextFormat = '%4.2f' - lp.yValueAxis.valueMin = 100 - lp.yValueAxis.valueMax = 110 - lp.yValueAxis.valueStep = 2 - - drawing.add(lp) - - return drawing diff --git a/bin/reportlab/graphics/charts/markers.py b/bin/reportlab/graphics/charts/markers.py deleted file mode 100644 index 6f0168c4cf3..00000000000 --- a/bin/reportlab/graphics/charts/markers.py +++ /dev/null @@ -1,81 +0,0 @@ -#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/graphics/charts/markers.py -""" -This modules defines a collection of markers used in charts. - -The make* functions return a simple shape or a widget as for -the smiley. -""" -__version__=''' $Id$ ''' -from reportlab.lib import colors -from reportlab.graphics.shapes import Rect, Line, Circle, Polygon -from reportlab.graphics.widgets.signsandsymbols import SmileyFace - - -def makeEmptySquare(x, y, size, color): - "Make an empty square marker." - - d = size/2.0 - rect = Rect(x-d, y-d, 2*d, 2*d) - rect.strokeColor = color - rect.fillColor = None - - return rect - - -def makeFilledSquare(x, y, size, color): - "Make a filled square marker." - - d = size/2.0 - rect = Rect(x-d, y-d, 2*d, 2*d) - rect.strokeColor = color - rect.fillColor = color - - return rect - - -def makeFilledDiamond(x, y, size, color): - "Make a filled diamond marker." - - d = size/2.0 - poly = Polygon((x-d,y, x,y+d, x+d,y, x,y-d)) - poly.strokeColor = color - poly.fillColor = color - - return poly - - -def makeEmptyCircle(x, y, size, color): - "Make a hollow circle marker." - - d = size/2.0 - circle = Circle(x, y, d) - circle.strokeColor = color - circle.fillColor = colors.white - - return circle - - -def makeFilledCircle(x, y, size, color): - "Make a hollow circle marker." - - d = size/2.0 - circle = Circle(x, y, d) - circle.strokeColor = color - circle.fillColor = color - - return circle - - -def makeSmiley(x, y, size, color): - "Make a smiley marker." - - d = size - s = SmileyFace() - s.fillColor = color - s.x = x-d - s.y = y-d - s.size = d*2 - - return s \ No newline at end of file diff --git a/bin/reportlab/graphics/charts/piecharts.py b/bin/reportlab/graphics/charts/piecharts.py deleted file mode 100644 index afa688ce45e..00000000000 --- a/bin/reportlab/graphics/charts/piecharts.py +++ /dev/null @@ -1,863 +0,0 @@ -#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/graphics/charts/piecharts.py -# experimental pie chart script. Two types of pie - one is a monolithic -#widget with all top-level properties, the other delegates most stuff to -#a wedges collection whic lets you customize the group or every individual -#wedge. - -"""Basic Pie Chart class. - -This permits you to customize and pop out individual wedges; -supports elliptical and circular pies. -""" -__version__=''' $Id$ ''' - -import copy -from math import sin, cos, pi - -from reportlab.lib import colors -from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\ - isListOfNumbers, isColorOrNone, isString,\ - isListOfStringsOrNone, OneOf, SequenceOf,\ - isBoolean, isListOfColors, isNumberOrNone,\ - isNoneOrListOfNoneOrStrings, isTextAnchor,\ - isNoneOrListOfNoneOrNumbers, isBoxAnchor,\ - isStringOrNone -from reportlab.lib.attrmap import * -from reportlab.pdfgen.canvas import Canvas -from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, ArcPath, Polygon -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder -from textlabels import Label - -_ANGLE2BOXANCHOR={0:'w', 45:'sw', 90:'s', 135:'se', 180:'e', 225:'ne', 270:'n', 315: 'nw', -45: 'nw'} -class WedgeLabel(Label): - def _checkDXY(self,ba): - pass - def _getBoxAnchor(self): - na = (int((self._pmv%360)/45.)*45)%360 - if not (na % 90): # we have a right angle case - da = (self._pmv - na) % 360 - if abs(da)>5: - na = na + (da>0 and 45 or -45) - ba = _ANGLE2BOXANCHOR[na] - self._checkDXY(ba) - return ba - -class WedgeProperties(PropHolder): - """This holds descriptive information about the wedges in a pie chart. - - It is not to be confused with the 'wedge itself'; this just holds - a recipe for how to format one, and does not allow you to hack the - angles. It can format a genuine Wedge object for you with its - format method. - """ - - _attrMap = AttrMap( - strokeWidth = AttrMapValue(isNumber), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone), - popout = AttrMapValue(isNumber), - fontName = AttrMapValue(isString), - fontSize = AttrMapValue(isNumber), - fontColor = AttrMapValue(isColorOrNone), - labelRadius = AttrMapValue(isNumber), - label_dx = AttrMapValue(isNumber), - label_dy = AttrMapValue(isNumber), - label_angle = AttrMapValue(isNumber), - label_boxAnchor = AttrMapValue(isBoxAnchor), - label_boxStrokeColor = AttrMapValue(isColorOrNone), - label_boxStrokeWidth = AttrMapValue(isNumber), - label_boxFillColor = AttrMapValue(isColorOrNone), - label_strokeColor = AttrMapValue(isColorOrNone), - label_strokeWidth = AttrMapValue(isNumber), - label_text = AttrMapValue(isStringOrNone), - label_leading = AttrMapValue(isNumberOrNone), - label_width = AttrMapValue(isNumberOrNone), - label_maxWidth = AttrMapValue(isNumberOrNone), - label_height = AttrMapValue(isNumberOrNone), - label_textAnchor = AttrMapValue(isTextAnchor), - label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), - label_topPadding = AttrMapValue(isNumber,'padding at top of box'), - label_leftPadding = AttrMapValue(isNumber,'padding at left of box'), - label_rightPadding = AttrMapValue(isNumber,'padding at right of box'), - label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), - ) - - def __init__(self): - self.strokeWidth = 0 - self.fillColor = None - self.strokeColor = STATE_DEFAULTS["strokeColor"] - self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] - self.popout = 0 - self.fontName = STATE_DEFAULTS["fontName"] - self.fontSize = STATE_DEFAULTS["fontSize"] - self.fontColor = STATE_DEFAULTS["fillColor"] - self.labelRadius = 1.2 - self.label_dx = self.label_dy = self.label_angle = 0 - self.label_text = None - self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0 - self.label_boxAnchor = 'c' - self.label_boxStrokeColor = None #boxStroke - self.label_boxStrokeWidth = 0.5 #boxStrokeWidth - self.label_boxFillColor = None - self.label_strokeColor = None - self.label_strokeWidth = 0.1 - self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None - self.label_textAnchor = 'start' - self.label_visible = 1 - -def _addWedgeLabel(self,text,add,angle,labelX,labelY,wedgeStyle,labelClass=WedgeLabel): - # now draw a label - if self.simpleLabels: - theLabel = String(labelX, labelY, text) - theLabel.textAnchor = "middle" - else: - theLabel = labelClass() - theLabel._pmv = angle - theLabel.x = labelX - theLabel.y = labelY - theLabel.dx = wedgeStyle.label_dx - theLabel.dy = wedgeStyle.label_dy - theLabel.angle = wedgeStyle.label_angle - theLabel.boxAnchor = wedgeStyle.label_boxAnchor - theLabel.boxStrokeColor = wedgeStyle.label_boxStrokeColor - theLabel.boxStrokeWidth = wedgeStyle.label_boxStrokeWidth - theLabel.boxFillColor = wedgeStyle.label_boxFillColor - theLabel.strokeColor = wedgeStyle.label_strokeColor - theLabel.strokeWidth = wedgeStyle.label_strokeWidth - _text = wedgeStyle.label_text - if _text is None: _text = text - theLabel._text = _text - theLabel.leading = wedgeStyle.label_leading - theLabel.width = wedgeStyle.label_width - theLabel.maxWidth = wedgeStyle.label_maxWidth - theLabel.height = wedgeStyle.label_height - theLabel.textAnchor = wedgeStyle.label_textAnchor - theLabel.visible = wedgeStyle.label_visible - theLabel.topPadding = wedgeStyle.label_topPadding - theLabel.leftPadding = wedgeStyle.label_leftPadding - theLabel.rightPadding = wedgeStyle.label_rightPadding - theLabel.bottomPadding = wedgeStyle.label_bottomPadding - theLabel.fontSize = wedgeStyle.fontSize - theLabel.fontName = wedgeStyle.fontName - theLabel.fillColor = wedgeStyle.fontColor - add(theLabel) - -def _fixLabels(labels,n): - if labels is None: - labels = [''] * n - else: - i = n-len(labels) - if i>0: labels = labels + ['']*i - return labels - -class Pie(Widget): - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc='X position of the chart within its container.'), - y = AttrMapValue(isNumber, desc='Y position of the chart within its container.'), - width = AttrMapValue(isNumber, desc='width of pie bounding box. Need not be same as width.'), - height = AttrMapValue(isNumber, desc='height of pie bounding box. Need not be same as height.'), - data = AttrMapValue(isListOfNumbers, desc='list of numbers defining wedge sizes; need not sum to 1'), - labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"), - startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"), - direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"), - slices = AttrMapValue(None, desc="collection of wedge descriptor objects"), - simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use String not super duper WedgeLabel"), - other_threshold = AttrMapValue(isNumber, desc='A value for doing thresh holding, not used yet.'), - ) - other_threshold=None - - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 100 - self.height = 100 - self.data = [1] - self.labels = None # or list of strings - self.startAngle = 90 - self.direction = "clockwise" - self.simpleLabels = 1 - - self.slices = TypedPropertyCollection(WedgeProperties) - self.slices[0].fillColor = colors.darkcyan - self.slices[1].fillColor = colors.blueviolet - self.slices[2].fillColor = colors.blue - self.slices[3].fillColor = colors.cyan - - def demo(self): - d = Drawing(200, 100) - - pc = Pie() - pc.x = 50 - pc.y = 10 - pc.width = 100 - pc.height = 80 - 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 = 10 - pc.slices[3].strokeWidth = 2 - pc.slices[3].strokeDashArray = [2,2] - pc.slices[3].labelRadius = 1.75 - pc.slices[3].fontColor = colors.red - pc.slices[0].fillColor = colors.darkcyan - pc.slices[1].fillColor = colors.blueviolet - pc.slices[2].fillColor = colors.blue - pc.slices[3].fillColor = colors.cyan - pc.slices[4].fillColor = colors.aquamarine - pc.slices[5].fillColor = colors.cadetblue - pc.slices[6].fillColor = colors.lightcoral - - d.add(pc) - return d - - def normalizeData(self): - from operator import add - data = self.data - self._sum = sum = float(reduce(add,data,0)) - return abs(sum)>=1e-8 and map(lambda x,f=360./sum: f*x, data) or len(data)*[0] - - def makeWedges(self): - # normalize slice data - normData = self.normalizeData() - n = len(normData) - labels = _fixLabels(self.labels,n) - - xradius = self.width/2.0 - yradius = self.height/2.0 - centerx = self.x + xradius - centery = self.y + yradius - - if self.direction == "anticlockwise": - whichWay = 1 - else: - whichWay = -1 - - g = Group() - i = 0 - styleCount = len(self.slices) - - startAngle = self.startAngle #% 360 - for angle in normData: - endAngle = (startAngle + (angle * whichWay)) #% 360 - if abs(startAngle-endAngle)>=1e-5: - if startAngle < endAngle: - a1 = startAngle - a2 = endAngle - else: - a1 = endAngle - a2 = startAngle - - #if we didn't use %stylecount here we'd end up with the later wedges - #all having the default style - wedgeStyle = self.slices[i%styleCount] - - # is it a popout? - cx, cy = centerx, centery - if wedgeStyle.popout <> 0: - # pop out the wedge - averageAngle = (a1+a2)/2.0 - aveAngleRadians = averageAngle * pi/180.0 - popdistance = wedgeStyle.popout - cx = centerx + popdistance * cos(aveAngleRadians) - cy = centery + popdistance * sin(aveAngleRadians) - - if n > 1: - theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius) - elif n==1: - theWedge = Ellipse(cx, cy, xradius, yradius) - - theWedge.fillColor = wedgeStyle.fillColor - theWedge.strokeColor = wedgeStyle.strokeColor - theWedge.strokeWidth = wedgeStyle.strokeWidth - theWedge.strokeDashArray = wedgeStyle.strokeDashArray - - g.add(theWedge) - text = labels[i] - if text: - averageAngle = (a1+a2)/2.0 - aveAngleRadians = averageAngle*pi/180.0 - labelRadius = wedgeStyle.labelRadius - labelX = cx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius) - labelY = cy + (0.5 * self.height * sin(aveAngleRadians) * labelRadius) - _addWedgeLabel(self,text,g.add,averageAngle,labelX,labelY,wedgeStyle) - - startAngle = endAngle - i = i + 1 - - return g - - def draw(self): - g = Group() - g.add(self.makeWedges()) - return g - -class LegendedPie(Pie): - """Pie with a two part legend (one editable with swatches, one hidden without swatches).""" - - _attrMap = AttrMap(BASE=Pie, - drawLegend = AttrMapValue(isBoolean, desc="If true then create and draw legend"), - legend1 = AttrMapValue(None, desc="Handle to legend for pie"), - legendNumberFormat = AttrMapValue(None, desc="Formatting routine for number on right hand side of legend."), - legendNumberOffset = AttrMapValue(isNumber, desc="Horizontal space between legend and numbers on r/hand side"), - pieAndLegend_colors = AttrMapValue(isListOfColors, desc="Colours used for both swatches and pie"), - legend_names = AttrMapValue(isNoneOrListOfNoneOrStrings, desc="Names used in legend (or None)"), - legend_data = AttrMapValue(isNoneOrListOfNoneOrNumbers, desc="Numbers used on r/hand side of legend (or None)"), - leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'), - rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'), - topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'), - bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'), - ) - - def __init__(self): - Pie.__init__(self) - self.x = 0 - self.y = 0 - self.height = 100 - self.width = 100 - self.data = [38.4, 20.7, 18.9, 15.4, 6.6] - self.labels = None - self.direction = 'clockwise' - PCMYKColor, black = colors.PCMYKColor, colors.black - self.pieAndLegend_colors = [PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV'), - PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV'), - PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=75), - PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=75), - PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=50), - PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=50)] - - #Allows us up to six 'wedges' to be coloured - self.slices[0].fillColor=self.pieAndLegend_colors[0] - self.slices[1].fillColor=self.pieAndLegend_colors[1] - self.slices[2].fillColor=self.pieAndLegend_colors[2] - self.slices[3].fillColor=self.pieAndLegend_colors[3] - self.slices[4].fillColor=self.pieAndLegend_colors[4] - self.slices[5].fillColor=self.pieAndLegend_colors[5] - - self.slices.strokeWidth = 0.75 - self.slices.strokeColor = black - - legendOffset = 17 - self.legendNumberOffset = 51 - self.legendNumberFormat = '%.1f%%' - self.legend_data = self.data - - #set up the legends - from reportlab.graphics.charts.legends import Legend - self.legend1 = Legend() - self.legend1.x = self.width+legendOffset - self.legend1.y = self.height - self.legend1.deltax = 5.67 - self.legend1.deltay = 14.17 - self.legend1.dxTextSpace = 11.39 - self.legend1.dx = 5.67 - self.legend1.dy = 5.67 - self.legend1.columnMaximum = 7 - self.legend1.alignment = 'right' - self.legend_names = ['AAA:','AA:','A:','BBB:','NR:'] - for f in range(0,len(self.data)): - self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f])) - self.legend1.fontName = "Helvetica-Bold" - self.legend1.fontSize = 6 - self.legend1.strokeColor = black - self.legend1.strokeWidth = 0.5 - - self._legend2 = Legend() - self._legend2.dxTextSpace = 0 - self._legend2.dx = 0 - self._legend2.alignment = 'right' - self._legend2.fontName = "Helvetica-Oblique" - self._legend2.fontSize = 6 - self._legend2.strokeColor = self.legend1.strokeColor - - self.leftPadding = 5 - self.rightPadding = 5 - self.topPadding = 5 - self.bottomPadding = 5 - self.drawLegend = 1 - - def draw(self): - if self.drawLegend: - self.legend1.colorNamePairs = [] - self._legend2.colorNamePairs = [] - for f in range(0,len(self.data)): - if self.legend_names == None: - self.slices[f].fillColor = self.pieAndLegend_colors[f] - self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], None)) - else: - try: - self.slices[f].fillColor = self.pieAndLegend_colors[f] - self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f])) - except IndexError: - self.slices[f].fillColor = self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)] - self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)], self.legend_names[f])) - if self.legend_data != None: - ldf = self.legend_data[f] - lNF = self.legendNumberFormat - from types import StringType - if ldf is None or lNF is None: - pass - elif type(lNF) is StringType: - ldf = lNF % ldf - elif callable(lNF): - ldf = lNF(ldf) - else: - p = self.legend_names[f] - if self.legend_data != None: - ldf = self.legend_data[f] - lNF = self.legendNumberFormat - if ldf is None or lNF is None: - pass - elif type(lNF) is StringType: - ldf = lNF % ldf - elif callable(lNF): - ldf = lNF(ldf) - else: - msg = "Unknown formatter type %s, expected string or function" % self.legendNumberFormat - raise Exception, msg - self._legend2.colorNamePairs.append((None,ldf)) - p = Pie.draw(self) - if self.drawLegend: - p.add(self.legend1) - #hide from user - keeps both sides lined up! - self._legend2.x = self.legend1.x+self.legendNumberOffset - self._legend2.y = self.legend1.y - self._legend2.deltax = self.legend1.deltax - self._legend2.deltay = self.legend1.deltay - self._legend2.dy = self.legend1.dy - self._legend2.columnMaximum = self.legend1.columnMaximum - p.add(self._legend2) - p.shift(self.leftPadding, self.bottomPadding) - return p - - def _getDrawingDimensions(self): - tx = self.rightPadding - if self.drawLegend: - tx = tx+self.legend1.x+self.legendNumberOffset #self._legend2.x - tx = tx + self._legend2._calculateMaxWidth(self._legend2.colorNamePairs) - ty = self.bottomPadding+self.height+self.topPadding - return (tx,ty) - - def demo(self, drawing=None): - if not drawing: - tx,ty = self._getDrawingDimensions() - drawing = Drawing(tx, ty) - drawing.add(self.draw()) - return drawing - -from utils3d import _getShaded, _2rad, _360, _pi_2, _2pi -class Wedge3dProperties(PropHolder): - """This holds descriptive information about the wedges in a pie chart. - - It is not to be confused with the 'wedge itself'; this just holds - a recipe for how to format one, and does not allow you to hack the - angles. It can format a genuine Wedge object for you with its - format method. - """ - _attrMap = AttrMap( - fillColor = AttrMapValue(isColorOrNone), - fillColorShaded = AttrMapValue(isColorOrNone), - fontColor = AttrMapValue(isColorOrNone), - fontName = AttrMapValue(isString), - fontSize = AttrMapValue(isNumber), - label_angle = AttrMapValue(isNumber), - label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), - label_boxAnchor = AttrMapValue(isBoxAnchor), - label_boxFillColor = AttrMapValue(isColorOrNone), - label_boxStrokeColor = AttrMapValue(isColorOrNone), - label_boxStrokeWidth = AttrMapValue(isNumber), - label_dx = AttrMapValue(isNumber), - label_dy = AttrMapValue(isNumber), - label_height = AttrMapValue(isNumberOrNone), - label_leading = AttrMapValue(isNumberOrNone), - label_leftPadding = AttrMapValue(isNumber,'padding at left of box'), - label_maxWidth = AttrMapValue(isNumberOrNone), - label_rightPadding = AttrMapValue(isNumber,'padding at right of box'), - label_strokeColor = AttrMapValue(isColorOrNone), - label_strokeWidth = AttrMapValue(isNumber), - label_text = AttrMapValue(isStringOrNone), - label_textAnchor = AttrMapValue(isTextAnchor), - label_topPadding = AttrMapValue(isNumber,'padding at top of box'), - label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), - label_width = AttrMapValue(isNumberOrNone), - labelRadius = AttrMapValue(isNumber), - popout = AttrMapValue(isNumber), - shading = AttrMapValue(isNumber), - strokeColor = AttrMapValue(isColorOrNone), - strokeColorShaded = AttrMapValue(isColorOrNone), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone), - strokeWidth = AttrMapValue(isNumber), - visible = AttrMapValue(isBoolean,'set to false to skip displaying'), - ) - - def __init__(self): - self.strokeWidth = 0 - self.shading = 0.3 - self.visible = 1 - self.strokeColorShaded = self.fillColorShaded = self.fillColor = None - self.strokeColor = STATE_DEFAULTS["strokeColor"] - self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] - self.popout = 0 - self.fontName = STATE_DEFAULTS["fontName"] - self.fontSize = STATE_DEFAULTS["fontSize"] - self.fontColor = STATE_DEFAULTS["fillColor"] - self.labelRadius = 1.2 - self.label_dx = self.label_dy = self.label_angle = 0 - self.label_text = None - self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0 - self.label_boxAnchor = 'c' - self.label_boxStrokeColor = None #boxStroke - self.label_boxStrokeWidth = 0.5 #boxStrokeWidth - self.label_boxFillColor = None - self.label_strokeColor = None - self.label_strokeWidth = 0.1 - self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None - self.label_textAnchor = 'start' - self.label_visible = 1 - -class _SL3D: - def __init__(self,lo,hi): - if lo<0: - lo += 360 - hi += 360 - self.lo = lo - self.hi = hi - self.mid = (lo+hi)*0.5 - - def __str__(self): - return '_SL3D(%.2f,%.2f)' % (self.lo,self.hi) - -_270r = _2rad(270) -class Pie3d(Pie): - _attrMap = AttrMap(BASE=Pie, - perspective = AttrMapValue(isNumber, desc='A flattening parameter.'), - depth_3d = AttrMapValue(isNumber, desc='depth of the pie.'), - angle_3d = AttrMapValue(isNumber, desc='The view angle.'), - ) - perspective = 70 - depth_3d = 25 - angle_3d = 180 - - def _popout(self,i): - return self.slices[i].popout or 0 - - def CX(self, i,d ): - return self._cx+(d and self._xdepth_3d or 0)+self._popout(i)*cos(_2rad(self._sl3d[i].mid)) - def CY(self,i,d): - return self._cy+(d and self._ydepth_3d or 0)+self._popout(i)*sin(_2rad(self._sl3d[i].mid)) - def OX(self,i,o,d): - return self.CX(i,d)+self._radiusx*cos(_2rad(o)) - def OY(self,i,o,d): - return self.CY(i,d)+self._radiusy*sin(_2rad(o)) - - def rad_dist(self,a): - _3dva = self._3dva - return min(abs(a-_3dva),abs(a-_3dva+360)) - - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 300 - self.height = 200 - self.data = [12.50,20.10,2.00,22.00,5.00,18.00,13.00] - self.labels = None # or list of strings - self.startAngle = 90 - self.direction = "clockwise" - self.simpleLabels = 1 - self.slices = TypedPropertyCollection(Wedge3dProperties) - self.slices[0].fillColor = colors.darkcyan - self.slices[1].fillColor = colors.blueviolet - self.slices[2].fillColor = colors.blue - self.slices[3].fillColor = colors.cyan - self.slices[4].fillColor = colors.azure - self.slices[5].fillColor = colors.crimson - self.slices[6].fillColor = colors.darkviolet - - def _fillSide(self,L,i,angle,strokeColor,strokeWidth,fillColor): - rd = self.rad_dist(angle) - if rd0: angle0, angle1 = angle1, angle0 - _sl3d.append(_SL3D(angle0,angle1)) - #print '%d: %.2f %.2f --> %s' %(len(_sl3d)-1,angle0,angle1,_sl3d[-1]) - - labels = _fixLabels(self.labels,n) - a0 = _3d_angle - a1 = _3d_angle+180 - T = [] - S = [] - L = [] - - class WedgeLabel3d(WedgeLabel): - def _checkDXY(self,ba): - if ba[0]=='n': - if not hasattr(self,'_ody'): - self._ody = self.dy - self.dy = -self._ody + self._ydepth_3d - WedgeLabel3d._ydepth_3d = self._ydepth_3d - - for i in xrange(n): - style = slices[i] - if not style.visible: continue - sl = _sl3d[i] - lo = angle0 = sl.lo - hi = angle1 = sl.hi - if abs(hi-lo)<=1e-7: continue - fillColor = _getShaded(style.fillColor,style.fillColorShaded,style.shading) - strokeColor = _getShaded(style.strokeColor,style.strokeColorShaded,style.shading) or fillColor - strokeWidth = style.strokeWidth - cx0 = CX(i,0) - cy0 = CY(i,0) - cx1 = CX(i,1) - cy1 = CY(i,1) - #background shaded pie bottom - g.add(Wedge(cx1,cy1,radiusx, lo, hi,yradius=radiusy, - strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor, - strokeLineJoin=1)) - #connect to top - if lo < a0 < hi: angle0 = a0 - if lo < a1 < hi: angle1 = a1 - if 1: - p = ArcPath(strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1) - p.addArc(cx1,cy1,radiusx,angle0,angle1,yradius=radiusy,moveTo=1) - p.lineTo(OX(i,angle1,0),OY(i,angle1,0)) - p.addArc(cx0,cy0,radiusx,angle0,angle1,yradius=radiusy,reverse=1) - p.closePath() - if angle0<=_3dva and angle1>=_3dva: - rd = 0 - else: - rd = min(rad_dist(angle0),rad_dist(angle1)) - S.append((rd,p)) - _fillSide(S,i,lo,strokeColor,strokeWidth,fillColor) - _fillSide(S,i,hi,strokeColor,strokeWidth,fillColor) - - #bright shaded top - fillColor = style.fillColor - strokeColor = style.strokeColor or fillColor - T.append(Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy, - strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1)) - - text = labels[i] - if text: - rat = style.labelRadius - self._radiusx *= rat - self._radiusy *= rat - mid = sl.mid - _addWedgeLabel(self,text,L.append,mid,OX(i,mid,0),OY(i,mid,0),style,labelClass=WedgeLabel3d) - self._radiusx = radiusx - self._radiusy = radiusy - - S.sort(lambda a,b: -cmp(a[0],b[0])) - map(g.add,map(lambda x:x[1],S)+T+L) - return g - - def demo(self): - d = Drawing(200, 100) - - pc = Pie() - pc.x = 50 - pc.y = 10 - pc.width = 100 - pc.height = 80 - 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 = 10 - pc.slices[3].strokeWidth = 2 - pc.slices[3].strokeDashArray = [2,2] - pc.slices[3].labelRadius = 1.75 - pc.slices[3].fontColor = colors.red - pc.slices[0].fillColor = colors.darkcyan - pc.slices[1].fillColor = colors.blueviolet - pc.slices[2].fillColor = colors.blue - pc.slices[3].fillColor = colors.cyan - pc.slices[4].fillColor = colors.aquamarine - pc.slices[5].fillColor = colors.cadetblue - pc.slices[6].fillColor = colors.lightcoral - self.slices[1].visible = 0 - self.slices[3].visible = 1 - self.slices[4].visible = 1 - self.slices[5].visible = 1 - self.slices[6].visible = 0 - - d.add(pc) - return d - - -def sample0a(): - "Make a degenerated pie chart with only one slice." - - d = Drawing(400, 200) - - pc = Pie() - pc.x = 150 - pc.y = 50 - pc.data = [10] - pc.labels = ['a'] - pc.slices.strokeWidth=1#0.5 - - d.add(pc) - - return d - - -def sample0b(): - "Make a degenerated pie chart with only one slice." - - d = Drawing(400, 200) - - pc = Pie() - pc.x = 150 - pc.y = 50 - pc.width = 120 - pc.height = 100 - pc.data = [10] - pc.labels = ['a'] - pc.slices.strokeWidth=1#0.5 - - d.add(pc) - - return d - - -def sample1(): - "Make a typical pie chart with with one slice treated in a special way." - - d = Drawing(400, 200) - - 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=1#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) - - return d - - -def sample2(): - "Make a pie chart with nine slices." - - d = Drawing(400, 200) - - pc = Pie() - pc.x = 125 - pc.y = 25 - pc.data = [0.31, 0.148, 0.108, - 0.076, 0.033, 0.03, - 0.019, 0.126, 0.15] - pc.labels = ['1', '2', '3', '4', '5', '6', '7', '8', 'X'] - - pc.width = 150 - pc.height = 150 - pc.slices.strokeWidth=1#0.5 - - pc.slices[0].fillColor = colors.steelblue - pc.slices[1].fillColor = colors.thistle - pc.slices[2].fillColor = colors.cornflower - pc.slices[3].fillColor = colors.lightsteelblue - pc.slices[4].fillColor = colors.aquamarine - pc.slices[5].fillColor = colors.cadetblue - pc.slices[6].fillColor = colors.lightcoral - pc.slices[7].fillColor = colors.tan - pc.slices[8].fillColor = colors.darkseagreen - - d.add(pc) - - return d - - -def sample3(): - "Make a pie chart with a very slim slice." - - d = Drawing(400, 200) - - pc = Pie() - pc.x = 125 - pc.y = 25 - - pc.data = [74, 1, 25] - - pc.width = 150 - pc.height = 150 - pc.slices.strokeWidth=1#0.5 - pc.slices[0].fillColor = colors.steelblue - pc.slices[1].fillColor = colors.thistle - pc.slices[2].fillColor = colors.cornflower - - d.add(pc) - - return d - - -def sample4(): - "Make a pie chart with several very slim slices." - - d = Drawing(400, 200) - - pc = Pie() - pc.x = 125 - pc.y = 25 - - pc.data = [74, 1, 1, 1, 1, 22] - - pc.width = 150 - pc.height = 150 - pc.slices.strokeWidth=1#0.5 - pc.slices[0].fillColor = colors.steelblue - pc.slices[1].fillColor = colors.thistle - pc.slices[2].fillColor = colors.cornflower - pc.slices[3].fillColor = colors.lightsteelblue - pc.slices[4].fillColor = colors.aquamarine - pc.slices[5].fillColor = colors.cadetblue - - d.add(pc) - - return d diff --git a/bin/reportlab/graphics/charts/slidebox.py b/bin/reportlab/graphics/charts/slidebox.py deleted file mode 100644 index 309fb07ba0d..00000000000 --- a/bin/reportlab/graphics/charts/slidebox.py +++ /dev/null @@ -1,186 +0,0 @@ -from reportlab.lib.colors import Color, white, black -from reportlab.graphics.charts.textlabels import Label -from reportlab.graphics.shapes import Polygon, Line, Circle, String, Drawing, PolyLine, Group, Rect -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection -from reportlab.lib.attrmap import * -from reportlab.lib.validators import * -from reportlab.lib.units import cm -from reportlab.pdfbase.pdfmetrics import stringWidth, getFont -from reportlab.graphics.widgets.grids import ShadedRect, Grid - -class SlideBox(Widget): - """Returns a slidebox widget""" - _attrMap = AttrMap( - labelFontName = AttrMapValue(isString, desc="Name of font used for the labels"), - labelFontSize = AttrMapValue(isNumber, desc="Size of font used for the labels"), - labelStrokeColor = AttrMapValue(isColorOrNone, desc="Colour for for number outlines"), - labelFillColor = AttrMapValue(isColorOrNone, desc="Colour for number insides"), - startColor = AttrMapValue(isColor, desc='Color of first box'), - endColor = AttrMapValue(isColor, desc='Color of last box'), - numberOfBoxes = AttrMapValue(isInt, desc='How many boxes there are'), - trianglePosition = AttrMapValue(isInt, desc='Which box is highlighted by the triangles'), - triangleHeight = AttrMapValue(isNumber, desc="Height of indicator triangles"), - triangleWidth = AttrMapValue(isNumber, desc="Width of indicator triangles"), - triangleFillColor = AttrMapValue(isColor, desc="Colour of indicator triangles"), - triangleStrokeColor = AttrMapValue(isColorOrNone, desc="Colour of indicator triangle outline"), - triangleStrokeWidth = AttrMapValue(isNumber, desc="Colour of indicator triangle outline"), - boxHeight = AttrMapValue(isNumber, desc="Height of the boxes"), - boxWidth = AttrMapValue(isNumber, desc="Width of the boxes"), - boxSpacing = AttrMapValue(isNumber, desc="Space between the boxes"), - boxOutlineColor = AttrMapValue(isColorOrNone, desc="Colour used to outline the boxes (if any)"), - boxOutlineWidth = AttrMapValue(isNumberOrNone, desc="Width of the box outline (if any)"), - leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'), - rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'), - topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'), - bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'), - background = AttrMapValue(isColorOrNone, desc='Colour of the background to the drawing (if any)'), - sourceLabelText = AttrMapValue(isNoneOrString, desc="Text used for the 'source' label (can be empty)"), - sourceLabelOffset = AttrMapValue(isNumber, desc='Padding at bottom of drawing'), - sourceLabelFontName = AttrMapValue(isString, desc="Name of font used for the 'source' label"), - sourceLabelFontSize = AttrMapValue(isNumber, desc="Font size for the 'source' label"), - sourceLabelFillColor = AttrMapValue(isColorOrNone, desc="Colour ink for the 'source' label (bottom right)"), - ) - - def __init__(self): - self.labelFontName = "Helvetica-Bold" - self.labelFontSize = 10 - self.labelStrokeColor = black - self.labelFillColor = white - self.startColor = colors.Color(232/255.0,224/255.0,119/255.0) - self.endColor = colors.Color(25/255.0,77/255.0,135/255.0) - self.numberOfBoxes = 7 - self.trianglePosition = 7 - self.triangleHeight = 0.12*cm - self.triangleWidth = 0.38*cm - self.triangleFillColor = white - self.triangleStrokeColor = black - self.triangleStrokeWidth = 0.58 - self.boxHeight = 0.55*cm - self.boxWidth = 0.73*cm - self.boxSpacing = 0.075*cm - self.boxOutlineColor = black - self.boxOutlineWidth = 0.58 - self.leftPadding=5 - self.rightPadding=5 - self.topPadding=5 - self.bottomPadding=5 - self.background=None - self.sourceLabelText = "Source: ReportLab" - self.sourceLabelOffset = 0.2*cm - self.sourceLabelFontName = "Helvetica-Oblique" - self.sourceLabelFontSize = 6 - self.sourceLabelFillColor = black - - def _getDrawingDimensions(self): - tx=(self.numberOfBoxes*self.boxWidth) - if self.numberOfBoxes>1: tx=tx+((self.numberOfBoxes-1)*self.boxSpacing) - tx=tx+self.leftPadding+self.rightPadding - ty=self.boxHeight+self.triangleHeight - ty=ty+self.topPadding+self.bottomPadding+self.sourceLabelOffset+self.sourceLabelFontSize - return (tx,ty) - - def _getColors(self): - # for calculating intermediate colors... - numShades = self.numberOfBoxes+1 - fillColorStart = self.startColor - fillColorEnd = self.endColor - colorsList =[] - - for i in range(0,numShades): - colorsList.append(colors.linearlyInterpolatedColor(fillColorStart, fillColorEnd, 0, numShades-1, i)) - return colorsList - - def demo(self,drawing=None): - from reportlab.lib import colors - if not drawing: - tx,ty=self._getDrawingDimensions() - drawing = Drawing(tx,ty) - drawing.add(self.draw()) - return drawing - - def draw(self): - g = Group() - ys = self.bottomPadding+(self.triangleHeight/2)+self.sourceLabelOffset+self.sourceLabelFontSize - if self.background: - x,y = self._getDrawingDimensions() - g.add(Rect(-self.leftPadding,-ys,x,y, - strokeColor=None, - strokeWidth=0, - fillColor=self.background)) - - ascent=getFont(self.labelFontName).face.ascent/1000. - if ascent==0: ascent=0.718 # default (from helvetica) - ascent=ascent*self.labelFontSize # normalize - - colorsList = self._getColors() - - # Draw the boxes - now uses ShadedRect from grids - x=0 - for f in range (0,self.numberOfBoxes): - sr=ShadedRect() - sr.x=x - sr.y=0 - sr.width=self.boxWidth - sr.height=self.boxHeight - sr.orientation = 'vertical' - sr.numShades = 30 - sr.fillColorStart = colorsList[f] - sr.fillColorEnd = colorsList[f+1] - sr.strokeColor = None - sr.strokeWidth = 0 - - g.add(sr) - - g.add(Rect(x,0,self.boxWidth,self.boxHeight, - strokeColor=self.boxOutlineColor, - strokeWidth=self.boxOutlineWidth, - fillColor=None)) - - g.add(String(x+self.boxWidth/2.,(self.boxHeight-ascent)/2., - text = str(f+1), - fillColor = self.labelFillColor, - strokeColor=self.labelStrokeColor, - textAnchor = 'middle', - fontName = self.labelFontName, - fontSize = self.labelFontSize)) - x=x+self.boxWidth+self.boxSpacing - - #do triangles - xt = (self.trianglePosition*self.boxWidth) - if self.trianglePosition>1: - xt = xt+(self.trianglePosition-1)*self.boxSpacing - xt = xt-(self.boxWidth/2) - g.add(Polygon( - strokeColor = self.triangleStrokeColor, - strokeWidth = self.triangleStrokeWidth, - fillColor = self.triangleFillColor, - points=[xt,self.boxHeight-(self.triangleHeight/2), - xt-(self.triangleWidth/2),self.boxHeight+(self.triangleHeight/2), - xt+(self.triangleWidth/2),self.boxHeight+(self.triangleHeight/2), - xt,self.boxHeight-(self.triangleHeight/2)])) - g.add(Polygon( - strokeColor = self.triangleStrokeColor, - strokeWidth = self.triangleStrokeWidth, - fillColor = self.triangleFillColor, - points=[xt,0+(self.triangleHeight/2), - xt-(self.triangleWidth/2),0-(self.triangleHeight/2), - xt+(self.triangleWidth/2),0-(self.triangleHeight/2), - xt,0+(self.triangleHeight/2)])) - - #source label - if self.sourceLabelText != None: - g.add(String(x-self.boxSpacing,0-(self.triangleHeight/2)-self.sourceLabelOffset-(self.sourceLabelFontSize), - text = self.sourceLabelText, - fillColor = self.sourceLabelFillColor, - textAnchor = 'end', - fontName = self.sourceLabelFontName, - fontSize = self.sourceLabelFontSize)) - - g.shift(self.leftPadding, ys) - - return g - - -if __name__ == "__main__": - d = SlideBox() - d.demo().save(fnRoot="slidebox") diff --git a/bin/reportlab/graphics/charts/spider.py b/bin/reportlab/graphics/charts/spider.py deleted file mode 100644 index 0e88ff1ff7f..00000000000 --- a/bin/reportlab/graphics/charts/spider.py +++ /dev/null @@ -1,353 +0,0 @@ - #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/graphics/charts/spider.py -# spider chart, also known as radar chart - -"""Spider Chart - -Normal use shows variation of 5-10 parameters against some 'norm' or target. -When there is more than one series, place the series with the largest -numbers first, as it will be overdrawn by each successive one. -""" -__version__=''' $Id$ ''' - -import copy -from math import sin, cos, pi - -from reportlab.lib import colors -from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\ - isListOfNumbers, isColorOrNone, isString,\ - isListOfStringsOrNone, OneOf, SequenceOf,\ - isBoolean, isListOfColors, isNumberOrNone,\ - isNoneOrListOfNoneOrStrings, isTextAnchor,\ - isNoneOrListOfNoneOrNumbers, isBoxAnchor,\ - isStringOrNone -from reportlab.lib.attrmap import * -from reportlab.pdfgen.canvas import Canvas -from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, Ellipse, \ - Wedge, String, STATE_DEFAULTS -from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder -from reportlab.graphics.charts.areas import PlotArea -from piecharts import WedgeLabel -from reportlab.graphics.widgets.markers import makeMarker, uSymbol2Symbol - -class StrandProperties(PropHolder): - """This holds descriptive information about concentric 'strands'. - - Line style, whether filled etc. - """ - - _attrMap = AttrMap( - strokeWidth = AttrMapValue(isNumber), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone), - fontName = AttrMapValue(isString), - fontSize = AttrMapValue(isNumber), - fontColor = AttrMapValue(isColorOrNone), - labelRadius = AttrMapValue(isNumber), - markers = AttrMapValue(isBoolean), - markerType = AttrMapValue(isAnything), - markerSize = AttrMapValue(isNumber), - label_dx = AttrMapValue(isNumber), - label_dy = AttrMapValue(isNumber), - label_angle = AttrMapValue(isNumber), - label_boxAnchor = AttrMapValue(isBoxAnchor), - label_boxStrokeColor = AttrMapValue(isColorOrNone), - label_boxStrokeWidth = AttrMapValue(isNumber), - label_boxFillColor = AttrMapValue(isColorOrNone), - label_strokeColor = AttrMapValue(isColorOrNone), - label_strokeWidth = AttrMapValue(isNumber), - label_text = AttrMapValue(isStringOrNone), - label_leading = AttrMapValue(isNumberOrNone), - label_width = AttrMapValue(isNumberOrNone), - label_maxWidth = AttrMapValue(isNumberOrNone), - label_height = AttrMapValue(isNumberOrNone), - label_textAnchor = AttrMapValue(isTextAnchor), - label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), - label_topPadding = AttrMapValue(isNumber,'padding at top of box'), - label_leftPadding = AttrMapValue(isNumber,'padding at left of box'), - label_rightPadding = AttrMapValue(isNumber,'padding at right of box'), - label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), - ) - - def __init__(self): - self.strokeWidth = 0 - self.fillColor = None - self.strokeColor = STATE_DEFAULTS["strokeColor"] - self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] - self.fontName = STATE_DEFAULTS["fontName"] - self.fontSize = STATE_DEFAULTS["fontSize"] - self.fontColor = STATE_DEFAULTS["fillColor"] - self.labelRadius = 1.2 - self.markers = 0 - self.markerType = None - self.markerSize = 0 - self.label_dx = self.label_dy = self.label_angle = 0 - self.label_text = None - self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0 - self.label_boxAnchor = 'c' - self.label_boxStrokeColor = None #boxStroke - self.label_boxStrokeWidth = 0.5 #boxStrokeWidth - self.label_boxFillColor = None - self.label_strokeColor = None - self.label_strokeWidth = 0.1 - self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None - self.label_textAnchor = 'start' - self.label_visible = 1 - -class SpiderChart(PlotArea): - _attrMap = AttrMap(BASE=PlotArea, - data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'), - labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"), - startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"), - direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"), - strands = AttrMapValue(None, desc="collection of strand descriptor objects"), - ) - - def __init__(self): - PlotArea.__init__(self) - - self.data = [[10,12,14,16,14,12], [6,8,10,12,9,11]] - self.labels = None # or list of strings - self.startAngle = 90 - self.direction = "clockwise" - - self.strands = TypedPropertyCollection(StrandProperties) - self.strands[0].fillColor = colors.cornsilk - self.strands[1].fillColor = colors.cyan - - - def demo(self): - d = Drawing(200, 100) - - sp = SpiderChart() - sp.x = 50 - sp.y = 10 - sp.width = 100 - sp.height = 80 - sp.data = [[10,12,14,16,18,20],[6,8,4,6,8,10]] - sp.labels = ['a','b','c','d','e','f'] - - d.add(sp) - return d - - def normalizeData(self, outer = 0.0): - """Turns data into normalized ones where each datum is < 1.0, - and 1.0 = maximum radius. Adds 10% at outside edge by default""" - data = self.data - theMax = 0.0 - for row in data: - for element in row: - assert element >=0, "Cannot do spider plots of negative numbers!" - if element > theMax: - theMax = element - theMax = theMax * (1.0+outer) - - scaled = [] - for row in data: - scaledRow = [] - for element in row: - scaledRow.append(element / theMax) - scaled.append(scaledRow) - return scaled - - - def draw(self): - # normalize slice data - g = self.makeBackground() or Group() - - xradius = self.width/2.0 - yradius = self.height/2.0 - self._radius = radius = min(xradius, yradius) - centerx = self.x + xradius - centery = self.y + yradius - - data = self.normalizeData() - - n = len(data[0]) - - #labels - if self.labels is None: - labels = [''] * n - else: - labels = self.labels - #there's no point in raising errors for less than enough errors if - #we silently create all for the extreme case of no labels. - i = n-len(labels) - if i>0: - labels = labels + ['']*i - - spokes = [] - csa = [] - angle = self.startAngle*pi/180 - direction = self.direction == "clockwise" and -1 or 1 - angleBetween = direction*(2 * pi)/n - markers = self.strands.markers - for i in xrange(n): - car = cos(angle)*radius - sar = sin(angle)*radius - csa.append((car,sar,angle)) - spoke = Line(centerx, centery, centerx + car, centery + sar, strokeWidth = 0.5) - #print 'added spoke (%0.2f, %0.2f) -> (%0.2f, %0.2f)' % (spoke.x1, spoke.y1, spoke.x2, spoke.y2) - spokes.append(spoke) - if labels: - si = self.strands[i] - text = si.label_text - if text is None: text = labels[i] - if text: - labelRadius = si.labelRadius - L = WedgeLabel() - L.x = centerx + labelRadius*car - L.y = centery + labelRadius*sar - L.boxAnchor = si.label_boxAnchor - L._pmv = angle*180/pi - L.dx = si.label_dx - L.dy = si.label_dy - L.angle = si.label_angle - L.boxAnchor = si.label_boxAnchor - L.boxStrokeColor = si.label_boxStrokeColor - L.boxStrokeWidth = si.label_boxStrokeWidth - L.boxFillColor = si.label_boxFillColor - L.strokeColor = si.label_strokeColor - L.strokeWidth = si.label_strokeWidth - L._text = text - L.leading = si.label_leading - L.width = si.label_width - L.maxWidth = si.label_maxWidth - L.height = si.label_height - L.textAnchor = si.label_textAnchor - L.visible = si.label_visible - L.topPadding = si.label_topPadding - L.leftPadding = si.label_leftPadding - L.rightPadding = si.label_rightPadding - L.bottomPadding = si.label_bottomPadding - L.fontName = si.fontName - L.fontSize = si.fontSize - L.fillColor = si.fontColor - spokes.append(L) - angle = angle + angleBetween - - # now plot the polygons - - rowIdx = 0 - for row in data: - # series plot - points = [] - car, sar = csa[-1][:2] - r = row[-1] - points.append(centerx+car*r) - points.append(centery+sar*r) - for i in xrange(n): - car, sar = csa[i][:2] - r = row[i] - points.append(centerx+car*r) - points.append(centery+sar*r) - - # make up the 'strand' - strand = Polygon(points) - strand.fillColor = self.strands[rowIdx].fillColor - strand.strokeColor = self.strands[rowIdx].strokeColor - strand.strokeWidth = self.strands[rowIdx].strokeWidth - strand.strokeDashArray = self.strands[rowIdx].strokeDashArray - - g.add(strand) - - # put in a marker, if it needs one - if markers: - if hasattr(self.strands[rowIdx], 'markerType'): - uSymbol = self.strands[rowIdx].markerType - elif hasattr(self.strands, 'markerType'): - uSymbol = self.strands.markerType - else: - uSymbol = None - m_x = centerx+car*r - m_y = centery+sar*r - m_size = self.strands[rowIdx].markerSize - m_fillColor = self.strands[rowIdx].fillColor - m_strokeColor = self.strands[rowIdx].strokeColor - m_strokeWidth = self.strands[rowIdx].strokeWidth - m_angle = 0 - if type(uSymbol) is type(''): - symbol = makeMarker(uSymbol, - size = m_size, - x = m_x, - y = m_y, - fillColor = m_fillColor, - strokeColor = m_strokeColor, - strokeWidth = m_strokeWidth, - angle = m_angle, - ) - else: - symbol = uSymbol2Symbol(uSymbol,m_x,m_y,m_fillColor) - for k,v in (('size', m_size), ('fillColor', m_fillColor), - ('x', m_x), ('y', m_y), - ('strokeColor',m_strokeColor), ('strokeWidth',m_strokeWidth), - ('angle',m_angle),): - try: - setattr(uSymbol,k,v) - except: - pass - g.add(symbol) - - rowIdx = rowIdx + 1 - - # spokes go over strands - for spoke in spokes: - g.add(spoke) - return g - -def sample1(): - "Make a simple spider chart" - - d = Drawing(400, 400) - - pc = SpiderChart() - pc.x = 50 - pc.y = 50 - pc.width = 300 - pc.height = 300 - pc.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8,3]] - pc.labels = ['a','b','c','d','e','f'] - pc.strands[2].fillColor=colors.palegreen - - d.add(pc) - - return d - - -def sample2(): - "Make a spider chart with markers, but no fill" - - d = Drawing(400, 400) - - pc = SpiderChart() - pc.x = 50 - pc.y = 50 - pc.width = 300 - pc.height = 300 - pc.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8,3]] - pc.labels = ['U','V','W','X','Y','Z'] - pc.strands.strokeWidth = 2 - pc.strands[0].fillColor = None - pc.strands[1].fillColor = None - pc.strands[2].fillColor = None - pc.strands[0].strokeColor = colors.red - pc.strands[1].strokeColor = colors.blue - pc.strands[2].strokeColor = colors.green - pc.strands.markers = 1 - pc.strands.markerType = "FilledDiamond" - pc.strands.markerSize = 6 - - d.add(pc) - - return d - - -if __name__=='__main__': - d = sample1() - from reportlab.graphics.renderPDF import drawToFile - drawToFile(d, 'spider.pdf') - d = sample2() - drawToFile(d, 'spider2.pdf') - #print 'saved spider.pdf' diff --git a/bin/reportlab/graphics/charts/textlabels.py b/bin/reportlab/graphics/charts/textlabels.py deleted file mode 100644 index 9a89258849a..00000000000 --- a/bin/reportlab/graphics/charts/textlabels.py +++ /dev/null @@ -1,440 +0,0 @@ -#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/graphics/charts/textlabels.py -__version__=''' $Id$ ''' -import string - -from reportlab.lib import colors -from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \ - isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString -from reportlab.lib.attrmap import * -from reportlab.pdfbase.pdfmetrics import stringWidth -from reportlab.graphics.shapes import Drawing, Group, Circle, Rect, String, STATE_DEFAULTS -from reportlab.graphics.shapes import _PATH_OP_ARG_COUNT, _PATH_OP_NAMES, definePath -from reportlab.graphics.widgetbase import Widget, PropHolder - -_gs = None -_A2BA= { - 'x': {0:'n', 45:'ne', 90:'e', 135:'se', 180:'s', 225:'sw', 270:'w', 315: 'nw', -45: 'nw'}, - 'y': {0:'e', 45:'se', 90:'s', 135:'sw', 180:'w', 225:'nw', 270:'n', 315: 'ne', -45: 'ne'}, - } -def _simpleSplit(txt,mW,SW): - L = [] - ws = SW(' ') - O = [] - w = -ws - for t in string.split(txt): - lt = SW(t) - if w+ws+lt<=mW or O==[]: - O.append(t) - w = w + ws + lt - else: - L.append(string.join(O,' ')) - O = [t] - w = lt - if O!=[]: L.append(string.join(O,' ')) - return L - -def _pathNumTrunc(n): - if int(n)==n: return int(n) - return round(n,5) - -def _processGlyph(G, truncate=1, pathReverse=0): - O = [] - P = [] - R = [] - for g in G+(('end',),): - op = g[0] - if O and op in ['moveTo', 'moveToClosed','end']: - if O[0]=='moveToClosed': - O = O[1:] - if pathReverse: - for i in xrange(0,len(P),2): - P[i+1], P[i] = P[i:i+2] - P.reverse() - O.reverse() - O.insert(0,'moveTo') - O.append('closePath') - i = 0 - if truncate: P = map(_pathNumTrunc,P) - for o in O: - j = i + _PATH_OP_ARG_COUNT[_PATH_OP_NAMES.index(o)] - if o=='closePath': - R.append(o) - else: - R.append((o,)+ tuple(P[i:j])) - i = j - O = [] - P = [] - O.append(op) - P.extend(g[1:]) - return R - -def _text2PathDescription(text, x=0, y=0, fontName='Times-Roman', fontSize=1000, - anchor='start', truncate=1, pathReverse=0): - global _gs - if not _gs: - import _renderPM - _gs = _renderPM.gstate(1,1) - from reportlab.graphics import renderPM - renderPM._setFont(_gs,fontName,fontSize) - P = [] - if not anchor =='start': - textLen = stringWidth(text, fontName,fontSize) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2. - for g in _gs._stringPath(text,x,y): - P.extend(_processGlyph(g,truncate=truncate,pathReverse=pathReverse)) - return P - -def _text2Path(text, x=0, y=0, fontName='Times-Roman', fontSize=1000, - anchor='start', truncate=1, pathReverse=0): - return definePath(_text2PathDescription(text,x=x,y=y,fontName=fontName, - fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse)) - -_BA2TA={'w':'start','nw':'start','sw':'start','e':'end', 'ne': 'end', 'se':'end', 'n':'middle','s':'middle','c':'middle'} -class Label(Widget): - """A text label to attach to something else, such as a chart axis. - - This allows you to specify an offset, angle and many anchor - properties relative to the label's origin. It allows, for example, - angled multiline axis labels. - """ - # fairly straight port of Robin Becker's textbox.py to new widgets - # framework. - - _attrMap = AttrMap( - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - dx = AttrMapValue(isNumber), - dy = AttrMapValue(isNumber), - angle = AttrMapValue(isNumber), - boxAnchor = AttrMapValue(isBoxAnchor), - boxStrokeColor = AttrMapValue(isColorOrNone), - boxStrokeWidth = AttrMapValue(isNumber), - boxFillColor = AttrMapValue(isColorOrNone), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - strokeWidth = AttrMapValue(isNumber), - text = AttrMapValue(isString), - fontName = AttrMapValue(isString), - fontSize = AttrMapValue(isNumber), - leading = AttrMapValue(isNumberOrNone), - width = AttrMapValue(isNumberOrNone), - maxWidth = AttrMapValue(isNumberOrNone), - height = AttrMapValue(isNumberOrNone), - textAnchor = AttrMapValue(isTextAnchor), - visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), - topPadding = AttrMapValue(isNumber,'padding at top of box'), - leftPadding = AttrMapValue(isNumber,'padding at left of box'), - rightPadding = AttrMapValue(isNumber,'padding at right of box'), - bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), - ) - - def __init__(self,**kw): - self._setKeywords(**kw) - self._setKeywords( - _text = 'Multi-Line\nString', - boxAnchor = 'c', - angle = 0, - x = 0, - y = 0, - dx = 0, - dy = 0, - topPadding = 0, - leftPadding = 0, - rightPadding = 0, - bottomPadding = 0, - boxStrokeWidth = 0.5, - boxStrokeColor = None, - strokeColor = None, - boxFillColor = None, - leading = None, - width = None, - maxWidth = None, - height = None, - fillColor = STATE_DEFAULTS['fillColor'], - fontName = STATE_DEFAULTS['fontName'], - fontSize = STATE_DEFAULTS['fontSize'], - strokeWidth = 0.1, - textAnchor = 'start', - visible = 1, - ) - - def setText(self, text): - """Set the text property. May contain embedded newline characters. - Called by the containing chart or axis.""" - self._text = text - - - def setOrigin(self, x, y): - """Set the origin. This would be the tick mark or bar top relative to - which it is defined. Called by the containing chart or axis.""" - self.x = x - self.y = y - - - def demo(self): - """This shows a label positioned with its top right corner - at the top centre of the drawing, and rotated 45 degrees.""" - - d = Drawing(200, 100) - - # mark the origin of the label - d.add(Circle(100,90, 5, fillColor=colors.green)) - - lab = Label() - lab.setOrigin(100,90) - lab.boxAnchor = 'ne' - lab.angle = 45 - lab.dx = 0 - lab.dy = -20 - lab.boxStrokeColor = colors.green - lab.setText('Another\nMulti-Line\nString') - d.add(lab) - - return d - - def _getBoxAnchor(self): - '''hook for allowing special box anchor effects''' - ba = self.boxAnchor - if ba in ('autox', 'autoy'): - angle = self.angle - na = (int((angle%360)/45.)*45)%360 - if not (na % 90): # we have a right angle case - da = (angle - na) % 360 - if abs(da)>5: - na = na + (da>0 and 45 or -45) - ba = _A2BA[ba[-1]][na] - return ba - - def computeSize(self): - # the thing will draw in its own coordinate system - self._lines = string.split(self._text, '\n') - self._lineWidths = [] - topPadding = self.topPadding - leftPadding = self.leftPadding - rightPadding = self.rightPadding - bottomPadding = self.bottomPadding - SW = lambda text, fN=self.fontName, fS=self.fontSize: stringWidth(text, fN, fS) - if self.maxWidth: - L = [] - for l in self._lines: - L[-1:-1] = _simpleSplit(l,self.maxWidth,SW) - self._lines = L - if not self.width: - w = 0 - for line in self._lines: - thisWidth = SW(line) - self._lineWidths.append(thisWidth) - w = max(w,thisWidth) - self._width = w+leftPadding+rightPadding - else: - self._width = self.width - self._height = self.height or ((self.leading or 1.2*self.fontSize) * len(self._lines)+topPadding+bottomPadding) - self._ewidth = (self._width-leftPadding-rightPadding) - self._eheight = (self._height-topPadding-bottomPadding) - boxAnchor = self._getBoxAnchor() - if boxAnchor in ['n','ne','nw']: - self._top = -topPadding - elif boxAnchor in ['s','sw','se']: - self._top = self._height-topPadding - else: - self._top = 0.5*self._eheight - self._bottom = self._top - self._eheight - - if boxAnchor in ['ne','e','se']: - self._left = leftPadding - self._width - elif boxAnchor in ['nw','w','sw']: - self._left = leftPadding - else: - self._left = -self._ewidth*0.5 - self._right = self._left+self._ewidth - - def _getTextAnchor(self): - '''This can be overridden to allow special effects''' - ta = self.textAnchor - if ta=='boxauto': ta = _BA2TA[self._getBoxAnchor()] - return ta - - def draw(self): - _text = self._text - self._text = _text or '' - self.computeSize() - self._text = _text - g = Group() - g.translate(self.x + self.dx, self.y + self.dy) - g.rotate(self.angle) - - y = self._top - self.fontSize - textAnchor = self._getTextAnchor() - if textAnchor == 'start': - x = self._left - elif textAnchor == 'middle': - x = self._left + self._ewidth*0.5 - else: - x = self._right - - # paint box behind text just in case they - # fill it - if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth): - g.add(Rect( self._left-self.leftPadding, - self._bottom-self.bottomPadding, - self._width, - self._height, - strokeColor=self.boxStrokeColor, - strokeWidth=self.boxStrokeWidth, - fillColor=self.boxFillColor) - ) - - fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize - strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, (self.leading or 1.2*fontSize) - if strokeColor: - for line in self._lines: - s = _text2Path(line, x, y, fontName, fontSize, textAnchor) - s.fillColor = fillColor - s.strokeColor = strokeColor - s.strokeWidth = strokeWidth - g.add(s) - y = y - leading - else: - for line in self._lines: - s = String(x, y, line) - s.textAnchor = textAnchor - s.fontName = fontName - s.fontSize = fontSize - s.fillColor = fillColor - g.add(s) - y = y - leading - - return g - -class LabelDecorator: - _attrMap = AttrMap( - x = AttrMapValue(isNumberOrNone), - y = AttrMapValue(isNumberOrNone), - dx = AttrMapValue(isNumberOrNone), - dy = AttrMapValue(isNumberOrNone), - angle = AttrMapValue(isNumberOrNone), - boxAnchor = AttrMapValue(isBoxAnchor), - boxStrokeColor = AttrMapValue(isColorOrNone), - boxStrokeWidth = AttrMapValue(isNumberOrNone), - boxFillColor = AttrMapValue(isColorOrNone), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - strokeWidth = AttrMapValue(isNumberOrNone), - fontName = AttrMapValue(isNoneOrString), - fontSize = AttrMapValue(isNumberOrNone), - leading = AttrMapValue(isNumberOrNone), - width = AttrMapValue(isNumberOrNone), - maxWidth = AttrMapValue(isNumberOrNone), - height = AttrMapValue(isNumberOrNone), - textAnchor = AttrMapValue(isTextAnchor), - visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), - ) - - def __init__(self): - self.textAnchor = 'start' - self.boxAnchor = 'w' - for a in self._attrMap.keys(): - if not hasattr(self,a): setattr(self,a,None) - - def decorate(self,l,L): - chart,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0 = l._callOutInfo - L.setText(chart.categoryAxis.categoryNames[colNo]) - g.add(L) - - def __call__(self,l): - from copy import deepcopy - L = Label() - for a,v in self.__dict__.items(): - if v is None: v = getattr(l,a,None) - setattr(L,a,v) - self.decorate(l,L) - -isOffsetMode=OneOf('high','low','bar','axis') -class LabelOffset(PropHolder): - _attrMap = AttrMap( - posMode = AttrMapValue(isOffsetMode,desc="Where to base +ve offset"), - pos = AttrMapValue(isNumber,desc='Value for positive elements'), - negMode = AttrMapValue(isOffsetMode,desc="Where to base -ve offset"), - neg = AttrMapValue(isNumber,desc='Value for negative elements'), - ) - def __init__(self): - self.posMode=self.negMode='axis' - self.pos = self.neg = 0 - - def _getValue(self, chart, val): - flipXY = chart._flipXY - A = chart.categoryAxis - jA = A.joinAxis - if val>=0: - mode = self.posMode - delta = self.pos - else: - mode = self.negMode - delta = self.neg - if flipXY: - v = A._x - else: - v = A._y - if jA: - if flipXY: - _v = jA._x - else: - _v = jA._y - if mode=='high': - v = _v + jA._length - elif mode=='low': - v = _v - elif mode=='bar': - v = _v+val - return v+delta - -NoneOrInstanceOfLabelOffset=NoneOr(isInstanceOf(LabelOffset)) - -class BarChartLabel(Label): - """ - An extended Label allowing for nudging, lines visibility etc - """ - _attrMap = AttrMap( - BASE=Label, - lineStrokeWidth = AttrMapValue(isNumberOrNone, desc="Non-zero for a drawn line"), - lineStrokeColor = AttrMapValue(isColorOrNone, desc="Color for a drawn line"), - fixedEnd = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw ends +/-"), - fixedStart = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw starts +/-"), - nudge = AttrMapValue(isNumber, desc="Non-zero sign dependent nudge"), - ) - - def __init__(self): - Label.__init__(self) - self.lineStrokeWidth = 0 - self.lineStrokeColor = None - self.nudge = 0 - self.fixedStart = self.fixedEnd = None - self._pmv = 0 - - def _getBoxAnchor(self): - a = self.boxAnchor - if self._pmv<0: a = {'nw':'se','n':'s','ne':'sw','w':'e','c':'c','e':'w','sw':'ne','s':'n','se':'nw'}[a] - return a - - def _getTextAnchor(self): - a = self.textAnchor - if self._pmv<0: a = {'start':'end', 'middle':'middle', 'end':'start'}[a] - return a - -class NA_Label(BarChartLabel): - """ - An extended Label allowing for nudging, lines visibility etc - """ - _attrMap = AttrMap( - BASE=BarChartLabel, - text = AttrMapValue(isNoneOrString, desc="Text to be used for N/A values"), - ) - def __init__(self): - BarChartLabel.__init__(self) - self.text = 'n/a' -NoneOrInstanceOfNA_Label=NoneOr(isInstanceOf(NA_Label)) diff --git a/bin/reportlab/graphics/charts/utils.py b/bin/reportlab/graphics/charts/utils.py deleted file mode 100644 index 028f0f70284..00000000000 --- a/bin/reportlab/graphics/charts/utils.py +++ /dev/null @@ -1,191 +0,0 @@ -#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/graphics/charts/utils.py -"Utilities used here and there." -__version__=''' $Id$ ''' - -from time import mktime, gmtime, strftime -import string - - -### Dinu's stuff used in some line plots (likely to vansih). - -def mkTimeTuple(timeString): - "Convert a 'dd/mm/yyyy' formatted string to a tuple for use in the time module." - - list = [0] * 9 - dd, mm, yyyy = map(int, string.split(timeString, '/')) - list[:3] = [yyyy, mm, dd] - - return tuple(list) - - -def str2seconds(timeString): - "Convert a number of seconds since the epoch into a date string." - - return mktime(mkTimeTuple(timeString)) - - -def seconds2str(seconds): - "Convert a date string into the number of seconds since the epoch." - - return strftime('%Y-%m-%d', gmtime(seconds)) - - -### Aaron's rounding function for making nice values on axes. - -from math import log10 - -def nextRoundNumber(x): - """Return the first 'nice round number' greater than or equal to x - - Used in selecting apropriate tick mark intervals; we say we want - an interval which places ticks at least 10 points apart, work out - what that is in chart space, and ask for the nextRoundNumber(). - Tries the series 1,2,5,10,20,50,100.., going up or down as needed. - """ - - #guess to nearest order of magnitude - if x in (0, 1): - return x - - if x < 0: - return -1.0 * nextRoundNumber(-x) - else: - lg = int(log10(x)) - - if lg == 0: - if x < 1: - base = 0.1 - else: - base = 1.0 - elif lg < 0: - base = 10.0 ** (lg - 1) - else: - base = 10.0 ** lg # e.g. base(153) = 100 - # base will always be lower than x - - if base >= x: - return base * 1.0 - elif (base * 2) >= x: - return base * 2.0 - elif (base * 5) >= x: - return base * 5.0 - else: - return base * 10.0 - - -### Robin's stuff from rgb_ticks. - -from math import log10, floor - -_intervals=(.1, .2, .25, .5) -_j_max=len(_intervals)-1 - - -def find_interval(lo,hi,I=5): - 'determine tick parameters for range [lo, hi] using I intervals' - - if lo >= hi: - if lo==hi: - if lo==0: - lo = -.1 - hi = .1 - else: - lo = 0.9*lo - hi = 1.1*hi - else: - raise ValueError, "lo>hi" - x=(hi - lo)/float(I) - b= (x>0 and (x<1 or x>10)) and 10**floor(log10(x)) or 1 - b = b - while 1: - a = x/b - if a<=_intervals[-1]: break - b = b*10 - - j = 0 - while a>_intervals[j]: j = j + 1 - - while 1: - ss = _intervals[j]*b - n = lo/ss - l = int(n)-(n<0) - n = ss*l - x = ss*(l+I) - a = I*ss - if n>0: - if a>=hi: - n = 0.0 - x = a - elif hi<0: - a = -a - if lo>a: - n = a - x = 0 - if hi<=x and n<=lo: break - j = j + 1 - if j>_j_max: - j = 0 - b = b*10 - return n, x, ss, lo - n + x - hi - - -def find_good_grid(lower,upper,n=(4,5,6,7,8,9), grid=None): - if grid: - t = divmod(lower,grid)[0] * grid - hi, z = divmod(upper,grid) - if z>1e-8: hi = hi+1 - hi = hi*grid - else: - try: - n[0] - except TypeError: - n = xrange(max(1,n-2),max(n+3,2)) - - w = 1e308 - for i in n: - z=find_interval(lower,upper,i) - if z[3] 3 or power < -3: - format = '%+'+`w+7`+'.0e' - else: - if power >= 0: - digits = int(power)+w - format = '%' + `digits`+'.0f' - else: - digits = w-int(power) - format = '%'+`digits+2`+'.'+`digits`+'f' - - if percent: format=format+'%%' - T = [] - n = int(float(hi-t)/grid+0.1)+1 - if split: - labels = [] - for i in xrange(n): - v = t+grid*i - T.append(v) - labels.append(format % v) - return T, labels - else: - for i in xrange(n): - v = t+grid*i - T.append((v, format % v)) - return T \ No newline at end of file diff --git a/bin/reportlab/graphics/charts/utils3d.py b/bin/reportlab/graphics/charts/utils3d.py deleted file mode 100644 index e1ff5403c12..00000000000 --- a/bin/reportlab/graphics/charts/utils3d.py +++ /dev/null @@ -1,233 +0,0 @@ -from reportlab.lib import colors -from reportlab.lib.attrmap import * -from reportlab.pdfgen.canvas import Canvas -from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, Polygon, Line - -def _getShaded(col,shd=None,shading=0.1): - if shd is None: - from reportlab.lib.colors import Blacker - if col: shd = Blacker(col,1-shading) - return shd - -def _getLit(col,shd=None,lighting=0.1): - if shd is None: - from reportlab.lib.colors import Whiter - if col: shd = Whiter(col,1-lighting) - return shd - - -def _draw_3d_bar(G, x1, x2, y0, yhigh, xdepth, ydepth, - fillColor=None, fillColorShaded=None, - strokeColor=None, strokeWidth=1, shading=0.1): - fillColorShaded = _getShaded(fillColor,None,shading) - fillColorShadedTop = _getShaded(fillColor,None,shading/2.0) - - def _add_3d_bar(x1, x2, y1, y2, xoff, yoff, - G=G,strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor): - G.add(Polygon((x1,y1, x1+xoff,y1+yoff, x2+xoff,y2+yoff, x2,y2), - strokeWidth=strokeWidth, strokeColor=strokeColor, fillColor=fillColor,strokeLineJoin=1)) - - usd = max(y0, yhigh) - if xdepth or ydepth: - if y0!=yhigh: #non-zero height - _add_3d_bar( x2, x2, y0, yhigh, xdepth, ydepth, fillColor=fillColorShaded) #side - - _add_3d_bar(x1, x2, usd, usd, xdepth, ydepth, fillColor=fillColorShadedTop) #top - - G.add(Polygon((x1,y0,x2,y0,x2,yhigh,x1,yhigh), - strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor,strokeLineJoin=1)) #front - - if xdepth or ydepth: - G.add(Line( x1, usd, x2, usd, strokeWidth=strokeWidth, strokeColor=strokeColor or fillColorShaded)) - -class _YStrip: - def __init__(self,y0,y1, slope, fillColor, fillColorShaded, shading=0.1): - self.y0 = y0 - self.y1 = y1 - self.slope = slope - self.fillColor = fillColor - self.fillColorShaded = _getShaded(fillColor,fillColorShaded,shading) - -def _ystrip_poly( x0, x1, y0, y1, xoff, yoff): - return [x0,y0,x0+xoff,y0+yoff,x1+xoff,y1+yoff,x1,y1] - - -def _make_3d_line_info( G, x0, x1, y0, y1, z0, z1, - theta_x, theta_y, - fillColor, fillColorShaded=None, tileWidth=1, - strokeColor=None, strokeWidth=None, strokeDashArray=None, - shading=0.1): - zwidth = abs(z1-z0) - xdepth = zwidth*theta_x - ydepth = zwidth*theta_y - depth_slope = xdepth==0 and 1e150 or -ydepth/float(xdepth) - - x = float(x1-x0) - slope = x==0 and 1e150 or (y1-y0)/x - - c = slope>depth_slope and _getShaded(fillColor,fillColorShaded,shading) or fillColor - zy0 = z0*theta_y - zx0 = z0*theta_x - - tileStrokeWidth = 0.6 - if tileWidth is None: - D = [(x1,y1)] - else: - T = ((y1-y0)**2+(x1-x0)**2)**0.5 - tileStrokeWidth *= tileWidth - if Tself.x1: return 1 - if o.s==self.s and o.i in (self.i-1,self.i+1): return - a = self.a - b = self.b - oa = o.a - ob = o.b - det = ob*a - oa*b - if -1e-81 or ou<0 or ou>1: return - x = x0 + u*a - y = self.y0 + u*b - if _ZERO=small: a(seg) - S.sort(_segCmp) - I = [] - n = len(S) - for i in xrange(0,n-1): - s = S[i] - for j in xrange(i+1,n): - if s.intersect(S[j],I)==1: break - I.sort() - return I - -if __name__=='__main__': - from reportlab.graphics.shapes import Drawing - from reportlab.lib.colors import lightgrey, pink - D = Drawing(300,200) - _draw_3d_bar(D, 10, 20, 10, 50, 5, 5, fillColor=lightgrey, strokeColor=pink) - _draw_3d_bar(D, 30, 40, 10, 45, 5, 5, fillColor=lightgrey, strokeColor=pink) - - D.save(formats=['pdf'],outDir='.',fnRoot='_draw_3d_bar') - - print find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(.2666666667,0.4),(0.1,0.4),(0.1,0.2),(0,0),(1,1)],[(0,1),(0.4,0.1),(1,0.1)]]) - print find_intersections([[(0.1, 0.2), (0.1, 0.4)], [(0, 1), (0.4, 0.1)]]) - print find_intersections([[(0.2, 0.4), (0.1, 0.4)], [(0.1, 0.8), (0.4, 0.1)]]) - print find_intersections([[(0,0),(1,1)],[(0.4,0.1),(1,0.1)]]) - print find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(0,0),(1,1)],[(0.1,0.8),(0.4,0.1),(1,0.1)]]) diff --git a/bin/reportlab/graphics/renderPDF.py b/bin/reportlab/graphics/renderPDF.py deleted file mode 100644 index b3b4c8ab62d..00000000000 --- a/bin/reportlab/graphics/renderPDF.py +++ /dev/null @@ -1,361 +0,0 @@ -#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/graphics/renderPDF.py -# renderPDF - draws Drawings onto a canvas -"""Usage: - import renderpdf - renderpdf.draw(drawing, canvas, x, y) -Execute the script to see some test drawings. -changed -""" -__version__=''' $Id$ ''' - -from reportlab.graphics.shapes import * -from reportlab.pdfgen.canvas import Canvas -from reportlab.pdfbase.pdfmetrics import stringWidth -from reportlab.lib.utils import getStringIO -from reportlab import rl_config - -# the main entry point for users... -def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_): - """As it says""" - R = _PDFRenderer() - R.draw(drawing, canvas, x, y, showBoundary=showBoundary) - -from renderbase import Renderer, StateTracker, getStateDelta - -class _PDFRenderer(Renderer): - """This draws onto a PDF document. It needs to be a class - rather than a function, as some PDF-specific state tracking is - needed outside of the state info in the SVG model.""" - - def __init__(self): - self._stroke = 0 - self._fill = 0 - self._tracker = StateTracker() - - def drawNode(self, node): - """This is the recursive method called for each node - in the tree""" - #print "pdf:drawNode", self - #if node.__class__ is Wedge: stop - if not (isinstance(node, Path) and node.isClipPath): - self._canvas.saveState() - - #apply state changes - deltas = getStateDelta(node) - self._tracker.push(deltas) - self.applyStateChanges(deltas, {}) - - #draw the object, or recurse - self.drawNodeDispatcher(node) - - self._tracker.pop() - if not (isinstance(node, Path) and node.isClipPath): - self._canvas.restoreState() - - def drawRect(self, rect): - if rect.rx == rect.ry == 0: - #plain old rectangle - self._canvas.rect( - rect.x, rect.y, - rect.width, rect.height, - stroke=self._stroke, - fill=self._fill - ) - else: - #cheat and assume ry = rx; better to generalize - #pdfgen roundRect function. TODO - self._canvas.roundRect( - rect.x, rect.y, - rect.width, rect.height, rect.rx, - fill=self._fill, - stroke=self._stroke - ) - - def drawImage(self, image): - # currently not implemented in other renderers - if image.path and os.path.exists(image.path): - self._canvas.drawInlineImage( - image.path, - image.x, image.y, - image.width, image.height - ) - - def drawLine(self, line): - if self._stroke: - self._canvas.line(line.x1, line.y1, line.x2, line.y2) - - def drawCircle(self, circle): - self._canvas.circle( - circle.cx, circle.cy, circle.r, - fill=self._fill, - stroke=self._stroke - ) - - def drawPolyLine(self, polyline): - if self._stroke: - assert len(polyline.points) >= 2, 'Polyline must have 2 or more points' - head, tail = polyline.points[0:2], polyline.points[2:], - path = self._canvas.beginPath() - path.moveTo(head[0], head[1]) - for i in range(0, len(tail), 2): - path.lineTo(tail[i], tail[i+1]) - self._canvas.drawPath(path) - - def drawWedge(self, wedge): - centerx, centery, radius, startangledegrees, endangledegrees = \ - wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees - yradius, radius1, yradius1 = wedge._xtraRadii() - if yradius is None: yradius = radius - angle = endangledegrees-startangledegrees - path = self._canvas.beginPath() - if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None): - path.moveTo(centerx, centery) - path.arcTo(centerx-radius, centery-yradius, centerx+radius, centery+yradius, - startangledegrees, angle) - else: - path.arc(centerx-radius, centery-yradius, centerx+radius, centery+yradius, - startangledegrees, angle) - path.arcTo(centerx-radius1, centery-yradius1, centerx+radius1, centery+yradius1, - endangledegrees, -angle) - path.close() - self._canvas.drawPath(path, - fill=self._fill, - stroke=self._stroke) - - def drawEllipse(self, ellipse): - #need to convert to pdfgen's bounding box representation - x1 = ellipse.cx - ellipse.rx - x2 = ellipse.cx + ellipse.rx - y1 = ellipse.cy - ellipse.ry - y2 = ellipse.cy + ellipse.ry - self._canvas.ellipse(x1,y1,x2,y2,fill=self._fill,stroke=self._stroke) - - def drawPolygon(self, polygon): - assert len(polygon.points) >= 2, 'Polyline must have 2 or more points' - head, tail = polygon.points[0:2], polygon.points[2:], - path = self._canvas.beginPath() - path.moveTo(head[0], head[1]) - for i in range(0, len(tail), 2): - path.lineTo(tail[i], tail[i+1]) - path.close() - self._canvas.drawPath( - path, - stroke=self._stroke, - fill=self._fill - ) - - def drawString(self, stringObj): - if self._fill: - S = self._tracker.getState() - text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text - if not text_anchor in ['start','inherited']: - font, font_size = S['fontName'], S['fontSize'] - textLen = stringWidth(text, font,font_size) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2 - else: - raise ValueError, 'bad value for textAnchor '+str(text_anchor) - t = self._canvas.beginText(x,y) - t.textLine(text) - self._canvas.drawText(t) - - def drawPath(self, path): - from reportlab.graphics.shapes import _renderPath - pdfPath = self._canvas.beginPath() - drawFuncs = (pdfPath.moveTo, pdfPath.lineTo, pdfPath.curveTo, pdfPath.close) - isClosed = _renderPath(path, drawFuncs) - if isClosed: - fill = self._fill - else: - fill = 0 - if path.isClipPath: - self._canvas.clipPath(pdfPath, fill=fill, stroke=self._stroke) - else: - self._canvas.drawPath(pdfPath, - fill=fill, - stroke=self._stroke) - - def applyStateChanges(self, delta, newState): - """This takes a set of states, and outputs the PDF operators - needed to set those properties""" - for key, value in delta.items(): - if key == 'transform': - self._canvas.transform(value[0], value[1], value[2], - value[3], value[4], value[5]) - elif key == 'strokeColor': - #this has different semantics in PDF to SVG; - #we always have a color, and either do or do - #not apply it; in SVG one can have a 'None' color - if value is None: - self._stroke = 0 - else: - self._stroke = 1 - self._canvas.setStrokeColor(value) - elif key == 'strokeWidth': - self._canvas.setLineWidth(value) - elif key == 'strokeLineCap': #0,1,2 - self._canvas.setLineCap(value) - elif key == 'strokeLineJoin': - self._canvas.setLineJoin(value) -# elif key == 'stroke_dasharray': -# self._canvas.setDash(array=value) - elif key == 'strokeDashArray': - if value: - self._canvas.setDash(value) - else: - self._canvas.setDash() - elif key == 'fillColor': - #this has different semantics in PDF to SVG; - #we always have a color, and either do or do - #not apply it; in SVG one can have a 'None' color - if value is None: - self._fill = 0 - else: - self._fill = 1 - self._canvas.setFillColor(value) - elif key in ['fontSize', 'fontName']: - # both need setting together in PDF - # one or both might be in the deltas, - # so need to get whichever is missing - fontname = delta.get('fontName', self._canvas._fontname) - fontsize = delta.get('fontSize', self._canvas._fontsize) - self._canvas.setFont(fontname, fontsize) - -from reportlab.platypus import Flowable - -class GraphicsFlowable(Flowable): - """Flowable wrapper around a Pingo drawing""" - def __init__(self, drawing): - self.drawing = drawing - self.width = self.drawing.width - self.height = self.drawing.height - - def draw(self): - draw(self.drawing, self.canv, 0, 0) - -def drawToFile(d, fn, msg="", showBoundary=rl_config._unset_, autoSize=1): - """Makes a one-page PDF with just the drawing. - - If autoSize=1, the PDF will be the same size as - the drawing; if 0, it will place the drawing on - an A4 page with a title above it - possibly overflowing - if too big.""" - c = Canvas(fn) - c.setFont('Times-Roman', 36) - c.drawString(80, 750, msg) - c.setTitle(msg) - - if autoSize: - c.setPageSize((d.width, d.height)) - draw(d, c, 0, 0, showBoundary=showBoundary) - else: - #show with a title - c.setFont('Times-Roman', 12) - y = 740 - i = 1 - y = y - d.height - draw(d, c, 80, y, showBoundary=showBoundary) - - c.showPage() - c.save() - if sys.platform=='mac' and not hasattr(fn, "write"): - try: - import macfs, macostools - macfs.FSSpec(fn).SetCreatorType("CARO", "PDF ") - macostools.touched(fn) - except: - pass - - -def drawToString(d, msg="", showBoundary=rl_config._unset_,autoSize=1): - "Returns a PDF as a string in memory, without touching the disk" - s = getStringIO() - drawToFile(d, s, msg=msg, showBoundary=showBoundary,autoSize=autoSize) - return s.getvalue() - - -######################################################### -# -# test code. First, defin a bunch of drawings. -# Routine to draw them comes at the end. -# -######################################################### - - -def test(): - c = Canvas('renderPDF.pdf') - c.setFont('Times-Roman', 36) - c.drawString(80, 750, 'Graphics Test') - - # print all drawings and their doc strings from the test - # file - - #grab all drawings from the test module - from reportlab.graphics import testshapes - drawings = [] - for funcname in dir(testshapes): - if funcname[0:10] == 'getDrawing': - drawing = eval('testshapes.' + funcname + '()') #execute it - docstring = eval('testshapes.' + funcname + '.__doc__') - drawings.append((drawing, docstring)) - - #print in a loop, with their doc strings - c.setFont('Times-Roman', 12) - y = 740 - i = 1 - for (drawing, docstring) in drawings: - assert (docstring is not None), "Drawing %d has no docstring!" % i - if y < 300: #allows 5-6 lines of text - c.showPage() - y = 740 - # draw a title - y = y - 30 - c.setFont('Times-BoldItalic',12) - c.drawString(80, y, 'Drawing %d' % i) - c.setFont('Times-Roman',12) - y = y - 14 - textObj = c.beginText(80, y) - textObj.textLines(docstring) - c.drawText(textObj) - y = textObj.getY() - y = y - drawing.height - draw(drawing, c, 80, y) - i = i + 1 - if y!=740: c.showPage() - - c.save() - print 'saved renderPDF.pdf' - -##def testFlowable(): -## """Makes a platypus document""" -## from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer -## from reportlab.lib.styles import getSampleStyleSheet -## styles = getSampleStyleSheet() -## styNormal = styles['Normal'] -## -## doc = SimpleDocTemplate('test_flowable.pdf') -## story = [] -## story.append(Paragraph("This sees is a drawing can work as a flowable", styNormal)) -## -## import testdrawings -## drawings = [] -## -## for funcname in dir(testdrawings): -## if funcname[0:10] == 'getDrawing': -## drawing = eval('testdrawings.' + funcname + '()') #execute it -## docstring = eval('testdrawings.' + funcname + '.__doc__') -## story.append(Paragraph(docstring, styNormal)) -## story.append(Spacer(18,18)) -## story.append(drawing) -## story.append(Spacer(36,36)) -## -## doc.build(story) -## print 'saves test_flowable.pdf' - -if __name__=='__main__': - test() - #testFlowable() diff --git a/bin/reportlab/graphics/renderPM.py b/bin/reportlab/graphics/renderPM.py deleted file mode 100644 index 4da2cffc015..00000000000 --- a/bin/reportlab/graphics/renderPM.py +++ /dev/null @@ -1,631 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history www.reportlab.co.uk/rl-cgi/viewcvs.cgi/rlextra/graphics/Csrc/renderPM/renderP.py -__version__=''' $Id$ ''' -"""Usage: - from reportlab.graphics import renderPM - renderPM.drawToFile(drawing,filename,fmt='GIF',configPIL={....}) -Other functions let you create a PM drawing as string or into a PM buffer. -Execute the script to see some test drawings.""" - -from reportlab.graphics.shapes import * -from reportlab.graphics.renderbase import StateTracker, getStateDelta -from reportlab.pdfbase.pdfmetrics import getFont -from math import sin, cos, pi, ceil -from reportlab.lib.utils import getStringIO, open_and_read -from reportlab import rl_config - -class RenderPMError(Exception): - pass - -import string, os, sys - -try: - import _renderPM -except ImportError, errMsg: - raise ImportError, "No module named _renderPM\n" + \ - (str(errMsg)!='No module named _renderPM' and "it may be the wrong version or badly installed!" or - "see http://www.reportlab.org/rl_addons.html") - -from types import TupleType, ListType -_SeqTypes = (TupleType,ListType) - -def _getImage(): - try: - from PIL import Image - except ImportError: - import Image - return Image - -def Color2Hex(c): - #assert isinstance(colorobj, colors.Color) #these checks don't work well RGB - if c: return ((0xFF&int(255*c.red)) << 16) | ((0xFF&int(255*c.green)) << 8) | (0xFF&int(255*c.blue)) - return c - -# the main entry point for users... -def draw(drawing, canvas, x, y, showBoundary=rl_config._unset_): - """As it says""" - R = _PMRenderer() - R.draw(drawing, canvas, x, y, showBoundary=showBoundary) - -from reportlab.graphics.renderbase import Renderer -class _PMRenderer(Renderer): - """This draws onto a pix map image. It needs to be a class - rather than a function, as some image-specific state tracking is - needed outside of the state info in the SVG model.""" - - def __init__(self): - self._tracker = StateTracker() - - def pop(self): - self._tracker.pop() - self.applyState() - - def push(self,node): - deltas = getStateDelta(node) - self._tracker.push(deltas) - self.applyState() - - def applyState(self): - s = self._tracker.getState() - self._canvas.ctm = s['ctm'] - self._canvas.strokeWidth = s['strokeWidth'] - self._canvas.strokeColor = Color2Hex(s['strokeColor']) - self._canvas.lineCap = s['strokeLineCap'] - self._canvas.lineJoin = s['strokeLineJoin'] - da = s['strokeDashArray'] - da = da and (0,da) or None - self._canvas.dashArray = da - self._canvas.fillColor = Color2Hex(s['fillColor']) - self._canvas.setFont(s['fontName'], s['fontSize']) - - def initState(self,x,y): - deltas = STATE_DEFAULTS.copy() - deltas['transform'] = self._canvas._baseCTM[0:4]+(x,y) - self._tracker.push(deltas) - self.applyState() - - def drawNode(self, node): - """This is the recursive method called for each node - in the tree""" - - #apply state changes - self.push(node) - - #draw the object, or recurse - self.drawNodeDispatcher(node) - - # restore the state - self.pop() - - def drawRect(self, rect): - c = self._canvas - if rect.rx == rect.ry == 0: - #plain old rectangle, draw clockwise (x-axis to y-axis) direction - c.rect(rect.x,rect.y, rect.width, rect.height) - else: - c.roundRect(rect.x,rect.y, rect.width, rect.height, rect.rx, rect.ry) - - def drawLine(self, line): - self._canvas.line(line.x1,line.y1,line.x2,line.y2) - - def drawImage(self, image): - if image.path and os.path.exists(image.path): - if type(image.path) is type(''): - im = _getImage().open(image.path).convert('RGB') - else: - im = image.path.convert('RGB') - srcW, srcH = im.size - dstW, dstH = image.width, image.height - if dstW is None: dstW = srcW - if dstH is None: dstH = srcH - self._canvas._aapixbuf( - image.x, image.y, dstW, dstH, - im.tostring(), srcW, srcH, 3, - ) - - def drawCircle(self, circle): - c = self._canvas - c.circle(circle.cx,circle.cy, circle.r) - c.fillstrokepath() - - def drawPolyLine(self, polyline, _doClose=0): - P = polyline.points - assert len(P) >= 2, 'Polyline must have 1 or more points' - c = self._canvas - c.pathBegin() - c.moveTo(P[0], P[1]) - for i in range(2, len(P), 2): - c.lineTo(P[i], P[i+1]) - if _doClose: - c.pathClose() - c.pathFill() - c.pathStroke() - - def drawEllipse(self, ellipse): - c=self._canvas - c.ellipse(ellipse.cx, ellipse.cy, ellipse.rx,ellipse.ry) - c.fillstrokepath() - - def drawPolygon(self, polygon): - self.drawPolyLine(polygon,_doClose=1) - - def drawString(self, stringObj): - fill = self._canvas.fillColor - if fill is not None: - S = self._tracker.getState() - text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text - if not text_anchor in ['start','inherited']: - font, font_size = S['fontName'], S['fontSize'] - textLen = stringWidth(text, font,font_size) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2 - else: - raise ValueError, 'bad value for textAnchor '+str(text_anchor) - self._canvas.drawString(x,y,text) - - def drawPath(self, path): - c = self._canvas - if path is EmptyClipPath: - del c._clipPaths[-1] - if c._clipPaths: - P = c._clipPaths[-1] - icp = P.isClipPath - P.isClipPath = 1 - self.drawPath(P) - P.isClipPath = icp - else: - c.clipPathClear() - return - c.pathBegin() - drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.pathClose) - from reportlab.graphics.shapes import _renderPath - isClosed = _renderPath(path, drawFuncs) - if path.isClipPath: - c.clipPathSet() - c._clipPaths.append(path) - else: - if isClosed: c.pathFill() - c.pathStroke() - -def _setFont(gs,fontName,fontSize): - try: - gs.setFont(fontName,fontSize) - except _renderPM.Error, errMsg: - if errMsg.args[0]!="Can't find font!": raise - #here's where we try to add a font to the canvas - try: - f = getFont(fontName) - if _renderPM._version<='0.98': #added reader arg in 0.99 - _renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector) - else: - _renderPM.makeT1Font(fontName,f.face.findT1File(),f.encoding.vector,open_and_read) - except: - s1, s2 = map(str,sys.exc_info()[:2]) - raise RenderPMError, "Can't setFont(%s) missing the T1 files?\nOriginally %s: %s" % (fontName,s1,s2) - gs.setFont(fontName,fontSize) - -def _convert2pilp(im): - Image = _getImage() - return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE) - -def _saveAsPICT(im,fn,fmt,transparent=None): - im = _convert2pilp(im) - cols, rows = im.size - #s = _renderPM.pil2pict(cols,rows,im.tostring(),im.im.getpalette(),transparent is not None and Color2Hex(transparent) or -1) - s = _renderPM.pil2pict(cols,rows,im.tostring(),im.im.getpalette()) - if not hasattr(fn,'write'): - open(os.path.splitext(fn)[0]+'.'+string.lower(fmt),'wb').write(s) - if os.name=='mac': - from reportlab.lib.utils import markfilename - markfilename(fn,ext='PICT') - else: - fn.write(s) - -BEZIER_ARC_MAGIC = 0.5522847498 #constant for drawing circular arcs w/ Beziers -class PMCanvas: - def __init__(self,w,h,dpi=72,bg=0xffffff,configPIL=None): - '''configPIL dict is passed to image save method''' - scale = dpi/72.0 - w = int(w*scale+0.5) - h = int(h*scale+0.5) - self.__dict__['_gs'] = _renderPM.gstate(w,h,bg=bg) - self.__dict__['_bg'] = bg - self.__dict__['_baseCTM'] = (scale,0,0,scale,0,0) - self.__dict__['_clipPaths'] = [] - self.__dict__['configPIL'] = configPIL - self.__dict__['_dpi'] = dpi - self.ctm = self._baseCTM - - def _drawTimeResize(self,w,h,bg=None): - if bg is None: bg = self._bg - self._drawing.width, self._drawing.height = w, h - A = {'ctm':None, 'strokeWidth':None, 'strokeColor':None, 'lineCap':None, 'lineJoin':None, 'dashArray':None, 'fillColor':None} - gs = self._gs - fN,fS = gs.fontName, gs.fontSize - for k in A.keys(): - A[k] = getattr(gs,k) - del gs, self._gs - gs = self.__dict__['_gs'] = _renderPM.gstate(w,h,bg=bg) - for k in A.keys(): - setattr(self,k,A[k]) - gs.setFont(fN,fS) - - def toPIL(self): - im = _getImage().new('RGB', size=(self._gs.width, self._gs.height)) - im.fromstring(self._gs.pixBuf) - return im - - def saveToFile(self,fn,fmt=None): - im = self.toPIL() - if fmt is None: - if type(fn) is not StringType: - raise ValueError, "Invalid type '%s' for fn when fmt is None" % type(fn) - fmt = os.path.splitext(fn)[1] - if fmt.startswith('.'): fmt = fmt[1:] - configPIL = self.configPIL or {} - fmt = string.upper(fmt) - if fmt in ['GIF']: - im = _convert2pilp(im) - elif fmt in ['PCT','PICT']: - return _saveAsPICT(im,fn,fmt,transparent=configPIL.get('transparent',None)) - elif fmt in ['PNG','TIFF','BMP', 'PPM', 'TIF']: - if fmt=='TIF': fmt = 'TIFF' - if fmt=='PNG': - try: - from PIL import PngImagePlugin - except ImportError: - import PngImagePlugin - elif fmt=='BMP': - try: - from PIL import BmpImagePlugin - except ImportError: - import BmpImagePlugin - elif fmt in ('JPG','JPEG'): - fmt = 'JPEG' - else: - raise RenderPMError,"Unknown image kind %s" % fmt - if fmt=='TIFF': - tc = configPIL.get('transparent',None) - if tc: - from PIL import ImageChops, Image - T = 768*[0] - for o, c in zip((0,256,512), tc.bitmap_rgb()): - T[o+c] = 255 - #if type(fn) is type(''): ImageChops.invert(im.point(T).convert('L').point(255*[0]+[255])).save(fn+'_mask.gif','GIF') - im = Image.merge('RGBA', im.split()+(ImageChops.invert(im.point(T).convert('L').point(255*[0]+[255])),)) - #if type(fn) is type(''): im.save(fn+'_masked.gif','GIF') - for a,d in ('resolution',self._dpi),('resolution unit','inch'): - configPIL[a] = configPIL.get(a,d) - apply(im.save,(fn,fmt),configPIL) - if not hasattr(fn,'write') and os.name=='mac': - from reportlab.lib.utils import markfilename - markfilename(fn,ext=fmt) - - def saveToString(self,fmt='GIF'): - s = getStringIO() - self.saveToFile(s,fmt=fmt) - return s.getvalue() - - def _saveToBMP(self,f): - ''' - Niki Spahiev, , asserts that this is a respectable way to get BMP without PIL - f is a file like object to which the BMP is written - ''' - import struct - gs = self._gs - pix, width, height = gs.pixBuf, gs.width, gs.height - f.write(struct.pack('=2sLLLLLLhh24x','BM',len(pix)+54,0,54,40,width,height,1,24)) - rowb = width * 3 - for o in range(len(pix),0,-rowb): - f.write(pix[o-rowb:o]) - f.write( '\0' * 14 ) - - def setFont(self,fontName,fontSize,leading=None): - _setFont(self._gs,fontName,fontSize) - - def __setattr__(self,name,value): - setattr(self._gs,name,value) - - def __getattr__(self,name): - return getattr(self._gs,name) - - def fillstrokepath(self,stroke=1,fill=1): - if fill: self.pathFill() - if stroke: self.pathStroke() - - def _bezierArcSegmentCCW(self, cx,cy, rx,ry, theta0, theta1): - """compute the control points for a bezier arc with theta1-theta0 <= 90. - Points are computed for an arc with angle theta increasing in the - counter-clockwise (CCW) direction. returns a tuple with starting point - and 3 control points of a cubic bezier curve for the curvto opertator""" - - # Requires theta1 - theta0 <= 90 for a good approximation - assert abs(theta1 - theta0) <= 90 - cos0 = cos(pi*theta0/180.0) - sin0 = sin(pi*theta0/180.0) - x0 = cx + rx*cos0 - y0 = cy + ry*sin0 - - cos1 = cos(pi*theta1/180.0) - sin1 = sin(pi*theta1/180.0) - - x3 = cx + rx*cos1 - y3 = cy + ry*sin1 - - dx1 = -rx * sin0 - dy1 = ry * cos0 - - #from pdfgeom - halfAng = pi*(theta1-theta0)/(2.0 * 180.0) - k = abs(4.0 / 3.0 * (1.0 - cos(halfAng) ) /(sin(halfAng)) ) - x1 = x0 + dx1 * k - y1 = y0 + dy1 * k - - dx2 = -rx * sin1 - dy2 = ry * cos1 - - x2 = x3 - dx2 * k - y2 = y3 - dy2 * k - return ((x0,y0), ((x1,y1), (x2,y2), (x3,y3)) ) - - def bezierArcCCW(self, cx,cy, rx,ry, theta0, theta1): - """return a set of control points for Bezier approximation to an arc - with angle increasing counter clockwise. No requirement on |theta1-theta0| <= 90 - However, it must be true that theta1-theta0 > 0.""" - - # I believe this is also clockwise - # pretty much just like Robert Kern's pdfgeom.BezierArc - angularExtent = theta1 - theta0 - # break down the arc into fragments of <=90 degrees - if abs(angularExtent) <= 90.0: # we just need one fragment - angleList = [(theta0,theta1)] - else: - Nfrag = int( ceil( abs(angularExtent)/90.) ) - fragAngle = float(angularExtent)/ Nfrag # this could be negative - angleList = [] - for ii in range(Nfrag): - a = theta0 + ii * fragAngle - b = a + fragAngle # hmm.. is I wonder if this is precise enought - angleList.append((a,b)) - - ctrlpts = [] - for (a,b) in angleList: - if not ctrlpts: # first time - [(x0,y0), pts] = self._bezierArcSegmentCCW(cx,cy, rx,ry, a,b) - ctrlpts.append(pts) - else: - [(tmpx,tmpy), pts] = self._bezierArcSegmentCCW(cx,cy, rx,ry, a,b) - ctrlpts.append(pts) - return ((x0,y0), ctrlpts) - - def addEllipsoidalArc(self, cx,cy, rx, ry, ang1, ang2): - """adds an ellisesoidal arc segment to a path, with an ellipse centered - on cx,cy and with radii (major & minor axes) rx and ry. The arc is - drawn in the CCW direction. Requires: (ang2-ang1) > 0""" - - ((x0,y0), ctrlpts) = self.bezierArcCCW(cx,cy, rx,ry,ang1,ang2) - - self.lineTo(x0,y0) - for ((x1,y1), (x2,y2),(x3,y3)) in ctrlpts: - self.curveTo(x1,y1,x2,y2,x3,y3) - - def drawCentredString(self, x, y, text, text_anchor='middle'): - if self.fillColor is not None: - textLen = stringWidth(text, self.fontName,self.fontSize) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2 - self.drawString(x,y,text) - - def drawRightString(self, text, x, y): - self.drawCentredString(text,x,y,text_anchor='end') - - def line(self,x1,y1,x2,y2): - if self.strokeColor is not None: - self.pathBegin() - self.moveTo(x1,y1) - self.lineTo(x2,y2) - self.pathStroke() - - def rect(self,x,y,width,height,stroke=1,fill=1): - self.pathBegin() - self.moveTo(x, y) - self.lineTo(x+width, y) - self.lineTo(x+width, y + height) - self.lineTo(x, y + height) - self.pathClose() - self.fillstrokepath(stroke=stroke,fill=fill) - - def roundRect(self, x, y, width, height, rx,ry): - """rect(self, x, y, width, height, rx,ry): - Draw a rectangle if rx or rx and ry are specified the corners are - rounded with ellipsoidal arcs determined by rx and ry - (drawn in the counter-clockwise direction)""" - if rx==0: rx = ry - if ry==0: ry = rx - x2 = x + width - y2 = y + height - self.pathBegin() - self.moveTo(x+rx,y) - self.addEllipsoidalArc(x2-rx, y+ry, rx, ry, 270, 360 ) - self.addEllipsoidalArc(x2-rx, y2-ry, rx, ry, 0, 90) - self.addEllipsoidalArc(x+rx, y2-ry, rx, ry, 90, 180) - self.addEllipsoidalArc(x+rx, y+ry, rx, ry, 180, 270) - self.pathClose() - self.fillstrokepath() - - def circle(self, cx, cy, r): - "add closed path circle with center cx,cy and axes r: counter-clockwise orientation" - self.ellipse(cx,cy,r,r) - - def ellipse(self, cx,cy,rx,ry): - """add closed path ellipse with center cx,cy and axes rx,ry: counter-clockwise orientation - (remember y-axis increases downward) """ - self.pathBegin() - # first segment - x0 = cx + rx # (x0,y0) start pt - y0 = cy - - x3 = cx # (x3,y3) end pt of arc - y3 = cy-ry - - x1 = cx+rx - y1 = cy-ry*BEZIER_ARC_MAGIC - - x2 = x3 + rx*BEZIER_ARC_MAGIC - y2 = y3 - self.moveTo(x0, y0) - self.curveTo(x1,y1,x2,y2,x3,y3) - # next segment - x0 = x3 - y0 = y3 - - x3 = cx-rx - y3 = cy - - x1 = cx-rx*BEZIER_ARC_MAGIC - y1 = cy-ry - - x2 = x3 - y2 = cy- ry*BEZIER_ARC_MAGIC - self.curveTo(x1,y1,x2,y2,x3,y3) - # next segment - x0 = x3 - y0 = y3 - - x3 = cx - y3 = cy+ry - - x1 = cx-rx - y1 = cy+ry*BEZIER_ARC_MAGIC - - x2 = cx -rx*BEZIER_ARC_MAGIC - y2 = cy+ry - self.curveTo(x1,y1,x2,y2,x3,y3) - #last segment - x0 = x3 - y0 = y3 - - x3 = cx+rx - y3 = cy - - x1 = cx+rx*BEZIER_ARC_MAGIC - y1 = cy+ry - - x2 = cx+rx - y2 = cy+ry*BEZIER_ARC_MAGIC - self.curveTo(x1,y1,x2,y2,x3,y3) - self.pathClose() - - def saveState(self): - '''do nothing for compatibility''' - pass - - def setFillColor(self,aColor): - self.fillColor = Color2Hex(aColor) - - def setStrokeColor(self,aColor): - self.strokeColor = Color2Hex(aColor) - - restoreState = saveState - - # compatibility routines - def setLineCap(self,cap): - self.lineCap = cap - - def setLineWidth(self,width): - self.strokeWidth = width - -def drawToPMCanvas(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_): - c = PMCanvas(d.width, d.height, dpi=dpi, bg=bg, configPIL=configPIL) - draw(d, c, 0, 0, showBoundary=showBoundary) - return c - -def drawToPIL(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_): - return drawToPMCanvas(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary).toPIL() - -def drawToPILP(d, dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_): - Image = _getImage() - im = drawToPIL(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary) - return im.convert("P", dither=Image.NONE, palette=Image.ADAPTIVE) - -def drawToFile(d,fn,fmt='GIF', dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_): - '''create a pixmap and draw drawing, d to it then save as a file - configPIL dict is passed to image save method''' - c = drawToPMCanvas(d, dpi=dpi, bg=bg, configPIL=configPIL, showBoundary=showBoundary) - c.saveToFile(fn,fmt) - -def drawToString(d,fmt='GIF', dpi=72, bg=0xffffff, configPIL=None, showBoundary=rl_config._unset_): - s = getStringIO() - drawToFile(d,s,fmt=fmt, dpi=dpi, bg=bg, configPIL=configPIL) - return s.getvalue() - -save = drawToFile - -def test(): - def ext(x): - if x=='tiff': x='tif' - return x - #grab all drawings from the test module and write out. - #make a page of links in HTML to assist viewing. - import os - from reportlab.graphics import testshapes - getAllTestDrawings = testshapes.getAllTestDrawings - drawings = [] - if not os.path.isdir('pmout'): - os.mkdir('pmout') - htmlTop = """renderPM output results - -

renderPM results of output

- """ - htmlBottom = """ - - """ - html = [htmlTop] - - i = 0 - #print in a loop, with their doc strings - for (drawing, docstring, name) in getAllTestDrawings(doTTF=hasattr(_renderPM,'ft_get_face')): - fnRoot = 'renderPM%d' % i - if 1 or i==10: - w = int(drawing.width) - h = int(drawing.height) - html.append('

Drawing %s %d

\n
%s
' % (name, i, docstring)) - - for k in ['gif','tiff', 'png', 'jpg', 'pct']: - if k in ['gif','png','jpg','pct']: - html.append('

%s format

\n' % string.upper(k)) - try: - filename = '%s.%s' % (fnRoot, ext(k)) - fullpath = os.path.join('pmout', filename) - if os.path.isfile(fullpath): - os.remove(fullpath) - if k=='pct': - from reportlab.lib.colors import white - drawToFile(drawing,fullpath,fmt=k,configPIL={'transparent':white}) - else: - drawToFile(drawing,fullpath,fmt=k) - if k in ['gif','png','jpg']: - html.append('
\n' % filename) - print 'wrote',fullpath - except AttributeError: - print 'Problem drawing %s file'%k - raise - if os.environ.get('RL_NOEPSPREVIEW','0')=='1': drawing.__dict__['preview'] = 0 - drawing.save(formats=['eps','pdf'],outDir='pmout',fnRoot=fnRoot) - i = i + 1 - #if i==10: break - html.append(htmlBottom) - htmlFileName = os.path.join('pmout', 'index.html') - open(htmlFileName, 'w').writelines(html) - if sys.platform=='mac': - from reportlab.lib.utils import markfilename - markfilename(htmlFileName,ext='HTML') - print 'wrote %s' % htmlFileName - -if __name__=='__main__': - test() diff --git a/bin/reportlab/graphics/renderPS.py b/bin/reportlab/graphics/renderPS.py deleted file mode 100644 index e117bb7fc89..00000000000 --- a/bin/reportlab/graphics/renderPS.py +++ /dev/null @@ -1,859 +0,0 @@ -#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/graphics/renderPS.py -__version__=''' $Id$ ''' -import string, types -from reportlab.pdfbase.pdfmetrics import getFont, stringWidth # for font info -from reportlab.lib.utils import fp_str, getStringIO -from reportlab.lib.colors import black -from reportlab.graphics.renderbase import StateTracker, getStateDelta -from reportlab.graphics.shapes import STATE_DEFAULTS -import math -from types import StringType -from operator import getitem -from reportlab import rl_config - - -# we need to create encoding vectors for each font we use, or they will -# come out in Adobe's old StandardEncoding, which NOBODY uses. -PS_WinAnsiEncoding=""" -/RE { %def - findfont begin - currentdict dup length dict begin - { %forall - 1 index /FID ne { def } { pop pop } ifelse - } forall - /FontName exch def dup length 0 ne { %if - /Encoding Encoding 256 array copy def - 0 exch { %forall - dup type /nametype eq { %ifelse - Encoding 2 index 2 index put - pop 1 add - }{ %else - exch pop - } ifelse - } forall - } if pop - currentdict dup end end - /FontName get exch definefont pop -} bind def - -/WinAnsiEncoding [ - 39/quotesingle 96/grave 128/euro 130/quotesinglbase/florin/quotedblbase - /ellipsis/dagger/daggerdbl/circumflex/perthousand - /Scaron/guilsinglleft/OE 145/quoteleft/quoteright - /quotedblleft/quotedblright/bullet/endash/emdash - /tilde/trademark/scaron/guilsinglright/oe/dotlessi - 159/Ydieresis 164/currency 166/brokenbar 168/dieresis/copyright - /ordfeminine 172/logicalnot 174/registered/macron/ring - 177/plusminus/twosuperior/threesuperior/acute/mu - 183/periodcentered/cedilla/onesuperior/ordmasculine - 188/onequarter/onehalf/threequarters 192/Agrave/Aacute - /Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla - /Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute - /Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute - /Ocircumflex/Otilde/Odieresis/multiply/Oslash - /Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn - /germandbls/agrave/aacute/acircumflex/atilde/adieresis - /aring/ae/ccedilla/egrave/eacute/ecircumflex - /edieresis/igrave/iacute/icircumflex/idieresis - /eth/ntilde/ograve/oacute/ocircumflex/otilde - /odieresis/divide/oslash/ugrave/uacute/ucircumflex - /udieresis/yacute/thorn/ydieresis -] def - -""" - -class PSCanvas: - def __init__(self,size=(300,300), PostScriptLevel=2): - self.width, self.height = size - self.code = [] - self._sep = '\n' - self._strokeColor = self._fillColor = self._lineWidth = \ - self._font = self._fontSize = self._lineCap = \ - self._lineJoin = self._color = None - - - self._fontsUsed = [] # track them as we go - - self.setFont(STATE_DEFAULTS['fontName'],STATE_DEFAULTS['fontSize']) - self.setStrokeColor(STATE_DEFAULTS['strokeColor']) - self.setLineCap(2) - self.setLineJoin(0) - self.setLineWidth(1) - - self.PostScriptLevel=PostScriptLevel - - def comment(self,msg): - self.code.append('%'+msg) - - def drawImage(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version - # select between postscript level 1 or level 2 - if PostScriptLevel==1: - self._drawImageLevel1(image, x1,y1, x2=None,y2=None) - elif PostScriptLevel == 2 : - self._drawImageLevel2(image, x1,y1, x2=None,y2=None) - else : - raise 'PostScriptLevelException' - - def clear(self): - self.code.append('showpage') # ugh, this makes no sense oh well. - - def save(self,f=None): - if not hasattr(f,'write'): - file = open(f,'wb') - else: - file = f - if self.code[-1]!='showpage': self.clear() - self.code.insert(0,'''\ -%%!PS-Adobe-3.0 EPSF-3.0 -%%%%BoundingBox: 0 0 %d %d -%%%% Initialization: -/m {moveto} bind def -/l {lineto} bind def -/c {curveto} bind def - -%s -''' % (self.width,self.height, PS_WinAnsiEncoding)) - - # for each font used, reencode the vectors - fontReencode = [] - for fontName in self._fontsUsed: - fontReencode.append('WinAnsiEncoding /%s /%s RE' % (fontName, fontName)) - self.code.insert(1, string.join(fontReencode, self._sep)) - - file.write(string.join(self.code,self._sep)) - if file is not f: - file.close() - from reportlab.lib.utils import markfilename - markfilename(f,creatorcode='XPR3',filetype='EPSF') - - def saveState(self): - self.code.append('gsave') - - def restoreState(self): - self.code.append('grestore') - - def stringWidth(self, s, font=None, fontSize=None): - """Return the logical width of the string if it were drawn - in the current font (defaults to self.font).""" - font = font or self._font - fontSize = fontSize or self._fontSize - return stringWidth(s, font, fontSize) - - def setLineCap(self,v): - if self._lineCap!=v: - self._lineCap = v - self.code.append('%d setlinecap'%v) - - def setLineJoin(self,v): - if self._lineJoin!=v: - self._lineJoin = v - self.code.append('%d setlinejoin'%v) - - def setDash(self, array=[], phase=0): - """Two notations. pass two numbers, or an array and phase""" - # copied and modified from reportlab.canvas - psoperation = "setdash" - if type(array) == types.IntType or type(array) == types.FloatType: - self._code.append('[%s %s] 0 %s' % (array, phase, psoperation)) - elif type(array) == types.ListType or type(array) == types.TupleType: - assert phase >= 0, "phase is a length in user space" - textarray = string.join(map(str, array)) - self.code.append('[%s] %s %s' % (textarray, phase, psoperation)) - - def setStrokeColor(self, color): - self._strokeColor = color - self.setColor(color) - - def setColor(self, color): - if self._color!=color: - self._color = color - if color: - if hasattr(color, "cyan"): - self.code.append('%s setcmykcolor' % fp_str(color.cyan, color.magenta, color.yellow, color.black)) - else: - self.code.append('%s setrgbcolor' % fp_str(color.red, color.green, color.blue)) - - def setFillColor(self, color): - self._fillColor = color - self.setColor(color) - - def setLineWidth(self, width): - if width != self._lineWidth: - self._lineWidth = width - self.code.append('%s setlinewidth' % width) - - def setFont(self,font,fontSize,leading=None): - if self._font!=font or self._fontSize!=fontSize: - self._fontCodeLoc = len(self.code) - self._font = font - self._fontSize = fontSize - self.code.append('') - - def line(self, x1, y1, x2, y2): - if self._strokeColor != None: - self.code.append('%s m %s l stroke' % (fp_str(x1, y1), fp_str(x2, y2))) - - def _escape(self, s): - ''' - return a copy of string s with special characters in postscript strings - escaped with backslashes. - Have not handled characters that are converted normally in python strings - i.e. \n -> newline - ''' - str = string.replace(s, chr(0x5C), r'\\' ) - str = string.replace(str, '(', '\(' ) - str = string.replace(str, ')', '\)') - return str - - def drawString(self, x, y, s, angle=0): - if self._fillColor != None: - if not self.code[self._fontCodeLoc]: - psName = getFont(self._font).face.name - self.code[self._fontCodeLoc]='(%s) findfont %s scalefont setfont' % (psName,fp_str(self._fontSize)) - if psName not in self._fontsUsed: - self._fontsUsed.append(psName) - self.setColor(self._fillColor) - s = self._escape(s) -## before inverting... -## if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops -## self.code.append('%s m 1 -1 scale (%s) show 1 -1 scale' % (fp_str(x,y),s)) -## else : # general case, rotated text -## self.code.append('gsave %s %s translate %s rotate' % (x,y,angle)) -## self.code.append('0 0 m 1 -1 scale (%s) show' % s) -## self.code.append('grestore') - if angle == 0 : # do special case of angle = 0 first. Avoids a bunch of gsave/grestore ops - self.code.append('%s m (%s) show ' % (fp_str(x,y),s)) - else : # general case, rotated text - self.code.append('gsave %s %s translate %s rotate' % (x,y,angle)) - self.code.append('0 0 m (%s) show' % s) - self.code.append('grestore') - - def drawCentredString(self, x, y, text, text_anchor='middle'): - if self.fillColor is not None: - textLen = stringWidth(text, self._font,self._fontSize) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2 - self.drawString(x,y,text) - - def drawRightString(self, text, x, y): - self.drawCentredString(text,x,y,text_anchor='end') - - def drawCurve(self, x1, y1, x2, y2, x3, y3, x4, y4, closed=0): - codeline = '%s m %s curveto' - data = (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4)) - if self._fillColor != None: - self.setColor(self._fillColor) - self.code.append((codeline % data) + ' eofill') - if self._strokeColor != None: - self.setColor(self._strokeColor) - self.code.append((codeline % data) - + ((closed and ' closepath') or '') - + ' stroke') - - ######################################################################################## - - def rect(self, x1,y1, x2,y2, stroke=1, fill=1): - "Draw a rectangle between x1,y1, and x2,y2" - # Path is drawn in counter-clockwise direction" - - x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py - y1, y2 = min(y1,y2), max(y1, y2) - self.polygon(((x1,y1),(x2,y1),(x2,y2),(x1,y2)), closed=1, stroke=stroke, fill = fill) - - def roundRect(self, x1,y1, x2,y2, rx=8, ry=8): - """Draw a rounded rectangle between x1,y1, and x2,y2, - with corners inset as ellipses with x radius rx and y radius ry. - These should have x10, and ry>0.""" - # Path is drawn in counter-clockwise direction - - x1, x2 = min(x1,x2), max(x1, x2) # from piddle.py - y1, y2 = min(y1,y2), max(y1, y2) - - # Note: arcto command draws a line from current point to beginning of arc - # save current matrix, translate to center of ellipse, scale by rx ry, and draw - # a circle of unit radius in counterclockwise dir, return to original matrix - # arguments are (cx, cy, rx, ry, startAngle, endAngle) - ellipsePath = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s arc setmatrix' - - # choice between newpath and moveTo beginning of arc - # go with newpath for precision, does this violate any assumptions in code??? - rrCode = ['newpath'] # Round Rect code path - # upper left corner ellipse is first - rrCode.append(ellipsePath % (x1+rx, y1+ry, rx, -ry, 90, 180)) - rrCode.append(ellipsePath % (x1+rx, y2-ry, rx, -ry, 180, 270)) - rrCode.append(ellipsePath % (x2-rx, y2-ry, rx, -ry, 270, 360)) - rrCode.append(ellipsePath % (x2-rx, y1+ry, rx, -ry, 0, 90) ) - rrCode.append('closepath') - - self._fillAndStroke(rrCode) - - def ellipse(self, x1,y1, x2,y2): - """Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. - These should have x1= 0: - arc='arc' - else: - arc='arcn' - data = (x,y, xScale, yScale, startAng, startAng+extent, arc) - - return codeline % data - - def polygon(self, p, closed=0, stroke=1, fill=1): - assert len(p) >= 2, 'Polygon must have 2 or more points' - - start = p[0] - p = p[1:] - - polyCode = [] - polyCode.append("%s m" % fp_str(start)) - for point in p: - polyCode.append("%s l" % fp_str(point)) - if closed: - polyCode.append("closepath") - - self._fillAndStroke(polyCode,stroke=stroke,fill=fill) - - def lines(self, lineList, color=None, width=None): - if self._strokeColor != None: - self._setColor(self._strokeColor) - codeline = '%s m %s l stroke' - for line in lineList: - self.code.append(codeline % (fp_str(line[0]),fp_str(line[1]))) - - def moveTo(self,x,y): - self.code.append('%s m' % fp_str(x, y)) - - def lineTo(self,x,y): - self.code.append('%s l' % fp_str(x, y)) - - def curveTo(self,x1,y1,x2,y2,x3,y3): - self.code.append('%s c' % fp_str(x1,y1,x2,y2,x3,y3)) - - def closePath(self): - self.code.append('closepath') - - def polyLine(self, p): - assert len(p) >= 1, 'Polyline must have 1 or more points' - if self._strokeColor != None: - self.setColor(self._strokeColor) - self.moveTo(p[0][0], p[0][1]) - for t in p[1:]: - self.lineTo(t[0], t[1]) - self.code.append('stroke') - - - def drawFigure(self, partList, closed=0): - figureCode = [] - first = 1 - - for part in partList: - op = part[0] - args = list(part[1:]) - - if op == figureLine: - if first: - first = 0 - figureCode.append("%s m" % fp_str(args[:2])) - else: - figureCode.append("%s l" % fp_str(args[:2])) - figureCode.append("%s l" % fp_str(args[2:])) - - elif op == figureArc: - first = 0 - x1,y1,x2,y2,startAngle,extent = args[:6] - figureCode.append(self._genArcCode(x1,y1,x2,y2,startAngle,extent)) - - elif op == figureCurve: - if first: - first = 0 - figureCode.append("%s m" % fp_str(args[:2])) - else: - figureCode.append("%s l" % fp_str(args[:2])) - figureCode.append("%s curveto" % fp_str(args[2:])) - else: - raise TypeError, "unknown figure operator: "+op - - if closed: - figureCode.append("closepath") - self._fillAndStroke(figureCode) - - def _fillAndStroke(self,code,clip=0,fill=1,stroke=1): - fill = self._fillColor and fill - stroke = self._strokeColor and stroke - if fill or stroke or clip: - self.code.extend(code) - if fill: - if stroke or clip: self.code.append("gsave") - self.setColor(self._fillColor) - self.code.append("eofill") - if stroke or clip: self.code.append("grestore") - if stroke: - if clip: self.code.append("gsave") - self.setColor(self._strokeColor) - self.code.append("stroke") - if clip: self.code.append("grestore") - if clip: - self.code.append("clip") - self.code.append("newpath") - - - def translate(self,x,y): - self.code.append('%s translate' % fp_str(x,y)) - - def scale(self,x,y): - self.code.append('%s scale' % fp_str(x,y)) - - def transform(self,a,b,c,d,e,f): - self.code.append('[%s] concat' % fp_str(a,b,c,d,e,f)) - - def _drawTimeResize(self,w,h): - '''if this is used we're probably in the wrong world''' - self.width, self.height = w, h - - ############################################################################################ - # drawImage(self. image, x1, y1, x2=None, y2=None) is now defined by either _drawImageLevel1 - # ._drawImageLevel2, the choice is made in .__init__ depending on option - def _drawImageLevel1(self, image, x1, y1, x2=None,y2=None): - # Postscript Level1 version available for fallback mode when Level2 doesn't work - """drawImage(self,image,x1,y1,x2=None,y2=None) : If x2 and y2 are ommitted, they are - calculated from image size. (x1,y1) is upper left of image, (x2,y2) is lower right of - image in piddle coordinates.""" - # For now let's start with 24 bit RGB images (following piddlePDF again) - print "Trying to drawImage in piddlePS" - component_depth = 8 - myimage = image.convert('RGB') - imgwidth, imgheight = myimage.size - if not x2: - x2 = imgwidth + x1 - if not y2: - y2 = y1 + imgheight - drawwidth = x2 - x1 - drawheight = y2 - y1 - print 'Image size (%d, %d); Draw size (%d, %d)' % (imgwidth, imgheight, drawwidth, drawheight) - # now I need to tell postscript how big image is - - # "image operators assume that they receive sample data from - # their data source in x-axis major index order. The coordinate - # of the lower-left corner of the first sample is (0,0), of the - # second (1,0) and so on" -PS2 ref manual p. 215 - # - # The ImageMatrix maps unit squre of user space to boundary of the source image - # - - # The CurrentTransformationMatrix (CTM) maps the unit square of - # user space to the rect...on the page that is to receive the - # image. A common ImageMatrix is [width 0 0 -height 0 height] - # (for a left to right, top to bottom image ) - - # first let's map the user coordinates start at offset x1,y1 on page - - self.code.extend([ - 'gsave', - '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image - '%s %s scale' % (drawwidth,drawheight), - '/scanline %d 3 mul string def' % imgwidth # scanline by multiples of image width - ]) - - # now push the dimensions and depth info onto the stack - # and push the ImageMatrix to map the source to the target rectangle (see above) - # finally specify source (PS2 pp. 225 ) and by exmample - self.code.extend([ - '%s %s %s' % (imgwidth, imgheight, component_depth), - '[%s %s %s %s %s %s]' % (imgwidth, 0, 0, -imgheight, 0, imgheight), - '{ currentfile scanline readhexstring pop } false 3', - 'colorimage ' - ]) - - # data source output--now we just need to deliver a hex encode - # series of lines of the right overall size can follow - # piddlePDF again - - rawimage = myimage.tostring() - assert(len(rawimage) == imgwidth*imgheight, 'Wrong amount of data for image') - #compressed = zlib.compress(rawimage) # no zlib at moment - hex_encoded = self._AsciiHexEncode(rawimage) - - # write in blocks of 78 chars per line - outstream = getStringIO(hex_encoded) - - dataline = outstream.read(78) - while dataline <> "": - self.code.append(dataline) - dataline= outstream.read(78) - self.code.append('% end of image data') # for clarity - self.code.append('grestore') # return coordinates to normal - - # end of drawImage - def _AsciiHexEncode(self, input): # also based on piddlePDF - "Helper function used by images" - output = getStringIO() - for char in input: - output.write('%02x' % ord(char)) - return output.getvalue() - - def _drawImageLevel2(self, image, x1,y1, x2=None,y2=None): # Postscript Level2 version - Image = import_Image() - if not Image: return - ### what sort of image are we to draw - if image.mode=='L' : - print 'found image.mode= L' - imBitsPerComponent = 8 - imNumComponents = 1 - myimage = image - elif image.mode == '1': - print 'found image.mode= 1' - myimage = image.convert('L') - imNumComponents = 1 - myimage = image - else : - myimage = image.convert('RGB') - imNumComponents = 3 - imBitsPerComponent = 8 - - imwidth, imheight = myimage.size - # print 'imwidth = %s, imheight = %s' % myimage.size - if not x2: - x2 = imwidth + x1 - if not y2: - y2 = y1 + imheight - drawwidth = x2 - x1 - drawheight = y2 - y1 - self.code.extend([ - 'gsave', - '%s %s translate' % (x1,-y1 - drawheight), # need to start are lower left of image - '%s %s scale' % (drawwidth,drawheight)]) - - if imNumComponents == 3 : - self.code.append('/DeviceRGB setcolorspace') - elif imNumComponents == 1 : - self.code.append('/DeviceGray setcolorspace') - print 'setting colorspace gray' - # create the image dictionary - self.code.append(""" -<< -/ImageType 1 -/Width %d /Height %d %% dimensions of source image -/BitsPerComponent %d""" % (imwidth, imheight, imBitsPerComponent) ) - - if imNumComponents == 1: - self.code.append('/Decode [0 1]') - if imNumComponents == 3: - self.code.append('/Decode [0 1 0 1 0 1] %% decode color values normally') - - self.code.extend([ '/ImageMatrix [%s 0 0 %s 0 %s]' % (imwidth, -imheight, imheight), - '/DataSource currentfile /ASCIIHexDecode filter', - '>> % End image dictionary', - 'image']) - # after image operator just need to dump image dat to file as hexstring - rawimage = myimage.tostring() - assert(len(rawimage) == imwidth*imheight, 'Wrong amount of data for image') - #compressed = zlib.compress(rawimage) # no zlib at moment - hex_encoded = self._AsciiHexEncode(rawimage) - - # write in blocks of 78 chars per line - outstream = getStringIO(hex_encoded) - - dataline = outstream.read(78) - while dataline <> "": - self.code.append(dataline) - dataline= outstream.read(78) - self.code.append('> % end of image data') # > is EOD for hex encoded filterfor clarity - self.code.append('grestore') # return coordinates to normal - -# renderpdf - draws them onto a canvas -"""Usage: - from reportlab.graphics import renderPS - renderPS.draw(drawing, canvas, x, y) -Execute the script to see some test drawings.""" -from shapes import * -from renderbase import Renderer - -# hack so we only get warnings once each -#warnOnce = WarnOnce() - -# the main entry point for users... -def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary): - """As it says""" - R = _PSRenderer() - R.draw(drawing, canvas, x, y, showBoundary=showBoundary) - -def _pointsFromList(L): - ''' - given a list of coordinates [x0, y0, x1, y1....] - produce a list of points [(x0,y0), (y1,y0),....] - ''' - P=[] - for i in range(0,len(L),2): - P.append((L[i],L[i+1])) - return P - -class _PSRenderer(Renderer): - """This draws onto a EPS document. It needs to be a class - rather than a function, as some EPS-specific state tracking is - needed outside of the state info in the SVG model.""" - - def __init__(self): - self._tracker = StateTracker() - - def drawNode(self, node): - """This is the recursive method called for each node - in the tree""" - self._canvas.comment('begin node %s'%`node`) - color = self._canvas._color - if not (isinstance(node, Path) and node.isClipPath): - self._canvas.saveState() - - #apply state changes - deltas = getStateDelta(node) - self._tracker.push(deltas) - self.applyStateChanges(deltas, {}) - - #draw the object, or recurse - self.drawNodeDispatcher(node) - - rDeltas = self._tracker.pop() - if not (isinstance(node, Path) and node.isClipPath): - self._canvas.restoreState() - self._canvas.comment('end node %s'%`node`) - self._canvas._color = color - - #restore things we might have lost (without actually doing anything). - for k, v in rDeltas.items(): - if self._restores.has_key(k): - setattr(self._canvas,self._restores[k],v) - -## _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap', -## 'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font', -## 'font_size':'_fontSize'} - _restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap', - 'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font', - 'fontSize':'_fontSize'} - - def drawRect(self, rect): - if rect.rx == rect.ry == 0: - #plain old rectangle - self._canvas.rect( - rect.x, rect.y, - rect.x+rect.width, rect.y+rect.height) - else: - #cheat and assume ry = rx; better to generalize - #pdfgen roundRect function. TODO - self._canvas.roundRect( - rect.x, rect.y, - rect.x+rect.width, rect.y+rect.height, rect.rx, rect.ry - ) - - def drawLine(self, line): - if self._canvas._strokeColor: - self._canvas.line(line.x1, line.y1, line.x2, line.y2) - - def drawCircle(self, circle): - self._canvas.circle( circle.cx, circle.cy, circle.r) - - def drawWedge(self, wedge): - yradius, radius1, yradius1 = wedge._xtraRadii() - if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None): - startangledegrees = wedge.startangledegrees - endangledegrees = wedge.endangledegrees - centerx= wedge.centerx - centery = wedge.centery - radius = wedge.radius - extent = endangledegrees - startangledegrees - self._canvas.drawArc(centerx-radius, centery-yradius, centerx+radius, centery+yradius, - startangledegrees, extent, fromcenter=1) - else: - self.drawPolygon(wedge.asPolygon()) - - def drawPolyLine(self, p): - if self._canvas._strokeColor: - self._canvas.polyLine(_pointsFromList(p.points)) - - def drawEllipse(self, ellipse): - #need to convert to pdfgen's bounding box representation - x1 = ellipse.cx - ellipse.rx - x2 = ellipse.cx + ellipse.rx - y1 = ellipse.cy - ellipse.ry - y2 = ellipse.cy + ellipse.ry - self._canvas.ellipse(x1,y1,x2,y2) - - def drawPolygon(self, p): - self._canvas.polygon(_pointsFromList(p.points), closed=1) - - def drawString(self, stringObj): - if self._canvas._fillColor: - S = self._tracker.getState() - text_anchor, x, y, text = S['textAnchor'], stringObj.x,stringObj.y,stringObj.text - if not text_anchor in ['start','inherited']: - font, fontSize = S['fontName'], S['fontSize'] - textLen = stringWidth(text, font,fontSize) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2 - else: - raise ValueError, 'bad value for text_anchor '+str(text_anchor) - self._canvas.drawString(x,y,text) - - def drawPath(self, path): - from reportlab.graphics.shapes import _renderPath - c = self._canvas - drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath) - isClosed = _renderPath(path, drawFuncs) - if not isClosed: - c._fillColor = None - c._fillAndStroke([], clip=path.isClipPath) - - def applyStateChanges(self, delta, newState): - """This takes a set of states, and outputs the operators - needed to set those properties""" - for key, value in delta.items(): - if key == 'transform': - self._canvas.transform(value[0], value[1], value[2], - value[3], value[4], value[5]) - elif key == 'strokeColor': - #this has different semantics in PDF to SVG; - #we always have a color, and either do or do - #not apply it; in SVG one can have a 'None' color - self._canvas.setStrokeColor(value) - elif key == 'strokeWidth': - self._canvas.setLineWidth(value) - elif key == 'strokeLineCap': #0,1,2 - self._canvas.setLineCap(value) - elif key == 'strokeLineJoin': - self._canvas.setLineJoin(value) - elif key == 'strokeDashArray': - if value: - self._canvas.setDash(value) - else: - self._canvas.setDash() -## elif key == 'stroke_opacity': -## warnOnce('Stroke Opacity not supported yet') - elif key == 'fillColor': - #this has different semantics in PDF to SVG; - #we always have a color, and either do or do - #not apply it; in SVG one can have a 'None' color - self._canvas.setFillColor(value) -## elif key == 'fill_rule': -## warnOnce('Fill rules not done yet') -## elif key == 'fill_opacity': -## warnOnce('Fill opacity not done yet') - elif key in ['fontSize', 'fontName']: - # both need setting together in PDF - # one or both might be in the deltas, - # so need to get whichever is missing - fontname = delta.get('fontName', self._canvas._font) - fontsize = delta.get('fontSize', self._canvas._fontSize) - self._canvas.setFont(fontname, fontsize) - -def drawToFile(d,fn, showBoundary=rl_config.showBoundary): - c = PSCanvas((d.width,d.height)) - draw(d, c, 0, 0, showBoundary=showBoundary) - c.save(fn) - -def drawToString(d, showBoundary=rl_config.showBoundary): - "Returns a PS as a string in memory, without touching the disk" - s = getStringIO() - drawToFile(d, s, showBoundary=showBoundary) - return s.getvalue() - -######################################################### -# -# test code. First, defin a bunch of drawings. -# Routine to draw them comes at the end. -# -######################################################### -def test(outdir='epsout'): - import os - # print all drawings and their doc strings from the test - # file - if not os.path.isdir(outdir): - os.mkdir(outdir) - #grab all drawings from the test module - import testshapes - drawings = [] - - for funcname in dir(testshapes): - #if funcname[0:11] == 'getDrawing2': - # print 'hacked to only show drawing 2' - if funcname[0:10] == 'getDrawing': - drawing = eval('testshapes.' + funcname + '()') #execute it - docstring = eval('testshapes.' + funcname + '.__doc__') - drawings.append((drawing, docstring)) - - i = 0 - for (d, docstring) in drawings: - filename = outdir + os.sep + 'renderPS_%d.eps'%i - drawToFile(d,filename) - print 'saved', filename - i = i + 1 - -if __name__=='__main__': - import sys - if len(sys.argv)>1: - outdir = sys.argv[1] - else: - outdir = 'epsout' - test(outdir) diff --git a/bin/reportlab/graphics/renderSVG.py b/bin/reportlab/graphics/renderSVG.py deleted file mode 100644 index c987dc8a695..00000000000 --- a/bin/reportlab/graphics/renderSVG.py +++ /dev/null @@ -1,813 +0,0 @@ -"""An experimental SVG renderer for the ReportLab graphics framework. - -This will create SVG code from the ReportLab Graphics API (RLG). -To read existing SVG code and convert it into ReportLab graphics -objects download the svglib module here: - - http://python.net/~gherman/#svglib -""" - -import math, string, types, sys, os -from types import StringType -from operator import getitem - -from reportlab.pdfbase.pdfmetrics import stringWidth # for font info -from reportlab.lib.utils import fp_str -from reportlab.lib.colors import black -from reportlab.graphics.renderbase import StateTracker, getStateDelta, Renderer -from reportlab.graphics.shapes import STATE_DEFAULTS, Path, UserNode -from reportlab.graphics.shapes import * # (only for test0) -from reportlab import rl_config -from reportlab.lib.utils import getStringIO - -from xml.dom import getDOMImplementation - - -### some constants ### - -sin = math.sin -cos = math.cos -pi = math.pi - -LINE_STYLES = 'stroke-width stroke-linecap stroke fill stroke-dasharray' -TEXT_STYLES = 'font-family font-size' - - -### top-level user function ### - -def drawToString(d, showBoundary=rl_config.showBoundary): - "Returns a SVG as a string in memory, without touching the disk" - s = getStringIO() - drawToFile(d, s, showBoundary=showBoundary) - return s.getvalue() - - -def drawToFile(d, fn, showBoundary=rl_config.showBoundary): - c = SVGCanvas((d.width, d.height)) - draw(d, c, 0, 0, showBoundary=showBoundary) - c.save(fn) - - -def draw(drawing, canvas, x=0, y=0, showBoundary=rl_config.showBoundary): - """As it says.""" - - r = _SVGRenderer() - r.draw(drawing, canvas, x, y, showBoundary=showBoundary) - - -### helper functions ### - -def _pointsFromList(L): - """ - given a list of coordinates [x0, y0, x1, y1....] - produce a list of points [(x0,y0), (y1,y0),....] - """ - - P=[] - for i in range(0,len(L), 2): - P.append((L[i], L[i+1])) - - return P - - -def transformNode(doc, newTag, node=None, **attrDict): - """Transform a DOM node into new node and copy selected attributes. - - Creates a new DOM node with tag name 'newTag' for document 'doc' - and copies selected attributes from an existing 'node' as provided - in 'attrDict'. The source 'node' can be None. Attribute values will - be converted to strings. - - E.g. - - n = transformNode(doc, "node1", x="0", y="1") - -> DOM node for - - n = transformNode(doc, "node1", x=0, y=1+1) - -> DOM node for - - n = transformNode(doc, "node1", node0, x="x0", y="x0", zoo=bar()) - -> DOM node for - """ - - newNode = doc.createElement(newTag) - for newAttr, attr in attrDict.items(): - sattr = str(attr) - if not node: - newNode.setAttribute(newAttr, sattr) - else: - attrVal = node.getAttribute(sattr) - newNode.setAttribute(newAttr, attrVal or sattr) - - return newNode - - -### classes ### - -class SVGCanvas: - def __init__(self, size=(300,300)): - self.verbose = 0 - self.width, self.height = self.size = size - # self.height = size[1] - self.code = [] - self.style = {} - self.path = '' - self._strokeColor = self._fillColor = self._lineWidth = \ - self._font = self._fontSize = self._lineCap = \ - self._lineJoin = self._color = None - - implementation = getDOMImplementation('minidom') - self.doc = implementation.createDocument(None, "svg", None) - self.svg = self.doc.documentElement - self.svg.setAttribute("width", str(size[0])) - self.svg.setAttribute("height", str(self.height)) - - title = self.doc.createElement('title') - text = self.doc.createTextNode('...') - title.appendChild(text) - self.svg.appendChild(title) - - desc = self.doc.createElement('desc') - text = self.doc.createTextNode('...') - desc.appendChild(text) - self.svg.appendChild(desc) - - self.setFont(STATE_DEFAULTS['fontName'], STATE_DEFAULTS['fontSize']) - self.setStrokeColor(STATE_DEFAULTS['strokeColor']) - self.setLineCap(2) - self.setLineJoin(0) - self.setLineWidth(1) - - # Add a rectangular clipping path identical to view area. - clipPath = transformNode(self.doc, "clipPath", id="clip") - clipRect = transformNode(self.doc, "rect", x=0, y=0, - width=self.width, height=self.height) - clipPath.appendChild(clipRect) - self.svg.appendChild(clipPath) - - self.groupTree = transformNode(self.doc, "g", - id="group", - transform="scale(1,-1) translate(0,-%d)" % self.height, - style="clip-path: url(#clip)") - self.svg.appendChild(self.groupTree) - self.currGroup = self.groupTree - - - def save(self, f=None): - if type(f) is StringType: - file = open(f, 'w') - else: - file = f - - file.write("""\ - -\n""") - - # use = self.doc.createElement('use') - # use.setAttribute("xlink:href", "#group") - # use.setAttribute("transform", "scale(1, -1)") - # self.svg.appendChild(use) - - result = self.svg.toprettyxml(indent=" ") - file.write(result) - - if file is not f: - file.close() - - - ### helpers ### - - def NOTUSED_stringWidth(self, s, font=None, fontSize=None): - """Return the logical width of the string if it were drawn - in the current font (defaults to self.font). - """ - - font = font or self._font - fontSize = fontSize or self._fontSize - - return stringWidth(s, font, fontSize) - - - def _formatStyle(self, include=''): - str = '' - include = string.split(include) - keys = self.style.keys() - if include: - #2.1-safe version of the line below follows: - #keys = filter(lambda k: k in include, keys) - tmp = [] - for word in keys: - if word in include: - tmp.append(word) - keys = tmp - - items = [] - for k in keys: - items.append((k, self.style[k])) - items = map(lambda i: "%s: %s"%(i[0], i[1]), items) - str = string.join(items, '; ') + ';' - - return str - - - def _escape(self, s): - """ - return a copy of string s with special characters in postscript strings - escaped with backslashes. - Have not handled characters that are converted normally in python strings - i.e. \n -> newline - """ - - str = string.replace(s, chr(0x5C), r'\\' ) - str = string.replace(str, '(', '\(' ) - str = string.replace(str, ')', '\)') - return str - - - def _genArcCode(self, x1, y1, x2, y2, startAng, extent): - """Calculate the path for an arc inscribed in rectangle defined - by (x1,y1),(x2,y2).""" - - return - - #calculate semi-minor and semi-major axes of ellipse - xScale = abs((x2-x1)/2.0) - yScale = abs((y2-y1)/2.0) - #calculate centre of ellipse - x, y = (x1+x2)/2.0, (y1+y2)/2.0 - - codeline = 'matrix currentmatrix %s %s translate %s %s scale 0 0 1 %s %s %s setmatrix' - - if extent >= 0: - arc='arc' - else: - arc='arcn' - data = (x,y, xScale, yScale, startAng, startAng+extent, arc) - - return codeline % data - - - def _fillAndStroke(self, code, clip=0): - path = transformNode(self.doc, "path", - d=self.path, style=self._formatStyle(LINE_STYLES)) - self.currGroup.appendChild(path) - self.path = '' - - return - - """ - if self._fillColor or self._strokeColor or clip: - self.code.extend(code) - if self._fillColor: - if self._strokeColor or clip: - self.code.append("gsave") - self.setColor(self._fillColor) - self.code.append("eofill") - if self._strokeColor or clip: - self.code.append("grestore") - if self._strokeColor != None: - if clip: self.code.append("gsave") - self.setColor(self._strokeColor) - self.code.append("stroke") - if clip: self.code.append("grestore") - if clip: - self.code.append("clip") - self.code.append("newpath") - """ - - - ### styles ### - - def setLineCap(self, v): - vals = {0:'butt', 1:'round', 2:'square'} - if self._lineCap != v: - self._lineCap = v - self.style['stroke-linecap'] = vals[v] - - - def setLineJoin(self, v): - vals = {0:'miter', 1:'round', 2:'bevel'} - if self._lineJoin != v: - self._lineJoin = v - self.style['stroke-linecap'] = vals[v] - - - def setDash(self, array=[], phase=0): - """Two notations. Pass two numbers, or an array and phase.""" - - join = string.join - if type(array) in (types.IntType, types.FloatType): - self.style['stroke-dasharray'] = join(map(str, ([array, phase])), ', ') - elif type(array) in (types.ListType, types.TupleType) and len(array) > 0: - assert phase >= 0, "phase is a length in user space" - self.style['stroke-dasharray'] = join(map(str, (array+[phase])), ', ') - - - def setStrokeColor(self, color): - self._strokeColor = color - self.setColor(color) - if color == None: - self.style['stroke'] = 'none' - else: - r, g, b = color.red, color.green, color.blue - self.style['stroke'] = 'rgb(%d%%,%d%%,%d%%)' % (r*100, g*100, b*100) - - - def setColor(self, color): - if self._color != color: - self._color = color - - - def setFillColor(self, color): - self._fillColor = color - self.setColor(color) - if color == None: - self.style['fill'] = 'none' - else: - r, g, b = color.red, color.green, color.blue - self.style['fill'] = 'rgb(%d%%,%d%%,%d%%)' % (r*100, g*100, b*100) - - - def setLineWidth(self, width): - if width != self._lineWidth: - self._lineWidth = width - self.style['stroke-width'] = width - - - def setFont(self, font, fontSize): - if self._font != font or self._fontSize != fontSize: - self._font, self._fontSize = (font, fontSize) - self.style['font-family'] = font - self.style['font-size'] = fontSize - - - ### shapes ### - - def rect(self, x1,y1, x2,y2, rx=8, ry=8): - "Draw a rectangle between x1,y1 and x2,y2." - - if self.verbose: print "+++ SVGCanvas.rect" - - rect = transformNode(self.doc, "rect", - x=x1, y=y1, width=x2-x1, height=y2-y1, - style=self._formatStyle(LINE_STYLES)) - - self.currGroup.appendChild(rect) - - - def roundRect(self, x1,y1, x2,y2, rx=8, ry=8): - """Draw a rounded rectangle between x1,y1 and x2,y2. - - Corners inset as ellipses with x-radius rx and y-radius ry. - These should have x10, and ry>0. - """ - - rect = transformNode(self.doc, "rect", - x=x1, y=y1, width=x2-x1, height=y2-y1, rx=rx, ry=ry, - style=self._formatStyle(LINE_STYLES)) - - self.currGroup.appendChild(rect) - - - def drawString(self, s, x, y, angle=0): - if self.verbose: print "+++ SVGCanvas.drawString" - - if self._fillColor != None: - self.setColor(self._fillColor) - s = self._escape(s) - st = self._formatStyle(TEXT_STYLES) - if angle != 0: - st = st + " rotate(%f %f %f);" % (angle, x, y) - st = st + " fill: %s;" % self.style['fill'] - text = transformNode(self.doc, "text", - x=x, y=y, style=st, - transform="translate(0,%d) scale(1,-1)" % (2*y)) - content = self.doc.createTextNode(s) - text.appendChild(content) - - self.currGroup.appendChild(text) - - - def comment(self, data): - "Add a comment." - - comment = self.doc.createComment(data) - # self.currGroup.appendChild(comment) - - - def drawImage(self, image, x1, y1, x2=None, y2=None): - pass - - - def line(self, x1, y1, x2, y2): - if self._strokeColor != None: - if 0: # something is wrong with line in my SVG viewer... - line = transformNode(self.doc, "line", - x=x1, y=y1, x2=x2, y2=y2, - style=self._formatStyle(LINE_STYLES)) - self.currGroup.appendChild(line) - path = transformNode(self.doc, "path", - d="M %f,%f L %f,%f Z" % (x1,y1,x2,y2), - style=self._formatStyle(LINE_STYLES)) - self.currGroup.appendChild(path) - - - def ellipse(self, x1, y1, x2, y2): - """Draw an orthogonal ellipse inscribed within the rectangle x1,y1,x2,y2. - - These should have x1=180, 0, mx, my) - else: - str = str + "M %f, %f A %f, %f %d %d %d %f, %f Z " % \ - (mx, my, rx, ry, 0, extent>=180, 0, mx, my) - - if fromcenter: - str = str + "L %f, %f Z " % (cx, cy) - - path = transformNode(self.doc, "path", - d=str, style=self._formatStyle()) - self.currGroup.appendChild(path) - - - def polygon(self, points, closed=0): - assert len(points) >= 2, 'Polygon must have 2 or more points' - - if self._strokeColor != None: - self.setColor(self._strokeColor) - pairs = [] - for i in xrange(len(points)): - pairs.append("%f %f" % (points[i])) - pts = string.join(pairs, ', ') - polyline = transformNode(self.doc, "polygon", - points=pts, style=self._formatStyle(LINE_STYLES)) - self.currGroup.appendChild(polyline) - - # self._fillAndStroke(polyCode) - - - def lines(self, lineList, color=None, width=None): - # print "### lineList", lineList - return - - if self._strokeColor != None: - self._setColor(self._strokeColor) - codeline = '%s m %s l stroke' - for line in lineList: - self.code.append(codeline % (fp_str(line[0]), fp_str(line[1]))) - - - def polyLine(self, points): - assert len(points) >= 1, 'Polyline must have 1 or more points' - - if self._strokeColor != None: - self.setColor(self._strokeColor) - pairs = [] - for i in xrange(len(points)): - pairs.append("%f %f" % (points[i])) - pts = string.join(pairs, ', ') - polyline = transformNode(self.doc, "polyline", - points=pts, style=self._formatStyle(LINE_STYLES)) - self.currGroup.appendChild(polyline) - - - ### groups ### - - def startGroup(self): - if self.verbose: print "+++ begin SVGCanvas.startGroup" - currGroup, group = self.currGroup, transformNode(self.doc, "g", transform="") - currGroup.appendChild(group) - self.currGroup = group - if self.verbose: print "+++ end SVGCanvas.startGroup" - return currGroup - - def endGroup(self,currGroup): - if self.verbose: print "+++ begin SVGCanvas.endGroup" - self.currGroup = currGroup - if self.verbose: print "+++ end SVGCanvas.endGroup" - - - def transform(self, a, b, c, d, e, f): - if self.verbose: print "!!! begin SVGCanvas.transform", a, b, c, d, e, f - tr = self.currGroup.getAttribute("transform") - t = 'matrix(%f, %f, %f, %f, %f, %f)' % (a,b,c,d,e,f) - if (a, b, c, d, e, f) != (1, 0, 0, 1, 0, 0): - self.currGroup.setAttribute("transform", "%s %s" % (tr, t)) - - - def translate(self, x, y): - # probably never used - print "!!! begin SVGCanvas.translate" - return - - tr = self.currGroup.getAttribute("transform") - t = 'translate(%f, %f)' % (x, y) - self.currGroup.setAttribute("transform", "%s %s" % (tr, t)) - - - def scale(self, x, y): - # probably never used - print "!!! begin SVGCanvas.scale" - return - - tr = self.groups[-1].getAttribute("transform") - t = 'scale(%f, %f)' % (x, y) - self.currGroup.setAttribute("transform", "%s %s" % (tr, t)) - - - ### paths ### - - def moveTo(self, x, y): - self.path = self.path + 'M %f %f ' % (x, y) - - - def lineTo(self, x, y): - self.path = self.path + 'L %f %f ' % (x, y) - - - def curveTo(self, x1, y1, x2, y2, x3, y3): - self.path = self.path + 'C %f %f %f %f %f %f ' % (x1, y1, x2, y2, x3, y3) - - - def closePath(self): - self.path = self.path + 'Z ' - - def saveState(self): - pass - - def restoreState(self): - pass - -class _SVGRenderer(Renderer): - """This draws onto an SVG document. - """ - - def __init__(self): - self._tracker = StateTracker() - self.verbose = 0 - - def drawNode(self, node): - """This is the recursive method called for each node in the tree. - """ - - if self.verbose: print "### begin _SVGRenderer.drawNode" - - self._canvas.comment('begin node %s'%`node`) - color = self._canvas._color - if not (isinstance(node, Path) and node.isClipPath): - pass # self._canvas.saveState() - - #apply state changes - deltas = getStateDelta(node) - self._tracker.push(deltas) - self.applyStateChanges(deltas, {}) - - #draw the object, or recurse - self.drawNodeDispatcher(node) - - rDeltas = self._tracker.pop() - if not (isinstance(node, Path) and node.isClipPath): - pass # self._canvas.restoreState() - self._canvas.comment('end node %s'%`node`) - self._canvas._color = color - - #restore things we might have lost (without actually doing anything). - for k, v in rDeltas.items(): - if self._restores.has_key(k): - setattr(self._canvas,self._restores[k],v) - - if self.verbose: print "### end _SVGRenderer.drawNode" - - _restores = {'strokeColor':'_strokeColor','strokeWidth': '_lineWidth','strokeLineCap':'_lineCap', - 'strokeLineJoin':'_lineJoin','fillColor':'_fillColor','fontName':'_font', - 'fontSize':'_fontSize'} - - - def drawGroup(self, group): - if self.verbose: print "### begin _SVGRenderer.drawGroup" - - currGroup = self._canvas.startGroup() - a, b, c, d, e, f = self._tracker.getCTM() - for childNode in group.getContents(): - if isinstance(childNode, UserNode): - node2 = childNode.provideNode() - else: - node2 = childNode - self.drawNode(node2) - self._canvas.transform(a, b, c, d, e, f) - self._canvas.endGroup(currGroup) - - if self.verbose: print "### end _SVGRenderer.drawGroup" - - - def drawRect(self, rect): - if rect.rx == rect.ry == 0: - #plain old rectangle - self._canvas.rect( - rect.x, rect.y, - rect.x+rect.width, rect.y+rect.height) - else: - #cheat and assume ry = rx; better to generalize - #pdfgen roundRect function. TODO - self._canvas.roundRect( - rect.x, rect.y, - rect.x+rect.width, rect.y+rect.height, - rect.rx, rect.ry - ) - - - def drawString(self, stringObj): - if self._canvas._fillColor: - S = self._tracker.getState() - text_anchor, x, y, text = S['textAnchor'], stringObj.x, stringObj.y, stringObj.text - if not text_anchor in ['start', 'inherited']: - font, fontSize = S['fontName'], S['fontSize'] - textLen = stringWidth(text, font,fontSize) - if text_anchor=='end': - x = x-textLen - elif text_anchor=='middle': - x = x - textLen/2 - else: - raise ValueError, 'bad value for text_anchor ' + str(text_anchor) - self._canvas.drawString(text,x,y) - - - def drawLine(self, line): - if self._canvas._strokeColor: - self._canvas.line(line.x1, line.y1, line.x2, line.y2) - - - def drawCircle(self, circle): - self._canvas.circle( circle.cx, circle.cy, circle.r) - - - def drawWedge(self, wedge): - centerx, centery, radius, startangledegrees, endangledegrees = \ - wedge.centerx, wedge.centery, wedge.radius, wedge.startangledegrees, wedge.endangledegrees - yradius = wedge.yradius or wedge.radius - (x1, y1) = (centerx-radius, centery-yradius) - (x2, y2) = (centerx+radius, centery+yradius) - extent = endangledegrees - startangledegrees - self._canvas.drawArc(x1, y1, x2, y2, startangledegrees, extent, fromcenter=1) - - - def drawPolyLine(self, p): - if self._canvas._strokeColor: - self._canvas.polyLine(_pointsFromList(p.points)) - - - def drawEllipse(self, ellipse): - #need to convert to pdfgen's bounding box representation - x1 = ellipse.cx - ellipse.rx - x2 = ellipse.cx + ellipse.rx - y1 = ellipse.cy - ellipse.ry - y2 = ellipse.cy + ellipse.ry - self._canvas.ellipse(x1,y1,x2,y2) - - - def drawPolygon(self, p): - self._canvas.polygon(_pointsFromList(p.points), closed=1) - - - def drawPath(self, path): - # print "### drawPath", path.points - from reportlab.graphics.shapes import _renderPath - c = self._canvas - drawFuncs = (c.moveTo, c.lineTo, c.curveTo, c.closePath) - isClosed = _renderPath(path, drawFuncs) - if not isClosed: - c._fillColor = None - c._fillAndStroke([], clip=path.isClipPath) - - - def applyStateChanges(self, delta, newState): - """This takes a set of states, and outputs the operators - needed to set those properties""" - - for key, value in delta.items(): - if key == 'transform': - pass - #self._canvas.transform(value[0], value[1], value[2], value[3], value[4], value[5]) - elif key == 'strokeColor': - self._canvas.setStrokeColor(value) - elif key == 'strokeWidth': - self._canvas.setLineWidth(value) - elif key == 'strokeLineCap': #0,1,2 - self._canvas.setLineCap(value) - elif key == 'strokeLineJoin': - self._canvas.setLineJoin(value) - elif key == 'strokeDashArray': - if value: - self._canvas.setDash(value) - else: - self._canvas.setDash() - elif key == 'fillColor': - self._canvas.setFillColor(value) - elif key in ['fontSize', 'fontName']: - fontname = delta.get('fontName', self._canvas._font) - fontsize = delta.get('fontSize', self._canvas._fontSize) - self._canvas.setFont(fontname, fontsize) - - - - -def test0(outdir='svgout'): - # print all drawings and their doc strings from the test - # file - if not os.path.isdir(outdir): - os.mkdir(outdir) - #grab all drawings from the test module - from reportlab.graphics import testshapes - drawings = [] - - for funcname in dir(testshapes): - #if funcname[0:11] == 'getDrawing2': - # print 'hacked to only show drawing 2' - if funcname[0:10] == 'getDrawing': - drawing = eval('testshapes.' + funcname + '()') - docstring = eval('testshapes.' + funcname + '.__doc__') - drawings.append((drawing, docstring)) - - # return - - i = 0 - for (d, docstring) in drawings: - filename = outdir + os.sep + 'renderSVG_%d.svg' % i - drawToFile(d, filename) - # print 'saved', filename - i = i + 1 - - -def test1(): - from reportlab.graphics.testshapes import getDrawing01 - d = getDrawing01() - drawToFile(d, "svgout/test.svg") - - -def test2(): - from reportlab.lib.corp import RL_CorpLogo - from reportlab.graphics.shapes import Drawing - - rl = RL_CorpLogo() - d = Drawing(rl.width,rl.height) - d.add(rl) - drawToFile(d, "svgout/corplogo.svg") - - -if __name__=='__main__': - test0() - test1() - test2() diff --git a/bin/reportlab/graphics/renderbase.py b/bin/reportlab/graphics/renderbase.py deleted file mode 100644 index 51f73f4d2a5..00000000000 --- a/bin/reportlab/graphics/renderbase.py +++ /dev/null @@ -1,315 +0,0 @@ -#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/graphics/renderbase.py -""" -Superclass for renderers to factor out common functionality and default implementations. -""" - - -__version__=''' $Id $ ''' - -from reportlab.graphics.shapes import * -from reportlab import rl_config - -def inverse(A): - "For A affine 2D represented as 6vec return 6vec version of A**(-1)" - # I checked this RGB - det = float(A[0]*A[3] - A[2]*A[1]) - R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det] - return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]]) - -def mmult(A, B): - "A postmultiplied by B" - # I checked this RGB - # [a0 a2 a4] [b0 b2 b4] - # [a1 a3 a5] * [b1 b3 b5] - # [ 1 ] [ 1 ] - # - return (A[0]*B[0] + A[2]*B[1], - A[1]*B[0] + A[3]*B[1], - A[0]*B[2] + A[2]*B[3], - A[1]*B[2] + A[3]*B[3], - A[0]*B[4] + A[2]*B[5] + A[4], - A[1]*B[4] + A[3]*B[5] + A[5]) - - -def getStateDelta(shape): - """Used to compute when we need to change the graphics state. - For example, if we have two adjacent red shapes we don't need - to set the pen color to red in between. Returns the effect - the given shape would have on the graphics state""" - delta = {} - for (prop, value) in shape.getProperties().items(): - if STATE_DEFAULTS.has_key(prop): - delta[prop] = value - return delta - - -class StateTracker: - """Keeps a stack of transforms and state - properties. It can contain any properties you - want, but the keys 'transform' and 'ctm' have - special meanings. The getCTM() - method returns the current transformation - matrix at any point, without needing to - invert matrixes when you pop.""" - def __init__(self, defaults=None): - # one stack to keep track of what changes... - self.__deltas = [] - - # and another to keep track of cumulative effects. Last one in - # list is the current graphics state. We put one in to simplify - # loops below. - self.__combined = [] - if defaults is None: - defaults = STATE_DEFAULTS.copy() - #ensure that if we have a transform, we have a CTM - if defaults.has_key('transform'): - defaults['ctm'] = defaults['transform'] - self.__combined.append(defaults) - - def push(self,delta): - """Take a new state dictionary of changes and push it onto - the stack. After doing this, the combined state is accessible - through getState()""" - - newstate = self.__combined[-1].copy() - for (key, value) in delta.items(): - if key == 'transform': #do cumulative matrix - newstate['transform'] = delta['transform'] - newstate['ctm'] = mmult(self.__combined[-1]['ctm'], delta['transform']) - #print 'statetracker transform = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['transform']) - #print 'statetracker ctm = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['ctm']) - - else: #just overwrite it - newstate[key] = value - - self.__combined.append(newstate) - self.__deltas.append(delta) - - def pop(self): - """steps back one, and returns a state dictionary with the - deltas to reverse out of wherever you are. Depending - on your back end, you may not need the return value, - since you can get the complete state afterwards with getState()""" - del self.__combined[-1] - newState = self.__combined[-1] - lastDelta = self.__deltas[-1] - del self.__deltas[-1] - #need to diff this against the last one in the state - reverseDelta = {} - #print 'pop()...' - for key, curValue in lastDelta.items(): - #print ' key=%s, value=%s' % (key, curValue) - prevValue = newState[key] - if prevValue <> curValue: - #print ' state popping "%s"="%s"' % (key, curValue) - if key == 'transform': - reverseDelta[key] = inverse(lastDelta['transform']) - else: #just return to previous state - reverseDelta[key] = prevValue - return reverseDelta - - def getState(self): - "returns the complete graphics state at this point" - return self.__combined[-1] - - def getCTM(self): - "returns the current transformation matrix at this point""" - return self.__combined[-1]['ctm'] - - def __getitem__(self,key): - "returns the complete graphics state value of key at this point" - return self.__combined[-1][key] - - def __setitem__(self,key,value): - "sets the complete graphics state value of key to value" - self.__combined[-1][key] = value - -def testStateTracker(): - print 'Testing state tracker' - defaults = {'fillColor':None, 'strokeColor':None,'fontName':None, 'transform':[1,0,0,1,0,0]} - deltas = [ - {'fillColor':'red'}, - {'fillColor':'green', 'strokeColor':'blue','fontName':'Times-Roman'}, - {'transform':[0.5,0,0,0.5,0,0]}, - {'transform':[0.5,0,0,0.5,2,3]}, - {'strokeColor':'red'} - ] - - st = StateTracker(defaults) - print 'initial:', st.getState() - print - for delta in deltas: - print 'pushing:', delta - st.push(delta) - print 'state: ',st.getState(),'\n' - - for delta in deltas: - print 'popping:',st.pop() - print 'state: ',st.getState(),'\n' - - -def _expandUserNode(node,canvas): - if isinstance(node, UserNode): - try: - if hasattr(node,'_canvas'): - ocanvas = 1 - else: - node._canvas = canvas - ocanvas = None - onode = node - node = node.provideNode() - finally: - if not ocanvas: del onode._canvas - return node - -class Renderer: - """Virtual superclass for graphics renderers.""" - - def __init__(self): - self._tracker = StateTracker() - - def undefined(self, operation): - raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__) - - def draw(self, drawing, canvas, x=0, y=0, showBoundary=rl_config._unset_): - """This is the top level function, which draws the drawing at the given - location. The recursive part is handled by drawNode.""" - #stash references for ease of communication - if showBoundary is rl_config._unset_: showBoundary=rl_config.showBoundary - self._canvas = canvas - canvas.__dict__['_drawing'] = self._drawing = drawing - drawing._parent = None - try: - #bounding box - if showBoundary: canvas.rect(x, y, drawing.width, drawing.height) - canvas.saveState() - self.initState(x,y) - self.drawNode(drawing) - self.pop() - canvas.restoreState() - finally: - #remove any circular references - del self._canvas, self._drawing, canvas._drawing, drawing._parent - - def initState(self,x,y): - deltas = STATE_DEFAULTS.copy() - deltas['transform'] = [1,0,0,1,x,y] - self._tracker.push(deltas) - self.applyStateChanges(deltas, {}) - - def pop(self): - self._tracker.pop() - - def drawNode(self, node): - """This is the recursive method called for each node - in the tree""" - # Undefined here, but with closer analysis probably can be handled in superclass - self.undefined("drawNode") - - def drawNodeDispatcher(self, node): - """dispatch on the node's (super) class: shared code""" - - canvas = getattr(self,'_canvas',None) - # replace UserNode with its contents - - try: - node = _expandUserNode(node,canvas) - if hasattr(node,'_canvas'): - ocanvas = 1 - else: - node._canvas = canvas - ocanvas = None - - #draw the object, or recurse - if isinstance(node, Line): - self.drawLine(node) - elif isinstance(node, Image): - self.drawImage(node) - elif isinstance(node, Rect): - self.drawRect(node) - elif isinstance(node, Circle): - self.drawCircle(node) - elif isinstance(node, Ellipse): - self.drawEllipse(node) - elif isinstance(node, PolyLine): - self.drawPolyLine(node) - elif isinstance(node, Polygon): - self.drawPolygon(node) - elif isinstance(node, Path): - self.drawPath(node) - elif isinstance(node, String): - self.drawString(node) - elif isinstance(node, Group): - self.drawGroup(node) - elif isinstance(node, Wedge): - self.drawWedge(node) - else: - print 'DrawingError','Unexpected element %s in drawing!' % str(node) - finally: - if not ocanvas: del node._canvas - - _restores = {'stroke':'_stroke','stroke_width': '_lineWidth','stroke_linecap':'_lineCap', - 'stroke_linejoin':'_lineJoin','fill':'_fill','font_family':'_font', - 'font_size':'_fontSize'} - - def drawGroup(self, group): - # just do the contents. Some renderers might need to override this - # if they need a flipped transform - canvas = getattr(self,'_canvas',None) - for node in group.getContents(): - node = _expandUserNode(node,canvas) - try: - if hasattr(node,'_canvas'): - ocanvas = 1 - else: - node._canvas = canvas - ocanvas = None - node._parent = group - self.drawNode(node) - finally: - del node._parent - if not ocanvas: del node._canvas - - def drawWedge(self, wedge): - # by default ask the wedge to make a polygon of itself and draw that! - #print "drawWedge" - polygon = wedge.asPolygon() - self.drawPolygon(polygon) - - def drawPath(self, path): - polygons = path.asPolygons() - for polygon in polygons: - self.drawPolygon(polygon) - - def drawRect(self, rect): - # could be implemented in terms of polygon - self.undefined("drawRect") - - def drawLine(self, line): - self.undefined("drawLine") - - def drawCircle(self, circle): - self.undefined("drawCircle") - - def drawPolyLine(self, p): - self.undefined("drawPolyLine") - - def drawEllipse(self, ellipse): - self.undefined("drawEllipse") - - def drawPolygon(self, p): - self.undefined("drawPolygon") - - def drawString(self, stringObj): - self.undefined("drawString") - - def applyStateChanges(self, delta, newState): - """This takes a set of states, and outputs the operators - needed to set those properties""" - self.undefined("applyStateChanges") - -if __name__=='__main__': - print "this file has no script interpretation" - print __doc__ diff --git a/bin/reportlab/graphics/samples/__init__.py b/bin/reportlab/graphics/samples/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bin/reportlab/graphics/samples/bubble.py b/bin/reportlab/graphics/samples/bubble.py deleted file mode 100644 index 64bf3137912..00000000000 --- a/bin/reportlab/graphics/samples/bubble.py +++ /dev/null @@ -1,73 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.lineplots import ScatterPlot -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class Bubble(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.lines[0].strokeColor = color01 - self.chart.lines[1].strokeColor = color02 - self.chart.lines[2].strokeColor = color03 - self.chart.lines[3].strokeColor = color04 - self.chart.lines[4].strokeColor = color05 - self.chart.lines[5].strokeColor = color06 - self.chart.lines[6].strokeColor = color07 - self.chart.lines[7].strokeColor = color08 - self.chart.lines[8].strokeColor = color09 - self.chart.lines[9].strokeColor = color10 - self.chart.lines.symbol.kind ='Circle' - self.chart.lines.symbol.size = 15 - self.chart.fillColor = backgroundGrey - self.chart.lineLabels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontSize = 7 - self.chart.xValueAxis.forceZero = 0 - self.chart.data = [((100,100), (200,200), (250,210), (300,300), (350,450))] - self.chart.xValueAxis.avoidBoundFrac = 1 - self.chart.xValueAxis.gridEnd = 115 - self.chart.xValueAxis.tickDown = 3 - self.chart.xValueAxis.visibleGrid = 1 - self.chart.yValueAxis.tickLeft = 3 - self.chart.yValueAxis.labels.fontName = 'Helvetica' - self.chart.yValueAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.chart.lineLabelFormat = None - self.chart.xLabel = 'X Axis' - self.chart.y = 30 - self.chart.yLabel = 'Y Axis' - self.chart.yValueAxis.labelTextFormat = '%d' - self.chart.yValueAxis.forceZero = 1 - self.chart.xValueAxis.forceZero = 1 - - - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - Bubble().save(formats=['pdf'],outDir=None,fnRoot='bubble') diff --git a/bin/reportlab/graphics/samples/clustered_bar.py b/bin/reportlab/graphics/samples/clustered_bar.py deleted file mode 100644 index 4d8c363eb29..00000000000 --- a/bin/reportlab/graphics/samples/clustered_bar.py +++ /dev/null @@ -1,84 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from excelcolors import * -from reportlab.graphics.charts.barcharts import HorizontalBarChart -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label - -class ClusteredBar(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,HorizontalBarChart(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.bars[0].fillColor = color01 - self.chart.bars[1].fillColor = color02 - self.chart.bars[2].fillColor = color03 - self.chart.bars[3].fillColor = color04 - self.chart.bars[4].fillColor = color05 - self.chart.bars[5].fillColor = color06 - self.chart.bars[6].fillColor = color07 - self.chart.bars[7].fillColor = color08 - self.chart.bars[8].fillColor = color09 - self.chart.bars[9].fillColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.barLabels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontSize = 6 - self.chart.valueAxis.forceZero = 1 - self.chart.data = [(100, 150, 180), (125, 180, 200)] - self.chart.groupSpacing = 15 - self.chart.valueAxis.avoidBoundFrac = 1 - self.chart.valueAxis.gridEnd = 80 - self.chart.valueAxis.tickDown = 3 - self.chart.valueAxis.visibleGrid = 1 - self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central'] - self.chart.categoryAxis.tickLeft = 3 - self.chart.categoryAxis.labels.fontName = 'Helvetica' - self.chart.categoryAxis.labels.fontSize = 6 - self.chart.categoryAxis.labels.dx = -3 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis") - self.XLabel.fontName = 'Helvetica' - self.XLabel.fontSize = 7 - self.XLabel.x = 85 - self.XLabel.y = 10 - self.XLabel.textAnchor ='middle' - self.XLabel.maxWidth = 100 - self.XLabel.height = 20 - self.XLabel._text = "X Axis" - self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis") - self.YLabel.fontName = 'Helvetica' - self.YLabel.fontSize = 7 - self.YLabel.x = 12 - self.YLabel.y = 80 - self.YLabel.angle = 90 - self.YLabel.textAnchor ='middle' - self.YLabel.maxWidth = 100 - self.YLabel.height = 20 - self.YLabel._text = "Y Axis" - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - ClusteredBar().save(formats=['pdf'],outDir=None,fnRoot='clustered_bar') \ No newline at end of file diff --git a/bin/reportlab/graphics/samples/clustered_column.py b/bin/reportlab/graphics/samples/clustered_column.py deleted file mode 100644 index 8ea9542eadc..00000000000 --- a/bin/reportlab/graphics/samples/clustered_column.py +++ /dev/null @@ -1,83 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from excelcolors import * -from reportlab.graphics.charts.barcharts import VerticalBarChart -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label - -class ClusteredColumn(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,VerticalBarChart(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.bars[0].fillColor = color01 - self.chart.bars[1].fillColor = color02 - self.chart.bars[2].fillColor = color03 - self.chart.bars[3].fillColor = color04 - self.chart.bars[4].fillColor = color05 - self.chart.bars[5].fillColor = color06 - self.chart.bars[6].fillColor = color07 - self.chart.bars[7].fillColor = color08 - self.chart.bars[8].fillColor = color09 - self.chart.bars[9].fillColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.barLabels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontSize = 7 - self.chart.valueAxis.forceZero = 1 - self.chart.data = [(100, 150, 180), (125, 180, 200)] - self.chart.groupSpacing = 15 - self.chart.valueAxis.avoidBoundFrac = 1 - self.chart.valueAxis.gridEnd = 115 - self.chart.valueAxis.tickLeft = 3 - self.chart.valueAxis.visibleGrid = 1 - self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central'] - self.chart.categoryAxis.tickDown = 3 - self.chart.categoryAxis.labels.fontName = 'Helvetica' - self.chart.categoryAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis") - self.XLabel.fontName = 'Helvetica' - self.XLabel.fontSize = 7 - self.XLabel.x = 85 - self.XLabel.y = 10 - self.XLabel.textAnchor ='middle' - self.XLabel.maxWidth = 100 - self.XLabel.height = 20 - self.XLabel._text = "X Axis" - self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis") - self.YLabel.fontName = 'Helvetica' - self.YLabel.fontSize = 7 - self.YLabel.x = 12 - self.YLabel.y = 80 - self.YLabel.angle = 90 - self.YLabel.textAnchor ='middle' - self.YLabel.maxWidth = 100 - self.YLabel.height = 20 - self.YLabel._text = "Y Axis" - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - ClusteredColumn().save(formats=['pdf'],outDir=None,fnRoot='clustered_column') diff --git a/bin/reportlab/graphics/samples/excelcolors.py b/bin/reportlab/graphics/samples/excelcolors.py deleted file mode 100644 index f3f4f5b6950..00000000000 --- a/bin/reportlab/graphics/samples/excelcolors.py +++ /dev/null @@ -1,45 +0,0 @@ -# define standard colors to mimic those used by Microsoft Excel -from reportlab.lib.colors import CMYKColor, PCMYKColor - -#colour names as comments at the end of each line are as a memory jogger ONLY -#NOT HTML named colours! - -#Main colours as used for bars etc -color01 = PCMYKColor(40,40,0,0) # Lavender -color02 = PCMYKColor(0,66,33,39) # Maroon -color03 = PCMYKColor(0,0,20,0) # Yellow -color04 = PCMYKColor(20,0,0,0) # Cyan -color05 = PCMYKColor(0,100,0,59) # Purple -color06 = PCMYKColor(0,49,49,0) # Salmon -color07 = PCMYKColor(100,49,0,19) # Blue -color08 = PCMYKColor(20,20,0,0) # PaleLavender -color09 = PCMYKColor(100,100,0,49) # NavyBlue -color10 = PCMYKColor(0,100,0,0) # Purple - -#Highlight colors - eg for the tops of bars -color01Light = PCMYKColor(39,39,0,25) # Light Lavender -color02Light = PCMYKColor(0,66,33,54) # Light Maroon -color03Light = PCMYKColor(0,0,19,25) # Light Yellow -color04Light = PCMYKColor(19,0,0,25) # Light Cyan -color05Light = PCMYKColor(0,100,0,69) # Light Purple -color06Light = PCMYKColor(0,49,49,25) # Light Salmon -color07Light = PCMYKColor(100,49,0,39) # Light Blue -color08Light = PCMYKColor(19,19,0,25) # Light PaleLavender -color09Light = PCMYKColor(100,100,0,62) # Light NavyBlue -color10Light = PCMYKColor(0,100,0,25) # Light Purple - -#Lowlight colors - eg for the sides of bars -color01Dark = PCMYKColor(39,39,0,49) # Dark Lavender -color02Dark = PCMYKColor(0,66,33,69) # Dark Maroon -color03Dark = PCMYKColor(0,0,20,49) # Dark Yellow -color04Dark = PCMYKColor(20,0,0,49) # Dark Cyan -color05Dark = PCMYKColor(0,100,0,80) # Dark Purple -color06Dark = PCMYKColor(0,50,50,49) # Dark Salmon -color07Dark = PCMYKColor(100,50,0,59) # Dark Blue -color08Dark = PCMYKColor(20,20,0,49) # Dark PaleLavender -color09Dark = PCMYKColor(100,100,0,79) # Dark NavyBlue -color10Dark = PCMYKColor(0,100,0,49) # Dark Purple - -#for standard grey backgrounds -backgroundGrey = PCMYKColor(0,0,0,24) - diff --git a/bin/reportlab/graphics/samples/exploded_pie.py b/bin/reportlab/graphics/samples/exploded_pie.py deleted file mode 100644 index 8076493bd82..00000000000 --- a/bin/reportlab/graphics/samples/exploded_pie.py +++ /dev/null @@ -1,65 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.piecharts import Pie -from excelcolors import * -from reportlab.graphics.widgets.grids import ShadedRect -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label - -class ExplodedPie(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,Pie(),name='chart',validate=None,desc="The main chart") - self.chart.width = 100 - self.chart.height = 100 - self.chart.x = 25 - self.chart.y = 25 - self.chart.slices[0].fillColor = color01 - self.chart.slices[1].fillColor = color02 - self.chart.slices[2].fillColor = color03 - self.chart.slices[3].fillColor = color04 - self.chart.slices[4].fillColor = color05 - self.chart.slices[5].fillColor = color06 - self.chart.slices[6].fillColor = color07 - self.chart.slices[7].fillColor = color08 - self.chart.slices[8].fillColor = color09 - self.chart.slices[9].fillColor = color10 - self.chart.data = (100, 150, 180) - self.chart.startAngle = -90 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'North'), (color02, 'South'), (color03, 'Central')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 160 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.Legend.columnMaximum = 10 - self.chart.slices.strokeWidth = 1 - self.chart.slices.fontName = 'Helvetica' - self.background = ShadedRect() - self.background.fillColorStart = backgroundGrey - self.background.fillColorEnd = backgroundGrey - self.background.numShades = 1 - self.background.strokeWidth = 0.5 - self.background.x = 20 - self.background.y = 20 - self.chart.slices.popout = 5 - self.background.height = 110 - self.background.width = 110 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - ExplodedPie().save(formats=['pdf'],outDir=None,fnRoot='exploded_pie') \ No newline at end of file diff --git a/bin/reportlab/graphics/samples/filled_radar.py b/bin/reportlab/graphics/samples/filled_radar.py deleted file mode 100644 index ba66662a239..00000000000 --- a/bin/reportlab/graphics/samples/filled_radar.py +++ /dev/null @@ -1,54 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.spider import SpiderChart -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class FilledRadarChart(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,SpiderChart(),name='chart',validate=None,desc="The main chart") - self.chart.width = 90 - self.chart.height = 90 - self.chart.x = 45 - self.chart.y = 25 - self.chart.strands[0].fillColor = color01 - self.chart.strands[1].fillColor = color02 - self.chart.strands[2].fillColor = color03 - self.chart.strands[3].fillColor = color04 - self.chart.strands[4].fillColor = color05 - self.chart.strands[5].fillColor = color06 - self.chart.strands[6].fillColor = color07 - self.chart.strands[7].fillColor = color08 - self.chart.strands[8].fillColor = color09 - self.chart.strands[9].fillColor = color10 - self.chart.strands.fontName = 'Helvetica' - self.chart.strands.fontSize = 6 - self.chart.fillColor = backgroundGrey - self.chart.data = [(125, 180, 200), (100, 150, 180)] - self.chart.labels = ['North', 'South', 'Central'] - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - FilledRadarChart().save(formats=['pdf'],outDir=None,fnRoot='filled_radar') \ No newline at end of file diff --git a/bin/reportlab/graphics/samples/line_chart.py b/bin/reportlab/graphics/samples/line_chart.py deleted file mode 100644 index 49563a94766..00000000000 --- a/bin/reportlab/graphics/samples/line_chart.py +++ /dev/null @@ -1,83 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.lineplots import LinePlot -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class LineChart(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,LinePlot(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.lines[0].strokeColor = color01 - self.chart.lines[1].strokeColor = color02 - self.chart.lines[2].strokeColor = color03 - self.chart.lines[3].strokeColor = color04 - self.chart.lines[4].strokeColor = color05 - self.chart.lines[5].strokeColor = color06 - self.chart.lines[6].strokeColor = color07 - self.chart.lines[7].strokeColor = color08 - self.chart.lines[8].strokeColor = color09 - self.chart.lines[9].strokeColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.lineLabels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontSize = 7 - self.chart.xValueAxis.forceZero = 0 - self.chart.data = [((0, 50), (100,100), (200,200), (250,210), (300,300), (400,500)), ((0, 150), (100,200), (200,300), (250,200), (300,400), (400, 600))] - self.chart.xValueAxis.avoidBoundFrac = 1 - self.chart.xValueAxis.gridEnd = 115 - self.chart.xValueAxis.tickDown = 3 - self.chart.xValueAxis.visibleGrid = 1 - self.chart.yValueAxis.tickLeft = 3 - self.chart.yValueAxis.labels.fontName = 'Helvetica' - self.chart.yValueAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis") - self.XLabel.fontName = 'Helvetica' - self.XLabel.fontSize = 7 - self.XLabel.x = 85 - self.XLabel.y = 10 - self.XLabel.textAnchor ='middle' - self.XLabel.maxWidth = 100 - self.XLabel.height = 20 - self.XLabel._text = "X Axis" - self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis") - self.YLabel.fontName = 'Helvetica' - self.YLabel.fontSize = 7 - self.YLabel.x = 12 - self.YLabel.y = 80 - self.YLabel.angle = 90 - self.YLabel.textAnchor ='middle' - self.YLabel.maxWidth = 100 - self.YLabel.height = 20 - self.YLabel._text = "Y Axis" - self.chart.yValueAxis.forceZero = 1 - self.chart.xValueAxis.forceZero = 1 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - LineChart().save(formats=['pdf'],outDir=None,fnRoot='line_chart') diff --git a/bin/reportlab/graphics/samples/linechart_with_markers.py b/bin/reportlab/graphics/samples/linechart_with_markers.py deleted file mode 100644 index 2875cecb5d7..00000000000 --- a/bin/reportlab/graphics/samples/linechart_with_markers.py +++ /dev/null @@ -1,94 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.lineplots import LinePlot -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.widgets.markers import makeMarker -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class LineChartWithMarkers(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,LinePlot(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.lines[0].strokeColor = color01 - self.chart.lines[1].strokeColor = color02 - self.chart.lines[2].strokeColor = color03 - self.chart.lines[3].strokeColor = color04 - self.chart.lines[4].strokeColor = color05 - self.chart.lines[5].strokeColor = color06 - self.chart.lines[6].strokeColor = color07 - self.chart.lines[7].strokeColor = color08 - self.chart.lines[8].strokeColor = color09 - self.chart.lines[9].strokeColor = color10 - self.chart.lines[0].symbol = makeMarker('FilledSquare') - self.chart.lines[1].symbol = makeMarker('FilledDiamond') - self.chart.lines[2].symbol = makeMarker('FilledStarFive') - self.chart.lines[3].symbol = makeMarker('FilledTriangle') - self.chart.lines[4].symbol = makeMarker('FilledCircle') - self.chart.lines[5].symbol = makeMarker('FilledPentagon') - self.chart.lines[6].symbol = makeMarker('FilledStarSix') - self.chart.lines[7].symbol = makeMarker('FilledHeptagon') - self.chart.lines[8].symbol = makeMarker('FilledOctagon') - self.chart.lines[9].symbol = makeMarker('FilledCross') - self.chart.fillColor = backgroundGrey - self.chart.lineLabels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontSize = 7 - self.chart.xValueAxis.forceZero = 0 - self.chart.data = [((0, 50), (100,100), (200,200), (250,210), (300,300), (400,500)), ((0, 150), (100,200), (200,300), (250,200), (300,400), (400, 600))] - self.chart.xValueAxis.avoidBoundFrac = 1 - self.chart.xValueAxis.gridEnd = 115 - self.chart.xValueAxis.tickDown = 3 - self.chart.xValueAxis.visibleGrid = 1 - self.chart.yValueAxis.tickLeft = 3 - self.chart.yValueAxis.labels.fontName = 'Helvetica' - self.chart.yValueAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis") - self.XLabel.fontName = 'Helvetica' - self.XLabel.fontSize = 7 - self.XLabel.x = 85 - self.XLabel.y = 10 - self.XLabel.textAnchor ='middle' - self.XLabel.maxWidth = 100 - self.XLabel.height = 20 - self.XLabel._text = "X Axis" - self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis") - self.YLabel.fontName = 'Helvetica' - self.YLabel.fontSize = 7 - self.YLabel.x = 12 - self.YLabel.y = 80 - self.YLabel.angle = 90 - self.YLabel.textAnchor ='middle' - self.YLabel.maxWidth = 100 - self.YLabel.height = 20 - self.YLabel._text = "Y Axis" - self.chart.yValueAxis.forceZero = 1 - self.chart.xValueAxis.forceZero = 1 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - LineChartWithMarkers().save(formats=['pdf'],outDir=None,fnRoot='linechart_with_markers') diff --git a/bin/reportlab/graphics/samples/radar.py b/bin/reportlab/graphics/samples/radar.py deleted file mode 100644 index 2d61d091ab9..00000000000 --- a/bin/reportlab/graphics/samples/radar.py +++ /dev/null @@ -1,66 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from excelcolors import * -from reportlab.graphics.charts.spider import SpiderChart -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label - -class RadarChart(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,SpiderChart(),name='chart',validate=None,desc="The main chart") - self.chart.width = 90 - self.chart.height = 90 - self.chart.x = 45 - self.chart.y = 25 - self.chart.strands[0].strokeColor= color01 - self.chart.strands[1].strokeColor= color02 - self.chart.strands[2].strokeColor= color03 - self.chart.strands[3].strokeColor= color04 - self.chart.strands[4].strokeColor= color05 - self.chart.strands[5].strokeColor= color06 - self.chart.strands[6].strokeColor= color07 - self.chart.strands[7].strokeColor= color08 - self.chart.strands[8].strokeColor= color09 - self.chart.strands[9].strokeColor= color10 - self.chart.strands[0].fillColor = None - self.chart.strands[1].fillColor = None - self.chart.strands[2].fillColor = None - self.chart.strands[3].fillColor = None - self.chart.strands[4].fillColor = None - self.chart.strands[5].fillColor = None - self.chart.strands[6].fillColor = None - self.chart.strands[7].fillColor = None - self.chart.strands[8].fillColor = None - self.chart.strands[9].fillColor = None - self.chart.strands.strokeWidth = 1 - self.chart.strands.fontName = 'Helvetica' - self.chart.strands.fontSize = 6 - self.chart.fillColor = backgroundGrey - self.chart.data = [(125, 180, 200), (100, 150, 180)] - self.chart.labels = ['North', 'South', 'Central'] - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.chart.strands.strokeWidth = 1 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - RadarChart().save(formats=['pdf'],outDir=None,fnRoot='radar') \ No newline at end of file diff --git a/bin/reportlab/graphics/samples/runall.py b/bin/reportlab/graphics/samples/runall.py deleted file mode 100644 index 938ab8fec42..00000000000 --- a/bin/reportlab/graphics/samples/runall.py +++ /dev/null @@ -1,59 +0,0 @@ -# runs all the GUIedit charts in this directory - -# makes a PDF sample for eaxh existing chart type -import sys -import glob -import string -import inspect -import types - -def moduleClasses(mod): - def P(obj, m=mod.__name__, CT=types.ClassType): - return (type(obj)==CT and obj.__module__==m) - try: - return inspect.getmembers(mod, P)[0][1] - except: - return None - -def getclass(f): - return moduleClasses(__import__(f)) - -def run(format, VERBOSE=0): - formats = string.split(format, ',') - for i in range(0, len(formats)): - formats[i] == string.lower(string.strip(formats[i])) - allfiles = glob.glob('*.py') - allfiles.sort() - for fn in allfiles: - f = string.split(fn, '.')[0] - c = getclass(f) - if c != None: - print c.__name__ - try: - for fmt in formats: - if fmt: - c().save(formats=[fmt],outDir='.',fnRoot=c.__name__) - if VERBOSE: - print " %s.%s" % (c.__name__, fmt) - except: - print " COULDN'T CREATE '%s.%s'!" % (c.__name__, format) - -if __name__ == "__main__": - if len(sys.argv) == 1: - run('pdf,pict,png') - else: - try: - if sys.argv[1] == "-h": - print 'usage: runall.py [FORMAT] [-h]' - print ' if format is supplied is should be one or more of pdf,gif,eps,png etc' - print ' if format is missing the following formats are assumed: pdf,pict,png' - print ' -h prints this message' - else: - t = sys.argv[1:] - for f in t: - run(f) - except: - print 'usage: runall.py [FORMAT][-h]' - print ' if format is supplied is should be one or more of pdf,gif,eps,png etc' - print ' if format is missing the following formats are assumed: pdf,pict,png' - print ' -h prints this message' - raise diff --git a/bin/reportlab/graphics/samples/scatter.py b/bin/reportlab/graphics/samples/scatter.py deleted file mode 100644 index ea1a9991d3e..00000000000 --- a/bin/reportlab/graphics/samples/scatter.py +++ /dev/null @@ -1,71 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.lineplots import ScatterPlot -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class Scatter(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.lines[0].strokeColor = color01 - self.chart.lines[1].strokeColor = color02 - self.chart.lines[2].strokeColor = color03 - self.chart.lines[3].strokeColor = color04 - self.chart.lines[4].strokeColor = color05 - self.chart.lines[5].strokeColor = color06 - self.chart.lines[6].strokeColor = color07 - self.chart.lines[7].strokeColor = color08 - self.chart.lines[8].strokeColor = color09 - self.chart.lines[9].strokeColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.lineLabels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontSize = 7 - self.chart.xValueAxis.forceZero = 0 - self.chart.data = [((100,100), (200,200), (250,210), (300,300), (400,500)), ((100,200), (200,300), (250,200), (300,400), (400, 600))] - self.chart.xValueAxis.avoidBoundFrac = 1 - self.chart.xValueAxis.gridEnd = 115 - self.chart.xValueAxis.tickDown = 3 - self.chart.xValueAxis.visibleGrid = 1 - self.chart.yValueAxis.tickLeft = 3 - self.chart.yValueAxis.labels.fontName = 'Helvetica' - self.chart.yValueAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.chart.lineLabelFormat = None - self.chart.xLabel = 'X Axis' - self.chart.y = 30 - self.chart.yLabel = 'Y Axis' - self.chart.yValueAxis.labelTextFormat = '%d' - self.chart.yValueAxis.forceZero = 1 - self.chart.xValueAxis.forceZero = 1 - - - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - Scatter().save(formats=['pdf'],outDir=None,fnRoot='scatter') diff --git a/bin/reportlab/graphics/samples/scatter_lines.py b/bin/reportlab/graphics/samples/scatter_lines.py deleted file mode 100644 index 16ed718b2fd..00000000000 --- a/bin/reportlab/graphics/samples/scatter_lines.py +++ /dev/null @@ -1,82 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.lineplots import ScatterPlot -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class ScatterLines(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.lines[0].strokeColor = color01 - self.chart.lines[1].strokeColor = color02 - self.chart.lines[2].strokeColor = color03 - self.chart.lines[3].strokeColor = color04 - self.chart.lines[4].strokeColor = color05 - self.chart.lines[5].strokeColor = color06 - self.chart.lines[6].strokeColor = color07 - self.chart.lines[7].strokeColor = color08 - self.chart.lines[8].strokeColor = color09 - self.chart.lines[9].strokeColor = color10 - self.chart.lines[0].symbol = None - self.chart.lines[1].symbol = None - self.chart.lines[2].symbol = None - self.chart.lines[3].symbol = None - self.chart.lines[4].symbol = None - self.chart.lines[5].symbol = None - self.chart.lines[6].symbol = None - self.chart.lines[7].symbol = None - self.chart.lines[8].symbol = None - self.chart.lines[9].symbol = None - self.chart.fillColor = backgroundGrey - self.chart.lineLabels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontSize = 7 - self.chart.xValueAxis.forceZero = 0 - self.chart.data = [((100,100), (200,200), (250,210), (300,300), (400,500)), ((100,200), (200,300), (250,200), (300,400), (400, 600))] - self.chart.xValueAxis.avoidBoundFrac = 1 - self.chart.xValueAxis.gridEnd = 115 - self.chart.xValueAxis.tickDown = 3 - self.chart.xValueAxis.visibleGrid = 1 - self.chart.yValueAxis.tickLeft = 3 - self.chart.yValueAxis.labels.fontName = 'Helvetica' - self.chart.yValueAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.chart.lineLabelFormat = None - self.chart.xLabel = 'X Axis' - self.chart.y = 30 - self.chart.yLabel = 'Y Axis' - self.chart.yValueAxis.gridEnd = 115 - self.chart.yValueAxis.visibleGrid = 1 - self.chart.yValueAxis.labelTextFormat = '%d' - self.chart.yValueAxis.forceZero = 1 - self.chart.xValueAxis.forceZero = 1 - self.chart.joinedLines = 1 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - ScatterLines().save(formats=['pdf'],outDir=None,fnRoot='scatter_lines') diff --git a/bin/reportlab/graphics/samples/scatter_lines_markers.py b/bin/reportlab/graphics/samples/scatter_lines_markers.py deleted file mode 100644 index 34f8ff220f5..00000000000 --- a/bin/reportlab/graphics/samples/scatter_lines_markers.py +++ /dev/null @@ -1,72 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.lineplots import ScatterPlot -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class ScatterLinesMarkers(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,ScatterPlot(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.lines[0].strokeColor = color01 - self.chart.lines[1].strokeColor = color02 - self.chart.lines[2].strokeColor = color03 - self.chart.lines[3].strokeColor = color04 - self.chart.lines[4].strokeColor = color05 - self.chart.lines[5].strokeColor = color06 - self.chart.lines[6].strokeColor = color07 - self.chart.lines[7].strokeColor = color08 - self.chart.lines[8].strokeColor = color09 - self.chart.lines[9].strokeColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.lineLabels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontName = 'Helvetica' - self.chart.xValueAxis.labels.fontSize = 7 - self.chart.xValueAxis.forceZero = 0 - self.chart.data = [((100,100), (200,200), (250,210), (300,300), (400,500)), ((100,200), (200,300), (250,200), (300,400), (400, 600))] - self.chart.xValueAxis.avoidBoundFrac = 1 - self.chart.xValueAxis.gridEnd = 115 - self.chart.xValueAxis.tickDown = 3 - self.chart.xValueAxis.visibleGrid = 1 - self.chart.yValueAxis.tickLeft = 3 - self.chart.yValueAxis.labels.fontName = 'Helvetica' - self.chart.yValueAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.chart.lineLabelFormat = None - self.chart.xLabel = 'X Axis' - self.chart.y = 30 - self.chart.yLabel = 'Y Axis' - self.chart.yValueAxis.gridEnd = 115 - self.chart.yValueAxis.visibleGrid = 1 - self.chart.yValueAxis.labelTextFormat = '%d' - self.chart.yValueAxis.forceZero = 1 - self.chart.xValueAxis.forceZero = 1 - self.chart.joinedLines = 1 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - ScatterLinesMarkers().save(formats=['pdf'],outDir=None,fnRoot='scatter_lines_markers') diff --git a/bin/reportlab/graphics/samples/simple_pie.py b/bin/reportlab/graphics/samples/simple_pie.py deleted file mode 100644 index 7542607f6ae..00000000000 --- a/bin/reportlab/graphics/samples/simple_pie.py +++ /dev/null @@ -1,61 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.piecharts import Pie -from reportlab.graphics.widgets.grids import ShadedRect -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class SimplePie(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,Pie(),name='chart',validate=None,desc="The main chart") - self.chart.width = 100 - self.chart.height = 100 - self.chart.x = 25 - self.chart.y = 25 - self.chart.slices[0].fillColor = color01 - self.chart.slices[1].fillColor = color02 - self.chart.slices[2].fillColor = color03 - self.chart.slices[3].fillColor = color04 - self.chart.slices[4].fillColor = color05 - self.chart.slices[5].fillColor = color06 - self.chart.slices[6].fillColor = color07 - self.chart.slices[7].fillColor = color08 - self.chart.slices[8].fillColor = color09 - self.chart.slices[9].fillColor = color10 - self.chart.data = (100, 150, 180) - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'North'), (color02, 'South'),(color03, 'Central')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 160 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self.chart.slices.strokeWidth = 1 - self.chart.slices.fontName = 'Helvetica' - self.background = ShadedRect() - self.background.fillColorStart = backgroundGrey - self.background.fillColorEnd = backgroundGrey - self.background.numShades = 1 - self.background.strokeWidth = 0.5 - self.background.x = 25 - self.background.y = 25 - self.Legend.columnMaximum = 10 - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - SimplePie().save(formats=['pdf'],outDir=None,fnRoot=None) \ No newline at end of file diff --git a/bin/reportlab/graphics/samples/stacked_bar.py b/bin/reportlab/graphics/samples/stacked_bar.py deleted file mode 100644 index 9ba2a962f82..00000000000 --- a/bin/reportlab/graphics/samples/stacked_bar.py +++ /dev/null @@ -1,85 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.barcharts import HorizontalBarChart -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class StackedBar(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,HorizontalBarChart(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.bars[0].fillColor = color01 - self.chart.bars[1].fillColor = color02 - self.chart.bars[2].fillColor = color03 - self.chart.bars[3].fillColor = color04 - self.chart.bars[4].fillColor = color05 - self.chart.bars[5].fillColor = color06 - self.chart.bars[6].fillColor = color07 - self.chart.bars[7].fillColor = color08 - self.chart.bars[8].fillColor = color09 - self.chart.bars[9].fillColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.barLabels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontSize = 6 - self.chart.valueAxis.forceZero = 1 - self.chart.data = [(100, 150, 180), (125, 180, 200)] - self.chart.groupSpacing = 15 - self.chart.valueAxis.avoidBoundFrac = 1 - self.chart.valueAxis.gridEnd = 80 - self.chart.valueAxis.tickDown = 3 - self.chart.valueAxis.visibleGrid = 1 - self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central'] - self.chart.categoryAxis.tickLeft = 3 - self.chart.categoryAxis.labels.fontName = 'Helvetica' - self.chart.categoryAxis.labels.fontSize = 6 - self.chart.categoryAxis.labels.dx = -3 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis") - self.XLabel.fontName = 'Helvetica' - self.XLabel.fontSize = 7 - self.XLabel.x = 85 - self.XLabel.y = 10 - self.XLabel.textAnchor ='middle' - self.XLabel.maxWidth = 100 - self.XLabel.height = 20 - self.XLabel._text = "X Axis" - self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis") - self.YLabel.fontName = 'Helvetica' - self.YLabel.fontSize = 7 - self.YLabel.x = 12 - self.YLabel.y = 80 - self.YLabel.angle = 90 - self.YLabel.textAnchor ='middle' - self.YLabel.maxWidth = 100 - self.YLabel.height = 20 - self.YLabel._text = "Y Axis" - self.chart.categoryAxis.style='stacked' - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - StackedBar().save(formats=['pdf'],outDir=None,fnRoot='stacked_bar') \ No newline at end of file diff --git a/bin/reportlab/graphics/samples/stacked_column.py b/bin/reportlab/graphics/samples/stacked_column.py deleted file mode 100644 index a50a1598c90..00000000000 --- a/bin/reportlab/graphics/samples/stacked_column.py +++ /dev/null @@ -1,84 +0,0 @@ -#Autogenerated by ReportLab guiedit do not edit -from reportlab.graphics.charts.legends import Legend -from reportlab.graphics.charts.barcharts import VerticalBarChart -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin, String -from reportlab.graphics.charts.textlabels import Label -from excelcolors import * - -class StackedColumn(_DrawingEditorMixin,Drawing): - def __init__(self,width=200,height=150,*args,**kw): - apply(Drawing.__init__,(self,width,height)+args,kw) - self._add(self,VerticalBarChart(),name='chart',validate=None,desc="The main chart") - self.chart.width = 115 - self.chart.height = 80 - self.chart.x = 30 - self.chart.y = 40 - self.chart.bars[0].fillColor = color01 - self.chart.bars[1].fillColor = color02 - self.chart.bars[2].fillColor = color03 - self.chart.bars[3].fillColor = color04 - self.chart.bars[4].fillColor = color05 - self.chart.bars[5].fillColor = color06 - self.chart.bars[6].fillColor = color07 - self.chart.bars[7].fillColor = color08 - self.chart.bars[8].fillColor = color09 - self.chart.bars[9].fillColor = color10 - self.chart.fillColor = backgroundGrey - self.chart.barLabels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontName = 'Helvetica' - self.chart.valueAxis.labels.fontSize = 7 - self.chart.valueAxis.forceZero = 1 - self.chart.data = [(100, 150, 180), (125, 180, 200)] - self.chart.groupSpacing = 15 - self.chart.valueAxis.avoidBoundFrac = 1 - self.chart.valueAxis.gridEnd = 115 - self.chart.valueAxis.tickLeft = 3 - self.chart.valueAxis.visibleGrid = 1 - self.chart.categoryAxis.categoryNames = ['North', 'South', 'Central'] - self.chart.categoryAxis.tickDown = 3 - self.chart.categoryAxis.labels.fontName = 'Helvetica' - self.chart.categoryAxis.labels.fontSize = 7 - self._add(self,Label(),name='Title',validate=None,desc="The title at the top of the chart") - self.Title.fontName = 'Helvetica-Bold' - self.Title.fontSize = 7 - self.Title.x = 100 - self.Title.y = 135 - self.Title._text = 'Chart Title' - self.Title.maxWidth = 180 - self.Title.height = 20 - self.Title.textAnchor ='middle' - self._add(self,Legend(),name='Legend',validate=None,desc="The legend or key for the chart") - self.Legend.colorNamePairs = [(color01, 'Widgets'), (color02, 'Sprockets')] - self.Legend.fontName = 'Helvetica' - self.Legend.fontSize = 7 - self.Legend.x = 153 - self.Legend.y = 85 - self.Legend.dxTextSpace = 5 - self.Legend.dy = 5 - self.Legend.dx = 5 - self.Legend.deltay = 5 - self.Legend.alignment ='right' - self._add(self,Label(),name='XLabel',validate=None,desc="The label on the horizontal axis") - self.XLabel.fontName = 'Helvetica' - self.XLabel.fontSize = 7 - self.XLabel.x = 85 - self.XLabel.y = 10 - self.XLabel.textAnchor ='middle' - self.XLabel.maxWidth = 100 - self.XLabel.height = 20 - self.XLabel._text = "X Axis" - self._add(self,Label(),name='YLabel',validate=None,desc="The label on the vertical axis") - self.YLabel.fontName = 'Helvetica' - self.YLabel.fontSize = 7 - self.YLabel.x = 12 - self.YLabel.y = 80 - self.YLabel.angle = 90 - self.YLabel.textAnchor ='middle' - self.YLabel.maxWidth = 100 - self.YLabel.height = 20 - self.YLabel._text = "Y Axis" - self.chart.categoryAxis.style='stacked' - self._add(self,0,name='preview',validate=None,desc=None) - -if __name__=="__main__": #NORUNTESTS - StackedColumn().save(formats=['pdf'],outDir=None,fnRoot='stacked_column') diff --git a/bin/reportlab/graphics/shapes.py b/bin/reportlab/graphics/shapes.py deleted file mode 100644 index 6944cf84fb0..00000000000 --- a/bin/reportlab/graphics/shapes.py +++ /dev/null @@ -1,1244 +0,0 @@ -#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/graphics/shapes.py -""" -core of the graphics library - defines Drawing and Shapes -""" -__version__=''' $Id$ ''' - -import string, os, sys -from math import pi, cos, sin, tan -from types import FloatType, IntType, ListType, TupleType, StringType, InstanceType -from pprint import pprint - -from reportlab.platypus import Flowable -from reportlab.rl_config import shapeChecking, verbose -from reportlab.lib import logger -from reportlab.lib import colors -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * -from reportlab.lib.utils import fp_str -from reportlab.pdfbase.pdfmetrics import stringWidth - -class NotImplementedError(Exception): - pass - -# two constants for filling rules -NON_ZERO_WINDING = 'Non-Zero Winding' -EVEN_ODD = 'Even-Odd' - -## these can be overridden at module level before you start -#creating shapes. So, if using a special color model, -#this provides support for the rendering mechanism. -#you can change defaults globally before you start -#making shapes; one use is to substitute another -#color model cleanly throughout the drawing. - -STATE_DEFAULTS = { # sensible defaults for all - 'transform': (1,0,0,1,0,0), - - # styles follow SVG naming - 'strokeColor': colors.black, - 'strokeWidth': 1, - 'strokeLineCap': 0, - 'strokeLineJoin': 0, - 'strokeMiterLimit' : 'TBA', # don't know yet so let bomb here - 'strokeDashArray': None, - 'strokeOpacity': 1.0, #100% - - 'fillColor': colors.black, #...or text will be invisible - #'fillRule': NON_ZERO_WINDING, - these can be done later - #'fillOpacity': 1.0, #100% - can be done later - - 'fontSize': 10, - 'fontName': 'Times-Roman', - 'textAnchor': 'start' # can be start, middle, end, inherited - } - - -#################################################################### -# math utilities. These could probably be moved into lib -# somewhere. -#################################################################### - -# constructors for matrices: -def nullTransform(): - return (1, 0, 0, 1, 0, 0) - -def translate(dx, dy): - return (1, 0, 0, 1, dx, dy) - -def scale(sx, sy): - return (sx, 0, 0, sy, 0, 0) - -def rotate(angle): - a = angle * pi/180 - return (cos(a), sin(a), -sin(a), cos(a), 0, 0) - -def skewX(angle): - a = angle * pi/180 - return (1, 0, tan(a), 1, 0, 0) - -def skewY(angle): - a = angle * pi/180 - return (1, tan(a), 0, 1, 0, 0) - -def mmult(A, B): - "A postmultiplied by B" - # I checked this RGB - # [a0 a2 a4] [b0 b2 b4] - # [a1 a3 a5] * [b1 b3 b5] - # [ 1 ] [ 1 ] - # - return (A[0]*B[0] + A[2]*B[1], - A[1]*B[0] + A[3]*B[1], - A[0]*B[2] + A[2]*B[3], - A[1]*B[2] + A[3]*B[3], - A[0]*B[4] + A[2]*B[5] + A[4], - A[1]*B[4] + A[3]*B[5] + A[5]) - -def inverse(A): - "For A affine 2D represented as 6vec return 6vec version of A**(-1)" - # I checked this RGB - det = float(A[0]*A[3] - A[2]*A[1]) - R = [A[3]/det, -A[1]/det, -A[2]/det, A[0]/det] - return tuple(R+[-R[0]*A[4]-R[2]*A[5],-R[1]*A[4]-R[3]*A[5]]) - -def zTransformPoint(A,v): - "Apply the homogenous part of atransformation a to vector v --> A*v" - return (A[0]*v[0]+A[2]*v[1],A[1]*v[0]+A[3]*v[1]) - -def transformPoint(A,v): - "Apply transformation a to vector v --> A*v" - return (A[0]*v[0]+A[2]*v[1]+A[4],A[1]*v[0]+A[3]*v[1]+A[5]) - -def transformPoints(matrix, V): - return map(transformPoint, V) - -def zTransformPoints(matrix, V): - return map(lambda x,matrix=matrix: zTransformPoint(matrix,x), V) - -def _textBoxLimits(text, font, fontSize, leading, textAnchor, boxAnchor): - w = 0 - for t in text: - w = max(w,stringWidth(t,font, fontSize)) - - h = len(text)*leading - yt = fontSize - if boxAnchor[0]=='s': - yb = -h - yt = yt - h - elif boxAnchor[0]=='n': - yb = 0 - else: - yb = -h/2.0 - yt = yt + yb - - if boxAnchor[-1]=='e': - xb = -w - if textAnchor=='end': xt = 0 - elif textAnchor=='start': xt = -w - else: xt = -w/2.0 - elif boxAnchor[-1]=='w': - xb = 0 - if textAnchor=='end': xt = w - elif textAnchor=='start': xt = 0 - else: xt = w/2.0 - else: - xb = -w/2.0 - if textAnchor=='end': xt = -xb - elif textAnchor=='start': xt = xb - else: xt = 0 - - return xb, yb, w, h, xt, yt - -def _rotatedBoxLimits( x, y, w, h, angle): - ''' - Find the corner points of the rotated w x h sized box at x,y - return the corner points and the min max points in the original space - ''' - C = zTransformPoints(rotate(angle),((x,y),(x+w,y),(x+w,y+h),(x,y+h))) - X = map(lambda x: x[0], C) - Y = map(lambda x: x[1], C) - return min(X), max(X), min(Y), max(Y), C - - -class _DrawTimeResizeable: - '''Addin class to provide the horribleness of _drawTimeResize''' - def _drawTimeResize(self,w,h): - if hasattr(self,'_canvas'): - canvas = self._canvas - drawing = canvas._drawing - drawing.width, drawing.height = w, h - if hasattr(canvas,'_drawTimeResize'): - canvas._drawTimeResize(w,h) - -class _SetKeyWordArgs: - def __init__(self, keywords={}): - """In general properties may be supplied to the constructor.""" - for key, value in keywords.items(): - setattr(self, key, value) - - -################################################################# -# -# Helper functions for working out bounds -# -################################################################# - -def getRectsBounds(rectList): - # filter out any None objects, e.g. empty groups - L = filter(lambda x: x is not None, rectList) - if not L: return None - - xMin, yMin, xMax, yMax = L[0] - for (x1, y1, x2, y2) in L[1:]: - if x1 < xMin: - xMin = x1 - if x2 > xMax: - xMax = x2 - if y1 < yMin: - yMin = y1 - if y2 > yMax: - yMax = y2 - return (xMin, yMin, xMax, yMax) - -def getPathBounds(points): - n = len(points) - f = lambda i,p = points: p[i] - xs = map(f,xrange(0,n,2)) - ys = map(f,xrange(1,n,2)) - return (min(xs), min(ys), max(xs), max(ys)) - -def getPointsBounds(pointList): - "Helper function for list of points" - first = pointList[0] - if type(first) in (ListType, TupleType): - xs = map(lambda xy: xy[0],pointList) - ys = map(lambda xy: xy[1],pointList) - return (min(xs), min(ys), max(xs), max(ys)) - else: - return getPathBounds(pointList) - -################################################################# -# -# And now the shapes themselves.... -# -################################################################# -class Shape(_SetKeyWordArgs,_DrawTimeResizeable): - """Base class for all nodes in the tree. Nodes are simply - packets of data to be created, stored, and ultimately - rendered - they don't do anything active. They provide - convenience methods for verification but do not - check attribiute assignments or use any clever setattr - tricks this time.""" - _attrMap = AttrMap() - - def copy(self): - """Return a clone of this shape.""" - - # implement this in the descendants as they need the right init methods. - raise NotImplementedError, "No copy method implemented for %s" % self.__class__.__name__ - - def getProperties(self,recur=1): - """Interface to make it easy to extract automatic - documentation""" - - #basic nodes have no children so this is easy. - #for more complex objects like widgets you - #may need to override this. - props = {} - for key, value in self.__dict__.items(): - if key[0:1] <> '_': - props[key] = value - return props - - def setProperties(self, props): - """Supports the bulk setting if properties from, - for example, a GUI application or a config file.""" - - self.__dict__.update(props) - #self.verify() - - def dumpProperties(self, prefix=""): - """Convenience. Lists them on standard output. You - may provide a prefix - mostly helps to generate code - samples for documentation.""" - - propList = self.getProperties().items() - propList.sort() - if prefix: - prefix = prefix + '.' - for (name, value) in propList: - print '%s%s = %s' % (prefix, name, value) - - def verify(self): - """If the programmer has provided the optional - _attrMap attribute, this checks all expected - attributes are present; no unwanted attributes - are present; and (if a checking function is found) - checks each attribute. Either succeeds or raises - an informative exception.""" - - if self._attrMap is not None: - for key in self.__dict__.keys(): - if key[0] <> '_': - assert self._attrMap.has_key(key), "Unexpected attribute %s found in %s" % (key, self) - for (attr, metavalue) in self._attrMap.items(): - assert hasattr(self, attr), "Missing attribute %s from %s" % (attr, self) - value = getattr(self, attr) - assert metavalue.validate(value), "Invalid value %s for attribute %s in class %s" % (value, attr, self.__class__.__name__) - - if shapeChecking: - """This adds the ability to check every attribute assignment as it is made. - It slows down shapes but is a big help when developing. It does not - get defined if rl_config.shapeChecking = 0""" - def __setattr__(self, attr, value): - """By default we verify. This could be off - in some parallel base classes.""" - validateSetattr(self,attr,value) #from reportlab.lib.attrmap - - def getBounds(self): - "Returns bounding rectangle of object as (x1,y1,x2,y2)" - raise NotImplementedError("Shapes and widgets must implement getBounds") - -class Group(Shape): - """Groups elements together. May apply a transform - to its contents. Has a publicly accessible property - 'contents' which may be used to iterate over contents. - In addition, child nodes may be given a name in which - case they are subsequently accessible as properties.""" - - _attrMap = AttrMap( - transform = AttrMapValue(isTransform,desc="Coordinate transformation to apply"), - contents = AttrMapValue(isListOfShapes,desc="Contained drawable elements"), - ) - - def __init__(self, *elements, **keywords): - """Initial lists of elements may be provided to allow - compact definitions in literal Python code. May or - may not be useful.""" - - # Groups need _attrMap to be an instance rather than - # a class attribute, as it may be extended at run time. - self._attrMap = self._attrMap.clone() - self.contents = [] - self.transform = (1,0,0,1,0,0) - for elt in elements: - self.add(elt) - # this just applies keywords; do it at the end so they - #don;t get overwritten - _SetKeyWordArgs.__init__(self, keywords) - - def _addNamedNode(self,name,node): - 'if name is not None add an attribute pointing to node and add to the attrMap' - if name: - if name not in self._attrMap.keys(): - self._attrMap[name] = AttrMapValue(isValidChild) - setattr(self, name, node) - - def add(self, node, name=None): - """Appends non-None child node to the 'contents' attribute. In addition, - if a name is provided, it is subsequently accessible by name - """ - # propagates properties down - if node is not None: - assert isValidChild(node), "Can only add Shape or UserNode objects to a Group" - self.contents.append(node) - self._addNamedNode(name,node) - - def _nn(self,node): - self.add(node) - return self.contents[-1] - - def insert(self, i, n, name=None): - 'Inserts sub-node n in contents at specified location' - if n is not None: - assert isValidChild(n), "Can only insert Shape or UserNode objects in a Group" - if i<0: - self.contents[i:i] =[n] - else: - self.contents.insert(i,n) - self._addNamedNode(name,n) - - def expandUserNodes(self): - """Return a new object which only contains primitive shapes.""" - - # many limitations - shared nodes become multiple ones, - obj = isinstance(self,Drawing) and Drawing(self.width,self.height) or Group() - obj._attrMap = self._attrMap.clone() - if hasattr(obj,'transform'): obj.transform = self.transform[:] - - self_contents = self.contents - a = obj.contents.append - for child in self_contents: - if isinstance(child, UserNode): - newChild = child.provideNode() - elif isinstance(child, Group): - newChild = child.expandUserNodes() - else: - newChild = child.copy() - a(newChild) - - self._copyNamedContents(obj) - return obj - - def _explode(self): - ''' return a fully expanded object''' - from reportlab.graphics.widgetbase import Widget - obj = Group() - if hasattr(obj,'transform'): obj.transform = self.transform[:] - P = self.contents[:] # pending nodes - while P: - n = P.pop(0) - if isinstance(n, UserNode): - P.append(n.provideNode()) - elif isinstance(n, Group): - n = n._explode() - if n.transform==(1,0,0,1,0,0): - obj.contents.extend(n.contents) - else: - obj.add(n) - else: - obj.add(n) - return obj - - def _copyContents(self,obj): - for child in self.contents: - obj.contents.append(child) - - def _copyNamedContents(self,obj,aKeys=None,noCopy=('contents',)): - from copy import copy - self_contents = self.contents - if not aKeys: aKeys = self._attrMap.keys() - for (k, v) in self.__dict__.items(): - if v in self_contents: - pos = self_contents.index(v) - setattr(obj, k, obj.contents[pos]) - elif k in aKeys and k not in noCopy: - setattr(obj, k, copy(v)) - - def _copy(self,obj): - """copies to obj""" - obj._attrMap = self._attrMap.clone() - self._copyContents(obj) - self._copyNamedContents(obj) - return obj - - def copy(self): - """returns a copy""" - return self._copy(self.__class__()) - - def rotate(self, theta): - """Convenience to help you set transforms""" - self.transform = mmult(self.transform, rotate(theta)) - - def translate(self, dx, dy): - """Convenience to help you set transforms""" - self.transform = mmult(self.transform, translate(dx, dy)) - - def scale(self, sx, sy): - """Convenience to help you set transforms""" - self.transform = mmult(self.transform, scale(sx, sy)) - - - def skew(self, kx, ky): - """Convenience to help you set transforms""" - self.transform = mmult(mmult(self.transform, skewX(kx)),skewY(ky)) - - def shift(self, x, y): - '''Convenience function to set the origin arbitrarily''' - self.transform = self.transform[:-2]+(x,y) - - def asDrawing(self, width, height): - """ Convenience function to make a drawing from a group - After calling this the instance will be a drawing! - """ - self.__class__ = Drawing - self._attrMap.update(self._xtraAttrMap) - self.width = width - self.height = height - - def getContents(self): - '''Return the list of things to be rendered - override to get more complicated behaviour''' - b = getattr(self,'background',None) - C = self.contents - if b and b not in C: C = [b]+C - return C - - def getBounds(self): - if self.contents: - b = [] - for elem in self.contents: - b.append(elem.getBounds()) - x1 = getRectsBounds(b) - if x1 is None: return None - x1, y1, x2, y2 = x1 - trans = self.transform - corners = [[x1,y1], [x1, y2], [x2, y1], [x2,y2]] - newCorners = [] - for corner in corners: - newCorners.append(transformPoint(trans, corner)) - return getPointsBounds(newCorners) - else: - #empty group needs a sane default; this - #will happen when interactively creating a group - #nothing has been added to yet. The alternative is - #to handle None as an allowed return value everywhere. - return None - -def _addObjImport(obj,I,n=None): - '''add an import of obj's class to a dictionary of imports''' #' - from inspect import getmodule - c = obj.__class__ - m = getmodule(c).__name__ - n = n or c.__name__ - if not I.has_key(m): - I[m] = [n] - elif n not in I[m]: - I[m].append(n) - -def _repr(self,I=None): - '''return a repr style string with named fixed args first, then keywords''' - if type(self) is InstanceType: - if self is EmptyClipPath: - _addObjImport(self,I,'EmptyClipPath') - return 'EmptyClipPath' - if I: _addObjImport(self,I) - if isinstance(self,Shape): - from inspect import getargs - args, varargs, varkw = getargs(self.__init__.im_func.func_code) - P = self.getProperties() - s = self.__class__.__name__+'(' - for n in args[1:]: - v = P[n] - del P[n] - s = s + '%s,' % _repr(v,I) - for n,v in P.items(): - v = P[n] - s = s + '%s=%s,' % (n, _repr(v,I)) - return s[:-1]+')' - else: - return repr(self) - elif type(self) is FloatType: - return fp_str(self) - elif type(self) in (ListType,TupleType): - s = '' - for v in self: - s = s + '%s,' % _repr(v,I) - if type(self) is ListType: - return '[%s]' % s[:-1] - else: - return '(%s%s)' % (s[:-1],len(self)==1 and ',' or '') - else: - return repr(self) - -def _renderGroupPy(G,pfx,I,i=0,indent='\t\t'): - s = '' - C = getattr(G,'transform',None) - if C: s = s + ('%s%s.transform = %s\n' % (indent,pfx,_repr(C))) - C = G.contents - for n in C: - if isinstance(n, Group): - npfx = 'v%d' % i - i = i + 1 - s = s + '%s%s=%s._nn(Group())\n' % (indent,npfx,pfx) - s = s + _renderGroupPy(n,npfx,I,i,indent) - i = i - 1 - else: - s = s + '%s%s.add(%s)\n' % (indent,pfx,_repr(n,I)) - return s - -class Drawing(Group, Flowable): - """Outermost container; the thing a renderer works on. - This has no properties except a height, width and list - of contents.""" - - _xtraAttrMap = AttrMap( - width = AttrMapValue(isNumber,desc="Drawing width in points."), - height = AttrMapValue(isNumber,desc="Drawing height in points."), - canv = AttrMapValue(None), - background = AttrMapValue(isValidChildOrNone,desc="Background widget for the drawing"), - hAlign = AttrMapValue(OneOf("LEFT", "RIGHT", "CENTER", "CENTRE"), desc="Alignment within parent document"), - vAlign = AttrMapValue(OneOf("TOP", "BOTTOM", "CENTER", "CENTRE"), desc="Alignment within parent document") - ) - - _attrMap = AttrMap(BASE=Group) - _attrMap.update(_xtraAttrMap) - - def __init__(self, width=400, height=200, *nodes, **keywords): - self.background = None - apply(Group.__init__,(self,)+nodes,keywords) - self.width = width - self.height = height - self.hAlign = 'LEFT' - self.vAlign = 'BOTTOM' - - def _renderPy(self): - I = {'reportlab.graphics.shapes': ['_DrawingEditorMixin','Drawing','Group']} - G = _renderGroupPy(self._explode(),'self',I) - n = 'ExplodedDrawing_' + self.__class__.__name__ - s = '#Autogenerated by ReportLab guiedit do not edit\n' - for m, o in I.items(): - s = s + 'from %s import %s\n' % (m,string.replace(str(o)[1:-1],"'","")) - s = s + '\nclass %s(_DrawingEditorMixin,Drawing):\n' % n - s = s + '\tdef __init__(self,width=%s,height=%s,*args,**kw):\n' % (self.width,self.height) - s = s + '\t\tapply(Drawing.__init__,(self,width,height)+args,kw)\n' - s = s + G - s = s + '\n\nif __name__=="__main__": #NORUNTESTS\n\t%s().save(formats=[\'pdf\'],outDir=\'.\',fnRoot=None)\n' % n - return s - - def draw(self): - """This is used by the Platypus framework to let the document - draw itself in a story. It is specific to PDF and should not - be used directly.""" - - import renderPDF - R = renderPDF._PDFRenderer() - R.draw(self, self.canv, 0, 0) - - def expandUserNodes(self): - """Return a new drawing which only contains primitive shapes.""" - obj = Group.expandUserNodes(self) - obj.width = self.width - obj.height = self.height - return obj - - def copy(self,obj): - """Returns a copy""" - return self._copy(Drawing(self.width, self.height)) - - def asGroup(self,*args,**kw): - return self._copy(apply(Group,args,kw)) - - def save(self, formats=None, verbose=None, fnRoot=None, outDir=None, title=''): - """Saves copies of self in desired location and formats. - - Multiple formats can be supported in one call""" - from reportlab import rl_config - ext = '' - if not fnRoot: - fnRoot = getattr(self,'fileNamePattern',(self.__class__.__name__+'%03d')) - chartId = getattr(self,'chartId',0) - if callable(fnRoot): - fnRoot = fnRoot(chartId) - else: - try: - fnRoot = fnRoot % getattr(self,'chartId',0) - except TypeError, err: - if str(err) != 'not all arguments converted': raise - - if os.path.isabs(fnRoot): - outDir, fnRoot = os.path.split(fnRoot) - else: - outDir = outDir or getattr(self,'outDir','.') - if not os.path.isabs(outDir): outDir = os.path.join(os.path.dirname(sys.argv[0]),outDir) - if not os.path.isdir(outDir): os.makedirs(outDir) - fnroot = os.path.normpath(os.path.join(outDir,fnRoot)) - plotMode = os.path.splitext(fnroot) - if string.lower(plotMode[1][1:]) in ['pdf','ps','eps','gif','png','jpg','jpeg','pct','pict','tiff','tif','py','bmp']: - fnroot = plotMode[0] - - plotMode, verbose = formats or getattr(self,'formats',['pdf']), (verbose is not None and (verbose,) or (getattr(self,'verbose',verbose),))[0] - _saved = logger.warnOnce.enabled, logger.infoOnce.enabled - logger.warnOnce.enabled = logger.infoOnce.enabled = verbose - if 'pdf' in plotMode: - from reportlab.graphics import renderPDF - filename = fnroot+'.pdf' - if verbose: print "generating PDF file %s" % filename - renderPDF.drawToFile(self, filename, title, showBoundary=getattr(self,'showBorder',rl_config.showBoundary)) - ext = ext + '/.pdf' - if sys.platform=='mac': - import macfs, macostools - macfs.FSSpec(filename).SetCreatorType("CARO", "PDF ") - macostools.touched(filename) - - for bmFmt in ['gif','png','tif','jpg','tiff','pct','pict', 'bmp']: - if bmFmt in plotMode: - from reportlab.graphics import renderPM - filename = '%s.%s' % (fnroot,bmFmt) - if verbose: print "generating %s file %s" % (bmFmt,filename) - renderPM.drawToFile(self, filename,fmt=bmFmt,showBoundary=getattr(self,'showBorder',rl_config.showBoundary)) - ext = ext + '/.' + bmFmt - - if 'eps' in plotMode: - from rlextra.graphics import renderPS_SEP - filename = fnroot+'.eps' - if verbose: print "generating EPS file %s" % filename - renderPS_SEP.drawToFile(self, - filename, - title = fnroot, - dept = getattr(self,'EPS_info',['Testing'])[0], - company = getattr(self,'EPS_info',['','ReportLab'])[1], - preview = getattr(self,'preview',1), - showBoundary=getattr(self,'showBorder',rl_config.showBoundary)) - ext = ext + '/.eps' - - if 'ps' in plotMode: - from reportlab.graphics import renderPS - filename = fnroot+'.ps' - if verbose: print "generating EPS file %s" % filename - renderPS.drawToFile(self, filename, showBoundary=getattr(self,'showBorder',rl_config.showBoundary)) - ext = ext + '/.ps' - - if 'py' in plotMode: - filename = fnroot+'.py' - if verbose: print "generating py file %s" % filename - open(filename,'w').write(self._renderPy()) - ext = ext + '/.py' - - logger.warnOnce.enabled, logger.infoOnce.enabled = _saved - if hasattr(self,'saveLogger'): - self.saveLogger(fnroot,ext) - return ext and fnroot+ext[1:] or '' - - - def asString(self, format, verbose=None, preview=0): - """Converts to an 8 bit string in given format.""" - assert format in ['pdf','ps','eps','gif','png','jpg','jpeg','bmp','ppm','tiff','tif','py','pict','pct'], 'Unknown file format "%s"' % format - from reportlab import rl_config - #verbose = verbose is not None and (verbose,) or (getattr(self,'verbose',verbose),)[0] - if format == 'pdf': - from reportlab.graphics import renderPDF - return renderPDF.drawToString(self) - elif format in ['gif','png','tif','jpg','pct','pict','bmp','ppm']: - from reportlab.graphics import renderPM - return renderPM.drawToString(self, fmt=format) - elif format == 'eps': - from rlextra.graphics import renderPS_SEP - return renderPS_SEP.drawToString(self, - preview = preview, - showBoundary=getattr(self,'showBorder',rl_config.showBoundary)) - elif format == 'ps': - from reportlab.graphics import renderPS - return renderPS.drawToString(self, showBoundary=getattr(self,'showBorder',rl_config.showBoundary)) - elif format == 'py': - return self._renderPy() - -class _DrawingEditorMixin: - '''This is a mixin to provide functionality for edited drawings''' - def _add(self,obj,value,name=None,validate=None,desc=None,pos=None): - ''' - effectively setattr(obj,name,value), but takes care of things with _attrMaps etc - ''' - ivc = isValidChild(value) - if name and hasattr(obj,'_attrMap'): - if not obj.__dict__.has_key('_attrMap'): - obj._attrMap = obj._attrMap.clone() - if ivc and validate is None: validate = isValidChild - obj._attrMap[name] = AttrMapValue(validate,desc) - if hasattr(obj,'add') and ivc: - if pos: - obj.insert(pos,value,name) - else: - obj.add(value,name) - elif name: - setattr(obj,name,value) - else: - raise ValueError, "Can't add, need name" - -class LineShape(Shape): - # base for types of lines - - _attrMap = AttrMap( - strokeColor = AttrMapValue(isColorOrNone), - strokeWidth = AttrMapValue(isNumber), - strokeLineCap = AttrMapValue(None), - strokeLineJoin = AttrMapValue(None), - strokeMiterLimit = AttrMapValue(isNumber), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone), - ) - - def __init__(self, kw): - self.strokeColor = STATE_DEFAULTS['strokeColor'] - self.strokeWidth = 1 - self.strokeLineCap = 0 - self.strokeLineJoin = 0 - self.strokeMiterLimit = 0 - self.strokeDashArray = None - self.setProperties(kw) - - -class Line(LineShape): - _attrMap = AttrMap(BASE=LineShape, - x1 = AttrMapValue(isNumber), - y1 = AttrMapValue(isNumber), - x2 = AttrMapValue(isNumber), - y2 = AttrMapValue(isNumber), - ) - - def __init__(self, x1, y1, x2, y2, **kw): - LineShape.__init__(self, kw) - self.x1 = x1 - self.y1 = y1 - self.x2 = x2 - self.y2 = y2 - - def getBounds(self): - "Returns bounding rectangle of object as (x1,y1,x2,y2)" - return (self.x1, self.y1, self.x2, self.y2) - - -class SolidShape(LineShape): - # base for anything with outline and content - - _attrMap = AttrMap(BASE=LineShape, - fillColor = AttrMapValue(isColorOrNone), - ) - - def __init__(self, kw): - self.fillColor = STATE_DEFAULTS['fillColor'] - # do this at the end so keywords overwrite - #the above settings - LineShape.__init__(self, kw) - - -# path operator constants -_MOVETO, _LINETO, _CURVETO, _CLOSEPATH = range(4) -_PATH_OP_ARG_COUNT = (2, 2, 6, 0) # [moveTo, lineTo, curveTo, closePath] -_PATH_OP_NAMES=['moveTo','lineTo','curveTo','closePath'] - -def _renderPath(path, drawFuncs): - """Helper function for renderers.""" - # this could be a method of Path... - points = path.points - i = 0 - hadClosePath = 0 - hadMoveTo = 0 - for op in path.operators: - nArgs = _PATH_OP_ARG_COUNT[op] - func = drawFuncs[op] - j = i + nArgs - apply(func, points[i:j]) - i = j - if op == _CLOSEPATH: - hadClosePath = hadClosePath + 1 - if op == _MOVETO: - hadMoveTo = hadMoveTo + 1 - return hadMoveTo == hadClosePath - -class Path(SolidShape): - """Path, made up of straight lines and bezier curves.""" - - _attrMap = AttrMap(BASE=SolidShape, - points = AttrMapValue(isListOfNumbers), - operators = AttrMapValue(isListOfNumbers), - isClipPath = AttrMapValue(isBoolean), - ) - - def __init__(self, points=None, operators=None, isClipPath=0, **kw): - SolidShape.__init__(self, kw) - if points is None: - points = [] - if operators is None: - operators = [] - assert len(points) % 2 == 0, 'Point list must have even number of elements!' - self.points = points - self.operators = operators - self.isClipPath = isClipPath - - def copy(self): - new = Path(self.points[:], self.operators[:]) - new.setProperties(self.getProperties()) - return new - - def moveTo(self, x, y): - self.points.extend([x, y]) - self.operators.append(_MOVETO) - - def lineTo(self, x, y): - self.points.extend([x, y]) - self.operators.append(_LINETO) - - def curveTo(self, x1, y1, x2, y2, x3, y3): - self.points.extend([x1, y1, x2, y2, x3, y3]) - self.operators.append(_CURVETO) - - def closePath(self): - self.operators.append(_CLOSEPATH) - - def getBounds(self): - return getPathBounds(self.points) - -EmptyClipPath=Path() #special path - -def getArcPoints(centerx, centery, radius, startangledegrees, endangledegrees, yradius=None, degreedelta=None, reverse=None): - degreedelta = degreedelta or 1 - if yradius is None: yradius = radius - points = [] - from math import sin, cos, pi - degreestoradians = pi/180.0 - radiansdelta = degreedelta*degreestoradians - startangle = startangledegrees*degreestoradians - endangle = endangledegrees*degreestoradians - while endangle.001: - n = max(int(angle/radiansdelta+0.5),1) - radiansdelta = angle/n - a = points.append - for angle in xrange(n+1): - angle = startangle+angle*radiansdelta - a((centerx+radius*cos(angle),centery+yradius*sin(angle))) - #a((centerx+radius*cos(endangle),centery+yradius*sin(endangle))) - if reverse: points.reverse() - return points - -class ArcPath(Path): - '''Path with an addArc method''' - def addArc(self, centerx, centery, radius, startangledegrees, endangledegrees, yradius=None, degreedelta=None, moveTo=None, reverse=None): - P = getArcPoints(centerx, centery, radius, startangledegrees, endangledegrees, yradius=yradius, degreedelta=degreedelta, reverse=reverse) - if moveTo or not len(self.operators): - self.moveTo(P[0][0],P[0][1]) - del P[0] - for x, y in P: self.lineTo(x,y) - -def definePath(pathSegs=[],isClipPath=0, dx=0, dy=0, **kw): - O = [] - P = [] - for seg in pathSegs: - if type(seg) not in [ListType,TupleType]: - opName = seg - args = [] - else: - opName = seg[0] - args = seg[1:] - if opName not in _PATH_OP_NAMES: - raise ValueError, 'bad operator name %s' % opName - op = _PATH_OP_NAMES.index(opName) - if len(args)!=_PATH_OP_ARG_COUNT[op]: - raise ValueError, '%s bad arguments %s' % (opName,str(args)) - O.append(op) - P.extend(list(args)) - for d,o in (dx,0), (dy,1): - for i in xrange(o,len(P),2): - P[i] = P[i]+d - return apply(Path,(P,O,isClipPath),kw) - -class Rect(SolidShape): - """Rectangle, possibly with rounded corners.""" - - _attrMap = AttrMap(BASE=SolidShape, - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - width = AttrMapValue(isNumber), - height = AttrMapValue(isNumber), - rx = AttrMapValue(isNumber), - ry = AttrMapValue(isNumber), - ) - - def __init__(self, x, y, width, height, rx=0, ry=0, **kw): - SolidShape.__init__(self, kw) - self.x = x - self.y = y - self.width = width - self.height = height - self.rx = rx - self.ry = ry - - def copy(self): - new = Rect(self.x, self.y, self.width, self.height) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return (self.x, self.y, self.x + self.width, self.y + self.height) - - -class Image(SolidShape): - """Bitmap image.""" - - _attrMap = AttrMap(BASE=SolidShape, - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - width = AttrMapValue(isNumberOrNone), - height = AttrMapValue(isNumberOrNone), - path = AttrMapValue(None), - ) - - def __init__(self, x, y, width, height, path, **kw): - SolidShape.__init__(self, kw) - self.x = x - self.y = y - self.width = width - self.height = height - self.path = path - - def copy(self): - new = Image(self.x, self.y, self.width, self.height, self.path) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return (self.x, self.y, self.x + width, self.y + width) - -class Circle(SolidShape): - - _attrMap = AttrMap(BASE=SolidShape, - cx = AttrMapValue(isNumber), - cy = AttrMapValue(isNumber), - r = AttrMapValue(isNumber), - ) - - def __init__(self, cx, cy, r, **kw): - SolidShape.__init__(self, kw) - self.cx = cx - self.cy = cy - self.r = r - - def copy(self): - new = Circle(self.cx, self.cy, self.r) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return (self.cx - self.r, self.cy - self.r, self.cx + self.r, self.cy + self.r) - -class Ellipse(SolidShape): - - _attrMap = AttrMap(BASE=SolidShape, - cx = AttrMapValue(isNumber), - cy = AttrMapValue(isNumber), - rx = AttrMapValue(isNumber), - ry = AttrMapValue(isNumber), - ) - - def __init__(self, cx, cy, rx, ry, **kw): - SolidShape.__init__(self, kw) - self.cx = cx - self.cy = cy - self.rx = rx - self.ry = ry - - def copy(self): - new = Ellipse(self.cx, self.cy, self.rx, self.ry) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return (self.cx - self.rx, self.cy - self.ry, self.cx + self.rx, self.cy + self.ry) - -class Wedge(SolidShape): - """A "slice of a pie" by default translates to a polygon moves anticlockwise - from start angle to end angle""" - - _attrMap = AttrMap(BASE=SolidShape, - centerx = AttrMapValue(isNumber), - centery = AttrMapValue(isNumber), - radius = AttrMapValue(isNumber), - startangledegrees = AttrMapValue(isNumber), - endangledegrees = AttrMapValue(isNumber), - yradius = AttrMapValue(isNumberOrNone), - radius1 = AttrMapValue(isNumberOrNone), - yradius1 = AttrMapValue(isNumberOrNone), - ) - - degreedelta = 1 # jump every 1 degrees - - def __init__(self, centerx, centery, radius, startangledegrees, endangledegrees, yradius=None, **kw): - SolidShape.__init__(self, kw) - while endangledegrees.001: - n = max(1,int(angle/radiansdelta+0.5)) - radiansdelta = angle/n - a = points.append - CA = [] - CAA = CA.append - for angle in xrange(n+1): - angle = startangle+angle*radiansdelta - CAA((cos(angle),sin(angle))) - #CAA((cos(endangle),sin(endangle))) - for c,s in CA: - a(centerx+radius*c) - a(centery+yradius*s) - if (radius1==0 or radius1 is None) and (yradius1==0 or yradius1 is None): - a(centerx); a(centery) - else: - CA.reverse() - for c,s in CA: - a(centerx+radius1*c) - a(centery+yradius1*s) - return Polygon(points) - - def copy(self): - new = Wedge(self.centerx, - self.centery, - self.radius, - self.startangledegrees, - self.endangledegrees) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return self.asPolygon().getBounds() - -class Polygon(SolidShape): - """Defines a closed shape; Is implicitly - joined back to the start for you.""" - - _attrMap = AttrMap(BASE=SolidShape, - points = AttrMapValue(isListOfNumbers), - ) - - def __init__(self, points=[], **kw): - SolidShape.__init__(self, kw) - assert len(points) % 2 == 0, 'Point list must have even number of elements!' - self.points = points - - def copy(self): - new = Polygon(self.points) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return getPointsBounds(self.points) - -class PolyLine(LineShape): - """Series of line segments. Does not define a - closed shape; never filled even if apparently joined. - Put the numbers in the list, not two-tuples.""" - - _attrMap = AttrMap(BASE=LineShape, - points = AttrMapValue(isListOfNumbers), - ) - - def __init__(self, points=[], **kw): - LineShape.__init__(self, kw) - lenPoints = len(points) - if lenPoints: - if type(points[0]) in (ListType,TupleType): - L = [] - for (x,y) in points: - L.append(x) - L.append(y) - points = L - else: - assert len(points) % 2 == 0, 'Point list must have even number of elements!' - self.points = points - - def copy(self): - new = PolyLine(self.points) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - return getPointsBounds(self.points) - -class String(Shape): - """Not checked against the spec, just a way to make something work. - Can be anchored left, middle or end.""" - - # to do. - _attrMap = AttrMap( - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - text = AttrMapValue(isString), - fontName = AttrMapValue(None), - fontSize = AttrMapValue(isNumber), - fillColor = AttrMapValue(isColorOrNone), - textAnchor = AttrMapValue(isTextAnchor), - ) - - def __init__(self, x, y, text, **kw): - self.x = x - self.y = y - self.text = text - self.textAnchor = 'start' - self.fontName = STATE_DEFAULTS['fontName'] - self.fontSize = STATE_DEFAULTS['fontSize'] - self.fillColor = STATE_DEFAULTS['fillColor'] - self.setProperties(kw) - - def getEast(self): - return self.x + stringWidth(self.text,self.fontName,self.fontSize) - - def copy(self): - new = String(self.x, self.y, self.text) - new.setProperties(self.getProperties()) - return new - - def getBounds(self): - # assumes constant drop of 0.2*size to baseline - w = stringWidth(self.text,self.fontName,self.fontSize) - if self.textAnchor == 'start': - x = self.x - elif self.textAnchor == 'middle': - x = self.x - 0.5*w - elif self.textAnchor == 'end': - x = self.x - w - return (x, self.y - 0.2 * self.fontSize, x+w, self.y + self.fontSize) - -class UserNode(_DrawTimeResizeable): - """A simple template for creating a new node. The user (Python - programmer) may subclasses this. provideNode() must be defined to - provide a Shape primitive when called by a renderer. It does - NOT inherit from Shape, as the renderer always replaces it, and - your own classes can safely inherit from it without getting - lots of unintended behaviour.""" - - def provideNode(self): - """Override this to create your own node. This lets widgets be - added to drawings; they must create a shape (typically a group) - so that the renderer can draw the custom node.""" - - raise NotImplementedError, "this method must be redefined by the user/programmer" - - -def test(): - r = Rect(10,10,200,50) - import pprint - pp = pprint.pprint - print 'a Rectangle:' - pp(r.getProperties()) - print - print 'verifying...', - r.verify() - print 'OK' - #print 'setting rect.z = "spam"' - #r.z = 'spam' - print 'deleting rect.width' - del r.width - print 'verifying...', - r.verify() - - -if __name__=='__main__': - test() diff --git a/bin/reportlab/graphics/testdrawings.py b/bin/reportlab/graphics/testdrawings.py deleted file mode 100755 index b5048d1287c..00000000000 --- a/bin/reportlab/graphics/testdrawings.py +++ /dev/null @@ -1,294 +0,0 @@ -#! /usr/bin/python2.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/graphics/testdrawings.py -__version__=''' $Id $ ''' -"""This contains a number of routines to generate test drawings -for reportlab/graphics. For now they are contrived, but we will expand them -to try and trip up any parser. Feel free to add more. - -""" - -from reportlab.graphics.shapes import * -from reportlab.lib import colors - -def getDrawing1(): - """Hello World, on a rectangular background""" - - D = Drawing(400, 200) - D.add(Rect(50, 50, 300, 100, fillColor=colors.yellow)) #round corners - D.add(String(180,100, 'Hello World', fillColor=colors.red)) - - - return D - - -def getDrawing2(): - """This demonstrates the basic shapes. There are - no groups or references. Each solid shape should have - a purple fill.""" - D = Drawing(400, 200) #, fillColor=colors.purple) - - D.add(Line(10,10,390,190)) - D.add(Circle(100,100,20, fillColor=colors.purple)) - D.add(Circle(200,100,20, fillColor=colors.purple)) - D.add(Circle(300,100,20, fillColor=colors.purple)) - - D.add(Wedge(330,100,40, -10,40, fillColor=colors.purple)) - - D.add(PolyLine([120,10,130,20,140,10,150,20,160,10, - 170,20,180,10,190,20,200,10])) - - D.add(Polygon([300,20,350,20,390,80,300,75, 330, 40])) - - D.add(Ellipse(50, 150, 40, 20)) - - D.add(Rect(120, 150, 60, 30, - strokeWidth=10, - strokeColor=colors.red, - fillColor=colors.yellow)) #square corners - - D.add(Rect(220, 150, 60, 30, 10, 10)) #round corners - - D.add(String(10,50, 'Basic Shapes', fillColor=colors.black)) - - return D - - -##def getDrawing2(): -## """This drawing uses groups. Each group has two circles and a comment. -## The line style is set at group level and should be red for the left, -## bvlue for the right.""" -## D = Drawing(400, 200) -## -## Group1 = Group() -## -## Group1.add(String(50, 50, 'Group 1', fillColor=colors.black)) -## Group1.add(Circle(75,100,25)) -## Group1.add(Circle(125,100,25)) -## D.add(Group1) -## -## Group2 = Group( -## String(250, 50, 'Group 2', fillColor=colors.black), -## Circle(275,100,25), -## Circle(325,100,25)#, - - -##def getDrawing2(): -## """This drawing uses groups. Each group has two circles and a comment. -## The line style is set at group level and should be red for the left, -## bvlue for the right.""" -## D = Drawing(400, 200) -## -## Group1 = Group() -## -## Group1.add(String(50, 50, 'Group 1', fillColor=colors.black)) -## Group1.add(Circle(75,100,25)) -## Group1.add(Circle(125,100,25)) -## D.add(Group1) -## -## Group2 = Group( -## String(250, 50, 'Group 2', fillColor=colors.black), -## Circle(275,100,25), -## Circle(325,100,25)#, -## -## #group attributes -## #strokeColor=colors.blue -## ) -## D.add(Group2) - -## return D -## -## -##def getDrawing3(): -## """This uses a named reference object. The house is a 'subroutine' -## the basic brick colored walls are defined, but the roof and window -## color are undefined and may be set by the container.""" -## -## D = Drawing(400, 200, fill=colors.bisque) -## -## -## House = Group( -## Rect(2,20,36,30, fill=colors.bisque), #walls -## Polygon([0,20,40,20,20,5]), #roof -## Rect(8, 38, 8, 12), #door -## Rect(25, 38, 8, 7), #window -## Rect(8, 25, 8, 7), #window -## Rect(25, 25, 8, 7) #window -## -## ) -## D.addDef('MyHouse', House) -## -## # one row all the same color -## D.add(String(20, 40, 'British Street...',fill=colors.black)) -## for i in range(6): -## x = i * 50 -## D.add(NamedReference('MyHouse', -## House, -## transform=translate(x, 40), -## fill = colors.brown -## ) -## ) -## -## # now do a row all different -## D.add(String(20, 120, 'Mediterranean Street...',fill=colors.black)) -## x = 0 -## for color in (colors.blue, colors.yellow, colors.orange, -## colors.red, colors.green, colors.chartreuse): -## D.add(NamedReference('MyHouse', -## House, -## transform=translate(x,120), -## fill = color, -## ) -## ) -## x = x + 50 -## #..by popular demand, the mayor gets a big one at the end -## D.add(NamedReference('MyHouse', -## House, -## transform=mmult(translate(x,110), scale(1.2,1.2)), -## fill = color, -## ) -## ) -## -## -## return D -## -##def getDrawing4(): -## """This tests that attributes are 'unset' correctly when -## one steps back out of a drawing node. All the circles are part of a -## group setting the line color to blue; the second circle explicitly -## sets it to red. Ideally, the third circle should go back to blue.""" -## D = Drawing(400, 200) -## -## -## G = Group( -## Circle(100,100,20), -## Circle(200,100,20, stroke=colors.blue), -## Circle(300,100,20), -## stroke=colors.red, -## stroke_width=3, -## fill=colors.aqua -## ) -## D.add(G) -## -## -## D.add(String(10,50, 'Stack Unwinding - should be red, blue, red')) -## -## return D -## -## -##def getDrawing5(): -## """This Rotates Coordinate Axes""" -## D = Drawing(400, 200) -## -## -## -## Axis = Group( -## Line(0,0,100,0), #x axis -## Line(0,0,0,50), # y axis -## Line(0,10,10,10), #ticks on y axis -## Line(0,20,10,20), -## Line(0,30,10,30), -## Line(0,40,10,40), -## Line(10,0,10,10), #ticks on x axis -## Line(20,0,20,10), -## Line(30,0,30,10), -## Line(40,0,40,10), -## Line(50,0,50,10), -## Line(60,0,60,10), -## Line(70,0,70,10), -## Line(80,0,80,10), -## Line(90,0,90,10), -## String(20, 35, 'Axes', fill=colors.black) -## ) -## -## D.addDef('Axes', Axis) -## -## D.add(NamedReference('Axis', Axis, -## transform=translate(10,10))) -## D.add(NamedReference('Axis', Axis, -## transform=mmult(translate(150,10),rotate(15))) -## ) -## return D -## -##def getDrawing6(): -## """This Rotates Text""" -## D = Drawing(400, 300, fill=colors.black) -## -## xform = translate(200,150) -## C = (colors.black,colors.red,colors.green,colors.blue,colors.brown,colors.gray, colors.pink, -## colors.lavender,colors.lime, colors.mediumblue, colors.magenta, colors.limegreen) -## -## for i in range(12): -## D.add(String(0, 0, ' - - Rotated Text', fill=C[i%len(C)], transform=mmult(xform, rotate(30*i)))) -## -## return D -## -##def getDrawing7(): -## """This defines and tests a simple UserNode0 (the trailing zero denotes -## an experimental method which is not part of the supported API yet). -## Each of the four charts is a subclass of UserNode which generates a random -## series when rendered.""" -## -## class MyUserNode(UserNode0): -## import whrandom, math -## -## -## def provideNode(self, sender): -## """draw a simple chart that changes everytime it's drawn""" -## # print "here's a random number %s" % self.whrandom.random() -## #print "MyUserNode.provideNode being called by %s" % sender -## g = Group() -## #g._state = self._state # this is naughty -## PingoNode.__init__(g, self._state) # is this less naughty ? -## w = 80.0 -## h = 50.0 -## g.add(Rect(0,0, w, h, stroke=colors.black)) -## N = 10.0 -## x,y = (0,h) -## dx = w/N -## for ii in range(N): -## dy = (h/N) * self.whrandom.random() -## g.add(Line(x,y,x+dx, y-dy)) -## x = x + dx -## y = y - dy -## return g -## -## D = Drawing(400,200, fill=colors.white) # AR - same size as others -## -## D.add(MyUserNode()) -## -## graphcolor= [colors.green, colors.red, colors.brown, colors.purple] -## for ii in range(4): -## D.add(Group( MyUserNode(stroke=graphcolor[ii], stroke_width=2), -## transform=translate(ii*90,0) )) -## -## #un = MyUserNode() -## #print un.provideNode() -## return D -## -##def getDrawing8(): -## """Test Path operations--lineto, curveTo, etc.""" -## D = Drawing(400, 200, fill=None, stroke=colors.purple, stroke_width=2) -## -## xform = translate(200,100) -## C = (colors.black,colors.red,colors.green,colors.blue,colors.brown,colors.gray, colors.pink, -## colors.lavender,colors.lime, colors.mediumblue, colors.magenta, colors.limegreen) -## p = Path(50,50) -## p.lineTo(100,100) -## p.moveBy(-25,25) -## p.curveTo(150,125, 125,125, 200,50) -## p.curveTo(175, 75, 175, 98, 62, 87) -## -## -## D.add(p) -## D.add(String(10,30, 'Tests of path elements-lines and bezier curves-and text formating')) -## D.add(Line(220,150, 220,200, stroke=colors.red)) -## D.add(String(220,180, "Text should be centered", text_anchor="middle") ) -## -## -## return D - - -if __name__=='__main__': - print __doc__ \ No newline at end of file diff --git a/bin/reportlab/graphics/testshapes.py b/bin/reportlab/graphics/testshapes.py deleted file mode 100755 index f73d15ba0a9..00000000000 --- a/bin/reportlab/graphics/testshapes.py +++ /dev/null @@ -1,547 +0,0 @@ -#! /usr/bin/python2.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/graphics/testshapes.py - -# testshapes.py - draws shapes onto a PDF canvas. - -""" -Execute the script to see some test drawings. - -This contains a number of routines to generate test drawings -for reportlab/graphics. For now many of them are contrived, -but we will expand them to try and trip up any parser. -Feel free to add more. -""" - -__version__ = ''' $Id $ ''' - - -import os, sys - -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.renderPDF import _PDFRenderer -import unittest - -_FONTS = ['Times-Roman','Courier','Times-BoldItalic',] - -######################################################### -# -# Collections of shape drawings. -# -######################################################### - - -def getFailedDrawing(funcName): - """Generate a drawing in case something goes really wrong. - - This will create a drawing to be displayed whenever some - other drawing could not be executed, because the generating - function does something terribly wrong! The box contains - an attention triangle, plus some error message. - """ - - D = Drawing(400, 200) - - points = [200,170, 140,80, 260,80] - D.add(Polygon(points, - strokeWidth=0.5*cm, - strokeColor=colors.red, - fillColor=colors.yellow)) - - s = String(200, 40, - "Error in generating function '%s'!" % funcName, - textAnchor='middle') - D.add(s) - - return D - - -# These are the real drawings to be eye-balled. - -def getDrawing01(): - """Hello World, on a rectangular background. - - The rectangle's fillColor is yellow. - The string's fillColor is red. - """ - - D = Drawing(400, 200) - D.add(Rect(50, 50, 300, 100, fillColor=colors.yellow)) - D.add(String(180,100, 'Hello World', fillColor=colors.red)) - - return D - - -def getDrawing02(): - """Various Line shapes. - - The lines are blue and their strokeWidth is 5 mm. - One line has a strokeDashArray set to [5, 10, 15]. - """ - - D = Drawing(400, 200) - D.add(Line(50,50, 300,100, - strokeColor=colors.blue, - strokeWidth=0.5*cm, - )) - D.add(Line(50,100, 300,50, - strokeColor=colors.blue, - strokeWidth=0.5*cm, - strokeDashArray=[5, 10, 15], - )) - - #x = 1/0 # Comment this to see the actual drawing! - - return D - - -def getDrawing03(): - """Text strings in various sizes and different fonts. - - Font size increases from 12 to 36 and from bottom left - to upper right corner. The first ones should be in - Times-Roman. Finally, a solitary Courier string at - the top right corner. - """ - - D = Drawing(400, 200) - for size in range(12, 36, 4): - D.add(String(10+size*2, - 10+size*2, - 'Hello World', - fontName=_FONTS[0], - fontSize=size)) - - D.add(String(150, 150, - 'Hello World', - fontName=_FONTS[1], - fontSize=36)) - return D - - -def getDrawing04(): - """Text strings in various colours. - - Colours are blue, yellow and red from bottom left - to upper right. - """ - - D = Drawing(400, 200) - i = 0 - for color in (colors.blue, colors.yellow, colors.red): - D.add(String(50+i*30, 50+i*30, - 'Hello World', fillColor=color)) - i = i + 1 - - return D - - -def getDrawing05(): - """Text strings with various anchors (alignments). - - Text alignment conforms to the anchors in the left column. - """ - - D = Drawing(400, 200) - - lineX = 250 - D.add(Line(lineX,10, lineX,190, strokeColor=colors.gray)) - - y = 130 - for anchor in ('start', 'middle', 'end'): - D.add(String(lineX, y, 'Hello World', textAnchor=anchor)) - D.add(String(50, y, anchor + ':')) - y = y - 30 - - return D - - -def getDrawing06(): - """This demonstrates all the basic shapes at once. - - There are no groups or references. - Each solid shape should have a purple fill. - """ - - purple = colors.purple - purple = colors.green - - D = Drawing(400, 200) #, fillColor=purple) - - D.add(Line(10,10, 390,190)) - - D.add(Circle(100,100,20, fillColor=purple)) - D.add(Circle(200,100,40, fillColor=purple)) - D.add(Circle(300,100,30, fillColor=purple)) - - D.add(Wedge(330,100,40, -10,40, fillColor=purple)) - - D.add(PolyLine([120,10, 130,20, 140,10, 150,20, 160,10, - 170,20, 180,10, 190,20, 200,10], fillColor=purple)) - - D.add(Polygon([300,20, 350,20, 390,80, 300,75, 330,40], fillColor=purple)) - - D.add(Ellipse(50,150, 40, 20, fillColor=purple)) - - D.add(Rect(120,150, 60,30, - strokeWidth=10, - strokeColor=colors.yellow, - fillColor=purple)) #square corners - - D.add(Rect(220, 150, 60, 30, 10, 10, fillColor=purple)) #round corners - - D.add(String(10,50, 'Basic Shapes', fillColor=colors.black)) - - return D - -def getDrawing07(): - """This tests the ability to translate and rotate groups. The first set of axes should be - near the bottom left of the drawing. The second should be rotated counterclockwise - by 15 degrees. The third should be rotated by 30 degrees.""" - D = Drawing(400, 200) - - Axis = Group( - Line(0,0,100,0), #x axis - Line(0,0,0,50), # y axis - Line(0,10,10,10), #ticks on y axis - Line(0,20,10,20), - Line(0,30,10,30), - Line(0,40,10,40), - Line(10,0,10,10), #ticks on x axis - Line(20,0,20,10), - Line(30,0,30,10), - Line(40,0,40,10), - Line(50,0,50,10), - Line(60,0,60,10), - Line(70,0,70,10), - Line(80,0,80,10), - Line(90,0,90,10), - String(20, 35, 'Axes', fill=colors.black) - ) - - firstAxisGroup = Group(Axis) - firstAxisGroup.translate(10,10) - D.add(firstAxisGroup) - - secondAxisGroup = Group(Axis) - secondAxisGroup.translate(150,10) - secondAxisGroup.rotate(15) - - D.add(secondAxisGroup) - - - thirdAxisGroup = Group(Axis, transform=mmult(translate(300,10), rotate(30))) - D.add(thirdAxisGroup) - - return D - - -def getDrawing08(): - """This tests the ability to scale coordinates. The bottom left set of axes should be - near the bottom left of the drawing. The bottom right should be stretched vertically - by a factor of 2. The top left one should be stretched horizontally by a factor of 2. - The top right should have the vertical axiss leaning over to the right by 30 degrees.""" - D = Drawing(400, 200) - - Axis = Group( - Line(0,0,100,0), #x axis - Line(0,0,0,50), # y axis - Line(0,10,10,10), #ticks on y axis - Line(0,20,10,20), - Line(0,30,10,30), - Line(0,40,10,40), - Line(10,0,10,10), #ticks on x axis - Line(20,0,20,10), - Line(30,0,30,10), - Line(40,0,40,10), - Line(50,0,50,10), - Line(60,0,60,10), - Line(70,0,70,10), - Line(80,0,80,10), - Line(90,0,90,10), - String(20, 35, 'Axes', fill=colors.black) - ) - - firstAxisGroup = Group(Axis) - firstAxisGroup.translate(10,10) - D.add(firstAxisGroup) - - secondAxisGroup = Group(Axis) - secondAxisGroup.translate(150,10) - secondAxisGroup.scale(1,2) - D.add(secondAxisGroup) - - thirdAxisGroup = Group(Axis) - thirdAxisGroup.translate(10,125) - thirdAxisGroup.scale(2,1) - D.add(thirdAxisGroup) - - fourthAxisGroup = Group(Axis) - fourthAxisGroup.translate(250,125) - fourthAxisGroup.skew(30,0) - D.add(fourthAxisGroup) - - - return D - -def getDrawing09(): - """This tests rotated strings - - Some renderers will have a separate mechanism for font drawing. This test - just makes sure strings get transformed the same way as regular graphics.""" - D = Drawing(400, 200) - - fontName = _FONTS[0] - fontSize = 12 - text = "I should be totally horizontal and enclosed in a box" - textWidth = stringWidth(text, fontName, fontSize) - - - g1 = Group( - String(20, 20, text, fontName=fontName, fontSize = fontSize), - Rect(18, 18, textWidth + 4, fontSize + 4, fillColor=None) - ) - D.add(g1) - - text = "I should slope up by 15 degrees, so my right end is higher than my left" - textWidth = stringWidth(text, fontName, fontSize) - g2 = Group( - String(20, 20, text, fontName=fontName, fontSize = fontSize), - Rect(18, 18, textWidth + 4, fontSize + 4, fillColor=None) - ) - g2.translate(0, 50) - g2.rotate(15) - D.add(g2) - - return D - -def getDrawing10(): - """This tests nested groups with multiple levels of coordinate transformation. - Each box should be staggered up and to the right, moving by 25 points each time.""" - D = Drawing(400, 200) - - fontName = _FONTS[0] - fontSize = 12 - - g1 = Group( - Rect(0, 0, 100, 20, fillColor=colors.yellow), - String(5, 5, 'Text in the box', fontName=fontName, fontSize = fontSize) - ) - D.add(g1) - - g2 = Group(g1, transform = translate(25,25)) - D.add(g2) - - g3 = Group(g2, transform = translate(25,25)) - D.add(g3) - - g4 = Group(g3, transform = translate(25,25)) - D.add(g4) - - - return D - -from widgets.signsandsymbols import SmileyFace -def getDrawing11(): - '''test of anchoring''' - def makeSmiley(x, y, size, color): - "Make a smiley data item representation." - d = size - s = SmileyFace() - s.fillColor = color - s.x = x-d - s.y = y-d - s.size = d*2 - return s - - D = Drawing(400, 200) #, fillColor=colors.purple) - g = Group(transform=(1,0,0,1,0,0)) - g.add(makeSmiley(100,100,10,colors.red)) - g.add(Line(90,100,110,100,strokeColor=colors.green)) - g.add(Line(100,90,100,110,strokeColor=colors.green)) - D.add(g) - g = Group(transform=(2,0,0,2,100,-100)) - g.add(makeSmiley(100,100,10,colors.blue)) - g.add(Line(90,100,110,100,strokeColor=colors.green)) - g.add(Line(100,90,100,110,strokeColor=colors.green)) - D.add(g) - g = Group(transform=(2,0,0,2,0,0)) - return D - - -def getDrawing12(): - """Text strings in a non-standard font. - All that is required is to place the .afm and .pfb files - on the font patch given in rl_config.py, - for example in reportlab/lib/fonts/. - """ - faceName = "Wargames-Regular" - D = Drawing(400, 200) - for size in range(12, 36, 4): - D.add(String(10+size*2, - 10+size*2, - 'Hello World', - fontName=faceName, - fontSize=size)) - return D - -def getDrawing13(): - 'Test Various TTF Fonts' - from reportlab.pdfbase import pdfmetrics, ttfonts - pdfmetrics.registerFont(ttfonts.TTFont("LuxiSerif", "luxiserif.ttf")) - pdfmetrics.registerFont(ttfonts.TTFont("Rina", "rina.ttf")) - _FONTS[1] = 'LuxiSerif' - _FONTS[2] = 'Rina' - F = ['Times-Roman','LuxiSerif', 'Rina'] - if sys.platform=='win32': - for name, ttf in [('Adventurer Light SF','Advlit.ttf'),('ArialMS','ARIAL.TTF'), - ('Book Antiqua','BKANT.TTF'), - ('Century Gothic','GOTHIC.TTF'), - ('Comic Sans MS', 'COMIC.TTF'), - ('Elementary Heavy SF Bold','Vwagh.ttf'), - ('Firenze SF','flot.ttf'), - ('Garamond','GARA.TTF'), - ('Jagger','Rols.ttf'), - ('Monotype Corsiva','MTCORSVA.TTF'), - ('Seabird SF','seag.ttf'), - ('Tahoma','TAHOMA.TTF'), - ('VerdanaMS','VERDANA.TTF'), - ]: - for D in ('c:\WINNT','c:\Windows'): - fn = os.path.join(D,'Fonts',ttf) - if os.path.isfile(fn): - try: - f = ttfonts.TTFont(name, fn) - pdfmetrics.registerFont(f) - F.append(name) - except: - pass - - def drawit(F,w=400,h=200,fontSize=12,slack=2,gap=5): - D = Drawing(w,h) - th = 2*gap + fontSize*1.2 - gh = gap + .2*fontSize - y = h - maxx = 0 - for fontName in F: - y -= th - text = fontName+": I should be totally horizontal and enclosed in a box" - textWidth = stringWidth(text, fontName, fontSize) - maxx = max(maxx,textWidth+20) - D.add( - Group(Rect(8, y-gh, textWidth + 4, th, strokeColor=colors.red, strokeWidth=.5, fillColor=colors.lightgrey), - String(10, y, text, fontName=fontName, fontSize = fontSize))) - y -= 5 - return maxx, h-y+gap, D - maxx, maxy, D = drawit(F) - if maxx>400 or maxy>200: _,_,D = drawit(F,maxx,maxy) - return D - -def getAllFunctionDrawingNames(doTTF=1): - "Get a list of drawing function names from somewhere." - - funcNames = [] - - # Here we get the names from the global name space. - symbols = globals().keys() - symbols.sort() - for funcName in symbols: - if funcName[0:10] == 'getDrawing': - if doTTF or funcName!='getDrawing13': - funcNames.append(funcName) - - return funcNames - -def _evalFuncDrawing(name, D, l=None, g=None): - try: - d = eval(name + '()', g or globals(), l or locals()) - except: - d = getFailedDrawing(name) - D.append((d, eval(name + '.__doc__'), name[3:])) - -def getAllTestDrawings(doTTF=1): - D = [] - for f in getAllFunctionDrawingNames(doTTF=doTTF): - _evalFuncDrawing(f,D) - return D - -def writePDF(drawings): - "Create and save a PDF file containing some drawings." - - pdfPath = os.path.splitext(sys.argv[0])[0] + '.pdf' - c = Canvas(pdfPath) - c.setFont(_FONTS[0], 32) - c.drawString(80, 750, 'ReportLab Graphics-Shapes Test') - - # Print drawings in a loop, with their doc strings. - c.setFont(_FONTS[0], 12) - y = 740 - i = 1 - for (drawing, docstring, funcname) in drawings: - if y < 300: # Allows 5-6 lines of text. - c.showPage() - y = 740 - # Draw a title. - y = y - 30 - c.setFont(_FONTS[2],12) - c.drawString(80, y, '%s (#%d)' % (funcname, i)) - c.setFont(_FONTS[0],12) - y = y - 14 - textObj = c.beginText(80, y) - textObj.textLines(docstring) - c.drawText(textObj) - y = textObj.getY() - y = y - drawing.height - drawing.drawOn(c, 80, y) - i = i + 1 - - c.save() - print 'wrote %s ' % pdfPath - - -class ShapesTestCase(unittest.TestCase): - "Test generating all kinds of shapes." - - def setUp(self): - "Prepare some things before the tests start." - - self.funcNames = getAllFunctionDrawingNames() - self.drawings = [] - - - def tearDown(self): - "Do what has to be done after the tests are over." - - writePDF(self.drawings) - - - # This should always succeed. If each drawing would be - # wrapped in a dedicated test method like this one, it - # would be possible to have a count for wrong tests - # as well... Something like this is left for later... - def testAllDrawings(self): - "Make a list of drawings." - - for f in self.funcNames: - if f[0:10] == 'getDrawing': - # Make an instance and get its doc string. - # If that fails, use a default error drawing. - _evalFuncDrawing(f,self.drawings) - - -def makeSuite(): - "Make a test suite for unit testing." - - suite = unittest.TestSuite() - suite.addTest(ShapesTestCase('testAllDrawings')) - return suite - - -if __name__ == "__main__": - unittest.TextTestRunner().run(makeSuite()) diff --git a/bin/reportlab/graphics/widgetbase.py b/bin/reportlab/graphics/widgetbase.py deleted file mode 100644 index db40ee219cb..00000000000 --- a/bin/reportlab/graphics/widgetbase.py +++ /dev/null @@ -1,490 +0,0 @@ -#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/graphics/widgetbase.py -__version__=''' $Id$ ''' -import string - -from reportlab.graphics import shapes -from reportlab import rl_config -from reportlab.lib import colors -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * - - -class PropHolder: - '''Base for property holders''' - - _attrMap = None - - def verify(self): - """If the _attrMap attribute is not None, this - checks all expected attributes are present; no - unwanted attributes are present; and (if a - checking function is found) checks each - attribute has a valid value. Either succeeds - or raises an informative exception. - """ - - if self._attrMap is not None: - for key in self.__dict__.keys(): - if key[0] <> '_': - msg = "Unexpected attribute %s found in %s" % (key, self) - assert self._attrMap.has_key(key), msg - for (attr, metavalue) in self._attrMap.items(): - msg = "Missing attribute %s from %s" % (attr, self) - assert hasattr(self, attr), msg - value = getattr(self, attr) - args = (value, attr, self.__class__.__name__) - assert metavalue.validate(value), "Invalid value %s for attribute %s in class %s" % args - - if rl_config.shapeChecking: - """This adds the ability to check every attribute assignment - as it is made. It slows down shapes but is a big help when - developing. It does not get defined if rl_config.shapeChecking = 0. - """ - - def __setattr__(self, name, value): - """By default we verify. This could be off - in some parallel base classes.""" - validateSetattr(self,name,value) - - - def getProperties(self,recur=1): - """Returns a list of all properties which can be edited and - which are not marked as private. This may include 'child - widgets' or 'primitive shapes'. You are free to override - this and provide alternative implementations; the default - one simply returns everything without a leading underscore. - """ - - from reportlab.lib.validators import isValidChild - - # TODO when we need it, but not before - - # expose sequence contents? - - props = {} - for name in self.__dict__.keys(): - if name[0:1] <> '_': - component = getattr(self, name) - - if recur and isValidChild(component): - # child object, get its properties too - childProps = component.getProperties(recur=recur) - for (childKey, childValue) in childProps.items(): - #key might be something indexed like '[2].fillColor' - #or simple like 'fillColor'; in the former case we - #don't need a '.' between me and my child. - if childKey[0] == '[': - props['%s%s' % (name, childKey)] = childValue - else: - props['%s.%s' % (name, childKey)] = childValue - else: - props[name] = component - - return props - - - def setProperties(self, propDict): - """Permits bulk setting of properties. These may include - child objects e.g. "chart.legend.width = 200". - - All assignments will be validated by the object as if they - were set individually in python code. - - All properties of a top-level object are guaranteed to be - set before any of the children, which may be helpful to - widget designers. - """ - - childPropDicts = {} - for (name, value) in propDict.items(): - parts = string.split(name, '.', 1) - if len(parts) == 1: - #simple attribute, set it now - setattr(self, name, value) - else: - (childName, remains) = parts - try: - childPropDicts[childName][remains] = value - except KeyError: - childPropDicts[childName] = {remains: value} - - # now assign to children - for (childName, childPropDict) in childPropDicts.items(): - child = getattr(self, childName) - child.setProperties(childPropDict) - - - def dumpProperties(self, prefix=""): - """Convenience. Lists them on standard output. You - may provide a prefix - mostly helps to generate code - samples for documentation. - """ - - propList = self.getProperties().items() - propList.sort() - if prefix: - prefix = prefix + '.' - for (name, value) in propList: - print '%s%s = %s' % (prefix, name, value) - - -class Widget(PropHolder, shapes.UserNode): - """Base for all user-defined widgets. Keep as simple as possible. Does - not inherit from Shape so that we can rewrite shapes without breaking - widgets and vice versa.""" - - def _setKeywords(self,**kw): - for k,v in kw.items(): - if not self.__dict__.has_key(k): - setattr(self,k,v) - - def draw(self): - msg = "draw() must be implemented for each Widget!" - raise shapes.NotImplementedError, msg - - def demo(self): - msg = "demo() must be implemented for each Widget!" - raise shapes.NotImplementedError, msg - - def provideNode(self): - return self.draw() - - def getBounds(self): - "Return outer boundary as x1,y1,x2,y2. Can be overridden for efficiency" - return self.draw().getBounds() - -_ItemWrapper={} - -class TypedPropertyCollection(PropHolder): - """A container with properties for objects of the same kind. - - This makes it easy to create lists of objects. You initialize - it with a class of what it is to contain, and that is all you - can add to it. You can assign properties to the collection - as a whole, or to a numeric index within it; if so it creates - a new child object to hold that data. - - So: - wedges = TypedPropertyCollection(WedgeProperties) - wedges.strokeWidth = 2 # applies to all - wedges.strokeColor = colors.red # applies to all - wedges[3].strokeColor = colors.blue # only to one - - The last line should be taken as a prescription of how to - create wedge no. 3 if one is needed; no error is raised if - there are only two data points. - """ - - def __init__(self, exampleClass): - #give it same validation rules as what it holds - self.__dict__['_value'] = exampleClass() - self.__dict__['_children'] = {} - - def __getitem__(self, index): - try: - return self._children[index] - except KeyError: - Klass = self._value.__class__ - if _ItemWrapper.has_key(Klass): - WKlass = _ItemWrapper[Klass] - else: - class WKlass(Klass): - def __getattr__(self,name): - try: - return self.__class__.__bases__[0].__getattr__(self,name) - except: - if self._index and self._parent._children.has_key(self._index): - if self._parent._children[self._index].__dict__.has_key(name): - return getattr(self._parent._children[self._index],name) - return getattr(self._parent,name) - _ItemWrapper[Klass] = WKlass - - child = WKlass() - child._parent = self - if type(index) in (type(()),type([])): - index = tuple(index) - if len(index)>1: - child._index = tuple(index[:-1]) - else: - child._index = None - else: - child._index = None - for i in filter(lambda x,K=child.__dict__.keys(): x in K,child._attrMap.keys()): - del child.__dict__[i] - - self._children[index] = child - return child - - def has_key(self,key): - if type(key) in (type(()),type([])): key = tuple(key) - return self._children.has_key(key) - - def __setitem__(self, key, value): - msg = "This collection can only hold objects of type %s" % self._value.__class__.__name__ - assert isinstance(value, self._value.__class__), msg - - def __len__(self): - return len(self._children.keys()) - - def getProperties(self,recur=1): - # return any children which are defined and whatever - # differs from the parent - props = {} - - for (key, value) in self._value.getProperties(recur=recur).items(): - props['%s' % key] = value - - for idx in self._children.keys(): - childProps = self._children[idx].getProperties(recur=recur) - for (key, value) in childProps.items(): - if not hasattr(self,key) or getattr(self, key)<>value: - newKey = '[%s].%s' % (idx, key) - props[newKey] = value - return props - - def setVector(self,**kw): - for name, value in kw.items(): - for i in xrange(len(value)): - setattr(self[i],name,value[i]) - - def __getattr__(self,name): - return getattr(self._value,name) - - def __setattr__(self,name,value): - return setattr(self._value,name,value) - -## No longer needed! -class StyleProperties(PropHolder): - """A container class for attributes used in charts and legends. - - Attributes contained can be those for any graphical element - (shape?) in the ReportLab graphics package. The idea for this - container class is to be useful in combination with legends - and/or the individual appearance of data series in charts. - - A legend could be as simple as a wrapper around a list of style - properties, where the 'desc' attribute contains a descriptive - string and the rest could be used by the legend e.g. to draw - something like a color swatch. The graphical presentation of - the legend would be its own business, though. - - A chart could be inspecting a legend or, more directly, a list - of style properties to pick individual attributes that it knows - about in order to render a particular row of the data. A bar - chart e.g. could simply use 'strokeColor' and 'fillColor' for - drawing the bars while a line chart could also use additional - ones like strokeWidth. - """ - - _attrMap = AttrMap( - strokeWidth = AttrMapValue(isNumber), - strokeLineCap = AttrMapValue(isNumber), - strokeLineJoin = AttrMapValue(isNumber), - strokeMiterLimit = AttrMapValue(None), - strokeDashArray = AttrMapValue(isListOfNumbersOrNone), - strokeOpacity = AttrMapValue(isNumber), - strokeColor = AttrMapValue(isColorOrNone), - fillColor = AttrMapValue(isColorOrNone), - desc = AttrMapValue(isString), - ) - - def __init__(self, **kwargs): - "Initialize with attributes if any." - - for k, v in kwargs.items(): - setattr(self, k, v) - - - def __setattr__(self, name, value): - "Verify attribute name and value, before setting it." - validateSetattr(self,name,value) - - -class TwoCircles(Widget): - def __init__(self): - self.leftCircle = shapes.Circle(100,100,20, fillColor=colors.red) - self.rightCircle = shapes.Circle(300,100,20, fillColor=colors.red) - - def draw(self): - return shapes.Group(self.leftCircle, self.rightCircle) - - -class Face(Widget): - """This draws a face with two eyes. - - It exposes a couple of properties - to configure itself and hides all other details. - """ - - _attrMap = AttrMap( - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - size = AttrMapValue(isNumber), - skinColor = AttrMapValue(isColorOrNone), - eyeColor = AttrMapValue(isColorOrNone), - mood = AttrMapValue(OneOf('happy','sad','ok')), - ) - - def __init__(self): - self.x = 10 - self.y = 10 - self.size = 80 - self.skinColor = None - self.eyeColor = colors.blue - self.mood = 'happy' - - def demo(self): - pass - - def draw(self): - s = self.size # abbreviate as we will use this a lot - g = shapes.Group() - g.transform = [1,0,0,1,self.x, self.y] - - # background - g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5, fillColor=self.skinColor)) - - # left eye - g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.1, fillColor=colors.white)) - g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.05, fillColor=self.eyeColor)) - - # right eye - g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.1, fillColor=colors.white)) - g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.05, fillColor=self.eyeColor)) - - # nose - g.add(shapes.Polygon( - points=[s * 0.5, s * 0.6, s * 0.4, s * 0.3, s * 0.6, s * 0.3], - fillColor=None)) - - # mouth - if self.mood == 'happy': - offset = -0.05 - elif self.mood == 'sad': - offset = +0.05 - else: - offset = 0 - - g.add(shapes.Polygon( - points = [ - s * 0.3, s * 0.2, #left of mouth - s * 0.7, s * 0.2, #right of mouth - s * 0.6, s * (0.2 + offset), # the bit going up or down - s * 0.4, s * (0.2 + offset) # the bit going up or down - ], - fillColor = colors.pink, - strokeColor = colors.red, - strokeWidth = s * 0.03 - )) - - return g - - -class TwoFaces(Widget): - def __init__(self): - self.faceOne = Face() - self.faceOne.mood = "happy" - self.faceTwo = Face() - self.faceTwo.x = 100 - self.faceTwo.mood = "sad" - - def draw(self): - """Just return a group""" - return shapes.Group(self.faceOne, self.faceTwo) - - def demo(self): - """The default case already looks good enough, - no implementation needed here""" - pass - -class Sizer(Widget): - "Container to show size of all enclosed objects" - - _attrMap = AttrMap(BASE=shapes.SolidShape, - contents = AttrMapValue(isListOfShapes,desc="Contained drawable elements"), - ) - def __init__(self, *elements): - self.contents = [] - self.fillColor = colors.cyan - self.strokeColor = colors.magenta - - for elem in elements: - self.add(elem) - - def _addNamedNode(self,name,node): - 'if name is not None add an attribute pointing to node and add to the attrMap' - if name: - if name not in self._attrMap.keys(): - self._attrMap[name] = AttrMapValue(isValidChild) - setattr(self, name, node) - - def add(self, node, name=None): - """Appends non-None child node to the 'contents' attribute. In addition, - if a name is provided, it is subsequently accessible by name - """ - # propagates properties down - if node is not None: - assert isValidChild(node), "Can only add Shape or UserNode objects to a Group" - self.contents.append(node) - self._addNamedNode(name,node) - - def getBounds(self): - # get bounds of each object - if self.contents: - b = [] - for elem in self.contents: - b.append(elem.getBounds()) - return shapes.getRectsBounds(b) - else: - return (0,0,0,0) - - def draw(self): - g = shapes.Group() - (x1, y1, x2, y2) = self.getBounds() - r = shapes.Rect( - x = x1, - y = y1, - width = x2-x1, - height = y2-y1, - fillColor = self.fillColor, - strokeColor = self.strokeColor - ) - g.add(r) - for elem in self.contents: - g.add(elem) - return g - -def test(): - from reportlab.graphics.charts.piecharts import WedgeProperties - wedges = TypedPropertyCollection(WedgeProperties) - wedges.fillColor = colors.red - wedges.setVector(fillColor=(colors.blue,colors.green,colors.white)) - print len(_ItemWrapper) - - d = shapes.Drawing(400, 200) - tc = TwoCircles() - d.add(tc) - import renderPDF - renderPDF.drawToFile(d, 'sample_widget.pdf', 'A Sample Widget') - print 'saved sample_widget.pdf' - - d = shapes.Drawing(400, 200) - f = Face() - f.skinColor = colors.yellow - f.mood = "sad" - d.add(f, name='theFace') - print 'drawing 1 properties:' - d.dumpProperties() - renderPDF.drawToFile(d, 'face.pdf', 'A Sample Widget') - print 'saved face.pdf' - - d2 = d.expandUserNodes() - renderPDF.drawToFile(d2, 'face_copy.pdf', 'An expanded drawing') - print 'saved face_copy.pdf' - print 'drawing 2 properties:' - d2.dumpProperties() - - -if __name__=='__main__': - test() diff --git a/bin/reportlab/graphics/widgets/__init__.py b/bin/reportlab/graphics/widgets/__init__.py deleted file mode 100644 index ca91d242511..00000000000 --- a/bin/reportlab/graphics/widgets/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#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/graphics/widgets/__init__.py -__version__=''' $Id$ ''' \ No newline at end of file diff --git a/bin/reportlab/graphics/widgets/eventcal.py b/bin/reportlab/graphics/widgets/eventcal.py deleted file mode 100644 index 677081b2d8a..00000000000 --- a/bin/reportlab/graphics/widgets/eventcal.py +++ /dev/null @@ -1,303 +0,0 @@ -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/eventcal.py -# Event Calendar widget -# author: Andy Robinson -"""This file is a -""" -__version__=''' $Id$ ''' - -from reportlab.lib import colors -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * -from reportlab.graphics.shapes import Line, Rect, Polygon, Drawing, Group, String, Circle, Wedge -from reportlab.graphics.charts.textlabels import Label -from reportlab.graphics.widgetbase import Widget -from reportlab.graphics import renderPDF - - - - -class EventCalendar(Widget): - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 300 - self.height = 150 - self.timeColWidth = None # if declared, use it; otherwise auto-size. - self.trackRowHeight = 20 - self.data = [] # list of Event objects - self.trackNames = None - - self.startTime = None #displays ALL data on day if not set - self.endTime = None # displays ALL data on day if not set - self.day = 0 - - - # we will keep any internal geometry variables - # here. These are computed by computeSize(), - # which is the first thing done when drawing. - self._talksVisible = [] # subset of data which will get plotted, cache - self._startTime = None - self._endTime = None - self._trackCount = 0 - self._colWidths = [] - self._colLeftEdges = [] # left edge of each column - - def computeSize(self): - "Called at start of draw. Sets various column widths" - self._talksVisible = self.getRelevantTalks(self.data) - self._trackCount = len(self.getAllTracks()) - self.computeStartAndEndTimes() - self._colLeftEdges = [self.x] - if self.timeColWidth is None: - w = self.width / (1 + self._trackCount) - self._colWidths = [w] * (1+ self._trackCount) - for i in range(self._trackCount): - self._colLeftEdges.append(self._colLeftEdges[-1] + w) - else: - self._colWidths = [self.timeColWidth] - w = (self.width - self.timeColWidth) / self._trackCount - for i in range(self._trackCount): - self._colWidths.append(w) - self._colLeftEdges.append(self._colLeftEdges[-1] + w) - - - - def computeStartAndEndTimes(self): - "Work out first and last times to display" - if self.startTime: - self._startTime = self.startTime - else: - for (title, speaker, trackId, day, start, duration) in self._talksVisible: - - if self._startTime is None: #first one - self._startTime = start - else: - if start < self._startTime: - self._startTime = start - - if self.endTime: - self._endTime = self.endTime - else: - for (title, speaker, trackId, day, start, duration) in self._talksVisible: - if self._endTime is None: #first one - self._endTime = start + duration - else: - if start + duration > self._endTime: - self._endTime = start + duration - - - - - def getAllTracks(self): - tracks = [] - for (title, speaker, trackId, day, hours, duration) in self.data: - if trackId is not None: - if trackId not in tracks: - tracks.append(trackId) - tracks.sort() - return tracks - - def getRelevantTalks(self, talkList): - "Scans for tracks actually used" - used = [] - for talk in talkList: - (title, speaker, trackId, day, hours, duration) = talk - assert trackId <> 0, "trackId must be None or 1,2,3... zero not allowed!" - if day == self.day: - if (((self.startTime is None) or ((hours + duration) >= self.startTime)) - and ((self.endTime is None) or (hours <= self.endTime))): - used.append(talk) - return used - - def scaleTime(self, theTime): - "Return y-value corresponding to times given" - axisHeight = self.height - self.trackRowHeight - # compute fraction between 0 and 1, 0 is at start of period - proportionUp = ((theTime - self._startTime) / (self._endTime - self._startTime)) - y = self.y + axisHeight - (axisHeight * proportionUp) - return y - - - def getTalkRect(self, startTime, duration, trackId, text): - "Return shapes for a specific talk" - g = Group() - y_bottom = self.scaleTime(startTime + duration) - y_top = self.scaleTime(startTime) - y_height = y_top - y_bottom - - if trackId is None: - #spans all columns - x = self._colLeftEdges[1] - width = self.width - self._colWidths[0] - else: - #trackId is 1-based and these arrays have the margin info in column - #zero, so no need to add 1 - x = self._colLeftEdges[trackId] - width = self._colWidths[trackId] - - lab = Label() - lab.setText(text) - lab.setOrigin(x + 0.5*width, y_bottom+0.5*y_height) - lab.boxAnchor = 'c' - lab.width = width - lab.height = y_height - lab.fontSize = 6 - - r = Rect(x, y_bottom, width, y_height, fillColor=colors.cyan) - g.add(r) - g.add(lab) - - #now for a label - # would expect to color-code and add text - return g - - def draw(self): - self.computeSize() - g = Group() - - # time column - g.add(Rect(self.x, self.y, self._colWidths[0], self.height - self.trackRowHeight, fillColor=colors.cornsilk)) - - # track headers - x = self.x + self._colWidths[0] - y = self.y + self.height - self.trackRowHeight - for trk in range(self._trackCount): - wid = self._colWidths[trk+1] - r = Rect(x, y, wid, self.trackRowHeight, fillColor=colors.yellow) - s = String(x + 0.5*wid, y, 'Track %d' % trk, align='middle') - g.add(r) - g.add(s) - x = x + wid - - for talk in self._talksVisible: - (title, speaker, trackId, day, start, duration) = talk - r = self.getTalkRect(start, duration, trackId, title + '\n' + speaker) - g.add(r) - - - return g - - - - -def test(): - "Make a conference event for day 1 of UP Python 2003" - - - d = Drawing(400,200) - - cal = EventCalendar() - cal.x = 50 - cal.y = 25 - cal.data = [ - # these might be better as objects instead of tuples, since I - # predict a large number of "optionsl" variables to affect - # formatting in future. - - #title, speaker, track id, day, start time (hrs), duration (hrs) - # track ID is 1-based not zero-based! - ('Keynote: Why design another programming language?', 'Guido van Rossum', None, 1, 9.0, 1.0), - - ('Siena Web Service Architecture', 'Marc-Andre Lemburg', 1, 1, 10.5, 1.5), - ('Extreme Programming in Python', 'Chris Withers', 2, 1, 10.5, 1.5), - ('Pattern Experiences in C++', 'Mark Radford', 3, 1, 10.5, 1.5), - ('What is the Type of std::toupper()', 'Gabriel Dos Reis', 4, 1, 10.5, 1.5), - ('Linguistic Variables: Clear Thinking with Fuzzy Logic ', 'Walter Banks', 5, 1, 10.5, 1.5), - - ('lunch, short presentations, vendor presentations', '', None, 1, 12.0, 2.0), - - ("CORBA? Isn't that obsolete", 'Duncan Grisby', 1, 1, 14.0, 1.5), - ("Python Design Patterns", 'Duncan Booth', 2, 1, 14.0, 1.5), - ("Inside Security Checks and Safe Exceptions", 'Brandon Bray', 3, 1, 14.0, 1.5), - ("Studying at a Distance", 'Panel Discussion, Panel to include Alan Lenton & Francis Glassborow', 4, 1, 14.0, 1.5), - ("Coding Standards - Given the ANSI C Standard why do I still need a coding Standard", 'Randy Marques', 5, 1, 14.0, 1.5), - - ("RESTful Python", 'Hamish Lawson', 1, 1, 16.0, 1.5), - ("Parsing made easier - a radical old idea", 'Andrew Koenig', 2, 1, 16.0, 1.5), - ("C++ & Multimethods", 'Julian Smith', 3, 1, 16.0, 1.5), - ("C++ Threading", 'Kevlin Henney', 4, 1, 16.0, 1.5), - ("The Organisation Strikes Back", 'Alan Griffiths & Sarah Lees', 5, 1, 16.0, 1.5), - - ('Birds of a Feather meeting', '', None, 1, 17.5, 2.0), - - ('Keynote: In the Spirit of C', 'Greg Colvin', None, 2, 9.0, 1.0), - - ('The Infinite Filing Cabinet - object storage in Python', 'Jacob Hallen', 1, 2, 10.5, 1.5), - ('Introduction to Python and Jython for C++ and Java Programmers', 'Alex Martelli', 2, 2, 10.5, 1.5), - ('Template metaprogramming in Haskell', 'Simon Peyton Jones', 3, 2, 10.5, 1.5), - ('Plenty People Programming: C++ Programming in a Group, Workshop with a difference', 'Nico Josuttis', 4, 2, 10.5, 1.5), - ('Design and Implementation of the Boost Graph Library', 'Jeremy Siek', 5, 2, 10.5, 1.5), - - ('lunch, short presentations, vendor presentations', '', None, 2, 12.0, 2.0), - - ("Building GUI Applications with PythonCard and PyCrust", 'Andy Todd', 1, 2, 14.0, 1.5), - ("Integrating Python, C and C++", 'Duncan Booth', 2, 2, 14.0, 1.5), - ("Secrets and Pitfalls of Templates", 'Nicolai Josuttis & David Vandevoorde', 3, 2, 14.0, 1.5), - ("Being a Mentor", 'Panel Discussion, Panel to include Alan Lenton & Francis Glassborow', 4, 2, 14.0, 1.5), - ("The Embedded C Extensions to C", 'Willem Wakker', 5, 2, 14.0, 1.5), - - ("Lightning Talks", 'Paul Brian', 1, 2, 16.0, 1.5), - ("Scripting Java Applications with Jython", 'Anthony Eden', 2, 2, 16.0, 1.5), - ("Metaprogramming and the Boost Metaprogramming Library", 'David Abrahams', 3, 2, 16.0, 1.5), - ("A Common Vendor ABI for C++ -- GCC's why, what and not", 'Nathan Sidwell & Gabriel Dos Reis', 4, 2, 16.0, 1.5), - ("The Timing and Cost of Choices", 'Hubert Matthews', 5, 2, 16.0, 1.5), - - ('Birds of a Feather meeting', '', None, 2, 17.5, 2.0), - - ('Keynote: The Cost of C & C++ Compatibility', 'Andy Koenig', None, 3, 9.0, 1.0), - - ('Prying Eyes: Generic Observer Implementations in C++', 'Andrei Alexandrescu', 1, 2, 10.5, 1.5), - ('The Roadmap to Generative Programming With C++', 'Ulrich Eisenecker', 2, 2, 10.5, 1.5), - ('Design Patterns in C++ and C# for the Common Language Runtime', 'Brandon Bray', 3, 2, 10.5, 1.5), - ('Extreme Hour (XH): (workshop) - Jutta Eckstein and Nico Josuttis', 'Jutta Ecstein', 4, 2, 10.5, 1.5), - ('The Lambda Library : Unnamed Functions for C++', 'Jaako Jarvi', 5, 2, 10.5, 1.5), - - ('lunch, short presentations, vendor presentations', '', None, 3, 12.0, 2.0), - - ('Reflective Metaprogramming', 'Daveed Vandevoorde', 1, 3, 14.0, 1.5), - ('Advanced Template Issues and Solutions (double session)', 'Herb Sutter',2, 3, 14.0, 3), - ('Concurrent Programming in Java (double session)', 'Angelika Langer', 3, 3, 14.0, 3), - ('What can MISRA-C (2nd Edition) do for us?', 'Chris Hills', 4, 3, 14.0, 1.5), - ('C++ Metaprogramming Concepts and Results', 'Walter E Brown', 5, 3, 14.0, 1.5), - - ('Binding C++ to Python with the Boost Python Library', 'David Abrahams', 1, 3, 16.0, 1.5), - ('Using Aspect Oriented Programming for Enterprise Application Integration', 'Arno Schmidmeier', 4, 3, 16.0, 1.5), - ('Defective C++', 'Marc Paterno', 5, 3, 16.0, 1.5), - - ("Speakers' Banquet & Birds of a Feather meeting", '', None, 3, 17.5, 2.0), - - ('Keynote: The Internet, Software and Computers - A Report Card', 'Alan Lenton', None, 4, 9.0, 1.0), - - ('Multi-Platform Software Development; Lessons from the Boost libraries', 'Beman Dawes', 1, 5, 10.5, 1.5), - ('The Stability of the C++ ABI', 'Steve Clamage', 2, 5, 10.5, 1.5), - ('Generic Build Support - A Pragmatic Approach to the Software Build Process', 'Randy Marques', 3, 5, 10.5, 1.5), - ('How to Handle Project Managers: a survival guide', 'Barb Byro', 4, 5, 10.5, 1.5), - - ('lunch, ACCU AGM', '', None, 5, 12.0, 2.0), - - ('Sauce: An OO recursive descent parser; its design and implementation.', 'Jon Jagger', 1, 5, 14.0, 1.5), - ('GNIRTS ESAC REWOL - Bringing the UNIX filters to the C++ iostream library.', 'JC van Winkel', 2, 5, 14.0, 1.5), - ('Pattern Writing: Live and Direct', 'Frank Buschmann & Kevlin Henney', 3, 5, 14.0, 3.0), - ('The Future of Programming Languages - A Goldfish Bowl', 'Francis Glassborow and friends', 3, 5, 14.0, 1.5), - - ('Honey, I Shrunk the Threads: Compile-time checked multithreaded transactions in C++', 'Andrei Alexandrescu', 1, 5, 16.0, 1.5), - ('Fun and Functionality with Functors', 'Lois Goldthwaite', 2, 5, 16.0, 1.5), - ('Agile Enough?', 'Alan Griffiths', 4, 5, 16.0, 1.5), - ("Conference Closure: A brief plenary session", '', None, 5, 17.5, 0.5), - - ] - - #return cal - cal.day = 1 - - d.add(cal) - - - for format in ['pdf']:#,'gif','png']: - out = d.asString(format) - open('eventcal.%s' % format, 'wb').write(out) - print 'saved eventcal.%s' % format - -if __name__=='__main__': - test() diff --git a/bin/reportlab/graphics/widgets/flags.py b/bin/reportlab/graphics/widgets/flags.py deleted file mode 100644 index b8857066da8..00000000000 --- a/bin/reportlab/graphics/widgets/flags.py +++ /dev/null @@ -1,879 +0,0 @@ -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgets/flags.py -# Flag Widgets - a collection of flags as widgets -# author: John Precedo (johnp@reportlab.com) -"""This file is a collection of flag graphics as widgets. - -All flags are represented at the ratio of 1:2, even where the official ratio for the flag is something else -(such as 3:5 for the German national flag). The only exceptions are for where this would look _very_ wrong, -such as the Danish flag whose (ratio is 28:37), or the Swiss flag (which is square). - -Unless otherwise stated, these flags are all the 'national flags' of the countries, rather than their -state flags, naval flags, ensigns or any other variants. (National flags are the flag flown by civilians -of a country and the ones usually used to represent a country abroad. State flags are the variants used by -the government and by diplomatic missions overseas). - -To check on how close these are to the 'official' representations of flags, check the World Flag Database at -http://www.flags.ndirect.co.uk/ - -The flags this file contains are: - -EU Members: -United Kingdom, Austria, Belgium, Denmark, Finland, France, Germany, Greece, Ireland, Italy, Luxembourg, -Holland (The Netherlands), Spain, Sweden - -Others: -USA, Czech Republic, European Union, Switzerland, Turkey, Brazil - -(Brazilian flag contributed by Publio da Costa Melo [publio@planetarium.com.br]). -""" -__version__=''' $Id$ ''' - -from reportlab.lib import colors -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * -from reportlab.graphics.shapes import Line, Rect, Polygon, Drawing, Group, String, Circle, Wedge -from reportlab.graphics.widgetbase import Widget -from reportlab.graphics import renderPDF -from signsandsymbols import _Symbol -import copy -from math import sin, cos, pi - -validFlag=OneOf(None, - 'UK', - 'USA', - 'Afghanistan', - 'Austria', - 'Belgium', - 'China', - 'Cuba', - 'Denmark', - 'Finland', - 'France', - 'Germany', - 'Greece', - 'Ireland', - 'Italy', - 'Japan', - 'Luxembourg', - 'Holland', - 'Palestine', - 'Portugal', - 'Russia', - 'Spain', - 'Sweden', - 'Norway', - 'CzechRepublic', - 'Turkey', - 'Switzerland', - 'EU', - 'Brazil' - ) - -_size = 100. - -class Star(_Symbol): - """This draws a 5-pointed star. - - possible attributes: - 'x', 'y', 'size', 'fillColor', 'strokeColor' - - """ - _attrMap = AttrMap(BASE=_Symbol, - angle = AttrMapValue(isNumber, desc='angle in degrees'), - ) - _size = 100. - - def __init__(self): - _Symbol.__init__(self) - self.size = 100 - self.fillColor = colors.yellow - self.strokeColor = None - self.angle = 0 - - def demo(self): - D = Drawing(200, 100) - et = Star() - et.x=50 - et.y=0 - D.add(et) - labelFontSize = 10 - D.add(String(et.x+(et.size/2.0),(et.y-(1.2*labelFontSize)), - et.__class__.__name__, fillColor=colors.black, textAnchor='middle', - fontSize=labelFontSize)) - return D - - def draw(self): - s = float(self.size) #abbreviate as we will use this a lot - g = Group() - - # new algorithm from markers.StarFive - R = float(self.size)/2 - r = R*sin(18*(pi/180.0))/cos(36*(pi/180.0)) - P = [] - angle = 90 - for i in xrange(5): - for radius in R, r: - theta = angle*(pi/180.0) - P.append(radius*cos(theta)) - P.append(radius*sin(theta)) - angle = angle + 36 - # star specific bits - star = Polygon(P, - fillColor = self.fillColor, - strokeColor = self.strokeColor, - strokeWidth=s/50) - g.rotate(self.angle) - g.shift(self.x+self.dx,self.y+self.dy) - g.add(star) - - return g - -class Flag(_Symbol): - """This is a generic flag class that all the flags in this file use as a basis. - - This class basically provides edges and a tidy-up routine to hide any bits of - line that overlap the 'outside' of the flag - - possible attributes: - 'x', 'y', 'size', 'fillColor' - """ - - _attrMap = AttrMap(BASE=_Symbol, - fillColor = AttrMapValue(isColor, desc='Background color'), - border = AttrMapValue(isBoolean, 'Whether a background is drawn'), - kind = AttrMapValue(validFlag, desc='Which flag'), - ) - - _cache = {} - - def __init__(self,**kw): - _Symbol.__init__(self) - self.kind = None - self.size = 100 - self.fillColor = colors.white - self.border=1 - self.setProperties(kw) - - def availableFlagNames(self): - '''return a list of the things we can display''' - return filter(lambda x: x is not None, self._attrMap['kind'].validate._enum) - - def _Flag_None(self): - s = _size # abbreviate as we will use this a lot - g = Group() - g.add(Rect(0, 0, s*2, s, fillColor = colors.purple, strokeColor = colors.black, strokeWidth=0)) - return g - - def _borderDraw(self,f): - s = self.size # abbreviate as we will use this a lot - g = Group() - g.add(f) - x, y, sW = self.x+self.dx, self.y+self.dy, self.strokeWidth/2. - g.insert(0,Rect(-sW, -sW, width=getattr(self,'_width',2*s)+3*sW, height=getattr(self,'_height',s)+2*sW, - fillColor = None, strokeColor = self.strokeColor, strokeWidth=sW*2)) - g.shift(x,y) - g.scale(s/_size, s/_size) - return g - - def draw(self): - kind = self.kind or 'None' - f = self._cache.get(kind) - if not f: - f = getattr(self,'_Flag_'+kind)() - self._cache[kind] = f._explode() - return self._borderDraw(f) - - def clone(self): - return copy.copy(self) - - def demo(self): - D = Drawing(200, 100) - name = self.availableFlagNames() - import time - name = name[int(time.time()) % len(name)] - fx = Flag() - fx.kind = name - fx.x = 0 - fx.y = 0 - D.add(fx) - labelFontSize = 10 - D.add(String(fx.x+(fx.size/2),(fx.y-(1.2*labelFontSize)), - name, fillColor=colors.black, textAnchor='middle', - fontSize=labelFontSize)) - labelFontSize = int(fx.size/4) - D.add(String(fx.x+(fx.size),(fx.y+((fx.size/2))), - "SAMPLE", fillColor=colors.gold, textAnchor='middle', - fontSize=labelFontSize, fontName="Helvetica-Bold")) - return D - - def _Flag_UK(self): - s = _size - g = Group() - w = s*2 - g.add(Rect(0, 0, w, s, fillColor = colors.navy, strokeColor = colors.black, strokeWidth=0)) - g.add(Polygon([0,0, s*.225,0, w,s*(1-.1125), w,s, w-s*.225,s, 0, s*.1125], fillColor = colors.mintcream, strokeColor=None, strokeWidth=0)) - g.add(Polygon([0,s*(1-.1125), 0, s, s*.225,s, w, s*.1125, w,0, w-s*.225,0], fillColor = colors.mintcream, strokeColor=None, strokeWidth=0)) - g.add(Polygon([0, s-(s/15), (s-((s/10)*4)), (s*0.65), (s-(s/10)*3), (s*0.65), 0, s], fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Polygon([0, 0, (s-((s/10)*3)), (s*0.35), (s-((s/10)*2)), (s*0.35), (s/10), 0], fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Polygon([w, s, (s+((s/10)*3)), (s*0.65), (s+((s/10)*2)), (s*0.65), w-(s/10), s], fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Polygon([w, (s/15), (s+((s/10)*4)), (s*0.35), (s+((s/10)*3)), (s*0.35), w, 0], fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Rect(((s*0.42)*2), 0, width=(0.16*s)*2, height=s, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)) - g.add(Rect(0, (s*0.35), width=w, height=s*0.3, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)) - g.add(Rect(((s*0.45)*2), 0, width=(0.1*s)*2, height=s, fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Rect(0, (s*0.4), width=w, height=s*0.2, fillColor = colors.red, strokeColor = None, strokeWidth=0)) - return g - - def _Flag_USA(self): - s = _size # abbreviate as we will use this a lot - g = Group() - - box = Rect(0, 0, s*2, s, fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - for stripecounter in range (13,0, -1): - stripeheight = s/13.0 - if not (stripecounter%2 == 0): - stripecolor = colors.red - else: - stripecolor = colors.mintcream - redorwhiteline = Rect(0, (s-(stripeheight*stripecounter)), width=s*2, height=stripeheight, - fillColor = stripecolor, strokeColor = None, strokeWidth=20) - g.add(redorwhiteline) - - bluebox = Rect(0, (s-(stripeheight*7)), width=0.8*s, height=stripeheight*7, - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(bluebox) - - lss = s*0.045 - lss2 = lss/2 - s9 = s/9 - s7 = s/7 - for starxcounter in range(5): - for starycounter in range(4): - ls = Star() - ls.size = lss - ls.x = 0-s/22+lss/2+s7+starxcounter*s7 - ls.fillColor = colors.mintcream - ls.y = s-(starycounter+1)*s9+lss2 - g.add(ls) - - for starxcounter in range(6): - for starycounter in range(5): - ls = Star() - ls.size = lss - ls.x = 0-(s/22)+lss/2+s/14+starxcounter*s7 - ls.fillColor = colors.mintcream - ls.y = s-(starycounter+1)*s9+(s/18)+lss2 - g.add(ls) - return g - - def _Flag_Afghanistan(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - greenbox = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0, - fillColor = colors.limegreen, strokeColor = None, strokeWidth=0) - g.add(greenbox) - - blackbox = Rect(0, 0, width=s*2.0, height=s/3.0, - fillColor = colors.black, strokeColor = None, strokeWidth=0) - g.add(blackbox) - return g - - def _Flag_Austria(self): - s = _size # abbreviate as we will use this a lot - g = Group() - - box = Rect(0, 0, s*2, s, fillColor = colors.mintcream, - strokeColor = colors.black, strokeWidth=0) - g.add(box) - - - redbox1 = Rect(0, 0, width=s*2.0, height=s/3.0, - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(redbox1) - - redbox2 = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0, - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(redbox2) - return g - - def _Flag_Belgium(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.black, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - - box1 = Rect(0, 0, width=(s/3.0)*2.0, height=s, - fillColor = colors.black, strokeColor = None, strokeWidth=0) - g.add(box1) - - box2 = Rect(((s/3.0)*2.0), 0, width=(s/3.0)*2.0, height=s, - fillColor = colors.gold, strokeColor = None, strokeWidth=0) - g.add(box2) - - box3 = Rect(((s/3.0)*4.0), 0, width=(s/3.0)*2.0, height=s, - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(box3) - return g - - def _Flag_China(self): - s = _size - g = Group() - self._width = w = s*1.5 - g.add(Rect(0, 0, w, s, fillColor=colors.red, strokeColor=None, strokeWidth=0)) - - def addStar(x,y,size,angle,g=g,w=s/20,x0=0,y0=s/2): - s = Star() - s.fillColor=colors.yellow - s.angle = angle - s.size = size*w*2 - s.x = x*w+x0 - s.y = y*w+y0 - g.add(s) - - addStar(5,5,3, 0) - addStar(10,1,1,36.86989765) - addStar(12,3,1,8.213210702) - addStar(12,6,1,16.60154960) - addStar(10,8,1,53.13010235) - return g - - def _Flag_Cuba(self): - s = _size - g = Group() - - for i in range(5): - stripe = Rect(0, i*s/5, width=s*2, height=s/5, - fillColor = [colors.darkblue, colors.mintcream][i%2], - strokeColor = None, - strokeWidth=0) - g.add(stripe) - - redwedge = Polygon(points = [ 0, 0, 4*s/5, (s/2), 0, s], - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(redwedge) - - star = Star() - star.x = 2.5*s/10 - star.y = s/2 - star.size = 3*s/10 - star.fillColor = colors.white - g.add(star) - - box = Rect(0, 0, s*2, s, - fillColor = None, - strokeColor = colors.black, - strokeWidth=0) - g.add(box) - - return g - - def _Flag_Denmark(self): - s = _size - g = Group() - self._width = w = s*1.4 - - box = Rect(0, 0, w, s, - fillColor = colors.red, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - whitebox1 = Rect(((s/5)*2), 0, width=s/6, height=s, - fillColor = colors.mintcream, strokeColor = None, strokeWidth=0) - g.add(whitebox1) - - whitebox2 = Rect(0, ((s/2)-(s/12)), width=w, height=s/6, - fillColor = colors.mintcream, strokeColor = None, strokeWidth=0) - g.add(whitebox2) - return g - - def _Flag_Finland(self): - s = _size - g = Group() - - # crossbox specific bits - box = Rect(0, 0, s*2, s, - fillColor = colors.ghostwhite, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - blueline1 = Rect((s*0.6), 0, width=0.3*s, height=s, - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(blueline1) - - blueline2 = Rect(0, (s*0.4), width=s*2, height=s*0.3, - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(blueline2) - return g - - def _Flag_France(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, fillColor = colors.navy, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - bluebox = Rect(0, 0, width=((s/3.0)*2.0), height=s, - fillColor = colors.blue, strokeColor = None, strokeWidth=0) - g.add(bluebox) - - whitebox = Rect(((s/3.0)*2.0), 0, width=((s/3.0)*2.0), height=s, - fillColor = colors.mintcream, strokeColor = None, strokeWidth=0) - g.add(whitebox) - - redbox = Rect(((s/3.0)*4.0), 0, width=((s/3.0)*2.0), height=s, - fillColor = colors.red, - strokeColor = None, - strokeWidth=0) - g.add(redbox) - return g - - def _Flag_Germany(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.gold, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - blackbox1 = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0, - fillColor = colors.black, strokeColor = None, strokeWidth=0) - g.add(blackbox1) - - redbox1 = Rect(0, (s/3.0), width=s*2.0, height=s/3.0, - fillColor = colors.orangered, strokeColor = None, strokeWidth=0) - g.add(redbox1) - return g - - def _Flag_Greece(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, fillColor = colors.gold, - strokeColor = colors.black, strokeWidth=0) - g.add(box) - - for stripecounter in range (9,0, -1): - stripeheight = s/9.0 - if not (stripecounter%2 == 0): - stripecolor = colors.deepskyblue - else: - stripecolor = colors.mintcream - - blueorwhiteline = Rect(0, (s-(stripeheight*stripecounter)), width=s*2, height=stripeheight, - fillColor = stripecolor, strokeColor = None, strokeWidth=20) - g.add(blueorwhiteline) - - bluebox1 = Rect(0, ((s)-stripeheight*5), width=(stripeheight*5), height=stripeheight*5, - fillColor = colors.deepskyblue, strokeColor = None, strokeWidth=0) - g.add(bluebox1) - - whiteline1 = Rect(0, ((s)-stripeheight*3), width=stripeheight*5, height=stripeheight, - fillColor = colors.mintcream, strokeColor = None, strokeWidth=0) - g.add(whiteline1) - - whiteline2 = Rect((stripeheight*2), ((s)-stripeheight*5), width=stripeheight, height=stripeheight*5, - fillColor = colors.mintcream, strokeColor = None, strokeWidth=0) - g.add(whiteline2) - - return g - - def _Flag_Ireland(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.forestgreen, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - whitebox = Rect(((s*2.0)/3.0), 0, width=(2.0*(s*2.0)/3.0), height=s, - fillColor = colors.mintcream, strokeColor = None, strokeWidth=0) - g.add(whitebox) - - orangebox = Rect(((2.0*(s*2.0)/3.0)), 0, width=(s*2.0)/3.0, height=s, - fillColor = colors.darkorange, strokeColor = None, strokeWidth=0) - g.add(orangebox) - return g - - def _Flag_Italy(self): - s = _size - g = Group() - g.add(Rect(0,0,s*2,s,fillColor=colors.forestgreen,strokeColor=None, strokeWidth=0)) - g.add(Rect((2*s)/3, 0, width=(s*4)/3, height=s, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)) - g.add(Rect((4*s)/3, 0, width=(s*2)/3, height=s, fillColor = colors.red, strokeColor = None, strokeWidth=0)) - return g - - def _Flag_Japan(self): - s = _size - g = Group() - w = self._width = s*1.5 - g.add(Rect(0,0,w,s,fillColor=colors.mintcream,strokeColor=None, strokeWidth=0)) - g.add(Circle(cx=w/2,cy=s/2,r=0.3*w,fillColor=colors.red,strokeColor=None, strokeWidth=0)) - return g - - def _Flag_Luxembourg(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - redbox = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0, - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(redbox) - - bluebox = Rect(0, 0, width=s*2.0, height=s/3.0, - fillColor = colors.dodgerblue, strokeColor = None, strokeWidth=0) - g.add(bluebox) - return g - - def _Flag_Holland(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.mintcream, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - redbox = Rect(0, ((s/3.0)*2.0), width=s*2.0, height=s/3.0, - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(redbox) - - bluebox = Rect(0, 0, width=s*2.0, height=s/3.0, - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(bluebox) - return g - - def _Flag_Portugal(self): - return Group() - - def _Flag_Russia(self): - s = _size - g = Group() - w = self._width = s*1.5 - t = s/3 - g.add(Rect(0, 0, width=w, height=t, fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Rect(0, t, width=w, height=t, fillColor = colors.blue, strokeColor = None, strokeWidth=0)) - g.add(Rect(0, 2*t, width=w, height=t, fillColor = colors.mintcream, strokeColor = None, strokeWidth=0)) - return g - - def _Flag_Spain(self): - s = _size - g = Group() - w = self._width = s*1.5 - g.add(Rect(0, 0, width=w, height=s, fillColor = colors.red, strokeColor = None, strokeWidth=0)) - g.add(Rect(0, (s/4), width=w, height=s/2, fillColor = colors.yellow, strokeColor = None, strokeWidth=0)) - return g - - def _Flag_Sweden(self): - s = _size - g = Group() - self._width = s*1.4 - box = Rect(0, 0, self._width, s, - fillColor = colors.dodgerblue, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - box1 = Rect(((s/5)*2), 0, width=s/6, height=s, - fillColor = colors.gold, strokeColor = None, strokeWidth=0) - g.add(box1) - - box2 = Rect(0, ((s/2)-(s/12)), width=self._width, height=s/6, - fillColor = colors.gold, - strokeColor = None, - strokeWidth=0) - g.add(box2) - return g - - def _Flag_Norway(self): - s = _size - g = Group() - self._width = s*1.4 - - box = Rect(0, 0, self._width, s, - fillColor = colors.red, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - box = Rect(0, 0, self._width, s, - fillColor = colors.red, strokeColor = colors.black, strokeWidth=0) - g.add(box) - - whiteline1 = Rect(((s*0.2)*2), 0, width=s*0.2, height=s, - fillColor = colors.ghostwhite, strokeColor = None, strokeWidth=0) - g.add(whiteline1) - - whiteline2 = Rect(0, (s*0.4), width=self._width, height=s*0.2, - fillColor = colors.ghostwhite, strokeColor = None, strokeWidth=0) - g.add(whiteline2) - - blueline1 = Rect(((s*0.225)*2), 0, width=0.1*s, height=s, - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(blueline1) - - blueline2 = Rect(0, (s*0.45), width=self._width, height=s*0.1, - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(blueline2) - return g - - def _Flag_CzechRepublic(self): - s = _size - g = Group() - box = Rect(0, 0, s*2, s, - fillColor = colors.mintcream, - strokeColor = colors.black, - strokeWidth=0) - g.add(box) - - redbox = Rect(0, 0, width=s*2, height=s/2, - fillColor = colors.red, - strokeColor = None, - strokeWidth=0) - g.add(redbox) - - bluewedge = Polygon(points = [ 0, 0, s, (s/2), 0, s], - fillColor = colors.darkblue, strokeColor = None, strokeWidth=0) - g.add(bluewedge) - return g - - def _Flag_Palestine(self): - s = _size - g = Group() - box = Rect(0, s/3, s*2, s/3, - fillColor = colors.mintcream, - strokeColor = None, - strokeWidth=0) - g.add(box) - - greenbox = Rect(0, 0, width=s*2, height=s/3, - fillColor = colors.limegreen, - strokeColor = None, - strokeWidth=0) - g.add(greenbox) - - blackbox = Rect(0, 2*s/3, width=s*2, height=s/3, - fillColor = colors.black, - strokeColor = None, - strokeWidth=0) - g.add(blackbox) - - redwedge = Polygon(points = [ 0, 0, 2*s/3, (s/2), 0, s], - fillColor = colors.red, strokeColor = None, strokeWidth=0) - g.add(redwedge) - return g - - def _Flag_Turkey(self): - s = _size - g = Group() - - box = Rect(0, 0, s*2, s, - fillColor = colors.red, - strokeColor = colors.black, - strokeWidth=0) - g.add(box) - - whitecircle = Circle(cx=((s*0.35)*2), cy=s/2, r=s*0.3, - fillColor = colors.mintcream, - strokeColor = None, - strokeWidth=0) - g.add(whitecircle) - - redcircle = Circle(cx=((s*0.39)*2), cy=s/2, r=s*0.24, - fillColor = colors.red, - strokeColor = None, - strokeWidth=0) - g.add(redcircle) - - ws = Star() - ws.angle = 15 - ws.size = s/5 - ws.x = (s*0.5)*2+ws.size/2 - ws.y = (s*0.5) - ws.fillColor = colors.mintcream - ws.strokeColor = None - g.add(ws) - return g - - def _Flag_Switzerland(self): - s = _size - g = Group() - self._width = s - - g.add(Rect(0, 0, s, s, fillColor = colors.red, strokeColor = colors.black, strokeWidth=0)) - g.add(Line((s/2), (s/5.5), (s/2), (s-(s/5.5)), - fillColor = colors.mintcream, strokeColor = colors.mintcream, strokeWidth=(s/5))) - g.add(Line((s/5.5), (s/2), (s-(s/5.5)), (s/2), - fillColor = colors.mintcream, strokeColor = colors.mintcream, strokeWidth=s/5)) - return g - - def _Flag_EU(self): - s = _size - g = Group() - w = self._width = 1.5*s - - g.add(Rect(0, 0, w, s, fillColor = colors.darkblue, strokeColor = None, strokeWidth=0)) - centerx=w/2 - centery=s/2 - radius=s/3 - yradius = radius - xradius = radius - nStars = 12 - delta = 2*pi/nStars - for i in range(nStars): - rad = i*delta - gs = Star() - gs.x=cos(rad)*radius+centerx - gs.y=sin(rad)*radius+centery - gs.size=s/10 - gs.fillColor=colors.gold - g.add(gs) - return g - - def _Flag_Brazil(self): - s = _size # abbreviate as we will use this a lot - g = Group() - - m = s/14 - self._width = w = (m * 20) - - def addStar(x,y,size, g=g, w=w, s=s, m=m): - st = Star() - st.fillColor=colors.mintcream - st.size = size*m - st.x = (w/2) + (x * (0.35 * m)) - st.y = (s/2) + (y * (0.35 * m)) - g.add(st) - - g.add(Rect(0, 0, w, s, fillColor = colors.green, strokeColor = None, strokeWidth=0)) - g.add(Polygon(points = [ 1.7*m, (s/2), (w/2), s-(1.7*m), w-(1.7*m),(s/2),(w/2), 1.7*m], - fillColor = colors.yellow, strokeColor = None, strokeWidth=0)) - g.add(Circle(cx=w/2, cy=s/2, r=3.5*m, - fillColor=colors.blue,strokeColor=None, strokeWidth=0)) - g.add(Wedge((w/2)-(2*m), 0, 8.5*m, 50, 98.1, 8.5*m, - fillColor=colors.mintcream,strokeColor=None, strokeWidth=0)) - g.add(Wedge((w/2), (s/2), 3.501*m, 156, 352, 3.501*m, - fillColor=colors.mintcream,strokeColor=None, strokeWidth=0)) - g.add(Wedge((w/2)-(2*m), 0, 8*m, 48.1, 100, 8*m, - fillColor=colors.blue,strokeColor=None, strokeWidth=0)) - g.add(Rect(0, 0, w, (s/4) + 1.7*m, - fillColor = colors.green, strokeColor = None, strokeWidth=0)) - g.add(Polygon(points = [ 1.7*m,(s/2), (w/2),s/2 - 2*m, w-(1.7*m),(s/2) , (w/2),1.7*m], - fillColor = colors.yellow, strokeColor = None, strokeWidth=0)) - g.add(Wedge(w/2, s/2, 3.502*m, 166, 342.1, 3.502*m, - fillColor=colors.blue,strokeColor=None, strokeWidth=0)) - - addStar(3.2,3.5,0.3) - addStar(-8.5,1.5,0.3) - addStar(-7.5,-3,0.3) - addStar(-4,-5.5,0.3) - addStar(0,-4.5,0.3) - addStar(7,-3.5,0.3) - addStar(-3.5,-0.5,0.25) - addStar(0,-1.5,0.25) - addStar(1,-2.5,0.25) - addStar(3,-7,0.25) - addStar(5,-6.5,0.25) - addStar(6.5,-5,0.25) - addStar(7,-4.5,0.25) - addStar(-5.5,-3.2,0.25) - addStar(-6,-4.2,0.25) - addStar(-1,-2.75,0.2) - addStar(2,-5.5,0.2) - addStar(4,-5.5,0.2) - addStar(5,-7.5,0.2) - addStar(5,-5.5,0.2) - addStar(6,-5.5,0.2) - addStar(-8.8,-3.2,0.2) - addStar(2.5,0.5,0.2) - addStar(-0.2,-3.2,0.14) - addStar(-7.2,-2,0.14) - addStar(0,-8,0.1) - - sTmp = "ORDEM E PROGRESSO" - nTmp = len(sTmp) - delta = 0.850848010347/nTmp - radius = 7.9 *m - centerx = (w/2)-(2*m) - centery = 0 - for i in range(nTmp): - rad = 2*pi - i*delta -4.60766922527 - x=cos(rad)*radius+centerx - y=sin(rad)*radius+centery - if i == 6: - z = 0.35*m - else: - z= 0.45*m - g2 = Group(String(x, y, sTmp[i], fontName='Helvetica-Bold', - fontSize = z,strokeColor=None,fillColor=colors.green)) - g2.rotate(rad) - g.add(g2) - return g - -def makeFlag(name): - flag = Flag() - flag.kind = name - return flag - -def test(): - """This function produces three pdf files with examples of all the signs and symbols from this file. - """ -# page 1 - - labelFontSize = 10 - - X = (20,245) - - flags = [ - 'UK', - 'USA', - 'Afghanistan', - 'Austria', - 'Belgium', - 'Denmark', - 'Cuba', - 'Finland', - 'France', - 'Germany', - 'Greece', - 'Ireland', - 'Italy', - 'Luxembourg', - 'Holland', - 'Palestine', - 'Portugal', - 'Spain', - 'Sweden', - 'Norway', - 'CzechRepublic', - 'Turkey', - 'Switzerland', - 'EU', - 'Brazil', - ] - y = Y0 = 530 - f = 0 - D = None - for name in flags: - if not D: D = Drawing(450,650) - flag = makeFlag(name) - i = flags.index(name) - flag.x = X[i%2] - flag.y = y - D.add(flag) - D.add(String(flag.x+(flag.size/2),(flag.y-(1.2*labelFontSize)), - name, fillColor=colors.black, textAnchor='middle', fontSize=labelFontSize)) - if i%2: y = y - 125 - if (i%2 and y<0) or name==flags[-1]: - renderPDF.drawToFile(D, 'flags%02d.pdf'%f, 'flags.py - Page #%d'%(f+1)) - y = Y0 - f = f+1 - D = None - -if __name__=='__main__': - test() diff --git a/bin/reportlab/graphics/widgets/grids.py b/bin/reportlab/graphics/widgets/grids.py deleted file mode 100644 index ff859285907..00000000000 --- a/bin/reportlab/graphics/widgets/grids.py +++ /dev/null @@ -1,504 +0,0 @@ -#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/graphics/widgets/grids.py -__version__=''' $Id$ ''' - -from reportlab.lib import colors -from reportlab.lib.validators import isNumber, isColorOrNone, isBoolean, isListOfNumbers, OneOf, isListOfColors -from reportlab.lib.attrmap import AttrMap, AttrMapValue -from reportlab.graphics.shapes import Drawing, Group, Line, Rect, LineShape, definePath, EmptyClipPath -from reportlab.graphics.widgetbase import Widget - -def frange(start, end=None, inc=None): - "A range function, that does accept float increments..." - - if end == None: - end = start + 0.0 - start = 0.0 - - if inc == None: - inc = 1.0 - - L = [] - end = end - inc*0.0001 #to avoid numrical problems - while 1: - next = start + len(L) * inc - if inc > 0 and next >= end: - break - elif inc < 0 and next <= end: - break - L.append(next) - - return L - - -def makeDistancesList(list): - """Returns a list of distances between adjacent numbers in some input list. - - E.g. [1, 1, 2, 3, 5, 7] -> [0, 1, 1, 2, 2] - """ - - d = [] - for i in range(len(list[:-1])): - d.append(list[i+1] - list[i]) - - return d - - -class Grid(Widget): - """This makes a rectangular grid of equidistant stripes. - - The grid contains an outer border rectangle, and stripes - inside which can be drawn with lines and/or as solid tiles. - The drawing order is: outer rectangle, then lines and tiles. - - The stripes' width is indicated as 'delta'. The sequence of - stripes can have an offset named 'delta0'. Both values need - to be positive! - """ - - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc="The grid's lower-left x position."), - y = AttrMapValue(isNumber, desc="The grid's lower-left y position."), - width = AttrMapValue(isNumber, desc="The grid's width."), - height = AttrMapValue(isNumber, desc="The grid's height."), - orientation = AttrMapValue(OneOf(('vertical', 'horizontal')), - desc='Determines if stripes are vertical or horizontal.'), - useLines = AttrMapValue(OneOf((0, 1)), - desc='Determines if stripes are drawn with lines.'), - useRects = AttrMapValue(OneOf((0, 1)), - desc='Determines if stripes are drawn with solid rectangles.'), - delta = AttrMapValue(isNumber, - desc='Determines the width/height of the stripes.'), - delta0 = AttrMapValue(isNumber, - desc='Determines the stripes initial width/height offset.'), - deltaSteps = AttrMapValue(isListOfNumbers, - desc='List of deltas to be used cyclically.'), - stripeColors = AttrMapValue(isListOfColors, - desc='Colors applied cyclically in the right or upper direction.'), - fillColor = AttrMapValue(isColorOrNone, - desc='Background color for entire rectangle.'), - strokeColor = AttrMapValue(isColorOrNone, - desc='Color used for lines.'), - strokeWidth = AttrMapValue(isNumber, - desc='Width used for lines.'), - rectStrokeColor = AttrMapValue(isColorOrNone, desc='Color for outer rect stroke.'), - rectStrokeWidth = AttrMapValue(isColorOrNone, desc='Width for outer rect stroke.'), - ) - - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 100 - self.height = 100 - self.orientation = 'vertical' - self.useLines = 0 - self.useRects = 1 - self.delta = 20 - self.delta0 = 0 - self.deltaSteps = [] - self.fillColor = colors.white - self.stripeColors = [colors.red, colors.green, colors.blue] - self.strokeColor = colors.black - self.strokeWidth = 2 - - - def demo(self): - D = Drawing(100, 100) - - g = Grid() - D.add(g) - - return D - - def makeOuterRect(self): - strokeColor = getattr(self,'rectStrokeColor',self.strokeColor) - strokeWidth = getattr(self,'rectStrokeWidth',self.strokeWidth) - if self.fillColor or (strokeColor and strokeWidth): - rect = Rect(self.x, self.y, self.width, self.height) - rect.fillColor = self.fillColor - rect.strokeColor = strokeColor - rect.strokeWidth = strokeWidth - return rect - else: - return None - - def makeLinePosList(self, start, isX=0): - "Returns a list of positions where to place lines." - - w, h = self.width, self.height - if isX: - length = w - else: - length = h - if self.deltaSteps: - r = [start + self.delta0] - i = 0 - while 1: - if r[-1] > start + length: - del r[-1] - break - r.append(r[-1] + self.deltaSteps[i % len(self.deltaSteps)]) - i = i + 1 - else: - r = frange(start + self.delta0, start + length, self.delta) - - r.append(start + length) - if self.delta0 != 0: - r.insert(0, start) - #print 'Grid.makeLinePosList() -> %s' % r - return r - - - def makeInnerLines(self): - # inner grid lines - group = Group() - - w, h = self.width, self.height - - if self.useLines == 1: - if self.orientation == 'vertical': - r = self.makeLinePosList(self.x, isX=1) - for x in r: - line = Line(x, self.y, x, self.y + h) - line.strokeColor = self.strokeColor - line.strokeWidth = self.strokeWidth - group.add(line) - elif self.orientation == 'horizontal': - r = self.makeLinePosList(self.y, isX=0) - for y in r: - line = Line(self.x, y, self.x + w, y) - line.strokeColor = self.strokeColor - line.strokeWidth = self.strokeWidth - group.add(line) - - return group - - - def makeInnerTiles(self): - # inner grid lines - group = Group() - - w, h = self.width, self.height - - # inner grid stripes (solid rectangles) - if self.useRects == 1: - cols = self.stripeColors - - if self.orientation == 'vertical': - r = self.makeLinePosList(self.x, isX=1) - elif self.orientation == 'horizontal': - r = self.makeLinePosList(self.y, isX=0) - - dist = makeDistancesList(r) - - i = 0 - for j in range(len(dist)): - if self.orientation == 'vertical': - x = r[j] - stripe = Rect(x, self.y, dist[j], h) - elif self.orientation == 'horizontal': - y = r[j] - stripe = Rect(self.x, y, w, dist[j]) - stripe.fillColor = cols[i % len(cols)] - stripe.strokeColor = None - group.add(stripe) - i = i + 1 - - return group - - - def draw(self): - # general widget bits - group = Group() - - group.add(self.makeOuterRect()) - group.add(self.makeInnerTiles()) - group.add(self.makeInnerLines(),name='_gridLines') - - return group - - -class DoubleGrid(Widget): - """This combines two ordinary Grid objects orthogonal to each other. - """ - - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc="The grid's lower-left x position."), - y = AttrMapValue(isNumber, desc="The grid's lower-left y position."), - width = AttrMapValue(isNumber, desc="The grid's width."), - height = AttrMapValue(isNumber, desc="The grid's height."), - grid0 = AttrMapValue(None, desc="The first grid component."), - grid1 = AttrMapValue(None, desc="The second grid component."), - ) - - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 100 - self.height = 100 - - g0 = Grid() - g0.x = self.x - g0.y = self.y - g0.width = self.width - g0.height = self.height - g0.orientation = 'vertical' - g0.useLines = 1 - g0.useRects = 0 - g0.delta = 20 - g0.delta0 = 0 - g0.deltaSteps = [] - g0.fillColor = colors.white - g0.stripeColors = [colors.red, colors.green, colors.blue] - g0.strokeColor = colors.black - g0.strokeWidth = 1 - - g1 = Grid() - g1.x = self.x - g1.y = self.y - g1.width = self.width - g1.height = self.height - g1.orientation = 'horizontal' - g1.useLines = 1 - g1.useRects = 0 - g1.delta = 20 - g1.delta0 = 0 - g1.deltaSteps = [] - g1.fillColor = colors.white - g1.stripeColors = [colors.red, colors.green, colors.blue] - g1.strokeColor = colors.black - g1.strokeWidth = 1 - - self.grid0 = g0 - self.grid1 = g1 - - -## # This gives an AttributeError: -## # DoubleGrid instance has no attribute 'grid0' -## def __setattr__(self, name, value): -## if name in ('x', 'y', 'width', 'height'): -## setattr(self.grid0, name, value) -## setattr(self.grid1, name, value) - - - def demo(self): - D = Drawing(100, 100) - g = DoubleGrid() - D.add(g) - return D - - - def draw(self): - group = Group() - g0, g1 = self.grid0, self.grid1 - # Order groups to make sure both v and h lines - # are visible (works only when there is only - # one kind of stripes, v or h). - G = g0.useRects == 1 and g1.useRects == 0 and (g0,g1) or (g1,g0) - for g in G: - group.add(g.makeOuterRect()) - for g in G: - group.add(g.makeInnerTiles()) - group.add(g.makeInnerLines(),name='_gridLines') - - return group - - -class ShadedRect(Widget): - """This makes a rectangle with shaded colors between two colors. - - Colors are interpolated linearly between 'fillColorStart' - and 'fillColorEnd', both of which appear at the margins. - If 'numShades' is set to one, though, only 'fillColorStart' - is used. - """ - - _attrMap = AttrMap( - x = AttrMapValue(isNumber, desc="The grid's lower-left x position."), - y = AttrMapValue(isNumber, desc="The grid's lower-left y position."), - width = AttrMapValue(isNumber, desc="The grid's width."), - height = AttrMapValue(isNumber, desc="The grid's height."), - orientation = AttrMapValue(OneOf(('vertical', 'horizontal')), desc='Determines if stripes are vertical or horizontal.'), - numShades = AttrMapValue(isNumber, desc='The number of interpolating colors.'), - fillColorStart = AttrMapValue(isColorOrNone, desc='Start value of the color shade.'), - fillColorEnd = AttrMapValue(isColorOrNone, desc='End value of the color shade.'), - strokeColor = AttrMapValue(isColorOrNone, desc='Color used for border line.'), - strokeWidth = AttrMapValue(isNumber, desc='Width used for lines.'), - cylinderMode = AttrMapValue(isBoolean, desc='True if shading reverses in middle.'), - ) - - def __init__(self,**kw): - self.x = 0 - self.y = 0 - self.width = 100 - self.height = 100 - self.orientation = 'vertical' - self.numShades = 20 - self.fillColorStart = colors.pink - self.fillColorEnd = colors.black - self.strokeColor = colors.black - self.strokeWidth = 2 - self.cylinderMode = 0 - self.setProperties(kw) - - def demo(self): - D = Drawing(100, 100) - g = ShadedRect() - D.add(g) - - return D - - def _flipRectCorners(self): - "Flip rectangle's corners if width or height is negative." - x, y, width, height, fillColorStart, fillColorEnd = self.x, self.y, self.width, self.height, self.fillColorStart, self.fillColorEnd - if width < 0 and height > 0: - x = x + width - width = -width - if self.orientation=='vertical': fillColorStart, fillColorEnd = fillColorEnd, fillColorStart - elif height<0 and width>0: - y = y + height - height = -height - if self.orientation=='horizontal': fillColorStart, fillColorEnd = fillColorEnd, fillColorStart - elif height < 0 and height < 0: - x = x + width - width = -width - y = y + height - height = -height - return x, y, width, height, fillColorStart, fillColorEnd - - def draw(self): - # general widget bits - group = Group() - x, y, w, h, c0, c1 = self._flipRectCorners() - numShades = self.numShades - if self.cylinderMode: - if not numShades%2: numShades = numShades+1 - halfNumShades = (numShades-1)/2 + 1 - num = float(numShades) # must make it float! - vertical = self.orientation == 'vertical' - if vertical: - if numShades == 1: - V = [x] - else: - V = frange(x, x + w, w/num) - else: - if numShades == 1: - V = [y] - else: - V = frange(y, y + h, h/num) - - for v in V: - stripe = vertical and Rect(v, y, w/num, h) or Rect(x, v, w, h/num) - if self.cylinderMode: - if V.index(v)>=halfNumShades: - col = colors.linearlyInterpolatedColor(c1,c0,V[halfNumShades],V[-1], v) - else: - col = colors.linearlyInterpolatedColor(c0,c1,V[0],V[halfNumShades], v) - else: - col = colors.linearlyInterpolatedColor(c0,c1,V[0],V[-1], v) - stripe.fillColor = col - stripe.strokeColor = col - stripe.strokeWidth = 1 - group.add(stripe) - if self.strokeColor and self.strokeWidth>=0: - rect = Rect(x, y, w, h) - rect.strokeColor = self.strokeColor - rect.strokeWidth = self.strokeWidth - rect.fillColor = None - group.add(rect) - return group - - -def colorRange(c0, c1, n): - "Return a range of intermediate colors between c0 and c1" - if n==1: return [c0] - - C = [] - if n>1: - lim = n-1 - for i in range(n): - C.append(colors.linearlyInterpolatedColor(c0,c1,0,lim, i)) - return C - - -def centroid(P): - '''compute average point of a set of points''' - return reduce(lambda x,y, fn=float(len(P)): (x[0]+y[0]/fn,x[1]+y[1]/fn),P,(0,0)) - -def rotatedEnclosingRect(P, angle, rect): - ''' - given P a sequence P of x,y coordinate pairs and an angle in degrees - find the centroid of P and the axis at angle theta through it - find the extreme points of P wrt axis parallel distance and axis - orthogonal distance. Then compute the least rectangle that will still - enclose P when rotated by angle. - - The class R - ''' - from math import pi, cos, sin, tan - x0, y0 = centroid(P) - theta = (angle/180.)*pi - s,c=sin(theta),cos(theta) - def parallelAxisDist((x,y),s=s,c=c,x0=x0,y0=y0): - return (s*(y-y0)+c*(x-x0)) - def orthogonalAxisDist((x,y),s=s,c=c,x0=x0,y0=y0): - return (c*(y-y0)+s*(x-x0)) - L = map(parallelAxisDist,P) - L.sort() - a0, a1 = L[0], L[-1] - L = map(orthogonalAxisDist,P) - L.sort() - b0, b1 = L[0], L[-1] - rect.x, rect.width = a0, a1-a0 - rect.y, rect.height = b0, b1-b0 - g = Group(transform=(c,s,-s,c,x0,y0)) - g.add(rect) - return g - -class ShadedPolygon(Widget,LineShape): - _attrMap = AttrMap(BASE=LineShape, - angle = AttrMapValue(isNumber,desc="Shading angle"), - fillColorStart = AttrMapValue(isColorOrNone), - fillColorEnd = AttrMapValue(isColorOrNone), - numShades = AttrMapValue(isNumber, desc='The number of interpolating colors.'), - cylinderMode = AttrMapValue(isBoolean, desc='True if shading reverses in middle.'), - points = AttrMapValue(isListOfNumbers), - ) - - def __init__(self,**kw): - self.angle = 90 - self.fillColorStart = colors.red - self.fillColorEnd = colors.green - self.cylinderMode = 0 - self.numShades = 50 - self.points = [-1,-1,2,2,3,-1] - LineShape.__init__(self,kw) - - def draw(self): - P = self.points - P = map(lambda i, P=P:(P[i],P[i+1]),xrange(0,len(P),2)) - path = definePath([('moveTo',)+P[0]]+map(lambda x: ('lineTo',)+x,P[1:])+['closePath'], - fillColor=None, strokeColor=None) - path.isClipPath = 1 - g = Group() - g.add(path) - rect = ShadedRect(strokeWidth=0,strokeColor=None) - for k in 'fillColorStart', 'fillColorEnd', 'numShades', 'cylinderMode': - setattr(rect,k,getattr(self,k)) - g.add(rotatedEnclosingRect(P, self.angle, rect)) - g.add(EmptyClipPath) - path = path.copy() - path.isClipPath = 0 - path.strokeColor = self.strokeColor - path.strokeWidth = self.strokeWidth - g.add(path) - return g - -if __name__=='__main__': #noruntests - from reportlab.lib.colors import blue - from reportlab.graphics.shapes import Drawing - angle=45 - D = Drawing(120,120) - D.add(ShadedPolygon(points=(10,10,60,60,110,10),strokeColor=None,strokeWidth=1,angle=90,numShades=50,cylinderMode=0)) - D.save(formats=['gif'],fnRoot='shobj',outDir='/tmp') diff --git a/bin/reportlab/graphics/widgets/markers.py b/bin/reportlab/graphics/widgets/markers.py deleted file mode 100644 index 4399dfe9a67..00000000000 --- a/bin/reportlab/graphics/widgets/markers.py +++ /dev/null @@ -1,228 +0,0 @@ -#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/graphics/widgets/markers.py -""" -This modules defines a collection of markers used in charts. -""" -__version__=''' $Id$ ''' -from types import FunctionType, ClassType -from reportlab.graphics.shapes import Rect, Line, Circle, Polygon, Drawing, Group -from reportlab.graphics.widgets.signsandsymbols import SmileyFace -from reportlab.graphics.widgetbase import Widget -from reportlab.lib.validators import isNumber, isColorOrNone, OneOf, Validator -from reportlab.lib.attrmap import AttrMap, AttrMapValue -from reportlab.lib.colors import black -from reportlab.graphics.widgets.flags import Flag -from math import sin, cos, pi -import copy, new -_toradians = pi/180.0 - -class Marker(Widget): - '''A polymorphic class of markers''' - _attrMap = AttrMap(BASE=Widget, - kind = AttrMapValue( - OneOf(None, 'Square', 'Diamond', 'Circle', 'Cross', 'Triangle', 'StarSix', - 'Pentagon', 'Hexagon', 'Heptagon', 'Octagon', 'StarFive', - 'FilledSquare', 'FilledCircle', 'FilledDiamond', 'FilledCross', - 'FilledTriangle','FilledStarSix', 'FilledPentagon', 'FilledHexagon', - 'FilledHeptagon', 'FilledOctagon', 'FilledStarFive', - 'Smiley'), - desc='marker type name'), - size = AttrMapValue(isNumber,desc='marker size'), - x = AttrMapValue(isNumber,desc='marker x coordinate'), - y = AttrMapValue(isNumber,desc='marker y coordinate'), - dx = AttrMapValue(isNumber,desc='marker x coordinate adjustment'), - dy = AttrMapValue(isNumber,desc='marker y coordinate adjustment'), - angle = AttrMapValue(isNumber,desc='marker rotation'), - fillColor = AttrMapValue(isColorOrNone, desc='marker fill colour'), - strokeColor = AttrMapValue(isColorOrNone, desc='marker stroke colour'), - strokeWidth = AttrMapValue(isNumber, desc='marker stroke width'), - ) - - def __init__(self,*args,**kw): - self.kind = None - self.strokeColor = black - self.strokeWidth = 0.1 - self.fillColor = None - self.size = 5 - self.x = self.y = self.dx = self.dy = self.angle = 0 - self.setProperties(kw) - - def clone(self): - return new.instance(self.__class__,self.__dict__.copy()) - - def _Smiley(self): - x, y = self.x+self.dx, self.y+self.dy - d = self.size/2.0 - s = SmileyFace() - s.fillColor = self.fillColor - s.strokeWidth = self.strokeWidth - s.strokeColor = self.strokeColor - s.x = x-d - s.y = y-d - s.size = d*2 - return s - - def _Square(self): - x, y = self.x+self.dx, self.y+self.dy - d = self.size/2.0 - s = Rect(x-d,y-d,2*d,2*d,fillColor=self.fillColor,strokeColor=self.strokeColor,strokeWidth=self.strokeWidth) - return s - - def _Diamond(self): - d = self.size/2.0 - return self._doPolygon((-d,0,0,d,d,0,0,-d)) - - def _Circle(self): - x, y = self.x+self.dx, self.y+self.dy - s = Circle(x,y,self.size/2.0,fillColor=self.fillColor,strokeColor=self.strokeColor,strokeWidth=self.strokeWidth) - return s - - def _Cross(self): - x, y = self.x+self.dx, self.y+self.dy - s = float(self.size) - h, s = s/2, s/6 - return self._doPolygon((-s,-h,-s,-s,-h,-s,-h,s,-s,s,-s,h,s,h,s,s,h,s,h,-s,s,-s,s,-h)) - - def _Triangle(self): - x, y = self.x+self.dx, self.y+self.dy - r = float(self.size)/2 - c = 30*_toradians - s = sin(30*_toradians)*r - c = cos(c)*r - return self._doPolygon((0,r,-c,-s,c,-s)) - - def _StarSix(self): - r = float(self.size)/2 - c = 30*_toradians - s = sin(c)*r - c = cos(c)*r - z = s/2 - g = c/2 - return self._doPolygon((0,r,-z,s,-c,s,-s,0,-c,-s,-z,-s,0,-r,z,-s,c,-s,s,0,c,s,z,s)) - - def _StarFive(self): - R = float(self.size)/2 - r = R*sin(18*_toradians)/cos(36*_toradians) - P = [] - angle = 90 - for i in xrange(5): - for radius in R, r: - theta = angle*_toradians - P.append(radius*cos(theta)) - P.append(radius*sin(theta)) - angle = angle + 36 - return self._doPolygon(P) - - def _Pentagon(self): - return self._doNgon(5) - - def _Hexagon(self): - return self._doNgon(6) - - def _Heptagon(self): - return self._doNgon(7) - - def _Octagon(self): - return self._doNgon(8) - - def _doPolygon(self,P): - x, y = self.x+self.dx, self.y+self.dy - if x or y: P = map(lambda i,P=P,A=[x,y]: P[i] + A[i&1], range(len(P))) - return Polygon(P, strokeWidth =self.strokeWidth, strokeColor=self.strokeColor, fillColor=self.fillColor) - - def _doFill(self): - old = self.fillColor - if old is None: - self.fillColor = self.strokeColor - r = (self.kind and getattr(self,'_'+self.kind[6:]) or Group)() - self.fillColor = old - return r - - def _doNgon(self,n): - P = [] - size = float(self.size)/2 - for i in xrange(n): - r = (2.*i/n+0.5)*pi - P.append(size*cos(r)) - P.append(size*sin(r)) - return self._doPolygon(P) - - _FilledCircle = _doFill - _FilledSquare = _doFill - _FilledDiamond = _doFill - _FilledCross = _doFill - _FilledTriangle = _doFill - _FilledStarSix = _doFill - _FilledPentagon = _doFill - _FilledHexagon = _doFill - _FilledHeptagon = _doFill - _FilledOctagon = _doFill - _FilledStarFive = _doFill - - def draw(self): - if self.kind: - m = getattr(self,'_'+self.kind) - if self.angle: - _x, _dx, _y, _dy = self.x, self.dx, self.y, self.dy - self.x, self.dx, self.y, self.dy = 0,0,0,0 - try: - m = m() - finally: - self.x, self.dx, self.y, self.dy = _x, _dx, _y, _dy - if not isinstance(m,Group): - _m, m = m, Group() - m.add(_m) - if self.angle: m.rotate(self.angle) - x, y = _x+_dx, _y+_dy - if x or y: m.shift(x,y) - else: - m = m() - else: - m = Group() - return m - -def uSymbol2Symbol(uSymbol,x,y,color): - if type(uSymbol) == FunctionType: - symbol = uSymbol(x, y, 5, color) - elif type(uSymbol) == ClassType and issubclass(uSymbol,Widget): - size = 10. - symbol = uSymbol() - symbol.x = x - (size/2) - symbol.y = y - (size/2) - try: - symbol.size = size - symbol.color = color - except: - pass - elif isinstance(uSymbol,Marker) or isinstance(uSymbol,Flag): - symbol = uSymbol.clone() - if isinstance(uSymbol,Marker): symbol.fillColor = symbol.fillColor or color - symbol.x, symbol.y = x, y - else: - symbol = None - return symbol - -class _isSymbol(Validator): - def test(self,x): - return callable(x) or isinstance(x,Marker) or isinstance(x,Flag) \ - or (type(x)==ClassType and issubclass(x,Widget)) - -isSymbol = _isSymbol() - -def makeMarker(name,**kw): - if Marker._attrMap['kind'].validate(name): - m = apply(Marker,(),kw) - m.kind = name - elif name[-5:]=='_Flag' and Flag._attrMap['kind'].validate(name[:-5]): - m = apply(Flag,(),kw) - m.kind = name[:-5] - m.size = 10 - else: - raise ValueError, "Invalid marker name %s" % name - return m - -if __name__=='__main__': - D = Drawing() - D.add(Marker()) - D.save(fnRoot='Marker',formats=['pdf'], outDir='/tmp') diff --git a/bin/reportlab/graphics/widgets/signsandsymbols.py b/bin/reportlab/graphics/widgets/signsandsymbols.py deleted file mode 100644 index 743b02be748..00000000000 --- a/bin/reportlab/graphics/widgets/signsandsymbols.py +++ /dev/null @@ -1,919 +0,0 @@ -#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/graphics/widgets/signsandsymbols.py -# signsandsymbols.py -# A collection of new widgets -# author: John Precedo (johnp@reportlab.com) -"""This file is a collection of widgets to produce some common signs and symbols. - -Widgets include: -- ETriangle (an equilateral triangle), -- RTriangle (a right angled triangle), -- Octagon, -- Crossbox, -- Tickbox, -- SmileyFace, -- StopSign, -- NoEntry, -- NotAllowed (the red roundel from 'no smoking' signs), -- NoSmoking, -- DangerSign (a black exclamation point in a yellow triangle), -- YesNo (returns a tickbox or a crossbox depending on a testvalue), -- FloppyDisk, -- ArrowOne, and -- ArrowTwo -""" -__version__=''' $Id$ ''' - -from reportlab.lib import colors -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * -from reportlab.graphics import shapes -from reportlab.graphics.widgetbase import Widget -from reportlab.graphics import renderPDF - - -class _Symbol(Widget): - """Abstract base widget - possible attributes: - 'x', 'y', 'size', 'fillColor', 'strokeColor' - """ - _nodoc = 1 - _attrMap = AttrMap( - x = AttrMapValue(isNumber,desc='symbol x coordinate'), - y = AttrMapValue(isNumber,desc='symbol y coordinate'), - dx = AttrMapValue(isNumber,desc='symbol x coordinate adjustment'), - dy = AttrMapValue(isNumber,desc='symbol x coordinate adjustment'), - size = AttrMapValue(isNumber), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - strokeWidth = AttrMapValue(isNumber), - ) - def __init__(self): - assert self.__class__.__name__!='_Symbol', 'Abstract class _Symbol instantiated' - self.x = self.y = self.dx = self.dy = 0 - self.size = 100 - self.fillColor = colors.red - self.strokeColor = None - self.strokeWidth = 0.1 - - def demo(self): - D = shapes.Drawing(200, 100) - s = float(self.size) - ob = self.__class__() - ob.x=50 - ob.y=0 - ob.draw() - D.add(ob) - D.add(shapes.String(ob.x+(s/2),(ob.y-12), - ob.__class__.__name__, fillColor=colors.black, textAnchor='middle', - fontSize=10)) - return D - -class ETriangle(_Symbol): - """This draws an equilateral triangle.""" - - def __init__(self): - pass #AbstractSymbol - - def draw(self): - # general widget bits - s = float(self.size) # abbreviate as we will use this a lot - g = shapes.Group() - - # Triangle specific bits - ae = s*0.125 #(ae = 'an eighth') - triangle = shapes.Polygon(points = [ - self.x, self.y, - self.x+s, self.y, - self.x+(s/2),self.y+s], - fillColor = self.fillColor, - strokeColor = self.strokeColor, - strokeWidth=s/50.) - g.add(triangle) - return g - -class RTriangle(_Symbol): - """This draws a right-angled triangle. - - possible attributes: - 'x', 'y', 'size', 'fillColor', 'strokeColor' - - """ - - def __init__(self): - self.x = 0 - self.y = 0 - self.size = 100 - self.fillColor = colors.green - self.strokeColor = None - - def draw(self): - # general widget bits - s = float(self.size) # abbreviate as we will use this a lot - g = shapes.Group() - - # Triangle specific bits - ae = s*0.125 #(ae = 'an eighth') - triangle = shapes.Polygon(points = [ - self.x, self.y, - self.x+s, self.y, - self.x,self.y+s], - fillColor = self.fillColor, - strokeColor = self.strokeColor, - strokeWidth=s/50.) - g.add(triangle) - return g - -class Octagon(_Symbol): - """This widget draws an Octagon. - - possible attributes: - 'x', 'y', 'size', 'fillColor', 'strokeColor' - - """ - - def __init__(self): - self.x = 0 - self.y = 0 - self.size = 100 - self.fillColor = colors.yellow - self.strokeColor = None - - def draw(self): - # general widget bits - s = float(self.size) # abbreviate as we will use this a lot - g = shapes.Group() - - # Octagon specific bits - athird=s/3 - - octagon = shapes.Polygon(points=[self.x+athird, self.y, - self.x, self.y+athird, - self.x, self.y+(athird*2), - self.x+athird, self.y+s, - self.x+(athird*2), self.y+s, - self.x+s, self.y+(athird*2), - self.x+s, self.y+athird, - self.x+(athird*2), self.y], - strokeColor = self.strokeColor, - fillColor = self.fillColor, - strokeWidth=10) - g.add(octagon) - return g - -class Crossbox(_Symbol): - """This draws a black box with a red cross in it - a 'checkbox'. - - possible attributes: - 'x', 'y', 'size', 'crossColor', 'strokeColor', 'crosswidth' - - """ - - _attrMap = AttrMap(BASE=_Symbol, - crossColor = AttrMapValue(isColorOrNone), - crosswidth = AttrMapValue(isNumber), - ) - - def __init__(self): - self.x = 0 - self.y = 0 - self.size = 100 - self.fillColor = colors.white - self.crossColor = colors.red - self.strokeColor = colors.black - self.crosswidth = 10 - - def draw(self): - # general widget bits - s = float(self.size) # abbreviate as we will use this a lot - g = shapes.Group() - - # crossbox specific bits - box = shapes.Rect(self.x+1, self.y+1, s-2, s-2, - fillColor = self.fillColor, - strokeColor = self.strokeColor, - strokeWidth=2) - g.add(box) - - crossLine1 = shapes.Line(self.x+(s*0.15), self.y+(s*0.15), self.x+(s*0.85), self.y+(s*0.85), - fillColor = self.crossColor, - strokeColor = self.crossColor, - strokeWidth = self.crosswidth) - g.add(crossLine1) - - crossLine2 = shapes.Line(self.x+(s*0.15), self.y+(s*0.85), self.x+(s*0.85) ,self.y+(s*0.15), - fillColor = self.crossColor, - strokeColor = self.crossColor, - strokeWidth = self.crosswidth) - g.add(crossLine2) - - return g - - -class Tickbox(_Symbol): - """This draws a black box with a red tick in it - another 'checkbox'. - - possible attributes: - 'x', 'y', 'size', 'tickColor', 'strokeColor', 'tickwidth' - -""" - - _attrMap = AttrMap(BASE=_Symbol, - tickColor = AttrMapValue(isColorOrNone), - tickwidth = AttrMapValue(isNumber), - ) - - def __init__(self): - self.x = 0 - self.y = 0 - self.size = 100 - self.tickColor = colors.red - self.strokeColor = colors.black - self.fillColor = colors.white - self.tickwidth = 10 - - def draw(self): - # general widget bits - s = float(self.size) # abbreviate as we will use this a lot - g = shapes.Group() - - # tickbox specific bits - box = shapes.Rect(self.x+1, self.y+1, s-2, s-2, - fillColor = self.fillColor, - strokeColor = self.strokeColor, - strokeWidth=2) - g.add(box) - - tickLine = shapes.PolyLine(points = [self.x+(s*0.15), self.y+(s*0.35), self.x+(s*0.35), self.y+(s*0.15), - self.x+(s*0.35), self.y+(s*0.15), self.x+(s*0.85) ,self.y+(s*0.85)], - fillColor = self.tickColor, - strokeColor = self.tickColor, - strokeWidth = self.tickwidth) - g.add(tickLine) - - return g - -class SmileyFace(_Symbol): - """This draws a classic smiley face. - - possible attributes: - 'x', 'y', 'size', 'fillColor' - - """ - - def __init__(self): - _Symbol.__init__(self) - self.x = 0 - self.y = 0 - self.size = 100 - self.fillColor = colors.yellow - self.strokeColor = colors.black - - def draw(self): - # general widget bits - s = float(self.size) # abbreviate as we will use this a lot - g = shapes.Group() - - # SmileyFace specific bits - g.add(shapes.Circle(cx=self.x+(s/2), cy=self.y+(s/2), r=s/2, - fillColor=self.fillColor, strokeColor=self.strokeColor, - strokeWidth=max(s/38.,self.strokeWidth))) - - for i in (1,2): - g.add(shapes.Ellipse(self.x+(s/3)*i,self.y+(s/3)*2, s/30, s/10, - fillColor=self.strokeColor, strokeColor = self.strokeColor, - strokeWidth=max(s/38.,self.strokeWidth))) - - # calculate a pointslist for the mouth - # THIS IS A HACK! - don't use if there is a 'shapes.Arc' - centerx=self.x+(s/2) - centery=self.y+(s/2) - radius=s/3 - yradius = radius - xradius = radius - startangledegrees=200 - endangledegrees=340 - degreedelta = 1 - pointslist = [] - a = pointslist.append - from math import sin, cos, pi - degreestoradians = pi/180.0 - radiansdelta = degreedelta*degreestoradians - startangle = startangledegrees*degreestoradians - endangle = endangledegrees*degreestoradians - while endangle= searchto: - break # EXIT LOOP - match = m.group(0) - end = start + len(match) - c = match[0] - if c not in "#'\"": - # Must have matched a keyword. - if start <> searchfrom: - # there's still a redundant char before and after it, strip! - match = match[1:-1] - start = start + 1 - else: - # this is the first keyword in the text. - # Only a space at the end. - match = match[:-1] - end = end - 1 - tags_append((keywordTag, start, end, None)) - # If this was a defining keyword, look ahead to the - # following identifier. - if match in ["def", "class"]: - m = idSearch(pytext, end) - if m is not None: - start = m.start() - if start == end: - match = m.group(0) - end = start + len(match) - tags_append((identifierTag, start, end, None)) - elif c == "#": - tags_append((commentTag, start, end, None)) - else: - tags_append((stringTag, start, end, None)) - return tags - - -def test(path): - f = open(path) - text = f.read() - f.close() - tags = fontify(text) - for tag, start, end, sublist in tags: - print tag, `text[start:end]` \ No newline at end of file diff --git a/bin/reportlab/lib/__init__.py b/bin/reportlab/lib/__init__.py deleted file mode 100755 index 840a1e6cbd6..00000000000 --- a/bin/reportlab/lib/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#! /usr/bin/python2.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/lib/__init__.py -__version__=''' $Id$ ''' -import os -RL_DEBUG = os.environ.has_key('RL_DEBUG') \ No newline at end of file diff --git a/bin/reportlab/lib/abag.py b/bin/reportlab/lib/abag.py deleted file mode 100644 index 0cd01dcd426..00000000000 --- a/bin/reportlab/lib/abag.py +++ /dev/null @@ -1,44 +0,0 @@ -#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/lib/abag.py -__version__=''' $Id$ ''' - -class ABag: - """ - 'Attribute Bag' - a trivial BAG class for holding attributes. - - You may initialize with keyword arguments. - a = ABag(k0=v0,....,kx=vx,....) ==> getattr(a,'kx')==vx - - c = a.clone(ak0=av0,.....) copy with optional additional attributes. - """ - def __init__(self,**attr): - for k,v in attr.items(): - setattr(self,k,v) - - def clone(self,**attr): - n = apply(ABag,(),self.__dict__) - if attr != {}: apply(ABag.__init__,(n,),attr) - return n - - def __repr__(self): - import string - n = self.__class__.__name__ - L = [n+"("] - keys = self.__dict__.keys() - for k in keys: - v = getattr(self, k) - rk = repr(k) - rv = repr(v) - rk = " "+string.replace(rk, "\n", "\n ") - rv = " "+string.replace(rv, "\n", "\n ") - L.append(rk) - L.append(rv) - L.append(") #"+n) - return string.join(L, "\n") - -if __name__=="__main__": - AB = ABag(a=1, c="hello") - CD = AB.clone() - print AB - print CD \ No newline at end of file diff --git a/bin/reportlab/lib/attrmap.py b/bin/reportlab/lib/attrmap.py deleted file mode 100644 index 5e604214a5c..00000000000 --- a/bin/reportlab/lib/attrmap.py +++ /dev/null @@ -1,132 +0,0 @@ -#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/lib/attrmap.py -__version__=''' $Id$ ''' -from UserDict import UserDict -from reportlab.lib.validators import isAnything, _SequenceTypes -from reportlab import rl_config - -class CallableValue: - '''a class to allow callable initial values''' - def __init__(self,func,*args,**kw): - #assert iscallable(func) - self.func = func - self.args = args - self.kw = kw - - def __call__(self): - return apply(self.func,self.args,self.kw) - -class AttrMapValue: - '''Simple multi-value holder for attribute maps''' - def __init__(self,validate=None,desc=None,initial=None, **kw): - self.validate = validate or isAnything - self.desc = desc - self.initial = initial - for k,v in kw.items(): - setattr(self,k,v) - - def __getattr__(self,name): - #hack to allow callable initial values - if name=='initial': - if isinstance(self._initial,CallableValue): return self._initial() - return self._initial - elif name=='hidden': - return 0 - raise AttributeError, name - -class AttrMap(UserDict): - def __init__(self,BASE=None,UNWANTED=[],**kw): - data = {} - if BASE: - if isinstance(BASE,AttrMap): - data = BASE.data #they used BASECLASS._attrMap - else: - if type(BASE) not in (type(()),type([])): BASE = (BASE,) - for B in BASE: - if hasattr(B,'_attrMap'): - data.update(getattr(B._attrMap,'data',{})) - else: - raise ValueError, 'BASE=%s has wrong kind of value' % str(B) - - UserDict.__init__(self,data) - self.remove(UNWANTED) - self.data.update(kw) - - def update(self,kw): - if isinstance(kw,AttrMap): kw = kw.data - self.data.update(kw) - - def remove(self,unwanted): - for k in unwanted: - try: - del self[k] - except KeyError: - pass - - def clone(self,UNWANTED=[],**kw): - c = AttrMap(BASE=self,UNWANTED=UNWANTED) - c.update(kw) - return c - -def validateSetattr(obj,name,value): - '''validate setattr(obj,name,value)''' - if rl_config.shapeChecking: - map = obj._attrMap - if map and name[0]!= '_': - try: - validate = map[name].validate - if not validate(value): - raise AttributeError, "Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__) - except KeyError: - raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__) - obj.__dict__[name] = value - -def _privateAttrMap(obj,ret=0): - '''clone obj._attrMap if required''' - A = obj._attrMap - oA = getattr(obj.__class__,'_attrMap',None) - if ret: - if oA is A: - return A.clone(), oA - else: - return A, None - else: - if oA is A: - obj._attrMap = A.clone() - -def _findObjectAndAttr(src, P): - '''Locate the object src.P for P a string, return parent and name of attribute - ''' - P = string.split(P, '.') - if len(P) == 0: - return None, None - else: - for p in P[0:-1]: - src = getattr(src, p) - return src, P[-1] - -def hook__setattr__(obj): - if not hasattr(obj,'__attrproxy__'): - C = obj.__class__ - import new - obj.__class__=new.classobj(C.__name__,(C,)+C.__bases__, - {'__attrproxy__':[], - '__setattr__':lambda self,k,v,osa=getattr(obj,'__setattr__',None),hook=hook: hook(self,k,v,osa)}) - -def addProxyAttribute(src,name,validate=None,desc=None,initial=None,dst=None): - ''' - Add a proxy attribute 'name' to src with targets dst - ''' - #sanity - assert hasattr(src,'_attrMap'), 'src object has no _attrMap' - A, oA = _privateAttrMap(src,1) - if type(dst) not in _SequenceTypes: dst = dst, - D = [] - DV = [] - for d in dst: - if type(d) in _SequenceTypes: - d, e = d[0], d[1:] - obj, attr = _findObjectAndAttr(src,d) - if obj: - dA = getattr(obj,'_attrMap',None) diff --git a/bin/reportlab/lib/codecharts.py b/bin/reportlab/lib/codecharts.py deleted file mode 100644 index c695d753db2..00000000000 --- a/bin/reportlab/lib/codecharts.py +++ /dev/null @@ -1,340 +0,0 @@ -#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/lib/codecharts.py -#$Header $ -__version__=''' $Id ''' -__doc__="""Routines to print code page (character set) drawings. - -To be sure we can accurately represent characters in various encodings -and fonts, we need some routines to display all those characters. -These are defined herein. The idea is to include flowable, drawable -and graphic objects for single and multi-byte fonts. """ -import string - -from reportlab.pdfgen.canvas import Canvas -from reportlab.platypus import Flowable -from reportlab.pdfbase import pdfmetrics, cidfonts -from reportlab.graphics.shapes import Drawing, Group, String, Circle, Rect -from reportlab.graphics.widgetbase import Widget -from reportlab.lib import colors - - -class CodeChartBase(Flowable): - """Basic bits of drawing furniture used by - single and multi-byte versions: ability to put letters - into boxes.""" - - def calcLayout(self): - "Work out x and y positions for drawing" - - - rows = self.codePoints * 1.0 / self.charsPerRow - if rows == int(rows): - self.rows = int(rows) - else: - self.rows = int(rows) + 1 - # size allows for a gray column of labels - self.width = self.boxSize * (1+self.charsPerRow) - self.height = self.boxSize * (1+self.rows) - - #handy lists - self.ylist = [] - for row in range(self.rows + 2): - self.ylist.append(row * self.boxSize) - self.xlist = [] - for col in range(self.charsPerRow + 2): - self.xlist.append(col * self.boxSize) - - def formatByte(self, byt): - if self.hex: - return '%02X' % byt - else: - return '%d' % byt - - def drawChars(self, charList): - """Fills boxes in order. None means skip a box. - Empty boxes at end get filled with gray""" - extraNeeded = (self.rows * self.charsPerRow - len(charList)) - for i in range(extraNeeded): - charList.append(None) - #charList.extend([None] * extraNeeded) - row = 0 - col = 0 - self.canv.setFont(self.fontName, self.boxSize * 0.75) - for ch in charList: # may be 2 bytes or 1 - if ch is None: - self.canv.setFillGray(0.9) - self.canv.rect((1+col) * self.boxSize, (self.rows - row - 1) * self.boxSize, - self.boxSize, self.boxSize, stroke=0, fill=1) - self.canv.setFillGray(0.0) - else: - try: - self.canv.drawCentredString( - (col+1.5) * self.boxSize, - (self.rows - row - 0.875) * self.boxSize, - ch, - ) - except: - self.canv.setFillGray(0.9) - self.canv.rect((1+col) * self.boxSize, (self.rows - row - 1) * self.boxSize, - self.boxSize, self.boxSize, stroke=0, fill=1) - self.canv.drawCentredString( - (col+1.5) * self.boxSize, - (self.rows - row - 0.875) * self.boxSize, - '?', - ) - self.canv.setFillGray(0.0) - col = col + 1 - if col == self.charsPerRow: - row = row + 1 - col = 0 - - def drawLabels(self, topLeft = ''): - """Writes little labels in the top row and first column""" - self.canv.setFillGray(0.8) - self.canv.rect(0, self.ylist[-2], self.width, self.boxSize, fill=1, stroke=0) - self.canv.rect(0, 0, self.boxSize, self.ylist[-2], fill=1, stroke=0) - self.canv.setFillGray(0.0) - - #label each row and column - self.canv.setFont('Helvetica-Oblique',0.375 * self.boxSize) - byt = 0 - for row in range(self.rows): - if self.rowLabels: - label = self.rowLabels[row] - else: # format start bytes as hex or decimal - label = self.formatByte(row * self.charsPerRow) - self.canv.drawCentredString(0.5 * self.boxSize, - (self.rows - row - 0.75) * self.boxSize, - label - ) - for col in range(self.charsPerRow): - self.canv.drawCentredString((col + 1.5) * self.boxSize, - (self.rows + 0.25) * self.boxSize, - self.formatByte(col) - ) - - if topLeft: - self.canv.setFont('Helvetica-BoldOblique',0.5 * self.boxSize) - self.canv.drawCentredString(0.5 * self.boxSize, - (self.rows + 0.25) * self.boxSize, - topLeft - ) - -class SingleByteEncodingChart(CodeChartBase): - def __init__(self, faceName='Helvetica', encodingName='WinAnsiEncoding', - charsPerRow=16, boxSize=14, hex=1): - self.codePoints = 256 - self.faceName = faceName - self.encodingName = encodingName - self.fontName = self.faceName + '-' + self.encodingName - self.charsPerRow = charsPerRow - self.boxSize = boxSize - self.hex = hex - self.rowLabels = None - pdfmetrics.registerFont(pdfmetrics.Font(self.fontName, - self.faceName, - self.encodingName) - ) - - self.calcLayout() - - - def draw(self): - self.drawLabels() - charList = [None] * 32 + map(chr, range(32, 256)) - self.drawChars(charList) - self.canv.grid(self.xlist, self.ylist) - - -class KutenRowCodeChart(CodeChartBase): - """Formats one 'row' of the 94x94 space used in many Asian encodings.aliases - - These deliberately resemble the code charts in Ken Lunde's "Understanding - CJKV Information Processing", to enable manual checking. Due to the large - numbers of characters, we don't try to make one graphic with 10,000 characters, - but rather output a sequence of these.""" - #would be cleaner if both shared one base class whose job - #was to draw the boxes, but never mind... - def __init__(self, row, faceName, encodingName): - self.row = row - self.codePoints = 94 - self.boxSize = 18 - self.charsPerRow = 20 - self.rows = 5 - self.rowLabels = ['00','20','40','60','80'] - self.hex = 0 - self.faceName = faceName - self.encodingName = encodingName - - try: - # the dependent files might not be available - font = cidfonts.CIDFont(self.faceName, self.encodingName) - pdfmetrics.registerFont(font) - except: - # fall back to English and at least shwo we can draw the boxes - self.faceName = 'Helvetica' - self.encodingName = 'WinAnsiEncoding' - self.fontName = self.faceName + '-' + self.encodingName - self.calcLayout() - - def makeRow(self, row): - """Works out the character values for this kuten row""" - cells = [] - if string.find(self.encodingName, 'EUC') > -1: - # it is an EUC family encoding. - for col in range(1, 95): - ch = chr(row + 160) + chr(col+160) - cells.append(ch) -## elif string.find(self.encodingName, 'GB') > -1: -## # it is an EUC family encoding. -## for col in range(1, 95): -## ch = chr(row + 160) + chr(col+160) - else: - cells.append([None] * 94) - return cells - - def draw(self): - self.drawLabels(topLeft= 'R%d' % self.row) - - # work out which characters we need for the row - #assert string.find(self.encodingName, 'EUC') > -1, 'Only handles EUC encoding today, you gave me %s!' % self.encodingName - - # pad out by 1 to match Ken Lunde's tables - charList = [None] + self.makeRow(self.row) - self.drawChars(charList) - self.canv.grid(self.xlist, self.ylist) - - -class Big5CodeChart(CodeChartBase): - """Formats one 'row' of the 94x160 space used in Big 5 - - These deliberately resemble the code charts in Ken Lunde's "Understanding - CJKV Information Processing", to enable manual checking.""" - def __init__(self, row, faceName, encodingName): - self.row = row - self.codePoints = 160 - self.boxSize = 18 - self.charsPerRow = 16 - self.rows = 10 - self.hex = 1 - self.faceName = faceName - self.encodingName = encodingName - self.rowLabels = ['4','5','6','7','A','B','C','D','E','F'] - try: - # the dependent files might not be available - font = cidfonts.CIDFont(self.faceName, self.encodingName) - pdfmetrics.registerFont(font) - except: - # fall back to English and at least shwo we can draw the boxes - self.faceName = 'Helvetica' - self.encodingName = 'WinAnsiEncoding' - self.fontName = self.faceName + '-' + self.encodingName - self.calcLayout() - - def makeRow(self, row): - """Works out the character values for this Big5 row. - Rows start at 0xA1""" - cells = [] - if string.find(self.encodingName, 'B5') > -1: - # big 5, different row size - for y in [4,5,6,7,10,11,12,13,14,15]: - for x in range(16): - col = y*16+x - ch = chr(row) + chr(col) - cells.append(ch) - - else: - cells.append([None] * 160) - return cells - - def draw(self): - self.drawLabels(topLeft='%02X' % self.row) - - charList = self.makeRow(self.row) - self.drawChars(charList) - self.canv.grid(self.xlist, self.ylist) - - -def hBoxText(msg, canvas, x, y, faceName, encName): - """Helper for stringwidth tests on Asian fonts. - - Registers font if needed. Then draws the string, - and a box around it derived from the stringWidth function""" - canvas.saveState() - fontName = faceName + '-' + encName - try: - font = pdfmetrics.getFont(fontName) - except KeyError: - font = cidfonts.CIDFont(faceName, encName) - pdfmetrics.registerFont(font) - - canvas.setFillGray(0.8) - canvas.rect(x,y,pdfmetrics.stringWidth(msg, fontName, 16),16,stroke=0,fill=1) - canvas.setFillGray(0) - canvas.setFont(fontName, 16,16) - canvas.drawString(x,y,msg) - canvas.restoreState() - - -class CodeWidget(Widget): - """Block showing all the characters""" - def __init__(self): - self.x = 0 - self.y = 0 - self.width = 160 - self.height = 160 - - def draw(self): - dx = self.width / 16.0 - dy = self.height / 16.0 - g = Group() - g.add(Rect(self.x, self.y, self.width, self.height, - fillColor=None, strokeColor=colors.black)) - for x in range(16): - for y in range(16): - charValue = y * 16 + x - if charValue > 32: - s = String(self.x + x * dx, - self.y + (self.height - y*dy), chr(charValue)) - g.add(s) - return g - - - - - - -def test(): - c = Canvas('codecharts.pdf') - c.setFont('Helvetica-Bold', 24) - c.drawString(72, 750, 'Testing code page charts') - cc1 = SingleByteEncodingChart() - cc1.drawOn(c, 72, 500) - - cc2 = SingleByteEncodingChart(charsPerRow=32) - cc2.drawOn(c, 72, 300) - - cc3 = SingleByteEncodingChart(charsPerRow=25, hex=0) - cc3.drawOn(c, 72, 100) - -## c.showPage() -## -## c.setFont('Helvetica-Bold', 24) -## c.drawString(72, 750, 'Multi-byte Kuten code chart examples') -## KutenRowCodeChart(1, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 600) -## KutenRowCodeChart(16, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 450) -## KutenRowCodeChart(84, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 300) -## -## c.showPage() -## c.setFont('Helvetica-Bold', 24) -## c.drawString(72, 750, 'Big5 Code Chart Examples') -## #Big5CodeChart(0xA1, 'MSungStd-Light-Acro','ETenms-B5-H').drawOn(c, 72, 500) - - c.save() - print 'saved codecharts.pdf' - -if __name__=='__main__': - test() - - diff --git a/bin/reportlab/lib/colors.py b/bin/reportlab/lib/colors.py deleted file mode 100644 index 639a0966b80..00000000000 --- a/bin/reportlab/lib/colors.py +++ /dev/null @@ -1,564 +0,0 @@ -#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/lib/colors.py -__version__=''' $Id$ ''' - -import string, math -from types import StringType, ListType, TupleType -from reportlab.lib.utils import fp_str -_SeqTypes = (ListType,TupleType) - -class Color: - """This class is used to represent color. Components red, green, blue - are in the range 0 (dark) to 1 (full intensity).""" - - def __init__(self, red=0, green=0, blue=0): - "Initialize with red, green, blue in range [0-1]." - self.red, self.green, self.blue = red,green,blue - - def __repr__(self): - return "Color(%s)" % string.replace(fp_str(self.red, self.green, self.blue),' ',',') - - def __hash__(self): - return hash( (self.red, self.green, self.blue) ) - - def __cmp__(self,other): - try: - dsum = 4*self.red-4*other.red + 2*self.green-2*other.green + self.blue-other.blue - except: - return -1 - if dsum > 0: return 1 - if dsum < 0: return -1 - return 0 - - def rgb(self): - "Returns a three-tuple of components" - return (self.red, self.green, self.blue) - - def bitmap_rgb(self): - return tuple(map(lambda x: int(x*255)&255, self.rgb())) - - def hexval(self): - return '0x%02x%02x%02x' % self.bitmap_rgb() - -class CMYKColor(Color): - """This represents colors using the CMYK (cyan, magenta, yellow, black) - model commonly used in professional printing. This is implemented - as a derived class so that renderers which only know about RGB "see it" - as an RGB color through its 'red','green' and 'blue' attributes, according - to an approximate function. - - The RGB approximation is worked out when the object in constructed, so - the color attributes should not be changed afterwards. - - Extra attributes may be attached to the class to support specific ink models, - and renderers may look for these.""" - - def __init__(self, cyan=0, magenta=0, yellow=0, black=0, - spotName=None, density=1, knockout=None): - """ - Initialize with four colors in range [0-1]. the optional - spotName, density & knockout may be of use to specific renderers. - spotName is intended for use as an identifier to the renderer not client programs. - density is used to modify the overall amount of ink. - knockout is a renderer dependent option that determines whether the applied colour - knocksout (removes) existing colour; None means use the global default. - """ - self.cyan = cyan - self.magenta = magenta - self.yellow = yellow - self.black = black - self.spotName = spotName - self.density = max(min(density,1),0) # force into right range - self.knockout = knockout - - # now work out the RGB approximation. override - self.red, self.green, self.blue = cmyk2rgb( (cyan, magenta, yellow, black) ) - - if density<1: - #density adjustment of rgb approximants, effectively mix with white - r, g, b = self.red, self.green, self.blue - r = density*(r-1)+1 - g = density*(g-1)+1 - b = density*(b-1)+1 - self.red, self.green, self.blue = (r,g,b) - - def __repr__(self): - return "CMYKColor(%s%s%s%s)" % ( - string.replace(fp_str(self.cyan, self.magenta, self.yellow, self.black),' ',','), - (self.spotName and (',spotName='+repr(self.spotName)) or ''), - (self.density!=1 and (',density='+fp_str(self.density)) or ''), - (self.knockout is not None and (',knockout=%d' % self.knockout) or ''), - ) - - def __hash__(self): - return hash( (self.cyan, self.magenta, self.yellow, self.black, self.density, self.spotName) ) - - def __cmp__(self,other): - """Partial ordering of colors according to a notion of distance. - - Comparing across the two color models is of limited use.""" - # why the try-except? What can go wrong? - if isinstance(other, CMYKColor): - dsum = (((( (self.cyan-other.cyan)*2 + - (self.magenta-other.magenta))*2+ - (self.yellow-other.yellow))*2+ - (self.black-other.black))*2+ - (self.density-other.density))*2 + cmp(self.spotName or '',other.spotName or '') - else: # do the RGB comparison - try: - dsum = ((self.red-other.red)*2+(self.green-other.green))*2+(self.blue-other.blue) - except: # or just return 'not equal' if not a color - return -1 - if dsum >= 0: - return dsum>0 - else: - return -1 - - def cmyk(self): - "Returns a tuple of four color components - syntactic sugar" - return (self.cyan, self.magenta, self.yellow, self.black) - - def _density_str(self): - return fp_str(self.density) - -class PCMYKColor(CMYKColor): - '''100 based CMYKColor with density and a spotName; just like Rimas uses''' - def __init__(self,cyan,magenta,yellow,black,density=100,spotName=None,knockout=None): - CMYKColor.__init__(self,cyan/100.,magenta/100.,yellow/100.,black/100.,spotName,density/100.,knockout=knockout) - - def __repr__(self): - return "PCMYKColor(%s%s%s%s)" % ( - string.replace(fp_str(self.cyan*100, self.magenta*100, self.yellow*100, self.black*100),' ',','), - (self.spotName and (',spotName='+repr(self.spotName)) or ''), - (self.density!=1 and (',density='+fp_str(self.density*100)) or ''), - (self.knockout is not None and (',knockout=%d' % self.knockout) or ''), - ) - -def cmyk2rgb((c,m,y,k),density=1): - "Convert from a CMYK color tuple to an RGB color tuple" - # From the Adobe Postscript Ref. Manual 2nd ed. - r = 1.0 - min(1.0, c + k) - g = 1.0 - min(1.0, m + k) - b = 1.0 - min(1.0, y + k) - return (r,g,b) - -def rgb2cmyk(r,g,b): - '''one way to get cmyk from rgb''' - c = 1 - r - m = 1 - g - y = 1 - b - k = min(c,m,y) - c = min(1,max(0,c-k)) - m = min(1,max(0,m-k)) - y = min(1,max(0,y-k)) - k = min(1,max(0,k)) - return (c,m,y,k) - -def color2bw(colorRGB): - "Transform an RGB color to a black and white equivalent." - - col = colorRGB - r, g, b = col.red, col.green, col.blue - n = (r + g + b) / 3.0 - bwColorRGB = Color(n, n, n) - return bwColorRGB - -def HexColor(val): - """This function converts a hex string, or an actual integer number, - into the corresponding color. E.g., in "AABBCC" or 0xAABBCC, - AA is the red, BB is the green, and CC is the blue (00-FF). - - HTML uses a hex string with a preceding hash; if this is present, - it is stripped off. (AR, 3-3-2000) - - For completeness I assume that #aabbcc or 0xaabbcc are hex numbers - otherwise a pure integer is converted as decimal rgb - """ - - if type(val) == StringType: - b = 10 - if val[:1] == '#': - val = val[1:] - b = 16 - elif string.lower(val[:2]) == '0x': - b = 16 - val = val[2:] - val = string.atoi(val,b) - return Color(((val>>16)&0xFF)/255.0,((val>>8)&0xFF)/255.0,(val&0xFF)/255.0) - -def linearlyInterpolatedColor(c0, c1, x0, x1, x): - """ - Linearly interpolates colors. Can handle RGB, CMYK and PCMYK - colors - give ValueError if colours aren't the same. - Doesn't currently handle 'Spot Color Interpolation'. - """ - - if c0.__class__ != c1.__class__: - raise ValueError, "Color classes must be the same for interpolation!" - if x1x0 - if xx1+1e-8: # fudge factor for numerical problems - raise ValueError, "Can't interpolate: x=%f is not between %f and %f!" % (x,x0,x1) - if x<=x0: - return c0 - elif x>=x1: - return c1 - - cname = c0.__class__.__name__ - dx = float(x1-x0) - x = x-x0 - - if cname == 'Color': # RGB - r = c0.red+x*(c1.red - c0.red)/dx - g = c0.green+x*(c1.green- c0.green)/dx - b = c0.blue+x*(c1.blue - c0.blue)/dx - return Color(r,g,b) - elif cname == 'CMYKColor': - c = c0.cyan+x*(c1.cyan - c0.cyan)/dx - m = c0.magenta+x*(c1.magenta - c0.magenta)/dx - y = c0.yellow+x*(c1.yellow - c0.yellow)/dx - k = c0.black+x*(c1.black - c0.black)/dx - d = c0.density+x*(c1.density - c0.density)/dx - return CMYKColor(c,m,y,k, density=d) - elif cname == 'PCMYKColor': - if cmykDistance(c0,c1)<1e-8: - #colors same do density and preserve spotName if any - assert c0.spotName == c1.spotName, "Identical cmyk, but different spotName" - c = c0.cyan - m = c0.magenta - y = c0.yellow - k = c0.black - d = c0.density+x*(c1.density - c0.density)/dx - return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c0.spotName) - elif cmykDistance(c0,_CMYK_white)<1e-8: - #special c0 is white - c = c1.cyan - m = c1.magenta - y = c1.yellow - k = c1.black - d = x*c1.density/dx - return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c1.spotName) - elif cmykDistance(c1,_CMYK_white)<1e-8: - #special c1 is white - c = c0.cyan - m = c0.magenta - y = c0.yellow - k = c0.black - d = x*c0.density/dx - d = c0.density*(1-x/dx) - return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c0.spotName) - else: - c = c0.cyan+x*(c1.cyan - c0.cyan)/dx - m = c0.magenta+x*(c1.magenta - c0.magenta)/dx - y = c0.yellow+x*(c1.yellow - c0.yellow)/dx - k = c0.black+x*(c1.black - c0.black)/dx - d = c0.density+x*(c1.density - c0.density)/dx - return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100) - else: - raise ValueError, "Can't interpolate: Unknown color class %s!" % cname - -# special case -- indicates no drawing should be done -# this is a hangover from PIDDLE - suggest we ditch it since it is not used anywhere -#transparent = Color(-1, -1, -1) - -_CMYK_white=CMYKColor(0,0,0,0) -_PCMYK_white=PCMYKColor(0,0,0,0) -_CMYK_black=CMYKColor(0,0,0,1) -_PCMYK_black=PCMYKColor(0,0,0,100) - -# Special colors -ReportLabBlueOLD = HexColor(0x4e5688) -ReportLabBlue = HexColor(0x00337f) -ReportLabBluePCMYK = PCMYKColor(100,65,0,30,spotName='Pantone 288U') -ReportLabLightBlue = HexColor(0xb7b9d3) -ReportLabFidBlue=HexColor(0x3366cc) -ReportLabFidRed=HexColor(0xcc0033) -ReportLabGreen = HexColor(0x336600) -ReportLabLightGreen = HexColor(0x339933) - -# color constants -- mostly from HTML standard -aliceblue = HexColor(0xF0F8FF) -antiquewhite = HexColor(0xFAEBD7) -aqua = HexColor(0x00FFFF) -aquamarine = HexColor(0x7FFFD4) -azure = HexColor(0xF0FFFF) -beige = HexColor(0xF5F5DC) -bisque = HexColor(0xFFE4C4) -black = HexColor(0x000000) -blanchedalmond = HexColor(0xFFEBCD) -blue = HexColor(0x0000FF) -blueviolet = HexColor(0x8A2BE2) -brown = HexColor(0xA52A2A) -burlywood = HexColor(0xDEB887) -cadetblue = HexColor(0x5F9EA0) -chartreuse = HexColor(0x7FFF00) -chocolate = HexColor(0xD2691E) -coral = HexColor(0xFF7F50) -cornflowerblue = cornflower = HexColor(0x6495ED) -cornsilk = HexColor(0xFFF8DC) -crimson = HexColor(0xDC143C) -cyan = HexColor(0x00FFFF) -darkblue = HexColor(0x00008B) -darkcyan = HexColor(0x008B8B) -darkgoldenrod = HexColor(0xB8860B) -darkgray = HexColor(0xA9A9A9) -darkgreen = HexColor(0x006400) -darkkhaki = HexColor(0xBDB76B) -darkmagenta = HexColor(0x8B008B) -darkolivegreen = HexColor(0x556B2F) -darkorange = HexColor(0xFF8C00) -darkorchid = HexColor(0x9932CC) -darkred = HexColor(0x8B0000) -darksalmon = HexColor(0xE9967A) -darkseagreen = HexColor(0x8FBC8B) -darkslateblue = HexColor(0x483D8B) -darkslategray = HexColor(0x2F4F4F) -darkturquoise = HexColor(0x00CED1) -darkviolet = HexColor(0x9400D3) -deeppink = HexColor(0xFF1493) -deepskyblue = HexColor(0x00BFFF) -dimgray = HexColor(0x696969) -dodgerblue = HexColor(0x1E90FF) -firebrick = HexColor(0xB22222) -floralwhite = HexColor(0xFFFAF0) -forestgreen = HexColor(0x228B22) -fuchsia = HexColor(0xFF00FF) -gainsboro = HexColor(0xDCDCDC) -ghostwhite = HexColor(0xF8F8FF) -gold = HexColor(0xFFD700) -goldenrod = HexColor(0xDAA520) -gray = HexColor(0x808080) -grey = gray -green = HexColor(0x008000) -greenyellow = HexColor(0xADFF2F) -honeydew = HexColor(0xF0FFF0) -hotpink = HexColor(0xFF69B4) -indianred = HexColor(0xCD5C5C) -indigo = HexColor(0x4B0082) -ivory = HexColor(0xFFFFF0) -khaki = HexColor(0xF0E68C) -lavender = HexColor(0xE6E6FA) -lavenderblush = HexColor(0xFFF0F5) -lawngreen = HexColor(0x7CFC00) -lemonchiffon = HexColor(0xFFFACD) -lightblue = HexColor(0xADD8E6) -lightcoral = HexColor(0xF08080) -lightcyan = HexColor(0xE0FFFF) -lightgoldenrodyellow = HexColor(0xFAFAD2) -lightgreen = HexColor(0x90EE90) -lightgrey = HexColor(0xD3D3D3) -lightpink = HexColor(0xFFB6C1) -lightsalmon = HexColor(0xFFA07A) -lightseagreen = HexColor(0x20B2AA) -lightskyblue = HexColor(0x87CEFA) -lightslategray = HexColor(0x778899) -lightsteelblue = HexColor(0xB0C4DE) -lightyellow = HexColor(0xFFFFE0) -lime = HexColor(0x00FF00) -limegreen = HexColor(0x32CD32) -linen = HexColor(0xFAF0E6) -magenta = HexColor(0xFF00FF) -maroon = HexColor(0x800000) -mediumaquamarine = HexColor(0x66CDAA) -mediumblue = HexColor(0x0000CD) -mediumorchid = HexColor(0xBA55D3) -mediumpurple = HexColor(0x9370DB) -mediumseagreen = HexColor(0x3CB371) -mediumslateblue = HexColor(0x7B68EE) -mediumspringgreen = HexColor(0x00FA9A) -mediumturquoise = HexColor(0x48D1CC) -mediumvioletred = HexColor(0xC71585) -midnightblue = HexColor(0x191970) -mintcream = HexColor(0xF5FFFA) -mistyrose = HexColor(0xFFE4E1) -moccasin = HexColor(0xFFE4B5) -navajowhite = HexColor(0xFFDEAD) -navy = HexColor(0x000080) -oldlace = HexColor(0xFDF5E6) -olive = HexColor(0x808000) -olivedrab = HexColor(0x6B8E23) -orange = HexColor(0xFFA500) -orangered = HexColor(0xFF4500) -orchid = HexColor(0xDA70D6) -palegoldenrod = HexColor(0xEEE8AA) -palegreen = HexColor(0x98FB98) -paleturquoise = HexColor(0xAFEEEE) -palevioletred = HexColor(0xDB7093) -papayawhip = HexColor(0xFFEFD5) -peachpuff = HexColor(0xFFDAB9) -peru = HexColor(0xCD853F) -pink = HexColor(0xFFC0CB) -plum = HexColor(0xDDA0DD) -powderblue = HexColor(0xB0E0E6) -purple = HexColor(0x800080) -red = HexColor(0xFF0000) -rosybrown = HexColor(0xBC8F8F) -royalblue = HexColor(0x4169E1) -saddlebrown = HexColor(0x8B4513) -salmon = HexColor(0xFA8072) -sandybrown = HexColor(0xF4A460) -seagreen = HexColor(0x2E8B57) -seashell = HexColor(0xFFF5EE) -sienna = HexColor(0xA0522D) -silver = HexColor(0xC0C0C0) -skyblue = HexColor(0x87CEEB) -slateblue = HexColor(0x6A5ACD) -slategray = HexColor(0x708090) -snow = HexColor(0xFFFAFA) -springgreen = HexColor(0x00FF7F) -steelblue = HexColor(0x4682B4) -tan = HexColor(0xD2B48C) -teal = HexColor(0x008080) -thistle = HexColor(0xD8BFD8) -tomato = HexColor(0xFF6347) -turquoise = HexColor(0x40E0D0) -violet = HexColor(0xEE82EE) -wheat = HexColor(0xF5DEB3) -white = HexColor(0xFFFFFF) -whitesmoke = HexColor(0xF5F5F5) -yellow = HexColor(0xFFFF00) -yellowgreen = HexColor(0x9ACD32) -fidblue=HexColor(0x3366cc) -fidlightblue=HexColor("#d6e0f5") - -ColorType=type(black) - - ################################################################ - # - # Helper functions for dealing with colors. These tell you - # which are predefined, so you can print color charts; - # and can give the nearest match to an arbitrary color object - # - ################################################################# - -def colorDistance(col1, col2): - """Returns a number between 0 and root(3) stating how similar - two colours are - distance in r,g,b, space. Only used to find - names for things.""" - return math.sqrt( - (col1.red - col2.red)**2 + - (col1.green - col2.green)**2 + - (col1.blue - col2.blue)**2 - ) - -def cmykDistance(col1, col2): - """Returns a number between 0 and root(4) stating how similar - two colours are - distance in r,g,b, space. Only used to find - names for things.""" - return math.sqrt( - (col1.cyan - col2.cyan)**2 + - (col1.magenta - col2.magenta)**2 + - (col1.yellow - col2.yellow)**2 + - (col1.black - col2.black)**2 - ) - -_namedColors = None - -def getAllNamedColors(): - #returns a dictionary of all the named ones in the module - # uses a singleton for efficiency - global _namedColors - if _namedColors is not None: return _namedColors - import colors - _namedColors = {} - for (name, value) in colors.__dict__.items(): - if isinstance(value, Color): - _namedColors[name] = value - - return _namedColors - -def describe(aColor,mode=0): - '''finds nearest colour match to aColor. - mode=0 print a string desription - mode=1 return a string description - mode=2 return (distance, colorName) - ''' - namedColors = getAllNamedColors() - closest = (10, None, None) #big number, name, color - for (name, color) in namedColors.items(): - distance = colorDistance(aColor, color) - if distance < closest[0]: - closest = (distance, name, color) - if mode<=1: - s = 'best match is %s, distance %0.4f' % (closest[1], closest[0]) - if mode==0: print s - else: return s - elif mode==2: - return (closest[1], closest[0]) - else: - raise ValueError, "Illegal value for mode "+str(mode) - -def toColor(arg,default=None): - '''try to map an arbitrary arg to a color instance''' - if isinstance(arg,Color): return arg - tArg = type(arg) - if tArg in _SeqTypes: - assert 3<=len(arg)<=4, 'Can only convert 3 and 4 sequences to color' - assert 0<=min(arg) and max(arg)<=1 - return len(arg)==3 and Color(arg[0],arg[1],arg[2]) or CMYKColor(arg[0],arg[1],arg[2],arg[3]) - elif tArg == StringType: - C = getAllNamedColors() - s = string.lower(arg) - if C.has_key(s): return C[s] - try: - return toColor(eval(arg)) - except: - pass - - try: - return HexColor(arg) - except: - if default is None: - raise 'Invalid color value', str(arg) - return default - -def toColorOrNone(arg,default=None): - '''as above but allows None as a legal value''' - if arg is None: - return None - else: - return toColor(arg, default) - -def setColors(**kw): - UNDEF = [] - progress = 1 - assigned = {} - while kw and progress: - progress = 0 - for k, v in kw.items(): - if type(v) in (type(()),type([])): - c = map(lambda x,UNDEF=UNDEF: toColor(x,UNDEF),v) - if type(v) is type(()): c = tuple(c) - ok = UNDEF not in c - else: - c = toColor(v,UNDEF) - ok = c is not UNDEF - if ok: - assigned[k] = c - del kw[k] - progress = 1 - - if kw: raise ValueError("Can't convert\n%s" % str(kw)) - getAllNamedColors() - for k, c in assigned.items(): - globals()[k] = c - if isinstance(c,Color): _namedColors[k] = c - -def Whiter(c,f): - '''given a color combine with white as c*f w*(1-f) 0<=f<=1''' - c = toColor(c) - if isinstance(c,PCMYKColor): - w = _PCMYK_white - elif isinstance(c,CMYKColor): w = _CMYK_white - else: w = white - return linearlyInterpolatedColor(w, c, 0, 1, f) - -def Blacker(c,f): - '''given a color combine with black as c*f+b*(1-f) 0<=f<=1''' - c = toColor(c) - if isinstance(c,PCMYKColor): - b = _PCMYK_black - elif isinstance(c,CMYKColor): b = _CMYK_black - else: b = black - return linearlyInterpolatedColor(b, c, 0, 1, f) diff --git a/bin/reportlab/lib/corp.py b/bin/reportlab/lib/corp.py deleted file mode 100755 index a4d83119518..00000000000 --- a/bin/reportlab/lib/corp.py +++ /dev/null @@ -1,443 +0,0 @@ -#! /usr/bin/python2.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/lib/corp.py -""" This module includes some reusable routines for ReportLab's - 'Corporate Image' - the logo, standard page backdrops and - so on - you are advised to do the same for your own company!""" -__version__=''' $Id$ ''' - -from reportlab.lib.units import inch,cm -from reportlab.lib.validators import * -from reportlab.lib.attrmap import * -from reportlab.graphics.shapes import definePath, Group, Drawing, Rect, PolyLine, String -from reportlab.graphics.widgetbase import Widget -from reportlab.lib.colors import Color, black, white, ReportLabBlue -from reportlab.pdfbase.pdfmetrics import stringWidth -from math import sin, pi - -class RL_CorpLogo(Widget): - '''Dinu's fat letter logo as hacked into decent paths by Robin''' - _attrMap = AttrMap( - x = AttrMapValue(isNumber,'Logo x-coord'), - y = AttrMapValue(isNumber,'Logo y-coord'), - angle = AttrMapValue(isNumber,'Logo rotation'), - strokeColor = AttrMapValue(isColorOrNone, 'Logo lettering stroke color'), - fillColor = AttrMapValue(isColorOrNone, 'Logo lettering fill color'), - strokeWidth = AttrMapValue(isNumber,'Logo lettering stroke width'), - background = AttrMapValue(isColorOrNone,desc="Logo background color"), - border = AttrMapValue(isColorOrNone,desc="Logo border color"), - borderWidth = AttrMapValue(isNumber,desc="Logo border width (1)"), - shadow = AttrMapValue(isNumberOrNone,desc="None or fraction of background for shadowing" ), - width = AttrMapValue(isNumber, desc="width in points of the logo (default 129)"), - height = AttrMapValue(isNumber, desc="height in points of the logo (default 86)"), - skewX = AttrMapValue(isNumber, desc="x-skew of the logo (default 10)"), - skewY = AttrMapValue(isNumber, desc="y-skew of the logo (default 0)"), - showPage = AttrMapValue(isBoolean, desc="If true show the page lines"), - xFlip = AttrMapValue(isBoolean, desc="If true do x reversal"), - yFlip = AttrMapValue(isBoolean, desc="If true do y reversal"), - ) - - def __init__(self): - self.fillColor = white - self.strokeColor = None - self.strokeWidth = 0.1 - self.background = ReportLabBlue - self.border = None - self.borderWidth = 1 - self.shadow = 0.5 - self.height = 86 - self.width = 130 - self.x = self.y = self.angle = self.skewY = self._dx = 0 - self.skewX = 10 - self._dy = 35.5 - self.showPage = 1 - - def demo(self): - D = Drawing(self.width, self.height) - D.add(self) - return D - - def _paintLogo(self, g, dx=0, dy=0, strokeColor=None, strokeWidth=0.1, fillColor=white): - P = [ - ('moveTo' ,15.7246,0 ), ('lineTo' ,9.49521,0 ), ('lineTo' ,6.64988,6.83711 ), ('curveTo' ,6.62224,6.95315 ,6.57391,7.10646 ,6.50485,7.29708 ), ('curveTo' ,6.43578,7.48767 ,6.35059,7.71559 ,6.24931,7.98079 ), ('lineTo' ,6.29074,6.71282 ), ('lineTo' ,6.29074,0 ), ('lineTo' ,0.55862,0 ), ('lineTo' ,0.55862,19.19365 ), ('lineTo' ,6.45649,19.19365 ), ('curveTo' ,9.05324,19.19365 ,10.99617,18.73371 ,12.28532,17.8138 ), ('curveTo' ,13.92439,16.63697 ,14.7439,14.96293 ,14.7439,12.79161 ), ('curveTo' ,14.7439,10.47114 ,13.64354,8.86755 ,11.44276,7.98079 ), 'closePath', ('moveTo' ,6.31838,10.30542 ), ('lineTo' ,6.70513,10.30542 ), ('curveTo' ,7.36812,10.30542 ,7.92062,10.53331 ,8.36261,10.98912 ), ('curveTo' ,8.80461,11.44491 ,9.0256,12.02504 ,9.0256,12.72947 ), ('curveTo' ,9.0256,14.16321 ,8.19227,14.88004 ,6.52556,14.88004 ), ('lineTo' ,6.31838,14.88004 ), 'closePath', - ('moveTo' ,25.06173,4.54978 ), ('lineTo' ,30.47611,4.45033 ), ('curveTo' ,30.08951,2.88402 ,29.33668,1.70513 ,28.21787,0.91369 ), ('curveTo' ,27.09906,0.12223 ,25.63726,-0.27348 ,23.83245,-0.27348 ), ('curveTo' ,21.69611,-0.27348 ,20.02024,0.32322 ,18.80475,1.5166 ), ('curveTo' ,17.59846,2.72658 ,16.99531,4.37988 ,16.99531,6.47662 ), ('curveTo' ,16.99531,8.6065 ,17.64451,10.34269 ,18.94286,11.68527 ), ('curveTo' ,20.24124,13.03612 ,21.91711,13.71152 ,23.97056,13.71152 ), ('curveTo' ,26.01482,13.71152 ,27.64466,13.06096 ,28.86015,11.75985 ), ('curveTo' ,30.07566,10.45042 ,30.68326,8.71423 ,30.68326,6.5512 ), ('lineTo' ,30.65586,5.66859 ), ('lineTo' ,22.53407,5.66859 ), ('curveTo' ,22.59855,4.29287 ,23.03132,3.60503 ,23.83245,3.60503 ), ('curveTo' ,24.45861,3.60503 ,24.86837,3.91994 ,25.06173,4.54978 ), 'closePath', ('moveTo' ,25.18604,8.35371 ), ('curveTo' ,25.18604,8.60235 ,25.15384,8.83024 ,25.08937,9.03742 ), ('curveTo' ,25.02489,9.24463 ,24.93514,9.42278 ,24.82001,9.57197 ), ('curveTo' ,24.70492,9.72113 ,24.56911,9.83923 ,24.41255,9.92624 ), ('curveTo' ,24.25603,10.01326 ,24.08568,10.05678 ,23.90152,10.05678 ), ('curveTo' ,23.51474,10.05678 ,23.20169,9.89725 ,22.96225,9.57819 ), ('curveTo' ,22.72283,9.25913 ,22.60314,8.85096 ,22.60314,8.35371 ), 'closePath', - ('moveTo' ,38.36308,-5.99181 ), ('lineTo' ,32.82428,-5.99181 ), ('lineTo' ,32.82428,13.43804 ), ('lineTo' ,38.36308,13.43804 ), ('lineTo' ,38.23873,11.53608 ), ('curveTo' ,38.46886,11.93387 ,38.70371,12.27159 ,38.94327,12.54922 ), ('curveTo' ,39.18254,12.82685 ,39.44037,13.05268 ,39.71676,13.22671 ), ('curveTo' ,39.99286,13.40074 ,40.28988,13.52712 ,40.60753,13.60585 ), ('curveTo' ,40.92518,13.68459 ,41.27759,13.72396 ,41.66419,13.72396 ), ('curveTo' ,43.10068,13.72396 ,44.2702,13.07755 ,45.17246,11.78472 ), ('curveTo' ,46.06588,10.50844 ,46.51229,8.81368 ,46.51229,6.70038 ), ('curveTo' ,46.51229,4.55394 ,46.08415,2.85502 ,45.22785,1.60362 ), ('curveTo' ,44.38983,0.35221 ,43.23416,-0.27348 ,41.76084,-0.27348 ), ('curveTo' ,40.41659,-0.27348 ,39.24235,0.42679 ,38.23873,1.82739 ), ('curveTo' ,38.2847,1.40472 ,38.31239,1.04007 ,38.32153,0.73345 ), ('curveTo' ,38.34923,0.41851 ,38.36308,0.04146 ,38.36308,-0.3978 ), 'closePath', ('moveTo' ,40.7802,6.84954 ), ('curveTo' ,40.7802,7.72802 ,40.66734,8.40964 ,40.44193,8.89448 ), ('curveTo' ,40.21621,9.37929 ,39.89621,9.62168 ,39.48191,9.62168 ), ('curveTo' ,38.62533,9.62168 ,38.19718,8.68108 ,38.19718,6.79983 ), ('curveTo' ,38.19718,4.87712 ,38.61177,3.91581 ,39.44037,3.91581 ), ('curveTo' ,39.85466,3.91581 ,40.18174,4.1727 ,40.42101,4.68654 ), ('curveTo' ,40.66057,5.20037 ,40.7802,5.92135 ,40.7802,6.84954 ), 'closePath', - ('moveTo' ,62.10648,6.51392 ), ('curveTo' ,62.10648,4.44205 ,61.47118,2.79288 ,60.2003,1.56631 ), ('curveTo' ,58.92971,0.33978 ,57.22626,-0.27348 ,55.08965,-0.27348 ), ('curveTo' ,52.99018,-0.27348 ,51.31914,0.35221 ,50.07595,1.60362 ), ('curveTo' ,48.8419,2.8633 ,48.22517,4.55394 ,48.22517,6.67551 ), ('curveTo' ,48.22517,8.79709 ,48.85575,10.50016 ,50.1175,11.78472 ), ('curveTo' ,51.36982,13.07755 ,53.03172,13.72396 ,55.1035,13.72396 ), ('curveTo' ,57.28608,13.72396 ,58.99866,13.08168 ,60.24185,11.79712 ), ('curveTo' ,61.48503,10.51259 ,62.10648,8.75154 ,62.10648,6.51392 ), 'closePath', ('moveTo' ,56.73358,6.67551 ), ('curveTo' ,56.73358,7.17276 ,56.69675,7.62236 ,56.62308,8.02428 ), ('curveTo' ,56.54942,8.42623 ,56.44334,8.77016 ,56.30544,9.05607 ), ('curveTo' ,56.16724,9.34198 ,56.00134,9.56369 ,55.80804,9.72113 ), ('curveTo' ,55.61474,9.8786 ,55.39817,9.95733 ,55.1589,9.95733 ), ('curveTo' ,54.68921,9.95733 ,54.31174,9.65898 ,54.02621,9.06229 ), ('curveTo' ,53.74068,8.54018 ,53.59807,7.75702 ,53.59807,6.71282 ), ('curveTo' ,53.59807,5.68515 ,53.74068,4.90202 ,54.02621,4.36332 ), ('curveTo' ,54.31174,3.76663 ,54.69392,3.46828 ,55.17275,3.46828 ), ('curveTo' ,55.62388,3.46828 ,55.99692,3.7625 ,56.29159,4.35088 ), ('curveTo' ,56.58625,5.0056 ,56.73358,5.78047 ,56.73358,6.67551 ), 'closePath', - ('moveTo' ,69.78629,0 ), ('lineTo' ,64.2475,0 ), ('lineTo' ,64.2475,13.43804 ), ('lineTo' ,69.78629,13.43804 ), ('lineTo' ,69.49605,10.81507 ), ('curveTo' ,70.33407,12.77921 ,71.71988,13.76126 ,73.65346,13.76126 ), ('lineTo' ,73.65346,8.16725 ), ('curveTo' ,73.04586,8.4656 ,72.5302,8.61478 ,72.10647,8.61478 ), ('curveTo' ,71.36068,8.61478 ,70.78756,8.37236 ,70.38711,7.88755 ), ('curveTo' ,69.98637,7.40274 ,69.78629,6.69623 ,69.78629,5.76804 ), 'closePath', - ('moveTo' ,81.55427,0 ), ('lineTo' ,76.00163,0 ), ('lineTo' ,76.00163,9.42278 ), ('lineTo' ,74.42725,9.42278 ), ('lineTo' ,74.42725,13.43804 ), ('lineTo' ,76.00163,13.43804 ), ('lineTo' ,76.00163,17.39113 ), ('lineTo' ,81.55427,17.39113 ), ('lineTo' ,81.55427,13.43804 ), ('lineTo' ,83.39121,13.43804 ), ('lineTo' ,83.39121,9.42278 ), ('lineTo' ,81.55427,9.42278 ), 'closePath', - ('moveTo' ,95.17333,0 ), ('lineTo' ,85.09024,0 ), ('lineTo' ,85.09024,19.19365 ), ('lineTo' ,90.85002,19.19365 ), ('lineTo' ,90.85002,4.61196 ), ('lineTo' ,95.17333,4.61196 ), 'closePath', - ('moveTo' ,110.00787,0 ), ('lineTo' ,104.45523,0 ), ('curveTo' ,104.5012,0.44754 ,104.53803,0.87433 ,104.56573,1.2804 ), ('curveTo' ,104.59313,1.68651 ,104.62083,2.01385 ,104.64853,2.26246 ), ('curveTo' ,103.69087,0.57182 ,102.40644,-0.27348 ,100.79492,-0.27348 ), ('curveTo' ,99.39527,-0.27348 ,98.28557,0.35637 ,97.46611,1.61605 ), ('curveTo' ,96.65578,2.86746 ,96.25062,4.59952 ,96.25062,6.81227 ), ('curveTo' ,96.25062,8.95041 ,96.66963,10.63276 ,97.50765,11.8593 ), ('curveTo' ,98.34538,13.10242 ,99.4872,13.72396 ,100.93312,13.72396 ), ('curveTo' ,102.41557,13.72396 ,103.61249,12.92008 ,104.52418,11.31231 ), ('curveTo' ,104.50591,11.47806 ,104.49206,11.62309 ,104.48293,11.74741 ), ('curveTo' ,104.4735,11.87173 ,104.46437,11.9753 ,104.45523,12.05819 ), ('lineTo' ,104.39983,12.84135 ), ('lineTo' ,104.35858,13.43804 ), ('lineTo' ,110.00787,13.43804 ), 'closePath', ('moveTo' ,104.39983,6.88685 ), ('curveTo' ,104.39983,7.38409 ,104.37921,7.80676 ,104.33766,8.15481 ), ('curveTo' ,104.29641,8.5029 ,104.22952,8.78672 ,104.13758,9.00636 ), ('curveTo' ,104.04535,9.22598 ,103.92572,9.38341 ,103.77839,9.47874 ), ('curveTo' ,103.63106,9.57403 ,103.45161,9.62168 ,103.23974,9.62168 ), ('curveTo' ,102.30036,9.62168 ,101.83096,8.49875 ,101.83096,6.25285 ), ('curveTo' ,101.83096,4.64508 ,102.24967,3.8412 ,103.0877,3.8412 ), ('curveTo' ,103.96255,3.8412 ,104.39983,4.85641 ,104.39983,6.88685 ), 'closePath', - ('moveTo' ,118.22604,0 ), ('lineTo' ,112.5629,0 ), ('lineTo' ,112.5629,20.99616 ), ('lineTo' ,118.10169,20.99616 ), ('lineTo' ,118.10169,13.63694 ), ('curveTo' ,118.10169,13.01538 ,118.07399,12.30268 ,118.01889,11.49877 ), ('curveTo' ,118.52542,12.31096 ,119.03636,12.88693 ,119.55202,13.22671 ), ('curveTo' ,120.08625,13.55821 ,120.75838,13.72396 ,121.5687,13.72396 ), ('curveTo' ,123.07885,13.72396 ,124.24837,13.09827 ,125.07697,11.84686 ), ('curveTo' ,125.90586,10.60373 ,126.32015,8.85099 ,126.32015,6.5885 ), ('curveTo' ,126.32015,4.42546 ,125.89201,2.74314 ,125.03571,1.54147 ), ('curveTo' ,124.18826,0.3315 ,123.01432,-0.27348 ,121.51331,-0.27348 ), ('curveTo' ,120.78608,-0.27348 ,120.16905,-0.12432 ,119.66252,0.17403 ), ('curveTo' ,119.41383,0.3315 ,119.15835,0.54283 ,118.8961,0.80803 ), ('curveTo' ,118.63356,1.07322 ,118.36866,1.40472 ,118.10169,1.80252 ), ('curveTo' ,118.11112,1.64505 ,118.12025,1.51039 ,118.12939,1.3985 ), ('curveTo' ,118.13852,1.28662 ,118.14766,1.19339 ,118.15709,1.11881 ), 'closePath', ('moveTo' ,120.58806,6.70038 ), ('curveTo' ,120.58806,8.62306 ,120.11837,9.5844 ,119.17898,9.5844 ), ('curveTo' ,118.35039,9.5844 ,117.93609,8.67693 ,117.93609,6.86198 ), ('curveTo' ,117.93609,4.96417 ,118.36424,4.01526 ,119.22053,4.01526 ), ('curveTo' ,120.13222,4.01526 ,120.58806,4.91027 ,120.58806,6.70038 ), 'closePath', - ] + (self.showPage and [ - ('moveTo',38.30626,-7.28346),('lineTo',38.30626,-25.55261),('lineTo',85.15777,-25.55261),('lineTo',85.15777,-1.39019),('lineTo',90.46172,-1.39019),('lineTo',90.46172,-31.15121),('lineTo',32.70766,-31.15121),('lineTo',32.70766,-7.28346), 'closePath', - ('moveTo' ,32.70766,14.52164 ), ('lineTo' ,32.70766,47.81862 ), ('lineTo' ,80.14849,47.81862 ), ('lineTo' ,90.46172,37.21073 ), ('lineTo' ,90.46172,20.12025 ), ('lineTo' ,85.15777,20.12025 ), ('lineTo' ,85.15777,30.72814 ), ('lineTo' ,73.66589,30.72814 ), ('lineTo' ,73.66589,42.22002 ), ('lineTo' ,38.30626,42.22002 ), ('lineTo' ,38.30626,14.52164 ), 'closePath', ('moveTo' ,79.2645,36.32674 ), ('lineTo' ,85.15777,36.32674 ), ('lineTo' ,79.2645,42.22002 ), 'closePath', - ] or []) - g.add(definePath(P,strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor, dx=dx, dy=dy)) - - def draw(self): - fillColor = self.fillColor - strokeColor = self.strokeColor - g = Group() - bg = self.background - bd = self.border - bdw = self.borderWidth - shadow = self.shadow - x, y = self.x, self.y - if bg: - if shadow is not None and 0<=shadow<1: - shadow = Color(bg.red*shadow,bg.green*shadow,bg.blue*shadow) - self._paintLogo(g,dy=-2.5, dx=2,fillColor=shadow) - self._paintLogo(g,fillColor=fillColor,strokeColor=strokeColor) - g.skew(kx=self.skewX, ky=self.skewY) - g.shift(self._dx,self._dy) - G = Group() - G.add(g) - _w, _h = 130, 86 - w, h = self.width, self.height - if bg or (bd and bdw): - G.insert(0,Rect(0,0,_w,_h,fillColor=bg,strokeColor=bd,strokeWidth=bdw)) - if w!=_w or h!=_h: G.scale(w/float(_w),h/float(_h)) - - angle = self.angle - if self.angle: - w, h = w/2., h/2. - G.shift(-w,-h) - G.rotate(angle) - G.shift(w,h) - xFlip = getattr(self,'xFlip',0) and -1 or 0 - yFlip = getattr(self,'yFlip',0) and -1 or 0 - if xFlip or yFlip: - sx = xFlip or 1 - sy = yFlip or 1 - G.shift(sx*x+w*xFlip,sy*y+yFlip*h) - G = Group(G,transform=(sx,0,0,sy,0,0)) - else: - G.shift(x,y) - return G - -class RL_CorpLogoReversed(RL_CorpLogo): - def __init__(self): - RL_CorpLogo.__init__(self) - self.background = white - self.fillColor = ReportLabBlue - -class RL_CorpLogoThin(Widget): - """The ReportLab Logo. - - New version created by John Precedo on 7-8 August 2001. - Based on bitmapped imaged from E-Id. - Improved by Robin Becker.""" - - _attrMap = AttrMap( - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - height = AttrMapValue(isNumberOrNone), - width = AttrMapValue(isNumberOrNone), - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue( isColorOrNone) - ) - - _h = 90.5 - _w = 136.5 - _text='R e p o r t L a b' - _fontName = 'Helvetica-Bold' - _fontSize = 16 - - def __init__(self): - self.fillColor = ReportLabBlue - self.strokeColor = white - self.x = 0 - self.y = 0 - self.height = self._h - self.width = self._w - - def demo(self): - D = Drawing(self.width, self.height) - D.add(self) - return D - - def _getText(self, x=0, y=0, color=None): - return String(x,y, self._text, fontName=self._fontName, fontSize=self._fontSize, fillColor=color) - - def _sw(self,f=None,l=None): - text = self._text - if f is None: f = 0 - if l is None: l = len(text) - return stringWidth(text[f:l],self._fontName,self._fontSize) - - def _addPage(self, g, strokeWidth=3, color=None, dx=0, dy=0): - x1, x2 = 31.85+dx, 80.97+dx - fL = 10 # fold length - y1, y2 = dy-34, dy+50.5 - L = [[x1,dy-4,x1,y1, x2, y1, x2, dy-1], - [x1,dy+11,x1,y2,x2-fL,y2,x2,y2-fL,x2,dy+14], - [x2-10,y2,x2-10,y2-fL,x2,y2-fL]] - - for l in L: - g.add(PolyLine(l, strokeWidth=strokeWidth, strokeColor=color, strokeLineJoin=0)) - - def draw(self): - sx = 0.5 - fillColor = self.fillColor - strokeColor = self.strokeColor - shadow = Color(fillColor.red*sx,fillColor.green*sx,fillColor.blue*sx) - g = Group() - g2= Group() - g.add(Rect(fillColor=fillColor, strokeColor=fillColor, x=0, y=0, width=self._w, height=self._h)) - sx = (self._w-2)/self._sw() - g2.scale(sx,1) - self._addPage(g2,strokeWidth=3,dx=2,dy=-2.5,color=shadow) - self._addPage(g2,strokeWidth=3,color=strokeColor) - g2.scale(1/sx,1) - g2.add(self._getText(x=1,y=0,color=shadow)) - g2.add(self._getText(x=0,y=1,color=strokeColor)) - g2.scale(sx,1) - g2.skew(kx=10, ky=0) - g2.shift(0,38) - g.add(g2) - g.scale(self.width/self._w,self.height/self._h) - g.shift(self.x,self.y) - return g - -class ReportLabLogo: - """vector reportlab logo centered in a 250x by 150y rectangle""" - - def __init__(self, atx=0, aty=0, width=2.5*inch, height=1.5*inch, powered_by=0): - self.origin = (atx, aty) - self.dimensions = (width, height) - self.powered_by = powered_by - - def draw(self, canvas): - from reportlab.graphics import renderPDF - canvas.saveState() - (atx,aty) = self.origin - (width, height) = self.dimensions - logo = RL_CorpLogo() - logo.width, logo.height = width, height - renderPDF.draw(logo.demo(),canvas,atx,aty,0) - canvas.restoreState() - -class RL_BusinessCard(Widget): - """Widget that creates a single business card. - Uses RL_CorpLogo for the logo. - - For a black border around your card, set self.border to 1. - To change the details on the card, over-ride the following properties: - self.name, self.position, self.telephone, self.mobile, self.fax, self.email, self.web - The office locations are set in self.rh_blurb_top ("London office" etc), and - self.rh_blurb_bottom ("New York office" etc). - """ - # for items where it 'isString' the string can be an empty one... - _attrMap = AttrMap( - fillColor = AttrMapValue(isColorOrNone), - strokeColor = AttrMapValue(isColorOrNone), - altStrokeColor = AttrMapValue(isColorOrNone), - x = AttrMapValue(isNumber), - y = AttrMapValue(isNumber), - height = AttrMapValue(isNumber), - width = AttrMapValue(isNumber), - borderWidth = AttrMapValue(isNumber), - bleed=AttrMapValue(isNumberOrNone), - cropMarks=AttrMapValue(isBoolean), - border=AttrMapValue(isBoolean), - name=AttrMapValue(isString), - position=AttrMapValue(isString), - telephone=AttrMapValue(isString), - mobile=AttrMapValue(isString), - fax=AttrMapValue(isString), - email=AttrMapValue(isString), - web=AttrMapValue(isString), - rh_blurb_top=AttrMapValue(isListOfStringsOrNone), - rh_blurb_bottom=AttrMapValue(isListOfStringsOrNone) - ) - - _h = 5.35*cm - _w = 8.5*cm - _fontName = 'Helvetica-Bold' - _strapline = "strategic reporting solutions for e-business" - - - def __init__(self): - self.fillColor = ReportLabBlue - self.strokeColor = black - self.altStrokeColor = white - self.x = 0 - self.y = 0 - self.height = self._h - self.width = self._w - self.borderWidth = self.width/6.15 - self.bleed=0.2*cm - self.cropMarks=1 - self.border=0 - #Over-ride these with your own info - self.name="Joe Cool" - self.position="Freelance Demonstrator" - self.telephone="020 8545 7271" - self.mobile="-" - self.fax="020 8544 1311" - self.email="info@reportlab.com" - self.web="www.reportlab.com" - self.rh_blurb_top = ["London office:", - "ReportLab Europe Ltd", - "Lombard Business Park", - "8 Lombard Road", - "Wimbledon", - "London SW19 3TZ", - "United Kingdom"] - self.rh_blurb_bottom = ["New York office:", - "ReportLab Inc", - "219 Harper Street", - "Highland Park", - "New Jersey 08904", - "USA"] - - def demo(self): - D = Drawing(self.width, self.height) - D.add(self) - return D - - def draw(self): - fillColor = self.fillColor - strokeColor = self.strokeColor - - g = Group() - g.add(Rect(x = 0, y = 0, - fillColor = self.fillColor, - strokeColor = self.fillColor, - width = self.borderWidth, - height = self.height)) - g.add(Rect(x = 0, y = self.height-self.borderWidth, - fillColor = self.fillColor, - strokeColor = self.fillColor, - width = self.width, - height = self.borderWidth)) - - g2 = Group() - rl=RL_CorpLogo() - rl.height = 1.25*cm - rl.width = 1.9*cm - rl.draw() - g2.add(rl) - g.add(g2) - g2.shift(x=(self.width-(rl.width+(self.width/42))), - y=(self.height - (rl.height+(self.height/42)))) - - g.add(String(x = self.borderWidth/5.0, - y = ((self.height - (rl.height+(self.height/42)))+((38/90.5)*rl.height)), - fontSize = 6, - fillColor = self.altStrokeColor, - fontName = "Helvetica-BoldOblique", - textAnchor = 'start', - text = self._strapline)) - - leftText=["Tel:", "Mobile:", "Fax:", "Email:", "Web:"] - leftDetails=[self.telephone,self.mobile,self.fax,self.email,self.web] - leftText.reverse() - leftDetails.reverse() - for f in range(len(leftText),0,-1): - g.add(String(x = self.borderWidth+(self.borderWidth/5.0), - y = (self.borderWidth/5.0)+((f-1)*(5*1.2)), - fontSize = 5, - fillColor = self.strokeColor, - fontName = "Helvetica", - textAnchor = 'start', - text = leftText[f-1])) - g.add(String(x = self.borderWidth+(self.borderWidth/5.0)+self.borderWidth, - y = (self.borderWidth/5.0)+((f-1)*(5*1.2)), - fontSize = 5, - fillColor = self.strokeColor, - fontName = "Helvetica", - textAnchor = 'start', - text = leftDetails[f-1])) - - rightText=self.rh_blurb_bottom - rightText.reverse() - for f in range(len(rightText),0,-1): - g.add(String(x = self.width-((self.borderWidth/5.0)), - y = (self.borderWidth/5.0)+((f-1)*(5*1.2)), - fontSize = 5, - fillColor = self.strokeColor, - fontName = "Helvetica", - textAnchor = 'end', - text = rightText[f-1])) - - ty = (self.height-self.borderWidth-(self.borderWidth/5.0)+2) -# g.add(Line(self.borderWidth, ty, self.borderWidth+(self.borderWidth/5.0), ty)) -# g.add(Line(self.borderWidth+(self.borderWidth/5.0), ty, self.borderWidth+(self.borderWidth/5.0), -# ty+(self.borderWidth/5.0))) -# g.add(Line(self.borderWidth, ty-10, -# self.borderWidth+(self.borderWidth/5.0), ty-10)) - - rightText=self.rh_blurb_top - for f in range(1,(len(rightText)+1)): - g.add(String(x = self.width-(self.borderWidth/5.0), - y = ty-((f)*(5*1.2)), - fontSize = 5, - fillColor = self.strokeColor, - fontName = "Helvetica", - textAnchor = 'end', - text = rightText[f-1])) - - g.add(String(x = self.borderWidth+(self.borderWidth/5.0), - y = ty-10, - fontSize = 10, - fillColor = self.strokeColor, - fontName = "Helvetica", - textAnchor = 'start', - text = self.name)) - - ty1 = ty-10*1.2 - - g.add(String(x = self.borderWidth+(self.borderWidth/5.0), - y = ty1-8, - fontSize = 8, - fillColor = self.strokeColor, - fontName = "Helvetica", - textAnchor = 'start', - text = self.position)) - if self.border: - g.add(Rect(x = 0, y = 0, - fillColor=None, - strokeColor = black, - width = self.width, - height = self.height)) - g.shift(self.x,self.y) - return g - - -def test(): - """This function produces a pdf with examples. """ - - #wbite on blue - rl = RL_CorpLogo() - rl.width = 129 - rl.height = 86 - D = Drawing(rl.width,rl.height) - D.add(rl) - D.__dict__['verbose'] = 1 - D.save(fnRoot='corplogo_whiteonblue',formats=['pdf','eps','jpg','gif']) - - - #blue on white - rl = RL_CorpLogoReversed() - rl.width = 129 - rl.height = 86 - D = Drawing(rl.width,rl.height) - D.add(rl) - D.__dict__['verbose'] = 1 - D.save(fnRoot='corplogo_blueonwhite',formats=['pdf','eps','jpg','gif']) - - - - rl = RL_BusinessCard() - rl.x=25 - rl.y=25 - rl.border=1 - D = Drawing(rl.width+50,rl.height+50) - D.add(rl) - D.__dict__['verbose'] = 1 - D.save(fnRoot='RL_BusinessCard',formats=['pdf']) - -if __name__=='__main__': - test() diff --git a/bin/reportlab/lib/enums.py b/bin/reportlab/lib/enums.py deleted file mode 100644 index 38105ec4fee..00000000000 --- a/bin/reportlab/lib/enums.py +++ /dev/null @@ -1,11 +0,0 @@ -#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/lib/enums.py -__version__=''' $Id$ ''' -__doc__=""" -holder for all reportlab's enumerated types -""" -TA_LEFT = 0 -TA_CENTER = 1 -TA_RIGHT = 2 -TA_JUSTIFY = 4 \ No newline at end of file diff --git a/bin/reportlab/lib/extformat.py b/bin/reportlab/lib/extformat.py deleted file mode 100644 index f3450596081..00000000000 --- a/bin/reportlab/lib/extformat.py +++ /dev/null @@ -1,81 +0,0 @@ -#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/lib/extformat.py -from tokenize import tokenprog -import sys - -def _matchorfail(text, pos): - match = tokenprog.match(text, pos) - if match is None: raise ValueError(text, pos) - return match, match.end() - -''' - Extended dictionary formatting - We allow expressions in the parentheses instead of - just a simple variable. -''' -def dictformat(_format, L={}, G={}): - format = _format - - S = {} - chunks = [] - pos = 0 - n = 0 - - while 1: - pc = format.find("%", pos) - if pc < 0: break - nextchar = format[pc+1] - - if nextchar == "(": - chunks.append(format[pos:pc]) - pos, level = pc+2, 1 - while level: - match, pos = _matchorfail(format, pos) - tstart, tend = match.regs[3] - token = format[tstart:tend] - if token == "(": level = level+1 - elif token == ")": level = level-1 - vname = '__superformat_%d' % n - n += 1 - S[vname] = eval(format[pc+2:pos-1],L,G) - chunks.append('%%(%s)' % vname) - else: - nc = pc+1+(nextchar=="%") - chunks.append(format[pos:nc]) - pos = nc - - if pos < len(format): chunks.append(format[pos:]) - return (''.join(chunks)) % S - -def magicformat(format): - """Evaluate and substitute the appropriate parts of the string.""" - try: 1/0 - except: frame = sys.exc_traceback.tb_frame - while frame.f_globals["__name__"] == __name__: frame = frame.f_back - return dictformat(format,frame.f_locals, frame.f_globals) - -if __name__=='__main__': - from reportlab.lib.formatters import DecimalFormatter - _DF={} - def df(n,dp=2,ds='.',ts=','): - try: - _df = _DF[dp,ds] - except KeyError: - _df = _DF[dp,ds] = DecimalFormatter(places=dp,decimalSep=ds,thousandSep=ts) - return _df(n) - - from reportlab.lib.extformat import magicformat - - Z={'abc': ('ab','c')} - x = 300000.23 - percent=79.2 - class dingo: - a=3 - print magicformat(''' -$%%(df(x,dp=3))s --> $%(df(x,dp=3))s -$%%(df(x,dp=2,ds=',',ts='.'))s --> $%(df(x,dp=2,ds=',',ts='.'))s -%%(percent).2f%%%% --> %(percent).2f%% -%%(dingo.a)s --> %(dingo.a)s -%%(Z['abc'][0])s --> %(Z['abc'][0])s -''') diff --git a/bin/reportlab/lib/fonts.py b/bin/reportlab/lib/fonts.py deleted file mode 100755 index 19a33952d5e..00000000000 --- a/bin/reportlab/lib/fonts.py +++ /dev/null @@ -1,89 +0,0 @@ -#! /usr/bin/python2.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/lib/fonts.py -__version__=''' $Id$ ''' -import string, sys, os -############################################################################### -# A place to put useful font stuff -############################################################################### -# -# Font Mappings -# The brute force approach to finding the correct postscript font name; -# much safer than the rule-based ones we tried. -# preprocessor to reduce font face names to the shortest list -# possible. Add any aliases you wish; it keeps looking up -# until it finds no more translations to do. Any input -# will be lowercased before checking. -_family_alias = { - 'serif':'times', - 'sansserif':'helvetica', - 'monospaced':'courier', - 'arial':'helvetica' - } -#maps a piddle font to a postscript one. -_tt2ps_map = { - #face, bold, italic -> ps name - ('times', 0, 0) :'Times-Roman', - ('times', 1, 0) :'Times-Bold', - ('times', 0, 1) :'Times-Italic', - ('times', 1, 1) :'Times-BoldItalic', - - ('courier', 0, 0) :'Courier', - ('courier', 1, 0) :'Courier-Bold', - ('courier', 0, 1) :'Courier-Oblique', - ('courier', 1, 1) :'Courier-BoldOblique', - - ('helvetica', 0, 0) :'Helvetica', - ('helvetica', 1, 0) :'Helvetica-Bold', - ('helvetica', 0, 1) :'Helvetica-Oblique', - ('helvetica', 1, 1) :'Helvetica-BoldOblique', - - - # there is only one Symbol font - ('symbol', 0, 0) :'Symbol', - ('symbol', 1, 0) :'Symbol', - ('symbol', 0, 1) :'Symbol', - ('symbol', 1, 1) :'Symbol', - - # ditto for dingbats - ('zapfdingbats', 0, 0) :'ZapfDingbats', - ('zapfdingbats', 1, 0) :'ZapfDingbats', - ('zapfdingbats', 0, 1) :'ZapfDingbats', - ('zapfdingbats', 1, 1) :'ZapfDingbats', - - - } - -_ps2tt_map={} -for k,v in _tt2ps_map.items(): - if not _ps2tt_map.has_key(k): - _ps2tt_map[string.lower(v)] = k - -def ps2tt(psfn): - 'ps fontname to family name, bold, italic' - psfn = string.lower(psfn) - if _ps2tt_map.has_key(psfn): - return _ps2tt_map[psfn] - raise ValueError, "Can't map determine family/bold/italic for %s" % psfn - -def tt2ps(fn,b,i): - 'family name + bold & italic to ps font name' - K = (string.lower(fn),b,i) - if _tt2ps_map.has_key(K): - return _tt2ps_map[K] - else: - fn, b1, i1 = ps2tt(K[0]) - K = fn, b1|b, i1|i - if _tt2ps_map.has_key(K): - return _tt2ps_map[K] - raise ValueError, "Can't find concrete font for family=%s, bold=%d, italic=%d" % (fn, b, i) - -def addMapping(face, bold, italic, psname): - 'allow a custom font to be put in the mapping' - k = (string.lower(face), bold, italic) - _tt2ps_map[k] = psname - # rebuild inverse - inefficient - for k,v in _tt2ps_map.items(): - if not _ps2tt_map.has_key(k): - _ps2tt_map[string.lower(v)] = k diff --git a/bin/reportlab/lib/formatters.py b/bin/reportlab/lib/formatters.py deleted file mode 100755 index fd24866d043..00000000000 --- a/bin/reportlab/lib/formatters.py +++ /dev/null @@ -1,100 +0,0 @@ -#! /usr/bin/python2.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/lib/formatters.py -__version__=''' $Id$ ''' -__doc__=""" -These help format numbers and dates in a user friendly way. - -Used by the graphics framework. -""" -import string, sys, os - -class Formatter: - "Base formatter - simply applies python format strings" - def __init__(self, pattern): - self.pattern = pattern - def format(self, obj): - return self.pattern % obj - def __repr__(self): - return "%s('%s')" % (self.__class__.__name__, self.pattern) - def __call__(self, x): - return self.format(x) - - -class DecimalFormatter(Formatter): - """lets you specify how to build a decimal. - - A future NumberFormatter class will take Microsoft-style patterns - instead - "$#,##0.00" is WAY easier than this.""" - def __init__(self, places=2, decimalSep='.', thousandSep=None, prefix=None, suffix=None): - self.places = places - self.dot = decimalSep - self.comma = thousandSep - self.prefix = prefix - self.suffix = suffix - - def format(self, num): - # positivize the numbers - sign=num<0 - if sign: - num = -num - places, sep = self.places, self.dot - strip = places<=0 - if places and strip: places = -places - strInt = ('%.' + str(places) + 'f') % num - if places: - strInt, strFrac = strInt.split('.') - strFrac = sep + strFrac - if strip: - while strFrac and strFrac[-1] in ['0',sep]: strFrac = strFrac[:-1] - else: - strFrac = '' - - if self.comma is not None: - strNew = '' - while strInt: - left, right = strInt[0:-3], strInt[-3:] - if left == '': - #strNew = self.comma + right + strNew - strNew = right + strNew - else: - strNew = self.comma + right + strNew - strInt = left - strInt = strNew - - strBody = strInt + strFrac - if sign: strBody = '-' + strBody - if self.prefix: - strBody = self.prefix + strBody - if self.suffix: - strBody = strBody + self.suffix - return strBody - - def __repr__(self): - return "%s(places=%d, decimalSep=%s, thousandSep=%s, prefix=%s, suffix=%s)" % ( - self.__class__.__name__, - self.places, - repr(self.dot), - repr(self.comma), - repr(self.prefix), - repr(self.suffix) - ) - -if __name__=='__main__': - def t(n, s, places=2, decimalSep='.', thousandSep=None, prefix=None, suffix=None): - f=DecimalFormatter(places,decimalSep,thousandSep,prefix,suffix) - r = f(n) - print "places=%2d dot=%-4s comma=%-4s prefix=%-4s suffix=%-4s result=%10s %s" %(f.places, f.dot, f.comma, f.prefix, f.suffix,r, r==s and 'OK' or 'BAD') - t(1000.9,'1,000.9',1,thousandSep=',') - t(1000.95,'1,001.0',1,thousandSep=',') - t(1000.95,'1,001',-1,thousandSep=',') - t(1000.9,'1,001',0,thousandSep=',') - t(1000.9,'1000.9',1) - t(1000.95,'1001.0',1) - t(1000.95,'1001',-1) - t(1000.9,'1001',0) - t(1000.1,'1000.1',1) - t(1000.55,'1000.6',1) - t(1000.449,'1000.4',-1) - t(1000.45,'1000',0) \ No newline at end of file diff --git a/bin/reportlab/lib/logger.py b/bin/reportlab/lib/logger.py deleted file mode 100755 index 501596e62e1..00000000000 --- a/bin/reportlab/lib/logger.py +++ /dev/null @@ -1,61 +0,0 @@ -#! /usr/bin/python2.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/lib/logger.py -__version__=''' $Id$ ''' - -from sys import stderr -class Logger: - ''' - An extended file type thing initially equivalent to sys.stderr - You can add/remove file type things; it has a write method - ''' - def __init__(self): - self._fps = [stderr] - self._fns = {} - - def add(self,fp): - '''add the file/string fp to the destinations''' - if type(fp) is StringType: - if fp in self._fns: return - fp = open(fn,'wb') - self._fns[fn] = fp - self._fps.append(fp) - - def remove(self,fp): - '''remove the file/string fp from the destinations''' - if type(fp) is StringType: - if fp not in self._fns: return - fn = fp - fp = self._fns[fn] - del self.fns[fn] - if fp in self._fps: - del self._fps[self._fps.index(fp)] - - def write(self,text): - '''write text to all the destinations''' - if text[-1]!='\n': text=text+'\n' - map(lambda fp,t=text: fp.write(t),self._fps) - - def __call__(self,text): - self.write(text) - -logger=Logger() - -class WarnOnce: - - def __init__(self,kind='Warn'): - self.uttered = {} - self.pfx = '%s: '%kind - self.enabled = 1 - - def once(self,warning): - if not self.uttered.has_key(warning): - if self.enabled: logger.write(self.pfx + warning) - self.uttered[warning] = 1 - - def __call__(self,warning): - self.once(warning) - -warnOnce=WarnOnce() -infoOnce=WarnOnce('Info') \ No newline at end of file diff --git a/bin/reportlab/lib/normalDate.py b/bin/reportlab/lib/normalDate.py deleted file mode 100755 index a15c75900dc..00000000000 --- a/bin/reportlab/lib/normalDate.py +++ /dev/null @@ -1,603 +0,0 @@ -#! /usr/bin/python2.3 -# normalDate.py - version 1.0 - 20000717 -#hacked by Robin Becker 10/Apr/2001 -#major changes include -# using Types instead of type(0) etc -# BusinessDate class -# __radd__, __rsub__ methods -# formatMS stuff - -# derived from an original version created -# by Jeff Bauer of Rubicon Research and used -# with his kind permission -__version__=''' $Id$ ''' - - - -_bigBangScalar = -4345732 # based on (-9999, 1, 1) BC/BCE minimum -_bigCrunchScalar = 2958463 # based on (9999,12,31) AD/CE maximum -_daysInMonthNormal = [31,28,31,30,31,30,31,31,30,31,30,31] -_daysInMonthLeapYear = [31,29,31,30,31,30,31,31,30,31,30,31] -_dayOfWeekName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', - 'Friday', 'Saturday', 'Sunday'] -_monthName = ['January', 'February', 'March', 'April', 'May', 'June', - 'July','August','September','October','November','December'] - -from types import IntType, StringType, ListType, TupleType -import string, re, time -if hasattr(time,'struct_time'): - _DateSeqTypes = (ListType,TupleType,time.struct_time) -else: - _DateSeqTypes = (ListType,TupleType) - -_fmtPat = re.compile('\\{(m{1,5}|yyyy|yy|d{1,4})\\}',re.MULTILINE|re.IGNORECASE) -_iso_re = re.compile(r'(\d\d\d\d|\d\d)-(\d\d)-(\d\d)') - -def getStdMonthNames(): - return map(string.lower,_monthName) - -def getStdShortMonthNames(): - return map(lambda x: x[:3],getStdMonthNames()) - -def getStdDayNames(): - return map(string.lower,_dayOfWeekName) - -def getStdShortDayNames(): - return map(lambda x: x[:3],getStdDayNames()) - -def isLeapYear(year): - """determine if specified year is leap year, returns Python boolean""" - if year < 1600: - if year % 4: - return 0 - else: - return 1 - elif year % 4 != 0: - return 0 - elif year % 100 != 0: - return 1 - elif year % 400 != 0: - return 0 - else: - return 1 - -class NormalDateException(Exception): - """Exception class for NormalDate""" - pass - -class NormalDate: - """ - NormalDate is a specialized class to handle dates without - all the excess baggage (time zones, daylight savings, leap - seconds, etc.) of other date structures. The minimalist - strategy greatly simplifies its implementation and use. - - Internally, NormalDate is stored as an integer with values - in a discontinuous range of -99990101 to 99991231. The - integer value is used principally for storage and to simplify - the user interface. Internal calculations are performed by - a scalar based on Jan 1, 1900. - - Valid NormalDate ranges include (-9999,1,1) B.C.E. through - (9999,12,31) C.E./A.D. - - 1.0 - No changes, except the version number. After 3 years of use - by various parties I think we can consider it stable. - 0.8 - added Prof. Stephen Walton's suggestion for a range method - - module author resisted the temptation to use lambda <0.5 wink> - 0.7 - added Dan Winkler's suggestions for __add__, __sub__ methods - 0.6 - modifications suggested by Kevin Digweed to fix: - - dayOfWeek, dayOfWeekAbbrev, clone methods - - permit NormalDate to be a better behaved superclass - 0.5 - minor tweaking - 0.4 - added methods __cmp__, __hash__ - - added Epoch variable, scoped to the module - - added setDay, setMonth, setYear methods - 0.3 - minor touch-ups - 0.2 - fixed bug for certain B.C.E leap years - - added Jim Fulton's suggestions for short alias class name =ND - and __getstate__, __setstate__ methods - - Special thanks: Roedy Green - """ - def __init__(self, normalDate=None): - """ - Accept 1 of 4 values to initialize a NormalDate: - 1. None - creates a NormalDate for the current day - 2. integer in yyyymmdd format - 3. string in yyyymmdd format - 4. tuple in (yyyy, mm, dd) - localtime/gmtime can also be used - """ - if normalDate is None: - self.setNormalDate(time.localtime(time.time())) - else: - self.setNormalDate(normalDate) - - def add(self, days): - """add days to date; use negative integers to subtract""" - if not type(days) is IntType: - raise NormalDateException( \ - 'add method parameter must be integer type') - self.normalize(self.scalar() + days) - - def __add__(self, days): - """add integer to normalDate and return a new, calculated value""" - if not type(days) is IntType: - raise NormalDateException( \ - '__add__ parameter must be integer type') - cloned = self.clone() - cloned.add(days) - return cloned - - def __radd__(self,days): - '''for completeness''' - return self.__add__(days) - - def clone(self): - """return a cloned instance of this normalDate""" - return self.__class__(self.normalDate) - - def __cmp__(self, target): - if target is None: - return 1 - elif not hasattr(target, 'normalDate'): - return 1 - else: - return cmp(self.normalDate, target.normalDate) - - def day(self): - """return the day as integer 1-31""" - return int(repr(self.normalDate)[-2:]) - - def dayOfWeek(self): - """return integer representing day of week, Mon=0, Tue=1, etc.""" - return apply(dayOfWeek, self.toTuple()) - - def dayOfWeekAbbrev(self): - """return day of week abbreviation for current date: Mon, Tue, etc.""" - return _dayOfWeekName[self.dayOfWeek()][:3] - - def dayOfWeekName(self): - """return day of week name for current date: Monday, Tuesday, etc.""" - return _dayOfWeekName[self.dayOfWeek()] - - def dayOfYear(self): - """day of year""" - if self.isLeapYear(): - daysByMonth = _daysInMonthLeapYear - else: - daysByMonth = _daysInMonthNormal - priorMonthDays = 0 - for m in xrange(self.month() - 1): - priorMonthDays = priorMonthDays + daysByMonth[m] - return self.day() + priorMonthDays - - def daysBetweenDates(self, normalDate): - """ - return value may be negative, since calculation is - self.scalar() - arg - """ - if type(normalDate) is _NDType: - return self.scalar() - normalDate.scalar() - else: - return self.scalar() - NormalDate(normalDate).scalar() - - def equals(self, target): - if type(target) is _NDType: - if target is None: - return self.normalDate is None - else: - return self.normalDate == target.normalDate - else: - return 0 - - def endOfMonth(self): - """returns (cloned) last day of month""" - return self.__class__(self.__repr__()[-8:-2]+str(self.lastDayOfMonth())) - - def firstDayOfMonth(self): - """returns (cloned) first day of month""" - return self.__class__(self.__repr__()[-8:-2]+"01") - - def formatUS(self): - """return date as string in common US format: MM/DD/YY""" - d = self.__repr__() - return "%s/%s/%s" % (d[-4:-2], d[-2:], d[-6:-4]) - - def formatUSCentury(self): - """return date as string in 4-digit year US format: MM/DD/YYYY""" - d = self.__repr__() - return "%s/%s/%s" % (d[-4:-2], d[-2:], d[-8:-4]) - - def _fmtM(self): - return str(self.month()) - - def _fmtMM(self): - return '%02d' % self.month() - - def _fmtMMM(self): - return self.monthAbbrev() - - def _fmtMMMM(self): - return self.monthName() - - def _fmtMMMMM(self): - return self.monthName()[0] - - def _fmtD(self): - return str(self.day()) - - def _fmtDD(self): - return '%02d' % self.day() - - def _fmtDDD(self): - return self.dayOfWeekAbbrev() - - def _fmtDDDD(self): - return self.dayOfWeekName() - - def _fmtYY(self): - return '%02d' % (self.year()%100) - - def _fmtYYYY(self): - return str(self.year()) - - def formatMS(self,fmt): - '''format like MS date using the notation - {YY} --> 2 digit year - {YYYY} --> 4 digit year - {M} --> month as digit - {MM} --> 2 digit month - {MMM} --> abbreviated month name - {MMMM} --> monthname - {MMMMM} --> first character of monthname - {D} --> day of month as digit - {DD} --> 2 digit day of month - {DDD} --> abrreviated weekday name - {DDDD} --> weekday name - ''' - r = fmt[:] - f = 0 - while 1: - m = _fmtPat.search(r,f) - if m: - y = getattr(self,'_fmt'+string.upper(m.group()[1:-1]))() - i, j = m.span() - r = (r[0:i] + y) + r[j:] - f = i + len(y) - else: - return r - - def __getstate__(self): - """minimize persistent storage requirements""" - return self.normalDate - - def __hash__(self): - return hash(self.normalDate) - - def __int__(self): - return self.normalDate - - def isLeapYear(self): - """ - determine if specified year is leap year, returning true (1) or - false (0) - """ - return isLeapYear(self.year()) - - def _isValidNormalDate(self, normalDate): - """checks for date validity in [-]yyyymmdd format""" - if type(normalDate) is not IntType: - return 0 - if len(repr(normalDate)) > 9: - return 0 - if normalDate < 0: - dateStr = "%09d" % normalDate - else: - dateStr = "%08d" % normalDate - if len(dateStr) < 8: - return 0 - elif len(dateStr) == 9: - if (dateStr[0] != '-' and dateStr[0] != '+'): - return 0 - year = int(dateStr[:-4]) - if year < -9999 or year > 9999 or year == 0: - return 0 # note: zero (0) is not a valid year - month = int(dateStr[-4:-2]) - if month < 1 or month > 12: - return 0 - if isLeapYear(year): - maxDay = _daysInMonthLeapYear[month - 1] - else: - maxDay = _daysInMonthNormal[month - 1] - day = int(dateStr[-2:]) - if day < 1 or day > maxDay: - return 0 - if year == 1582 and month == 10 and day > 4 and day < 15: - return 0 # special case of 10 days dropped: Oct 5-14, 1582 - return 1 - - def lastDayOfMonth(self): - """returns last day of the month as integer 28-31""" - if self.isLeapYear(): - return _daysInMonthLeapYear[self.month() - 1] - else: - return _daysInMonthNormal[self.month() - 1] - - def localeFormat(self): - """override this method to use your preferred locale format""" - return self.formatUS() - - def month(self): - """returns month as integer 1-12""" - return int(repr(self.normalDate)[-4:-2]) - - def monthAbbrev(self): - """returns month as a 3-character abbreviation, i.e. Jan, Feb, etc.""" - return _monthName[self.month() - 1][:3] - - def monthName(self): - """returns month name, i.e. January, February, etc.""" - return _monthName[self.month() - 1] - - def normalize(self, scalar): - """convert scalar to normalDate""" - if scalar < _bigBangScalar: - msg = "normalize(%d): scalar below minimum" % \ - _bigBangScalar - raise NormalDateException(msg) - if scalar > _bigCrunchScalar: - msg = "normalize(%d): scalar exceeds maximum" % \ - _bigCrunchScalar - raise NormalDateException(msg) - from math import floor - if scalar >= -115860: - year = 1600 + int(floor((scalar + 109573) / 365.2425)) - elif scalar >= -693597: - year = 4 + int(floor((scalar + 692502) / 365.2425)) - else: - year = -4 + int(floor((scalar + 695058) / 365.2425)) - days = scalar - firstDayOfYear(year) + 1 - if days <= 0: - year = year - 1 - days = scalar - firstDayOfYear(year) + 1 - daysInYear = 365 - if isLeapYear(year): - daysInYear = daysInYear + 1 - if days > daysInYear: - year = year + 1 - days = scalar - firstDayOfYear(year) + 1 - # add 10 days if between Oct 15, 1582 and Dec 31, 1582 - if (scalar >= -115860 and scalar <= -115783): - days = days + 10 - if isLeapYear(year): - daysByMonth = _daysInMonthLeapYear - else: - daysByMonth = _daysInMonthNormal - dc = 0; month = 12 - for m in xrange(len(daysByMonth)): - dc = dc + daysByMonth[m] - if dc >= days: - month = m + 1 - break - # add up the days in prior months - priorMonthDays = 0 - for m in xrange(month - 1): - priorMonthDays = priorMonthDays + daysByMonth[m] - day = days - priorMonthDays - self.setNormalDate((year, month, day)) - - def range(self, days): - """Return a range of normalDates as a list. Parameter - may be an int or normalDate.""" - if type(days) is not IntType: - days = days - self # if not int, assume arg is normalDate type - r = [] - for i in range(days): - r.append(self + i) - return r - - def __repr__(self): - """print format: [-]yyyymmdd""" - # Note: When disassembling a NormalDate string, be sure to - # count from the right, i.e. epochMonth = int(`Epoch`[-4:-2]), - # or the slice won't work for dates B.C. - if self.normalDate < 0: - return "%09d" % self.normalDate - else: - return "%08d" % self.normalDate - - def scalar(self): - """days since baseline date: Jan 1, 1900""" - (year, month, day) = self.toTuple() - days = firstDayOfYear(year) + day - 1 - if self.isLeapYear(): - for m in xrange(month - 1): - days = days + _daysInMonthLeapYear[m] - else: - for m in xrange(month - 1): - days = days + _daysInMonthNormal[m] - if year == 1582: - if month > 10 or (month == 10 and day > 4): - days = days - 10 - return days - - def setDay(self, day): - """set the day of the month""" - maxDay = self.lastDayOfMonth() - if day < 1 or day > maxDay: - msg = "day is outside of range 1 to %d" % maxDay - raise NormalDateException(msg) - (y, m, d) = self.toTuple() - self.setNormalDate((y, m, day)) - - def setMonth(self, month): - """set the month [1-12]""" - if month < 1 or month > 12: - raise NormalDateException('month is outside range 1 to 12') - (y, m, d) = self.toTuple() - self.setNormalDate((y, month, d)) - - def setNormalDate(self, normalDate): - """ - accepts date as scalar string/integer (yyyymmdd) or tuple - (year, month, day, ...)""" - tn=type(normalDate) - if tn is IntType: - self.normalDate = normalDate - elif tn is StringType: - try: - self.normalDate = int(normalDate) - except: - m = _iso_re.match(normalDate) - if m: - self.setNormalDate(m.group(1)+m.group(2)+m.group(3)) - else: - raise NormalDateException("unable to setNormalDate(%s)" % `normalDate`) - elif tn in _DateSeqTypes: - self.normalDate = int("%04d%02d%02d" % normalDate[:3]) - elif tn is _NDType: - self.normalDate = normalDate.normalDate - if not self._isValidNormalDate(self.normalDate): - raise NormalDateException("unable to setNormalDate(%s)" % `normalDate`) - - def setYear(self, year): - if year == 0: - raise NormalDateException('cannot set year to zero') - elif year < -9999: - raise NormalDateException('year cannot be less than -9999') - elif year > 9999: - raise NormalDateException('year cannot be greater than 9999') - (y, m, d) = self.toTuple() - self.setNormalDate((year, m, d)) - - __setstate__ = setNormalDate - - def __sub__(self, v): - if type(v) is IntType: - return self.__add__(-v) - return self.scalar() - v.scalar() - - def __rsub__(self,v): - if type(v) is IntType: - return NormalDate(v) - self - else: - return v.scalar() - self.scalar() - - def toTuple(self): - """return date as (year, month, day) tuple""" - return (self.year(), self.month(), self.day()) - - def year(self): - """return year in yyyy format, negative values indicate B.C.""" - return int(repr(self.normalDate)[:-4]) - -################# Utility functions ################# - -def bigBang(): - """return lower boundary as a NormalDate""" - return NormalDate((-9999, 1, 1)) - -def bigCrunch(): - """return upper boundary as a NormalDate""" - return NormalDate((9999, 12, 31)) - -def dayOfWeek(y, m, d): - """return integer representing day of week, Mon=0, Tue=1, etc.""" - if m == 1 or m == 2: - m = m + 12 - y = y - 1 - return (d + 2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7 - -def firstDayOfYear(year): - """number of days to the first of the year, relative to Jan 1, 1900""" - if type(year) is not IntType: - msg = "firstDayOfYear() expected integer, got %s" % type(year) - raise NormalDateException(msg) - if year == 0: - raise NormalDateException('first day of year cannot be zero (0)') - elif year < 0: # BCE calculation - firstDay = (year * 365) + int((year - 1) / 4) - 693596 - else: # CE calculation - leapAdjust = int((year + 3) / 4) - if year > 1600: - leapAdjust = leapAdjust - int((year + 99 - 1600) / 100) + \ - int((year + 399 - 1600) / 400) - firstDay = year * 365 + leapAdjust - 693963 - if year > 1582: - firstDay = firstDay - 10 - return firstDay - -def FND(d): - '''convert to ND if required''' - return (type(d) is _NDType) and d or ND(d) - -Epoch=bigBang() -ND=NormalDate -_NDType = type(Epoch) -BDEpoch=ND(15821018) -BDEpochScalar = -115857 - -class BusinessDate(NormalDate): - """ - Specialised NormalDate - """ - def add(self, days): - """add days to date; use negative integers to subtract""" - if not type(days) is IntType: - raise NormalDateException('add method parameter must be integer type') - self.normalize(self.scalar() + days) - - def __add__(self, days): - """add integer to BusinessDate and return a new, calculated value""" - if not type(days) is IntType: - raise NormalDateException('__add__ parameter must be integer type') - cloned = self.clone() - cloned.add(days) - return cloned - - def __sub__(self, v): - return type(v) is IntType and self.__add__(-v) or self.scalar() - v.scalar() - - def asNormalDate(self): - return ND(self.normalDate) - - def daysBetweenDates(self, normalDate): - return self.asNormalDate.daysBetweenDates(normalDate) - - def _checkDOW(self): - if self.dayOfWeek()>4: raise NormalDateException("%s isn't a business day" % `self.normalDate`) - - def normalize(self, i): - i = int(i) - NormalDate.normalize(self,(i/5)*7+i%5+BDEpochScalar) - - def scalar(self): - d = self.asNormalDate() - i = d - BDEpoch #luckily BDEpoch is a Monday so we don't have a problem - #concerning the relative weekday - return 5*(i/7) + i%7 - - def setNormalDate(self, normalDate): - NormalDate.setNormalDate(self,normalDate) - self._checkDOW() - -if __name__ == '__main__': - today = NormalDate() - print "NormalDate test:" - print " Today (%s) is: %s %s" % (today, today.dayOfWeekAbbrev(), today.localeFormat()) - yesterday = today - 1 - print " Yesterday was: %s %s" % (yesterday.dayOfWeekAbbrev(), yesterday.localeFormat()) - tomorrow = today + 1 - print " Tomorrow will be: %s %s" % (tomorrow.dayOfWeekAbbrev(), tomorrow.localeFormat()) - print " Days between tomorrow and yesterday: %d" % (tomorrow - yesterday) - print today.formatMS('{d}/{m}/{yy}') - print today.formatMS('{dd}/{m}/{yy}') - print today.formatMS('{ddd} {d}/{m}/{yy}') - print today.formatMS('{dddd} {d}/{m}/{yy}') - print today.formatMS('{d}/{mm}/{yy}') - print today.formatMS('{d}/{mmm}/{yy}') - print today.formatMS('{d}/{mmmm}/{yy}') - print today.formatMS('{d}/{m}/{yyyy}') - b = BusinessDate('20010116') - print 'b=',b,'b.scalar()', b.scalar() \ No newline at end of file diff --git a/bin/reportlab/lib/pagesizes.py b/bin/reportlab/lib/pagesizes.py deleted file mode 100755 index 3bf7f8aadda..00000000000 --- a/bin/reportlab/lib/pagesizes.py +++ /dev/null @@ -1,55 +0,0 @@ -#! /usr/bin/python2.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/lib/pagesizes.py - -"""This module defines a few common page sizes in points (1/72 inch). -To be expanded to include things like label sizes, envelope windows -etc.""" -__version__=''' $Id$ ''' - -from reportlab.lib.units import cm, inch - -_W, _H = (21*cm, 29.7*cm) - -A6 = (_W*.5, _H*.5) -A5 = (_H*.5, _W) -A4 = (_W, _H) -A3 = (_H, _W*2) -A2 = (_W*2, _H*2) -A1 = (_H*2, _W*4) -A0 = (_W*4, _H*4) - -LETTER = (8.5*inch, 11*inch) -LEGAL = (8.5*inch, 14*inch) -ELEVENSEVENTEEN = (11*inch, 17*inch) -# lower case is deprecated as of 12/2001, but here -# for compatability -letter=LETTER -legal=LEGAL -elevenSeventeen = ELEVENSEVENTEEN - -_BW, _BH = (25*cm, 35.3*cm) -B6 = (_BW*.5, _BH*.5) -B5 = (_BH*.5, _BW) -B4 = (_BW, _BH) -B3 = (_BH*2, _BW) -B2 = (_BW*2, _BH*2) -B1 = (_BH*4, _BW*2) -B0 = (_BW*4, _BH*4) - -def landscape(pagesize): - """Use this to get page orientation right""" - a, b = pagesize - if a < b: - return (b, a) - else: - return (a, b) - -def portrait(pagesize): - """Use this to get page orientation right""" - a, b = pagesize - if a >= b: - return (b, a) - else: - return (a, b) \ No newline at end of file diff --git a/bin/reportlab/lib/randomtext.py b/bin/reportlab/lib/randomtext.py deleted file mode 100755 index b024d0cfa62..00000000000 --- a/bin/reportlab/lib/randomtext.py +++ /dev/null @@ -1,348 +0,0 @@ -#! /usr/bin/python2.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/lib/randomtext.py - -__version__=''' $Id$ ''' - -############################################################################### -# generates so-called 'Greek Text' for use in filling documents. -############################################################################### -""" -This module exposes a function randomText() which generates paragraphs. -These can be used when testing out document templates and stylesheets. -A number of 'themes' are provided - please contribute more! -We need some real Greek text too. - -There are currently six themes provided: - STARTUP (words suitable for a business plan - or not as the case may be), - COMPUTERS (names of programming languages and operating systems etc), - BLAH (variations on the word 'blah'), - BUZZWORD (buzzword bingo), - STARTREK (Star Trek), - PRINTING (print-related terms) - PYTHON (snippets and quotes from Monty Python) - CHOMSKY (random lingusitic nonsense) - -EXAMPLE USAGE: - from reportlab.lib import randomtext - print randomtext.randomText(randomtext.PYTHON, 10) - - This prints a random number of random sentences (up to a limit - of ten) using the theme 'PYTHON'. - -""" - -#theme one :-) -STARTUP = ['strategic', 'direction', 'proactive', 'venture capital', - 'reengineering', 'forecast', 'resources', 'SWOT analysis', - 'forward-thinking', 'profit', 'growth', 'doubletalk', 'B2B', 'B2C', - 'venture capital', 'IPO', "NASDAQ meltdown - we're all doomed!"] - -#theme two - computery things. -COMPUTERS = ['Python', 'Perl', 'Pascal', 'Java', 'Javascript', - 'VB', 'Basic', 'LISP', 'Fortran', 'ADA', 'APL', 'C', 'C++', - 'assembler', 'Larry Wall', 'Guido van Rossum', 'XML', 'HTML', - 'cgi', 'cgi-bin', 'Amiga', 'Macintosh', 'Dell', 'Microsoft', - 'firewall', 'server', 'Linux', 'Unix', 'MacOS', 'BeOS', 'AS/400', - 'sendmail', 'TCP/IP', 'SMTP', 'RFC822-compliant', 'dynamic', - 'Internet', 'A/UX', 'Amiga OS', 'BIOS', 'boot managers', 'CP/M', - 'DOS', 'file system', 'FreeBSD', 'Freeware', 'GEOS', 'GNU', - 'Hurd', 'Linux', 'Mach', 'Macintosh OS', 'mailing lists', 'Minix', - 'Multics', 'NetWare', 'NextStep', 'OS/2', 'Plan 9', 'Realtime', - 'UNIX', 'VMS', 'Windows', 'X Windows', 'Xinu', 'security', 'Intel', - 'encryption', 'PGP' , 'software', 'ActiveX', 'AppleScript', 'awk', - 'BETA', 'COBOL', 'Delphi', 'Dylan', 'Eiffel', 'extreme programming', - 'Forth', 'Fortran', 'functional languages', 'Guile', 'format your hard drive', - 'Icon', 'IDL', 'Infer', 'Intercal', 'J', 'Java', 'JavaScript', 'CD-ROM', - 'JCL', 'Lisp', '"literate programming"', 'Logo', 'MUMPS', 'C: drive', - 'Modula-2', 'Modula-3', 'Oberon', 'Occam', 'OpenGL', 'parallel languages', - 'Pascal', 'Perl', 'PL/I', 'PostScript', 'Prolog', 'hardware', 'Blue Screen of Death', - 'Rexx', 'RPG', 'Scheme', 'scripting languages', 'Smalltalk', 'crash!', 'disc crash', - 'Spanner', 'SQL', 'Tcl/Tk', 'TeX', 'TOM', 'Visual', 'Visual Basic', '4GL', - 'VRML', 'Virtual Reality Modeling Language', 'difference engine', '...went into "yo-yo mode"', - 'Sun', 'Sun Microsystems', 'Hewlett Packard', 'output device', - 'CPU', 'memory', 'registers', 'monitor', 'TFT display', 'plasma screen', - 'bug report', '"mis-feature"', '...millions of bugs!', 'pizza', - '"illiterate programming"','...lots of pizza!', 'pepperoni pizza', - 'coffee', 'Jolt Cola[TM]', 'beer', 'BEER!'] - -#theme three - 'blah' - for when you want to be subtle. :-) -BLAH = ['Blah', 'BLAH', 'blahblah', 'blahblahblah', 'blah-blah', - 'blah!', '"Blah Blah Blah"', 'blah-de-blah', 'blah?', 'blah!!!', - 'blah...', 'Blah.', 'blah;', 'blah, Blah, BLAH!', 'Blah!!!'] - -#theme four - 'buzzword bingo' time! -BUZZWORD = ['intellectual capital', 'market segment', 'flattening', - 'regroup', 'platform', 'client-based', 'long-term', 'proactive', - 'quality vector', 'out of the loop', 'implement', - 'streamline', 'cost-centered', 'phase', 'synergy', - 'synergize', 'interactive', 'facilitate', - 'appropriate', 'goal-setting', 'empowering', 'low-risk high-yield', - 'peel the onion', 'goal', 'downsize', 'result-driven', - 'conceptualize', 'multidisciplinary', 'gap analysis', 'dysfunctional', - 'networking', 'knowledge management', 'goal-setting', - 'mastery learning', 'communication', 'real-estate', 'quarterly', - 'scalable', 'Total Quality Management', 'best of breed', - 'nimble', 'monetize', 'benchmark', 'hardball', - 'client-centered', 'vision statement', 'empowerment', - 'lean & mean', 'credibility', 'synergistic', - 'backward-compatible', 'hardball', 'stretch the envelope', - 'bleeding edge', 'networking', 'motivation', 'best practice', - 'best of breed', 'implementation', 'Total Quality Management', - 'undefined', 'disintermediate', 'mindset', 'architect', - 'gap analysis', 'morale', 'objective', 'projection', - 'contribution', 'proactive', 'go the extra mile', 'dynamic', - 'world class', 'real estate', 'quality vector', 'credibility', - 'appropriate', 'platform', 'projection', 'mastery learning', - 'recognition', 'quality', 'scenario', 'performance based', - 'solutioning', 'go the extra mile', 'downsize', 'phase', - 'networking', 'experiencing slippage', 'knowledge management', - 'high priority', 'process', 'ethical', 'value-added', 'implement', - 're-factoring', 're-branding', 'embracing change'] - -#theme five - Star Trek -STARTREK = ['Starfleet', 'Klingon', 'Romulan', 'Cardassian', 'Vulcan', - 'Benzite', 'IKV Pagh', 'emergency transponder', 'United Federation of Planets', - 'Bolian', "K'Vort Class Bird-of-Prey", 'USS Enterprise', 'USS Intrepid', - 'USS Reliant', 'USS Voyager', 'Starfleet Academy', 'Captain Picard', - 'Captain Janeway', 'Tom Paris', 'Harry Kim', 'Counsellor Troi', - 'Lieutenant Worf', 'Lieutenant Commander Data', 'Dr. Beverly Crusher', - 'Admiral Nakamura', 'Irumodic Syndrome', 'Devron system', 'Admiral Pressman', - 'asteroid field', 'sensor readings', 'Binars', 'distress signal', 'shuttlecraft', - 'cloaking device', 'shuttle bay 2', 'Dr. Pulaski', 'Lwaxana Troi', 'Pacifica', - 'William Riker', "Chief O'Brian", 'Soyuz class science vessel', 'Wolf-359', - 'Galaxy class vessel', 'Utopia Planitia yards', 'photon torpedo', 'Archer IV', - 'quantum flux', 'spacedock', 'Risa', 'Deep Space Nine', 'blood wine', - 'quantum torpedoes', 'holodeck', 'Romulan Warbird', 'Betazoid', 'turbolift', 'battle bridge', - 'Memory Alpha', '...with a phaser!', 'Romulan ale', 'Ferrengi', 'Klingon opera', - 'Quark', 'wormhole', 'Bajoran', 'cruiser', 'warship', 'battlecruiser', '"Intruder alert!"', - 'scout ship', 'science vessel', '"Borg Invasion imminent!" ', '"Abandon ship!"', - 'Red Alert!', 'warp-core breech', '"All hands abandon ship! This is not a drill!"'] - -#theme six - print-related terms -PRINTING = ['points', 'picas', 'leading', 'kerning', 'CMYK', 'offset litho', - 'type', 'font family', 'typography', 'type designer', - 'baseline', 'white-out type', 'WOB', 'bicameral', 'bitmap', - 'blockletter', 'bleed', 'margin', 'body', 'widow', 'orphan', - 'cicero', 'cursive', 'letterform', 'sidehead', 'dingbat', 'leader', - 'DPI', 'drop-cap', 'paragraph', 'En', 'Em', 'flush left', 'left justified', - 'right justified', 'centered', 'italic', 'Latin letterform', 'ligature', - 'uppercase', 'lowercase', 'serif', 'sans-serif', 'weight', 'type foundry', - 'fleuron', 'folio', 'gutter', 'whitespace', 'humanist letterform', 'caption', - 'page', 'frame', 'ragged setting', 'flush-right', 'rule', 'drop shadows', - 'prepress', 'spot-colour', 'duotones', 'colour separations', 'four-colour printing', - 'Pantone[TM]', 'service bureau', 'imagesetter'] - -#it had to be done!... -#theme seven - the "full Monty"! -PYTHON = ['Good evening ladies and Bruces','I want to buy some cheese', 'You do have some cheese, do you?', - "Of course sir, it's a cheese shop sir, we've got...",'discipline?... naked? ... With a melon!?', - 'The Church Police!!' , "There's a dead bishop on the landing", 'Would you like a twist of lemming sir?', - '"Conquistador Coffee brings a new meaning to the word vomit"','Your lupins please', - 'Crelm Toothpaste, with the miracle ingredient Fraudulin', - "Well there's the first result and the Silly Party has held Leicester.", - 'Hello, I would like to buy a fish license please', "Look, it's people like you what cause unrest!", - "When we got home, our Dad would thrash us to sleep with his belt!", 'Luxury', "Gumby Brain Specialist", - "My brain hurts!!!", "My brain hurts too.", "How not to be seen", - "In this picture there are 47 people. None of them can be seen", - "Mrs Smegma, will you stand up please?", - "Mr. Nesbitt has learned the first lesson of 'Not Being Seen', not to stand up.", - "My hovercraft is full of eels", "Ah. You have beautiful thighs.", "My nipples explode with delight", - "Drop your panties Sir William, I cannot wait 'til lunchtime", - "I'm a completely self-taught idiot.", "I always wanted to be a lumberjack!!!", - "Told you so!! Oh, coitus!!", "", - "Nudge nudge?", "Know what I mean!", "Nudge nudge, nudge nudge?", "Say no more!!", - "Hello, well it's just after 8 o'clock, and time for the penguin on top of your television set to explode", - "Oh, intercourse the penguin!!", "Funny that penguin being there, isn't it?", - "I wish to register a complaint.", "Now that's what I call a dead parrot", "Pining for the fjords???", - "No, that's not dead, it's ,uhhhh, resting", "This is an ex-parrot!!", - "That parrot is definitely deceased.", "No, no, no - it's spelt Raymond Luxury Yach-t, but it's pronounced 'Throatwobbler Mangrove'.", - "You're a very silly man and I'm not going to interview you.", "No Mungo... never kill a customer." - "And I'd like to conclude by putting my finger up my nose", - "egg and Spam", "egg bacon and Spam", "egg bacon sausage and Spam", "Spam bacon sausage and Spam", - "Spam egg Spam Spam bacon and Spam", "Spam sausage Spam Spam Spam bacon Spam tomato and Spam", - "Spam Spam Spam egg and Spam", "Spam Spam Spam Spam Spam Spam baked beans Spam Spam Spam", - "Spam!!", "I don't like Spam!!!", "You can't have egg, bacon, Spam and sausage without the Spam!", - "I'll have your Spam. I Love it!", - "I'm having Spam Spam Spam Spam Spam Spam Spam baked beans Spam Spam Spam and Spam", - "Have you got anything without Spam?", "There's Spam egg sausage and Spam, that's not got much Spam in it.", - "No one expects the Spanish Inquisition!!", "Our weapon is surprise, surprise and fear!", - "Get the comfy chair!", "Amongst our weaponry are such diverse elements as: fear, surprise, ruthless efficiency, an almost fanatical devotion to the Pope, and nice red uniforms - Oh damn!", - "Nobody expects the... Oh bugger!", "What swims in the sea and gets caught in nets? Henri Bergson?", - "Goats. Underwater goats with snorkels and flippers?", "A buffalo with an aqualung?", - "Dinsdale was a looney, but he was a happy looney.", "Dinsdale!!", - "The 127th Upper-Class Twit of the Year Show", "What a great Twit!", - "thought by many to be this year's outstanding twit", - "...and there's a big crowd here today to see these prize idiots in action.", - "And now for something completely different.", "Stop that, it's silly", - "We interrupt this program to annoy you and make things generally irritating", - "This depraved and degrading spectacle is going to stop right now, do you hear me?", - "Stop right there!", "This is absolutely disgusting and I'm not going to stand for it", - "I object to all this sex on the television. I mean, I keep falling off", - "Right! Stop that, it's silly. Very silly indeed", "Very silly indeed", "Lemon curry?", - "And now for something completely different, a man with 3 buttocks", - "I've heard of unisex, but I've never had it", "That's the end, stop the program! Stop it!"] -leadins=[ - "To characterize a linguistic level L,", - "On the other hand,", - "This suggests that", - "It appears that", - "Furthermore,", - "We will bring evidence in favor of the following thesis: ", - "To provide a constituent structure for T(Z,K),", - "From C1, it follows that", - "For any transformation which is sufficiently diversified in application to be of any interest,", - "Analogously,", - "Clearly,", - "Note that", - "Of course,", - "Suppose, for instance, that", - "Thus", - "With this clarification,", - "Conversely,", - "We have already seen that", - "By combining adjunctions and certain deformations,", - "I suggested that these results would follow from the assumption that", - "If the position of the trace in (99c) were only relatively inaccessible to movement,", - "However, this assumption is not correct, since", - "Comparing these examples with their parasitic gap counterparts in (96) and (97), we see that", - "In the discussion of resumptive pronouns following (81),", - "So far,", - "Nevertheless,", - "For one thing,", - "Summarizing, then, we assume that", - "A consequence of the approach just outlined is that", - "Presumably,", - "On our assumptions,", - "It may be, then, that", - "It must be emphasized, once again, that", - "Let us continue to suppose that", - "Notice, incidentally, that", - "A majority of informed linguistic specialists agree that", - ] - -subjects = [ - "the notion of level of grammaticalness", - "a case of semigrammaticalness of a different sort", - "most of the methodological work in modern linguistics", - "a subset of English sentences interesting on quite independent grounds", - "the natural general principle that will subsume this case", - "an important property of these three types of EC", - "any associated supporting element", - "the appearance of parasitic gaps in domains relatively inaccessible to ordinary extraction", - "the speaker-hearer's linguistic intuition", - "the descriptive power of the base component", - "the earlier discussion of deviance", - "this analysis of a formative as a pair of sets of features", - "this selectionally introduced contextual feature", - "a descriptively adequate grammar", - "the fundamental error of regarding functional notions as categorial", - "relational information", - "the systematic use of complex symbols", - "the theory of syntactic features developed earlier", - ] - -verbs= [ - "can be defined in such a way as to impose", - "delimits", - "suffices to account for", - "cannot be arbitrary in", - "is not subject to", - "does not readily tolerate", - "raises serious doubts about", - "is not quite equivalent to", - "does not affect the structure of", - "may remedy and, at the same time, eliminate", - "is not to be considered in determining", - "is to be regarded as", - "is unspecified with respect to", - "is, apparently, determined by", - "is necessary to impose an interpretation on", - "appears to correlate rather closely with", - "is rather different from", - ] - -objects = [ - "problems of phonemic and morphological analysis.", - "a corpus of utterance tokens upon which conformity has been defined by the paired utterance test.", - "the traditional practice of grammarians.", - "the levels of acceptability from fairly high (e.g. (99a)) to virtual gibberish (e.g. (98d)).", - "a stipulation to place the constructions into these various categories.", - "a descriptive fact.", - "a parasitic gap construction.", - "the extended c-command discussed in connection with (34).", - "the ultimate standard that determines the accuracy of any proposed grammar.", - "the system of base rules exclusive of the lexicon.", - "irrelevant intervening contexts in selectional rules.", - "nondistinctness in the sense of distinctive feature theory.", - "a general convention regarding the forms of the grammar.", - "an abstract underlying order.", - "an important distinction in language use.", - "the requirement that branching is not tolerated within the dominance scope of a complex symbol.", - "the strong generative capacity of the theory.", - ] - -def format_wisdom(text,line_length=72): - try: - import textwrap - return textwrap.fill(text, line_length) - except: - return text - -def chomsky(times = 1): - if not isinstance(times, int): - return format_wisdom(__doc__) - import random - prevparts = [] - newparts = [] - output = [] - for i in xrange(times): - for partlist in (leadins, subjects, verbs, objects): - while 1: - part = random.choice(partlist) - if part not in prevparts: - break - newparts.append(part) - output.append(' '.join(newparts)) - prevparts = newparts - newparts = [] - return format_wisdom(' '.join(output)) - -from reportlab import rl_config -if rl_config.invariant: - if not getattr(rl_config,'_random',None): - rl_config._random = 1 - import random - random.seed(2342471922L) - del random -del rl_config - -def randomText(theme=STARTUP, sentences=5): - #this may or may not be appropriate in your company - if type(theme)==type(''): - if theme.lower()=='chomsky': return chomsky(sentences) - elif theme.upper() in ('STARTUP','COMPUTERS','BLAH','BUZZWORD','STARTREK','PRINTING','PYTHON'): - theme = globals()[theme] - else: - raise ValueError('Unknown theme "%s"' % theme) - - from random import randint, choice - - RANDOMWORDS = theme - - #sentences = 5 - output = "" - for sentenceno in range(randint(1,sentences)): - output = output + 'Blah' - for wordno in range(randint(10,25)): - if randint(0,4)==0: - word = choice(RANDOMWORDS) - else: - word = 'blah' - output = output + ' ' +word - output = output+'. ' - return output - -if __name__=='__main__': - print chomsky(5) diff --git a/bin/reportlab/lib/rparsexml.py b/bin/reportlab/lib/rparsexml.py deleted file mode 100644 index 95871442435..00000000000 --- a/bin/reportlab/lib/rparsexml.py +++ /dev/null @@ -1,440 +0,0 @@ -"""Radically simple xml parsing - -Example parse - -text in xml - -( "this", - {"type": "xml"}, - [ "text ", - ("b", None, ["in"], None), - " xml" - ] - None ) - -{ 0: "this" - "type": "xml" - 1: ["text ", - {0: "b", 1:["in"]}, - " xml"] -} - -Ie, xml tag translates to a tuple: - (name, dictofattributes, contentlist, miscellaneousinfo) - -where miscellaneousinfo can be anything, (but defaults to None) -(with the intention of adding, eg, line number information) - -special cases: name of "" means "top level, no containing tag". -Top level parse always looks like this - - ("", list, None, None) - - contained text of None means - -In order to support stuff like - - - -AT THE MOMENT & ETCETERA ARE IGNORED. THEY MUST BE PROCESSED -IN A POST-PROCESSING STEP. - -PROLOGUES ARE NOT UNDERSTOOD. OTHER STUFF IS PROBABLY MISSING. -""" - -RequirePyRXP = 0 # set this to 1 to disable the nonvalidating fallback parser. - -import string -try: - #raise ImportError, "dummy error" - simpleparse = 0 - import pyRXP - if pyRXP.version>='0.5': - def warnCB(s): - print s - pyRXP_parser = pyRXP.Parser( - ErrorOnValidityErrors=1, - NoNoDTDWarning=1, - ExpandCharacterEntities=0, - ExpandGeneralEntities=0, - warnCB = warnCB, - srcName='string input') - def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None): - pyRXP_parser.eoCB = eoCB - p = pyRXP_parser.parse(xmlText) - return oneOutermostTag and p or ('',None,[p],None) - else: - def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None): - '''eoCB is the entity open callback''' - def warnCB(s): - print s - flags = 0x0157e1ff | pyRXP.PARSER_FLAGS['ErrorOnValidityErrors'] - for k in ('ExpandCharacterEntities','ExpandGeneralEntities'): - flags = flags & (~pyRXP.PARSER_FLAGS[k]) - p = pyRXP.parse(xmlText,srcName='string input',flags=flags,warnCB=warnCB,eoCB=eoCB) - return oneOutermostTag and p or ('',None,[p],None) -except ImportError: - simpleparse = 1 - -NONAME = "" -NAMEKEY = 0 -CONTENTSKEY = 1 -CDATAMARKER = "" -replacelist = [("<", "<"), (">", ">"), ("&", "&")] # amp must be last -#replacelist = [] -def unEscapeContentList(contentList): - result = [] - from string import replace - for e in contentList: - if "&" in e: - for (old, new) in replacelist: - e = replace(e, old, new) - result.append(e) - return result - -def parsexmlSimple(xmltext, oneOutermostTag=0,eoCB=None,entityReplacer=unEscapeContentList): - """official interface: discard unused cursor info""" - if RequirePyRXP: - raise ImportError, "pyRXP not found, fallback parser disabled" - (result, cursor) = parsexml0(xmltext,entityReplacer=entityReplacer) - if oneOutermostTag: - return result[2][0] - else: - return result - -if simpleparse: - parsexml = parsexmlSimple - -def parseFile(filename): - raw = open(filename, 'r').read() - return parsexml(raw) - -verbose = 0 - -def skip_prologue(text, cursor): - """skip any prologue found after cursor, return index of rest of text""" - ### NOT AT ALL COMPLETE!!! definitely can be confused!!! - from string import find - prologue_elements = ("!DOCTYPE", "?xml", "!--") - done = None - while done is None: - #print "trying to skip:", repr(text[cursor:cursor+20]) - openbracket = find(text, "<", cursor) - if openbracket<0: break - past = openbracket+1 - found = None - for e in prologue_elements: - le = len(e) - if text[past:past+le]==e: - found = 1 - cursor = find(text, ">", past) - if cursor<0: - raise ValueError, "can't close prologue %s" % `e` - cursor = cursor+1 - if found is None: - done=1 - #print "done skipping" - return cursor - -def parsexml0(xmltext, startingat=0, toplevel=1, - # snarf in some globals - strip=string.strip, split=string.split, find=string.find, entityReplacer=unEscapeContentList, - #len=len, None=None - #LENCDATAMARKER=LENCDATAMARKER, CDATAMARKER=CDATAMARKER - ): - """simple recursive descent xml parser... - return (dictionary, endcharacter) - special case: comment returns (None, endcharacter)""" - #from string import strip, split, find - #print "parsexml0", `xmltext[startingat: startingat+10]` - # DEFAULTS - NameString = NONAME - ContentList = AttDict = ExtraStuff = None - if toplevel is not None: - #if verbose: print "at top level" - #if startingat!=0: - # raise ValueError, "have to start at 0 for top level!" - xmltext = strip(xmltext) - cursor = startingat - #look for interesting starting points - firstbracket = find(xmltext, "<", cursor) - afterbracket2char = xmltext[firstbracket+1:firstbracket+3] - #print "a", `afterbracket2char` - #firstampersand = find(xmltext, "&", cursor) - #if firstampersand>0 and firstampersand0: - #afterbracket2char = xmltext[firstbracket:firstbracket+2] - if toplevel is not None: - #print "toplevel with no outer tag" - NameString = name = NONAME - cursor = skip_prologue(xmltext, cursor) - #break - elif firstbracket<0: - raise ValueError, "non top level entry should be at start tag: %s" % repr(xmltext[:10]) - # special case: CDATA - elif afterbracket2char=="![" and xmltext[firstbracket:firstbracket+9]=="": - raise ValueError, "invalid comment: contains double dashes %s" % repr(xmltext[cursor:cursor+20]) - return (None, endcomment+1) # shortcut exit - else: - # get the rest of the tag - #if verbose: print "parsing start tag" - # make sure the tag isn't in doublequote pairs - closebracket = find(xmltext, ">", firstbracket) - noclose = closebracket<0 - startsearch = closebracket+1 - pastfirstbracket = firstbracket+1 - tagcontent = xmltext[pastfirstbracket:closebracket] - # shortcut, no equal means nothing but name in the tag content - if '=' not in tagcontent: - if tagcontent[-1]=="/": - # simple case - #print "simple case", tagcontent - tagcontent = tagcontent[:-1] - docontents = None - name = strip(tagcontent) - NameString = name - cursor = startsearch - else: - if '"' in tagcontent: - # check double quotes - stop = None - # not inside double quotes! (the split should have odd length) - if noclose or len(split(tagcontent+".", '"'))% 2: - stop=1 - while stop is None: - closebracket = find(xmltext, ">", startsearch) - startsearch = closebracket+1 - noclose = closebracket<0 - tagcontent = xmltext[pastfirstbracket:closebracket] - # not inside double quotes! (the split should have odd length) - if noclose or len(split(tagcontent+".", '"'))% 2: - stop=1 - if noclose: - raise ValueError, "unclosed start tag %s" % repr(xmltext[firstbracket:firstbracket+20]) - cursor = startsearch - #cursor = closebracket+1 - # handle simple tag /> syntax - if xmltext[closebracket-1]=="/": - #if verbose: print "it's a simple tag" - closebracket = closebracket-1 - tagcontent = tagcontent[:-1] - docontents = None - #tagcontent = xmltext[firstbracket+1:closebracket] - tagcontent = strip(tagcontent) - taglist = split(tagcontent, "=") - #if not taglist: - # raise ValueError, "tag with no name %s" % repr(xmltext[firstbracket:firstbracket+20]) - taglist0 = taglist[0] - taglist0list = split(taglist0) - #if len(taglist0list)>2: - # raise ValueError, "bad tag head %s" % repr(taglist0) - name = taglist0list[0] - #print "tag name is", name - NameString = name - # now parse the attributes - attributename = taglist0list[-1] - # put a fake att name at end of last taglist entry for consistent parsing - taglist[-1] = taglist[-1]+" f" - AttDict = D = {} - taglistindex = 1 - lasttaglistindex = len(taglist) - #for attentry in taglist[1:]: - while taglistindexlasttaglistindex: - raise ValueError, "unclosed value " + repr(attentry) - nextattentry = taglist[taglistindex] - taglistindex = taglistindex+1 - attentry = "%s=%s" % (attentry, nextattentry) - attentry = strip(attentry) # only needed for while loop... - attlist = split(attentry) - nextattname = attlist[-1] - attvalue = attentry[:-len(nextattname)] - attvalue = strip(attvalue) - try: - first = attvalue[0]; last=attvalue[-1] - except: - raise ValueError, "attvalue,attentry,attlist="+repr((attvalue, attentry,attlist)) - if first==last=='"' or first==last=="'": - attvalue = attvalue[1:-1] - #print attributename, "=", attvalue - D[attributename] = attvalue - attributename = nextattname - # pass over other tags and content looking for end tag - if docontents is not None: - #print "now looking for end tag" - ContentList = L - while docontents is not None: - nextopenbracket = find(xmltext, "<", cursor) - if nextopenbracket", nextopenbracket) - if nextclosebracket\n%s\n" % (name, attributes, textpprint, name) - # otherwise must be a simple tag - return "<%s %s/>" % (name, attributes) - -dump = 0 -def testparse(s): - from time import time - from pprint import pprint - now = time() - D = parsexmlSimple(s) - print "DONE", time()-now - if dump&4: - pprint(D) - #pprint(D) - if dump&1: - print "============== reformatting" - p = pprettyprint(D) - print p - -def test(): - testparse("""text <>in xml - - text in xml ]]> - just testing brackets feature - """) - -filenames = [ #"../../reportlab/demos/pythonpoint/pythonpoint.xml", - "samples/hamlet.xml"] - -#filenames = ["moa.xml"] - -dump=1 -if __name__=="__main__": - test() - from time import time - now = time() - for f in filenames: - t = open(f).read() - print "parsing", f - testparse(t) - print "elapsed", time()-now diff --git a/bin/reportlab/lib/sequencer.py b/bin/reportlab/lib/sequencer.py deleted file mode 100644 index a27485cfe26..00000000000 --- a/bin/reportlab/lib/sequencer.py +++ /dev/null @@ -1,284 +0,0 @@ -#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/lib/sequencer.py -__version__=''' $Id$ ''' -"""This module defines a single public class, Sequencer, which aids in -numbering and formatting lists.""" -# -# roman numbers conversion thanks to -# -# fredrik lundh, november 1996 (based on a C hack from 1984) -# -# fredrik@pythonware.com -# http://www.pythonware.com - -_RN_TEMPLATES = [ 0, 01, 011, 0111, 012, 02, 021, 0211, 02111, 013 ] -_RN_LETTERS = "IVXLCDM" - -from string import lower - - -def _format_I(value): - if value < 0 or value > 3999: - raise ValueError, "illegal value" - str = "" - base = -1 - while value: - value, index = divmod(value, 10) - tmp = _RN_TEMPLATES[index] - while tmp: - tmp, index = divmod(tmp, 8) - str = _RN_LETTERS[index+base] + str - base = base + 2 - return str - -def _format_i(num): - return lower(_format_I(num)) - -def _format_123(num): - """The simplest formatter""" - return str(num) - -def _format_ABC(num): - """Uppercase. Wraps around at 26.""" - n = (num -1) % 26 - return chr(n+65) - -def _format_abc(num): - """Lowercase. Wraps around at 26.""" - n = (num -1) % 26 - return chr(n+97) - - -class _Counter: - """Private class used by Sequencer. Each counter - knows its format, and the IDs of anything it - resets, as well as its value. Starts at zero - and increments just before you get the new value, - so that it is still 'Chapter 5' and not 'Chapter 6' - when you print 'Figure 5.1'""" - - def __init__(self): - self._base = 0 - self._value = self._base - self._formatter = _format_123 - self._resets = [] - - def setFormatter(self, formatFunc): - self._formatter = formatFunc - - def reset(self, value=None): - if value: - self._value = value - else: - self._value = self._base - - def next(self): - self._value = self._value + 1 - v = self._value - for counter in self._resets: - counter.reset() - return v - - def _this(self): - return self._value - - def nextf(self): - """Returns next value formatted""" - return self._formatter(self.next()) - - def thisf(self): - return self._formatter(self._this()) - - def chain(self, otherCounter): - if not otherCounter in self._resets: - self._resets.append(otherCounter) - - -class Sequencer: - """Something to make it easy to number paragraphs, sections, - images and anything else. The features include registering - new string formats for sequences, and 'chains' whereby - some counters are reset when their parents. - It keeps track of a number of - 'counters', which are created on request: - Usage: - >>> seq = layout.Sequencer() - >>> seq.next('Bullets') - 1 - >>> seq.next('Bullets') - 2 - >>> seq.next('Bullets') - 3 - >>> seq.reset('Bullets') - >>> seq.next('Bullets') - 1 - >>> seq.next('Figures') - 1 - >>> - """ - - def __init__(self): - self._counters = {} #map key to current number - self._defaultCounter = None - - self._formatters = { - # the formats it knows initially - '1':_format_123, - 'A':_format_ABC, - 'a':_format_abc, - 'I':_format_I, - 'i':_format_i, - } - - def _getCounter(self, counter=None): - """Creates one if not present""" - try: - return self._counters[counter] - except KeyError: - cnt = _Counter() - self._counters[counter] = cnt - return cnt - - def _this(self, counter=None): - """Retrieves counter value but does not increment. For - new counters, sets base value to 1.""" - if not counter: - counter = self._defaultCounter - return self._getCounter(counter)._this() - - def next(self, counter=None): - """Retrieves the numeric value for the given counter, then - increments it by one. New counters start at one.""" - if not counter: - counter = self._defaultCounter - return self._getCounter(counter).next() - - def thisf(self, counter=None): - if not counter: - counter = self._defaultCounter - return self._getCounter(counter).thisf() - - def nextf(self, counter=None): - """Retrieves the numeric value for the given counter, then - increments it by one. New counters start at one.""" - if not counter: - counter = self._defaultCounter - return self._getCounter(counter).nextf() - - def setDefaultCounter(self, default=None): - """Changes the key used for the default""" - self._defaultCounter = default - - def registerFormat(self, format, func): - """Registers a new formatting function. The funtion - must take a number as argument and return a string; - fmt is a short menmonic string used to access it.""" - self._formatters[format] = func - - def setFormat(self, counter, format): - """Specifies that the given counter should use - the given format henceforth.""" - func = self._formatters[format] - self._getCounter(counter).setFormatter(func) - - def reset(self, counter=None, base=0): - if not counter: - counter = self._defaultCounter - self._getCounter(counter)._value = base - - def chain(self, parent, child): - p = self._getCounter(parent) - c = self._getCounter(child) - p.chain(c) - - def __getitem__(self, key): - """Allows compact notation to support the format function. - s['key'] gets current value, s['key+'] increments.""" - if key[-1:] == '+': - counter = key[:-1] - return self.nextf(counter) - else: - return self.thisf(key) - - def format(self, template): - """The crowning jewels - formats multi-level lists.""" - return template % self - - def dump(self): - """Write current state to stdout for diagnostics""" - counters = self._counters.items() - counters.sort() - print 'Sequencer dump:' - for (key, counter) in counters: - print ' %s: value = %d, base = %d, format example = %s' % ( - key, counter._this(), counter._base, counter.thisf()) - - -"""Your story builder needs to set this to""" -_sequencer = None - -def getSequencer(): - global _sequencer - if _sequencer is None: - _sequencer = Sequencer() - return _sequencer - -def setSequencer(seq): - global _sequencer - s = _sequencer - _sequencer = seq - return s - -def test(): - s = Sequencer() - print 'Counting using default sequence: %d %d %d' % (s.next(),s.next(), s.next()) - print 'Counting Figures: Figure %d, Figure %d, Figure %d' % ( - s.next('figure'), s.next('figure'), s.next('figure')) - print 'Back to default again: %d' % s.next() - s.setDefaultCounter('list1') - print 'Set default to list1: %d %d %d' % (s.next(),s.next(), s.next()) - s.setDefaultCounter() - print 'Set default to None again: %d %d %d' % (s.next(),s.next(), s.next()) - print - print 'Creating Appendix counter with format A, B, C...' - s.setFormat('Appendix', 'A') - print ' Appendix %s, Appendix %s, Appendix %s' % ( - s.nextf('Appendix'), s.nextf('Appendix'),s.nextf('Appendix')) - - def format_french(num): - return ('un','deux','trois','quatre','cinq')[(num-1)%5] - print - print 'Defining a custom format with french words:' - s.registerFormat('french', format_french) - s.setFormat('FrenchList', 'french') - print ' ', - for i in range(1,6): - print s.nextf('FrenchList'), - print - print 'Chaining H1 and H2 - H2 goes back to one when H1 increases' - s.chain('H1','H2') - print ' H1 = %d' % s.next('H1') - print ' H2 = %d' % s.next('H2') - print ' H2 = %d' % s.next('H2') - print ' H2 = %d' % s.next('H2') - print ' H1 = %d' % s.next('H1') - print ' H2 = %d' % s.next('H2') - print ' H2 = %d' % s.next('H2') - print ' H2 = %d' % s.next('H2') - print - print 'GetItem notation - append a plus to increment' - print ' seq["Appendix"] = %s' % s["Appendix"] - print ' seq["Appendix+"] = %s' % s["Appendix+"] - print ' seq["Appendix+"] = %s' % s["Appendix+"] - print ' seq["Appendix"] = %s' % s["Appendix"] - print - print 'Finally, string format notation for nested lists. Cool!' - print 'The expression ("Figure %(Chapter)s.%(Figure+)s" % seq) gives:' - print ' Figure %(Chapter)s.%(Figure+)s' % s - print ' Figure %(Chapter)s.%(Figure+)s' % s - print ' Figure %(Chapter)s.%(Figure+)s' % s - - -if __name__=='__main__': - test() diff --git a/bin/reportlab/lib/set_ops.py b/bin/reportlab/lib/set_ops.py deleted file mode 100755 index 8f8834839ef..00000000000 --- a/bin/reportlab/lib/set_ops.py +++ /dev/null @@ -1,38 +0,0 @@ -#! /usr/bin/python2.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/lib/set_ops.py -import types -import string - -def __set_coerce(t, S): - if t is types.ListType: - return list(S) - elif t is types.TupleType: - return tuple(S) - elif t is types.StringType: - return string.join(S, '') - return S - -def unique(seq): - result = [] - for i in seq: - if i not in result: - result.append(i) - return __set_coerce(type(seq), result) - -def intersect(seq1, seq2): - result = [] - if type(seq1) != type(seq2) and type(seq2) == types.StringType: seq2 = list(seq2) - for i in seq1: - if i in seq2 and i not in result: result.append(i) - return __set_coerce(type(seq1), result) - -def union(seq1, seq2): - if type(seq1) == type(seq2): - return unique(seq1 + seq2) - if type(seq1) == types.ListType or type(seq2) == types.ListType: - return unique(list(seq1) + list(seq2)) - if type(seq1) == types.TupleType or type(seq2) == types.TupleType: - return unique(tuple(seq1) + tuple(seq2)) - return unique(list(seq1) + list(seq2)) diff --git a/bin/reportlab/lib/styles.py b/bin/reportlab/lib/styles.py deleted file mode 100644 index f0074b0996a..00000000000 --- a/bin/reportlab/lib/styles.py +++ /dev/null @@ -1,256 +0,0 @@ -#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/lib/styles.py -__version__=''' $Id$ ''' - -from reportlab.lib.colors import white, black -from reportlab.lib.enums import TA_LEFT, TA_CENTER - -########################################################### -# This class provides an 'instance inheritance' -# mechanism for its descendants, simpler than acquisition -# but not as far-reaching -########################################################### -class PropertySet: - defaults = {} - - def __init__(self, name, parent=None, **kw): - """When initialized, it copies the class defaults; - then takes a copy of the attributes of the parent - if any. All the work is done in init - styles - should cost little to use at runtime.""" - # step one - validate the hell out of it - assert not self.defaults.has_key('name'), "Class Defaults may not contain a 'name' attribute" - assert not self.defaults.has_key('parent'), "Class Defaults may not contain a 'parent' attribute" - if parent: - assert parent.__class__ == self.__class__, "Parent style must have same class as new style" - - #step two - self.name = name - self.parent = parent - self.__dict__.update(self.defaults) - - #step two - copy from parent if any. Try to be - # very strict that only keys in class defaults are - # allowed, so they cannot inherit - self.refresh() - - #step three - copy keywords if any - for (key, value) in kw.items(): - self.__dict__[key] = value - - - def __repr__(self): - return "<%s '%s'>" % (self.__class__.__name__, self.name) - - def refresh(self): - """re-fetches attributes from the parent on demand; - use if you have been hacking the styles. This is - used by __init__""" - if self.parent: - for (key, value) in self.parent.__dict__.items(): - if (key not in ['name','parent']): - self.__dict__[key] = value - - - def listAttrs(self, indent=''): - print indent + 'name =', self.name - print indent + 'parent =', self.parent - keylist = self.__dict__.keys() - keylist.sort() - keylist.remove('name') - keylist.remove('parent') - for key in keylist: - value = self.__dict__.get(key, None) - print indent + '%s = %s' % (key, value) - -class ParagraphStyle(PropertySet): - defaults = { - 'fontName':'Times-Roman', - 'fontSize':10, - 'leading':12, - 'leftIndent':0, - 'rightIndent':0, - 'firstLineIndent':0, - 'alignment':TA_LEFT, - 'spaceBefore':0, - 'spaceAfter':0, - 'bulletFontName':'Times-Roman', - 'bulletFontSize':10, - 'bulletIndent':0, - 'textColor': black, - 'backColor':None - } - -class LineStyle(PropertySet): - defaults = { - 'width':1, - 'color': black - } - def prepareCanvas(self, canvas): - """You can ask a LineStyle to set up the canvas for drawing - the lines.""" - canvas.setLineWidth(1) - #etc. etc. - -class StyleSheet1: - """This may or may not be used. The idea is to - 1. slightly simplify construction of stylesheets; - 2. enforce rules to validate styles when added - (e.g. we may choose to disallow having both - 'heading1' and 'Heading1' - actual rules are - open to discussion); - 3. allow aliases and alternate style lookup - mechanisms - 4. Have a place to hang style-manipulation - methods (save, load, maybe support a GUI - editor) - Access is via getitem, so they can be - compatible with plain old dictionaries. - """ - def __init__(self): - self.byName = {} - self.byAlias = {} - - - def __getitem__(self, key): - try: - return self.byAlias[key] - except KeyError: - try: - return self.byName[key] - except KeyError: - raise KeyError, "Style '%s' not found in stylesheet" % key - - def has_key(self, key): - if self.byAlias.has_key(key): - return 1 - elif self.byName.has_key(key): - return 1 - else: - return 0 - - def add(self, style, alias=None): - key = style.name - if self.byName.has_key(key): - raise KeyError, "Style '%s' already defined in stylesheet" % key - if self.byAlias.has_key(key): - raise KeyError, "Style name '%s' is already an alias in stylesheet" % key - - if alias: - if self.byName.has_key(alias): - raise KeyError, "Style '%s' already defined in stylesheet" % alias - if self.byAlias.has_key(alias): - raise KeyError, "Alias name '%s' is already an alias in stylesheet" % alias - #passed all tests? OK, add it - self.byName[key] = style - if alias: - self.byAlias[alias] = style - - def list(self): - styles = self.byName.items() - styles.sort() - alii = {} - for (alias, style) in self.byAlias.items(): - alii[style] = alias - for (name, style) in styles: - alias = alii.get(style, None) - print name, alias - style.listAttrs(' ') - print - - - - -def testStyles(): - pNormal = ParagraphStyle('Normal',None) - pNormal.fontName = 'Times-Roman' - pNormal.fontSize = 12 - pNormal.leading = 14.4 - - pNormal.listAttrs() - print - pPre = ParagraphStyle('Literal', pNormal) - pPre.fontName = 'Courier' - pPre.listAttrs() - return pNormal, pPre - -def getSampleStyleSheet(): - """Returns a stylesheet object""" - stylesheet = StyleSheet1() - - stylesheet.add(ParagraphStyle(name='Normal', - fontName='Times-Roman', - fontSize=10, - leading=12) - ) - - stylesheet.add(ParagraphStyle(name='BodyText', - parent=stylesheet['Normal'], - spaceBefore=6) - ) - stylesheet.add(ParagraphStyle(name='Italic', - parent=stylesheet['BodyText'], - fontName = 'Times-Italic') - ) - - stylesheet.add(ParagraphStyle(name='Heading1', - parent=stylesheet['Normal'], - fontName = 'Times-Bold', - fontSize=18, - leading=22, - spaceAfter=6), - alias='h1') - - stylesheet.add(ParagraphStyle(name='Title', - parent=stylesheet['Normal'], - fontName = 'Times-Bold', - fontSize=18, - leading=22, - alignment=TA_CENTER, - spaceAfter=6), - alias='title') - - stylesheet.add(ParagraphStyle(name='Heading2', - parent=stylesheet['Normal'], - fontName = 'Times-Bold', - fontSize=14, - leading=18, - spaceBefore=12, - spaceAfter=6), - alias='h2') - - stylesheet.add(ParagraphStyle(name='Heading3', - parent=stylesheet['Normal'], - fontName = 'Times-BoldItalic', - fontSize=12, - leading=14, - spaceBefore=12, - spaceAfter=6), - alias='h3') - - stylesheet.add(ParagraphStyle(name='Bullet', - parent=stylesheet['Normal'], - firstLineIndent=0, - spaceBefore=3), - alias='bu') - - stylesheet.add(ParagraphStyle(name='Definition', - parent=stylesheet['Normal'], - firstLineIndent=0, - leftIndent=36, - bulletIndent=0, - spaceBefore=6, - bulletFontName='Times-BoldItalic'), - alias='df') - - stylesheet.add(ParagraphStyle(name='Code', - parent=stylesheet['Normal'], - fontName='Courier', - fontSize=8, - leading=8.8, - firstLineIndent=0, - leftIndent=36)) - - - return stylesheet diff --git a/bin/reportlab/lib/tocindex.py b/bin/reportlab/lib/tocindex.py deleted file mode 100644 index 17ff861bf01..00000000000 --- a/bin/reportlab/lib/tocindex.py +++ /dev/null @@ -1,294 +0,0 @@ -# Tables of Contents and Indices -#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/lib/tocindex.py -__version__=''' $Id$ ''' -__doc__='' -""" -This module will contain standard Table of Contents and Index objects. -under development, and pending certain hooks adding in DocTemplate -As of today, it onyl handles the formatting aspect of TOCs -""" - -import string - -from reportlab.platypus import Flowable, BaseDocTemplate, Paragraph, \ - PageBreak, Frame, PageTemplate, NextPageTemplate -from reportlab.platypus.doctemplate import IndexingFlowable -from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet -from reportlab.platypus import tables -from reportlab.lib import enums -from reportlab.lib import colors -from reportlab.lib.units import inch, cm -from reportlab.rl_config import defaultPageSize - - ############################################################## - # - # we first define a paragraph style for each level of the - # table, and one for the table as whole; you can supply your - # own. - # - ############################################################## - - -levelZeroParaStyle = ParagraphStyle(name='LevelZero', - fontName='Times-Roman', - fontSize=10, - leading=12) -levelOneParaStyle = ParagraphStyle(name='LevelOne', - parent = levelZeroParaStyle, - firstLineIndent = 0, - leftIndent = 18) -levelTwoParaStyle = ParagraphStyle(name='LevelTwo', - parent = levelOneParaStyle, - firstLineIndent = 0, - leftIndent = 36) -levelThreeParaStyle = ParagraphStyle(name='LevelThree', - parent = levelTwoParaStyle, - firstLineIndent = 0, - leftIndent = 54) - -defaultTableStyle = tables.TableStyle([ - ('VALIGN',(0,0),(-1,-1),'TOP'), - ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), - ('BOX', (0,0), (-1,-1), 0.25, colors.black), - ]) - - - - - - -class TableOfContents0(IndexingFlowable): - """This creates a formatted table of contents. It presumes - a correct block of data is passed in. The data block contains - a list of (level, text, pageNumber) triplets. You can supply - a paragraph style for each level (starting at zero).""" - def __init__(self): - self.entries = [] - self.rightColumnWidth = 72 - self.levelStyles = [levelZeroParaStyle, - levelOneParaStyle, - levelTwoParaStyle, - levelThreeParaStyle] - self.tableStyle = defaultTableStyle - self._table = None - self._entries = [] - self._lastEntries = [] - - def beforeBuild(self): - # keep track of the last run - self._lastEntries = self._entries[:] - self.clearEntries() - - def isIndexing(self): - return 1 - - def isSatisfied(self): - return (self._entries == self._lastEntries) - - def notify(self, kind, stuff): - """DocTemplate framework can call this with all kinds - of events; we say we are interested in 'TOCEntry' - events.""" - if kind == 'TOCEntry': - (level, text, pageNum) = stuff - self.addEntry(level, text, pageNum) - #print 'TOC notified of ', stuff -## elif kind == 'debug': -## # hack to watch its state -## import pprint -## print 'Last Entries first 5:' -## for (level, text, pageNum) in self._lastEntries[0:5]: -## print (level, text[0:30], pageNum), -## print -## print 'Current Entries first 5:' -## for (level, text, pageNum) in self._lastEntries[0:5]: -## print (level, text[0:30], pageNum), - - - def clearEntries(self): - self._entries = [] - - def addEntry(self, level, text, pageNum): - """Adds one entry; allows incremental buildup by a doctemplate. - Requires that enough styles are defined.""" - assert type(level) == type(1), "Level must be an integer" - assert level < len(self.levelStyles), \ - "Table of contents must have a style defined " \ - "for paragraph level %d before you add an entry" % level - self._entries.append((level, text, pageNum)) - - def addEntries(self, listOfEntries): - """Bulk creation. If you knew the titles but - not the page numbers, you could supply them to - get sensible output on the first run.""" - for (level, text, pageNum) in listOfEntries: - self.addEntry(level, text, pageNum) - - def wrap(self, availWidth, availHeight): - """All table properties should be known by now.""" - widths = (availWidth - self.rightColumnWidth, - self.rightColumnWidth) - - # makes an internal table which does all the work. - # we draw the LAST RUN's entries! If there are - # none, we make some dummy data to keep the table - # from complaining - if len(self._lastEntries) == 0: - _tempEntries = [(0,'Placeholder for table of contents',0)] - else: - _tempEntries = self._lastEntries - tableData = [] - for (level, text, pageNum) in _tempEntries: - leftColStyle = self.levelStyles[level] - #right col style is right aligned - rightColStyle = ParagraphStyle(name='leftColLevel%d' % level, - parent=leftColStyle, - leftIndent=0, - alignment=enums.TA_RIGHT) - leftPara = Paragraph(text, leftColStyle) - rightPara = Paragraph(str(pageNum), rightColStyle) - tableData.append([leftPara, rightPara]) - self._table = tables.Table(tableData, colWidths=widths, - style=self.tableStyle) - self.width, self.height = self._table.wrap(availWidth, availHeight) - return (self.width, self.height) - - def split(self, availWidth, availHeight): - """At this stage we do not care about splitting the entries, - we wil just return a list of platypus tables. Presumably the - calling app has a pointer to the original TableOfContents object; - Platypus just sees tables.""" - return self._table.split(availWidth, availHeight) - - def drawOn(self, canvas, x, y, _sW=0): - """Don't do this at home! The standard calls for implementing - draw(); we are hooking this in order to delegate ALL the drawing - work to the embedded table object""" - self._table.drawOn(canvas, x, y, _sW) - - - - ################################################################################# - # - # everything from here down is concerned with creating a good example document - # i.e. test code as well as tutorial -PAGE_HEIGHT = defaultPageSize[1] -def getSampleTOCData(depth=3): - """Returns a longish block of page numbers and headings over 3 levels""" - from random import randint - pgNum = 2 - data = [] - for chapter in range(1,8): - data.append((0, """Chapter %d with a really long name which will hopefully - wrap onto a second line, fnding out if the right things happen with - full paragraphs n the table of contents""" % chapter, pgNum)) - pgNum = pgNum + randint(0,2) - if depth > 1: - for section in range(1,5): - data.append((1, 'Chapter %d Section %d' % (chapter, section), pgNum)) - pgNum = pgNum + randint(0,2) - if depth > 2: - for subSection in range(1,6): - data.append(2, 'Chapter %d Section %d Subsection %d' % - (chapter, section, subSection), - pgNum) - pgNum = pgNum + randint(0,1) - from pprint import pprint as pp - pp(data) - return data - - -def getSampleStory(depth=3): - """Makes a story with lots of paragraphs. Uses the random - TOC data and makes paragraphs to correspond to each.""" - from reportlab.platypus.doctemplate import randomText - from random import randint - - styles = getSampleStyleSheet() - TOCData = getSampleTOCData(depth) - - story = [Paragraph("This is a demo of the table of contents object", - styles['Heading1'])] - - toc = TableOfContents0() # empty on first pass - #toc.addEntries(TOCData) # init with random page numbers - story.append(toc) - - # the next full page should switch to internal page style - story.append(NextPageTemplate("Body")) - - # now make some paragraphs to correspond to it - for (level, text, pageNum) in TOCData: - if level == 0: - #page break before chapter - story.append(PageBreak()) - - headingStyle = (styles['Heading1'], styles['Heading2'], styles['Heading3'])[level] - headingPara = Paragraph(text, headingStyle) - story.append(headingPara) - # now make some body text - for i in range(randint(1,6)): - bodyPara = Paragraph(randomText(), - styles['Normal']) - story.append(bodyPara) - - return story - -class MyDocTemplate(BaseDocTemplate): - """Example of how to do the indexing. Need the onDraw hook - to find out which things are drawn on which pages""" - def afterInit(self): - """Set up the page templates""" - frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal') - self.addPageTemplates([PageTemplate(id='Front',frames=frameT), - PageTemplate(id='Body',frames=frameT) - ]) - # just need a unique key generator for outline entries; - # easiest is to count all flowables in afterFlowable - # and set up a counter variable here - self._uniqueKey = 0 - - - def afterFlowable(self, flowable): - """Our rule for the table of contents is simply to take - the text of H1, H2 and H3 elements. We broadcast a - notification to the DocTemplate, which should inform - the TOC and let it pull them out. Also build an outline""" - self._uniqueKey = self._uniqueKey + 1 - - if hasattr(flowable, 'style'): - if flowable.style.name == 'Heading1': - self.notify('TOCEntry', (0, flowable.getPlainText(), self.page)) - self.canv.bookmarkPage(str(self._uniqueKey)) - self.canv.addOutlineEntry(flowable.getPlainText()[0:10], str(self._uniqueKey), 0) - - elif flowable.style.name == 'Heading2': - self.notify('TOCEntry', (1, flowable.getPlainText(), self.page)) - self.canv.bookmarkPage(str(self._uniqueKey)) - self.canv.addOutlineEntry(flowable.getPlainText(), str(self._uniqueKey), 1) - - elif flowable.style.name == 'Heading3': - self.notify('TOCEntry', (2, flowable.getPlainText(), self.page)) - self.canv.bookmarkPage(str(self._uniqueKey)) - self.canv.addOutlineEntry(flowable.getPlainText(), str(self._uniqueKey), 2) - - def beforePage(self): - """decorate the page""" - self.canv.saveState() - self.canv.setStrokeColor(colors.red) - self.canv.setLineWidth(5) - self.canv.line(66,72,66,PAGE_HEIGHT-72) - self.canv.setFont('Times-Roman',12) - self.canv.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page) - self.canv.restoreState() - -if __name__=='__main__': - from reportlab.platypus import SimpleDocTemplate - doc = MyDocTemplate('tocindex.pdf') - - #change this to depth=3 for a BIG document - story = getSampleStory(depth=2) - - doc.multiBuild(story, 'tocindex.pdf') \ No newline at end of file diff --git a/bin/reportlab/lib/units.py b/bin/reportlab/lib/units.py deleted file mode 100755 index 1d9bbd5c347..00000000000 --- a/bin/reportlab/lib/units.py +++ /dev/null @@ -1,23 +0,0 @@ -#! /usr/bin/python2.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/lib/units.py -__version__=''' $Id$ ''' - -inch = 72.0 -cm = inch / 2.54 -mm = cm * 0.1 -pica = 12.0 - -def toLength(s): - '''convert a string to a length''' - try: - if s[-2:]=='cm': return float(s[:-2])*cm - if s[-2:]=='in': return float(s[:-2])*inch - if s[-2:]=='pt': return float(s[:-2]) - if s[-1:]=='i': return float(s[:-1])*inch - if s[-2:]=='mm': return float(s[:-2])*mm - if s[-4:]=='pica': return float(s[:-2])*pica - return float(s) - except: - raise ValueError, "Can't convert '%s' to length" % s \ No newline at end of file diff --git a/bin/reportlab/lib/utils.py b/bin/reportlab/lib/utils.py deleted file mode 100644 index b8b2748e00b..00000000000 --- a/bin/reportlab/lib/utils.py +++ /dev/null @@ -1,776 +0,0 @@ -#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/lib/utils.py -__version__=''' $Id$ ''' - -import string, os, sys, imp -from types import * -from reportlab.lib.logger import warnOnce -SeqTypes = (ListType,TupleType) -if sys.hexversion<0x2030000: - True = 1 - False = 0 - -def _findFiles(dirList,ext='.ttf'): - from os.path import isfile, isdir, join as path_join - from os import listdir - ext = ext.lower() - R = [] - A = R.append - for D in dirList: - if not isdir(D): continue - for fn in listdir(D): - fn = path_join(D,fn) - if isfile(fn) and (not ext or fn.lower().endswith(ext)): A(fn) - return R - -try: - _UserDict = dict -except: - from UserDict import UserDict as _UserDict - -class CIDict(_UserDict): - def __init__(self,*a,**kw): - map(self.update, a) - self.update(kw) - - def update(self,D): - for k,v in D.items(): self[k] = v - - def __setitem__(self,k,v): - try: - k = k.lower() - except: - pass - _UserDict.__setitem__(self,k,v) - - def __getitem__(self,k): - try: - k = k.lower() - except: - pass - return _UserDict.__getitem__(self,k) - - def __delitem__(self,k): - try: - k = k.lower() - except: - pass - return _UserDict.__delitem__(self,k) - - def get(self,k,dv=None): - try: - return self[k] - except KeyError: - return dv - - def has_key(self,k): - try: - self[k] - return True - except: - return False - - def pop(self,k,*a): - try: - k = k.lower() - except: - pass - return _UserDict.pop(*((self,k)+a)) - - def setdefault(self,k,*a): - try: - k = k.lower() - except: - pass - return _UserDict.setdefault(*((self,k)+a)) - -if os.name == 'mac': - #with the Mac, we need to tag the file in a special - #way so the system knows it is a PDF file. - #This supplied by Joe Strout - import macfs, macostools - _KNOWN_MAC_EXT = { - 'BMP' : ('ogle','BMP '), - 'EPS' : ('ogle','EPSF'), - 'EPSF': ('ogle','EPSF'), - 'GIF' : ('ogle','GIFf'), - 'JPG' : ('ogle','JPEG'), - 'JPEG': ('ogle','JPEG'), - 'PCT' : ('ttxt','PICT'), - 'PICT': ('ttxt','PICT'), - 'PNG' : ('ogle','PNGf'), - 'PPM' : ('ogle','.PPM'), - 'TIF' : ('ogle','TIFF'), - 'TIFF': ('ogle','TIFF'), - 'PDF' : ('CARO','PDF '), - 'HTML': ('MSIE','TEXT'), - } - def markfilename(filename,creatorcode=None,filetype=None,ext='PDF'): - try: - if creatorcode is None or filetype is None and ext is not None: - try: - creatorcode, filetype = _KNOWN_MAC_EXT[string.upper(ext)] - except: - return - macfs.FSSpec(filename).SetCreatorType(creatorcode,filetype) - macostools.touched(filename) - except: - pass -else: - def markfilename(filename,creatorcode=None,filetype=None): - pass - -import reportlab -__RL_DIR=os.path.dirname(reportlab.__file__) #possibly relative -_RL_DIR=os.path.isabs(__RL_DIR) and __RL_DIR or os.path.abspath(__RL_DIR) -del reportlab - -#Attempt to detect if this copy of reportlab is running in a -#file system (as opposed to mostly running in a zip or McMillan -#archive or Jar file). This is used by test cases, so that -#we can write test cases that don't get activated in a compiled -try: - __file__ -except: - __file__ = sys.argv[0] -import glob, fnmatch -try: - _isFSD = not __loader__ - _archive = __loader__.archive - _archivepfx = _archive + os.sep - _archivedir = os.path.dirname(__loader__.archive) - _archivedirpfx = _archivedir + os.sep - _archivepfxlen = len(_archivepfx) - _archivedirpfxlen = len(_archivedirpfx) - def __startswith_rl(fn, - _archivepfx=os.path.normcase(_archivepfx), - _archivedirpfx=os.path.normcase(_archivedirpfx), - _archive=os.path.normcase(_archive), - _archivedir=os.path.normcase(_archivedir), - os_path_normpath=os.path.normpath, - os_path_normcase=os.path.normcase, - os_getcwd=os.getcwd, - os_sep=os.sep, - os_sep_len = len(os.sep)): - '''if the name starts with a known prefix strip it off''' - fn = os_path_normpath(fn.replace('/',os_sep)) - nfn = os_path_normcase(fn) - if nfn in (_archivedir,_archive): return 1,'' - if nfn.startswith(_archivepfx): return 1,fn[_archivepfxlen:] - if nfn.startswith(_archivedirpfx): return 1,fn[_archivedirpfxlen:] - cwd = os_path_normcase(os_getcwd()) - n = len(cwd) - if nfn.startswith(cwd): - if fn[n:].startswith(os_sep): return 1, fn[n+os_sep_len:] - if n==len(fn): return 1,'' - return not os.path.isabs(fn),fn - - def _startswith_rl(fn): - return __startswith_rl(fn)[1] - - def rl_glob(pattern,glob=glob.glob,fnmatch=fnmatch.fnmatch, _RL_DIR=_RL_DIR,pjoin=os.path.join): - c, pfn = __startswith_rl(pattern) - r = glob(pfn) - if c or r==[]: - r += map(lambda x,D=_archivepfx,pjoin=pjoin: pjoin(_archivepfx,x),filter(lambda x,pfn=pfn,fnmatch=fnmatch: fnmatch(x,pfn),__loader__._files.keys())) - return r -except: - _isFSD = os.path.isfile(__file__) #slight risk of wrong path - __loader__ = None - def _startswith_rl(fn): - return fn - def rl_glob(pattern,glob=glob.glob): - return glob(pattern) -del glob, fnmatch -_isFSSD = _isFSD and os.path.isfile(os.path.splitext(__file__)[0] +'.py') - -def isFileSystemDistro(): - '''return truth if a file system distribution''' - return _isFSD - -def isCompactDistro(): - '''return truth if not a file system distribution''' - return not _isFSD - -def isSourceDistro(): - '''return truth if a source file system distribution''' - return _isFSSD - -try: - #raise ImportError - ### NOTE! FP_STR SHOULD PROBABLY ALWAYS DO A PYTHON STR() CONVERSION ON ARGS - ### IN CASE THEY ARE "LAZY OBJECTS". ACCELLERATOR DOESN'T DO THIS (YET) - try: - from _rl_accel import fp_str # in case of builtin version - except ImportError: - from reportlab.lib._rl_accel import fp_str # specific -except ImportError: - from math import log - _log_10 = lambda x,log=log,_log_e_10=log(10.0): log(x)/_log_e_10 - _fp_fmts = "%.0f", "%.1f", "%.2f", "%.3f", "%.4f", "%.5f", "%.6f" - import re - _tz_re = re.compile('0+$') - del re - def fp_str(*a): - if len(a)==1 and type(a[0]) in SeqTypes: a = a[0] - s = [] - A = s.append - for i in a: - sa =abs(i) - if sa<=1e-7: A('0') - else: - l = sa<=1 and 6 or min(max(0,(6-int(_log_10(sa)))),6) - n = _fp_fmts[l]%i - if l: - n = _tz_re.sub('',n) - try: - if n[-1]=='.': n = n[:-1] - except: - print i, n - raise - A((n[0]!='0' or len(n)==1) and n or n[1:]) - return string.join(s) - -#hack test for comma users -if ',' in fp_str(0.25): - _FP_STR = fp_str - def fp_str(*a): - return string.replace(apply(_FP_STR,a),',','.') - -_rl_tempdir=None -def get_rl_tempdir(*subdirs): - global _rl_tempdir - if _rl_tempdir is None: - import tempfile - _rl_tempdir = os.path.join(tempfile.gettempdir(),'ReportLab_tmp%s' % (sys.platform=='unix' and `os.getuid()` or '')) - d = _rl_tempdir - if subdirs: d = os.path.join(*((d,)+subdirs)) - try: - os.makedirs(d) - except: - pass - return d - -def get_rl_tempfile(fn=None): - if not fn: - import tempfile - fn = tempfile.mktemp() - return os.path.join(get_rl_tempdir(),fn) - -def recursiveImport(modulename, baseDir=None, noCWD=0, debug=0): - """Dynamically imports possible packagized module, or raises ImportError""" - normalize = lambda x: os.path.normcase(os.path.abspath(os.path.normpath(x))) - path = map(normalize,sys.path) - if baseDir: - if type(baseDir) not in SeqTypes: - tp = [baseDir] - else: - tp = filter(None,list(baseDir)) - for p in tp: - p = normalize(p) - if p not in path: path.insert(0,p) - - if noCWD: - for p in ('','.',normalize('.')): - while p in path: - if debug: print 'removed "%s" from path' % p - path.remove(p) - elif '.' not in path: - path.insert(0,'.') - - if debug: - import pprint - pp = pprint.pprint - print 'path=', - pp(path) - - #make import errors a bit more informative - opath = sys.path - try: - sys.path = path - exec 'import %s\nm = %s\n' % (modulename,modulename) in locals() - sys.path = opath - return m - except ImportError: - sys.path = opath - msg = "recursiveimport(%s,baseDir=%s) failed" % (modulename,baseDir) - if baseDir: - msg = msg + " under paths '%s'" % `path` - raise ImportError, msg - -def recursiveGetAttr(obj, name): - "Can call down into e.g. object1.object2[4].attr" - return eval(name, obj.__dict__) - -def recursiveSetAttr(obj, name, value): - "Can call down into e.g. object1.object2[4].attr = value" - #get the thing above last. - tokens = string.split(name, '.') - if len(tokens) == 1: - setattr(obj, name, value) - else: - most = string.join(tokens[:-1], '.') - last = tokens[-1] - parent = recursiveGetAttr(obj, most) - setattr(parent, last, value) - -def import_zlib(): - try: - import zlib - except ImportError: - zlib = None - from reportlab.rl_config import ZLIB_WARNINGS - if ZLIB_WARNINGS: warnOnce('zlib not available') - return zlib - - -# Image Capability Detection. Set a flag haveImages -# to tell us if either PIL or Java imaging libraries present. -# define PIL_Image as either None, or an alias for the PIL.Image -# module, as there are 2 ways to import it - -if sys.platform[0:4] == 'java': - try: - import javax.imageio - import java.awt.image - haveImages = 1 - except: - haveImages = 0 -else: - try: - from PIL import Image - except ImportError: - try: - import Image - except ImportError: - Image = None - haveImages = Image is not None - if haveImages: del Image - - -__StringIO=None -def getStringIO(buf=None): - '''unified StringIO instance interface''' - global __StringIO - if not __StringIO: - try: - from cStringIO import StringIO - except ImportError: - from StringIO import StringIO - __StringIO = StringIO - return buf is not None and __StringIO(buf) or __StringIO() - -class ArgvDictValue: - '''A type to allow clients of getArgvDict to specify a conversion function''' - def __init__(self,value,func): - self.value = value - self.func = func - -def getArgvDict(**kw): - ''' Builds a dictionary from its keyword arguments with overrides from sys.argv. - Attempts to be smart about conversions, but the value can be an instance - of ArgDictValue to allow specifying a conversion function. - ''' - def handleValue(v,av,func): - if func: - v = func(av) - else: - t = type(v) - if t is StringType: - v = av - elif t is FloatType: - v = float(av) - elif t is IntType: - v = int(av) - elif t is ListType: - v = list(eval(av)) - elif t is TupleType: - v = tuple(eval(av)) - else: - raise TypeError, "Can't convert string '%s' to %s" % (av,str(t)) - return v - - A = sys.argv[1:] - R = {} - for k, v in kw.items(): - if isinstance(v,ArgvDictValue): - v, func = v.value, v.func - else: - func = None - handled = 0 - ke = k+'=' - for a in A: - if string.find(a,ke)==0: - av = a[len(ke):] - A.remove(a) - R[k] = handleValue(v,av,func) - handled = 1 - break - - if not handled: R[k] = handleValue(v,v,func) - - return R - -def getHyphenater(hDict=None): - try: - from reportlab.lib.pyHnj import Hyphen - if hDict is None: hDict=os.path.join(os.path.dirname(__file__),'hyphen.mashed') - return Hyphen(hDict) - except ImportError, errMsg: - if str(errMsg)!='No module named pyHnj': raise - return None - -def _className(self): - '''Return a shortened class name''' - try: - name = self.__class__.__name__ - i=string.rfind(name,'.') - if i>=0: return name[i+1:] - return name - except AttributeError: - return str(self) - -def open_for_read_by_name(name,mode='b'): - if 'r' not in mode: mode = 'r'+mode - try: - return open(name,mode) - except IOError: - if _isFSD or __loader__ is None: raise - #we have a __loader__, perhaps the filename starts with - #the dirname(reportlab.__file__) or is relative - name = _startswith_rl(name) - s = __loader__.get_data(name) - if 'b' not in mode and os.linesep!='\n': s = s.replace(os.linesep,'\n') - return getStringIO(s) - -import urllib -def open_for_read(name,mode='b', urlopen=urllib.urlopen): - '''attempt to open a file or URL for reading''' - if hasattr(name,'read'): return name - try: - return open_for_read_by_name(name,mode) - except: - try: - return getStringIO(urlopen(name).read()) - except: - raise IOError('Cannot open resource "%s"' % name) -del urllib - -def open_and_read(name,mode='b'): - return open_for_read(name,mode).read() - -def open_and_readlines(name,mode='t'): - return open_and_read(name,mode).split('\n') - -def rl_isfile(fn,os_path_isfile=os.path.isfile): - if hasattr(fn,'read'): return True - if os_path_isfile(fn): return True - if _isFSD or __loader__ is None: return False - fn = _startswith_rl(fn) - return fn in __loader__._files.keys() - -def rl_isdir(pn,os_path_isdir=os.path.isdir,os_path_normpath=os.path.normpath): - if os_path_isdir(pn): return True - if _isFSD or __loader__ is None: return False - pn = _startswith_rl(os_path_normpath(pn)) - if not pn.endswith(os.sep): pn += os.sep - return len(filter(lambda x,pn=pn: x.startswith(pn),__loader__._files.keys()))>0 - -def rl_get_module(name,dir): - if sys.modules.has_key(name): - om = sys.modules[name] - del sys.modules[name] - else: - om = None - try: - f = None - try: - f, p, desc= imp.find_module(name,[dir]) - return imp.load_module(name,f,p,desc) - except: - if isCompactDistro(): - #attempt a load from inside the zip archive - import zipimport - dir = _startswith_rl(dir) - dir = (dir=='.' or not dir) and _archive or os.path.join(_archive,dir.replace('/',os.sep)) - zi = zipimport.zipimporter(dir) - return zi.load_module(name) - raise ImportError('%s[%s]' % (name,dir)) - finally: - if om: sys.modules[name] = om - del om - if f: f.close() - -class ImageReader: - "Wraps up either PIL or Java to get data from bitmaps" - def __init__(self, fileName): - if not haveImages: - warnOnce('Imaging Library not available, unable to import bitmaps') - return - #start wih lots of null private fields, to be populated by - #the relevant engine. - self.fileName = fileName - self._image = None - self._width = None - self._height = None - self._transparent = None - self._data = None - self.fp = open_for_read(fileName,'b') - - #detect which library we are using and open the image - if sys.platform[0:4] == 'java': - from javax.imageio import ImageIO - self._image = ImageIO.read(self.fp) - else: - import PIL.Image - self._image = PIL.Image.open(self.fp) - - def getSize(self): - if (self._width is None or self._height is None): - if sys.platform[0:4] == 'java': - self._width = self._image.getWidth() - self._height = self._image.getHeight() - else: - self._width, self._height = self._image.size - return (self._width, self._height) - - def getRGBData(self): - "Return byte array of RGB data as string" - if self._data is None: - if sys.platform[0:4] == 'java': - import jarray - from java.awt.image import PixelGrabber - width, height = self.getSize() - buffer = jarray.zeros(width*height, 'i') - pg = PixelGrabber(self._image, 0,0,width,height,buffer,0,width) - pg.grabPixels() - # there must be a way to do this with a cast not a byte-level loop, - # I just haven't found it yet... - pixels = [] - a = pixels.append - for i in range(len(buffer)): - rgb = buffer[i] - a(chr((rgb>>16)&0xff)) - a(chr((rgb>>8)&0xff)) - a(chr(rgb&0xff)) - self._data = ''.join(pixels) - else: - rgb = self._image.convert('RGB') - self._data = rgb.tostring() - return self._data - - def getImageData(self): - width, height = self.getSize() - return width, height, self.getRGBData() - - def getTransparent(self): - if sys.platform[0:4] == 'java': - return None - else: - if self._image.info.has_key("transparency"): - transparency = self._image.info["transparency"] * 3 - palette = self._image.palette - try: - palette = palette.palette - except: - palette = palette.data - return map(ord, palette[transparency:transparency+3]) - else: - return None - -def getImageData(imageFileName): - "Get width, height and RGB pixels from image file. Wraps Java/PIL" - return ImageReader.getImageData(imageFileName) - -class DebugMemo: - '''Intended as a simple report back encapsulator - - Typical usages - 1) To record error data - dbg = DebugMemo(fn='dbgmemo.dbg',myVar=value) - dbg.add(anotherPayload='aaaa',andagain='bbb') - dbg.dump() - - 2) To show the recorded info - dbg = DebugMemo(fn='dbgmemo.dbg',mode='r') - dbg.load() - dbg.show() - - 3) To re-use recorded information - dbg = DebugMemo(fn='dbgmemo.dbg',mode='r') - dbg.load() - myTestFunc(dbg.payload('myVar'),dbg.payload('andagain')) - - in addition to the payload variables the dump records many useful bits - of information which are also printed in the show() method. - ''' - def __init__(self,fn='rl_dbgmemo.dbg',mode='w',getScript=1,modules=(),capture_traceback=1, stdout=None, **kw): - import time, socket - self.fn = fn - if mode!='w': return - if not stdout: - self.stdout = sys.stdout - else: - if hasattr(stdout,'write'): - self.stdout = stdout - else: - self.stdout = open(stdout,'w') - self.store = store = {} - if capture_traceback and sys.exc_info() != (None,None,None): - import traceback - s = getStringIO() - traceback.print_exc(None,s) - store['__traceback'] = s.getvalue() - cwd=os.getcwd() - lcwd = os.listdir(cwd) - exed = os.path.abspath(os.path.dirname(sys.argv[0])) - store.update({ 'gmt': time.asctime(time.gmtime(time.time())), - 'platform': sys.platform, - 'version': sys.version, - 'hexversion': hex(sys.hexversion), - 'executable': sys.executable, - 'exec_prefix': sys.exec_prefix, - 'prefix': sys.prefix, - 'path': sys.path, - 'argv': sys.argv, - 'cwd': cwd, - 'hostname': socket.gethostname(), - 'lcwd': lcwd, - 'byteorder': sys.byteorder, - 'maxint': sys.maxint, - 'maxint': getattr(sys,'maxunicode','????'), - 'api_version': getattr(sys,'api_version','????'), - 'version_info': getattr(sys,'version_info','????'), - 'winver': getattr(sys,'winver','????'), - }) - for M,A in ( - (sys,('getwindowsversion','getfilesystemencoding')), - (os,('uname', 'ctermid', 'getgid', 'getuid', 'getegid', - 'geteuid', 'getlogin', 'getgroups', 'getpgrp', 'getpid', 'getppid', - )), - ): - for a in A: - if hasattr(M,a): - try: - store[a] = getattr(M,a)() - except: - pass - if exed!=cwd: - try: - store.update({'exed': exed, 'lexed': os.listdir(exed),}) - except: - pass - if getScript: - fn = os.path.abspath(sys.argv[0]) - if os.path.isfile(fn): - try: - store['__script'] = (fn,open(fn,'r').read()) - except: - pass - module_versions = {} - for n,m in sys.modules.items(): - if n=='reportlab' or n=='rlextra' or n[:10]=='reportlab.' or n[:8]=='rlextra.': - v = getattr(m,'__version__',None) - if v: module_versions[n] = v - store['__module_versions'] = module_versions - self.store['__payload'] = {} - self._add(kw) - - def _add(self,D): - payload = self.store['__payload'] - for k, v in D.items(): - payload[k] = v - - def add(self,**kw): - self._add(kw) - - def dump(self): - import pickle - pickle.dump(self.store,open(self.fn,'wb')) - - def load(self): - import pickle - self.store = pickle.load(open(self.fn,'rb')) - - def _show_module_versions(self,k,v): - self._writeln(k[2:]) - K = v.keys() - K.sort() - for k in K: - vk = v[k] - try: - m = recursiveImport(k,sys.path[:],1) - d = getattr(m,'__version__',None)==vk and 'SAME' or 'DIFFERENT' - except: - m = None - d = '??????unknown??????' - self._writeln(' %s = %s (%s)' % (k,vk,d)) - - def _banner(self,k,what): - self._writeln('###################%s %s##################' % (what,k[2:])) - - def _start(self,k): - self._banner(k,'Start ') - - def _finish(self,k): - self._banner(k,'Finish ') - - def _show_lines(self,k,v): - self._start(k) - self._writeln(v) - self._finish(k) - - def _show_file(self,k,v): - k = '%s %s' % (k,os.path.basename(v[0])) - self._show_lines(k,v[1]) - - def _show_payload(self,k,v): - if v: - import pprint - self._start(k) - pprint.pprint(v,self.stdout) - self._finish(k) - - specials = {'__module_versions': _show_module_versions, - '__payload': _show_payload, - '__traceback': _show_lines, - '__script': _show_file, - } - def show(self): - K = self.store.keys() - K.sort() - for k in K: - if k not in self.specials.keys(): self._writeln('%-15s = %s' % (k,self.store[k])) - for k in K: - if k in self.specials.keys(): apply(self.specials[k],(self,k,self.store[k])) - - def payload(self,name): - return self.store['__payload'][name] - - def __setitem__(self,name,value): - self.store['__payload'][name] = value - - def __getitem__(self,name): - return self.store['__payload'][name] - - def _writeln(self,msg): - self.stdout.write(msg+'\n') - -def _flatten(L,a): - for x in L: - if type(x) in SeqTypes: _flatten(x,a) - else: a(x) - -def flatten(L): - '''recursively flatten the list or tuple L''' - R = [] - _flatten(L,R.append) - return R - -def find_locals(func,depth=0): - '''apply func to the locals at each stack frame till func returns a non false value''' - while 1: - _ = func(sys._getframe(depth).f_locals) - if _: return _ - depth += 1 diff --git a/bin/reportlab/lib/validators.py b/bin/reportlab/lib/validators.py deleted file mode 100644 index 02d502ed082..00000000000 --- a/bin/reportlab/lib/validators.py +++ /dev/null @@ -1,260 +0,0 @@ -#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/lib/validators.py -__version__=''' $Id$ ''' -""" -This module contains some standard verifying functions which can be -used in an attribute map. -""" - -import string, sys -from types import * -_SequenceTypes = (ListType,TupleType) -_NumberTypes = (FloatType,IntType) -from reportlab.lib import colors -if sys.hexversion<0x2030000: - True = 1 - False = 0 - -class Validator: - "base validator class" - def __call__(self,x): - return self.test(x) - - def __str__(self): - return getattr(self,'_str',self.__class__.__name__) - - def normalize(self,x): - return x - - def normalizeTest(self,x): - try: - self.normalize(x) - return True - except: - return False - -class _isAnything(Validator): - def test(self,x): - return True - -class _isNothing(Validator): - def test(self,x): - return False - -class _isBoolean(Validator): - if sys.hexversion>=0x2030000: - def test(self,x): - if type(x) in (IntType,BooleanType): return x in (0,1) - return self.normalizeTest(x) - else: - def test(self,x): - if type(x) is IntType: return x in (0,1) - return self.normalizeTest(x) - - def normalize(self,x): - if x in (0,1): return x - try: - S = string.upper(x) - except: - raise ValueError, 'Must be boolean' - if S in ('YES','TRUE'): return True - if S in ('NO','FALSE',None): return False - raise ValueError, 'Must be boolean' - -class _isString(Validator): - def test(self,x): - return type(x) is StringType - -class _isNumber(Validator): - def test(self,x): - if type(x) in _NumberTypes: return True - return self.normalizeTest(x) - - def normalize(self,x): - try: - return float(x) - except: - return int(x) - -class _isInt(Validator): - def test(self,x): - if type(x) not in (IntType,StringType): return False - return self.normalizeTest(x) - - def normalize(self,x): - return int(x) - -class _isNumberOrNone(_isNumber): - def test(self,x): - return x is None or isNumber(x) - - def normalize(self,x): - if x is None: return x - return _isNumber.normalize(x) - -class _isListOfNumbersOrNone(Validator): - "ListOfNumbersOrNone validator class." - def test(self, x): - if x is None: return True - return isListOfNumbers(x) - -class _isListOfShapes(Validator): - "ListOfShapes validator class." - def test(self, x): - from reportlab.graphics.shapes import Shape - if type(x) in _SequenceTypes: - answer = 1 - for element in x: - if not isinstance(x, Shape): - answer = 0 - return answer - else: - return False - -class _isListOfStringsOrNone(Validator): - "ListOfStringsOrNone validator class." - - def test(self, x): - if x is None: return True - return isListOfStrings(x) - -class _isTransform(Validator): - "Transform validator class." - def test(self, x): - if type(x) in _SequenceTypes: - if len(x) == 6: - for element in x: - if not isNumber(element): - return False - return True - else: - return False - else: - return False - -class _isColor(Validator): - "Color validator class." - def test(self, x): - return isinstance(x, colors.Color) - -class _isColorOrNone(Validator): - "ColorOrNone validator class." - def test(self, x): - if x is None: return True - return isColor(x) - -class _isValidChild(Validator): - "ValidChild validator class." - def test(self, x): - """Is this child allowed in a drawing or group? - I.e. does it descend from Shape or UserNode? - """ - - from reportlab.graphics.shapes import UserNode, Shape - return isinstance(x, UserNode) or isinstance(x, Shape) - -class _isValidChildOrNone(_isValidChild): - def test(self,x): - return _isValidChild.test(self,x) or x is None - -class _isCallable(Validator): - def test(self, x): - return callable(x) - -class OneOf(Validator): - """Make validator functions for list of choices. - - Usage: - f = reportlab.lib.validators.OneOf('happy','sad') - or - f = reportlab.lib.validators.OneOf(('happy','sad')) - f('sad'),f('happy'), f('grumpy') - (1,1,0) - """ - def __init__(self, enum,*args): - if type(enum) in [ListType,TupleType]: - if args!=(): - raise ValueError, "Either all singleton args or a single sequence argument" - self._enum = tuple(enum)+args - else: - self._enum = (enum,)+args - - def test(self, x): - return x in self._enum - -class SequenceOf(Validator): - def __init__(self,elemTest,name=None,emptyOK=1, NoneOK=0, lo=0,hi=0x7fffffff): - self._elemTest = elemTest - self._emptyOK = emptyOK - self._NoneOK = NoneOK - self._lo, self._hi = lo, hi - if name: self._str = name - - def test(self, x): - if type(x) not in _SequenceTypes: - if x is None: return self._NoneOK - return False - if x==[] or x==(): - return self._emptyOK - elif not self._lo<=len(x)<=self._hi: return False - for e in x: - if not self._elemTest(e): return False - return True - -class EitherOr(Validator): - def __init__(self,tests,name=None): - if type(tests) not in _SequenceTypes: tests = (tests,) - self._tests = tests - if name: self._str = name - - def test(self, x): - for t in self._tests: - if t(x): return True - return False - -class NoneOr(Validator): - def __init__(self,elemTest,name=None): - self._elemTest = elemTest - if name: self._str = name - - def test(self, x): - if x is None: return True - return self._elemTest(x) - -class isInstanceOf(Validator): - def __init__(self,klass=None): - self._klass = klass - def test(self,x): - return isinstance(x,self._klass) - -isBoolean = _isBoolean() -isString = _isString() -isNumber = _isNumber() -isInt = _isInt() -isNoneOrInt = NoneOr(isInt,'isNoneOrInt') -isNumberOrNone = _isNumberOrNone() -isTextAnchor = OneOf('start','middle','end','boxauto') -isListOfNumbers = SequenceOf(isNumber,'isListOfNumbers') -isListOfNumbersOrNone = _isListOfNumbersOrNone() -isListOfShapes = _isListOfShapes() -isListOfStrings = SequenceOf(isString,'isListOfStrings') -isListOfStringsOrNone = _isListOfStringsOrNone() -isTransform = _isTransform() -isColor = _isColor() -isListOfColors = SequenceOf(isColor,'isListOfColors') -isColorOrNone = _isColorOrNone() -isShape = isValidChild = _isValidChild() -isNoneOrShape = isValidChildOrNone = _isValidChildOrNone() -isAnything = _isAnything() -isNothing = _isNothing() -isXYCoord = SequenceOf(isNumber,lo=2,hi=2,emptyOK=0) -isBoxAnchor = OneOf('nw','n','ne','w','c','e','sw','s','se', 'autox', 'autoy') -isNoneOrString = NoneOr(isString,'NoneOrString') -isNoneOrListOfNoneOrStrings=SequenceOf(isNoneOrString,'isNoneOrListOfNoneOrStrings',NoneOK=1) -isListOfNoneOrString=SequenceOf(isNoneOrString,'isListOfNoneOrString',NoneOK=0) -isNoneOrListOfNoneOrNumbers=SequenceOf(isNumberOrNone,'isNoneOrListOfNoneOrNumbers',NoneOK=1) -isCallable = _isCallable() -isStringOrCallable=EitherOr((isString,isCallable),'isStringOrCallable') -isStringOrCallableOrNone=NoneOr(isStringOrCallable,'isStringOrCallableNone') -isStringOrNone=NoneOr(isString,'isStringOrNone') diff --git a/bin/reportlab/lib/xmllib.py b/bin/reportlab/lib/xmllib.py deleted file mode 100644 index 08687376859..00000000000 --- a/bin/reportlab/lib/xmllib.py +++ /dev/null @@ -1,770 +0,0 @@ -# A parser for XML, using the derived class as static DTD. -# Author: Sjoerd Mullender. - -# sgmlop support added by fredrik@pythonware.com (May 19, 1998) - -import re -import string - -try: - from _xmlplus.parsers import sgmlop - #import sgmlop # this works for both builtin on the path or relative -except ImportError: - sgmlop = None - -# standard entity defs - -ENTITYDEFS = { - 'lt': '<', - 'gt': '>', - 'amp': '&', - 'quot': '"', - 'apos': '\'' - } - -# XML parser base class -- find tags and call handler functions. -# Usage: p = XMLParser(); p.feed(data); ...; p.close(). -# The dtd is defined by deriving a class which defines methods with -# special names to handle tags: start_foo and end_foo to handle -# and , respectively. The data between tags is passed to the -# parser by calling self.handle_data() with some data as argument (the -# data may be split up in arbutrary chunks). Entity references are -# passed by calling self.handle_entityref() with the entity reference -# as argument. - -# -------------------------------------------------------------------- -# original re-based XML parser - -_S = '[ \t\r\n]+' -_opS = '[ \t\r\n]*' -_Name = '[a-zA-Z_:][-a-zA-Z0-9._:]*' -interesting = re.compile('[&<]') -incomplete = re.compile('&(' + _Name + '|#[0-9]*|#x[0-9a-fA-F]*)?|' - '<([a-zA-Z_:][^<>]*|' - '/([a-zA-Z_:][^<>]*)?|' - '![^<>]*|' - '\?[^<>]*)?') - -ref = re.compile('&(' + _Name + '|#[0-9]+|#x[0-9a-fA-F]+);?') -entityref = re.compile('&(?P' + _Name + ')[^-a-zA-Z0-9._:]') -charref = re.compile('&#(?P[0-9]+[^0-9]|x[0-9a-fA-F]+[^0-9a-fA-F])') -space = re.compile(_S) -newline = re.compile('\n') - -starttagopen = re.compile('<' + _Name) -endtagopen = re.compile('/?)>') -endbracket = re.compile('>') -tagfind = re.compile(_Name) -cdataopen = re.compile('') -special = re.compile('[^<>]*)>') -procopen = re.compile('<\?(?P' + _Name + ')' + _S) -procclose = re.compile('\?>') -commentopen = re.compile('') -doubledash = re.compile('--') -attrfind = re.compile( - _opS + '(?P' + _Name + ')' - '(' + _opS + '=' + _opS + - '(?P\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9.:+*%?!()_#=~]+))') - -class SlowXMLParser: - - # Interface -- initialize and reset this instance - def __init__(self, verbose=0): - self.verbose = verbose - self.reset() - - # Interface -- reset this instance. Loses all unprocessed data - def reset(self): - self.rawdata = '' - self.stack = [] - self.lasttag = '???' - self.nomoretags = 0 - self.literal = 0 - self.lineno = 1 - - # For derived classes only -- enter literal mode (CDATA) till EOF - def setnomoretags(self): - self.nomoretags = self.literal = 1 - - # For derived classes only -- enter literal mode (CDATA) - def setliteral(self, *args): - self.literal = 1 - - # Interface -- feed some data to the parser. Call this as - # often as you want, with as little or as much text as you - # want (may include '\n'). (This just saves the text, all the - # processing is done by goahead().) - def feed(self, data): - self.rawdata = self.rawdata + data - self.goahead(0) - - # Interface -- handle the remaining data - def close(self): - self.goahead(1) - - # Interface -- translate references - def translate_references(self, data): - newdata = [] - i = 0 - while 1: - res = ref.search(data, i) - if res is None: - newdata.append(data[i:]) - return string.join(newdata, '') - if data[res.end(0) - 1] != ';': - self.syntax_error(self.lineno, - '; missing after entity/char reference') - newdata.append(data[i:res.start(0)]) - str = res.group(1) - if str[0] == '#': - if str[1] == 'x': - newdata.append(chr(string.atoi(str[2:], 16))) - else: - newdata.append(chr(string.atoi(str[1:]))) - else: - try: - newdata.append(self.entitydefs[str]) - except KeyError: - # can't do it, so keep the entity ref in - newdata.append('&' + str + ';') - i = res.end(0) - - # Internal -- handle data as far as reasonable. May leave state - # and data to be processed by a subsequent call. If 'end' is - # true, force handling all data as if followed by EOF marker. - def goahead(self, end): - rawdata = self.rawdata - i = 0 - n = len(rawdata) - while i < n: - if self.nomoretags: - data = rawdata[i:n] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = n - break - res = interesting.search(rawdata, i) - if res: - j = res.start(0) - else: - j = n - if i < j: - data = rawdata[i:j] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = j - if i == n: break - if rawdata[i] == '<': - if starttagopen.match(rawdata, i): - if self.literal: - data = rawdata[i] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = i+1 - continue - k = self.parse_starttag(i) - if k < 0: break - self.lineno = self.lineno + string.count(rawdata[i:k], '\n') - i = k - continue - if endtagopen.match(rawdata, i): - k = self.parse_endtag(i) - if k < 0: break - self.lineno = self.lineno + string.count(rawdata[i:k], '\n') - i = k - self.literal = 0 - continue - if commentopen.match(rawdata, i): - if self.literal: - data = rawdata[i] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = i+1 - continue - k = self.parse_comment(i) - if k < 0: break - self.lineno = self.lineno + string.count(rawdata[i:k], '\n') - i = k - continue - if cdataopen.match(rawdata, i): - k = self.parse_cdata(i) - if k < 0: break - self.lineno = self.lineno + string.count(rawdata[i:i], '\n') - i = k - continue - res = procopen.match(rawdata, i) - if res: - k = self.parse_proc(i, res) - if k < 0: break - self.lineno = self.lineno + string.count(rawdata[i:k], '\n') - i = k - continue - res = special.match(rawdata, i) - if res: - if self.literal: - data = rawdata[i] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = i+1 - continue - self.handle_special(res.group('special')) - self.lineno = self.lineno + string.count(res.group(0), '\n') - i = res.end(0) - continue - elif rawdata[i] == '&': - res = charref.match(rawdata, i) - if res is not None: - i = res.end(0) - if rawdata[i-1] != ';': - self.syntax_error(self.lineno, '; missing in charref') - i = i-1 - self.handle_charref(res.group('char')[:-1]) - self.lineno = self.lineno + string.count(res.group(0), '\n') - continue - res = entityref.match(rawdata, i) - if res is not None: - i = res.end(0) - if rawdata[i-1] != ';': - self.syntax_error(self.lineno, '; missing in entityref') - i = i-1 - self.handle_entityref(res.group('name')) - self.lineno = self.lineno + string.count(res.group(0), '\n') - continue - else: - raise RuntimeError, 'neither < nor & ??' - # We get here only if incomplete matches but - # nothing else - res = incomplete.match(rawdata, i) - if not res: - data = rawdata[i] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = i+1 - continue - j = res.end(0) - if j == n: - break # Really incomplete - self.syntax_error(self.lineno, 'bogus < or &') - data = res.group(0) - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = j - # end while - if end and i < n: - data = rawdata[i:n] - self.handle_data(data) - self.lineno = self.lineno + string.count(data, '\n') - i = n - self.rawdata = rawdata[i:] - # XXX if end: check for empty stack - - # Internal -- parse comment, return length or -1 if not terminated - def parse_comment(self, i): - rawdata = self.rawdata - if rawdata[i:i+4] <> ' width of glyph - widthVectorsByFont fontName -> vector of widths -""" -import string, UserDict, os, sys - -# mapping of name to width vector, starts empty until fonts are added -# e.g. widths['Courier'] = [...600,600,600,...] -widthVectorsByFont = {} -fontsByName = {} -fontsByBaseEnc = {} -# this is a list of the standard 14 font names in Acrobat Reader -standardFonts = ( - 'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique', - 'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 'Helvetica-BoldOblique', - 'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic', - 'Symbol','ZapfDingbats') - -standardFontAttributes = { - #family, bold, italic defined for basic ones - 'Courier':('Courier',0,0), - 'Courier-Bold':('Courier',1,0), - 'Courier-Oblique':('Courier',0,1), - 'Courier-BoldOblique':('Courier',1,1), - - 'Helvetica':('Helvetica',0,0), - 'Helvetica-Bold':('Helvetica',1,0), - 'Helvetica-Oblique':('Helvetica',0,1), - 'Helvetica-BoldOblique':('Helvetica',1,1), - - 'Times-Roman':('Times-Roman',0,0), - 'Times-Bold':('Times-Roman',1,0), - 'Times-Italic':('Times-Roman',0,1), - 'Times-BoldItalic':('Times-Roman',1,1), - - 'Symbol':('Symbol',0,0), - 'ZapfDingbats':('ZapfDingbats',0,0) - - } - -#this maps fontnames to the equivalent filename root. -_font2fnrMapWin32 = { - 'symbol': 'Sy______', - 'zapfdingbats': 'Zd______', - 'helvetica': '_a______', - 'helvetica-bold': '_ab_____', - 'helvetica-boldoblique': '_abi____', - 'helvetica-oblique': '_ai_____', - 'times-bold': '_eb_____', - 'times-bolditalic': '_ebi____', - 'times-italic': '_ei_____', - 'times-roman': '_er_____', - 'courier-bold': 'cob_____', - 'courier-boldoblique': 'cobo____', - 'courier': 'com_____', - 'courier-oblique': 'coo_____', - } -if sys.platform in ('linux2',): - _font2fnrMapLinux2 ={ - 'symbol': 'Symbol', - 'zapfdingbats': 'ZapfDingbats', - 'helvetica': 'Arial', - 'helvetica-bold': 'Arial-Bold', - 'helvetica-boldoblique': 'Arial-BoldItalic', - 'helvetica-oblique': 'Arial-Italic', - 'times-bold': 'TimesNewRoman-Bold', - 'times-bolditalic':'TimesNewRoman-BoldItalic', - 'times-italic': 'TimesNewRoman-Italic', - 'times-roman': 'TimesNewRoman', - 'courier-bold': 'Courier-Bold', - 'courier-boldoblique': 'Courier-BoldOblique', - 'courier': 'Courier', - 'courier-oblique': 'Courier-Oblique', - } - _font2fnrMap = _font2fnrMapLinux2 - _revmap = None -else: - _font2fnrMap = _font2fnrMapWin32 - -def _findFNR(fontName): - return _font2fnrMap[string.lower(fontName)] - -from reportlab.rl_config import T1SearchPath -from reportlab.lib.utils import rl_isfile -def _searchT1Dirs(n,rl_isfile=rl_isfile,T1SearchPath=T1SearchPath): - assert T1SearchPath!=[], "No Type-1 font search path" - for d in T1SearchPath: - f = os.path.join(d,n) - if rl_isfile(f): return f - return None -del T1SearchPath, rl_isfile - -def findT1File(fontName,ext='.pfb'): - if sys.platform in ('linux2',) and ext=='.pfb': - try: - f = _searchT1Dirs(_findFNR(fontName)) - if f: return f - except: - pass - global _revmap - if not _revmap: - for k, v in _font2fnrMap.items(): - if k in _font2fnrMapWin32.keys(): - _font2fnrMapWin32[string.lower(v)] = _font2fnrMapWin32[k] - revmap = 1 - try: - f = _searchT1Dirs(_font2fnrMapWin32[string.lower(fontName)]+ext) - if f: return f - except: - pass - - return _searchT1Dirs(_findFNR(fontName)+ext) - -# this lists the predefined font encodings - WinAnsi and MacRoman. We have -# not added MacExpert - it's possible, but would complicate life and nobody -# is asking. StandardEncoding means something special. -standardEncodings = ('WinAnsiEncoding','MacRomanEncoding','StandardEncoding','SymbolEncoding','ZapfDingbatsEncoding','PDFDocEncoding', 'MacExpertEncoding') - -#this is the global mapping of standard encodings to name vectors -class _Name2StandardEncodingMap(UserDict.UserDict): - '''Trivial fake dictionary with some [] magic''' - _XMap = {'winansi':'WinAnsiEncoding','macroman': 'MacRomanEncoding','standard':'StandardEncoding','symbol':'SymbolEncoding', 'zapfdingbats':'ZapfDingbatsEncoding','pdfdoc':'PDFDocEncoding', 'macexpert':'MacExpertEncoding'} - def __setitem__(self,x,v): - y = string.lower(x) - if y[-8:]=='encoding': y = y[:-8] - y = self._XMap[y] - if y in self.keys(): raise IndexError, 'Encoding %s is already set' % y - self.data[y] = v - - def __getitem__(self,x): - y = string.lower(x) - if y[-8:]=='encoding': y = y[:-8] - y = self._XMap[y] - return self.data[y] - -encodings = _Name2StandardEncodingMap() -encodings['WinAnsiEncoding'] = ( - None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, 'space', 'exclam', - 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', - 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', - 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', - 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', - 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', - 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', - 'asciitilde', 'bullet', 'Euro', 'bullet', 'quotesinglbase', 'florin', - 'quotedblbase', 'ellipsis', 'dagger', 'daggerdbl', 'circumflex', - 'perthousand', 'Scaron', 'guilsinglleft', 'OE', 'bullet', 'Zcaron', - 'bullet', 'bullet', 'quoteleft', 'quoteright', 'quotedblleft', - 'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark', - 'scaron', 'guilsinglright', 'oe', 'bullet', 'zcaron', 'Ydieresis', - 'space', 'exclamdown', 'cent', 'sterling', 'currency', 'yen', 'brokenbar', - 'section', 'dieresis', 'copyright', 'ordfeminine', 'guillemotleft', - 'logicalnot', 'hyphen', 'registered', 'macron', 'degree', 'plusminus', - 'twosuperior', 'threesuperior', 'acute', 'mu', 'paragraph', 'periodcentered', - 'cedilla', 'onesuperior', 'ordmasculine', 'guillemotright', 'onequarter', - 'onehalf', 'threequarters', 'questiondown', 'Agrave', 'Aacute', - 'Acircumflex', 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla', - 'Egrave', 'Eacute', 'Ecircumflex', 'Edieresis', 'Igrave', 'Iacute', - 'Icircumflex', 'Idieresis', 'Eth', 'Ntilde', 'Ograve', 'Oacute', - 'Ocircumflex', 'Otilde', 'Odieresis', 'multiply', 'Oslash', 'Ugrave', - 'Uacute', 'Ucircumflex', 'Udieresis', 'Yacute', 'Thorn', 'germandbls', - 'agrave', 'aacute', 'acircumflex', 'atilde', 'adieresis', 'aring', 'ae', - 'ccedilla', 'egrave', 'eacute', 'ecircumflex', 'edieresis', 'igrave', - 'iacute', 'icircumflex', 'idieresis', 'eth', 'ntilde', 'ograve', 'oacute', - 'ocircumflex', 'otilde', 'odieresis', 'divide', 'oslash', 'ugrave', 'uacute', - 'ucircumflex', 'udieresis', 'yacute', 'thorn', 'ydieresis') - -encodings['MacRomanEncoding'] = ( - None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, 'space', 'exclam', - 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', - 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', - 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', - 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', - 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', - 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', - 'asciitilde', None, 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', - 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', - 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', - 'ecircumflex', 'edieresis', 'iacute', 'igrave', 'icircumflex', - 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', - 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', - 'degree', 'cent', 'sterling', 'section', 'bullet', 'paragraph', - 'germandbls', 'registered', 'copyright', 'trademark', 'acute', - 'dieresis', None, 'AE', 'Oslash', None, 'plusminus', None, None, 'yen', - 'mu', None, None, None, None, None, 'ordfeminine', 'ordmasculine', None, - 'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', None, 'florin', - None, None, 'guillemotleft', 'guillemotright', 'ellipsis', 'space', 'Agrave', - 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', - 'quotedblright', 'quoteleft', 'quoteright', 'divide', None, 'ydieresis', - 'Ydieresis', 'fraction', 'currency', 'guilsinglleft', 'guilsinglright', - 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', - 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', - 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', - 'Oacute', 'Ocircumflex', None, 'Ograve', 'Uacute', 'Ucircumflex', - 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', - 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron') -encodings['SymbolEncoding']=(None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, 'space', - 'exclam', 'universal', 'numbersign', 'existential', 'percent', 'ampersand', 'suchthat', - 'parenleft', 'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period', 'slash', 'zero', - 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', - 'less', 'equal', 'greater', 'question', 'congruent', 'Alpha', 'Beta', 'Chi', 'Delta', 'Epsilon', - 'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa', 'Lambda', 'Mu', 'Nu', 'Omicron', 'Pi', 'Theta', - 'Rho', 'Sigma', 'Tau', 'Upsilon', 'sigma1', 'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft', - 'therefore', 'bracketright', 'perpendicular', 'underscore', 'radicalex', 'alpha', 'beta', 'chi', - 'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota', 'phi1', 'kappa', 'lambda', 'mu', 'nu', - 'omicron', 'pi', 'theta', 'rho', 'sigma', 'tau', 'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta', - 'braceleft', 'bar', 'braceright', 'similar', None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, 'Euro', 'Upsilon1', 'minute', 'lessequal', - 'fraction', 'infinity', 'florin', 'club', 'diamond', 'heart', 'spade', 'arrowboth', 'arrowleft', - 'arrowup', 'arrowright', 'arrowdown', 'degree', 'plusminus', 'second', 'greaterequal', 'multiply', - 'proportional', 'partialdiff', 'bullet', 'divide', 'notequal', 'equivalence', 'approxequal', - 'ellipsis', 'arrowvertex', 'arrowhorizex', 'carriagereturn', 'aleph', 'Ifraktur', 'Rfraktur', - 'weierstrass', 'circlemultiply', 'circleplus', 'emptyset', 'intersection', 'union', - 'propersuperset', 'reflexsuperset', 'notsubset', 'propersubset', 'reflexsubset', 'element', - 'notelement', 'angle', 'gradient', 'registerserif', 'copyrightserif', 'trademarkserif', 'product', - 'radical', 'dotmath', 'logicalnot', 'logicaland', 'logicalor', 'arrowdblboth', 'arrowdblleft', - 'arrowdblup', 'arrowdblright', 'arrowdbldown', 'lozenge', 'angleleft', 'registersans', - 'copyrightsans', 'trademarksans', 'summation', 'parenlefttp', 'parenleftex', 'parenleftbt', - 'bracketlefttp', 'bracketleftex', 'bracketleftbt', 'bracelefttp', 'braceleftmid', 'braceleftbt', - 'braceex', None, 'angleright', 'integral', 'integraltp', 'integralex', 'integralbt', - 'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp', 'bracketrightex', - 'bracketrightbt', 'bracerighttp', 'bracerightmid', 'bracerightbt', None) -encodings['ZapfDingbatsEncoding'] = ( None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - 'space', 'a1', 'a2', 'a202', 'a3', 'a4', 'a5', 'a119', 'a118', 'a117', 'a11', 'a12', 'a13', 'a14', - 'a15', 'a16', 'a105', 'a17', 'a18', 'a19', 'a20', 'a21', 'a22', 'a23', 'a24', 'a25', 'a26', 'a27', - 'a28', 'a6', 'a7', 'a8', 'a9', 'a10', 'a29', 'a30', 'a31', 'a32', 'a33', 'a34', 'a35', 'a36', - 'a37', 'a38', 'a39', 'a40', 'a41', 'a42', 'a43', 'a44', 'a45', 'a46', 'a47', 'a48', 'a49', 'a50', - 'a51', 'a52', 'a53', 'a54', 'a55', 'a56', 'a57', 'a58', 'a59', 'a60', 'a61', 'a62', 'a63', 'a64', - 'a65', 'a66', 'a67', 'a68', 'a69', 'a70', 'a71', 'a72', 'a73', 'a74', 'a203', 'a75', 'a204', 'a76', - 'a77', 'a78', 'a79', 'a81', 'a82', 'a83', 'a84', 'a97', 'a98', 'a99', 'a100', None, 'a89', 'a90', - 'a93', 'a94', 'a91', 'a92', 'a205', 'a85', 'a206', 'a86', 'a87', 'a88', 'a95', 'a96', None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, 'a101', 'a102', 'a103', 'a104', 'a106', 'a107', 'a108', 'a112', 'a111', 'a110', 'a109', - 'a120', 'a121', 'a122', 'a123', 'a124', 'a125', 'a126', 'a127', 'a128', 'a129', 'a130', 'a131', - 'a132', 'a133', 'a134', 'a135', 'a136', 'a137', 'a138', 'a139', 'a140', 'a141', 'a142', 'a143', - 'a144', 'a145', 'a146', 'a147', 'a148', 'a149', 'a150', 'a151', 'a152', 'a153', 'a154', 'a155', - 'a156', 'a157', 'a158', 'a159', 'a160', 'a161', 'a163', 'a164', 'a196', 'a165', 'a192', 'a166', - 'a167', 'a168', 'a169', 'a170', 'a171', 'a172', 'a173', 'a162', 'a174', 'a175', 'a176', 'a177', - 'a178', 'a179', 'a193', 'a180', 'a199', 'a181', 'a200', 'a182', None, 'a201', 'a183', 'a184', - 'a197', 'a185', 'a194', 'a198', 'a186', 'a195', 'a187', 'a188', 'a189', 'a190', 'a191', None) -encodings['StandardEncoding']=(None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,"space","exclam", - "quotedbl","numbersign","dollar","percent","ampersand","quoteright","parenleft","parenright","asterisk","plus", - "comma","hyphen","period","slash","zero","one","two","three","four","five","six","seven","eight","nine","colon", - "semicolon","less","equal","greater","question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", - "P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore", - "quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y", - "z","braceleft","bar","braceright","asciitilde",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None, - None,None,None,"exclamdown","cent","sterling","fraction","yen","florin","section","currency","quotesingle","quotedblleft", - "guillemotleft","guilsinglleft","guilsinglright","fi","fl",None,"endash","dagger","daggerdbl","periodcentered",None, - "paragraph","bullet","quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis","perthousand", - None,"questiondown",None,"grave","acute","circumflex","tilde","macron","breve","dotaccent","dieresis",None,"ring", - "cedilla",None,"hungarumlaut","ogonek","caron","emdash",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,"AE",None,"ordfeminine", - None,None,None,None,"Lslash","Oslash","OE","ordmasculine",None,None,None,None,None,"ae",None,None,None,"dotlessi",None,None,"lslash","oslash", - "oe","germandbls",None,None,None,None) -encodings['PDFDocEncoding']=(None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None, - None,None,None,None,None,"breve","caron","circumflex", - "dotaccent","hungarumlaut","ogonek","ring","tilde","space","exclam","quotedbl","numbersign","dollar","percent", - "ampersand","quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen","period","slash","zero", - "one","two","three","four","five","six","seven","eight","nine","colon","semicolon","less","equal","greater", - "question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X", - "Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore","grave","a","b","c","d","e","f","g", - "h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","braceleft","bar","braceright", - "asciitilde",None,"bullet","dagger","daggerdbl","ellipsis","emdash","endash","florin","fraction","guilsinglleft", - "guilsinglright","minus","perthousand","quotedblbase","quotedblleft","quotedblright","quoteleft","quoteright", - "quotesinglbase","trademark","fi","fl","Lslash","OE","Scaron","Ydieresis","Zcaron","dotlessi","lslash","oe", - "scaron","zcaron",None,"Euro","exclamdown","cent","sterling","currency","yen","brokenbar","section","dieresis", - "copyright","ordfeminine","guillemotleft","logicalnot",None,"registered","macron","degree","plusminus","twosuperior", - "threesuperior","acute","mu","paragraph","periodcentered","cedilla","onesuperior","ordmasculine","guillemotright", - "onequarter","onehalf","threequarters","questiondown","Agrave","Aacute","Acircumflex","Atilde","Adieresis","Aring", - "AE","Ccedilla","Egrave","Eacute","Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth", - "Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply","Oslash","Ugrave","Uacute","Ucircumflex", - "Udieresis","Yacute","Thorn","germandbls","agrave","aacute","acircumflex","atilde","adieresis","aring","ae", - "ccedilla","egrave","eacute","ecircumflex","edieresis","igrave","iacute","icircumflex","idieresis","eth","ntilde", - "ograve","oacute","ocircumflex","otilde","odieresis","divide","oslash","ugrave","uacute","ucircumflex","udieresis", - "yacute","thorn","ydieresis") -encodings['MacExpertEncoding'] = (None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - 'space', 'exclamsmall', 'Hungarumlautsmall', 'centoldstyle', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', - 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', 'hyphen', - 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', - 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', None, - 'threequartersemdash', None, 'questionsmall', None, None, None, None, 'Ethsmall', None, None, 'onequarter', - 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', - None, None, None, None, None, None, 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', None, - 'parenrightinferior', 'Circumflexsmall', 'hypheninferior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', - 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', - 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', - 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', None, None, 'asuperior', 'centsuperior', None, None, None, - None, 'Aacutesmall', 'Agravesmall', 'Acircumflexsmall', 'Adieresissmall', 'Atildesmall', 'Aringsmall', - 'Ccedillasmall', 'Eacutesmall', 'Egravesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Iacutesmall', 'Igravesmall', - 'Icircumflexsmall', 'Idieresissmall', 'Ntildesmall', 'Oacutesmall', 'Ogravesmall', 'Ocircumflexsmall', - 'Odieresissmall', 'Otildesmall', 'Uacutesmall', 'Ugravesmall', 'Ucircumflexsmall', 'Udieresissmall', None, - 'eightsuperior', 'fourinferior', 'threeinferior', 'sixinferior', 'eightinferior', 'seveninferior', 'Scaronsmall', - None, 'centinferior', 'twoinferior', None, 'Dieresissmall', None, 'Caronsmall', 'osuperior', 'fiveinferior', None, - 'commainferior', 'periodinferior', 'Yacutesmall', None, 'dollarinferior', None, None, 'Thornsmall', None, - 'nineinferior', 'zeroinferior', 'Zcaronsmall', 'AEsmall', 'Oslashsmall', 'questiondownsmall', 'oneinferior', - 'Lslashsmall', None, None, None, None, None, None, 'Cedillasmall', None, None, None, None, None, 'OEsmall', - 'figuredash', 'hyphensuperior', None, None, None, None, 'exclamdownsmall', None, 'Ydieresissmall', None, - 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', - 'ninesuperior', 'zerosuperior', None, 'esuperior', 'rsuperior', 'tsuperior', None, None, 'isuperior', 'ssuperior', - 'dsuperior', None, None, None, None, None, 'lsuperior', 'Ogoneksmall', 'Brevesmall', 'Macronsmall', 'bsuperior', - 'nsuperior', 'msuperior', 'commasuperior', 'periodsuperior', 'Dotaccentsmall', 'Ringsmall', None, None, None, None) -ascent_descent = { - 'Courier': (629, -157), - 'Courier-Bold': (626, -142), - 'Courier-BoldOblique': (626, -142), - 'Courier-Oblique': (629, -157), - 'Helvetica': (718, -207), - 'Helvetica-Bold': (718, -207), - 'Helvetica-BoldOblique': (718, -207), - 'Helvetica-Oblique': (718, -207), - 'Times-Roman': (683, -217), - 'Times-Bold': (676, -205), - 'Times-BoldItalic': (699, -205), - 'Times-Italic': (683, -205), - 'Symbol': (0, 0), - 'ZapfDingbats': (0, 0) - } - -# nuild this up one entry at a time to stay under JPython's 64k limit. -widthsByFontGlyph = {} -widthsByFontGlyph['Helvetica'] = {'A': 667, - 'AE': 1000, - 'Aacute': 667, - 'Acircumflex': 667, - 'Adieresis': 667, - 'Agrave': 667, - 'Aring': 667, - 'Atilde': 667, - 'B': 667, - 'C': 722, - 'Ccedilla': 722, - 'D': 722, - 'E': 667, - 'Eacute': 667, - 'Ecircumflex': 667, - 'Edieresis': 667, - 'Egrave': 667, - 'Eth': 722, - 'Euro': 556, - 'F': 611, - 'G': 778, - 'H': 722, - 'I': 278, - 'Iacute': 278, - 'Icircumflex': 278, - 'Idieresis': 278, - 'Igrave': 278, - 'J': 500, - 'K': 667, - 'L': 556, - 'Lslash': 556, - 'M': 833, - 'N': 722, - 'Ntilde': 722, - 'O': 778, - 'OE': 1000, - 'Oacute': 778, - 'Ocircumflex': 778, - 'Odieresis': 778, - 'Ograve': 778, - 'Oslash': 778, - 'Otilde': 778, - 'P': 667, - 'Q': 778, - 'R': 722, - 'S': 667, - 'Scaron': 667, - 'T': 611, - 'Thorn': 667, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 667, - 'W': 944, - 'X': 667, - 'Y': 667, - 'Yacute': 667, - 'Ydieresis': 667, - 'Z': 611, - 'Zcaron': 611, - 'a': 556, - 'aacute': 556, - 'acircumflex': 556, - 'acute': 333, - 'adieresis': 556, - 'ae': 889, - 'agrave': 556, - 'ampersand': 667, - 'aring': 556, - 'asciicircum': 469, - 'asciitilde': 584, - 'asterisk': 389, - 'at': 1015, - 'atilde': 556, - 'b': 556, - 'backslash': 278, - 'bar': 260, - 'braceleft': 334, - 'braceright': 334, - 'bracketleft': 278, - 'bracketright': 278, - 'breve': 333, - 'brokenbar': 260, - 'bullet': 350, - 'c': 500, - 'caron': 333, - 'ccedilla': 500, - 'cedilla': 333, - 'cent': 556, - 'circumflex': 333, - 'colon': 278, - 'comma': 278, - 'copyright': 737, - 'currency': 556, - 'd': 556, - 'dagger': 556, - 'daggerdbl': 556, - 'degree': 400, - 'dieresis': 333, - 'divide': 584, - 'dollar': 556, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 556, - 'eacute': 556, - 'ecircumflex': 556, - 'edieresis': 556, - 'egrave': 556, - 'eight': 556, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 556, - 'equal': 584, - 'eth': 556, - 'exclam': 278, - 'exclamdown': 333, - 'f': 278, - 'fi': 500, - 'five': 556, - 'fl': 500, - 'florin': 556, - 'four': 556, - 'fraction': 167, - 'g': 556, - 'germandbls': 611, - 'grave': 333, - 'greater': 584, - 'guillemotleft': 556, - 'guillemotright': 556, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 556, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 222, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 222, - 'k': 500, - 'l': 222, - 'less': 584, - 'logicalnot': 584, - 'lslash': 222, - 'm': 833, - 'macron': 333, - 'minus': 584, - 'mu': 556, - 'multiply': 584, - 'n': 556, - 'nine': 556, - 'ntilde': 556, - 'numbersign': 556, - 'o': 556, - 'oacute': 556, - 'ocircumflex': 556, - 'odieresis': 556, - 'oe': 944, - 'ogonek': 333, - 'ograve': 556, - 'one': 556, - 'onehalf': 834, - 'onequarter': 834, - 'onesuperior': 333, - 'ordfeminine': 370, - 'ordmasculine': 365, - 'oslash': 611, - 'otilde': 556, - 'p': 556, - 'paragraph': 537, - 'parenleft': 333, - 'parenright': 333, - 'percent': 889, - 'period': 278, - 'periodcentered': 278, - 'perthousand': 1000, - 'plus': 584, - 'plusminus': 584, - 'q': 556, - 'question': 556, - 'questiondown': 611, - 'quotedbl': 355, - 'quotedblbase': 333, - 'quotedblleft': 333, - 'quotedblright': 333, - 'quoteleft': 222, - 'quoteright': 222, - 'quotesinglbase': 222, - 'quotesingle': 191, - 'r': 333, - 'registered': 737, - 'ring': 333, - 's': 500, - 'scaron': 500, - 'section': 556, - 'semicolon': 278, - 'seven': 556, - 'six': 556, - 'slash': 278, - 'space': 278, - 'sterling': 556, - 't': 278, - 'thorn': 556, - 'three': 556, - 'threequarters': 834, - 'threesuperior': 333, - 'tilde': 333, - 'trademark': 1000, - 'two': 556, - 'twosuperior': 333, - 'u': 556, - 'uacute': 556, - 'ucircumflex': 556, - 'udieresis': 556, - 'ugrave': 556, - 'underscore': 556, - 'v': 500, - 'w': 722, - 'x': 500, - 'y': 500, - 'yacute': 500, - 'ydieresis': 500, - 'yen': 556, - 'z': 500, - 'zcaron': 500, - 'zero': 556} - -widthsByFontGlyph['Helvetica-Bold'] = {'A': 722, - 'AE': 1000, - 'Aacute': 722, - 'Acircumflex': 722, - 'Adieresis': 722, - 'Agrave': 722, - 'Aring': 722, - 'Atilde': 722, - 'B': 722, - 'C': 722, - 'Ccedilla': 722, - 'D': 722, - 'E': 667, - 'Eacute': 667, - 'Ecircumflex': 667, - 'Edieresis': 667, - 'Egrave': 667, - 'Eth': 722, - 'Euro': 556, - 'F': 611, - 'G': 778, - 'H': 722, - 'I': 278, - 'Iacute': 278, - 'Icircumflex': 278, - 'Idieresis': 278, - 'Igrave': 278, - 'J': 556, - 'K': 722, - 'L': 611, - 'Lslash': 611, - 'M': 833, - 'N': 722, - 'Ntilde': 722, - 'O': 778, - 'OE': 1000, - 'Oacute': 778, - 'Ocircumflex': 778, - 'Odieresis': 778, - 'Ograve': 778, - 'Oslash': 778, - 'Otilde': 778, - 'P': 667, - 'Q': 778, - 'R': 722, - 'S': 667, - 'Scaron': 667, - 'T': 611, - 'Thorn': 667, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 667, - 'W': 944, - 'X': 667, - 'Y': 667, - 'Yacute': 667, - 'Ydieresis': 667, - 'Z': 611, - 'Zcaron': 611, - 'a': 556, - 'aacute': 556, - 'acircumflex': 556, - 'acute': 333, - 'adieresis': 556, - 'ae': 889, - 'agrave': 556, - 'ampersand': 722, - 'aring': 556, - 'asciicircum': 584, - 'asciitilde': 584, - 'asterisk': 389, - 'at': 975, - 'atilde': 556, - 'b': 611, - 'backslash': 278, - 'bar': 280, - 'braceleft': 389, - 'braceright': 389, - 'bracketleft': 333, - 'bracketright': 333, - 'breve': 333, - 'brokenbar': 280, - 'bullet': 350, - 'c': 556, - 'caron': 333, - 'ccedilla': 556, - 'cedilla': 333, - 'cent': 556, - 'circumflex': 333, - 'colon': 333, - 'comma': 278, - 'copyright': 737, - 'currency': 556, - 'd': 611, - 'dagger': 556, - 'daggerdbl': 556, - 'degree': 400, - 'dieresis': 333, - 'divide': 584, - 'dollar': 556, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 556, - 'eacute': 556, - 'ecircumflex': 556, - 'edieresis': 556, - 'egrave': 556, - 'eight': 556, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 556, - 'equal': 584, - 'eth': 611, - 'exclam': 333, - 'exclamdown': 333, - 'f': 333, - 'fi': 611, - 'five': 556, - 'fl': 611, - 'florin': 556, - 'four': 556, - 'fraction': 167, - 'g': 611, - 'germandbls': 611, - 'grave': 333, - 'greater': 584, - 'guillemotleft': 556, - 'guillemotright': 556, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 611, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 278, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 278, - 'k': 556, - 'l': 278, - 'less': 584, - 'logicalnot': 584, - 'lslash': 278, - 'm': 889, - 'macron': 333, - 'minus': 584, - 'mu': 611, - 'multiply': 584, - 'n': 611, - 'nine': 556, - 'ntilde': 611, - 'numbersign': 556, - 'o': 611, - 'oacute': 611, - 'ocircumflex': 611, - 'odieresis': 611, - 'oe': 944, - 'ogonek': 333, - 'ograve': 611, - 'one': 556, - 'onehalf': 834, - 'onequarter': 834, - 'onesuperior': 333, - 'ordfeminine': 370, - 'ordmasculine': 365, - 'oslash': 611, - 'otilde': 611, - 'p': 611, - 'paragraph': 556, - 'parenleft': 333, - 'parenright': 333, - 'percent': 889, - 'period': 278, - 'periodcentered': 278, - 'perthousand': 1000, - 'plus': 584, - 'plusminus': 584, - 'q': 611, - 'question': 611, - 'questiondown': 611, - 'quotedbl': 474, - 'quotedblbase': 500, - 'quotedblleft': 500, - 'quotedblright': 500, - 'quoteleft': 278, - 'quoteright': 278, - 'quotesinglbase': 278, - 'quotesingle': 238, - 'r': 389, - 'registered': 737, - 'ring': 333, - 's': 556, - 'scaron': 556, - 'section': 556, - 'semicolon': 333, - 'seven': 556, - 'six': 556, - 'slash': 278, - 'space': 278, - 'sterling': 556, - 't': 333, - 'thorn': 611, - 'three': 556, - 'threequarters': 834, - 'threesuperior': 333, - 'tilde': 333, - 'trademark': 1000, - 'two': 556, - 'twosuperior': 333, - 'u': 611, - 'uacute': 611, - 'ucircumflex': 611, - 'udieresis': 611, - 'ugrave': 611, - 'underscore': 556, - 'v': 556, - 'w': 778, - 'x': 556, - 'y': 556, - 'yacute': 556, - 'ydieresis': 556, - 'yen': 556, - 'z': 500, - 'zcaron': 500, - 'zero': 556} - -widthsByFontGlyph['Helvetica-Oblique'] = {'A': 667, - 'AE': 1000, - 'Aacute': 667, - 'Acircumflex': 667, - 'Adieresis': 667, - 'Agrave': 667, - 'Aring': 667, - 'Atilde': 667, - 'B': 667, - 'C': 722, - 'Ccedilla': 722, - 'D': 722, - 'E': 667, - 'Eacute': 667, - 'Ecircumflex': 667, - 'Edieresis': 667, - 'Egrave': 667, - 'Eth': 722, - 'Euro': 556, - 'F': 611, - 'G': 778, - 'H': 722, - 'I': 278, - 'Iacute': 278, - 'Icircumflex': 278, - 'Idieresis': 278, - 'Igrave': 278, - 'J': 500, - 'K': 667, - 'L': 556, - 'Lslash': 556, - 'M': 833, - 'N': 722, - 'Ntilde': 722, - 'O': 778, - 'OE': 1000, - 'Oacute': 778, - 'Ocircumflex': 778, - 'Odieresis': 778, - 'Ograve': 778, - 'Oslash': 778, - 'Otilde': 778, - 'P': 667, - 'Q': 778, - 'R': 722, - 'S': 667, - 'Scaron': 667, - 'T': 611, - 'Thorn': 667, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 667, - 'W': 944, - 'X': 667, - 'Y': 667, - 'Yacute': 667, - 'Ydieresis': 667, - 'Z': 611, - 'Zcaron': 611, - 'a': 556, - 'aacute': 556, - 'acircumflex': 556, - 'acute': 333, - 'adieresis': 556, - 'ae': 889, - 'agrave': 556, - 'ampersand': 667, - 'aring': 556, - 'asciicircum': 469, - 'asciitilde': 584, - 'asterisk': 389, - 'at': 1015, - 'atilde': 556, - 'b': 556, - 'backslash': 278, - 'bar': 260, - 'braceleft': 334, - 'braceright': 334, - 'bracketleft': 278, - 'bracketright': 278, - 'breve': 333, - 'brokenbar': 260, - 'bullet': 350, - 'c': 500, - 'caron': 333, - 'ccedilla': 500, - 'cedilla': 333, - 'cent': 556, - 'circumflex': 333, - 'colon': 278, - 'comma': 278, - 'copyright': 737, - 'currency': 556, - 'd': 556, - 'dagger': 556, - 'daggerdbl': 556, - 'degree': 400, - 'dieresis': 333, - 'divide': 584, - 'dollar': 556, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 556, - 'eacute': 556, - 'ecircumflex': 556, - 'edieresis': 556, - 'egrave': 556, - 'eight': 556, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 556, - 'equal': 584, - 'eth': 556, - 'exclam': 278, - 'exclamdown': 333, - 'f': 278, - 'fi': 500, - 'five': 556, - 'fl': 500, - 'florin': 556, - 'four': 556, - 'fraction': 167, - 'g': 556, - 'germandbls': 611, - 'grave': 333, - 'greater': 584, - 'guillemotleft': 556, - 'guillemotright': 556, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 556, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 222, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 222, - 'k': 500, - 'l': 222, - 'less': 584, - 'logicalnot': 584, - 'lslash': 222, - 'm': 833, - 'macron': 333, - 'minus': 584, - 'mu': 556, - 'multiply': 584, - 'n': 556, - 'nine': 556, - 'ntilde': 556, - 'numbersign': 556, - 'o': 556, - 'oacute': 556, - 'ocircumflex': 556, - 'odieresis': 556, - 'oe': 944, - 'ogonek': 333, - 'ograve': 556, - 'one': 556, - 'onehalf': 834, - 'onequarter': 834, - 'onesuperior': 333, - 'ordfeminine': 370, - 'ordmasculine': 365, - 'oslash': 611, - 'otilde': 556, - 'p': 556, - 'paragraph': 537, - 'parenleft': 333, - 'parenright': 333, - 'percent': 889, - 'period': 278, - 'periodcentered': 278, - 'perthousand': 1000, - 'plus': 584, - 'plusminus': 584, - 'q': 556, - 'question': 556, - 'questiondown': 611, - 'quotedbl': 355, - 'quotedblbase': 333, - 'quotedblleft': 333, - 'quotedblright': 333, - 'quoteleft': 222, - 'quoteright': 222, - 'quotesinglbase': 222, - 'quotesingle': 191, - 'r': 333, - 'registered': 737, - 'ring': 333, - 's': 500, - 'scaron': 500, - 'section': 556, - 'semicolon': 278, - 'seven': 556, - 'six': 556, - 'slash': 278, - 'space': 278, - 'sterling': 556, - 't': 278, - 'thorn': 556, - 'three': 556, - 'threequarters': 834, - 'threesuperior': 333, - 'tilde': 333, - 'trademark': 1000, - 'two': 556, - 'twosuperior': 333, - 'u': 556, - 'uacute': 556, - 'ucircumflex': 556, - 'udieresis': 556, - 'ugrave': 556, - 'underscore': 556, - 'v': 500, - 'w': 722, - 'x': 500, - 'y': 500, - 'yacute': 500, - 'ydieresis': 500, - 'yen': 556, - 'z': 500, - 'zcaron': 500, - 'zero': 556} - - - -widthsByFontGlyph['Helvetica-BoldOblique'] = {'A': 722, - 'AE': 1000, - 'Aacute': 722, - 'Acircumflex': 722, - 'Adieresis': 722, - 'Agrave': 722, - 'Aring': 722, - 'Atilde': 722, - 'B': 722, - 'C': 722, - 'Ccedilla': 722, - 'D': 722, - 'E': 667, - 'Eacute': 667, - 'Ecircumflex': 667, - 'Edieresis': 667, - 'Egrave': 667, - 'Eth': 722, - 'Euro': 556, - 'F': 611, - 'G': 778, - 'H': 722, - 'I': 278, - 'Iacute': 278, - 'Icircumflex': 278, - 'Idieresis': 278, - 'Igrave': 278, - 'J': 556, - 'K': 722, - 'L': 611, - 'Lslash': 611, - 'M': 833, - 'N': 722, - 'Ntilde': 722, - 'O': 778, - 'OE': 1000, - 'Oacute': 778, - 'Ocircumflex': 778, - 'Odieresis': 778, - 'Ograve': 778, - 'Oslash': 778, - 'Otilde': 778, - 'P': 667, - 'Q': 778, - 'R': 722, - 'S': 667, - 'Scaron': 667, - 'T': 611, - 'Thorn': 667, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 667, - 'W': 944, - 'X': 667, - 'Y': 667, - 'Yacute': 667, - 'Ydieresis': 667, - 'Z': 611, - 'Zcaron': 611, - 'a': 556, - 'aacute': 556, - 'acircumflex': 556, - 'acute': 333, - 'adieresis': 556, - 'ae': 889, - 'agrave': 556, - 'ampersand': 722, - 'aring': 556, - 'asciicircum': 584, - 'asciitilde': 584, - 'asterisk': 389, - 'at': 975, - 'atilde': 556, - 'b': 611, - 'backslash': 278, - 'bar': 280, - 'braceleft': 389, - 'braceright': 389, - 'bracketleft': 333, - 'bracketright': 333, - 'breve': 333, - 'brokenbar': 280, - 'bullet': 350, - 'c': 556, - 'caron': 333, - 'ccedilla': 556, - 'cedilla': 333, - 'cent': 556, - 'circumflex': 333, - 'colon': 333, - 'comma': 278, - 'copyright': 737, - 'currency': 556, - 'd': 611, - 'dagger': 556, - 'daggerdbl': 556, - 'degree': 400, - 'dieresis': 333, - 'divide': 584, - 'dollar': 556, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 556, - 'eacute': 556, - 'ecircumflex': 556, - 'edieresis': 556, - 'egrave': 556, - 'eight': 556, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 556, - 'equal': 584, - 'eth': 611, - 'exclam': 333, - 'exclamdown': 333, - 'f': 333, - 'fi': 611, - 'five': 556, - 'fl': 611, - 'florin': 556, - 'four': 556, - 'fraction': 167, - 'g': 611, - 'germandbls': 611, - 'grave': 333, - 'greater': 584, - 'guillemotleft': 556, - 'guillemotright': 556, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 611, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 278, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 278, - 'k': 556, - 'l': 278, - 'less': 584, - 'logicalnot': 584, - 'lslash': 278, - 'm': 889, - 'macron': 333, - 'minus': 584, - 'mu': 611, - 'multiply': 584, - 'n': 611, - 'nine': 556, - 'ntilde': 611, - 'numbersign': 556, - 'o': 611, - 'oacute': 611, - 'ocircumflex': 611, - 'odieresis': 611, - 'oe': 944, - 'ogonek': 333, - 'ograve': 611, - 'one': 556, - 'onehalf': 834, - 'onequarter': 834, - 'onesuperior': 333, - 'ordfeminine': 370, - 'ordmasculine': 365, - 'oslash': 611, - 'otilde': 611, - 'p': 611, - 'paragraph': 556, - 'parenleft': 333, - 'parenright': 333, - 'percent': 889, - 'period': 278, - 'periodcentered': 278, - 'perthousand': 1000, - 'plus': 584, - 'plusminus': 584, - 'q': 611, - 'question': 611, - 'questiondown': 611, - 'quotedbl': 474, - 'quotedblbase': 500, - 'quotedblleft': 500, - 'quotedblright': 500, - 'quoteleft': 278, - 'quoteright': 278, - 'quotesinglbase': 278, - 'quotesingle': 238, - 'r': 389, - 'registered': 737, - 'ring': 333, - 's': 556, - 'scaron': 556, - 'section': 556, - 'semicolon': 333, - 'seven': 556, - 'six': 556, - 'slash': 278, - 'space': 278, - 'sterling': 556, - 't': 333, - 'thorn': 611, - 'three': 556, - 'threequarters': 834, - 'threesuperior': 333, - 'tilde': 333, - 'trademark': 1000, - 'two': 556, - 'twosuperior': 333, - 'u': 611, - 'uacute': 611, - 'ucircumflex': 611, - 'udieresis': 611, - 'ugrave': 611, - 'underscore': 556, - 'v': 556, - 'w': 778, - 'x': 556, - 'y': 556, - 'yacute': 556, - 'ydieresis': 556, - 'yen': 556, - 'z': 500, - 'zcaron': 500, - 'zero': 556} - -# Courier can be expressed more compactly! -_w = {} -for charname in widthsByFontGlyph['Helvetica'].keys(): - _w[charname] = 600 -widthsByFontGlyph['Courier'] = _w -widthsByFontGlyph['Courier-Bold'] = _w -widthsByFontGlyph['Courier-Oblique'] = _w -widthsByFontGlyph['Courier-BoldOblique'] = _w - -widthsByFontGlyph['Times-Roman'] = {'A': 722, - 'AE': 889, - 'Aacute': 722, - 'Acircumflex': 722, - 'Adieresis': 722, - 'Agrave': 722, - 'Aring': 722, - 'Atilde': 722, - 'B': 667, - 'C': 667, - 'Ccedilla': 667, - 'D': 722, - 'E': 611, - 'Eacute': 611, - 'Ecircumflex': 611, - 'Edieresis': 611, - 'Egrave': 611, - 'Eth': 722, - 'Euro': 500, - 'F': 556, - 'G': 722, - 'H': 722, - 'I': 333, - 'Iacute': 333, - 'Icircumflex': 333, - 'Idieresis': 333, - 'Igrave': 333, - 'J': 389, - 'K': 722, - 'L': 611, - 'Lslash': 611, - 'M': 889, - 'N': 722, - 'Ntilde': 722, - 'O': 722, - 'OE': 889, - 'Oacute': 722, - 'Ocircumflex': 722, - 'Odieresis': 722, - 'Ograve': 722, - 'Oslash': 722, - 'Otilde': 722, - 'P': 556, - 'Q': 722, - 'R': 667, - 'S': 556, - 'Scaron': 556, - 'T': 611, - 'Thorn': 556, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 722, - 'W': 944, - 'X': 722, - 'Y': 722, - 'Yacute': 722, - 'Ydieresis': 722, - 'Z': 611, - 'Zcaron': 611, - 'a': 444, - 'aacute': 444, - 'acircumflex': 444, - 'acute': 333, - 'adieresis': 444, - 'ae': 667, - 'agrave': 444, - 'ampersand': 778, - 'aring': 444, - 'asciicircum': 469, - 'asciitilde': 541, - 'asterisk': 500, - 'at': 921, - 'atilde': 444, - 'b': 500, - 'backslash': 278, - 'bar': 200, - 'braceleft': 480, - 'braceright': 480, - 'bracketleft': 333, - 'bracketright': 333, - 'breve': 333, - 'brokenbar': 200, - 'bullet': 350, - 'c': 444, - 'caron': 333, - 'ccedilla': 444, - 'cedilla': 333, - 'cent': 500, - 'circumflex': 333, - 'colon': 278, - 'comma': 250, - 'copyright': 760, - 'currency': 500, - 'd': 500, - 'dagger': 500, - 'daggerdbl': 500, - 'degree': 400, - 'dieresis': 333, - 'divide': 564, - 'dollar': 500, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 444, - 'eacute': 444, - 'ecircumflex': 444, - 'edieresis': 444, - 'egrave': 444, - 'eight': 500, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 500, - 'equal': 564, - 'eth': 500, - 'exclam': 333, - 'exclamdown': 333, - 'f': 333, - 'fi': 556, - 'five': 500, - 'fl': 556, - 'florin': 500, - 'four': 500, - 'fraction': 167, - 'g': 500, - 'germandbls': 500, - 'grave': 333, - 'greater': 564, - 'guillemotleft': 500, - 'guillemotright': 500, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 500, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 278, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 278, - 'k': 500, - 'l': 278, - 'less': 564, - 'logicalnot': 564, - 'lslash': 278, - 'm': 778, - 'macron': 333, - 'minus': 564, - 'mu': 500, - 'multiply': 564, - 'n': 500, - 'nine': 500, - 'ntilde': 500, - 'numbersign': 500, - 'o': 500, - 'oacute': 500, - 'ocircumflex': 500, - 'odieresis': 500, - 'oe': 722, - 'ogonek': 333, - 'ograve': 500, - 'one': 500, - 'onehalf': 750, - 'onequarter': 750, - 'onesuperior': 300, - 'ordfeminine': 276, - 'ordmasculine': 310, - 'oslash': 500, - 'otilde': 500, - 'p': 500, - 'paragraph': 453, - 'parenleft': 333, - 'parenright': 333, - 'percent': 833, - 'period': 250, - 'periodcentered': 250, - 'perthousand': 1000, - 'plus': 564, - 'plusminus': 564, - 'q': 500, - 'question': 444, - 'questiondown': 444, - 'quotedbl': 408, - 'quotedblbase': 444, - 'quotedblleft': 444, - 'quotedblright': 444, - 'quoteleft': 333, - 'quoteright': 333, - 'quotesinglbase': 333, - 'quotesingle': 180, - 'r': 333, - 'registered': 760, - 'ring': 333, - 's': 389, - 'scaron': 389, - 'section': 500, - 'semicolon': 278, - 'seven': 500, - 'six': 500, - 'slash': 278, - 'space': 250, - 'sterling': 500, - 't': 278, - 'thorn': 500, - 'three': 500, - 'threequarters': 750, - 'threesuperior': 300, - 'tilde': 333, - 'trademark': 980, - 'two': 500, - 'twosuperior': 300, - 'u': 500, - 'uacute': 500, - 'ucircumflex': 500, - 'udieresis': 500, - 'ugrave': 500, - 'underscore': 500, - 'v': 500, - 'w': 722, - 'x': 500, - 'y': 500, - 'yacute': 500, - 'ydieresis': 500, - 'yen': 500, - 'z': 444, - 'zcaron': 444, - 'zero': 500} - -widthsByFontGlyph['Times-Bold'] = {'A': 722, - 'AE': 1000, - 'Aacute': 722, - 'Acircumflex': 722, - 'Adieresis': 722, - 'Agrave': 722, - 'Aring': 722, - 'Atilde': 722, - 'B': 667, - 'C': 722, - 'Ccedilla': 722, - 'D': 722, - 'E': 667, - 'Eacute': 667, - 'Ecircumflex': 667, - 'Edieresis': 667, - 'Egrave': 667, - 'Eth': 722, - 'Euro': 500, - 'F': 611, - 'G': 778, - 'H': 778, - 'I': 389, - 'Iacute': 389, - 'Icircumflex': 389, - 'Idieresis': 389, - 'Igrave': 389, - 'J': 500, - 'K': 778, - 'L': 667, - 'Lslash': 667, - 'M': 944, - 'N': 722, - 'Ntilde': 722, - 'O': 778, - 'OE': 1000, - 'Oacute': 778, - 'Ocircumflex': 778, - 'Odieresis': 778, - 'Ograve': 778, - 'Oslash': 778, - 'Otilde': 778, - 'P': 611, - 'Q': 778, - 'R': 722, - 'S': 556, - 'Scaron': 556, - 'T': 667, - 'Thorn': 611, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 722, - 'W': 1000, - 'X': 722, - 'Y': 722, - 'Yacute': 722, - 'Ydieresis': 722, - 'Z': 667, - 'Zcaron': 667, - 'a': 500, - 'aacute': 500, - 'acircumflex': 500, - 'acute': 333, - 'adieresis': 500, - 'ae': 722, - 'agrave': 500, - 'ampersand': 833, - 'aring': 500, - 'asciicircum': 581, - 'asciitilde': 520, - 'asterisk': 500, - 'at': 930, - 'atilde': 500, - 'b': 556, - 'backslash': 278, - 'bar': 220, - 'braceleft': 394, - 'braceright': 394, - 'bracketleft': 333, - 'bracketright': 333, - 'breve': 333, - 'brokenbar': 220, - 'bullet': 350, - 'c': 444, - 'caron': 333, - 'ccedilla': 444, - 'cedilla': 333, - 'cent': 500, - 'circumflex': 333, - 'colon': 333, - 'comma': 250, - 'copyright': 747, - 'currency': 500, - 'd': 556, - 'dagger': 500, - 'daggerdbl': 500, - 'degree': 400, - 'dieresis': 333, - 'divide': 570, - 'dollar': 500, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 444, - 'eacute': 444, - 'ecircumflex': 444, - 'edieresis': 444, - 'egrave': 444, - 'eight': 500, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 500, - 'equal': 570, - 'eth': 500, - 'exclam': 333, - 'exclamdown': 333, - 'f': 333, - 'fi': 556, - 'five': 500, - 'fl': 556, - 'florin': 500, - 'four': 500, - 'fraction': 167, - 'g': 500, - 'germandbls': 556, - 'grave': 333, - 'greater': 570, - 'guillemotleft': 500, - 'guillemotright': 500, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 556, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 278, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 333, - 'k': 556, - 'l': 278, - 'less': 570, - 'logicalnot': 570, - 'lslash': 278, - 'm': 833, - 'macron': 333, - 'minus': 570, - 'mu': 556, - 'multiply': 570, - 'n': 556, - 'nine': 500, - 'ntilde': 556, - 'numbersign': 500, - 'o': 500, - 'oacute': 500, - 'ocircumflex': 500, - 'odieresis': 500, - 'oe': 722, - 'ogonek': 333, - 'ograve': 500, - 'one': 500, - 'onehalf': 750, - 'onequarter': 750, - 'onesuperior': 300, - 'ordfeminine': 300, - 'ordmasculine': 330, - 'oslash': 500, - 'otilde': 500, - 'p': 556, - 'paragraph': 540, - 'parenleft': 333, - 'parenright': 333, - 'percent': 1000, - 'period': 250, - 'periodcentered': 250, - 'perthousand': 1000, - 'plus': 570, - 'plusminus': 570, - 'q': 556, - 'question': 500, - 'questiondown': 500, - 'quotedbl': 555, - 'quotedblbase': 500, - 'quotedblleft': 500, - 'quotedblright': 500, - 'quoteleft': 333, - 'quoteright': 333, - 'quotesinglbase': 333, - 'quotesingle': 278, - 'r': 444, - 'registered': 747, - 'ring': 333, - 's': 389, - 'scaron': 389, - 'section': 500, - 'semicolon': 333, - 'seven': 500, - 'six': 500, - 'slash': 278, - 'space': 250, - 'sterling': 500, - 't': 333, - 'thorn': 556, - 'three': 500, - 'threequarters': 750, - 'threesuperior': 300, - 'tilde': 333, - 'trademark': 1000, - 'two': 500, - 'twosuperior': 300, - 'u': 556, - 'uacute': 556, - 'ucircumflex': 556, - 'udieresis': 556, - 'ugrave': 556, - 'underscore': 500, - 'v': 500, - 'w': 722, - 'x': 500, - 'y': 500, - 'yacute': 500, - 'ydieresis': 500, - 'yen': 500, - 'z': 444, - 'zcaron': 444, - 'zero': 500} - -widthsByFontGlyph['Times-Italic'] = {'A': 611, - 'AE': 889, - 'Aacute': 611, - 'Acircumflex': 611, - 'Adieresis': 611, - 'Agrave': 611, - 'Aring': 611, - 'Atilde': 611, - 'B': 611, - 'C': 667, - 'Ccedilla': 667, - 'D': 722, - 'E': 611, - 'Eacute': 611, - 'Ecircumflex': 611, - 'Edieresis': 611, - 'Egrave': 611, - 'Eth': 722, - 'Euro': 500, - 'F': 611, - 'G': 722, - 'H': 722, - 'I': 333, - 'Iacute': 333, - 'Icircumflex': 333, - 'Idieresis': 333, - 'Igrave': 333, - 'J': 444, - 'K': 667, - 'L': 556, - 'Lslash': 556, - 'M': 833, - 'N': 667, - 'Ntilde': 667, - 'O': 722, - 'OE': 944, - 'Oacute': 722, - 'Ocircumflex': 722, - 'Odieresis': 722, - 'Ograve': 722, - 'Oslash': 722, - 'Otilde': 722, - 'P': 611, - 'Q': 722, - 'R': 611, - 'S': 500, - 'Scaron': 500, - 'T': 556, - 'Thorn': 611, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 611, - 'W': 833, - 'X': 611, - 'Y': 556, - 'Yacute': 556, - 'Ydieresis': 556, - 'Z': 556, - 'Zcaron': 556, - 'a': 500, - 'aacute': 500, - 'acircumflex': 500, - 'acute': 333, - 'adieresis': 500, - 'ae': 667, - 'agrave': 500, - 'ampersand': 778, - 'aring': 500, - 'asciicircum': 422, - 'asciitilde': 541, - 'asterisk': 500, - 'at': 920, - 'atilde': 500, - 'b': 500, - 'backslash': 278, - 'bar': 275, - 'braceleft': 400, - 'braceright': 400, - 'bracketleft': 389, - 'bracketright': 389, - 'breve': 333, - 'brokenbar': 275, - 'bullet': 350, - 'c': 444, - 'caron': 333, - 'ccedilla': 444, - 'cedilla': 333, - 'cent': 500, - 'circumflex': 333, - 'colon': 333, - 'comma': 250, - 'copyright': 760, - 'currency': 500, - 'd': 500, - 'dagger': 500, - 'daggerdbl': 500, - 'degree': 400, - 'dieresis': 333, - 'divide': 675, - 'dollar': 500, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 444, - 'eacute': 444, - 'ecircumflex': 444, - 'edieresis': 444, - 'egrave': 444, - 'eight': 500, - 'ellipsis': 889, - 'emdash': 889, - 'endash': 500, - 'equal': 675, - 'eth': 500, - 'exclam': 333, - 'exclamdown': 389, - 'f': 278, - 'fi': 500, - 'five': 500, - 'fl': 500, - 'florin': 500, - 'four': 500, - 'fraction': 167, - 'g': 500, - 'germandbls': 500, - 'grave': 333, - 'greater': 675, - 'guillemotleft': 500, - 'guillemotright': 500, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 500, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 278, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 278, - 'k': 444, - 'l': 278, - 'less': 675, - 'logicalnot': 675, - 'lslash': 278, - 'm': 722, - 'macron': 333, - 'minus': 675, - 'mu': 500, - 'multiply': 675, - 'n': 500, - 'nine': 500, - 'ntilde': 500, - 'numbersign': 500, - 'o': 500, - 'oacute': 500, - 'ocircumflex': 500, - 'odieresis': 500, - 'oe': 667, - 'ogonek': 333, - 'ograve': 500, - 'one': 500, - 'onehalf': 750, - 'onequarter': 750, - 'onesuperior': 300, - 'ordfeminine': 276, - 'ordmasculine': 310, - 'oslash': 500, - 'otilde': 500, - 'p': 500, - 'paragraph': 523, - 'parenleft': 333, - 'parenright': 333, - 'percent': 833, - 'period': 250, - 'periodcentered': 250, - 'perthousand': 1000, - 'plus': 675, - 'plusminus': 675, - 'q': 500, - 'question': 500, - 'questiondown': 500, - 'quotedbl': 420, - 'quotedblbase': 556, - 'quotedblleft': 556, - 'quotedblright': 556, - 'quoteleft': 333, - 'quoteright': 333, - 'quotesinglbase': 333, - 'quotesingle': 214, - 'r': 389, - 'registered': 760, - 'ring': 333, - 's': 389, - 'scaron': 389, - 'section': 500, - 'semicolon': 333, - 'seven': 500, - 'six': 500, - 'slash': 278, - 'space': 250, - 'sterling': 500, - 't': 278, - 'thorn': 500, - 'three': 500, - 'threequarters': 750, - 'threesuperior': 300, - 'tilde': 333, - 'trademark': 980, - 'two': 500, - 'twosuperior': 300, - 'u': 500, - 'uacute': 500, - 'ucircumflex': 500, - 'udieresis': 500, - 'ugrave': 500, - 'underscore': 500, - 'v': 444, - 'w': 667, - 'x': 444, - 'y': 444, - 'yacute': 444, - 'ydieresis': 444, - 'yen': 500, - 'z': 389, - 'zcaron': 389, - 'zero': 500} - -widthsByFontGlyph['Times-BoldItalic'] = {'A': 667, - 'AE': 944, - 'Aacute': 667, - 'Acircumflex': 667, - 'Adieresis': 667, - 'Agrave': 667, - 'Aring': 667, - 'Atilde': 667, - 'B': 667, - 'C': 667, - 'Ccedilla': 667, - 'D': 722, - 'E': 667, - 'Eacute': 667, - 'Ecircumflex': 667, - 'Edieresis': 667, - 'Egrave': 667, - 'Eth': 722, - 'Euro': 500, - 'F': 667, - 'G': 722, - 'H': 778, - 'I': 389, - 'Iacute': 389, - 'Icircumflex': 389, - 'Idieresis': 389, - 'Igrave': 389, - 'J': 500, - 'K': 667, - 'L': 611, - 'Lslash': 611, - 'M': 889, - 'N': 722, - 'Ntilde': 722, - 'O': 722, - 'OE': 944, - 'Oacute': 722, - 'Ocircumflex': 722, - 'Odieresis': 722, - 'Ograve': 722, - 'Oslash': 722, - 'Otilde': 722, - 'P': 611, - 'Q': 722, - 'R': 667, - 'S': 556, - 'Scaron': 556, - 'T': 611, - 'Thorn': 611, - 'U': 722, - 'Uacute': 722, - 'Ucircumflex': 722, - 'Udieresis': 722, - 'Ugrave': 722, - 'V': 667, - 'W': 889, - 'X': 667, - 'Y': 611, - 'Yacute': 611, - 'Ydieresis': 611, - 'Z': 611, - 'Zcaron': 611, - 'a': 500, - 'aacute': 500, - 'acircumflex': 500, - 'acute': 333, - 'adieresis': 500, - 'ae': 722, - 'agrave': 500, - 'ampersand': 778, - 'aring': 500, - 'asciicircum': 570, - 'asciitilde': 570, - 'asterisk': 500, - 'at': 832, - 'atilde': 500, - 'b': 500, - 'backslash': 278, - 'bar': 220, - 'braceleft': 348, - 'braceright': 348, - 'bracketleft': 333, - 'bracketright': 333, - 'breve': 333, - 'brokenbar': 220, - 'bullet': 350, - 'c': 444, - 'caron': 333, - 'ccedilla': 444, - 'cedilla': 333, - 'cent': 500, - 'circumflex': 333, - 'colon': 333, - 'comma': 250, - 'copyright': 747, - 'currency': 500, - 'd': 500, - 'dagger': 500, - 'daggerdbl': 500, - 'degree': 400, - 'dieresis': 333, - 'divide': 570, - 'dollar': 500, - 'dotaccent': 333, - 'dotlessi': 278, - 'e': 444, - 'eacute': 444, - 'ecircumflex': 444, - 'edieresis': 444, - 'egrave': 444, - 'eight': 500, - 'ellipsis': 1000, - 'emdash': 1000, - 'endash': 500, - 'equal': 570, - 'eth': 500, - 'exclam': 389, - 'exclamdown': 389, - 'f': 333, - 'fi': 556, - 'five': 500, - 'fl': 556, - 'florin': 500, - 'four': 500, - 'fraction': 167, - 'g': 500, - 'germandbls': 500, - 'grave': 333, - 'greater': 570, - 'guillemotleft': 500, - 'guillemotright': 500, - 'guilsinglleft': 333, - 'guilsinglright': 333, - 'h': 556, - 'hungarumlaut': 333, - 'hyphen': 333, - 'i': 278, - 'iacute': 278, - 'icircumflex': 278, - 'idieresis': 278, - 'igrave': 278, - 'j': 278, - 'k': 500, - 'l': 278, - 'less': 570, - 'logicalnot': 606, - 'lslash': 278, - 'm': 778, - 'macron': 333, - 'minus': 606, - 'mu': 576, - 'multiply': 570, - 'n': 556, - 'nine': 500, - 'ntilde': 556, - 'numbersign': 500, - 'o': 500, - 'oacute': 500, - 'ocircumflex': 500, - 'odieresis': 500, - 'oe': 722, - 'ogonek': 333, - 'ograve': 500, - 'one': 500, - 'onehalf': 750, - 'onequarter': 750, - 'onesuperior': 300, - 'ordfeminine': 266, - 'ordmasculine': 300, - 'oslash': 500, - 'otilde': 500, - 'p': 500, - 'paragraph': 500, - 'parenleft': 333, - 'parenright': 333, - 'percent': 833, - 'period': 250, - 'periodcentered': 250, - 'perthousand': 1000, - 'plus': 570, - 'plusminus': 570, - 'q': 500, - 'question': 500, - 'questiondown': 500, - 'quotedbl': 555, - 'quotedblbase': 500, - 'quotedblleft': 500, - 'quotedblright': 500, - 'quoteleft': 333, - 'quoteright': 333, - 'quotesinglbase': 333, - 'quotesingle': 278, - 'r': 389, - 'registered': 747, - 'ring': 333, - 's': 389, - 'scaron': 389, - 'section': 500, - 'semicolon': 333, - 'seven': 500, - 'six': 500, - 'slash': 278, - 'space': 250, - 'sterling': 500, - 't': 278, - 'thorn': 500, - 'three': 500, - 'threequarters': 750, - 'threesuperior': 300, - 'tilde': 333, - 'trademark': 1000, - 'two': 500, - 'twosuperior': 300, - 'u': 556, - 'uacute': 556, - 'ucircumflex': 556, - 'udieresis': 556, - 'ugrave': 556, - 'underscore': 500, - 'v': 444, - 'w': 667, - 'x': 500, - 'y': 444, - 'yacute': 444, - 'ydieresis': 444, - 'yen': 500, - 'z': 389, - 'zcaron': 389, - 'zero': 500} - -widthsByFontGlyph['Symbol'] = {'Alpha': 722, - 'Beta': 667, - 'Chi': 722, - 'Delta': 612, - 'Epsilon': 611, - 'Eta': 722, - 'Euro': 750, - 'Gamma': 603, - 'Ifraktur': 686, - 'Iota': 333, - 'Kappa': 722, - 'Lambda': 686, - 'Mu': 889, - 'Nu': 722, - 'Omega': 768, - 'Omicron': 722, - 'Phi': 763, - 'Pi': 768, - 'Psi': 795, - 'Rfraktur': 795, - 'Rho': 556, - 'Sigma': 592, - 'Tau': 611, - 'Theta': 741, - 'Upsilon': 690, - 'Upsilon1': 620, - 'Xi': 645, - 'Zeta': 611, - 'aleph': 823, - 'alpha': 631, - 'ampersand': 778, - 'angle': 768, - 'angleleft': 329, - 'angleright': 329, - 'apple': 790, - 'approxequal': 549, - 'arrowboth': 1042, - 'arrowdblboth': 1042, - 'arrowdbldown': 603, - 'arrowdblleft': 987, - 'arrowdblright': 987, - 'arrowdblup': 603, - 'arrowdown': 603, - 'arrowhorizex': 1000, - 'arrowleft': 987, - 'arrowright': 987, - 'arrowup': 603, - 'arrowvertex': 603, - 'asteriskmath': 500, - 'bar': 200, - 'beta': 549, - 'braceex': 494, - 'braceleft': 480, - 'braceleftbt': 494, - 'braceleftmid': 494, - 'bracelefttp': 494, - 'braceright': 480, - 'bracerightbt': 494, - 'bracerightmid': 494, - 'bracerighttp': 494, - 'bracketleft': 333, - 'bracketleftbt': 384, - 'bracketleftex': 384, - 'bracketlefttp': 384, - 'bracketright': 333, - 'bracketrightbt': 384, - 'bracketrightex': 384, - 'bracketrighttp': 384, - 'bullet': 460, - 'carriagereturn': 658, - 'chi': 549, - 'circlemultiply': 768, - 'circleplus': 768, - 'club': 753, - 'colon': 278, - 'comma': 250, - 'congruent': 549, - 'copyrightsans': 790, - 'copyrightserif': 790, - 'degree': 400, - 'delta': 494, - 'diamond': 753, - 'divide': 549, - 'dotmath': 250, - 'eight': 500, - 'element': 713, - 'ellipsis': 1000, - 'emptyset': 823, - 'epsilon': 439, - 'equal': 549, - 'equivalence': 549, - 'eta': 603, - 'exclam': 333, - 'existential': 549, - 'five': 500, - 'florin': 500, - 'four': 500, - 'fraction': 167, - 'gamma': 411, - 'gradient': 713, - 'greater': 549, - 'greaterequal': 549, - 'heart': 753, - 'infinity': 713, - 'integral': 274, - 'integralbt': 686, - 'integralex': 686, - 'integraltp': 686, - 'intersection': 768, - 'iota': 329, - 'kappa': 549, - 'lambda': 549, - 'less': 549, - 'lessequal': 549, - 'logicaland': 603, - 'logicalnot': 713, - 'logicalor': 603, - 'lozenge': 494, - 'minus': 549, - 'minute': 247, - 'mu': 576, - 'multiply': 549, - 'nine': 500, - 'notelement': 713, - 'notequal': 549, - 'notsubset': 713, - 'nu': 521, - 'numbersign': 500, - 'omega': 686, - 'omega1': 713, - 'omicron': 549, - 'one': 500, - 'parenleft': 333, - 'parenleftbt': 384, - 'parenleftex': 384, - 'parenlefttp': 384, - 'parenright': 333, - 'parenrightbt': 384, - 'parenrightex': 384, - 'parenrighttp': 384, - 'partialdiff': 494, - 'percent': 833, - 'period': 250, - 'perpendicular': 658, - 'phi': 521, - 'phi1': 603, - 'pi': 549, - 'plus': 549, - 'plusminus': 549, - 'product': 823, - 'propersubset': 713, - 'propersuperset': 713, - 'proportional': 713, - 'psi': 686, - 'question': 444, - 'radical': 549, - 'radicalex': 500, - 'reflexsubset': 713, - 'reflexsuperset': 713, - 'registersans': 790, - 'registerserif': 790, - 'rho': 549, - 'second': 411, - 'semicolon': 278, - 'seven': 500, - 'sigma': 603, - 'sigma1': 439, - 'similar': 549, - 'six': 500, - 'slash': 278, - 'space': 250, - 'spade': 753, - 'suchthat': 439, - 'summation': 713, - 'tau': 439, - 'therefore': 863, - 'theta': 521, - 'theta1': 631, - 'three': 500, - 'trademarksans': 786, - 'trademarkserif': 890, - 'two': 500, - 'underscore': 500, - 'union': 768, - 'universal': 713, - 'upsilon': 576, - 'weierstrass': 987, - 'xi': 493, - 'zero': 500, - 'zeta': 494} - -widthsByFontGlyph['ZapfDingbats'] = {'a1': 974, - 'a10': 692, - 'a100': 668, - 'a101': 732, - 'a102': 544, - 'a103': 544, - 'a104': 910, - 'a105': 911, - 'a106': 667, - 'a107': 760, - 'a108': 760, - 'a109': 626, - 'a11': 960, - 'a110': 694, - 'a111': 595, - 'a112': 776, - 'a117': 690, - 'a118': 791, - 'a119': 790, - 'a12': 939, - 'a120': 788, - 'a121': 788, - 'a122': 788, - 'a123': 788, - 'a124': 788, - 'a125': 788, - 'a126': 788, - 'a127': 788, - 'a128': 788, - 'a129': 788, - 'a13': 549, - 'a130': 788, - 'a131': 788, - 'a132': 788, - 'a133': 788, - 'a134': 788, - 'a135': 788, - 'a136': 788, - 'a137': 788, - 'a138': 788, - 'a139': 788, - 'a14': 855, - 'a140': 788, - 'a141': 788, - 'a142': 788, - 'a143': 788, - 'a144': 788, - 'a145': 788, - 'a146': 788, - 'a147': 788, - 'a148': 788, - 'a149': 788, - 'a15': 911, - 'a150': 788, - 'a151': 788, - 'a152': 788, - 'a153': 788, - 'a154': 788, - 'a155': 788, - 'a156': 788, - 'a157': 788, - 'a158': 788, - 'a159': 788, - 'a16': 933, - 'a160': 894, - 'a161': 838, - 'a162': 924, - 'a163': 1016, - 'a164': 458, - 'a165': 924, - 'a166': 918, - 'a167': 927, - 'a168': 928, - 'a169': 928, - 'a17': 945, - 'a170': 834, - 'a171': 873, - 'a172': 828, - 'a173': 924, - 'a174': 917, - 'a175': 930, - 'a176': 931, - 'a177': 463, - 'a178': 883, - 'a179': 836, - 'a18': 974, - 'a180': 867, - 'a181': 696, - 'a182': 874, - 'a183': 760, - 'a184': 946, - 'a185': 865, - 'a186': 967, - 'a187': 831, - 'a188': 873, - 'a189': 927, - 'a19': 755, - 'a190': 970, - 'a191': 918, - 'a192': 748, - 'a193': 836, - 'a194': 771, - 'a195': 888, - 'a196': 748, - 'a197': 771, - 'a198': 888, - 'a199': 867, - 'a2': 961, - 'a20': 846, - 'a200': 696, - 'a201': 874, - 'a202': 974, - 'a203': 762, - 'a204': 759, - 'a205': 509, - 'a206': 410, - 'a21': 762, - 'a22': 761, - 'a23': 571, - 'a24': 677, - 'a25': 763, - 'a26': 760, - 'a27': 759, - 'a28': 754, - 'a29': 786, - 'a3': 980, - 'a30': 788, - 'a31': 788, - 'a32': 790, - 'a33': 793, - 'a34': 794, - 'a35': 816, - 'a36': 823, - 'a37': 789, - 'a38': 841, - 'a39': 823, - 'a4': 719, - 'a40': 833, - 'a41': 816, - 'a42': 831, - 'a43': 923, - 'a44': 744, - 'a45': 723, - 'a46': 749, - 'a47': 790, - 'a48': 792, - 'a49': 695, - 'a5': 789, - 'a50': 776, - 'a51': 768, - 'a52': 792, - 'a53': 759, - 'a54': 707, - 'a55': 708, - 'a56': 682, - 'a57': 701, - 'a58': 826, - 'a59': 815, - 'a6': 494, - 'a60': 789, - 'a61': 789, - 'a62': 707, - 'a63': 687, - 'a64': 696, - 'a65': 689, - 'a66': 786, - 'a67': 787, - 'a68': 713, - 'a69': 791, - 'a7': 552, - 'a70': 785, - 'a71': 791, - 'a72': 873, - 'a73': 761, - 'a74': 762, - 'a75': 759, - 'a76': 892, - 'a77': 892, - 'a78': 788, - 'a79': 784, - 'a8': 537, - 'a81': 438, - 'a82': 138, - 'a83': 277, - 'a84': 415, - 'a85': 509, - 'a86': 410, - 'a87': 234, - 'a88': 234, - 'a89': 390, - 'a9': 577, - 'a90': 390, - 'a91': 276, - 'a92': 276, - 'a93': 317, - 'a94': 317, - 'a95': 334, - 'a96': 334, - 'a97': 392, - 'a98': 392, - 'a99': 668, - 'space': 278} diff --git a/bin/reportlab/pdfbase/cidfonts.py b/bin/reportlab/pdfbase/cidfonts.py deleted file mode 100644 index 37af8bb1812..00000000000 --- a/bin/reportlab/pdfbase/cidfonts.py +++ /dev/null @@ -1,399 +0,0 @@ -#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/pdfbase/cidfonts.py -#$Header $ -__version__=''' $Id$ ''' -__doc__="""CID (Asian multi-byte) font support. - -This defines classes to represent CID fonts. They know how to calculate -their own width and how to write themselves into PDF files.""" - -import os -from types import ListType, TupleType, DictType -from string import find, split, strip -import marshal -import md5 -import time - -import reportlab -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfbase._cidfontdata import allowedTypeFaces, allowedEncodings, CIDFontInfo -from reportlab.pdfgen.canvas import Canvas -from reportlab.pdfbase import pdfdoc -from reportlab.rl_config import CMapSearchPath - - -def findCMapFile(name): - "Returns full filename, or raises error" - for dirname in CMapSearchPath: - cmapfile = dirname + os.sep + name - if os.path.isfile(cmapfile): - return cmapfile - raise IOError, 'CMAP file for encodings "%s" not found!' % name - -def structToPDF(structure): - "Converts deeply nested structure to PDFdoc dictionary/array objects" - if type(structure) is DictType: - newDict = {} - for k, v in structure.items(): - newDict[k] = structToPDF(v) - return pdfdoc.PDFDictionary(newDict) - elif type(structure) in (ListType, TupleType): - newList = [] - for elem in structure: - newList.append(structToPDF(elem)) - return pdfdoc.PDFArray(newList) - else: - return structure - -class CIDEncoding(pdfmetrics.Encoding): - """Multi-byte encoding. These are loaded from CMAP files. - - A CMAP file is like a mini-codec. It defines the correspondence - between code points in the (multi-byte) input data and Character - IDs. """ - # aims to do similar things to Brian Hooper's CMap class, - # but I could not get it working and had to rewrite. - # also, we should really rearrange our current encoding - # into a SingleByteEncoding since many of its methods - # should not apply here. - - def __init__(self, name, useCache=1): - self.name = name - self._mapFileHash = None - self._codeSpaceRanges = [] - self._notDefRanges = [] - self._cmap = {} - self.source = None - if useCache: - from reportlab.lib.utils import get_rl_tempdir - fontmapdir = get_rl_tempdir('FastCMAPS') - if os.path.isfile(fontmapdir + os.sep + name + '.fastmap'): - self.fastLoad(fontmapdir) - self.source = fontmapdir + os.sep + name + '.fastmap' - else: - self.parseCMAPFile(name) - self.source = 'CMAP: ' + name - self.fastSave(fontmapdir) - else: - self.parseCMAPFile(name) - - def _hash(self, text): - hasher = md5.new() - hasher.update(text) - return hasher.digest() - - def parseCMAPFile(self, name): - """This is a tricky one as CMAP files are Postscript - ones. Some refer to others with a 'usecmap' - command""" - started = time.clock() - cmapfile = findCMapFile(name) - # this will CRAWL with the unicode encodings... - rawdata = open(cmapfile, 'r').read() - - self._mapFileHash = self._hash(rawdata) - #if it contains the token 'usecmap', parse the other - #cmap file first.... - usecmap_pos = find(rawdata, 'usecmap') - if usecmap_pos > -1: - #they tell us to look in another file - #for the code space ranges. The one - # to use will be the previous word. - chunk = rawdata[0:usecmap_pos] - words = split(chunk) - otherCMAPName = words[-1] - #print 'referred to another CMAP %s' % otherCMAPName - self.parseCMAPFile(otherCMAPName) - # now continue parsing this, as it may - # override some settings - - - words = split(rawdata) - while words <> []: - if words[0] == 'begincodespacerange': - words = words[1:] - while words[0] <> 'endcodespacerange': - strStart, strEnd, words = words[0], words[1], words[2:] - start = int(strStart[1:-1], 16) - end = int(strEnd[1:-1], 16) - self._codeSpaceRanges.append((start, end),) - elif words[0] == 'beginnotdefrange': - words = words[1:] - while words[0] <> 'endnotdefrange': - strStart, strEnd, strValue = words[0:3] - start = int(strStart[1:-1], 16) - end = int(strEnd[1:-1], 16) - value = int(strValue) - self._notDefRanges.append((start, end, value),) - words = words[3:] - elif words[0] == 'begincidrange': - words = words[1:] - while words[0] <> 'endcidrange': - strStart, strEnd, strValue = words[0:3] - start = int(strStart[1:-1], 16) - end = int(strEnd[1:-1], 16) - value = int(strValue) - # this means that 'start' corresponds to 'value', - # start+1 corresponds to value+1 and so on up - # to end - offset = 0 - while start + offset <= end: - self._cmap[start + offset] = value + offset - offset = offset + 1 - words = words[3:] - - else: - words = words[1:] - finished = time.clock() - print 'parsed CMAP %s in %0.4f seconds' % (self.name, finished - started) - - def translate(self, text): - "Convert a string into a list of CIDs" - output = [] - cmap = self._cmap - lastChar = '' - for char in text: - if lastChar <> '': - #print 'convert character pair "%s"' % (lastChar + char) - num = ord(lastChar) * 256 + ord(char) - else: - #print 'convert character "%s"' % char - num = ord(char) - lastChar = char - found = 0 - for low, high in self._codeSpaceRanges: - if low < num < high: - try: - cid = cmap[num] - #print '%d -> %d' % (num, cid) - except KeyError: - #not defined. Try to find the appropriate - # notdef character, or failing that return - # zero - cid = 0 - for low2, high2, notdef in self._notDefRanges: - if low2 < num < high2: - cid = notdef - break - output.append(cid) - found = 1 - break - if found: - lastChar = '' - else: - lastChar = char - return output - - def fastSave(self, directory): - f = open(os.path.join(directory, self.name + '.fastmap'), 'wb') - marshal.dump(self._mapFileHash, f) - marshal.dump(self._codeSpaceRanges, f) - marshal.dump(self._notDefRanges, f) - marshal.dump(self._cmap, f) - f.close() - - def fastLoad(self, directory): - started = time.clock() - f = open(os.path.join(directory, self.name + '.fastmap'), 'rb') - self._mapFileHash = marshal.load(f) - self._codeSpaceRanges = marshal.load(f) - self._notDefRanges = marshal.load(f) - self._cmap = marshal.load(f) - f.close() - finished = time.clock() - #print 'loaded %s in %0.4f seconds' % (self.name, finished - started) - - -class CIDTypeFace(pdfmetrics.TypeFace): - """Multi-byte type face. - - Conceptually similar to a single byte typeface, - but the glyphs are identified by a numeric Character - ID (CID) and not a glyph name. """ - def __init__(self, name): - """Initialised from one of the canned dictionaries in allowedEncodings - - Or rather, it will be shortly...""" - pdfmetrics.TypeFace.__init__(self, name) - self._extractDictInfo(name) - def _extractDictInfo(self, name): - try: - fontDict = CIDFontInfo[name] - except KeyError: - raise KeyError, ("Unable to find information on CID typeface '%s'" % name + - "Only the following font names work:" + repr(allowedTypeFaces) - ) - descFont = fontDict['DescendantFonts'][0] - self.ascent = descFont['FontDescriptor']['Ascent'] - self.descent = descFont['FontDescriptor']['Descent'] - self._defaultWidth = descFont['DW'] - self._explicitWidths = self._expandWidths(descFont['W']) - - # should really support self.glyphWidths, self.glyphNames - # but not done yet. - - - def _expandWidths(self, compactWidthArray): - """Expands Adobe nested list structure to get a dictionary of widths. - - Here is an example of such a structure. - ( - # starting at character ID 1, next n characters have the widths given. - 1, (277,305,500,668,668,906,727,305,445,445,508,668,305,379,305,539), - # all Characters from ID 17 to 26 are 668 em units wide - 17, 26, 668, - 27, (305, 305, 668, 668, 668, 566, 871, 727, 637, 652, 699, 574, 555, - 676, 687, 242, 492, 664, 582, 789, 707, 734, 582, 734, 605, 605, - 641, 668, 727, 945, 609, 609, 574, 445, 668, 445, 668, 668, 590, - 555, 609, 547, 602, 574, 391, 609, 582, 234, 277, 539, 234, 895, - 582, 605, 602, 602, 387, 508, 441, 582, 562, 781, 531, 570, 555, - 449, 246, 449, 668), - # these must be half width katakana and the like. - 231, 632, 500 - ) - """ - data = compactWidthArray[:] - widths = {} - while data: - start, data = data[0], data[1:] - if type(data[0]) in (ListType, TupleType): - items, data = data[0], data[1:] - for offset in range(len(items)): - widths[start + offset] = items[offset] - else: - end, width, data = data[0], data[1], data[2:] - for idx in range(start, end+1): - widths[idx] = width - return widths - - def getCharWidth(self, characterId): - return self._explicitWidths.get(characterId, self._defaultWidth) - -class CIDFont(pdfmetrics.Font): - "Represents a built-in multi-byte font" - def __init__(self, face, encoding): - - self._multiByte = 1 - assert face in allowedTypeFaces, "TypeFace '%s' not supported! Use any of these instead: %s" % (face, allowedTypeFaces) - self.faceName = face - #should cache in registry... - self.face = CIDTypeFace(face) - - assert encoding in allowedEncodings, "Encoding '%s' not supported! Use any of these instead: %s" % (encoding, allowedEncodings) - self.encodingName = encoding - self.encoding = CIDEncoding(encoding) - - #legacy hack doing quick cut and paste. - self.fontName = self.faceName + '-' + self.encodingName - self.name = self.fontName - - # need to know if it is vertical or horizontal - self.isVertical = (self.encodingName[-1] == 'V') - - - def stringWidth(self, text, size): - cidlist = self.encoding.translate(text) - if self.isVertical: - #this part is "not checked!" but seems to work. - #assume each is 1000 ems high - return len(cidlist) * size - else: - w = 0 - for cid in cidlist: - w = w + self.face.getCharWidth(cid) - return 0.001 * w * size - - - def addObjects(self, doc): - """The explicit code in addMinchoObjects and addGothicObjects - will be replaced by something that pulls the data from - _cidfontdata.py in the next few days.""" - internalName = 'F' + repr(len(doc.fontMapping)+1) - - bigDict = CIDFontInfo[self.face.name] - bigDict['Name'] = '/' + internalName - bigDict['Encoding'] = '/' + self.encodingName - - #convert to PDF dictionary/array objects - cidObj = structToPDF(bigDict) - - # link into document, and add to font map - r = doc.Reference(cidObj, internalName) - fontDict = doc.idToObject['BasicFonts'].dict - fontDict[internalName] = r - doc.fontMapping[self.name] = '/' + internalName - - - -def precalculate(cmapdir): - # crunches through all, making 'fastmap' files - import os - files = os.listdir(cmapdir) - for file in files: - if os.path.isfile(cmapdir + os.sep + self.name + '.fastmap'): - continue - try: - enc = CIDEncoding(file) - except: - print 'cannot parse %s, skipping' % enc - continue - enc.fastSave(cmapdir) - print 'saved %s.fastmap' % file - -def test(): - # only works if you have cirrect encodings on your box! - c = Canvas('test_japanese.pdf') - c.setFont('Helvetica', 30) - c.drawString(100,700, 'Japanese Font Support') - - pdfmetrics.registerFont(CIDFont('HeiseiMin-W3','90ms-RKSJ-H')) - pdfmetrics.registerFont(CIDFont('HeiseiKakuGo-W5','90ms-RKSJ-H')) - - - # 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) - c.save() - print 'saved test_japanese.pdf' - - -## print 'CMAP_DIR = ', CMAP_DIR -## tf1 = CIDTypeFace('HeiseiMin-W3') -## print 'ascent = ',tf1.ascent -## print 'descent = ',tf1.descent -## for cid in [1,2,3,4,5,18,19,28,231,1742]: -## print 'width of cid %d = %d' % (cid, tf1.getCharWidth(cid)) - - encName = '90ms-RKSJ-H' - enc = CIDEncoding(encName) - print message1, '->', enc.translate(message1) - - f = CIDFont('HeiseiMin-W3','90ms-RKSJ-H') - print 'width = %0.2f' % f.stringWidth(message1, 10) - - - #testing all encodings -## import time -## started = time.time() -## import glob -## for encName in _cidfontdata.allowedEncodings: -## #encName = '90ms-RKSJ-H' -## enc = CIDEncoding(encName) -## print 'encoding %s:' % encName -## print ' codeSpaceRanges = %s' % enc._codeSpaceRanges -## print ' notDefRanges = %s' % enc._notDefRanges -## print ' mapping size = %d' % len(enc._cmap) -## finished = time.time() -## print 'constructed all encodings in %0.2f seconds' % (finished - started) - -if __name__=='__main__': - test() - - - - diff --git a/bin/reportlab/pdfbase/pdfdoc.py b/bin/reportlab/pdfbase/pdfdoc.py deleted file mode 100644 index 387e272a8b0..00000000000 --- a/bin/reportlab/pdfbase/pdfdoc.py +++ /dev/null @@ -1,1855 +0,0 @@ -#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/pdfbase/pdfdoc.py -__version__=''' $Id$ ''' -__doc__=""" -The module pdfdoc.py handles the 'outer structure' of PDF documents, ensuring that -all objects are properly cross-referenced and indexed to the nearest byte. The -'inner structure' - the page descriptions - are presumed to be generated before -each page is saved. -pdfgen.py calls this and provides a 'canvas' object to handle page marking operators. -piddlePDF calls pdfgen and offers a high-level interface. - -The classes within this generally mirror structures in the PDF file -and are not part of any public interface. Instead, canvas and font -classes are made available elsewhere for users to manipulate. -""" - -import string, types -from reportlab.pdfbase import pdfutils -from reportlab.pdfbase.pdfutils import LINEEND # this constant needed in both -from reportlab import rl_config -from reportlab.lib.utils import import_zlib, open_for_read, fp_str - -from sys import platform -try: - from sys import version_info -except: # pre-2.0 - # may be inaccurate but will at least - #work in anything which seeks to format - # version_info into a string - version_info = (1,5,2,'unknown',0) - -if platform[:4] == 'java' and version_info[:2] == (2, 1): - # workaround for list()-bug in Jython 2.1 (should be fixed in 2.2) - def list(sequence): - def f(x): - return x - return map(f, sequence) - -class PDFError(Exception): - pass - - -# set this flag to get more vertical whitespace (and larger files) -LongFormat = 1 -##if LongFormat: (doesn't work) -## pass -##else: -## LINEEND = "\n" # no wasteful carriage returns! - -# __InternalName__ is a special attribute that can only be set by the Document arbitrator -__InternalName__ = "__InternalName__" - -# __RefOnly__ marks reference only elements that must be formatted on top level -__RefOnly__ = "__RefOnly__" - -# __Comment__ provides a (one line) comment to inline with an object ref, if present -# if it is more than one line then percentize it... -__Comment__ = "__Comment__" - -# If DoComments is set then add helpful (space wasting) comment lines to PDF files -DoComments = 1 -if not LongFormat: - DoComments = 0 - -# name for standard font dictionary -BasicFonts = "BasicFonts" - -# name for the pages object -Pages = "Pages" - -### generic utilities - - -# for % substitutions -LINEENDDICT = {"LINEEND": LINEEND, "PERCENT": "%"} - -def format(element, document, toplevel=0): - """Indirection step for formatting. - Ensures that document parameters alter behaviour - of formatting for all elements. - """ - from types import InstanceType, FloatType, IntType - if type(element) is InstanceType: - if not toplevel and hasattr(element, __RefOnly__): - # the object cannot be a component at non top level. - # make a reference to it and return it's format - R = document.Reference(element) - return R.format(document) - else: - try: - fmt = element.format - except: - raise AttributeError, "%s has no format operation" % element - f = fmt(document) - if not rl_config.invariant and DoComments and hasattr(element, __Comment__): - f = "%s%s%s%s" % ("% ", element.__Comment__, LINEEND, f) - return f - elif type(element) in (FloatType, IntType): - #use a controlled number formatting routine - #instead of str, so Jython/Python etc do not differ - return fp_str(element) - else: - return str(element) - -def indent(s, IND=LINEEND+" "): - return string.replace(s, LINEEND, IND) - -def xObjectName(externalname): - return "FormXob.%s" % externalname - -# backwards compatibility -formName = xObjectName - - -# no encryption -class NoEncryption: - def encode(self, t): - "encode a string, stream, text" - return t - def prepare(self, document): - # get ready to do encryption - pass - def register(self, objnum, version): - # enter a new direct object - pass - def info(self): - # the representation of self in file if any (should be None or PDFDict) - return None - -class DummyDoc: - "used to bypass encryption when required" - encrypt = NoEncryption() - -### the global document structure manager - -class PDFDocument: - _ID = None - objectcounter = 0 - inObject = None - # set this to define filters - defaultStreamFilters = None - encrypt = NoEncryption() # default no encryption - pageCounter = 1 - def __init__(self, - encoding=rl_config.defaultEncoding, - dummyoutline=0, - compression=rl_config.pageCompression, - invariant=rl_config.invariant): - #self.defaultStreamFilters = [PDFBase85Encode, PDFZCompress] # for testing! - #self.defaultStreamFilters = [PDFZCompress] # for testing! - assert encoding in ['MacRomanEncoding', - 'WinAnsiEncoding', - 'MacRoman', - 'WinAnsi'], 'Unsupported encoding %s' % encoding - if encoding[-8:] <> 'Encoding': - encoding = encoding + 'Encoding' - - # allow None value to be passed in to mean 'give system defaults' - if invariant is None: - self.invariant = rl_config.invariant - else: - self.invariant = invariant - self.setCompression(compression) - self.encoding = encoding - # signature for creating PDF ID - import md5 - sig = self.signature = md5.new() - sig.update("a reportlab document") - if not self.invariant: - cat = _getTimeStamp() - else: - cat = 946684800.0 - sig.update(repr(cat)) # initialize with timestamp digest - # mapping of internal identifier ("Page001") to PDF objectnumber and generation number (34, 0) - self.idToObjectNumberAndVersion = {} - # mapping of internal identifier ("Page001") to PDF object (PDFPage instance) - self.idToObject = {} - # internal id to file location - self.idToOffset = {} - # number to id - self.numberToId = {} - cat = self.Catalog = self._catalog = PDFCatalog() - pages = self.Pages = PDFPages() - cat.Pages = pages - if dummyoutline: - outlines = PDFOutlines0() - else: - outlines = PDFOutlines() - self.Outlines = self.outline = outlines - cat.Outlines = outlines - self.info = PDFInfo() - self.info.invariant = self.invariant - #self.Reference(self.Catalog) - #self.Reference(self.Info) - self.fontMapping = {} - #make an empty font dictionary - DD = PDFDictionary({}) - DD.__Comment__ = "The standard fonts dictionary" - DDR = self.Reference(DD, BasicFonts) - self.delayedFonts = [] - - def setCompression(self, onoff): - # XXX: maybe this should also set self.defaultStreamFilters? - self.compression = onoff - - def updateSignature(self, thing): - "add information to the signature" - if self._ID: return # but not if its used already! - self.signature.update(str(thing)) - - def ID(self): - "A unique fingerprint for the file (unless in invariant mode)" - if self._ID: - return self._ID - digest = self.signature.digest() - doc = DummyDoc() - ID = PDFString(digest) - IDs = ID.format(doc) - self._ID = "%s %% ReportLab generated PDF document -- digest (http://www.reportlab.com) %s [%s %s] %s" % ( - LINEEND, LINEEND, IDs, IDs, LINEEND) - return self._ID - - def SaveToFile(self, filename, canvas): - if callable(getattr(filename, "write",None)): - myfile = 0 - f = filename - filename = str(getattr(filename,'name','')) - else : - myfile = 1 - filename = str(filename) - f = open(filename, "wb") - f.write(self.GetPDFData(canvas)) - if myfile: - f.close() - import os - if os.name=='mac': - from reportlab.lib.utils import markfilename - markfilename(filename) # do platform specific file junk - if getattr(canvas,'_verbosity',None): print 'saved', filename - - def GetPDFData(self, canvas): - # realize delayed fonts - for fnt in self.delayedFonts: - fnt.addObjects(self) - # add info stuff to signature - self.info.invariant = self.invariant - self.info.digest(self.signature) - ### later: maybe add more info to sig? - # prepare outline - self.Reference(self.Catalog) - self.Reference(self.info) - outline = self.outline - outline.prepare(self, canvas) - return self.format() - - def inPage(self): - """specify the current object as a page (enables reference binding and other page features)""" - if self.inObject is not None: - if self.inObject=="page": return - raise ValueError, "can't go in page already in object %s" % self.inObject - self.inObject = "page" - - def inForm(self): - """specify that we are in a form xobject (disable page features, etc)""" - # don't need this check anymore since going in a form pushes old context at canvas level. - #if self.inObject not in ["form", None]: - # raise ValueError, "can't go in form already in object %s" % self.inObject - self.inObject = "form" - # don't need to do anything else, I think... - - def getInternalFontName(self, psfontname): - fm = self.fontMapping - if fm.has_key(psfontname): - return fm[psfontname] - else: - try: - # does pdfmetrics know about it? if so, add - from reportlab.pdfbase import pdfmetrics - fontObj = pdfmetrics.getFont(psfontname) - if getattr(fontObj, '_dynamicFont', 0): - raise PDFError, "getInternalFontName(%s) called for a dynamic font" % repr(psfontname) - fontObj.addObjects(self) - #self.addFont(fontObj) - return fm[psfontname] - except KeyError: - raise PDFError, "Font %s not known!" % repr(psfontname) - - def thisPageName(self): - return "Page"+repr(self.pageCounter) - - def thisPageRef(self): - return PDFObjectReference(self.thisPageName()) - - def addPage(self, page): - name = self.thisPageName() - self.Reference(page, name) - self.Pages.addPage(page) - self.pageCounter = self.pageCounter+1 - self.inObject = None - - - def addForm(self, name, form): - """add a Form XObject.""" - # XXX should check that name is a legal PDF name - if self.inObject != "form": - self.inForm() - self.Reference(form, xObjectName(name)) - self.inObject = None - - def annotationName(self, externalname): - return "Annot.%s"%externalname - - def addAnnotation(self, name, annotation): - self.Reference(annotation, self.annotationName(name)) - - def refAnnotation(self, name): - internalname = self.annotationName(name) - return PDFObjectReference(internalname) - - def setTitle(self, title): - "embeds in PDF file" - self.info.title = title - - def setAuthor(self, author): - "embedded in PDF file" - self.info.author = author - - def setSubject(self, subject): - "embeds in PDF file" - self.info.subject = subject - - def getAvailableFonts(self): - fontnames = self.fontMapping.keys() - # the standard 14 are also always available! (even if not initialized yet) - import _fontdata - for name in _fontdata.standardFonts: - if name not in fontnames: - fontnames.append(name) - fontnames.sort() - return fontnames - - - - def format(self): - # register the Catalog/INfo and then format the objects one by one until exhausted - # (possible infinite loop if there is a bug that continually makes new objects/refs...) - # Prepare encryption - self.encrypt.prepare(self) - cat = self.Catalog - info = self.info - self.Reference(self.Catalog) - self.Reference(self.info) - # register the encryption dictionary if present - encryptref = None - encryptinfo = self.encrypt.info() - if encryptinfo: - encryptref = self.Reference(encryptinfo) - # make std fonts (this could be made optional - counter = 0 # start at first object (object 1 after preincrement) - ids = [] # the collection of object ids in object number order - numbertoid = self.numberToId - idToNV = self.idToObjectNumberAndVersion - idToOb = self.idToObject - idToOf = self.idToOffset - ### note that new entries may be "appended" DURING FORMATTING - done = None - File = PDFFile() # output collector - while done is None: - counter = counter+1 # do next object... - if numbertoid.has_key(counter): - id = numbertoid[counter] - #printidToOb - obj = idToOb[id] - IO = PDFIndirectObject(id, obj) - # register object number and version - #encrypt.register(id, - IOf = IO.format(self) - # add a comment to the PDF output - if not rl_config.invariant and DoComments: - try: - classname = obj.__class__.__name__ - except: - classname = repr(obj) - File.add("%% %s: class %s %s" % (repr(id), classname[:50], LINEEND)) - offset = File.add(IOf) - idToOf[id] = offset - ids.append(id) - else: - done = 1 - # sanity checks (must happen AFTER formatting) - lno = len(numbertoid) - if counter-1!=lno: - raise ValueError, "counter %s doesn't match number to id dictionary %s" %(counter, lno) - # now add the xref - xref = PDFCrossReferenceTable() - xref.addsection(0, ids) - xreff = xref.format(self) - xrefoffset = File.add(xreff) - # now add the trailer - trailer = PDFTrailer( - startxref = xrefoffset, - Size = lno+1, - Root = self.Reference(cat), - Info = self.Reference(info), - Encrypt = encryptref, - ID = self.ID(), - ) - trailerf = trailer.format(self) - File.add(trailerf) - # return string format for pdf file - return File.format(self) - - def hasForm(self, name): - """test for existence of named form""" - internalname = xObjectName(name) - return self.idToObject.has_key(internalname) - - def getFormBBox(self, name): - "get the declared bounding box of the form as a list" - internalname = xObjectName(name) - if self.idToObject.has_key(internalname): - theform = self.idToObject[internalname] - if isinstance(theform, PDFFormXObject): - # internally defined form - return theform.BBoxList() - elif isinstance(theform, PDFStream): - # externally defined form - return list(theform.dictionary.dict["BBox"].sequence) - else: - raise ValueError, "I don't understand the form instance %s" % repr(name) - - def getXObjectName(self, name): - """Lets canvas find out what form is called internally. - Never mind whether it is defined yet or not.""" - return xObjectName(name) - - def xobjDict(self, formnames): - """construct an xobject dict (for inclusion in a resource dict, usually) - from a list of form names (images not yet supported)""" - D = {} - for name in formnames: - internalname = xObjectName(name) - reference = PDFObjectReference(internalname) - D[internalname] = reference - #print "xobjDict D", D - return PDFDictionary(D) - - def Reference(self, object, name=None): - ### note references may "grow" during the final formatting pass: don't use d.keys()! - # don't make references to other references, or non instances, unless they are named! - from types import InstanceType - #print"object type is ", type(object) - tob = type(object) - idToObject = self.idToObject - if name is None and ( - (tob is not InstanceType) or (tob is InstanceType and object.__class__ is PDFObjectReference)): - return object - if hasattr(object, __InternalName__): - # already registered - intname = object.__InternalName__ - if name is not None and name!=intname: - raise ValueError, "attempt to reregister object %s with new name %s" % ( - repr(intname), repr(name)) - if not idToObject.has_key(intname): - raise ValueError, "object named but not registered" - return PDFObjectReference(intname) - # otherwise register the new object - objectcounter = self.objectcounter = self.objectcounter+1 - if name is None: - name = "R"+repr(objectcounter) - if idToObject.has_key(name): - other = idToObject[name] - if other!=object: - raise ValueError, "redefining named object: "+repr(name) - return PDFObjectReference(name) - if tob is InstanceType: - object.__InternalName__ = name - #print "name", name, "counter", objectcounter - self.idToObjectNumberAndVersion[name] = (objectcounter, 0) - self.numberToId[objectcounter] = name - idToObject[name] = object - return PDFObjectReference(name) - -### chapter 4 Objects - -PDFtrue = "true" -PDFfalse = "false" -PDFnull = "null" - -class PDFText: - def __init__(self, t): - self.t = t - def format(self, document): - t = self.t - t = document.encrypt.encode(t) - L = list(t) - for i in range(len(L)): - ch = L[i] - n = ord(ch) - h = hex(n) - h2 = h[2:] # nuke the 0x - if len(h2)<2: - h2 = "0"+h2 - L[i] = h2 - result = string.join(L, "") - return "<%s>" % result - def __str__(self): - dummydoc = DummyDoc() - return self.format(dummydoc) - -def PDFnumber(n): - return n - -class PDFString: - def __init__(self, str): - # might need to change this to class for encryption - self.s = str - def format(self, document): - s = document.encrypt.encode(self.s) - try: - return "(%s)" % pdfutils._escape(s) - except: - raise ValueError, "cannot escape %s %s" %(s, repr(s)) - def __str__(self): - return "(%s)" % pdfutils._escape(self.s) - -def PDFName(data): - # might need to change this to class for encryption - # NOTE: RESULT MUST ALWAYS SUPPORT MEANINGFUL COMPARISONS (EQUALITY) AND HASH - # first convert the name - ldata = list(data) - index = 0 - for thischar in data: - if 0x21<=ord(thischar)<=0x7e and thischar not in "%()<>{}[]#": - pass # no problemo - else: - hexord = hex(ord(thischar))[2:] # forget the 0x thing... - ldata[index] = "#"+hexord - index = index+1 - data = string.join(ldata, "") - return "/%s" % data - -class PDFDictionary: - - multiline = LongFormat - def __init__(self, dict=None): - """dict should be namestring to value eg "a": 122 NOT pdfname to value NOT "/a":122""" - if dict is None: - self.dict = {} - else: - self.dict = dict.copy() - def __setitem__(self, name, value): - self.dict[name] = value - def Reference(name, document): - ob = self.dict[name] - self.dict[name] = document.Reference(ob) - def format(self, document): - dict = self.dict - keys = dict.keys() - keys.sort() - L = [] - a = L.append - for k in keys: - v = dict[k] - fv = format(v, document) - fk = format(PDFName(k), document) - #a(fk) - #a(" "+fv) - a(fk + " " + fv) - #L = map(str, L) - if self.multiline: - Lj = string.join(L, LINEEND) - Lj = indent(Lj) - else: - Lj = L - # break up every 6 elements anyway - for i in range(6, len(Lj), 6): - Lj.insert(i,LINEEND) - Lj = string.join(L, " ") - return "<< %s >>" % Lj - -# stream filters are objects to support round trip and -# possibly in the future also support parameters -class PDFStreamFilterZCompress: - pdfname = "FlateDecode" - def encode(self, text): - from reportlab.lib.utils import import_zlib - zlib = import_zlib() - if not zlib: raise ImportError, "cannot z-compress zlib unavailable" - return zlib.compress(text) - def decode(self, encoded): - from reportlab.lib.utils import import_zlib - zlib = import_zlib() - if not zlib: raise ImportError, "cannot z-decompress zlib unavailable" - return zlib.decompress(encoded) - -# need only one of these, unless we implement parameters later -PDFZCompress = PDFStreamFilterZCompress() - -class PDFStreamFilterBase85Encode: - pdfname = "ASCII85Decode" - def encode(self, text): - from pdfutils import _AsciiBase85Encode, _wrap - return _wrap(_AsciiBase85Encode(text)) - def decode(self, text): - from pdfutils import _AsciiBase85Decode - return _AsciiBase85Decode(text) - -# need only one of these too -PDFBase85Encode = PDFStreamFilterBase85Encode() - -STREAMFMT = ("%(dictionary)s%(LINEEND)s" # dictionary - "stream" # stream keyword - "%(LINEEND)s" # a line end (could be just a \n) - "%(content)s" # the content, with no lineend - "endstream%(LINEEND)s" # the endstream keyword - ) -class PDFStream: - '''set dictionary elements explicitly stream.dictionary[name]=value''' - ### compression stuff not implemented yet - __RefOnly__ = 1 # must be at top level - def __init__(self, dictionary=None, content=None): - if dictionary is None: - dictionary = PDFDictionary() - self.dictionary = dictionary - self.content = content - self.filters = None - def format(self, document): - dictionary = self.dictionary - # copy it for modification - dictionary = PDFDictionary(dictionary.dict.copy()) - content = self.content - filters = self.filters - if self.content is None: - raise ValueError, "stream content not set" - if filters is None: - filters = document.defaultStreamFilters - # only apply filters if they haven't been applied elsewhere - if filters is not None and not dictionary.dict.has_key("Filter"): - # apply filters in reverse order listed - rf = list(filters) - rf.reverse() - fnames = [] - for f in rf: - #print "*****************content:"; print repr(content[:200]) - #print "*****************filter", f.pdfname - content = f.encode(content) - fnames.insert(0, PDFName(f.pdfname)) - #print "*****************finally:"; print content[:200] - #print "****** FILTERS", fnames - #stop - dictionary["Filter"] = PDFArray(fnames) - # "stream encoding is done after all filters have been applied" - content = document.encrypt.encode(content) - fc = format(content, document) - #print "type(content)", type(content), len(content), type(self.dictionary) - lc = len(content) - #if fc!=content: burp - # set dictionary length parameter - dictionary["Length"] = lc - fd = format(dictionary, document) - sdict = LINEENDDICT.copy() - sdict["dictionary"] = fd - sdict["content"] = fc - return STREAMFMT % sdict - -def teststream(content=None): - #content = "" # test - if content is None: - content = teststreamcontent - content = string.strip(content) - content = string.replace(content, "\n", LINEEND) + LINEEND - S = PDFStream() - S.content = content - S.filters = [PDFBase85Encode, PDFZCompress] - # nothing else needed... - S.__Comment__ = "test stream" - return S - -teststreamcontent = """ -1 0 0 1 0 0 cm BT /F9 12 Tf 14.4 TL ET -1.00 0.00 1.00 rg -n 72.00 72.00 432.00 648.00 re B* -""" -class PDFArray: - multiline = LongFormat - def __init__(self, sequence): - self.sequence = list(sequence) - def References(self, document): - """make all objects in sequence references""" - self.sequence = map(document.Reference, self.sequence) - def format(self, document): - #ssequence = map(str, self.sequence) - sequence = self.sequence - fsequence = [] - for elt in sequence: - felt = format(elt, document) - fsequence.append(felt) - if self.multiline: - Lj = string.join(fsequence, LINEEND) - Lj = indent(Lj) - else: - # break up every 10 elements anyway - Lj = fsequence - breakline = LINEEND+" " - for i in range(10, len(Lj), 10): - Lj.insert(i,breakline) - Lj = string.join(Lj) - return "[ %s ]" % Lj - -INDIRECTOBFMT = ("%(n)s %(v)s obj%(LINEEND)s" - "%(content)s" "%(LINEEND)s" - "endobj" "%(LINEEND)s") - -class PDFIndirectObject: - __RefOnly__ = 1 - def __init__(self, name, content): - self.name = name - self.content = content - def format(self, document): - name = self.name - (n, v) = document.idToObjectNumberAndVersion[name] - # set encryption parameters - document.encrypt.register(n, v) - content = self.content - fcontent = format(content, document, toplevel=1) # yes this is at top level - sdict = LINEENDDICT.copy() - sdict["n"] = n - sdict["v"] = v - sdict["content"] = fcontent - return INDIRECTOBFMT % sdict - -class PDFObjectReference: - def __init__(self, name): - self.name = name - def format(self, document): - name = self.name - try: - (n, v) = document.idToObjectNumberAndVersion[name] - except: - raise KeyError, "forward reference to %s not resolved upon final formatting" % repr(name) - return "%s %s R" % (n,v) - -### chapter 5 -# Following Ken Lunde's advice and the PDF spec, this includes -# some high-order bytes. I chose the characters for Tokyo -# in Shift-JIS encoding, as these cannot be mistaken for -# any other encoding, and we'll be able to tell if something -# has run our PDF files through a dodgy Unicode conversion. -PDFHeader = ( -"%PDF-1.3"+LINEEND+ -"%\223\214\213\236 ReportLab Generated PDF document http://www.reportlab.com"+LINEEND) - -class PDFFile: - ### just accumulates strings: keeps track of current offset - def __init__(self): - self.strings = [] - self.offset = 0 - self.add(PDFHeader) - def add(self, s): - """should be constructed as late as possible, return position where placed""" - result = self.offset - self.offset = result+len(s) - self.strings.append(s) - return result - def format(self, document): - strings = map(str, self.strings) # final conversion, in case of lazy objects - return string.join(strings, "") - -XREFFMT = '%0.10d %0.5d n' - -class PDFCrossReferenceSubsection: - def __init__(self, firstentrynumber, idsequence): - self.firstentrynumber = firstentrynumber - self.idsequence = idsequence - def format(self, document): - """id sequence should represent contiguous object nums else error. free numbers not supported (yet)""" - firstentrynumber = self.firstentrynumber - idsequence = self.idsequence - entries = list(idsequence) - nentries = len(idsequence) - # special case: object number 0 is always free - taken = {} - if firstentrynumber==0: - taken[0] = "standard free entry" - nentries = nentries+1 - entries.insert(0, "0000000000 65535 f") - idToNV = document.idToObjectNumberAndVersion - idToOffset = document.idToOffset - lastentrynumber = firstentrynumber+nentries-1 - for id in idsequence: - (num, version) = idToNV[id] - if taken.has_key(num): - raise ValueError, "object number collision %s %s %s" % (num, repr(id), repr(taken[id])) - if num>lastentrynumber or num>""" - -class PDFOutlines0: - __Comment__ = "TEST OUTLINE!" - text = string.replace(DUMMYOUTLINE, "\n", LINEEND) - __RefOnly__ = 1 - def format(self, document): - return self.text - - -class OutlineEntryObject: - "an entry in an outline" - Title = Dest = Parent = Prev = Next = First = Last = Count = None - def format(self, document): - D = {} - D["Title"] = PDFString(self.Title) - D["Parent"] = self.Parent - D["Dest"] = self.Dest - for n in ("Prev", "Next", "First", "Last", "Count"): - v = getattr(self, n) - if v is not None: - D[n] = v - PD = PDFDictionary(D) - return PD.format(document) - - -class PDFOutlines: - """takes a recursive list of outline destinations - like - out = PDFOutline1() - out.setNames(canvas, # requires canvas for name resolution - "chapter1dest", - ("chapter2dest", - ["chapter2section1dest", - "chapter2section2dest", - "chapter2conclusiondest"] - ), # end of chapter2 description - "chapter3dest", - ("chapter4dest", ["c4s1", "c4s2"]) - ) - Higher layers may build this structure incrementally. KISS at base level. - """ - # first attempt, many possible features missing. - #no init for now - mydestinations = ready = None - counter = 0 - currentlevel = -1 # ie, no levels yet - - def __init__(self): - self.destinationnamestotitles = {} - self.destinationstotitles = {} - self.levelstack = [] - self.buildtree = [] - self.closedict = {} # dictionary of "closed" destinations in the outline - - def addOutlineEntry(self, destinationname, level=0, title=None, closed=None): - """destinationname of None means "close the tree" """ - from types import IntType, TupleType - if destinationname is None and level!=0: - raise ValueError, "close tree must have level of 0" - if type(level) is not IntType: raise ValueError, "level must be integer, got %s" % type(level) - if level<0: raise ValueError, "negative levels not allowed" - if title is None: title = destinationname - currentlevel = self.currentlevel - stack = self.levelstack - tree = self.buildtree - # adjust currentlevel and stack to match level - if level>currentlevel: - if level>currentlevel+1: - raise ValueError, "can't jump from outline level %s to level %s, need intermediates" %(currentlevel, level) - level = currentlevel = currentlevel+1 - stack.append([]) - while levelref - if Ot is ListType or Ot is TupleType: - L = [] - for o in object: - L.append(self.translateNames(canvas, o)) - if Ot is TupleType: - return tuple(L) - return L - raise "in outline, destination name must be string: got a %s" % Ot - - def prepare(self, document, canvas): - """prepare all data structures required for save operation (create related objects)""" - if self.mydestinations is None: - if self.levelstack: - self.addOutlineEntry(None) # close the tree - destnames = self.levelstack[0] - #from pprint import pprint; pprint(destnames); stop - self.mydestinations = self.translateNames(canvas, destnames) - else: - self.first = self.last = None - self.count = 0 - self.ready = 1 - return - #self.first = document.objectReference("Outline.First") - #self.last = document.objectReference("Outline.Last") - # XXXX this needs to be generalized for closed entries! - self.count = count(self.mydestinations, self.closedict) - (self.first, self.last) = self.maketree(document, self.mydestinations, toplevel=1) - self.ready = 1 - - def maketree(self, document, destinationtree, Parent=None, toplevel=0): - from types import ListType, TupleType, DictType - tdestinationtree = type(destinationtree) - if toplevel: - levelname = "Outline" - Parent = document.Reference(document.Outlines) - else: - self.count = self.count+1 - levelname = "Outline.%s" % self.count - if Parent is None: - raise ValueError, "non-top level outline elt parent must be specified" - if tdestinationtree is not ListType and tdestinationtree is not TupleType: - raise ValueError, "destinationtree must be list or tuple, got %s" - nelts = len(destinationtree) - lastindex = nelts-1 - lastelt = firstref = lastref = None - destinationnamestotitles = self.destinationnamestotitles - closedict = self.closedict - for index in range(nelts): - eltobj = OutlineEntryObject() - eltobj.Parent = Parent - eltname = "%s.%s" % (levelname, index) - eltref = document.Reference(eltobj, eltname) - #document.add(eltname, eltobj) - if lastelt is not None: - lastelt.Next = eltref - eltobj.Prev = lastref - if firstref is None: - firstref = eltref - lastref = eltref - lastelt = eltobj # advance eltobj - lastref = eltref - elt = destinationtree[index] - te = type(elt) - if te is DictType: - # simple leaf {name: dest} - leafdict = elt - elif te is TupleType: - # leaf with subsections: ({name: ref}, subsections) XXXX should clean up (see count(...)) - try: - (leafdict, subsections) = elt - except: - raise ValueError, "destination tree elt tuple should have two elts, got %s" % len(elt) - eltobj.Count = count(subsections, closedict) - (eltobj.First, eltobj.Last) = self.maketree(document, subsections, eltref) - else: - raise ValueError, "destination tree elt should be dict or tuple, got %s" % te - try: - [(Title, Dest)] = leafdict.items() - except: - raise ValueError, "bad outline leaf dictionary, should have one entry "+str(elt) - eltobj.Title = destinationnamestotitles[Title] - eltobj.Dest = Dest - if te is TupleType and closedict.has_key(Dest): - # closed subsection, count should be negative - eltobj.Count = -eltobj.Count - return (firstref, lastref) - -def count(tree, closedict=None): - """utility for outline: recursively count leaves in a tuple/list tree""" - from operator import add - from types import TupleType, ListType - tt = type(tree) - if tt is TupleType: - # leaf with subsections XXXX should clean up this structural usage - (leafdict, subsections) = tree - [(Title, Dest)] = leafdict.items() - if closedict and closedict.has_key(Dest): - return 1 # closed tree element - if tt is TupleType or tt is ListType: - #return reduce(add, map(count, tree)) - counts = [] - for e in tree: - counts.append(count(e, closedict)) - return reduce(add, counts) - return 1 - - - -#### dummy info -DUMMYINFO = """ -<> -""" -class PDFInfo0: - __Comment__ = "TEST INFO STRUCTURE" - text = string.replace(DUMMYINFO, "\n", LINEEND) - __RefOnly__ = 1 - def format(self, document): - return self.text - -class PDFInfo: - """PDF documents can have basic information embedded, viewable from - File | Document Info in Acrobat Reader. If this is wrong, you get - Postscript errors while printing, even though it does not print.""" - def __init__(self): - self.invariant = rl_config.invariant - self.title = "untitled" - self.author = "anonymous" - self.subject = "unspecified" - - def digest(self, md5object): - # add self information to signature - for x in (self.title, self.author, self.subject): - md5object.update(str(x)) - - def format(self, document): - D = {} - D["Title"] = PDFString(self.title) - D["Author"] = PDFString(self.author) - D["CreationDate"] = PDFDate(invariant=self.invariant) - D["Producer"] = PDFString("ReportLab http://www.reportlab.com") - D["Subject"] = PDFString(self.subject) - PD = PDFDictionary(D) - return PD.format(document) - -# skipping thumbnails, etc - - -class Annotation: - """superclass for all annotations.""" - defaults = [("Type", PDFName("Annot"),)] - required = ("Type", "Rect", "Contents", "Subtype") - permitted = required+( - "Border", "C", "T", "M", "F", "H", "BS", "AA", "AS", "Popup", "P") - def cvtdict(self, d): - """transform dict args from python form to pdf string rep as needed""" - Rect = d["Rect"] - if type(Rect) is not types.StringType: - d["Rect"] = PDFArray(Rect) - d["Contents"] = PDFString(d["Contents"]) - return d - def AnnotationDict(self, **kw): - d = {} - for (name,val) in self.defaults: - d[name] = val - d.update(kw) - for name in self.required: - if not d.has_key(name): - raise ValueError, "keyword argument %s missing" % name - d = self.cvtdict(d) - permitted = self.permitted - for name in d.keys(): - if name not in permitted: - raise ValueError, "bad annotation dictionary name %s" % name - return PDFDictionary(d) - def Dict(self): - raise ValueError, "DictString undefined for virtual superclass Annotation, must overload" - # but usually - #return self.AnnotationDict(self, Rect=(a,b,c,d)) or whatever - def format(self, document): - D = self.Dict() - return D.format(document) - -class TextAnnotation(Annotation): - permitted = Annotation.permitted + ( - "Open", "Name", "AP") - def __init__(self, Rect, Contents, **kw): - self.Rect = Rect - self.Contents = Contents - self.otherkw = kw - def Dict(self): - d = {} - d.update(self.otherkw) - d["Rect"] = self.Rect - d["Contents"] = self.Contents - d["Subtype"] = "/Text" - return apply(self.AnnotationDict, (), d) - -class LinkAnnotation(Annotation): - - permitted = Annotation.permitted + ( - "Dest", "A", "PA") - def __init__(self, Rect, Contents, Destination, Border="[0 0 1]", **kw): - self.Border = Border - self.Rect = Rect - self.Contents = Contents - self.Destination = Destination - self.otherkw = kw - - def dummyDictString(self): # old, testing - return """ - << /Type /Annot /Subtype /Link /Rect [71 717 190 734] /Border [16 16 1] - /Dest [23 0 R /Fit] >> - """ - - def Dict(self): - d = {} - d.update(self.otherkw) - d["Border"] = self.Border - d["Rect"] = self.Rect - d["Contents"] = self.Contents - d["Subtype"] = "/Link" - d["Dest"] = self.Destination - return apply(self.AnnotationDict, (), d) - - -# skipping names tree - -# skipping actions - -# skipping names trees - -# skipping to chapter 7 - -class PDFRectangle: - def __init__(self, llx, lly, urx, ury): - self.llx, self.lly, self.ulx, self.ury = llx, lly, urx, ury - def format(self, document): - A = PDFArray([self.llx, self.lly, self.ulx, self.ury]) - return format(A, document) - -_NOWT=None -def _getTimeStamp(): - global _NOWT - if not _NOWT: - import time - _NOWT = time.time() - return _NOWT - -class PDFDate: - # gmt offset not yet suppported - def __init__(self, yyyy=None, mm=None, dd=None, hh=None, m=None, s=None, invariant=rl_config.invariant): - if None in (yyyy, mm, dd, hh, m, s): - if invariant: - now = (2000,01,01,00,00,00,0) - else: - import time - now = tuple(time.localtime(_getTimeStamp())[:6]) - if yyyy is None: yyyy=now[0] - if mm is None: mm=now[1] - if dd is None: dd=now[2] - if hh is None: hh=now[3] - if m is None: m=now[4] - if s is None: s=now[5] - self.yyyy=yyyy; self.mm=mm; self.dd=dd; self.hh=hh; self.m=m; self.s=s - - def format(self, doc): - S = PDFString('%04d%02d%02d%02d%02d%02d' % (self.yyyy, self.mm, self.dd, self.hh, self.m, self.s)) - return format(S, doc) - - -class Destination: - """not a pdfobject! This is a placeholder that can delegates - to a pdf object only after it has been defined by the methods - below. EG a Destination can refer to Appendix A before it has been - defined, but only if Appendix A is explicitly noted as a destination - and resolved before the document is generated... - For example the following sequence causes resolution before doc generation. - d = Destination() - d.fit() # or other format defining method call - d.setPage(p) - (at present setPageRef is called on generation of the page). - """ - representation = format = page = None - def __init__(self,name): - self.name = name - self.fmt = self.page = None - def format(self, document): - f = self.fmt - if f is None: raise ValueError, "format not resolved %s" % self.name - p = self.page - if p is None: raise ValueError, "Page reference unbound %s" % self.name - f.page = p - return f.format(document) - def xyz(self, left, top, zoom): # see pdfspec mar 11 99 pp184+ - self.fmt = PDFDestinationXYZ(None, left, top, zoom) - def fit(self): - self.fmt = PDFDestinationFit(None) - def fitb(self): - self.fmt = PDFDestinationFitB(None) - def fith(self, top): - self.fmt = PDFDestinationFitH(None,top) - def fitv(self, left): - self.fmt = PDFDestinationFitV(None, left) - def fitbh(self, top): - self.fmt = PDFDestinationFitBH(None, top) - def fitbv(self, left): - self.fmt = PDFDestinationFitBV(None, left) - def fitr(self, left, bottom, right, top): - self.fmt = PDFDestinationFitR(None, left, bottom, right, top) - def setPage(self, page): - self.page = page - #self.fmt.page = page # may not yet be defined! - -class PDFDestinationXYZ: - typename = "XYZ" - def __init__(self, page, left, top, zoom): - self.page = page - self.top = top - self.zoom = zoom - self.left = left - def format(self, document): - pageref = document.Reference(self.page) - A = PDFArray( [ pageref, PDFName(self.typename), self.left, self.top, self.zoom ] ) - return format(A, document) - -class PDFDestinationFit: - typename = "Fit" - def __init__(self, page): - self.page = page - def format(self, document): - pageref = document.Reference(self.page) - A = PDFArray( [ pageref, PDFName(self.typename) ] ) - return format(A, document) - -class PDFDestinationFitB(PDFDestinationFit): - typename = "FitB" - -class PDFDestinationFitH: - typename = "FitH" - def __init__(self, page, top): - self.page = page; self.top=top - def format(self, document): - pageref = document.Reference(self.page) - A = PDFArray( [ pageref, PDFName(self.typename), self.top ] ) - return format(A, document) - -class PDFDestinationFitBH(PDFDestinationFitH): - typename = "FitBH" - -class PDFDestinationFitV: - typename = "FitV" - def __init__(self, page, left): - self.page = page; self.left=left - def format(self, document): - pageref = document.Reference(self.page) - A = PDFArray( [ pageref, PDFName(self.typename), self.left ] ) - return format(A, document) - -class PDFDestinationBV(PDFDestinationFitV): - typename = "FitBV" - -class PDFDestinationFitR: - typename = "FitR" - def __init__(self, page, left, bottom, right, top): - self.page = page; self.left=left; self.bottom=bottom; self.right=right; self.top=top - def format(self, document): - pageref = document.Reference(self.page) - A = PDFArray( [ pageref, PDFName(self.typename), self.left, self.bottom, self.right, self.top] ) - return format(A, document) - -# named destinations need nothing - -# skipping filespecs - -class PDFResourceDictionary: - """each element *could* be reset to a reference if desired""" - def __init__(self): - self.ColorSpace = {} - self.XObject = {} - self.ExtGState = {} - self.Font = {} - self.Pattern = {} - self.ProcSet = [] - self.Properties = {} - self.Shading = {} - # ?by default define the basicprocs - self.basicProcs() - stdprocs = map(PDFName, string.split("PDF Text ImageB ImageC ImageI")) - dict_attributes = ("ColorSpace", "XObject", "ExtGState", "Font", "Pattern", "Properties", "Shading") - def allProcs(self): - # define all standard procsets - self.ProcSet = self.stdprocs - def basicProcs(self): - self.ProcSet = self.stdprocs[:2] # just PDF and Text - def basicFonts(self): - self.Font = PDFObjectReference(BasicFonts) - def format(self, document): - D = {} - from types import ListType, DictType - for dname in self.dict_attributes: - v = getattr(self, dname) - if type(v) is DictType: - if v: - dv = PDFDictionary(v) - D[dname] = dv - else: - D[dname] = v - v = self.ProcSet - dname = "ProcSet" - if type(v) is ListType: - if v: - dv = PDFArray(v) - D[dname] = dv - else: - D[dname] = v - DD = PDFDictionary(D) - return format(DD, document) - - ############################################################################## - # - # Font objects - the PDFDocument.addFont() method knows which of these - # to construct when given a user-facing Font object - # - ############################################################################## - - -class PDFType1Font: - """no init: set attributes explicitly""" - __RefOnly__ = 1 - # note! /Name appears to be an undocumented attribute.... - name_attributes = string.split("Type Subtype BaseFont Name") - Type = "Font" - Subtype = "Type1" - # these attributes are assumed to already be of the right type - local_attributes = string.split("FirstChar LastChar Widths Encoding ToUnicode FontDescriptor") - def format(self, document): - D = {} - for name in self.name_attributes: - if hasattr(self, name): - value = getattr(self, name) - D[name] = PDFName(value) - for name in self.local_attributes: - if hasattr(self, name): - value = getattr(self, name) - D[name] = value - #print D - PD = PDFDictionary(D) - return PD.format(document) - -## These attribute listings will be useful in future, even if we -## put them elsewhere - -class PDFTrueTypeFont(PDFType1Font): - Subtype = "TrueType" - #local_attributes = string.split("FirstChar LastChar Widths Encoding ToUnicode FontDescriptor") #same - -##class PDFMMType1Font(PDFType1Font): -## Subtype = "MMType1" -## -##class PDFType3Font(PDFType1Font): -## Subtype = "Type3" -## local_attributes = string.split( -## "FirstChar LastChar Widths CharProcs FontBBox FontMatrix Resources Encoding") -## -##class PDFType0Font(PDFType1Font): -## Subtype = "Type0" -## local_attributes = string.split( -## "DescendantFonts Encoding") -## -##class PDFCIDFontType0(PDFType1Font): -## Subtype = "CIDFontType0" -## local_attributes = string.split( -## "CIDSystemInfo FontDescriptor DW W DW2 W2 Registry Ordering Supplement") -## -##class PDFCIDFontType0(PDFType1Font): -## Subtype = "CIDFontType2" -## local_attributes = string.split( -## "BaseFont CIDToGIDMap CIDSystemInfo FontDescriptor DW W DW2 W2") -## -##class PDFEncoding(PDFType1Font): -## Type = "Encoding" -## name_attributes = string.split("Type BaseEncoding") -## # these attributes are assumed to already be of the right type -## local_attributes = ["Differences"] -## - -# UGLY ALERT - this needs turning into something O-O, it was hacked -# across from the pdfmetrics.Encoding class to avoid circularity - -# skipping CMaps - -class PDFFormXObject: - # like page requires .info set by some higher level (doc) - # XXXX any resource used in a form must be propagated up to the page that (recursively) uses - # the form!! (not implemented yet). - XObjects = Annots = BBox = Matrix = Contents = stream = Resources = None - hasImages = 1 # probably should change - compression = 0 - def __init__(self, lowerx, lowery, upperx, uppery): - #not done - self.lowerx = lowerx; self.lowery=lowery; self.upperx=upperx; self.uppery=uppery - - def setStreamList(self, data): - if type(data) is types.ListType: - data = string.join(data, LINEEND) - self.stream = data - - def BBoxList(self): - "get the declared bounding box for the form as a list" - if self.BBox: - return list(self.BBox.sequence) - else: - return [self.lowerx, self.lowery, self.upperx, self.uppery] - - def format(self, document): - self.BBox = self.BBox or PDFArray([self.lowerx, self.lowery, self.upperx, self.uppery]) - self.Matrix = self.Matrix or PDFArray([1, 0, 0, 1, 0, 0]) - if not self.Annots: - self.Annots = None - else: - #these must be transferred to the page when the form is used - raise ValueError, "annotations not reimplemented yet" - if not self.Contents: - stream = self.stream - if not stream: - self.Contents = teststream() - else: - S = PDFStream() - S.content = stream - # need to add filter stuff (?) - S.__Comment__ = "xobject form stream" - self.Contents = S - if not self.Resources: - resources = PDFResourceDictionary() - # fonts! - resources.basicFonts() - if self.hasImages: - resources.allProcs() - else: - resources.basicProcs() - if self.XObjects: - #print "XObjects", self.XObjects.dict - resources.XObject = self.XObjects - if self.compression: - self.Contents.filters = [PDFBase85Encode, PDFZCompress] - sdict = self.Contents.dictionary - sdict["Type"] = PDFName("XObject") - sdict["Subtype"] = PDFName("Form") - sdict["FormType"] = 1 - sdict["BBox"] = self.BBox - sdict["Matrix"] = self.Matrix - sdict["Resources"] = resources - return self.Contents.format(document) - -class PDFPostScriptXObject: - "For embedding PD (e.g. tray commands) in PDF" - def __init__(self, content=None): - self.content = content - - def format(self, document): - S = PDFStream() - S.content = self.content - S.__Comment__ = "xobject postscript stream" - sdict = S.dictionary - sdict["Type"] = PDFName("XObject") - sdict["Subtype"] = PDFName("PS") - return S.format(document) - -class PDFImageXObject: - # first attempts at a hard-coded one - # in the file, Image XObjects are stream objects. We already - # have a PDFStream object with 3 attributes: dictionary, content - # and filters. So the job of this thing is to construct the - # right PDFStream instance and ask it to format itself. - def __init__(self, name, source=None, mask=None): - self.name = name - self.width = 24 - self.height = 23 - self.bitsPerComponent = 1 - self.colorSpace = 'DeviceGray' - self._filters = 'ASCII85Decode', - self.streamContent = """ - 003B00 002700 002480 0E4940 114920 14B220 3CB650 - 75FE88 17FF8C 175F14 1C07E2 3803C4 703182 F8EDFC - B2BBC2 BB6F84 31BFC2 18EA3C 0E3E00 07FC00 03F800 - 1E1800 1FF800> - """ - self.mask = mask - - if source is None: - pass # use the canned one. - elif type(source) == type(''): - # it is a filename - import os - ext = string.lower(os.path.splitext(source)[1]) - if ext in ('.jpg', '.jpeg'): - self.loadImageFromJPEG(open_for_read(source)) - else: - self.loadImageFromA85(source) - else: # it is already a PIL Image - self.loadImageFromSRC(source) - - def loadImageFromA85(self,source): - IMG=[] - imagedata = map(string.strip,pdfutils.cacheImageFile(source,returnInMemory=1,IMG=IMG)) - words = string.split(imagedata[1]) - self.width, self.height = map(string.atoi,(words[1],words[3])) - self.colorSpace = 'DeviceRGB' - self.bitsPerComponent = 8 - self._filters = 'ASCII85Decode','FlateDecode' #'A85','Fl' - if IMG: self._checkTransparency(IMG[0]) - elif self.mask=='auto': self.mask = None - self.streamContent = string.join(imagedata[3:-1],'') - - def loadImageFromJPEG(self,imageFile): - info = pdfutils.readJPEGInfo(imageFile) - self.width, self.height = info[0], info[1] - self.bitsPerComponent = 8 - if info[2] == 1: - self.colorSpace = 'DeviceGray' - elif info[2] == 3: - self.colorSpace = 'DeviceRGB' - else: #maybe should generate an error, is this right for CMYK? - self.colorSpace = 'DeviceCMYK' - imageFile.seek(0) #reset file pointer - self.streamContent = pdfutils._AsciiBase85Encode(imageFile.read()) - self._filters = 'ASCII85Decode','DCTDecode' #'A85','DCT' - self.mask = None - - def _checkTransparency(self,im): - if self.mask=='auto': - tc = im.getTransparent() - if tc: - self.mask = (tc[0], tc[0], tc[1], tc[1], tc[2], tc[2]) - else: - self.mask = None - elif hasattr(self.mask,'rgb'): - _ = self.mask.rgb() - self.mask = _[0],_[0],_[1],_[1],_[2],_[2] - - def loadImageFromSRC(self, im): - "Extracts the stream, width and height" - if im._image.format=='JPEG': - fp=im.fp - fp.seek(0) - self.loadImageFromJPEG(fp) - else: - zlib = import_zlib() - if not zlib: return - self.width, self.height = im.getSize() - raw = im.getRGBData() - assert(len(raw) == self.width*self.height, "Wrong amount of data for image") - self.streamContent = pdfutils._AsciiBase85Encode(zlib.compress(raw)) - self.colorSpace = 'DeviceRGB' - self.bitsPerComponent = 8 - self._filters = 'ASCII85Decode','FlateDecode' #'A85','Fl' - self._checkTransparency(im) - - def format(self, document): - S = PDFStream() - S.content = self.streamContent - dict = S.dictionary - dict["Type"] = PDFName("XObject") - dict["Subtype"] = PDFName("Image") - dict["Width"] = self.width - dict["Height"] = self.height - dict["BitsPerComponent"] = self.bitsPerComponent - dict["ColorSpace"] = PDFName(self.colorSpace) - dict["Filter"] = PDFArray(map(PDFName,self._filters)) - dict["Length"] = len(self.streamContent) - if self.mask: dict["Mask"] = PDFArray(self.mask) - return S.format(document) - -if __name__=="__main__": - print "There is no script interpretation for pdfdoc." diff --git a/bin/reportlab/pdfbase/pdfform.py b/bin/reportlab/pdfbase/pdfform.py deleted file mode 100644 index 9fca917021e..00000000000 --- a/bin/reportlab/pdfbase/pdfform.py +++ /dev/null @@ -1,632 +0,0 @@ - -"""Support for Acrobat Forms in ReportLab documents - -This module is somewhat experimental at this time. - -Includes basic support for - textfields, - select fields (drop down lists), and - check buttons. - -The public interface consists of functions at the moment. -At some later date these operations may be made into canvas -methods. (comments?) - -The ...Absolute(...) functions position the fields with respect -to the absolute canvas coordinate space -- that is, they do not -respect any coordinate transforms in effect for the canvas. - -The ...Relative(...) functions position the ONLY THE LOWER LEFT -CORNER of the field using the coordinate transform in effect for -the canvas. THIS WILL ONLY WORK CORRECTLY FOR TRANSLATED COORDINATES --- THE SHAPE, SIZE, FONTSIZE, AND ORIENTATION OF THE FIELD WILL NOT BE EFFECTED -BY SCALING, ROTATION, SKEWING OR OTHER NON-TRANSLATION COORDINATE -TRANSFORMS. - -Please note that all field names (titles) in a given document must be unique. -Textfields and select fields only support the "base 14" canvas fonts -at this time. - -See individual function docstrings below for more information. - -The function test1(...) generates a simple test file. - -THIS CONTRIBUTION WAS COMMISSIONED BY REPORTLAB USERS -WHO WISH TO REMAIN ANONYMOUS. -""" - -### NOTE: MAKE THE STRING FORMATS DYNAMIC IN PATTERNS TO SUPPORT ENCRYPTION XXXX - -import string -from reportlab.pdfbase.pdfdoc import LINEEND, PDFString, PDFStream, PDFDictionary, PDFName - -#==========================public interfaces - -def textFieldAbsolute(canvas, title, x, y, width, height, value="", maxlen=1000000, multiline=0): - """Place a text field on the current page - with name title at ABSOLUTE position (x,y) with - dimensions (width, height), using value as the default value and - maxlen as the maximum permissible length. If multiline is set make - it a multiline field. - """ - theform = getForm(canvas) - return theform.textField(canvas, title, x, y, x+width, y+height, value, maxlen, multiline) - -def textFieldRelative(canvas, title, xR, yR, width, height, value="", maxlen=1000000, multiline=0): - "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform" - (xA, yA) = canvas.absolutePosition(xR,yR) - return textFieldAbsolute(canvas, title, xA, yA, width, height, value, maxlen, multiline) - -def buttonFieldAbsolute(canvas, title, value, x, y): - """Place a check button field on the current page - with name title and default value value (one of "Yes" or "Off") - at ABSOLUTE position (x,y). - """ - theform = getForm(canvas) - return theform.buttonField(canvas, title, value, x, y) - -def buttonFieldRelative(canvas, title, value, xR, yR): - "same as buttonFieldAbsolute except the x and y are relative to the canvas coordinate transform" - (xA, yA) = canvas.absolutePosition(xR,yR) - return buttonFieldAbsolute(canvas, title, value, xA, yA) - -def selectFieldAbsolute(canvas, title, value, options, x, y, width, height): - """Place a select field (drop down list) on the current page - with name title and - with options listed in the sequence options - default value value (must be one of options) - at ABSOLUTE position (x,y) with dimensions (width, height).""" - theform = getForm(canvas) - theform.selectField(canvas, title, value, options, x, y, x+width, y+height) - -def selectFieldRelative(canvas, title, value, options, xR, yR, width, height): - "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform" - (xA, yA) = canvas.absolutePosition(xR,yR) - return selectFieldAbsolute(canvas, title, value, options, xA, yA, width, height) - -def test1(): - from reportlab.pdfgen import canvas - fn = "formtest1.pdf" - c = canvas.Canvas(fn) - # first page - c.setFont("Courier", 10) - c.drawString(100, 500, "hello world") - textFieldAbsolute(c, "fieldA", 100, 600, 100, 20, "default value") - textFieldAbsolute(c, "fieldB", 100, 300, 100, 50, "another default value", multiline=1) - selectFieldAbsolute(c, "fieldC", "France", ["Canada", "France", "China"], 100, 200, 100, 20) - c.rect(100, 600, 100, 20) - buttonFieldAbsolute(c, "field2", "Yes", 100, 700) - c.rect(100, 700, 20, 20) - buttonFieldAbsolute(c, "field3", "Off", 100, 800) - c.rect(100, 800, 20, 20) - # second page - c.showPage() - c.setFont("Helvetica", 7) - c.translate(50, 20) - c.drawString(100, 500, "hello world") - textFieldRelative(c, "fieldA_1", 100, 600, 100, 20, "default value 2") - c.setStrokeColorRGB(1,0,0) - c.setFillColorRGB(0,1,0.5) - textFieldRelative(c, "fieldB_1", 100, 300, 100, 50, "another default value 2", multiline=1) - selectFieldRelative(c, "fieldC_1", "France 1", ["Canada 0", "France 1", "China 2"], 100, 200, 100, 20) - c.rect(100, 600, 100, 20) - buttonFieldRelative(c, "field2_1", "Yes", 100, 700) - c.rect(100, 700, 20, 20) - buttonFieldRelative(c, "field3_1", "Off", 100, 800) - c.rect(100, 800, 20, 20) - c.save() - print "wrote", fn - -#==========================end of public interfaces - -from pdfpattern import PDFPattern - -def getForm(canvas): - "get form from canvas, create the form if needed" - try: - return canvas.AcroForm - except AttributeError: - theform = canvas.AcroForm = AcroForm() - # install the form in the document - d = canvas._doc - cat = d._catalog - cat.AcroForm = theform - return theform - -class AcroForm: - def __init__(self): - self.fields = [] - def textField(self, canvas, title, xmin, ymin, xmax, ymax, value="", maxlen=1000000, multiline=0): - # determine the page ref - doc = canvas._doc - page = doc.thisPageRef() - # determine text info - (R,G,B) = canvas._fillColorRGB - #print "rgb", (R,G,B) - font = canvas. _fontname - fontsize = canvas. _fontsize - field = TextField(title, value, xmin, ymin, xmax, ymax, page, maxlen, - font, fontsize, R, G, B, multiline) - self.fields.append(field) - canvas._addAnnotation(field) - def selectField(self, canvas, title, value, options, xmin, ymin, xmax, ymax): - # determine the page ref - doc = canvas._doc - page = doc.thisPageRef() - # determine text info - (R,G,B) = canvas._fillColorRGB - #print "rgb", (R,G,B) - font = canvas. _fontname - fontsize = canvas. _fontsize - field = SelectField(title, value, options, xmin, ymin, xmax, ymax, page, - font=font, fontsize=fontsize, R=R, G=G, B=B) - self.fields.append(field) - canvas._addAnnotation(field) - def buttonField(self, canvas, title, value, xmin, ymin): - # determine the page ref - doc = canvas._doc - page = doc.thisPageRef() - field = ButtonField(title, value, xmin, ymin, page) - self.fields.append(field) - canvas._addAnnotation(field) - def format(self, document): - from reportlab.pdfbase.pdfdoc import PDFArray - proxy = PDFPattern(FormPattern, Resources=GLOBALRESOURCES, fields=PDFArray(self.fields)) - return proxy.format(document) - -FormPattern = [ -'<<', LINEEND, -' /NeedAppearances true ', LINEEND, -' /DA ', PDFString('/Helv 0 Tf 0 g '), LINEEND, -' /DR ', LINEEND, -["Resources"], -' /Fields ', LINEEND, -["fields"], -'>>' -] - -def FormFontsDictionary(): - from reportlab.pdfbase.pdfdoc import PDFDictionary - fontsdictionary = PDFDictionary() - fontsdictionary.__RefOnly__ = 1 - for (fullname, shortname) in FORMFONTNAMES.items(): - fontsdictionary[shortname] = FormFont(fullname, shortname) - fontsdictionary["ZaDb"] = ZADB - return fontsdictionary - -def FormResources(): - return PDFPattern(FormResourcesDictionaryPattern, - Encoding=ENCODING, Font=GLOBALFONTSDICTIONARY) - -ZaDbPattern = [ -' <<' -' /BaseFont' -' /ZapfDingbats' -' /Name' -' /ZaDb' -' /Subtype' -' /Type1' -' /Type' -' /Font' -'>>'] - -ZADB = PDFPattern(ZaDbPattern) - -FormResourcesDictionaryPattern = [ -'<<', -' /Encoding ', -["Encoding"], LINEEND, -' /Font ', -["Font"], LINEEND, -'>>' -] - -FORMFONTNAMES = { - "Helvetica": "Helv", - "Helvetica-Bold": "HeBo", - 'Courier': "Cour", - 'Courier-Bold': "CoBo", - 'Courier-Oblique': "CoOb", - 'Courier-BoldOblique': "CoBO", - 'Helvetica-Oblique': "HeOb", - 'Helvetica-BoldOblique': "HeBO", - 'Times-Roman': "Time", - 'Times-Bold': "TiBo", - 'Times-Italic': "TiIt", - 'Times-BoldItalic': "TiBI", - } - -EncodingPattern = [ -'<<', -' /PDFDocEncoding ', -["PDFDocEncoding"], LINEEND, -'>>', -] - -PDFDocEncodingPattern = [ -'<<' -' /Differences' -' [' -' 24' -' /breve' -' /caron' -' /circumflex' -' /dotaccent' -' /hungarumlaut' -' /ogonek' -' /ring' -' /tilde' -' 39' -' /quotesingle' -' 96' -' /grave' -' 128' -' /bullet' -' /dagger' -' /daggerdbl' -' /ellipsis' -' /emdash' -' /endash' -' /florin' -' /fraction' -' /guilsinglleft' -' /guilsinglright' -' /minus' -' /perthousand' -' /quotedblbase' -' /quotedblleft' -' /quotedblright' -' /quoteleft' -' /quoteright' -' /quotesinglbase' -' /trademark' -' /fi' -' /fl' -' /Lslash' -' /OE' -' /Scaron' -' /Ydieresis' -' /Zcaron' -' /dotlessi' -' /lslash' -' /oe' -' /scaron' -' /zcaron' -' 160' -' /Euro' -' 164' -' /currency' -' 166' -' /brokenbar' -' 168' -' /dieresis' -' /copyright' -' /ordfeminine' -' 172' -' /logicalnot' -' /.notdef' -' /registered' -' /macron' -' /degree' -' /plusminus' -' /twosuperior' -' /threesuperior' -' /acute' -' /mu' -' 183' -' /periodcentered' -' /cedilla' -' /onesuperior' -' /ordmasculine' -' 188' -' /onequarter' -' /onehalf' -' /threequarters' -' 192' -' /Agrave' -' /Aacute' -' /Acircumflex' -' /Atilde' -' /Adieresis' -' /Aring' -' /AE' -' /Ccedilla' -' /Egrave' -' /Eacute' -' /Ecircumflex' -' /Edieresis' -' /Igrave' -' /Iacute' -' /Icircumflex' -' /Idieresis' -' /Eth' -' /Ntilde' -' /Ograve' -' /Oacute' -' /Ocircumflex' -' /Otilde' -' /Odieresis' -' /multiply' -' /Oslash' -' /Ugrave' -' /Uacute' -' /Ucircumflex' -' /Udieresis' -' /Yacute' -' /Thorn' -' /germandbls' -' /agrave' -' /aacute' -' /acircumflex' -' /atilde' -' /adieresis' -' /aring' -' /ae' -' /ccedilla' -' /egrave' -' /eacute' -' /ecircumflex' -' /edieresis' -' /igrave' -' /iacute' -' /icircumflex' -' /idieresis' -' /eth' -' /ntilde' -' /ograve' -' /oacute' -' /ocircumflex' -' /otilde' -' /odieresis' -' /divide' -' /oslash' -' /ugrave' -' /uacute' -' /ucircumflex' -' /udieresis' -' /yacute' -' /thorn' -' /ydieresis' -' ]' -' /Type' -' /Encoding' -'>>'] - -# global constant -PDFDOCENC = PDFPattern(PDFDocEncodingPattern) -# global constant -ENCODING = PDFPattern(EncodingPattern, PDFDocEncoding=PDFDOCENC) - - -def FormFont(BaseFont, Name): - from reportlab.pdfbase.pdfdoc import PDFName - return PDFPattern(FormFontPattern, BaseFont=PDFName(BaseFont), Name=PDFName(Name), Encoding=PDFDOCENC) - -FormFontPattern = [ -'<<', -' /BaseFont ', -["BaseFont"], LINEEND, -' /Encoding ', -["Encoding"], LINEEND, -' /Name ', -["Name"], LINEEND, -' /Subtype ' -' /Type1 ' -' /Type ' -' /Font ' -'>>' ] - -# global constants -GLOBALFONTSDICTIONARY = FormFontsDictionary() -GLOBALRESOURCES = FormResources() - - -def TextField(title, value, xmin, ymin, xmax, ymax, page, - maxlen=1000000, font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627, multiline=0): - from reportlab.pdfbase.pdfdoc import PDFString, PDFName - Flags = 0 - if multiline: - Flags = Flags | (1<<12) # bit 13 is at position 12 :) - fontname = FORMFONTNAMES[font] - return PDFPattern(TextFieldPattern, - value=PDFString(value), maxlen=maxlen, page=page, - title=PDFString(title), - xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax, - fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B, Flags=Flags) - - -TextFieldPattern = [ -'<<' -' /DA' -' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)' -' /DV ', -["value"], LINEEND, -' /F 4 /FT /Tx' -'/MK << /BC [ 0 0 0 ] >>' -' /MaxLen ', -["maxlen"], LINEEND, -' /P ', -["page"], LINEEND, -' /Rect ' -' [', ["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], ' ]' -'/Subtype /Widget' -' /T ', -["title"], LINEEND, -' /Type' -' /Annot' -' /V ', -["value"], LINEEND, -' /Ff ', -["Flags"],LINEEND, -'>>'] - -def SelectField(title, value, options, xmin, ymin, xmax, ymax, page, - font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627): - #print "ARGS", (title, value, options, xmin, ymin, xmax, ymax, page, font, fontsize, R, G, B) - from reportlab.pdfbase.pdfdoc import PDFString, PDFName, PDFArray - if value not in options: - raise ValueError, "value %s must be one of options %s" % (repr(value), repr(options)) - fontname = FORMFONTNAMES[font] - optionstrings = map(PDFString, options) - optionarray = PDFArray(optionstrings) - return PDFPattern(SelectFieldPattern, - Options=optionarray, - Selected=PDFString(value), Page=page, - Name=PDFString(title), - xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax, - fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B) - -SelectFieldPattern = [ -'<< % a select list',LINEEND, -' /DA ', -' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)',LINEEND, -#' (/Helv 12 Tf 0 g)',LINEEND, -' /DV ', -["Selected"],LINEEND, -' /F ', -' 4',LINEEND, -' /FT ', -' /Ch',LINEEND, -' /MK ', -' <<', -' /BC', -' [', -' 0', -' 0', -' 0', -' ]', -' /BG', -' [', -' 1', -' 1', -' 1', -' ]', -' >>',LINEEND, -' /Opt ', -["Options"],LINEEND, -' /P ', -["Page"],LINEEND, -'/Rect', -' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], -' ] ',LINEEND, -'/Subtype', -' /Widget',LINEEND, -' /T ', -["Name"],LINEEND, -' /Type ', -' /Annot', -' /V ', -["Selected"],LINEEND, -'>>'] - -def ButtonField(title, value, xmin, ymin, page): - if value not in ("Yes", "Off"): - raise ValueError, "button value must be 'Yes' or 'Off': "+repr(value) - (dx, dy) = (16.77036, 14.90698) - return PDFPattern(ButtonFieldPattern, - Name=PDFString(title), - xmin=xmin, ymin=ymin, xmax=xmin+dx, ymax=ymin+dy, - Hide=HIDE, - APDOff=APDOFF, - APDYes=APDYES, - APNYes=APNYES, - Value=PDFName(value), - Page=page) - -ButtonFieldPattern = ['<< ', -'/AA', -' <<', -' /D ', -["Hide"], LINEEND, -#' %(imported.18.0)s', -' >> ', -'/AP ', -' <<', -' /D', -' <<', -' /Off ', -#' %(imported.40.0)s', -["APDOff"], LINEEND, -' /Yes ', -#' %(imported.39.0)s', -["APDYes"], LINEEND, -' >>', LINEEND, -' /N', -' << ', -' /Yes ', -#' %(imported.38.0)s', -["APNYes"], LINEEND, -' >>', -' >>', LINEEND, -' /AS ', -["Value"], LINEEND, -' /DA ', -PDFString('/ZaDb 0 Tf 0 g'), LINEEND, -'/DV ', -["Value"], LINEEND, -'/F ', -' 4 ', -'/FT ', -' /Btn ', -'/H ', -' /T ', -'/MK ', -' <<', -' /AC (\\376\\377)', -#PDFString('\376\377'), -' /CA ', -PDFString('4'), -' /RC ', -PDFString('\376\377'), -' >> ',LINEEND, -'/P ', -["Page"], LINEEND, -'/Rect', -' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], -' ] ',LINEEND, -'/Subtype', -' /Widget ', -'/T ', -["Name"], LINEEND, -'/Type', -' /Annot ', -'/V ', -["Value"], LINEEND, -' >>'] - -HIDE = PDFPattern([ -'<< ' -'/S ' -' /Hide ' -'>>']) - -def buttonStreamDictionary(): - "everything except the length for the button appearance streams" - result = PDFDictionary() - result["SubType"] = "/Form" - result["BBox"] = "[0 0 16.77036 14.90698]" - font = PDFDictionary() - font["ZaDb"] = ZADB - resources = PDFDictionary() - resources["ProcSet"] = "[ /PDF /Text ]" - resources["Font"] = font - result["Resources"] = resources - return result - -def ButtonStream(content): - dict = buttonStreamDictionary() - result = PDFStream(dict, content) - result.filters = [] - return result - -APDOFF = ButtonStream('0.749 g 0 0 16.7704 14.907 re f'+LINEEND) -APDYES = ButtonStream( -'0.749 g 0 0 16.7704 14.907 re f q 1 1 14.7704 12.907 re W '+ -'n BT /ZaDb 11.3086 Tf 0 g 1 0 0 1 3.6017 3.3881 Tm (4) Tj ET'+LINEEND) -APNYES = ButtonStream( -'q 1 1 14.7704 12.907 re W n BT /ZaDb 11.3086 Tf 0 g 1 0 0 1 3.6017 3.3881 Tm (4) Tj ET Q'+LINEEND) - -#==== script interpretation - -if __name__=="__main__": - test1() \ No newline at end of file diff --git a/bin/reportlab/pdfbase/pdfmetrics.py b/bin/reportlab/pdfbase/pdfmetrics.py deleted file mode 100644 index a6d0dc60de3..00000000000 --- a/bin/reportlab/pdfbase/pdfmetrics.py +++ /dev/null @@ -1,773 +0,0 @@ -#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/pdfbase/pdfmetrics.py -#$Header $ -__version__=''' $Id$ ''' -__doc__=""" -This provides a database of font metric information and -efines Font, Encoding and TypeFace classes aimed at end users. - -There are counterparts to some of these in pdfbase/pdfdoc.py, but -the latter focus on constructing the right PDF objects. These -classes are declarative and focus on letting the user construct -and query font objects. - -The module maintains a registry of font objects at run time. - -It is independent of the canvas or any particular context. It keeps -a registry of Font, TypeFace and Encoding objects. Ideally these -would be pre-loaded, but due to a nasty circularity problem we -trap attempts to access them and do it on first access. -""" -import string, os -from types import StringType, ListType, TupleType -from reportlab.pdfbase import _fontdata -from reportlab.lib.logger import warnOnce -from reportlab.lib.utils import rl_isfile, open_and_read, open_and_readlines -from reportlab.rl_config import defaultEncoding - -standardFonts = _fontdata.standardFonts -standardEncodings = _fontdata.standardEncodings - -_dummyEncoding=' _not an encoding_ ' -# conditional import - try both import techniques, and set a flag -try: - import _rl_accel - try: - _stringWidth = _rl_accel.stringWidth - _rl_accel.defaultEncoding(_dummyEncoding) - except: - _stringWidth = None -except ImportError: - _stringWidth = None - -_typefaces = {} -_encodings = {} -_fonts = {} - -class FontError(Exception): - pass -class FontNotFoundError(Exception): - pass - -def parseAFMFile(afmFileName): - """Quick and dirty - gives back a top-level dictionary - with top-level items, and a 'widths' key containing - a dictionary of glyph names and widths. Just enough - needed for embedding. A better parser would accept - options for what data you wwanted, and preserve the - order.""" - - lines = open_and_readlines(afmFileName, 'r') - if len(lines)<=1: - #likely to be a MAC file - lines = string.split(lines,'\r') - if len(lines)<=1: - raise ValueError, 'AFM file %s hasn\'t enough data' % afmFileName - topLevel = {} - glyphLevel = [] - - lines = map(string.strip, lines) - #pass 1 - get the widths - inMetrics = 0 # os 'TOP', or 'CHARMETRICS' - for line in lines: - if line[0:16] == 'StartCharMetrics': - inMetrics = 1 - elif line[0:14] == 'EndCharMetrics': - inMetrics = 0 - elif inMetrics: - chunks = string.split(line, ';') - chunks = map(string.strip, chunks) - cidChunk, widthChunk, nameChunk = chunks[0:3] - - # character ID - l, r = string.split(cidChunk) - assert l == 'C', 'bad line in font file %s' % line - cid = string.atoi(r) - - # width - l, r = string.split(widthChunk) - assert l == 'WX', 'bad line in font file %s' % line - width = string.atoi(r) - - # name - l, r = string.split(nameChunk) - assert l == 'N', 'bad line in font file %s' % line - name = r - - glyphLevel.append((cid, width, name)) - - # pass 2 font info - inHeader = 0 - for line in lines: - if line[0:16] == 'StartFontMetrics': - inHeader = 1 - if line[0:16] == 'StartCharMetrics': - inHeader = 0 - elif inHeader: - if line[0:7] == 'Comment': pass - try: - left, right = string.split(line,' ',1) - except: - raise ValueError, "Header information error in afm %s: line='%s'" % (afmFileName, line) - try: - right = string.atoi(right) - except: - pass - topLevel[left] = right - - - return (topLevel, glyphLevel) - -class TypeFace: - def __init__(self, name): - self.name = name - self.glyphNames = [] - self.glyphWidths = {} - self.ascent = 0 - self.descent = 0 - - - # all typefaces of whatever class should have these 3 attributes. - # these are the basis for family detection. - self.familyName = None # should set on load/construction if possible - self.bold = 0 # bold faces should set this - self.italic = 0 #italic faces should set this - - - if name == 'ZapfDingbats': - self.requiredEncoding = 'ZapfDingbatsEncoding' - elif name == 'Symbol': - self.requiredEncoding = 'SymbolEncoding' - else: - self.requiredEncoding = None - if name in standardFonts: - self.builtIn = 1 - self._loadBuiltInData(name) - else: - self.builtIn = 0 - - def _loadBuiltInData(self, name): - """Called for the built in 14 fonts. Gets their glyph data. - We presume they never change so this can be a shared reference.""" - name = str(name) #needed for pycanvas&jython/2.1 compatibility - self.glyphWidths = _fontdata.widthsByFontGlyph[name] - self.glyphNames = self.glyphWidths.keys() - self.ascent,self.descent = _fontdata.ascent_descent[name] - - def getFontFiles(self): - "Info function, return list of the font files this depends on." - return [] - - def findT1File(self, ext='.pfb'): - possible_exts = (string.lower(ext), string.upper(ext)) - if hasattr(self,'pfbFileName'): - r_basename = os.path.splitext(self.pfbFileName)[0] - for e in possible_exts: - if rl_isfile(r_basename + e): - return r_basename + e - try: - r = _fontdata.findT1File(self.name) - except: - afm = bruteForceSearchForAFM(self.name) - if afm: - if string.lower(ext) == '.pfb': - for e in possible_exts: - pfb = os.path.splitext(afm)[0] + e - if rl_isfile(pfb): - r = pfb - else: - r = None - elif string.lower(ext) == '.afm': - r = afm - else: - r = None - if r is None: - warnOnce("Can't find %s for face '%s'" % (ext, self.name)) - return r - -def bruteForceSearchForAFM(faceName): - """Looks in all AFM files on path for face with given name. - - Returns AFM file name or None. Ouch!""" - import glob - from reportlab.rl_config import T1SearchPath - - for dirname in T1SearchPath: - if not os.path.isdir(dirname): - continue - possibles = glob.glob(dirname + os.sep + '*.[aA][fF][mM]') - for possible in possibles: - (topDict, glyphDict) = parseAFMFile(possible) - if topDict['FontName'] == faceName: - return possible - return None - - - -#for faceName in standardFonts: -# registerTypeFace(TypeFace(faceName)) - - -class Encoding: - """Object to help you create and refer to encodings.""" - def __init__(self, name, base=None): - self.name = name - self.frozen = 0 - if name in standardEncodings: - assert base is None, "Can't have a base encoding for a standard encoding" - self.baseEncodingName = name - self.vector = _fontdata.encodings[name] - elif base == None: - # assume based on the usual one - self.baseEncodingName = defaultEncoding - self.vector = _fontdata.encodings[defaultEncoding] - elif type(base) is StringType: - baseEnc = getEncoding(base) - self.baseEncodingName = baseEnc.name - self.vector = baseEnc.vector[:] - elif type(base) in (ListType, TupleType): - self.baseEncodingName = defaultEncoding - self.vector = base[:] - elif isinstance(base, Encoding): - # accept a vector - self.baseEncodingName = base.name - self.vector = base.vector[:] - - def __getitem__(self, index): - "Return glyph name for that code point, or None" - # THIS SHOULD BE INLINED FOR SPEED - return self.vector[index] - - def __setitem__(self, index, value): - # should fail if they are frozen - assert self.frozen == 0, 'Cannot modify a frozen encoding' - if self.vector[index]!=value: - L = list(self.vector) - L[index] = value - self.vector = tuple(L) - - def freeze(self): - self.vector = tuple(self.vector) - self.frozen = 1 - - def isEqual(self, other): - return ((self.name == other.name) and (self.vector == other.vector)) - - def modifyRange(self, base, newNames): - """Set a group of character names starting at the code point 'base'.""" - assert self.frozen == 0, 'Cannot modify a frozen encoding' - idx = base - for name in newNames: - self.vector[idx] = name - idx = idx + 1 - - def getDifferences(self, otherEnc): - """Return a compact list of the code points differing between two encodings - - This is in the Adobe format: list of - [[b1, name1, name2, name3], - [b2, name4]] - where b1...bn is the starting code point, and the glyph names following - are assigned consecutive code points.""" - - ranges = [] - curRange = None - for i in xrange(len(self.vector)): - glyph = self.vector[i] - if glyph==otherEnc.vector[i]: - if curRange: - ranges.append(curRange) - curRange = [] - else: - if curRange: - curRange.append(glyph) - elif glyph: - curRange = [i, glyph] - if curRange: - ranges.append(curRange) - return ranges - - def makePDFObject(self): - "Returns a PDF Object representing self" - # avoid circular imports - this cannot go at module level - from reportlab.pdfbase import pdfdoc - - D = {} - baseEnc = getEncoding(self.baseEncodingName) - differences = self.getDifferences(baseEnc) #[None] * 256) - - # if no differences, we just need the base name - if differences == []: - return pdfdoc.PDFName(self.baseEncodingName) - else: - #make up a dictionary describing the new encoding - diffArray = [] - for range in differences: - diffArray.append(range[0]) # numbers go 'as is' - for glyphName in range[1:]: - if glyphName is not None: - # there is no way to 'unset' a character in the base font. - diffArray.append('/' + glyphName) - - #print 'diffArray = %s' % diffArray - D["Differences"] = pdfdoc.PDFArray(diffArray) - D["BaseEncoding"] = pdfdoc.PDFName(self.baseEncodingName) - D["Type"] = pdfdoc.PDFName("Encoding") - PD = pdfdoc.PDFDictionary(D) - return PD - -#for encName in standardEncodings: -# registerEncoding(Encoding(encName)) - -class Font: - """Represents a font (i.e combination of face and encoding). - - Defines suitable machinery for single byte fonts. This is - a concrete class which can handle the basic built-in fonts; - not clear yet if embedded ones need a new font class or - just a new typeface class (which would do the job through - composition)""" - def __init__(self, name, faceName, encName): - self.fontName = name - self.face = getTypeFace(faceName) - self.encoding= getEncoding(encName) - self._calcWidths() - - # multi byte fonts do their own stringwidth calculations. - # signal this here. - self._multiByte = 0 - - def _calcWidths(self): - """Vector of widths for stringWidth function""" - #synthesize on first request - w = [0] * 256 - gw = self.face.glyphWidths - vec = self.encoding.vector - for i in range(256): - glyphName = vec[i] - if glyphName is not None: - try: - width = gw[glyphName] - w[i] = width - except KeyError: - import reportlab.rl_config - if reportlab.rl_config.warnOnMissingFontGlyphs: - print 'typeface "%s" does not have a glyph "%s", bad font!' % (self.face.name, glyphName) - else: - pass - self.widths = w - - if not _stringWidth: - def stringWidth(self, text, size): - """This is the "purist" approach to width. The practical one - is to use the stringWidth one which may be optimized - in C.""" - w = 0 - widths = self.widths - for ch in text: - w = w + widths[ord(ch)] - return w * 0.001 * size - - def _formatWidths(self): - "returns a pretty block in PDF Array format to aid inspection" - text = '[' - for i in range(256): - text = text + ' ' + str(self.widths[i]) - if i == 255: - text = text + ' ]' - if i % 16 == 15: - text = text + '\n' - return text - - def addObjects(self, doc): - """Makes and returns one or more PDF objects to be added - to the document. The caller supplies the internal name - to be used (typically F1, F2... in sequence) """ - # avoid circular imports - this cannot go at module level - from reportlab.pdfbase import pdfdoc - - # construct a Type 1 Font internal object - internalName = 'F' + repr(len(doc.fontMapping)+1) - pdfFont = pdfdoc.PDFType1Font() - pdfFont.Name = internalName - pdfFont.BaseFont = self.face.name - pdfFont.__Comment__ = 'Font %s' % self.fontName - pdfFont.Encoding = self.encoding.makePDFObject() - - # is it a built-in one? if not, need more stuff. - if not self.face.name in standardFonts: - pdfFont.FirstChar = 0 - pdfFont.LastChar = 255 - pdfFont.Widths = pdfdoc.PDFArray(self.widths) - pdfFont.FontDescriptor = self.face.addObjects(doc) - # now link it in - ref = doc.Reference(pdfFont, internalName) - - # also refer to it in the BasicFonts dictionary - fontDict = doc.idToObject['BasicFonts'].dict - fontDict[internalName] = pdfFont - - # and in the font mappings - doc.fontMapping[self.fontName] = '/' + internalName - -PFB_MARKER=chr(0x80) -PFB_ASCII=chr(1) -PFB_BINARY=chr(2) -PFB_EOF=chr(3) -def _pfbSegLen(p,d): - '''compute a pfb style length from the first 4 bytes of string d''' - return ((((ord(d[p+3])<<8)|ord(d[p+2])<<8)|ord(d[p+1]))<<8)|ord(d[p]) - -def _pfbCheck(p,d,m,fn): - if d[p]!=PFB_MARKER or d[p+1]!=m: - raise ValueError, 'Bad pfb file\'%s\' expected chr(%d)chr(%d) at char %d, got chr(%d)chr(%d)' % (fn,ord(PFB_MARKER),ord(m),p,ord(d[p]),ord(d[p+1])) - if m==PFB_EOF: return - p = p + 2 - l = _pfbSegLen(p,d) - p = p + 4 - if p+l>len(d): - raise ValueError, 'Bad pfb file\'%s\' needed %d+%d bytes have only %d!' % (fn,p,l,len(d)) - return p, p+l - - -class EmbeddedType1Face(TypeFace): - """A Type 1 font other than one of the basic 14. - - Its glyph data will be embedded in the PDF file.""" - def __init__(self, afmFileName, pfbFileName): - # ignore afm file for now - TypeFace.__init__(self, None) - #None is a hack, name will be supplied by AFM parse lower done - #in this __init__ method. - self.afmFileName = os.path.abspath(afmFileName) - self.pfbFileName = os.path.abspath(pfbFileName) - self.requiredEncoding = None - self._loadGlyphs(pfbFileName) - self._loadMetrics(afmFileName) - - def getFontFiles(self): - return [self.afmFileName, self.pfbFileName] - - def _loadGlyphs(self, pfbFileName): - """Loads in binary glyph data, and finds the four length - measurements needed for the font descriptor""" - assert rl_isfile(pfbFileName), 'file %s not found' % pfbFileName - d = open_and_read(pfbFileName, 'b') - s1, l1 = _pfbCheck(0,d,PFB_ASCII,pfbFileName) - s2, l2 = _pfbCheck(l1,d,PFB_BINARY,pfbFileName) - s3, l3 = _pfbCheck(l2,d,PFB_ASCII,pfbFileName) - _pfbCheck(l3,d,PFB_EOF,pfbFileName) - self._binaryData = d[s1:l1]+d[s2:l2]+d[s3:l3] - - self._length = len(self._binaryData) - self._length1 = l1-s1 - self._length2 = l2-s2 - self._length3 = l3-s3 - - - def _loadMetrics(self, afmFileName): - """Loads in and parses font metrics""" - #assert os.path.isfile(afmFileName), "AFM file %s not found" % afmFileName - (topLevel, glyphData) = parseAFMFile(afmFileName) - - self.name = topLevel['FontName'] - self.familyName = topLevel['FamilyName'] - self.ascent = topLevel.get('Ascender', 1000) - self.descent = topLevel.get('Descender', 0) - self.capHeight = topLevel.get('CapHeight', 1000) - self.italicAngle = topLevel.get('ItalicAngle', 0) - self.stemV = topLevel.get('stemV', 0) - self.xHeight = topLevel.get('XHeight', 1000) - - strBbox = topLevel.get('FontBBox', [0,0,1000,1000]) - tokens = string.split(strBbox) - self.bbox = [] - for tok in tokens: - self.bbox.append(string.atoi(tok)) - - glyphWidths = {} - for (cid, width, name) in glyphData: - glyphWidths[name] = width - self.glyphWidths = glyphWidths - self.glyphNames = glyphWidths.keys() - self.glyphNames.sort() - - # for font-specific encodings like Symbol, Dingbats, Carta we - # need to make a new encoding as well.... - if topLevel.get('EncodingScheme', None) == 'FontSpecific': - names = [None] * 256 - for (code, width, name) in glyphData: - if code >=0 and code <=255: - names[code] = name - encName = self.name + 'Encoding' - self.requiredEncoding = encName - enc = Encoding(encName, names) - registerEncoding(enc) - - def addObjects(self, doc): - """Add whatever needed to PDF file, and return a FontDescriptor reference""" - from reportlab.pdfbase import pdfdoc - - fontFile = pdfdoc.PDFStream() - fontFile.content = self._binaryData - #fontFile.dictionary['Length'] = self._length - fontFile.dictionary['Length1'] = self._length1 - fontFile.dictionary['Length2'] = self._length2 - fontFile.dictionary['Length3'] = self._length3 - #fontFile.filters = [pdfdoc.PDFZCompress] - - fontFileRef = doc.Reference(fontFile, 'fontFile:' + self.pfbFileName) - - fontDescriptor = pdfdoc.PDFDictionary({ - 'Type': '/FontDescriptor', - 'Ascent':self.ascent, - 'CapHeight':self.capHeight, - 'Descent':self.descent, - 'Flags': 34, - 'FontBBox':pdfdoc.PDFArray(self.bbox), - 'FontName':pdfdoc.PDFName(self.name), - 'ItalicAngle':self.italicAngle, - 'StemV':self.stemV, - 'XHeight':self.xHeight, - 'FontFile': fontFileRef, - }) - fontDescriptorRef = doc.Reference(fontDescriptor, 'fontDescriptor:' + self.name) - return fontDescriptorRef - -def registerTypeFace(face): - assert isinstance(face, TypeFace), 'Not a TypeFace: %s' % face - _typefaces[face.name] = face - # HACK - bold/italic do not apply for type 1, so egister - # all combinations of mappings. - from reportlab.lib import fonts - ttname = string.lower(face.name) - if not face.name in standardFonts: - fonts.addMapping(ttname, 0, 0, face.name) - fonts.addMapping(ttname, 1, 0, face.name) - fonts.addMapping(ttname, 0, 1, face.name) - fonts.addMapping(ttname, 1, 1, face.name) - -def registerEncoding(enc): - assert isinstance(enc, Encoding), 'Not an Encoding: %s' % enc - if _encodings.has_key(enc.name): - # already got one, complain if they are not the same - if enc.isEqual(_encodings[enc.name]): - enc.freeze() - else: - raise FontError('Encoding "%s" already registered with a different name vector!' % enc.Name) - else: - _encodings[enc.name] = enc - enc.freeze() - # have not yet dealt with immutability! - -def registerFont(font): - "Registers a font, including setting up info for accelerated stringWidth" - #assert isinstance(font, Font), 'Not a Font: %s' % font - fontName = font.fontName - _fonts[fontName] = font - if font._multiByte: - # CID fonts don't need to have typeface registered. - #need to set mappings so it can go in a paragraph even if within - # bold tags - from reportlab.lib import fonts - ttname = string.lower(font.fontName) - fonts.addMapping(ttname, 0, 0, font.fontName) - fonts.addMapping(ttname, 1, 0, font.fontName) - fonts.addMapping(ttname, 0, 1, font.fontName) - fonts.addMapping(ttname, 1, 1, font.fontName) - #cannot accelerate these yet... - else: - if _stringWidth: - _rl_accel.setFontInfo(string.lower(fontName), - _dummyEncoding, - font.face.ascent, - font.face.descent, - font.widths) - - -def getTypeFace(faceName): - """Lazily construct known typefaces if not found""" - try: - return _typefaces[faceName] - except KeyError: - # not found, construct it if known - if faceName in standardFonts: - face = TypeFace(faceName) - (face.familyName, face.bold, face.italic) = _fontdata.standardFontAttributes[faceName] - registerTypeFace(face) -## print 'auto-constructing type face %s with family=%s, bold=%d, italic=%d' % ( -## face.name, face.familyName, face.bold, face.italic) - return face - else: - #try a brute force search - afm = bruteForceSearchForAFM(faceName) - if afm: - for e in ('.pfb', '.PFB'): - pfb = os.path.splitext(afm)[0] + e - if rl_isfile(pfb): break - assert rl_isfile(pfb), 'file %s not found!' % pfb - face = EmbeddedType1Face(afm, pfb) - registerTypeFace(face) - return face - else: - raise - -def getEncoding(encName): - """Lazily construct known encodings if not found""" - try: - return _encodings[encName] - except KeyError: - if encName in standardEncodings: - enc = Encoding(encName) - registerEncoding(enc) - #print 'auto-constructing encoding %s' % encName - return enc - else: - raise - -def getFont(fontName): - """Lazily constructs known fonts if not found. - - Names of form 'face-encoding' will be built if - face and encoding are known. Also if the name is - just one of the standard 14, it will make up a font - in the default encoding.""" - try: - return _fonts[fontName] - except KeyError: - #it might have a font-specific encoding e.g. Symbol - # or Dingbats. If not, take the default. - face = getTypeFace(fontName) - if face.requiredEncoding: - font = Font(fontName, fontName, face.requiredEncoding) - else: - font = Font(fontName, fontName, defaultEncoding) - registerFont(font) - return font - -def getRegisteredFontNames(): - "Returns what's in there" - reg = _fonts.keys() - reg.sort() - return reg - -def _slowStringWidth(text, fontName, fontSize): - """Define this anyway so it can be tested, but whether it is used or not depends on _rl_accel""" - font = getFont(fontName) - return font.stringWidth(text, fontSize) - #this is faster, but will need more special-casing for multi-byte fonts. - #wid = getFont(fontName).widths - #w = 0 - #for ch in text: - # w = w + wid[ord(ch)] - #return 0.001 * w * fontSize - - -if _stringWidth: - import new - Font.stringWidth = new.instancemethod(_rl_accel._instanceStringWidth,None,Font) - stringWidth = _stringWidth - - #if accelerator present, make sure we at least - #register Courier font, since it will fall back to Courier - #as its default font. - f = getFont('Courier') - - - def _SWRecover(text, fontName, fontSize, encoding): - '''This is called when _rl_accel's database doesn't know about a font. - Currently encoding is always a dummy. - ''' - try: - font = getFont(fontName) - if font._multiByte: - return font.stringWidth(text, fontSize) - else: - registerFont(font) - return _stringWidth(text,fontName,fontSize,encoding) - except: - warnOnce('Font %s:%s not found - using Courier:%s for widths'%(fontName,encoding,encoding)) - return _stringWidth(text,'courier',fontSize,encoding) - - _rl_accel._SWRecover(_SWRecover) -else: - stringWidth = _slowStringWidth - -def dumpFontData(): - print 'Registered Encodings:' - keys = _encodings.keys() - keys.sort() - for encName in keys: - print ' ',encName - - print - print 'Registered Typefaces:' - faces = _typefaces.keys() - faces.sort() - for faceName in faces: - print ' ',faceName - - - print - print 'Registered Fonts:' - k = _fonts.keys() - k.sort() - for key in k: - font = _fonts[key] - print ' %s (%s/%s)' % (font.fontName, font.face.name, font.encoding.name) - - - -def test3widths(texts): - # checks all 3 algorithms give same answer, note speed - import time - for fontName in standardFonts[0:1]: - t0 = time.time() - for text in texts: - l1 = _stringWidth(text, fontName, 10) - t1 = time.time() - print 'fast stringWidth took %0.4f' % (t1 - t0) - - t0 = time.time() - w = getFont(fontName).widths - for text in texts: - l2 = 0 - for ch in text: - l2 = l2 + w[ord(ch)] - t1 = time.time() - print 'slow stringWidth took %0.4f' % (t1 - t0) - - t0 = time.time() - for text in texts: - l3 = getFont(fontName).stringWidth(text, 10) - t1 = time.time() - print 'class lookup and stringWidth took %0.4f' % (t1 - t0) - print - -def testStringWidthAlgorithms(): - rawdata = open('../../rlextra/rml2pdf/doc/rml_user_guide.prep').read() - print 'rawdata length %d' % len(rawdata) - print 'test one huge string...' - test3widths([rawdata]) - print - words = string.split(rawdata) - print 'test %d shorter strings (average length %0.2f chars)...' % (len(words), 1.0*len(rawdata)/len(words)) - test3widths(words) - - -def test(): - helv = TypeFace('Helvetica') - registerTypeFace(helv) - print helv.glyphNames[0:30] - - wombat = TypeFace('Wombat') - print wombat.glyphNames - registerTypeFace(wombat) - - dumpFontData() - - -if __name__=='__main__': - test() - testStringWidthAlgorithms() diff --git a/bin/reportlab/pdfbase/pdfpattern.py b/bin/reportlab/pdfbase/pdfpattern.py deleted file mode 100644 index 0ba62c9bed6..00000000000 --- a/bin/reportlab/pdfbase/pdfpattern.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -helper for importing pdf structures into a ReportLab generated document -""" -from reportlab.pdfbase.pdfdoc import format - -import string - -class PDFPattern: - __RefOnly__ = 1 - def __init__(self, pattern_sequence, **keywordargs): - """ - Description of a kind of PDF object using a pattern. - - Pattern sequence should contain strings or singletons of form [string]. - Strings are literal strings to be used in the object. - Singletons are names of keyword arguments to include. - Keyword arguments can be non-instances which are substituted directly in string conversion, - or they can be object instances in which case they should be pdfdoc.* style - objects with a x.format(doc) method. - Keyword arguments may be set on initialization or subsequently using __setitem__, before format. - "constant object" instances can also be inserted in the patterns. - """ - self.pattern = pattern_sequence - self.arguments = keywordargs - from types import StringType, InstanceType - toptypes = (StringType, InstanceType) - for x in pattern_sequence: - if type(x) not in toptypes: - if len(x)!=1: - raise ValueError, "sequence elts must be strings or singletons containing strings: "+repr(x) - if type(x[0]) is not StringType: - raise ValueError, "Singletons must contain strings or instances only: "+repr(x[0]) - def __setitem__(self, item, value): - self.arguments[item] = value - def __getitem__(self, item): - return self.arguments[item] - def format(self, document): - L = [] - arguments = self.arguments - from types import StringType, InstanceType - for x in self.pattern: - tx = type(x) - if tx is StringType: - L.append(x) - elif tx is InstanceType: - L.append( x.format(document) ) - else: - name = x[0] - value = arguments.get(name, None) - if value is None: - raise ValueError, "%s value not defined" % repr(name) - if type(value) is InstanceType: - #L.append( value.format(document) ) - L.append(format(value, document)) - else: - L.append( str(value) ) - return string.join(L, "") - - diff --git a/bin/reportlab/pdfbase/pdfutils.py b/bin/reportlab/pdfbase/pdfutils.py deleted file mode 100644 index d4cf13b56f8..00000000000 --- a/bin/reportlab/pdfbase/pdfutils.py +++ /dev/null @@ -1,453 +0,0 @@ -#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/pdfbase/pdfutils.py -__version__=''' $Id$ ''' -__doc__='' -# pdfutils.py - everything to do with images, streams, -# compression, and some constants - -import os -from reportlab import rl_config -from string import join, replace, strip, split -from reportlab.lib.utils import getStringIO, ImageReader - -LINEEND = '\015\012' - -def _chunker(src,dst=[],chunkSize=60): - for i in xrange(0,len(src),chunkSize): - dst.append(src[i:i+chunkSize]) - return dst - -########################################################## -# -# Image compression helpers. Preprocessing a directory -# of images will offer a vast speedup. -# -########################################################## - -def cacheImageFile(filename, returnInMemory=0, IMG=None): - "Processes image as if for encoding, saves to a file with .a85 extension." - - from reportlab.lib.utils import open_for_read - import zlib - - cachedname = os.path.splitext(filename)[0] + '.a85' - if filename==cachedname: - if cachedImageExists(filename): - if returnInMemory: return split(open_for_read(cachedname).read(),LINEEND)[:-1] - else: - raise IOError, 'No such cached image %s' % filename - else: - img = ImageReader(filename) - if IMG is not None: IMG.append(img) - - imgwidth, imgheight = img.getSize() - raw = img.getRGBData() - - code = [] - # this describes what is in the image itself - code.append('BI') - code.append('/W %s /H %s /BPC 8 /CS /RGB /F [/A85 /Fl]' % (imgwidth, imgheight)) - code.append('ID') - #use a flate filter and Ascii Base 85 - assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image") - compressed = zlib.compress(raw) #this bit is very fast... - encoded = _AsciiBase85Encode(compressed) #...sadly this may not be - - #append in blocks of 60 characters - _chunker(encoded,code) - - code.append('EI') - if returnInMemory: return code - - #save it to a file - f = open(cachedname,'wb') - f.write(join(code, LINEEND)+LINEEND) - f.close() - if rl_config.verbose: - print 'cached image as %s' % cachedname - - -def preProcessImages(spec): - """Preprocesses one or more image files. - - Accepts either a filespec ('C:\mydir\*.jpg') or a list - of image filenames, crunches them all to save time. Run this - to save huge amounts of time when repeatedly building image - documents.""" - - import types, glob - - if type(spec) is types.StringType: - filelist = glob.glob(spec) - else: #list or tuple OK - filelist = spec - - for filename in filelist: - if cachedImageExists(filename): - if rl_config.verbose: - print 'cached version of %s already exists' % filename - else: - cacheImageFile(filename) - - -def cachedImageExists(filename): - """Determines if a cached image already exists for a given file. - - Determines if a cached image exists which has the same name - and equal or newer date to the given file.""" - cachedname = os.path.splitext(filename)[0] + '.a85' - if os.path.isfile(cachedname): - #see if it is newer - original_date = os.stat(filename)[8] - cached_date = os.stat(cachedname)[8] - if original_date > cached_date: - return 0 - else: - return 1 - else: - return 0 - - -############################################################## -# -# PDF Helper functions -# -############################################################## - -try: - from _rl_accel import escapePDF, _instanceEscapePDF - _escape = escapePDF -except ImportError: - try: - from reportlab.lib._rl_accel import escapePDF, _instanceEscapePDF - _escape = escapePDF - except ImportError: - _instanceEscapePDF=None - if rl_config.sys_version>='2.1': - _ESCAPEDICT={} - for c in range(0,256): - if c<32 or c>=127: - _ESCAPEDICT[chr(c)]= '\\%03o' % c - elif c in (ord('\\'),ord('('),ord(')')): - _ESCAPEDICT[chr(c)] = '\\'+chr(c) - else: - _ESCAPEDICT[chr(c)] = chr(c) - del c - #Michael Hudson donated this - def _escape(s): - return join(map(lambda c, d=_ESCAPEDICT: d[c],s),'') - else: - def _escape(s): - """Escapes some PDF symbols (in fact, parenthesis). - PDF escapes are almost like Python ones, but brackets - need slashes before them too. Uses Python's repr function - and chops off the quotes first.""" - s = repr(s)[1:-1] - s = replace(s, '(','\(') - s = replace(s, ')','\)') - return s - -def _normalizeLineEnds(text,desired=LINEEND): - """Normalizes different line end character(s). - - Ensures all instances of CR, LF and CRLF end up as - the specified one.""" - unlikely = '\000\001\002\003' - text = replace(text, '\015\012', unlikely) - text = replace(text, '\015', unlikely) - text = replace(text, '\012', unlikely) - text = replace(text, unlikely, desired) - return text - - -def _AsciiHexEncode(input): - """Encodes input using ASCII-Hex coding. - - This is a verbose encoding used for binary data within - a PDF file. One byte binary becomes two bytes of ASCII. - Helper function used by images.""" - output = getStringIO() - for char in input: - output.write('%02x' % ord(char)) - output.write('>') - return output.getvalue() - - -def _AsciiHexDecode(input): - """Decodes input using ASCII-Hex coding. - - Not used except to provide a test of the inverse function.""" - - #strip out all whitespace - stripped = join(split(input),'') - assert stripped[-1] == '>', 'Invalid terminator for Ascii Hex Stream' - stripped = stripped[:-1] #chop off terminator - assert len(stripped) % 2 == 0, 'Ascii Hex stream has odd number of bytes' - - i = 0 - output = getStringIO() - while i < len(stripped): - twobytes = stripped[i:i+2] - output.write(chr(eval('0x'+twobytes))) - i = i + 2 - return output.getvalue() - - -if 1: # for testing always define this - def _AsciiBase85EncodePYTHON(input): - """Encodes input using ASCII-Base85 coding. - - This is a compact encoding used for binary data within - a PDF file. Four bytes of binary data become five bytes of - ASCII. This is the default method used for encoding images.""" - outstream = getStringIO() - # special rules apply if not a multiple of four bytes. - whole_word_count, remainder_size = divmod(len(input), 4) - cut = 4 * whole_word_count - body, lastbit = input[0:cut], input[cut:] - - for i in range(whole_word_count): - offset = i*4 - b1 = ord(body[offset]) - b2 = ord(body[offset+1]) - b3 = ord(body[offset+2]) - b4 = ord(body[offset+3]) - - if b1<128: - num = (((((b1<<8)|b2)<<8)|b3)<<8)|b4 - else: - num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4 - - if num == 0: - #special case - outstream.write('z') - else: - #solve for five base-85 numbers - temp, c5 = divmod(num, 85) - temp, c4 = divmod(temp, 85) - temp, c3 = divmod(temp, 85) - c1, c2 = divmod(temp, 85) - assert ((85**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5 == num, 'dodgy code!' - outstream.write(chr(c1+33)) - outstream.write(chr(c2+33)) - outstream.write(chr(c3+33)) - outstream.write(chr(c4+33)) - outstream.write(chr(c5+33)) - - # now we do the final bit at the end. I repeated this separately as - # the loop above is the time-critical part of a script, whereas this - # happens only once at the end. - - #encode however many bytes we have as usual - if remainder_size > 0: - while len(lastbit) < 4: - lastbit = lastbit + '\000' - b1 = ord(lastbit[0]) - b2 = ord(lastbit[1]) - b3 = ord(lastbit[2]) - b4 = ord(lastbit[3]) - - num = 16777216L * b1 + 65536 * b2 + 256 * b3 + b4 - - #solve for c1..c5 - temp, c5 = divmod(num, 85) - temp, c4 = divmod(temp, 85) - temp, c3 = divmod(temp, 85) - c1, c2 = divmod(temp, 85) - - #print 'encoding: %d %d %d %d -> %d -> %d %d %d %d %d' % ( - # b1,b2,b3,b4,num,c1,c2,c3,c4,c5) - lastword = chr(c1+33) + chr(c2+33) + chr(c3+33) + chr(c4+33) + chr(c5+33) - #write out most of the bytes. - outstream.write(lastword[0:remainder_size + 1]) - - #terminator code for ascii 85 - outstream.write('~>') - return outstream.getvalue() - - def _AsciiBase85DecodePYTHON(input): - """Decodes input using ASCII-Base85 coding. - - This is not used - Acrobat Reader decodes for you - - but a round trip is essential for testing.""" - outstream = getStringIO() - #strip all whitespace - stripped = join(split(input),'') - #check end - assert stripped[-2:] == '~>', 'Invalid terminator for Ascii Base 85 Stream' - stripped = stripped[:-2] #chop off terminator - - #may have 'z' in it which complicates matters - expand them - stripped = replace(stripped,'z','!!!!!') - # special rules apply if not a multiple of five bytes. - whole_word_count, remainder_size = divmod(len(stripped), 5) - #print '%d words, %d leftover' % (whole_word_count, remainder_size) - #assert remainder_size <> 1, 'invalid Ascii 85 stream!' - cut = 5 * whole_word_count - body, lastbit = stripped[0:cut], stripped[cut:] - - for i in range(whole_word_count): - offset = i*5 - c1 = ord(body[offset]) - 33 - c2 = ord(body[offset+1]) - 33 - c3 = ord(body[offset+2]) - 33 - c4 = ord(body[offset+3]) - 33 - c5 = ord(body[offset+4]) - 33 - - num = ((85L**4) * c1) + ((85**3) * c2) + ((85**2) * c3) + (85*c4) + c5 - - temp, b4 = divmod(num,256) - temp, b3 = divmod(temp,256) - b1, b2 = divmod(temp, 256) - - assert num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!' - outstream.write(chr(b1)) - outstream.write(chr(b2)) - outstream.write(chr(b3)) - outstream.write(chr(b4)) - - #decode however many bytes we have as usual - if remainder_size > 0: - while len(lastbit) < 5: - lastbit = lastbit + '!' - c1 = ord(lastbit[0]) - 33 - c2 = ord(lastbit[1]) - 33 - c3 = ord(lastbit[2]) - 33 - c4 = ord(lastbit[3]) - 33 - c5 = ord(lastbit[4]) - 33 - num = (((85*c1+c2)*85+c3)*85+c4)*85L + (c5 - +(0,0,0xFFFFFF,0xFFFF,0xFF)[remainder_size]) - temp, b4 = divmod(num,256) - temp, b3 = divmod(temp,256) - b1, b2 = divmod(temp, 256) - assert num == 16777216 * b1 + 65536 * b2 + 256 * b3 + b4, 'dodgy code!' - #print 'decoding: %d %d %d %d %d -> %d -> %d %d %d %d' % ( - # c1,c2,c3,c4,c5,num,b1,b2,b3,b4) - - #the last character needs 1 adding; the encoding loses - #data by rounding the number to x bytes, and when - #divided repeatedly we get one less - if remainder_size == 2: - lastword = chr(b1) - elif remainder_size == 3: - lastword = chr(b1) + chr(b2) - elif remainder_size == 4: - lastword = chr(b1) + chr(b2) + chr(b3) - else: - lastword = '' - outstream.write(lastword) - - #terminator code for ascii 85 - return outstream.getvalue() - -try: - from _rl_accel import _AsciiBase85Encode # builtin or on the path -except ImportError: - try: - from reportlab.lib._rl_accel import _AsciiBase85Encode # where we think it should be - except ImportError: - _AsciiBase85Encode = _AsciiBase85EncodePYTHON - -try: - from _rl_accel import _AsciiBase85Decode # builtin or on the path -except ImportError: - try: - from reportlab.lib._rl_accel import _AsciiBase85Decode # where we think it should be - except ImportError: - _AsciiBase85Decode = _AsciiBase85DecodePYTHON - -def _wrap(input, columns=60): - "Wraps input at a given column size by inserting LINEEND characters." - - output = [] - length = len(input) - i = 0 - pos = columns * i - while pos < length: - output.append(input[pos:pos+columns]) - i = i + 1 - pos = columns * i - - return join(output, LINEEND) - - -######################################################################### -# -# JPEG processing code - contributed by Eric Johnson -# -######################################################################### - -# Read data from the JPEG file. We should probably be using PIL to -# get this information for us -- but this way is more fun! -# Returns (width, height, color components) as a triple -# This is based on Thomas Merz's code from GhostScript (viewjpeg.ps) -def readJPEGInfo(image): - "Read width, height and number of components from open JPEG file." - - import struct - - #Acceptable JPEG Markers: - # SROF0=baseline, SOF1=extended sequential or SOF2=progressive - validMarkers = [0xC0, 0xC1, 0xC2] - - #JPEG markers without additional parameters - noParamMarkers = \ - [ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01 ] - - #Unsupported JPEG Markers - unsupportedMarkers = \ - [ 0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF ] - - #read JPEG marker segments until we find SOFn marker or EOF - done = 0 - while not done: - x = struct.unpack('B', image.read(1)) - if x[0] == 0xFF: #found marker - x = struct.unpack('B', image.read(1)) - #print "Marker: ", '%0.2x' % x[0] - #check marker type is acceptable and process it - if x[0] in validMarkers: - image.seek(2, 1) #skip segment length - x = struct.unpack('B', image.read(1)) #data precision - if x[0] != 8: - raise 'PDFError', ' JPEG must have 8 bits per component' - y = struct.unpack('BB', image.read(2)) - height = (y[0] << 8) + y[1] - y = struct.unpack('BB', image.read(2)) - width = (y[0] << 8) + y[1] - y = struct.unpack('B', image.read(1)) - color = y[0] - return width, height, color - done = 1 - elif x[0] in unsupportedMarkers: - raise 'PDFError', ' Unsupported JPEG marker: %0.2x' % x[0] - elif x[0] not in noParamMarkers: - #skip segments with parameters - #read length and skip the data - x = struct.unpack('BB', image.read(2)) - image.seek( (x[0] << 8) + x[1] - 2, 1) - -class _fusc: - def __init__(self,k, n): - assert k, 'Argument k should be a non empty string' - self._k = k - self._klen = len(k) - self._n = int(n) or 7 - - def encrypt(self,s): - return self.__rotate(_AsciiBase85Encode(''.join(map(chr,self.__fusc(map(ord,s))))),self._n) - - def decrypt(self,s): - return ''.join(map(chr,self.__fusc(map(ord,_AsciiBase85Decode(self.__rotate(s,-self._n)))))) - - def __rotate(self,s,n): - l = len(s) - if n<0: n = l+n - n %= l - if not n: return s - return s[-n:]+s[:l-n] - - def __fusc(self,s): - slen = len(s) - return map(lambda x,y: x ^ y,s,map(ord,((int(slen/self._klen)+1)*self._k)[:slen])) diff --git a/bin/reportlab/pdfbase/ttfonts.py b/bin/reportlab/pdfbase/ttfonts.py deleted file mode 100644 index d4f67a74239..00000000000 --- a/bin/reportlab/pdfbase/ttfonts.py +++ /dev/null @@ -1,1048 +0,0 @@ -#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/pdfbase/ttfonts.py -"""TrueType font support - -This defines classes to represent TrueType fonts. They know how to calculate -their own width and how to write themselves into PDF files. They support -subsetting and embedding and can represent all 16-bit Unicode characters. - -Note on dynamic fonts ---------------------- - -Usually a Font in ReportLab corresponds to a fixed set of PDF objects (Font, -FontDescriptor, Encoding). But with dynamic font subsetting a single TTFont -will result in a number of Font/FontDescriptor/Encoding object sets, and the -contents of those will depend on the actual characters used for printing. - -To support dynamic font subsetting a concept of "dynamic font" was introduced. -Dynamic Fonts have a _dynamicFont attribute set to 1. Since other Font object -may lack a this attribute, you should use constructs like - - if getattr(font, '_dynamicFont', 0): - # dynamic font - else: - # traditional static font - -Dynamic fonts have the following additional functions: - - def splitString(self, text, doc): - '''Splits text into a number of chunks, each of which belongs to a - single subset. Returns a list of tuples (subset, string). Use - subset numbers with getSubsetInternalName. Doc is used to identify - a document so that different documents may have different dynamically - constructed subsets.''' - - def getSubsetInternalName(self, subset, doc): - '''Returns the name of a PDF Font object corresponding to a given - subset of this dynamic font. Use this function instead of - PDFDocument.getInternalFontName.''' - -You must never call PDFDocument.getInternalFontName for dynamic fonts. - -If you have a traditional static font, mapping to PDF text output operators -is simple: - - '%s 14 Tf (%s) Tj' % (getInternalFontName(psfontname), text) - -If you have a dynamic font, use this instead: - - for subset, chunk in font.splitString(text, doc): - '%s 14 Tf (%s) Tj' % (font.getSubsetInternalName(subset, doc), chunk) - -(Tf is a font setting operator and Tj is a text ouput operator. You should -also escape invalid characters in Tj argument, see TextObject._formatText. -Oh, and that 14 up there is font size.) - -Canvas and TextObject have special support for dynamic fonts. -""" - -__version__ = '$Id$' - -import string -from types import StringType -from struct import pack, unpack -from cStringIO import StringIO -from reportlab.pdfbase import pdfmetrics, pdfdoc - -def _L2U32(L): - return unpack('l',pack('L',L))[0] - -class TTFError(pdfdoc.PDFError): - "TrueType font exception" - pass - -# -# Helpers -# - -from codecs import utf_8_encode, utf_8_decode, latin_1_decode -parse_utf8=lambda x, decode=utf_8_decode: map(ord,decode(x)[0]) -parse_latin1 = lambda x, decode=latin_1_decode: map(ord,decode(x)[0]) -def latin1_to_utf8(text): - "helper to convert when needed from latin input" - return utf_8_encode(latin_1_decode(text)[0])[0] - -def makeToUnicodeCMap(fontname, subset): - """Creates a ToUnicode CMap for a given subset. See Adobe - _PDF_Reference (ISBN 0-201-75839-3) for more information.""" - cmap = [ - "/CIDInit /ProcSet findresource begin", - "12 dict begin", - "begincmap", - "/CIDSystemInfo", - "<< /Registry (%s)" % fontname, - "/Ordering (%s)" % fontname, - "/Supplement 0", - ">> def", - "/CMapName /%s def" % fontname, - "/CMapType 2 def", - "1 begincodespacerange", - "<00> <%02X>" % (len(subset) - 1), - "endcodespacerange", - "%d beginbfchar" % len(subset) - ] + map(lambda n, subset=subset: "<%02X> <%04X>" % (n, subset[n]), - range(len(subset))) + [ - "endbfchar", - "endcmap", - "CMapName currentdict /CMap defineresource pop", - "end", - "end" - ] - return string.join(cmap, "\n") - -def splice(stream, offset, value): - """Splices the given value into stream at the given offset and - returns the resulting stream (the original is unchanged)""" - return stream[:offset] + value + stream[offset + len(value):] - -def _set_ushort(stream, offset, value): - """Writes the given unsigned short value into stream at the given - offset and returns the resulting stream (the original is unchanged)""" - return splice(stream, offset, pack(">H", value)) - -import sys -try: - import _rl_accel -except ImportError: - try: - from reportlab.lib import _rl_accel - except ImportError: - _rl_accel = None - -try: - hex32 = _rl_accel.hex32 -except: - def hex32(i): - return '0X%8.8X' % (long(i)&0xFFFFFFFFL) -try: - add32 = _rl_accel.add32 -except: - if sys.hexversion>=0x02030000: - def add32(x, y): - "Calculate (x + y) modulo 2**32" - return _L2U32((long(x)+y) & 0xffffffffL) - else: - def add32(x, y): - "Calculate (x + y) modulo 2**32" - lo = (x & 0xFFFF) + (y & 0xFFFF) - hi = (x >> 16) + (y >> 16) + (lo >> 16) - return (hi << 16) | (lo & 0xFFFF) - -try: - calcChecksum = _rl_accel.calcChecksum -except: - def calcChecksum(data): - """Calculates PDF-style checksums""" - if len(data)&3: data = data + (4-(len(data)&3))*"\0" - sum = 0 - for n in unpack(">%dl" % (len(data)>>2), data): - sum = add32(sum,n) - return sum -del _rl_accel, sys -# -# TrueType font handling -# - -GF_ARG_1_AND_2_ARE_WORDS = 1 << 0 -GF_ARGS_ARE_XY_VALUES = 1 << 1 -GF_ROUND_XY_TO_GRID = 1 << 2 -GF_WE_HAVE_A_SCALE = 1 << 3 -GF_RESERVED = 1 << 4 -GF_MORE_COMPONENTS = 1 << 5 -GF_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6 -GF_WE_HAVE_A_TWO_BY_TWO = 1 << 7 -GF_WE_HAVE_INSTRUCTIONS = 1 << 8 -GF_USE_MY_METRICS = 1 << 9 -GF_OVERLAP_COMPOUND = 1 << 10 -GF_SCALED_COMPONENT_OFFSET = 1 << 11 -GF_UNSCALED_COMPONENT_OFFSET = 1 << 12 - -def TTFOpenFile(fn): - '''Opens a TTF file possibly after searching TTFSearchPath - returns (filename,file) - ''' - from reportlab.lib.utils import rl_isfile, open_for_read - try: - f = open_for_read(fn,'rb') - return fn, f - except IOError: - import os - if not os.path.isabs(fn): - from reportlab import rl_config - for D in rl_config.TTFSearchPath: - tfn = os.path.join(D,fn) - if rl_isfile(tfn): - f = open_for_read(tfn,'rb') - return tfn, f - raise TTFError('Can\'t open file "%s"' % fn) - -class TTFontParser: - "Basic TTF file parser" - - def __init__(self, file, validate=0): - """Loads and parses a TrueType font file. file can be a filename or a - file object. If validate is set to a false values, skips checksum - validation. This can save time, especially if the font is large. - """ - - # Open the file - if type(file) is StringType: - self.filename, file = TTFOpenFile(file) - else: - self.filename = '(ttf)' - - self._ttf_data = file.read() - self._pos = 0 - - # Read header - try: - version = self.read_ulong() - if version == 0x4F54544F: - raise TTFError, 'OpenType fonts with PostScript outlines are not supported' - if version != 0x00010000 and version != 0x74727565: - raise TTFError, 'Not a TrueType font' - except: - raise TTFError, 'Not a TrueType font' - - try: - self.numTables = self.read_ushort() - self.searchRange = self.read_ushort() - self.entrySelector = self.read_ushort() - self.rangeShift = self.read_ushort() - - # Read table directory - self.table = {} - self.tables = [] - for n in range(self.numTables): - record = {} - record['tag'] = self.read_tag() - record['checksum'] = self.read_ulong() - record['offset'] = self.read_ulong() - record['length'] = self.read_ulong() - self.tables.append(record) - self.table[record['tag']] = record - except: - raise TTFError, 'Corrupt TrueType font file' - - if not validate: - return - - # Check the checksums for the whole file - checkSum = calcChecksum(self._ttf_data) - if add32(_L2U32(0xB1B0AFBAL), -checkSum) != 0: - raise TTFError, 'Invalid checksum %s len: %d &3: %d' % (hex32(checkSum),len(self._ttf_data),(len(self._ttf_data)&3)) - - # Check the checksums for all tables - for t in self.tables: - table = self.get_chunk(t['offset'], t['length']) - checkSum = calcChecksum(table) - if t['tag'] == 'head': - adjustment = unpack('>l', table[8:8+4])[0] - checkSum = add32(checkSum, -adjustment) - if t['checksum'] != checkSum: - raise TTFError, 'Invalid checksum %s table: %s' % (hex32(checkSum),t['tag']) - - def get_table_pos(self, tag): - "Returns the offset and size of a given TTF table." - offset = self.table[tag]['offset'] - length = self.table[tag]['length'] - return (offset, length) - - def seek(self, pos): - "Moves read pointer to a given offset in file." - self._pos = pos - - def skip(self, delta): - "Skip the given number of bytes." - self._pos = self._pos + delta - - def seek_table(self, tag, offset_in_table = 0): - """Moves read pointer to the given offset within a given table and - returns absolute offset of that position in the file.""" - self._pos = self.get_table_pos(tag)[0] + offset_in_table - return self._pos - - def read_tag(self): - "Read a 4-character tag" - self._pos = self._pos + 4 - return self._ttf_data[self._pos - 4:self._pos] - - def read_ushort(self): - "Reads an unsigned short" - self._pos = self._pos + 2 - return (ord(self._ttf_data[self._pos - 2]) << 8) + \ - (ord(self._ttf_data[self._pos - 1])) - - def read_ulong(self): - "Reads an unsigned long" - self._pos = self._pos + 4 - return unpack('>l',self._ttf_data[self._pos - 4:self._pos])[0] - - def read_short(self): - "Reads a signed short" - us = self.read_ushort() - if us >= 0x8000: - return us - 0x10000 - else: - return us - - def get_ushort(self, pos): - "Return an unsigned short at given position" - return (ord(self._ttf_data[pos]) << 8) + \ - (ord(self._ttf_data[pos + 1])) - - def get_ulong(self, pos): - "Return an unsigned long at given position" - return unpack('>l',self._ttf_data[pos:pos+4])[0] - - def get_chunk(self, pos, length): - "Return a chunk of raw data at given position" - return self._ttf_data[pos:pos+length] - - def get_table(self, tag): - "Return the given TTF table" - pos, length = self.get_table_pos(tag) - return self._ttf_data[pos:pos+length] - - -class TTFontMaker: - "Basic TTF file generator" - - def __init__(self): - "Initializes the generator." - self.tables = {} - - def add(self, tag, data): - "Adds a table to the TTF file." - if tag == 'head': - data = splice(data, 8, '\0\0\0\0') - self.tables[tag] = data - - def makeStream(self): - "Finishes the generation and returns the TTF file as a string" - stm = StringIO() - - numTables = len(self.tables) - searchRange = 1 - entrySelector = 0 - while searchRange * 2 <= numTables: - searchRange = searchRange * 2 - entrySelector = entrySelector + 1 - searchRange = searchRange * 16 - rangeShift = numTables * 16 - searchRange - - # Header - stm.write(pack(">lHHHH", 0x00010000, numTables, searchRange, - entrySelector, rangeShift)) - - # Table directory - tables = self.tables.items() - tables.sort() # XXX is this the correct order? - offset = 12 + numTables * 16 - for tag, data in tables: - if tag == 'head': - head_start = offset - checksum = calcChecksum(data) - stm.write(tag) - stm.write(pack(">LLL", checksum, offset, len(data))) - paddedLength = (len(data)+3)&~3 - offset = offset + paddedLength - - # Table data - for tag, data in tables: - data = data + "\0\0\0" - stm.write(data[:len(data)&~3]) - - checksum = calcChecksum(stm.getvalue()) - checksum = add32(_L2U32(0xB1B0AFBAL), -checksum) - stm.seek(head_start + 8) - stm.write(pack('>L', checksum)) - - return stm.getvalue() - -class TTFontFile(TTFontParser): - "TTF file parser and generator" - - def __init__(self, file, charInfo=1, validate=0): - """Loads and parses a TrueType font file. - - file can be a filename or a file object. If validate is set to a false - values, skips checksum validation. This can save time, especially if - the font is large. See TTFontFile.extractInfo for more information. - """ - TTFontParser.__init__(self, file, validate=validate) - self.extractInfo(charInfo) - - def extractInfo(self, charInfo=1): - """Extract typographic information from the loaded font file. - - The following attributes will be set: - name - PostScript font name - flags - Font flags - ascent - Typographic ascender in 1/1000ths of a point - descent - Typographic descender in 1/1000ths of a point - capHeight - Cap height in 1/1000ths of a point (0 if not available) - bbox - Glyph bounding box [l,t,r,b] in 1/1000ths of a point - italicAngle - Italic angle in degrees ccw - stemV - stem weight in 1/1000ths of a point (approximate) - If charInfo is true, the following will also be set: - defaultWidth - default glyph width in 1/1000ths of a point - charWidths - dictionary of character widths for every supported - UCS character code - - This will only work if the font has a Unicode cmap (platform 3, - encoding 1, format 4 or platform 0 any encoding format 4). Setting - charInfo to false avoids this requirement. - """ - # name - Naming table - name_offset = self.seek_table("name") - format = self.read_ushort() - if format != 0: - raise TTFError, "Unknown name table format (%d)" % format - numRecords = self.read_ushort() - string_data_offset = name_offset + self.read_ushort() - names = {1:None,2:None,3:None,4:None,6:None} - K = names.keys() - nameCount = len(names) - for i in range(numRecords): - platformId = self.read_ushort() - encodingId = self.read_ushort() - languageId = self.read_ushort() - nameId = self.read_ushort() - length = self.read_ushort() - offset = self.read_ushort() - if nameId not in K: continue - N = None - if platformId == 3 and encodingId == 1 and languageId == 0x409: # Microsoft, Unicode, US English, PS Name - self.seek(string_data_offset + offset) - if length % 2 != 0: - raise TTFError, "PostScript name is UTF-16BE string of odd length" - length /= 2 - N = [] - A = N.append - while length > 0: - char = self.read_ushort() - A(chr(char)) - length -= 1 - N = ''.join(N) - elif platformId == 1 and encodingId == 0 and languageId == 0: # Macintosh, Roman, English, PS Name - # According to OpenType spec, if PS name exists, it must exist - # both in MS Unicode and Macintosh Roman formats. Apparently, - # you can find live TTF fonts which only have Macintosh format. - N = self.get_chunk(string_data_offset + offset, length) - if N and names[nameId]==None: - names[nameId] = N - nameCount -= 1 - if nameCount==0: break - psName = names[6] - if not psName: - raise TTFError, "Could not find PostScript font name" - for c in psName: - oc = ord(c) - if oc<33 or oc>126 or c in ('[', ']', '(', ')', '{', '}', '<', '>', '/', '%'): - raise TTFError, "psName contains invalid character '%s' ie U+%04X" % (c,ord(c)) - self.name = psName - self.familyName = names[1] or psName - self.styleName = names[2] or 'Regular' - self.fullName = names[4] or psName - self.uniqueFontID = names[3] or psName - - # head - Font header table - self.seek_table("head") - ver_maj, ver_min = self.read_ushort(), self.read_ushort() - if ver_maj != 1: - raise TTFError, 'Unknown head table version %d.%04x' % (ver_maj, ver_min) - self.skip(8) - magic = self.read_ulong() - if magic != 0x5F0F3CF5: - raise TTFError, 'Invalid head table magic %04x' % magic - self.skip(2) - unitsPerEm = self.read_ushort() - scale = lambda x, unitsPerEm=unitsPerEm: x * 1000 / unitsPerEm - self.skip(16) - xMin = self.read_short() - yMin = self.read_short() - xMax = self.read_short() - yMax = self.read_short() - self.bbox = map(scale, [xMin, yMin, xMax, yMax]) - self.skip(3*2) - indexToLocFormat = self.read_ushort() - glyphDataFormat = self.read_ushort() - - # OS/2 - OS/2 and Windows metrics table - # (needs data from head table) - if self.table.has_key("OS/2"): - self.seek_table("OS/2") - version = self.read_ushort() - self.skip(2) - usWeightClass = self.read_ushort() - self.skip(2) - fsType = self.read_ushort() - if fsType == 0x0002 or (fsType & 0x0300) != 0: - raise TTFError, 'Font does not allow subsetting/embedding (%04X)' % fsType - self.skip(11*2 + 10 + 4*4 + 4 + 3*2) - sTypoAscender = self.read_short() - sTypoDescender = self.read_short() - self.ascent = scale(sTypoAscender) # XXX: for some reason it needs to be multiplied by 1.24--1.28 - self.descent = scale(sTypoDescender) - - if version > 1: - self.skip(3*2 + 2*4 + 2) - sCapHeight = self.read_short() - self.capHeight = scale(sCapHeight) - else: - self.capHeight = self.ascent - else: - # Microsoft TTFs require an OS/2 table; Apple ones do not. Try to - # cope. The data is not very important anyway. - usWeightClass = 500 - self.ascent = scale(yMax) - self.descent = scale(yMin) - self.capHeight = self.ascent - - # There's no way to get stemV from a TTF file short of analyzing actual outline data - # This fuzzy formula is taken from pdflib sources, but we could just use 0 here - self.stemV = 50 + int((usWeightClass / 65.0) ** 2) - - # post - PostScript table - # (needs data from OS/2 table) - self.seek_table("post") - ver_maj, ver_min = self.read_ushort(), self.read_ushort() - if ver_maj not in (1, 2, 3, 4): - # Adobe/MS documents 1, 2, 2.5, 3; Apple also has 4. - # From Apple docs it seems that we do not need to care - # about the exact version, so if you get this error, you can - # try to remove this check altogether. - raise TTFError, 'Unknown post table version %d.%04x' % (ver_maj, ver_min) - self.italicAngle = self.read_short() + self.read_ushort() / 65536.0 - self.skip(2*2) - isFixedPitch = self.read_ulong() - - self.flags = FF_SYMBOLIC # All fonts that contain characters - # outside the original Adobe character - # set are considered "symbolic". - if self.italicAngle != 0: - self.flags = self.flags | FF_ITALIC - if usWeightClass >= 600: # FW_REGULAR == 500, FW_SEMIBOLD == 600 - self.flags = self.flags | FF_FORCEBOLD - if isFixedPitch: - self.flags = self.flags | FF_FIXED - # XXX: FF_SERIF? FF_SCRIPT? FF_ALLCAP? FF_SMALLCAP? - - # hhea - Horizontal header table - self.seek_table("hhea") - ver_maj, ver_min = self.read_ushort(), self.read_ushort() - if ver_maj != 1: - raise TTFError, 'Unknown hhea table version %d.%04x' % (ver_maj, ver_min) - self.skip(28) - metricDataFormat = self.read_ushort() - if metricDataFormat != 0: - raise TTFError, 'Unknown horizontal metric data format (%d)' % metricDataFormat - numberOfHMetrics = self.read_ushort() - if numberOfHMetrics == 0: - raise TTFError, 'Number of horizontal metrics is 0' - - # maxp - Maximum profile table - self.seek_table("maxp") - ver_maj, ver_min = self.read_ushort(), self.read_ushort() - if ver_maj != 1: - raise TTFError, 'Unknown maxp table version %d.%04x' % (ver_maj, ver_min) - numGlyphs = self.read_ushort() - - if not charInfo: - self.charToGlyph = None - self.defaultWidth = None - self.charWidths = None - return - - if glyphDataFormat != 0: - raise TTFError, 'Unknown glyph data format (%d)' % glyphDataFormat - - # cmap - Character to glyph index mapping table - cmap_offset = self.seek_table("cmap") - self.skip(2) - cmapTableCount = self.read_ushort() - unicode_cmap_offset = None - for n in range(cmapTableCount): - platformID = self.read_ushort() - encodingID = self.read_ushort() - offset = self.read_ulong() - if platformID == 3 and encodingID == 1: # Microsoft, Unicode - format = self.get_ushort(cmap_offset + offset) - if format == 4: - unicode_cmap_offset = cmap_offset + offset - break - elif platformID == 0: # Unicode -- assume all encodings are compatible - format = self.get_ushort(cmap_offset + offset) - if format == 4: - unicode_cmap_offset = cmap_offset + offset - break - if unicode_cmap_offset is None: - raise TTFError, 'Font does not have cmap for Unicode (platform 3, encoding 1, format 4 or platform 0 any encoding format 4)' - self.seek(unicode_cmap_offset + 2) - length = self.read_ushort() - limit = unicode_cmap_offset + length - self.skip(2) - segCount = self.read_ushort() / 2 - self.skip(6) - endCount = map(lambda x, self=self: self.read_ushort(), range(segCount)) - self.skip(2) - startCount = map(lambda x, self=self: self.read_ushort(), range(segCount)) - idDelta = map(lambda x, self=self: self.read_short(), range(segCount)) - idRangeOffset_start = self._pos - idRangeOffset = map(lambda x, self=self: self.read_ushort(), range(segCount)) - - # Now it gets tricky. - glyphToChar = {} - charToGlyph = {} - for n in range(segCount): - for unichar in range(startCount[n], endCount[n] + 1): - if idRangeOffset[n] == 0: - glyph = (unichar + idDelta[n]) & 0xFFFF - else: - offset = (unichar - startCount[n]) * 2 + idRangeOffset[n] - offset = idRangeOffset_start + 2 * n + offset - if offset >= limit: - # workaround for broken fonts (like Thryomanes) - glyph = 0 - else: - glyph = self.get_ushort(offset) - if glyph != 0: - glyph = (glyph + idDelta[n]) & 0xFFFF - charToGlyph[unichar] = glyph - if glyphToChar.has_key(glyph): - glyphToChar[glyph].append(unichar) - else: - glyphToChar[glyph] = [unichar] - self.charToGlyph = charToGlyph - - # hmtx - Horizontal metrics table - # (needs data from hhea, maxp, and cmap tables) - self.seek_table("hmtx") - aw = None - self.charWidths = {} - self.hmetrics = [] - for glyph in range(numberOfHMetrics): - # advance width and left side bearing. lsb is actually signed - # short, but we don't need it anyway (except for subsetting) - aw, lsb = self.read_ushort(), self.read_ushort() - self.hmetrics.append((aw, lsb)) - aw = scale(aw) - if glyph == 0: - self.defaultWidth = aw - if glyphToChar.has_key(glyph): - for char in glyphToChar[glyph]: - self.charWidths[char] = aw - for glyph in range(numberOfHMetrics, numGlyphs): - # the rest of the table only lists advance left side bearings. - # so we reuse aw set by the last iteration of the previous loop - lsb = self.read_ushort() - self.hmetrics.append((aw, lsb)) - if glyphToChar.has_key(glyph): - for char in glyphToChar[glyph]: - self.charWidths[char] = aw - - # loca - Index to location - self.seek_table('loca') - self.glyphPos = [] - if indexToLocFormat == 0: - for n in range(numGlyphs + 1): - self.glyphPos.append(self.read_ushort() << 1) - elif indexToLocFormat == 1: - for n in range(numGlyphs + 1): - self.glyphPos.append(self.read_ulong()) - else: - raise TTFError, 'Unknown location table format (%d)' % indexToLocFormat - - # Subsetting - - def makeSubset(self, subset): - """Create a subset of a TrueType font""" - output = TTFontMaker() - - # Build a mapping of glyphs in the subset to glyph numbers in - # the original font. Also build a mapping of UCS codes to - # glyph values in the new font. - - # Start with 0 -> 0: "missing character" - glyphMap = [0] # new glyph index -> old glyph index - glyphSet = {0:0} # old glyph index -> new glyph index - codeToGlyph = {} # unicode -> new glyph index - for code in subset: - if self.charToGlyph.has_key(code): - originalGlyphIdx = self.charToGlyph[code] - else: - originalGlyphIdx = 0 - if not glyphSet.has_key(originalGlyphIdx): - glyphSet[originalGlyphIdx] = len(glyphMap) - glyphMap.append(originalGlyphIdx) - codeToGlyph[code] = glyphSet[originalGlyphIdx] - - # Also include glyphs that are parts of composite glyphs - start = self.get_table_pos('glyf')[0] - n = 0 - while n < len(glyphMap): - originalGlyphIdx = glyphMap[n] - glyphPos = self.glyphPos[originalGlyphIdx] - glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos - self.seek(start + glyphPos) - numberOfContours = self.read_short() - if numberOfContours < 0: - # composite glyph - self.skip(8) - flags = GF_MORE_COMPONENTS - while flags & GF_MORE_COMPONENTS: - flags = self.read_ushort() - glyphIdx = self.read_ushort() - if not glyphSet.has_key(glyphIdx): - glyphSet[glyphIdx] = len(glyphMap) - glyphMap.append(glyphIdx) - if flags & GF_ARG_1_AND_2_ARE_WORDS: - self.skip(4) - else: - self.skip(2) - if flags & GF_WE_HAVE_A_SCALE: - self.skip(2) - elif flags & GF_WE_HAVE_AN_X_AND_Y_SCALE: - self.skip(4) - elif flags & GF_WE_HAVE_A_TWO_BY_TWO: - self.skip(8) - n = n + 1 - - numGlyphs = n = len(glyphMap) - while n > 1 and self.hmetrics[n][0] == self.hmetrics[n - 1][0]: - n = n - 1 - numberOfHMetrics = n - - # The following tables are simply copied from the original - for tag in ('name', 'OS/2', 'cvt ', 'fpgm', 'prep'): - try: - output.add(tag, self.get_table(tag)) - except KeyError: - # Apparently some of the tables are optional (cvt, fpgm, prep). - # The lack of the required ones (name, OS/2) would have already - # been caught before. - pass - - # post - PostScript - post = "\x00\x03\x00\x00" + self.get_table('post')[4:16] + "\x00" * 16 - output.add('post', post) - - # hhea - Horizontal Header - hhea = self.get_table('hhea') - hhea = _set_ushort(hhea, 34, numberOfHMetrics) - output.add('hhea', hhea) - - # maxp - Maximum Profile - maxp = self.get_table('maxp') - maxp = _set_ushort(maxp, 4, numGlyphs) - output.add('maxp', maxp) - - # cmap - Character to glyph mapping - # XXX maybe use format 0 if possible, not 6? - entryCount = len(subset) - length = 10 + entryCount * 2 - cmap = [0, 1, # version, number of tables - 1, 0, 0,12, # platform, encoding, offset (hi,lo) - 6, length, 0, # format, length, language - 0, - entryCount] + \ - map(codeToGlyph.get, subset) - cmap = apply(pack, [">%dH" % len(cmap)] + cmap) - output.add('cmap', cmap) - - # hmtx - Horizontal Metrics - hmtx = [] - for n in range(numGlyphs): - originalGlyphIdx = glyphMap[n] - aw, lsb = self.hmetrics[originalGlyphIdx] - if n < numberOfHMetrics: - hmtx.append(aw) - hmtx.append(lsb) - hmtx = apply(pack, [">%dH" % len(hmtx)] + hmtx) - output.add('hmtx', hmtx) - - # glyf - Glyph data - glyphData = self.get_table('glyf') - offsets = [] - glyf = [] - pos = 0 - for n in range(numGlyphs): - offsets.append(pos) - originalGlyphIdx = glyphMap[n] - glyphPos = self.glyphPos[originalGlyphIdx] - glyphLen = self.glyphPos[originalGlyphIdx + 1] - glyphPos - data = glyphData[glyphPos:glyphPos+glyphLen] - # Fix references in composite glyphs - if glyphLen > 2 and unpack(">h", data[:2])[0] < 0: - # composite glyph - pos_in_glyph = 10 - flags = GF_MORE_COMPONENTS - while flags & GF_MORE_COMPONENTS: - flags = unpack(">H", data[pos_in_glyph:pos_in_glyph+2])[0] - glyphIdx = unpack(">H", data[pos_in_glyph+2:pos_in_glyph+4])[0] - data = _set_ushort(data, pos_in_glyph + 2, glyphSet[glyphIdx]) - pos_in_glyph = pos_in_glyph + 4 - if flags & GF_ARG_1_AND_2_ARE_WORDS: - pos_in_glyph = pos_in_glyph + 4 - else: - pos_in_glyph = pos_in_glyph + 2 - if flags & GF_WE_HAVE_A_SCALE: - pos_in_glyph = pos_in_glyph + 2 - elif flags & GF_WE_HAVE_AN_X_AND_Y_SCALE: - pos_in_glyph = pos_in_glyph + 4 - elif flags & GF_WE_HAVE_A_TWO_BY_TWO: - pos_in_glyph = pos_in_glyph + 8 - glyf.append(data) - pos = pos + glyphLen - if pos % 4 != 0: - padding = 4 - pos % 4 - glyf.append('\0' * padding) - pos = pos + padding - offsets.append(pos) - output.add('glyf', string.join(glyf, "")) - - # loca - Index to location - loca = [] - if (pos + 1) >> 1 > 0xFFFF: - indexToLocFormat = 1 # long format - for offset in offsets: - loca.append(offset) - loca = apply(pack, [">%dL" % len(loca)] + loca) - else: - indexToLocFormat = 0 # short format - for offset in offsets: - loca.append(offset >> 1) - loca = apply(pack, [">%dH" % len(loca)] + loca) - output.add('loca', loca) - - # head - Font header - head = self.get_table('head') - head = _set_ushort(head, 50, indexToLocFormat) - output.add('head', head) - - return output.makeStream() - - -# -# TrueType font embedding -# - -# PDF font flags (see PDF Reference Guide table 5.19) -FF_FIXED = 1 << 1-1 -FF_SERIF = 1 << 2-1 -FF_SYMBOLIC = 1 << 3-1 -FF_SCRIPT = 1 << 4-1 -FF_NONSYMBOLIC = 1 << 6-1 -FF_ITALIC = 1 << 7-1 -FF_ALLCAP = 1 << 17-1 -FF_SMALLCAP = 1 << 18-1 -FF_FORCEBOLD = 1 << 19-1 - -class TTFontFace(TTFontFile, pdfmetrics.TypeFace): - """TrueType typeface. - - Conceptually similar to a single byte typeface, but the glyphs are - identified by UCS character codes instead of glyph names.""" - - def __init__(self, filename, validate=0): - "Loads a TrueType font from filename." - pdfmetrics.TypeFace.__init__(self, None) - TTFontFile.__init__(self, filename, validate=validate) - - def getCharWidth(self, code): - "Returns the width of character U+" - return self.charWidths.get(code, self.defaultWidth) - - def addSubsetObjects(self, doc, fontname, subset): - """Generate a TrueType font subset and add it to the PDF document. - Returns a PDFReference to the new FontDescriptor object.""" - - fontFile = pdfdoc.PDFStream() - fontFile.content = self.makeSubset(subset) - fontFile.dictionary['Length1'] = len(fontFile.content) - if doc.compression: - fontFile.filters = [pdfdoc.PDFZCompress] - fontFileRef = doc.Reference(fontFile, 'fontFile:%s(%s)' % (self.filename, fontname)) - - flags = self.flags & ~ FF_NONSYMBOLIC - flags = flags | FF_SYMBOLIC - - fontDescriptor = pdfdoc.PDFDictionary({ - 'Type': '/FontDescriptor', - 'Ascent': self.ascent, - 'CapHeight': self.capHeight, - 'Descent': self.descent, - 'Flags': flags, - 'FontBBox': pdfdoc.PDFArray(self.bbox), - 'FontName': pdfdoc.PDFName(fontname), - 'ItalicAngle': self.italicAngle, - 'StemV': self.stemV, - 'FontFile2': fontFileRef, - }) - return doc.Reference(fontDescriptor, 'fontDescriptor:' + fontname) - -class TTEncoding: - """Encoding for TrueType fonts (always UTF-8). - - TTEncoding does not directly participate in PDF object creation, since - we need a number of different 8-bit encodings for every generated font - subset. TTFont itself cares about that.""" - - def __init__(self): - self.name = "UTF-8" - - -class TTFont: - """Represents a TrueType font. - - Its encoding is always UTF-8. - - Note: you cannot use the same TTFont object for different documents - at the same time. - - Example of usage: - - font = ttfonts.TTFont('PostScriptFontName', '/path/to/font.ttf') - pdfmetrics.registerFont(font) - - canvas.setFont('PostScriptFontName', size) - canvas.drawString(x, y, "Some text encoded in UTF-8") - """ - - class State: - def __init__(self): - self.assignments = {} - self.nextCode = 0 - self.subsets = [] - self.internalName = None - self.frozen = 0 - - def __init__(self, name, filename, validate=0): - """Loads a TrueType font from filename. - - If validate is set to a false values, skips checksum validation. This - can save time, especially if the font is large. - """ - self.fontName = name - self.face = TTFontFace(filename, validate=validate) - self.encoding = TTEncoding() - self._multiByte = 1 # We want our own stringwidth - self._dynamicFont = 1 # We want dynamic subsetting - self.state = {} - - def stringWidth(self, text, size): - "Calculate text width" - width = self.face.getCharWidth - w = 0 - for code in parse_utf8(text): - w = w + width(code) - return 0.001 * w * size - - def splitString(self, text, doc): - """Splits text into a number of chunks, each of which belongs to a - single subset. Returns a list of tuples (subset, string). Use subset - numbers with getSubsetInternalName. Doc is needed for distinguishing - subsets when building different documents at the same time.""" - try: state = self.state[doc] - except KeyError: state = self.state[doc] = TTFont.State() - curSet = -1 - cur = [] - results = [] - for code in parse_utf8(text): - if state.assignments.has_key(code): - n = state.assignments[code] - else: - if state.frozen: - raise pdfdoc.PDFError, "Font %s is already frozen, cannot add new character U+%04X" % (self.fontName, code) - n = state.nextCode - state.nextCode = state.nextCode + 1 - state.assignments[code] = n - if (n & 0xFF) == 0: - state.subsets.append([]) - state.subsets[n >> 8].append(code) - if (n >> 8) != curSet: - if cur: - results.append((curSet, string.join(map(chr, cur), ""))) - curSet = (n >> 8) - cur = [] - cur.append(n & 0xFF) - if cur: - results.append((curSet, string.join(map(chr, cur), ""))) - return results - - def getSubsetInternalName(self, subset, doc): - """Returns the name of a PDF Font object corresponding to a given - subset of this dynamic font. Use this function instead of - PDFDocument.getInternalFontName.""" - try: state = self.state[doc] - except KeyError: state = self.state[doc] = TTFont.State() - if subset < 0 or subset >= len(state.subsets): - raise IndexError, 'Subset %d does not exist in font %s' % (subset, self.fontName) - if state.internalName is None: - state.internalName = 'F%d' % (len(doc.fontMapping) + 1) - doc.fontMapping[self.fontName] = '/' + state.internalName - doc.delayedFonts.append(self) - return '/%s+%d' % (state.internalName, subset) - - def addObjects(self, doc): - """Makes one or more PDF objects to be added to the document. The - caller supplies the internal name to be used (typically F1, F2, ... in - sequence). - - This method creates a number of Font and FontDescriptor objects. Every - FontDescriptor is a (no more than) 256 character subset of the original - TrueType font.""" - try: state = self.state[doc] - except KeyError: state = self.state[doc] = TTFont.State() - state.frozen = 1 - for n in range(len(state.subsets)): - subset = state.subsets[n] - internalName = self.getSubsetInternalName(n, doc)[1:] - baseFontName = "SUBSET+%s+%d" % (self.face.name, n) - - pdfFont = pdfdoc.PDFTrueTypeFont() - pdfFont.__Comment__ = 'Font %s subset %d' % (self.fontName, n) - pdfFont.Name = internalName - pdfFont.BaseFont = baseFontName - - pdfFont.FirstChar = 0 - pdfFont.LastChar = len(subset) - 1 - - widths = map(self.face.getCharWidth, subset) - pdfFont.Widths = pdfdoc.PDFArray(widths) - - cmapStream = pdfdoc.PDFStream() - cmapStream.content = makeToUnicodeCMap(baseFontName, subset) - if doc.compression: - cmapStream.filters = [pdfdoc.PDFZCompress] - pdfFont.ToUnicode = doc.Reference(cmapStream, 'toUnicodeCMap:' + baseFontName) - - pdfFont.FontDescriptor = self.face.addSubsetObjects(doc, baseFontName, subset) - - # link it in - ref = doc.Reference(pdfFont, internalName) - fontDict = doc.idToObject['BasicFonts'].dict - fontDict[internalName] = pdfFont - del self.state[doc] diff --git a/bin/reportlab/pdfgen/__init__.py b/bin/reportlab/pdfgen/__init__.py deleted file mode 100644 index 9a2184f5f4a..00000000000 --- a/bin/reportlab/pdfgen/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#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/pdfgen/__init__.py -__version__=''' $Id$ ''' -__doc__='' \ No newline at end of file diff --git a/bin/reportlab/pdfgen/canvas.py b/bin/reportlab/pdfgen/canvas.py deleted file mode 100644 index 27eea75ad72..00000000000 --- a/bin/reportlab/pdfgen/canvas.py +++ /dev/null @@ -1,1487 +0,0 @@ -#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/pdfgen/canvas.py -__version__=''' $Id$ ''' -__doc__=""" -The Canvas object is the primary interface for creating PDF files. See -doc/userguide.pdf for copious examples. -""" -ENABLE_TRACKING = 1 # turn this off to do profile testing w/o tracking - -import os -import sys -from string import join, split, strip, atoi, replace, upper -import tempfile -from types import * -from math import sin, cos, tan, pi, ceil -import md5 - -from reportlab import rl_config -from reportlab.pdfbase import pdfutils -from reportlab.pdfbase import pdfdoc -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfgen import pdfgeom, pathobject, textobject -from reportlab.lib.colors import Color, CMYKColor, toColor -from reportlab.lib.utils import import_zlib -from reportlab.lib.utils import fp_str - -zlib = import_zlib() -_SeqTypes=(TupleType,ListType) - -# Robert Kern -# Constants for closing paths. -# May be useful if one changes 'arc' and 'rect' to take a -# default argument that tells how to close the path. -# That way we can draw filled shapes. - -FILL_EVEN_ODD = 0 -FILL_NON_ZERO = 1 - #this is used by path-closing routines. - #map stroke, fill, fillmode -> operator - # fillmode: 1 = non-Zero (obviously), 0 = evenOdd -PATH_OPS = {(0, 0, FILL_EVEN_ODD) : 'n', #no op - (0, 0, FILL_NON_ZERO) : 'n', #no op - (1, 0, FILL_EVEN_ODD) : 'S', #stroke only - (1, 0, FILL_NON_ZERO) : 'S', #stroke only - (0, 1, FILL_EVEN_ODD) : 'f*', #Fill only - (0, 1, FILL_NON_ZERO) : 'f', #Fill only - (1, 1, FILL_EVEN_ODD) : 'B*', #Stroke and Fill - (1, 1, FILL_NON_ZERO) : 'B', #Stroke and Fill - } - -_escapePDF = pdfutils._escape -_instanceEscapePDF = pdfutils._instanceEscapePDF - -if sys.hexversion >= 0x02000000: - def _digester(s): - return md5.md5(s).hexdigest() -else: - # hexdigest not available in 1.5 - def _digester(s): - return join(map(lambda x : "%02x" % ord(x), md5.md5(s).digest()), '') - -class Canvas: - """This class is the programmer's interface to the PDF file format. Methods - are (or will be) provided here to do just about everything PDF can do. - - The underlying model to the canvas concept is that of a graphics state machine - that at any given point in time has a current font, fill color (for figure - interiors), stroke color (for figure borders), line width and geometric transform, among - many other characteristics. - - Canvas methods generally either draw something (like canvas.line) using the - current state of the canvas or change some component of the canvas - state (like canvas.setFont). The current state can be saved and restored - using the saveState/restoreState methods. - - Objects are "painted" in the order they are drawn so if, for example - two rectangles overlap the last draw will appear "on top". PDF form - objects (supported here) are used to draw complex drawings only once, - for possible repeated use. - - There are other features of canvas which are not visible when printed, - such as outlines and bookmarks which are used for navigating a document - in a viewer. - - Here is a very silly example usage which generates a Hello World pdf document. - - from reportlab.pdfgen import canvas - c = canvas.Canvas("hello.pdf") - from reportlab.lib.units import inch - # move the origin up and to the left - c.translate(inch,inch) - # define a large font - c.setFont("Helvetica", 80) - # choose some colors - c.setStrokeColorRGB(0.2,0.5,0.3) - c.setFillColorRGB(1,0,1) - # draw a rectangle - c.rect(inch,inch,6*inch,9*inch, fill=1) - # make text go straight up - c.rotate(90) - # change color - c.setFillColorRGB(0,0,0.77) - # say hello (note after rotate the y coord needs to be negative!) - c.drawString(3*inch, -3*inch, "Hello World") - c.showPage() - c.save() - """ - - def __init__(self,filename, - pagesize=None, - bottomup = 1, - pageCompression=None, - encoding = None, - invariant = None, - verbosity=0): - """Create a canvas of a given size. etc. - Most of the attributes are private - we will use set/get methods - as the preferred interface. Default page size is A4.""" - if pagesize is None: pagesize = rl_config.defaultPageSize - if encoding is None: encoding = rl_config.defaultEncoding - if invariant is None: invariant = rl_config.invariant - self._filename = filename - self._encodingName = encoding - self._doc = pdfdoc.PDFDocument(encoding, - compression=pageCompression, - invariant=invariant) - - - #this only controls whether it prints 'saved ...' - 0 disables - self._verbosity = verbosity - - #this is called each time a page is output if non-null - self._onPage = None - - self._pagesize = pagesize - self._pageRotation = 0 - #self._currentPageHasImages = 0 - self._pageTransition = None - self._pageDuration = None - self._destinations = {} # dictionary of destinations for cross indexing. - - self.setPageCompression(pageCompression) - self._pageNumber = 1 # keep a count - #self3 = [] #where the current page's marking operators accumulate - # when we create a form we need to save operations not in the form - self._codeStack = [] - self._restartAccumulators() # restart all accumulation state (generalized, arw) - self._annotationCount = 0 - - self._outlines = [] # list for a name tree - self._psCommandsBeforePage = [] #for postscript tray/font commands - self._psCommandsAfterPage = [] #for postscript tray/font commands - - #PostScript has the origin at bottom left. It is easy to achieve a top- - #down coord system by translating to the top of the page and setting y - #scale to -1, but then text is inverted. So self.bottomup is used - #to also set the text matrix accordingly. You can now choose your - #drawing coordinates. - self.bottomup = bottomup - self.imageCaching = rl_config.defaultImageCaching - self._make_preamble() - self.init_graphics_state() - self.state_stack = [] - - def init_graphics_state(self): - #initial graphics state, never modify any of these in place - self._x = 0 - self._y = 0 - self._fontname = 'Times-Roman' - self._fontsize = 12 - self._dynamicFont = 0 - self._textMode = 0 #track if between BT/ET - self._leading = 14.4 - self._currentMatrix = (1., 0., 0., 1., 0., 0.) - self._fillMode = 0 #even-odd - - #text state - self._charSpace = 0 - self._wordSpace = 0 - self._horizScale = 100 - self._textRenderMode = 0 - self._rise = 0 - self._textLineMatrix = (1., 0., 0., 1., 0., 0.) - self._textMatrix = (1., 0., 0., 1., 0., 0.) - - # line drawing - self._lineCap = 0 - self._lineJoin = 0 - self._lineDash = None #not done - self._lineWidth = 0 - self._mitreLimit = 0 - - self._fillColorRGB = (0,0,0) - self._strokeColorRGB = (0,0,0) - - def push_state_stack(self): - state = {} - d = self.__dict__ - for name in self.STATE_ATTRIBUTES: - state[name] = d[name] #getattr(self, name) - self.state_stack.append(state) - - def pop_state_stack(self): - state = self.state_stack[-1] - del self.state_stack[-1] - d = self.__dict__ - d.update(state) - - STATE_ATTRIBUTES = split(""" - _x _y _fontname _fontsize _dynamicFont _textMode _leading _currentMatrix _fillMode - _fillMode _charSpace _wordSpace _horizScale _textRenderMode _rise _textLineMatrix - _textMatrix _lineCap _lineJoin _lineDash _lineWidth _mitreLimit _fillColorRGB - _strokeColorRGB""") - STATE_RANGE = range(len(STATE_ATTRIBUTES)) - - #self._addStandardFonts() - - def _make_preamble(self): - # yuk - iName = self._doc.getInternalFontName('Helvetica') - if self.bottomup: - #must set an initial font - self._preamble = '1 0 0 1 0 0 cm BT %s 12 Tf 14.4 TL ET' % iName - else: - #switch coordinates, flip text and set font - self._preamble = '1 0 0 -1 0 %s cm BT %s 12 Tf 14.4 TL ET' % (fp_str(self._pagesize[1]), iName) - - if not _instanceEscapePDF: - def _escape(self, s): - return _escapePDF(s) - - #info functions - non-standard - def setAuthor(self, author): - """identify the author for invisible embedding inside the PDF document. - the author annotation will appear in the the text of the file but will - not automatically be seen when the document is viewed.""" - self._doc.setAuthor(author) - - def addOutlineEntry(self, title, key, level=0, closed=None): - """Adds a new entry to the outline at given level. If LEVEL not specified, - entry goes at the top level. If level specified, it must be - no more than 1 greater than the outline level in the last call. - - The key must be the (unique) name of a bookmark. - the title is the (non-unique) name to be displayed for the entry. - - If closed is set then the entry should show no subsections by default - when displayed. - - Example - c.addOutlineEntry("first section", "section1") - c.addOutlineEntry("introduction", "s1s1", 1, closed=1) - c.addOutlineEntry("body", "s1s2", 1) - c.addOutlineEntry("detail1", "s1s2s1", 2) - c.addOutlineEntry("detail2", "s1s2s2", 2) - c.addOutlineEntry("conclusion", "s1s3", 1) - c.addOutlineEntry("further reading", "s1s3s1", 2) - c.addOutlineEntry("second section", "section1") - c.addOutlineEntry("introduction", "s2s1", 1) - c.addOutlineEntry("body", "s2s2", 1, closed=1) - c.addOutlineEntry("detail1", "s2s2s1", 2) - c.addOutlineEntry("detail2", "s2s2s2", 2) - c.addOutlineEntry("conclusion", "s2s3", 1) - c.addOutlineEntry("further reading", "s2s3s1", 2) - - generated outline looks like - - first section - |- introduction - |- body - | |- detail1 - | |- detail2 - |- conclusion - | |- further reading - - second section - |- introduction - |+ body - |- conclusion - | |- further reading - - Note that the second "body" is closed. - - Note that you can jump from level 5 to level 3 but not - from 3 to 5: instead you need to provide all intervening - levels going down (4 in this case). Note that titles can - collide but keys cannot. - """ - #to be completed - #self._outlines.append(title) - self._doc.outline.addOutlineEntry(key, level, title, closed=closed) - - def setOutlineNames0(self, *nametree): # keep this for now (?) - """nametree should can be a recursive tree like so - c.setOutlineNames( - "chapter1dest", - ("chapter2dest", - ["chapter2section1dest", - "chapter2section2dest", - "chapter2conclusiondest"] - ), # end of chapter2 description - "chapter3dest", - ("chapter4dest", ["c4s1", "c4s2"]) - ) - each of the string names inside must be bound to a bookmark - before the document is generated. - """ - apply(self._doc.outline.setNames, (self,)+nametree) - - def setTitle(self, title): - """write a title into the PDF file that won't automatically display - in the document itself.""" - self._doc.setTitle(title) - - def setSubject(self, subject): - """write a subject into the PDF file that won't automatically display - in the document itself.""" - self._doc.setSubject(subject) - - def pageHasData(self): - "Info function - app can call it after showPage to see if it needs a save" - return len(self._code) == 0 - - def showOutline(self): - """Specify that Acrobat Reader should start with the outline tree visible. - showFullScreen() and showOutline() conflict; the one called last - wins.""" - self._doc._catalog.showOutline() - - def showFullScreen0(self): - """Specify that Acrobat Reader should start in full screen mode. - showFullScreen() and showOutline() conflict; the one called last - wins.""" - self._doc._catalog.showFullScreen() - - def showPage(self): - """Close the current page and possibly start on a new page.""" - - # ensure a space at the end of the stream - Acrobat does - # not mind, but Ghostscript dislikes 'Qendstream' even if - # the length marker finishes after 'Q' - self._code.append(' ') - page = pdfdoc.PDFPage() - page.pagewidth = self._pagesize[0] - page.pageheight = self._pagesize[1] - page.Rotate = self._pageRotation - page.hasImages = self._currentPageHasImages - page.setPageTransition(self._pageTransition) - page.setCompression(self._pageCompression) - if self._pageDuration is not None: - page.Dur = self._pageDuration - - strm = self._psCommandsBeforePage + [self._preamble] + self._code + self._psCommandsAfterPage - page.setStream(strm) - self._setXObjects(page) - self._setAnnotations(page) - self._doc.addPage(page) - - if self._onPage: self._onPage(self._pageNumber) - self._startPage() - - def _startPage(self): - #now get ready for the next one - self._pageNumber = self._pageNumber+1 - self._restartAccumulators() - self.init_graphics_state() - self.state_stack = [] - - def setPageCallBack(self, func): - """func(pageNum) will be called on each page end. - - This is mainly a hook for progress monitoring. - Call setPageCallback(None) to clear a callback.""" - self._onPage = func - - def _setAnnotations(self,page): - page.Annots = self._annotationrefs - - def _setXObjects(self, thing): - """for pages and forms, define the XObject dictionary for resources, if needed""" - forms = self._formsinuse - if forms: - xobjectsdict = self._doc.xobjDict(forms) - thing.XObjects = xobjectsdict - else: - thing.XObjects = None - - def _bookmarkReference(self, name): - """get a reference to a (possibly undefined, possibly unbound) bookmark""" - d = self._destinations - try: - return d[name] - except: - result = d[name] = pdfdoc.Destination(name) # newly defined, unbound - return result - - def bookmarkPage(self, key, - fitType="Fit", - left=None, - top=None, - bottom=None, - right=None, - zoom=None - ): - """ - This creates a bookmark to the current page which can - be referred to with the given key elsewhere. - - PDF offers very fine grained control over how Acrobat - reader is zoomed when people link to this. The default - is to keep the user's current zoom settings. the last - arguments may or may not be needed depending on the - choice of 'fitType'. - - Fit types and the other arguments they use are: - /XYZ left top zoom - fine grained control. null - or zero for any of the parameters means 'leave - as is', so "0,0,0" will keep the reader's settings. - NB. Adobe Reader appears to prefer "null" to 0's. - - /Fit - entire page fits in window - - /FitH top - top coord at top of window, width scaled - to fit. - - /FitV left - left coord at left of window, height - scaled to fit - - /FitR left bottom right top - scale window to fit - the specified rectangle - - (question: do we support /FitB, FitBH and /FitBV - which are hangovers from version 1.1 / Acrobat 3.0?)""" - dest = self._bookmarkReference(key) - self._doc.inPage() # try to enable page-only features - pageref = self._doc.thisPageRef() - - #None = "null" for PDF - if left is None: - left = "null" - if top is None: - top = "null" - if bottom is None: - bottom = "null" - if right is None: - right = "null" - if zoom is None: - zoom = "null" - - if fitType == "XYZ": - dest.xyz(left,top,zoom) - elif fitType == "Fit": - dest.fit() - elif fitType == "FitH": - dest.fith(top) - elif fitType == "FitV": - dest.fitv(left) - elif fitType == "FitR": - dest.fitr(left,bottom,right,top) - #Do we need these (version 1.1 / Acrobat 3 versions)? - elif fitType == "FitB": - dest.fitb() - elif fitType == "FitBH": - dest.fitbh(top) - elif fitType == "FitBV": - dest.fitbv(left) - else: - raise "Unknown Fit type %s" % (fitType,) - - dest.setPage(pageref) - return dest - - def bookmarkHorizontalAbsolute(self, key, yhorizontal): - """Bind a bookmark (destination) to the current page at a horizontal position. - Note that the yhorizontal of the book mark is with respect to the default - user space (where the origin is at the lower left corner of the page) - and completely ignores any transform (translation, scale, skew, rotation, - etcetera) in effect for the current graphics state. The programmer is - responsible for making sure the bookmark matches an appropriate item on - the page.""" - #This method should probably be deprecated since it is just a sub-set of bookmarkPage - return self.bookmarkPage(key,fitType="FitH",top=yhorizontal) - - def bookmarkHorizontal(self, key, relativeX, relativeY): - """w.r.t. the current transformation, bookmark this horizontal.""" - (xt, yt) = self.absolutePosition(relativeX,relativeY) - self.bookmarkHorizontalAbsolute(key, yt) - - #def _inPage0(self): disallowed! - # """declare a page, enable page features""" - # self._doc.inPage() - - #def _inForm0(self): - # "deprecated in favore of beginForm...endForm" - # self._doc.inForm() - - def doForm(self, name): - """use a form XObj in current operation stream. - - The form should either have been defined previously using - beginForm ... endForm, or may be defined later. If it is not - defined at save time, an exception will be raised. The form - will be drawn within the context of the current graphics - state.""" - self._code.append("/%s Do" % self._doc.getXObjectName(name)) - self._formsinuse.append(name) - - def hasForm(self, name): - """Query whether form XObj really exists yet.""" - return self._doc.hasForm(name) - - - ###################################################### - # - # Image routines - # - ###################################################### - - def drawInlineImage(self, image, x,y, width=None,height=None): - """Draw an Image into the specified rectangle. If width and - height are omitted, they are calculated from the image size. - Also allow file names as well as images. The size in pixels - of the image is returned.""" - - self._currentPageHasImages = 1 - from pdfimages import PDFImage - img_obj = PDFImage(image, x,y, width, height) - img_obj.drawInlineImage(self) - return (img_obj.width, img_obj.height) - - def drawImage(self, image, x, y, width=None, height=None, mask=None): - """Draws the image (ImageReader object or filename) as specified. - - "image" may be an image filename or a ImageReader object. If width - and height are not given, the "natural" width and height in pixels - is used at a scale of 1 point to 1 pixel. - - The mask parameter takes 6 numbers and defines the range of - RGB values which will be masked out or treated as transparent. - For example with [0,2,40,42,136,139], it will mask out any - pixels with a Red value from 0-2, Green from 40-42 and - Blue from 136-139 (on a scale of 0-255) - - The method returns the width and height of the underlying image since - this is often useful for layout algorithms. - - Unlike drawInlineImage, this creates 'external images' which - are only stored once in the PDF file but can be drawn many times. - If you give it the same filename twice, even at different locations - and sizes, it will reuse the first occurrence. If you use ImageReader - objects, it tests whether the image content has changed before deciding - whether to reuse it. - - In general you should use drawImage in preference to drawInlineImage - unless you have read the PDF Spec and understand the tradeoffs.""" - self._currentPageHasImages = 1 - - # first, generate a unique name/signature for the image. If ANYTHING - # is different, even the mask, this should be different. - if type(image) == type(''): - #filename, use it - name = _digester('%s%s' % (image, mask)) - else: - rawdata = image.getRGBData() - name = _digester(rawdata+str(mask)) - - # in the pdf document, this will be prefixed with something to - # say it is an XObject. Does it exist yet? - regName = self._doc.getXObjectName(name) - imgObj = self._doc.idToObject.get(regName, None) - if not imgObj: - #first time seen, create and register the PDFImageXobject - imgObj = pdfdoc.PDFImageXObject(name, image, mask=mask) - imgObj.name = name - self._setXObjects(imgObj) - self._doc.Reference(imgObj, regName) - self._doc.addForm(name, imgObj) - - # ensure we have a size, as PDF will make it 1x1 pixel otherwise! - if width is None: - width = imgObj.width - if height is None: - height = imgObj.height - - # scale and draw - self.saveState() - self.translate(x, y) - self.scale(width, height) - self._code.append("/%s Do" % regName) - self.restoreState() - - # track what's been used on this page - self._formsinuse.append(name) - - return (imgObj.width, imgObj.height) - - def _restartAccumulators(self): - if self._codeStack: - # restore the saved code - saved = self._codeStack[-1] - del self._codeStack[-1] - (self._code, self._formsinuse, self._annotationrefs, self._formData) = saved - else: - self._code = [] # ready for more... - self._psCommandsAfterPage = [] - self._currentPageHasImages = 1 # for safety... - self._formsinuse = [] - self._annotationrefs = [] - self._formData = None - - def _pushAccumulators(self): - "when you enter a form, save accumulator info not related to the form for page (if any)" - saved = (self._code, self._formsinuse, self._annotationrefs, self._formData) - self._codeStack.append(saved) - self._code = [] # ready for more... - self._currentPageHasImages = 1 # for safety... - self._formsinuse = [] - self._annotationrefs = [] - self._formData = None - - def beginForm(self, name, lowerx=0, lowery=0, upperx=None, uppery=None): - """declare the current graphics stream to be a named form. - A graphics stream can either be a page or a form, not both. - Some operations (like bookmarking) are permitted for pages - but not forms. The form will not automatically be shown in the - document but must be explicitly referenced using doForm in pages - that require the form.""" - self.push_state_stack() - self.init_graphics_state() - if self._code: - # save the code that is not in the formf - self._pushAccumulators() - #self._codeStack.append(self._code) - #self._code = [] - self._formData = (name, lowerx, lowery, upperx, uppery) - self._doc.inForm() - #self._inForm0() - - def endForm(self): - """emit the current collection of graphics operations as a Form - as declared previously in beginForm.""" - (name, lowerx, lowery, upperx, uppery) = self._formData - #self.makeForm0(name, lowerx, lowery, upperx, uppery) - # fall through! makeForm0 disallowed - #def makeForm0(self, name, lowerx=0, lowery=0, upperx=None, uppery=None): - """Like showpage, but make a form using accumulated operations instead""" - # deprecated in favor or beginForm(...)... endForm() - (w,h) = self._pagesize - if upperx is None: upperx=w - if uppery is None: uppery=h - form = pdfdoc.PDFFormXObject(lowerx=lowerx, lowery=lowery, upperx=upperx, uppery=uppery) - form.compression = self._pageCompression - form.setStreamList([self._preamble] + self._code) # ??? minus preamble (seems to be needed!) - self._setXObjects(form) - self._setAnnotations(form) - self._doc.addForm(name, form) - self._restartAccumulators() - self.pop_state_stack() - - - def addPostScriptCommand(self, command, position=1): - """Embed literal Postscript in the document. - - With position=0, it goes at very beginning of page stream; - with position=1, at current point; and - with position=2, at very end of page stream. What that does - to the resulting Postscript depends on Adobe's header :-) - - Use with extreme caution, but sometimes needed for printer tray commands. - Acrobat 4.0 will export Postscript to a printer or file containing - the given commands. Adobe Reader 6.0 no longer does as this feature is - deprecated. 5.0, I don't know about (please let us know!). This was - funded by Bob Marshall of Vector.co.uk and tested on a Lexmark 750. - See test_pdfbase_postscript.py for 2 test cases - one will work on - any Postscript device, the other uses a 'setpapertray' command which - will error in Distiller but work on printers supporting it. - """ - #check if we've done this one already... - rawName = 'PS' + md5.md5(command).hexdigest() - regName = self._doc.getXObjectName(rawName) - psObj = self._doc.idToObject.get(regName, None) - if not psObj: - #first use of this chunk of Postscript, make an object - psObj = pdfdoc.PDFPostScriptXObject(command + '\r\n') - self._setXObjects(psObj) - self._doc.Reference(psObj, regName) - self._doc.addForm(rawName, psObj) - if position == 0: - self._psCommandsBeforePage.append("/%s Do" % regName) - elif position==1: - self._code.append("/%s Do" % regName) - else: - self._psCommandsAfterPage.append("/%s Do" % regName) - - self._formsinuse.append(rawName) - - def textAnnotation0(self, contents, Rect=None, addtopage=1, name=None, **kw): - """Experimental. - """ - if not Rect: - (w,h) = self._pagesize# default to whole page (?) - Rect = (0,0,w,h) - annotation = apply(pdfdoc.TextAnnotation, (Rect, contents), kw) - self._addAnnotation(annotation, name, addtopage) - - def inkAnnotation0(self, contents, InkList=None, Rect=None, addtopage=1, name=None, **kw): - "Experimental" - (w,h) = self._pagesize - if not Rect: - Rect = (0,0,w,h) - if not InkList: - InkList = ( (100,100,100,h-100,w-100,h-100,w-100,100), ) - annotation = apply(pdfdoc.InkAnnotation, (Rect, contents, InkList), kw) - self.addAnnotation(annotation, name, addtopage) - - def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw): - """rectangular link annotation positioned wrt the default user space. - The identified rectangle on the page becomes a "hot link" which - when clicked will send the viewer to the page and position identified - by the destination. - - Rect identifies (lowerx, lowery, upperx, uppery) for lower left - and upperright points of the rectangle. Translations and other transforms - are IGNORED (the rectangular position is given with respect - to the default user space. - destinationname should be the name of a bookmark (which may be defined later - but must be defined before the document is generated). - - You may want to use the keyword argument Border='[0 0 0]' to - suppress the visible rectangle around the during viewing link.""" - destination = self._bookmarkReference(destinationname) # permitted to be undefined... must bind later... - (w,h) = self._pagesize - if not Rect: - Rect = (0,0,w,h) - kw["Rect"] = Rect - kw["Contents"] = contents - kw["Destination"] = destination - annotation = apply(pdfdoc.LinkAnnotation, (), kw) - self._addAnnotation(annotation, name, addtopage) - - def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, **kw): - """rectangular link annotation w.r.t the current user transform. - if the transform is skewed/rotated the absolute rectangle will use the max/min x/y - """ - if Rect is None: - pass # do whatever linkAbsolute does - else: - (lx, ly, ux, uy) = Rect - (xll,yll) = self.absolutePosition(lx,ly) - (xur,yur) = self.absolutePosition(ux, uy) - (xul,yul) = self.absolutePosition(lx, uy) - (xlr,ylr) = self.absolutePosition(ux, ly) - xs = (xll, xur, xul, xlr) - ys = (yll, yur, yul, ylr) - (xmin, ymin) = (min(xs), min(ys)) - (xmax, ymax) = (max(xs), max(ys)) - #(w2, h2) = (xmax-xmin, ymax-ymin) - Rect = (xmin, ymin, xmax, ymax) - return apply(self.linkAbsolute, (contents, destinationname, Rect, addtopage, name), kw) - - def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None): - """Create a rectangular URL 'hotspot' in the given rectangle. - - if relative=1, this is in the current coord system, otherwise - in absolute page space. - The remaining options affect the border appearance; the border is - drawn by Acrobat, not us. Set thickness to zero to hide it. - Any border drawn this way is NOT part of the page stream and - will not show when printed to a Postscript printer or distilled; - it is safest to draw your own.""" - from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFName, PDFArray, PDFString - #tried the documented BS element in the pdf spec but it - #does not work, and Acrobat itself does not appear to use it! - if relative: - #adjust coordinates - (lx, ly, ux, uy) = rect - (xll,yll) = self.absolutePosition(lx,ly) - (xur,yur) = self.absolutePosition(ux, uy) - (xul,yul) = self.absolutePosition(lx, uy) - (xlr,ylr) = self.absolutePosition(ux, ly) - xs = (xll, xur, xul, xlr) - ys = (yll, yur, yul, ylr) - (xmin, ymin) = (min(xs), min(ys)) - (xmax, ymax) = (max(xs), max(ys)) - #(w2, h2) = (xmax-xmin, ymax-ymin) - rect = (xmin, ymin, xmax, ymax) - - ann = PDFDictionary() - ann["Type"] = PDFName("Annot") - ann["Subtype"] = PDFName("Link") - ann["Rect"] = PDFArray(rect) # the whole page for testing - - # the action is a separate dictionary - A = PDFDictionary() - A["Type"] = PDFName("Action") # not needed? - A["S"] = PDFName("URI") - A["URI"] = PDFString(url) - ann["A"] = A - - # now for formatting stuff. - if color: - ann["C"] = PDFArray([color.red, color.green, color.blue]) - border = [0,0,0] - if thickness: - border[2] = thickness - if dashArray: - border.append(PDFArray(dashArray)) - ann["Border"] = PDFArray(border) - - self._addAnnotation(ann) - - def _addAnnotation(self, annotation, name=None, addtopage=1): - count = self._annotationCount = self._annotationCount+1 - if not name: name="NUMBER"+repr(count) - self._doc.addAnnotation(name, annotation) - if addtopage: - self._annotatePage(name) - - def _annotatePage(self, name): - ref = self._doc.refAnnotation(name) - self._annotationrefs.append(ref) - - def getPageNumber(self): - "get the page number for the current page being generated." - return self._pageNumber - - def save(self): - """Saves and close the PDF document in the file. - If there is current data a ShowPage is executed automatically. - After this operation the canvas must not be used further.""" - if len(self._code): self.showPage() - self._doc.SaveToFile(self._filename, self) - - def getpdfdata(self): - """Returns the PDF data that would normally be written to a file. - If there is current data a ShowPage is executed automatically. - After this operation the canvas must not be used further.""" - if len(self._code): self.showPage() - return self._doc.GetPDFData(self) - - def setPageSize(self, size): - """accepts a 2-tuple in points for paper size for this - and subsequent pages""" - self._pagesize = size - self._make_preamble() - - def setPageRotation(self, rot): - """Instruct display device that this page is to be rotated""" - assert rot % 90.0 == 0.0, "Rotation must be a multiple of 90 degrees" - self._pageRotation = rot - - def addLiteral(self, s, escaped=1): - """introduce the literal text of PDF operations s into the current stream. - Only use this if you are an expert in the PDF file format.""" - s = str(s) # make sure its a string - if escaped==0: - s = self._escape(s) # convert to string for safety - self._code.append(s) - - ###################################################################### - # - # coordinate transformations - # - ###################################################################### - def resetTransforms(self): - """I want to draw something (eg, string underlines) w.r.t. the default user space. - Reset the matrix! This should be used usually as follows: - canv.saveState() - canv.resetTransforms() - ...draw some stuff in default space coords... - canv.restoreState() # go back! - """ - # we have to adjoin the inverse, since reset is not a basic operation (without save/restore) - (selfa, selfb, selfc, selfd, selfe, selff) = self._currentMatrix - det = selfa*selfd - selfc*selfb - resulta = selfd/det - resultc = -selfc/det - resulte = (selfc*selff - selfd*selfe)/det - resultd = selfa/det - resultb = -selfb/det - resultf = (selfe*selfb - selff*selfa)/det - self.transform(resulta, resultb, resultc, resultd, resulte, resultf) - - def transform(self, a,b,c,d,e,f): - """adjoin a mathematical transform to the current graphics state matrix. - Not recommended for beginners.""" - #"""How can Python track this?""" - if ENABLE_TRACKING: - a0,b0,c0,d0,e0,f0 = self._currentMatrix - self._currentMatrix = (a0*a+c0*b, b0*a+d0*b, - a0*c+c0*d, b0*c+d0*d, - a0*e+c0*f+e0, b0*e+d0*f+f0) - if self._code and self._code[-1][-3:]==' cm': - L = split(self._code[-1]) - a0, b0, c0, d0, e0, f0 = map(float,L[-7:-1]) - s = len(L)>7 and join(L)+ ' %s cm' or '%s cm' - self._code[-1] = s % fp_str(a0*a+c0*b,b0*a+d0*b,a0*c+c0*d,b0*c+d0*d,a0*e+c0*f+e0,b0*e+d0*f+f0) - else: - self._code.append('%s cm' % fp_str(a,b,c,d,e,f)) - ### debug -## (a,b,c,d,e,f) = self.Kolor -## self.Kolor = (f,a,b,c,d,e) -## self.setStrokeColorRGB(f,a,b) -## self.setFillColorRGB(f,a,b) -## self.line(-90,-1000,1,1); self.line(1000,-90,-1,1) -## self.drawString(0,0,"here") -## Kolor = (0, 0.5, 1, 0.25, 0.7, 0.3) - - def absolutePosition(self, x, y): - """return the absolute position of x,y in user space w.r.t. default user space""" - if not ENABLE_TRACKING: - raise ValueError, "tracking not enabled! (canvas.ENABLE_TRACKING=0)" - (a,b,c,d,e,f) = self._currentMatrix - xp = a*x + c*y + e - yp = b*x + d*y + f - return (xp, yp) - - def translate(self, dx, dy): - """move the origin from the current (0,0) point to the (dx,dy) point - (with respect to the current graphics state).""" - self.transform(1,0,0,1,dx,dy) - - def scale(self, x, y): - """Scale the horizontal dimension by x and the vertical by y - (with respect to the current graphics state). - For example canvas.scale(2.0, 0.5) will make everything short and fat.""" - self.transform(x,0,0,y,0,0) - - def rotate(self, theta): - """Canvas.rotate(theta) - - Rotate the canvas by the angle theta (in degrees).""" - c = cos(theta * pi / 180) - s = sin(theta * pi / 180) - self.transform(c, s, -s, c, 0, 0) - - def skew(self, alpha, beta): - tanAlpha = tan(alpha * pi / 180) - tanBeta = tan(beta * pi / 180) - self.transform(1, tanAlpha, tanBeta, 1, 0, 0) - - ###################################################################### - # - # graphics state management - # - ###################################################################### - - def saveState(self): - """Save the current graphics state to be restored later by restoreState. - - For example: - canvas.setFont("Helvetica", 20) - canvas.saveState() - ... - canvas.setFont("Courier", 9) - ... - canvas.restoreState() - # if the save/restore pairs match then font is Helvetica 20 again. - """ - self.push_state_stack() - self._code.append('q') - - def restoreState(self): - """restore the graphics state to the matching saved state (see saveState).""" - self._code.append('Q') - self.pop_state_stack() - - ############################################################### - # - # Drawing methods. These draw things directly without - # fiddling around with Path objects. We can add any geometry - # methods we wish as long as their meaning is precise and - # they are of general use. - # - # In general there are two patterns. Closed shapes - # have the pattern shape(self, args, stroke=1, fill=0); - # by default they draw an outline only. Line segments come - # in three flavours: line, bezier, arc (which is a segment - # of an elliptical arc, approximated by up to four bezier - # curves, one for each quadrant. - # - # In the case of lines, we provide a 'plural' to unroll - # the inner loop; it is useful for drawing big grids - ################################################################ - - #--------first the line drawing methods----------------------- - - def line(self, x1,y1, x2,y2): - """draw a line segment from (x1,y1) to (x2,y2) (with color, thickness and - other attributes determined by the current graphics state).""" - self._code.append('n %s m %s l S' % (fp_str(x1, y1), fp_str(x2, y2))) - - def lines(self, linelist): - """Like line(), permits many lines to be drawn in one call. - for example for the figure - | - -- -- - | - - crosshairs = [(20,0,20,10), (20,30,20,40), (0,20,10,20), (30,20,40,20)] - canvas.lines(crosshairs) - """ - self._code.append('n') - for (x1,y1,x2,y2) in linelist: - self._code.append('%s m %s l' % (fp_str(x1, y1), fp_str(x2, y2))) - self._code.append('S') - - def grid(self, xlist, ylist): - """Lays out a grid in current line style. Supply list of - x an y positions.""" - assert len(xlist) > 1, "x coordinate list must have 2+ items" - assert len(ylist) > 1, "y coordinate list must have 2+ items" - lines = [] - y0, y1 = ylist[0], ylist[-1] - x0, x1 = xlist[0], xlist[-1] - for x in xlist: - lines.append((x,y0,x,y1)) - for y in ylist: - lines.append((x0,y,x1,y)) - self.lines(lines) - - def bezier(self, x1, y1, x2, y2, x3, y3, x4, y4): - "Bezier curve with the four given control points" - self._code.append('n %s m %s c S' % - (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4)) - ) - def arc(self, x1,y1, x2,y2, startAng=0, extent=90): - """Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2, - starting at startAng degrees and covering extent degrees. Angles - start with 0 to the right (+x) and increase counter-clockwise. - These should have x1.""" - - pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent) - #move to first point - self._code.append('n %s m' % fp_str(pointList[0][:2])) - for curve in pointList: - self._code.append('%s c' % fp_str(curve[2:])) - # stroke - self._code.append('S') - - #--------now the shape drawing methods----------------------- - - def rect(self, x, y, width, height, stroke=1, fill=0): - "draws a rectangle with lower left corner at (x,y) and width and height as given." - self._code.append('n %s re ' % fp_str(x, y, width, height) - + PATH_OPS[stroke, fill, self._fillMode]) - - def ellipse(self, x1, y1, x2, y2, stroke=1, fill=0): - """Draw an ellipse defined by an enclosing rectangle. - - Note that (x1,y1) and (x2,y2) are the corner points of - the enclosing rectangle. - - Uses bezierArc, which conveniently handles 360 degrees. - Special thanks to Robert Kern.""" - - pointList = pdfgeom.bezierArc(x1,y1, x2,y2, 0, 360) - #move to first point - self._code.append('n %s m' % fp_str(pointList[0][:2])) - for curve in pointList: - self._code.append('%s c' % fp_str(curve[2:])) - #finish - self._code.append(PATH_OPS[stroke, fill, self._fillMode]) - - def wedge(self, x1,y1, x2,y2, startAng, extent, stroke=1, fill=0): - """Like arc, but connects to the centre of the ellipse. - Most useful for pie charts and PacMan!""" - - x_cen = (x1+x2)/2. - y_cen = (y1+y2)/2. - pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent) - - self._code.append('n %s m' % fp_str(x_cen, y_cen)) - # Move the pen to the center of the rectangle - self._code.append('%s l' % fp_str(pointList[0][:2])) - for curve in pointList: - self._code.append('%s c' % fp_str(curve[2:])) - # finish the wedge - self._code.append('%s l ' % fp_str(x_cen, y_cen)) - # final operator - self._code.append(PATH_OPS[stroke, fill, self._fillMode]) - - def circle(self, x_cen, y_cen, r, stroke=1, fill=0): - """draw a cirle centered at (x_cen,y_cen) with radius r (special case of ellipse)""" - - x1 = x_cen - r - x2 = x_cen + r - y1 = y_cen - r - y2 = y_cen + r - self.ellipse(x1, y1, x2, y2, stroke, fill) - - def roundRect(self, x, y, width, height, radius, stroke=1, fill=0): - """Draws a rectangle with rounded corners. The corners are - approximately quadrants of a circle, with the given radius.""" - #use a precomputed set of factors for the bezier approximation - #to a circle. There are six relevant points on the x axis and y axis. - #sketch them and it should all make sense! - t = 0.4472 * radius - - x0 = x - x1 = x0 + t - x2 = x0 + radius - x3 = x0 + width - radius - x4 = x0 + width - t - x5 = x0 + width - - y0 = y - y1 = y0 + t - y2 = y0 + radius - y3 = y0 + height - radius - y4 = y0 + height - t - y5 = y0 + height - - self._code.append('n %s m' % fp_str(x2, y0)) - self._code.append('%s l' % fp_str(x3, y0)) # bottom row - self._code.append('%s c' - % fp_str(x4, y0, x5, y1, x5, y2)) # bottom right - - self._code.append('%s l' % fp_str(x5, y3)) # right edge - self._code.append('%s c' - % fp_str(x5, y4, x4, y5, x3, y5)) # top right - - self._code.append('%s l' % fp_str(x2, y5)) # top row - self._code.append('%s c' - % fp_str(x1, y5, x0, y4, x0, y3)) # top left - - self._code.append('%s l' % fp_str(x0, y2)) # left edge - self._code.append('%s c' - % fp_str(x0, y1, x1, y0, x2, y0)) # bottom left - - self._code.append('h') #close off, although it should be where it started anyway - - self._code.append(PATH_OPS[stroke, fill, self._fillMode]) - - ################################################## - # - # Text methods - # - # As with graphics, a separate object ensures that - # everything is bracketed between text operators. - # The methods below are a high-level convenience. - # use PDFTextObject for multi-line text. - ################################################## - - def setFillColorCMYK(self, c, m, y, k): - """set the fill color useing negative color values - (cyan, magenta, yellow and darkness value). - Takes 4 arguments between 0.0 and 1.0""" - self._fillColorCMYK = (c, m, y, k) - self._code.append('%s k' % fp_str(c, m, y, k)) - - def setStrokeColorCMYK(self, c, m, y, k): - """set the stroke color useing negative color values - (cyan, magenta, yellow and darkness value). - Takes 4 arguments between 0.0 and 1.0""" - self._strokeColorCMYK = (c, m, y, k) - self._code.append('%s K' % fp_str(c, m, y, k)) - - def drawString(self, x, y, text): - """Draws a string in the current text styles.""" - #we could inline this for speed if needed - t = self.beginText(x, y) - t.textLine(text) - self.drawText(t) - - def drawRightString(self, x, y, text): - """Draws a string right-aligned with the x coordinate""" - width = self.stringWidth(text, self._fontname, self._fontsize) - t = self.beginText(x - width, y) - t.textLine(text) - self.drawText(t) - - def drawCentredString(self, x, y, text): - """Draws a string centred on the x coordinate.""" - width = self.stringWidth(text, self._fontname, self._fontsize) - t = self.beginText(x - 0.5*width, y) - t.textLine(text) - self.drawText(t) - - def drawAlignedString(self, x, y, text, pivotChar="."): - """Draws a string aligned on the first '.' (or other pivot character). - - The centre position of the pivot character will be used as x.""" - parts = text.split(pivotChar,1) - pivW = self.stringWidth(pivotChar, self._fontname, self._fontsize) - leftText = parts[0] - self.drawRightString(x-0.5*pivW, y, leftText) - if len(parts) > 1: - rightText = pivotChar + parts[1] - self.drawString(x-0.5*pivW, y, rightText) - - def getAvailableFonts(self): - """Returns the list of PostScript font names available. - - Standard set now, but may grow in future with font embedding.""" - fontnames = self._doc.getAvailableFonts() - fontnames.sort() - return fontnames - - def addFont(self, fontObj): - "add a new font for subsequent use." - self._doc.addFont(fontObj) - - def _addStandardFonts(self): - """Ensures the standard 14 fonts are available in the system encoding. - Called by canvas on initialization""" - for fontName in pdfmetrics.standardFonts: - self.addFont(pdfmetrics.fontsByName[fontName]) - - def listLoadedFonts0(self): - "Convenience function to list all loaded fonts" - names = pdfmetrics.widths.keys() - names.sort() - return names - - def setFont(self, psfontname, size, leading = None): - """Sets the font. If leading not specified, defaults to 1.2 x - font size. Raises a readable exception if an illegal font - is supplied. Font names are case-sensitive! Keeps track - of font name and size for metrics.""" - self._fontname = psfontname - self._fontsize = size - if leading is None: - leading = size * 1.2 - self._leading = leading - font = pdfmetrics.getFont(self._fontname) - self._dynamicFont = getattr(font, '_dynamicFont', 0) - if not self._dynamicFont: - pdffontname = self._doc.getInternalFontName(psfontname) - self._code.append('BT %s %s Tf %s TL ET' % (pdffontname, fp_str(size), fp_str(leading))) - - def stringWidth(self, text, fontName, fontSize, encoding=None): - "gets width of a string in the given font and size" - if encoding is not None: - from reportlab.lib import logger - logger.warnOnce('encoding argument to Canvas.stringWidth is deprecated and has no effect!') - #if encoding is None: encoding = self._doc.encoding - return pdfmetrics.stringWidth(text, fontName, fontSize) - - # basic graphics modes - - def setLineWidth(self, width): - self._lineWidth = width - self._code.append('%s w' % fp_str(width)) - - def setLineCap(self, mode): - """0=butt,1=round,2=square""" - assert mode in (0,1,2), "Line caps allowed: 0=butt,1=round,2=square" - self._lineCap = mode - self._code.append('%d J' % mode) - - def setLineJoin(self, mode): - """0=mitre, 1=round, 2=bevel""" - assert mode in (0,1,2), "Line Joins allowed: 0=mitre, 1=round, 2=bevel" - self._lineJoin = mode - self._code.append('%d j' % mode) - - def setMiterLimit(self, limit): - self._miterLimit = limit - self._code.append('%s M' % fp_str(limit)) - - def setDash(self, array=[], phase=0): - """Two notations. pass two numbers, or an array and phase""" - if type(array) == IntType or type(array) == FloatType: - self._code.append('[%s %s] 0 d' % (array, phase)) - elif type(array) == ListType or type(array) == TupleType: - assert phase >= 0, "phase is a length in user space" - textarray = join(map(str, array)) - self._code.append('[%s] %s d' % (textarray, phase)) - - def setFillColorRGB(self, r, g, b): - """Set the fill color using positive color description - (Red,Green,Blue). Takes 3 arguments between 0.0 and 1.0""" - self._fillColorRGB = (r, g, b) - self._code.append('%s rg' % fp_str(r,g,b)) - - def setStrokeColorRGB(self, r, g, b): - """Set the stroke color using positive color description - (Red,Green,Blue). Takes 3 arguments between 0.0 and 1.0""" - self._strokeColorRGB = (r, g, b) - self._code.append('%s RG' % fp_str(r,g,b)) - - def setFillColor(self, aColor): - """Takes a color object, allowing colors to be referred to by name""" - if isinstance(aColor, CMYKColor): - d = aColor.density - c,m,y,k = (d*aColor.cyan, d*aColor.magenta, d*aColor.yellow, d*aColor.black) - self._fillColorCMYK = (c, m, y, k) - self._code.append('%s k' % fp_str(c, m, y, k)) - elif isinstance(aColor, Color): - rgb = (aColor.red, aColor.green, aColor.blue) - self._fillColorRGB = rgb - self._code.append('%s rg' % fp_str(rgb) ) - elif type(aColor) in _SeqTypes: - l = len(aColor) - if l==3: - self._fillColorRGB = aColor - self._code.append('%s rg' % fp_str(aColor) ) - elif l==4: - self.setFillColorCMYK(aColor[0], aColor[1], aColor[2], aColor[3]) - else: - raise 'Unknown color', str(aColor) - elif type(aColor) is StringType: - self.setFillColor(toColor(aColor)) - else: - raise 'Unknown color', str(aColor) - - def setStrokeColor(self, aColor): - """Takes a color object, allowing colors to be referred to by name""" - if isinstance(aColor, CMYKColor): - d = aColor.density - c,m,y,k = (d*aColor.cyan, d*aColor.magenta, d*aColor.yellow, d*aColor.black) - self._strokeColorCMYK = (c, m, y, k) - self._code.append('%s K' % fp_str(c, m, y, k)) - elif isinstance(aColor, Color): - #if type(aColor) == ColorType: - rgb = (aColor.red, aColor.green, aColor.blue) - self._strokeColorRGB = rgb - self._code.append('%s RG' % fp_str(rgb) ) - elif type(aColor) in _SeqTypes: - l = len(aColor) - if l==3: - self._strokeColorRGB = aColor - self._code.append('%s RG' % fp_str(aColor) ) - elif l==4: - self.setStrokeColorCMYK(aColor[0], aColor[1], aColor[2], aColor[3]) - else: - raise 'Unknown color', str(aColor) - elif type(aColor) is StringType: - self.setStrokeColor(toColor(aColor)) - else: - raise 'Unknown color', str(aColor) - - def setFillGray(self, gray): - """Sets the gray level; 0.0=black, 1.0=white""" - self._fillColorRGB = (gray, gray, gray) - self._code.append('%s g' % fp_str(gray)) - - def setStrokeGray(self, gray): - """Sets the gray level; 0.0=black, 1.0=white""" - self._strokeColorRGB = (gray, gray, gray) - self._code.append('%s G' % fp_str(gray)) - - # path stuff - the separate path object builds it - - def beginPath(self): - """Returns a fresh path object. Paths are used to draw - complex figures. The object returned follows the protocol - for a pathobject.PDFPathObject instance""" - return pathobject.PDFPathObject() - - def drawPath(self, aPath, stroke=1, fill=0): - "Draw the path object in the mode indicated" - gc = aPath.getCode(); pathops = PATH_OPS[stroke, fill, self._fillMode] - item = "%s %s" % (gc, pathops) # ENSURE STRING CONVERSION - self._code.append(item) - #self._code.append(aPath.getCode() + ' ' + PATH_OPS[stroke, fill, self._fillMode]) - - def clipPath(self, aPath, stroke=1, fill=0): - "clip as well as drawing" - gc = aPath.getCode(); pathops = PATH_OPS[stroke, fill, self._fillMode] - clip = (self._fillMode == FILL_EVEN_ODD and ' W* ' or ' W ') - item = "%s%s%s" % (gc, clip, pathops) # ensure string conversion - self._code.append(item) - #self._code.append( aPath.getCode() - # + (self._fillMode == FILL_EVEN_ODD and ' W* ' or ' W ') - # + PATH_OPS[stroke,fill,self._fillMode]) - - def beginText(self, x=0, y=0): - """Returns a fresh text object. Text objects are used - to add large amounts of text. See textobject.PDFTextObject""" - return textobject.PDFTextObject(self, x, y) - - def drawText(self, aTextObject): - """Draws a text object""" - self._code.append(str(aTextObject.getCode())) - - def setPageCompression(self, pageCompression=1): - """Possible values None, 1 or 0 - If None the value from rl_config will be used. - If on, the page data will be compressed, leading to much - smaller files, but takes a little longer to create the files. - This applies to all subsequent pages, or until setPageCompression() - is next called.""" - if pageCompression is None: pageCompression = rl_config.pageCompression - if pageCompression and not zlib: - self._pageCompression = 0 - else: - self._pageCompression = pageCompression - self._doc.setCompression(self._pageCompression) - - def setPageDuration(self, duration=None): - """Allows hands-off animation of presentations :-) - - If this is set to a number, in full screen mode, Acrobat Reader - will advance to the next page after this many seconds. The - duration of the transition itself (fade/flicker etc.) is controlled - by the 'duration' argument to setPageTransition; this controls - the time spent looking at the page. This is effective for all - subsequent pages.""" - - self._pageDuration = duration - - def setPageTransition(self, effectname=None, duration=1, - direction=0,dimension='H',motion='I'): - """PDF allows page transition effects for use when giving - presentations. There are six possible effects. You can - just guive the effect name, or supply more advanced options - to refine the way it works. There are three types of extra - argument permitted, and here are the allowed values: - direction_arg = [0,90,180,270] - dimension_arg = ['H', 'V'] - motion_arg = ['I','O'] (start at inside or outside) - - This table says which ones take which arguments: - - PageTransitionEffects = { - 'Split': [direction_arg, motion_arg], - 'Blinds': [dimension_arg], - 'Box': [motion_arg], - 'Wipe' : [direction_arg], - 'Dissolve' : [], - 'Glitter':[direction_arg] - } - Have fun! - """ - # This builds a Python dictionary with the right arguments - # for the Trans dictionary in the PDFPage object, - # and stores it in the variable _pageTransition. - # showPage later passes this to the setPageTransition method - # of the PDFPage object, which turns it to a PDFDictionary. - self._pageTransition = {} - if not effectname: - return - - #first check each optional argument has an allowed value - if direction in [0,90,180,270]: - direction_arg = ('Di', '/%d' % direction) - else: - raise 'PDFError', ' directions allowed are 0,90,180,270' - - if dimension in ['H', 'V']: - dimension_arg = ('Dm', '/' + dimension) - else: - raise'PDFError','dimension values allowed are H and V' - - if motion in ['I','O']: - motion_arg = ('M', '/' + motion) - else: - raise'PDFError','motion values allowed are I and O' - - # this says which effects require which argument types from above - PageTransitionEffects = { - 'Split': [direction_arg, motion_arg], - 'Blinds': [dimension_arg], - 'Box': [motion_arg], - 'Wipe' : [direction_arg], - 'Dissolve' : [], - 'Glitter':[direction_arg] - } - - try: - args = PageTransitionEffects[effectname] - except KeyError: - raise 'PDFError', 'Unknown Effect Name "%s"' % effectname - - # now build the dictionary - transDict = {} - transDict['Type'] = '/Trans' - transDict['D'] = '%d' % duration - transDict['S'] = '/' + effectname - for (key, value) in args: - transDict[key] = value - self._pageTransition = transDict - -if _instanceEscapePDF: - import new - Canvas._escape = new.instancemethod(_instanceEscapePDF,None,Canvas) - -if __name__ == '__main__': - print 'For test scripts, look in reportlab/test' diff --git a/bin/reportlab/pdfgen/pathobject.py b/bin/reportlab/pdfgen/pathobject.py deleted file mode 100644 index 9216eb317dd..00000000000 --- a/bin/reportlab/pdfgen/pathobject.py +++ /dev/null @@ -1,93 +0,0 @@ -#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/pdfgen/pathobject.py -__version__=''' $Id$ ''' -__doc__=""" -PDFPathObject is an efficient way to draw paths on a Canvas. Do not -instantiate directly, obtain one from the Canvas instead. - -Progress Reports: -8.83, 2000-01-13, gmcm: - created from pdfgen.py -""" - -import string -from reportlab.pdfgen import pdfgeom -from reportlab.lib.utils import fp_str - - -class PDFPathObject: - """Represents a graphic path. There are certain 'modes' to PDF - drawing, and making a separate object to expose Path operations - ensures they are completed with no run-time overhead. Ask - the Canvas for a PDFPath with getNewPathObject(); moveto/lineto/ - curveto wherever you want; add whole shapes; and then add it back - into the canvas with one of the relevant operators. - - Path objects are probably not long, so we pack onto one line""" - - def __init__(self): - self._code = [] - self._code.append('n') #newpath - - def getCode(self): - "pack onto one line; used internally" - return string.join(self._code, ' ') - - def moveTo(self, x, y): - self._code.append('%s m' % fp_str(x,y)) - - def lineTo(self, x, y): - self._code.append('%s l' % fp_str(x,y)) - - def curveTo(self, x1, y1, x2, y2, x3, y3): - self._code.append('%s c' % fp_str(x1, y1, x2, y2, x3, y3)) - - def arc(self, x1,y1, x2,y2, startAng=0, extent=90): - """Contributed to piddlePDF by Robert Kern, 28/7/99. - Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2, - starting at startAng degrees and covering extent degrees. Angles - start with 0 to the right (+x) and increase counter-clockwise. - These should have x1.""" - - pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent) - #move to first point - self._code.append('%s m' % fp_str(pointList[0][:2])) - for curve in pointList: - self._code.append('%s c' % fp_str(curve[2:])) - - def arcTo(self, x1,y1, x2,y2, startAng=0, extent=90): - """Like arc, but draws a line from the current point to - the start if the start is not the current point.""" - pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent) - self._code.append('%s l' % fp_str(pointList[0][:2])) - for curve in pointList: - self._code.append('%s c' % fp_str(curve[2:])) - - def rect(self, x, y, width, height): - """Adds a rectangle to the path""" - self._code.append('%s re' % fp_str((x, y, width, height))) - - def ellipse(self, x, y, width, height): - """adds an ellipse to the path""" - pointList = pdfgeom.bezierArc(x, y, x + width,y + height, 0, 360) - self._code.append('%s m' % fp_str(pointList[0][:2])) - for curve in pointList: - self._code.append('%s c' % fp_str(curve[2:])) - - def circle(self, x_cen, y_cen, r): - """adds a circle to the path""" - x1 = x_cen - r - #x2 = x_cen + r - y1 = y_cen - r - #y2 = y_cen + r - width = height = 2*r - #self.ellipse(x_cen - r, y_cen - r, x_cen + r, y_cen + r) - self.ellipse(x1, y1, width, height) - - def close(self): - "draws a line back to where it started" - self._code.append('h') diff --git a/bin/reportlab/pdfgen/pdfgeom.py b/bin/reportlab/pdfgen/pdfgeom.py deleted file mode 100644 index c9f4826d5c8..00000000000 --- a/bin/reportlab/pdfgen/pdfgeom.py +++ /dev/null @@ -1,77 +0,0 @@ -#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/pdfgen/pdfgeom.py -__version__=''' $Id$ ''' -__doc__=""" -This module includes any mathematical methods needed for PIDDLE. -It should have no dependencies beyond the Python library. - -So far, just Robert Kern's bezierArc. -""" - -from math import sin, cos, pi, ceil - - -def bezierArc(x1,y1, x2,y2, startAng=0, extent=90): - """bezierArc(x1,y1, x2,y2, startAng=0, extent=90) --> List of Bezier -curve control points. - -(x1, y1) and (x2, y2) are the corners of the enclosing rectangle. The -coordinate system has coordinates that increase to the right and down. -Angles, measured in degress, start with 0 to the right (the positive X -axis) and increase counter-clockwise. The arc extends from startAng -to startAng+extent. I.e. startAng=0 and extent=180 yields an openside-down -semi-circle. - -The resulting coordinates are of the form (x1,y1, x2,y2, x3,y3, x4,y4) -such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and -(x3, y3) as their respective Bezier control points.""" - - x1,y1, x2,y2 = min(x1,x2), max(y1,y2), max(x1,x2), min(y1,y2) - - if abs(extent) <= 90: - arcList = [startAng] - fragAngle = float(extent) - Nfrag = 1 - else: - arcList = [] - Nfrag = int(ceil(abs(extent)/90.)) - fragAngle = float(extent) / Nfrag - - x_cen = (x1+x2)/2. - y_cen = (y1+y2)/2. - rx = (x2-x1)/2. - ry = (y2-y1)/2. - halfAng = fragAngle * pi / 360. - kappa = abs(4. / 3. * (1. - cos(halfAng)) / sin(halfAng)) - - if fragAngle < 0: - sign = -1 - else: - sign = 1 - - pointList = [] - - for i in range(Nfrag): - theta0 = (startAng + i*fragAngle) * pi / 180. - theta1 = (startAng + (i+1)*fragAngle) *pi / 180. - if fragAngle > 0: - pointList.append((x_cen + rx * cos(theta0), - y_cen - ry * sin(theta0), - x_cen + rx * (cos(theta0) - kappa * sin(theta0)), - y_cen - ry * (sin(theta0) + kappa * cos(theta0)), - x_cen + rx * (cos(theta1) + kappa * sin(theta1)), - y_cen - ry * (sin(theta1) - kappa * cos(theta1)), - x_cen + rx * cos(theta1), - y_cen - ry * sin(theta1))) - else: - pointList.append((x_cen + rx * cos(theta0), - y_cen - ry * sin(theta0), - x_cen + rx * (cos(theta0) + kappa * sin(theta0)), - y_cen - ry * (sin(theta0) - kappa * cos(theta0)), - x_cen + rx * (cos(theta1) - kappa * sin(theta1)), - y_cen - ry * (sin(theta1) + kappa * cos(theta1)), - x_cen + rx * cos(theta1), - y_cen - ry * sin(theta1))) - - return pointList \ No newline at end of file diff --git a/bin/reportlab/pdfgen/pdfimages.py b/bin/reportlab/pdfgen/pdfimages.py deleted file mode 100644 index 7ada7640785..00000000000 --- a/bin/reportlab/pdfgen/pdfimages.py +++ /dev/null @@ -1,186 +0,0 @@ -#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/pdfgen/pdfimages.py -__version__=''' $Id$ ''' -__doc__=""" -Image functionality sliced out of canvas.py for generalization -""" - -import os -import string -from types import StringType -import reportlab -from reportlab.pdfbase import pdfutils -from reportlab.pdfbase import pdfdoc -from reportlab.lib.utils import fp_str, getStringIO -from reportlab.lib.utils import import_zlib, haveImages - - -class PDFImage: - """Wrapper around different "image sources". You can make images - from a PIL Image object, a filename (in which case it uses PIL), - an image we previously cached (optimisation, hardly used these - days) or a JPEG (which PDF supports natively).""" - - def __init__(self, image, x,y, width=None, height=None, caching=0): - self.image = image - self.point = (x,y) - self.dimensions = (width, height) - self.filename = None - self.imageCaching = caching - # the following facts need to be determined, - # whatever the source. Declare what they are - # here for clarity. - self.colorSpace = 'DeviceRGB' - self.bitsPerComponent = 8 - self.filters = [] - self.source = None # JPEG or PIL, set later - self.getImageData() - - def jpg_imagedata(self): - #directly process JPEG files - #open file, needs some error handling!! - fp = open(self.image, 'rb') - result = self._jpg_imagedata(fp) - fp.close() - return result - - def _jpg_imagedata(self,imageFile): - self.source = 'JPEG' - info = pdfutils.readJPEGInfo(imageFile) - imgwidth, imgheight = info[0], info[1] - if info[2] == 1: - colorSpace = 'DeviceGray' - elif info[2] == 3: - colorSpace = 'DeviceRGB' - else: #maybe should generate an error, is this right for CMYK? - colorSpace = 'DeviceCMYK' - imageFile.seek(0) #reset file pointer - imagedata = [] - #imagedata.append('BI /Width %d /Height /BitsPerComponent 8 /ColorSpace /%s /Filter [/Filter [ /ASCII85Decode /DCTDecode] ID' % (info[0], info[1], colorSpace)) - imagedata.append('BI /W %d /H %d /BPC 8 /CS /%s /F [/A85 /DCT] ID' % (imgwidth, imgheight, colorSpace)) - #write in blocks of (??) 60 characters per line to a list - compressed = imageFile.read() - encoded = pdfutils._AsciiBase85Encode(compressed) - pdfutils._chunker(encoded,imagedata) - imagedata.append('EI') - return (imagedata, imgwidth, imgheight) - - def cache_imagedata(self): - image = self.image - if not pdfutils.cachedImageExists(image): - zlib = import_zlib() - if not zlib: return - if not haveImages: return - pdfutils.cacheImageFile(image) - - #now we have one cached, slurp it in - cachedname = os.path.splitext(image)[0] + '.a85' - imagedata = open(cachedname,'rb').readlines() - #trim off newlines... - imagedata = map(string.strip, imagedata) - return imagedata - - def PIL_imagedata(self): - image = self.image - if image.format=='JPEG': - fp=image.fp - fp.seek(0) - return self._jpg_imagedata(fp) - self.source = 'PIL' - zlib = import_zlib() - if not zlib: return - myimage = image.convert('RGB') - imgwidth, imgheight = myimage.size - - # this describes what is in the image itself - # *NB* according to the spec you can only use the short form in inline images - #imagedata=['BI /Width %d /Height /BitsPerComponent 8 /ColorSpace /%s /Filter [/Filter [ /ASCII85Decode /FlateDecode] ID]' % (imgwidth, imgheight,'RGB')] - imagedata=['BI /W %d /H %d /BPC 8 /CS /RGB /F [/A85 /Fl] ID' % (imgwidth, imgheight)] - - #use a flate filter and Ascii Base 85 to compress - raw = myimage.tostring() - assert(len(raw) == imgwidth * imgheight, "Wrong amount of data for image") - compressed = zlib.compress(raw) #this bit is very fast... - encoded = pdfutils._AsciiBase85Encode(compressed) #...sadly this may not be - #append in blocks of 60 characters - pdfutils._chunker(encoded,imagedata) - imagedata.append('EI') - return (imagedata, imgwidth, imgheight) - - def getImageData(self): - "Gets data, height, width - whatever type of image" - image = self.image - (width, height) = self.dimensions - - if type(image) == StringType: - self.filename = image - if os.path.splitext(image)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']: - (imagedata, imgwidth, imgheight) = self.jpg_imagedata() - else: - if not self.imageCaching: - imagedata = pdfutils.cacheImageFile(image,returnInMemory=1) - else: - imagedata = self.cache_imagedata() - #parse line two for width, height - words = string.split(imagedata[1]) - imgwidth = string.atoi(words[1]) - imgheight = string.atoi(words[3]) - else: - import sys - if sys.platform[0:4] == 'java': - #jython, PIL not available - (imagedata, imgwidth, imgheight) = self.JAVA_imagedata() - else: - (imagedata, imgwidth, imgheight) = self.PIL_imagedata() - #now build the PDF for the image. - if not width: - width = imgwidth - if not height: - height = imgheight - self.width = width - self.height = height - self.imageData = imagedata - - def drawInlineImage(self, canvas): #, image, x,y, width=None,height=None): - """Draw an Image into the specified rectangle. If width and - height are omitted, they are calculated from the image size. - Also allow file names as well as images. This allows a - caching mechanism""" - (x,y) = self.point - # this says where and how big to draw it - if not canvas.bottomup: y = y+self.height - canvas._code.append('q %s 0 0 %s cm' % (fp_str(self.width), fp_str(self.height, x, y))) - # self._code.extend(imagedata) if >=python-1.5.2 - for line in self.imageData: - canvas._code.append(line) - canvas._code.append('Q') - - def format(self, document): - """Allow it to be used within pdfdoc framework. This only - defines how it is stored, not how it is drawn later.""" - - dict = pdfdoc.PDFDictionary() - dict['Type'] = '/XObject' - dict['Subtype'] = '/Image' - dict['Width'] = self.width - dict['Height'] = self.height - dict['BitsPerComponent'] = 8 - dict['ColorSpace'] = pdfdoc.PDFName(self.colorSpace) - content = string.join(self.imageData[3:-1], '\n') + '\n' - strm = pdfdoc.PDFStream(dictionary=dict, content=content) - return strm.format(document) - -if __name__=='__main__': - srcfile = os.path.join( - os.path.dirname(reportlab.__file__), - 'test', - 'pythonpowered.gif' - ) - assert os.path.isfile(srcfile), 'image not found' - pdfdoc.LongFormat = 1 - img = PDFImage(srcfile, 100, 100) - import pprint - doc = pdfdoc.PDFDocument() - print 'source=',img.source - print img.format(doc) diff --git a/bin/reportlab/pdfgen/pycanvas.py b/bin/reportlab/pdfgen/pycanvas.py deleted file mode 100644 index 8a6115c460c..00000000000 --- a/bin/reportlab/pdfgen/pycanvas.py +++ /dev/null @@ -1,309 +0,0 @@ -# a Pythonesque Canvas v0.8 -# Author : Jerome Alet - -# License : ReportLab's license -# -# $Id$ -# -__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code. - -pycanvas.Canvas class works exactly like canvas.Canvas, but you can -call str() on pycanvas.Canvas instances. Doing so will return the -Python source code equivalent to your own program, which would, when -run, produce the same PDF document as your original program. - -Generated Python source code defines a doIt() function which accepts -a filename or file-like object as its first parameter, and an -optional boolean parameter named "regenerate". - -The doIt() function will generate a PDF document and save it in the -file you specified in this argument. If the regenerate parameter is -set then it will also return an automatically generated equivalent -Python source code as a string of text, which you can run again to -produce the very same PDF document and the Python source code, which -you can run again... ad nauseam ! If the regenerate parameter is -unset or not used at all (it then defaults to being unset) then None -is returned and the doIt() function is much much faster, it is also -much faster than the original non-serialized program. - -the reportlab/test/test_pdfgen_pycanvas.py program is the test suite -for pycanvas, you can do the following to run it : - - First set verbose=1 in reportlab/rl_config.py - - then from the command interpreter : - - $ cd reportlab/test - $ python test_pdfgen_pycanvas.py >n1.py - - this will produce both n1.py and test_pdfgen_pycanvas.pdf - - then : - - $ python n1.py n1.pdf >n2.py - $ python n2.py n2.pdf >n3.py - $ ... - - n1.py, n2.py, n3.py and so on will be identical files. - they eventually may end being a bit different because of - rounding problems, mostly in the comments, but this - doesn't matter since the values really are the same - (e.g. 0 instead of 0.0, or .53 instead of 0.53) - - n1.pdf, n2.pdf, n3.pdf and so on will be PDF files - similar to test_pdfgen_pycanvas.pdf. - -Alternatively you can import n1.py (or n3.py, or n16384.py if you prefer) -in your own program, and then call its doIt function : - - import n1 - pythonsource = n1.doIt("myfile.pdf", regenerate=1) - -Or if you don't need the python source code and want a faster result : - - import n1 - n1.doIt("myfile.pdf") - -When the generated source code is run directly as an independant program, -then the equivalent python source code is printed to stdout, e.g. : - - python n1.py - - will print the python source code equivalent to n1.py - -Why would you want to use such a beast ? - - - To linearize (serialize?) a program : optimizing some complex - parts for example. - - - To debug : reading the generated Python source code may help you or - the ReportLab team to diagnose problems. The generated code is now - clearly commented and shows nesting levels, page numbers, and so - on. You can use the generated script when asking for support : we - can see the results you obtain without needing your datas or complete - application. - - - To create standalone scripts : say your program uses a high level - environment to generate its output (databases, RML, etc...), using - this class would give you an equivalent program but with complete - independance from the high level environment (e.g. if you don't - have Oracle). - - - To contribute some nice looking PDF documents to the ReportLab website - without having to send a complete application you don't want to - distribute. - - - ... Insert your own ideas here ... - - - For fun because you can do it ! -""" - -import cStringIO -from reportlab.pdfgen import canvas -from reportlab.pdfgen import pathobject -from reportlab.pdfgen import textobject - -PyHeader = '''#! /usr/bin/env python - -# -# This code was entirely generated by ReportLab (http://www.reportlab.com) -# - -import sys -from reportlab.pdfgen import pathobject -from reportlab.pdfgen import textobject -from reportlab.lib.colors import Color - -def doIt(file, regenerate=0) : - """Generates a PDF document, save it into file. - - file : either a filename or a file-like object. - - regenerate : if set then this function returns the Python source - code which when run will produce the same result. - if unset then this function returns None, and is - much faster. - """ - if regenerate : - from reportlab.pdfgen.pycanvas import Canvas - else : - from reportlab.pdfgen.canvas import Canvas -''' - -PyFooter = ''' - # if we want the equivalent Python source code, then send it back - if regenerate : - return str(c) - -if __name__ == "__main__" : - if len(sys.argv) != 2 : - # second argument must be the name of the PDF file to create - sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0]) - sys.exit(-1) - else : - # we've got a filename, we can proceed. - print doIt(sys.argv[1], regenerate=1) - sys.exit(0)''' - -def buildargs(*args, **kwargs) : - """Constructs a printable list of arguments suitable for use in source function calls.""" - arguments = "" - for arg in args : - arguments = arguments + ("%s, " % repr(arg)) - for (kw, val) in kwargs.items() : - arguments = arguments+ ("%s=%s, " % (kw, repr(val))) - if arguments[-2:] == ", " : - arguments = arguments[:-2] - return arguments - -class PDFAction : - """Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject).""" - def __init__(self, parent, action) : - """Saves a pointer to the parent object, and the method name.""" - self._parent = parent - self._action = action - - def __getattr__(self, name) : - """Probably a method call on an attribute, returns the real one.""" - return getattr(getattr(self._parent._object, self._action), name) - - def __call__(self, *args, **kwargs) : - """The fake method is called, print it then call the real one.""" - if not self._parent._parent._in : - self._precomment() - self._parent._parent._PyWrite(" %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs))) - self._postcomment() - self._parent._parent._in = self._parent._parent._in + 1 - retcode = apply(getattr(self._parent._object, self._action), args, kwargs) - self._parent._parent._in = self._parent._parent._in - 1 - return retcode - - def __hash__(self) : - return hash(getattr(self._parent._object, self._action)) - - def __coerce__(self, other) : - """Needed.""" - return coerce(getattr(self._parent._object, self._action), other) - - def _precomment(self) : - """To be overriden.""" - pass - - def _postcomment(self) : - """To be overriden.""" - pass - -class PDFObject : - """Base class for PDF objects like PDFPathObject and PDFTextObject.""" - _number = 0 - def __init__(self, parent) : - """Saves a pointer to the parent Canvas.""" - self._parent = parent - self._initdone = 0 - - def __getattr__(self, name) : - """The user's programs wants to call one of our methods or get an attribute, fake it.""" - return PDFAction(self, name) - - def __repr__(self) : - """Returns the name used in the generated source code (e.g. 'p' or 't').""" - return self._name - - def __call__(self, *args, **kwargs) : - """Real object initialisation is made here, because now we've got the arguments.""" - if not self._initdone : - self.__class__._number = self.__class__._number + 1 - methodname = apply(self._postinit, args, kwargs) - self._parent._PyWrite("\n # create PDF%sObject number %i\n %s = %s.%s(%s)" % (methodname[5:], self.__class__._number, self._name, self._parent._name, methodname, apply(buildargs, args, kwargs))) - self._initdone = 1 - return self - -class Canvas : - """Our fake Canvas class, which will intercept each and every method or attribute access.""" - class TextObject(PDFObject) : - _name = "t" - def _postinit(self, *args, **kwargs) : - self._object = apply(textobject.PDFTextObject, (self._parent, ) + args, kwargs) - return "beginText" - - class PathObject(PDFObject) : - _name = "p" - def _postinit(self, *args, **kwargs) : - self._object = apply(pathobject.PDFPathObject, args, kwargs) - return "beginPath" - - class Action(PDFAction) : - """Class called for every Canvas method call.""" - def _precomment(self) : - """Outputs comments before the method call.""" - if self._action == "showPage" : - self._parent._PyWrite("\n # Ends page %i" % self._parent._pagenumber) - elif self._action == "saveState" : - state = {} - d = self._parent._object.__dict__ - for name in self._parent._object.STATE_ATTRIBUTES: - state[name] = d[name] - self._parent._PyWrite("\n # Saves context level %i %s" % (self._parent._contextlevel, state)) - self._parent._contextlevel = self._parent._contextlevel + 1 - elif self._action == "restoreState" : - self._parent._contextlevel = self._parent._contextlevel - 1 - self._parent._PyWrite("\n # Restores context level %i %s" % (self._parent._contextlevel, self._parent._object.state_stack[-1])) - elif self._action == "beginForm" : - self._parent._formnumber = self._parent._formnumber + 1 - self._parent._PyWrite("\n # Begins form %i" % self._parent._formnumber) - elif self._action == "endForm" : - self._parent._PyWrite("\n # Ends form %i" % self._parent._formnumber) - elif self._action == "save" : - self._parent._PyWrite("\n # Saves the PDF document to disk") - - def _postcomment(self) : - """Outputs comments after the method call.""" - if self._action == "showPage" : - self._parent._pagenumber = self._parent._pagenumber + 1 - self._parent._PyWrite("\n # Begins page %i" % self._parent._pagenumber) - elif self._action in [ "endForm", "drawPath", "clipPath" ] : - self._parent._PyWrite("") - - _name = "c" - def __init__(self, *args, **kwargs) : - """Initialize and begins source code.""" - self._parent = self # nice trick, isn't it ? - self._in = 0 - self._contextlevel = 0 - self._pagenumber = 1 - self._formnumber = 0 - self._footerpresent = 0 - self._object = apply(canvas.Canvas, args, kwargs) - self._pyfile = cStringIO.StringIO() - self._PyWrite(PyHeader) - try : - del kwargs["filename"] - except KeyError : - pass - self._PyWrite(" # create the PDF document\n %s = Canvas(file, %s)\n\n # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs))) - - def __nonzero__(self) : - """This is needed by platypus' tables.""" - return 1 - - def __str__(self) : - """Returns the equivalent Python source code.""" - if not self._footerpresent : - self._PyWrite(PyFooter) - self._footerpresent = 1 - return self._pyfile.getvalue() - - def __getattr__(self, name) : - """Method or attribute access.""" - if name == "beginPath" : - return self.PathObject(self) - elif name == "beginText" : - return self.TextObject(self) - else : - return self.Action(self, name) - - def _PyWrite(self, pycode) : - """Outputs the source code with a trailing newline.""" - self._pyfile.write("%s\n" % pycode) - -if __name__ == '__main__': - print 'For test scripts, look in reportlab/test' diff --git a/bin/reportlab/pdfgen/textobject.py b/bin/reportlab/pdfgen/textobject.py deleted file mode 100644 index b859dc146d4..00000000000 --- a/bin/reportlab/pdfgen/textobject.py +++ /dev/null @@ -1,360 +0,0 @@ -#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/pdfgen/textobject.py -__version__=''' $Id$ ''' -__doc__=""" -PDFTextObject is an efficient way to add text to a Canvas. Do not -instantiate directly, obtain one from the Canvas instead. - -Progress Reports: -8.83, 2000-01-13, gmcm: - created from pdfgen.py -""" - -import string -from types import * -from reportlab.lib import colors -from reportlab.lib.colors import ColorType -from reportlab.lib.utils import fp_str -from reportlab.pdfbase import pdfmetrics - -_SeqTypes=(TupleType,ListType) - - -class PDFTextObject: - """PDF logically separates text and graphics drawing; text - operations need to be bracketed between BT (Begin text) and - ET operators. This class ensures text operations are - properly encapusalted. Ask the canvas for a text object - with beginText(x, y). Do not construct one directly. - Do not use multiple text objects in parallel; PDF is - not multi-threaded! - - It keeps track of x and y coordinates relative to its origin.""" - - def __init__(self, canvas, x=0,y=0): - self._code = ['BT'] #no point in [] then append RGB - self._canvas = canvas #canvas sets this so it has access to size info - self._fontname = self._canvas._fontname - self._fontsize = self._canvas._fontsize - self._leading = self._canvas._leading - font = pdfmetrics.getFont(self._fontname) - self._dynamicFont = getattr(font, '_dynamicFont', 0) - self._curSubset = -1 - - self.setTextOrigin(x, y) - - def getCode(self): - "pack onto one line; used internally" - self._code.append('ET') - return string.join(self._code, ' ') - - def setTextOrigin(self, x, y): - if self._canvas.bottomup: - self._code.append('1 0 0 1 %s Tm' % fp_str(x, y)) #bottom up - else: - self._code.append('1 0 0 -1 %s Tm' % fp_str(x, y)) #top down - - # The current cursor position is at the text origin - self._x0 = self._x = x - self._y0 = self._y = y - - def setTextTransform(self, a, b, c, d, e, f): - "Like setTextOrigin, but does rotation, scaling etc." - if not self._canvas.bottomup: - c = -c #reverse bottom row of the 2D Transform - d = -d - self._code.append('%s Tm' % fp_str(a, b, c, d, e, f)) - - # The current cursor position is at the text origin Note that - # we aren't keeping track of all the transform on these - # coordinates: they are relative to the rotations/sheers - # defined in the matrix. - self._x0 = self._x = e - self._y0 = self._y = f - - def moveCursor(self, dx, dy): - - """Starts a new line at an offset dx,dy from the start of the - current line. This does not move the cursor relative to the - current position, and it changes the current offset of every - future line drawn (i.e. if you next do a textLine() call, it - will move the cursor to a position one line lower than the - position specificied in this call. """ - - # Check if we have a previous move cursor call, and combine - # them if possible. - if self._code and self._code[-1][-3:]==' Td': - L = string.split(self._code[-1]) - if len(L)==3: - del self._code[-1] - else: - self._code[-1] = string.join(L[:-4]) - - # Work out the last movement - lastDx = float(L[-3]) - lastDy = float(L[-2]) - - # Combine the two movement - dx += lastDx - dy -= lastDy - - # We will soon add the movement to the line origin, so if - # we've already done this for lastDx, lastDy, remove it - # first (so it will be right when added back again). - self._x0 -= lastDx - self._y0 -= lastDy - - # Output the move text cursor call. - self._code.append('%s Td' % fp_str(dx, -dy)) - - # Keep track of the new line offsets and the cursor position - self._x0 += dx - self._y0 += dy - self._x = self._x0 - self._y = self._y0 - - def setXPos(self, dx): - """Starts a new line dx away from the start of the - current line - NOT from the current point! So if - you call it in mid-sentence, watch out.""" - self.moveCursor(dx,0) - - def getCursor(self): - """Returns current text position relative to the last origin.""" - return (self._x, self._y) - - def getStartOfLine(self): - """Returns a tuple giving the text position of the start of the - current line.""" - return (self._x0, self._y0) - - def getX(self): - """Returns current x position relative to the last origin.""" - return self._x - - def getY(self): - """Returns current y position relative to the last origin.""" - return self._y - - def _setFont(self, psfontname, size): - """Sets the font and fontSize - Raises a readable exception if an illegal font - is supplied. Font names are case-sensitive! Keeps track - of font anme and size for metrics.""" - self._fontname = psfontname - self._fontsize = size - font = pdfmetrics.getFont(self._fontname) - self._dynamicFont = getattr(font, '_dynamicFont', 0) - if self._dynamicFont: - self._curSubset = -1 - else: - pdffontname = self._canvas._doc.getInternalFontName(psfontname) - self._code.append('%s %s Tf' % (pdffontname, fp_str(size))) - - def setFont(self, psfontname, size, leading = None): - """Sets the font. If leading not specified, defaults to 1.2 x - font size. Raises a readable exception if an illegal font - is supplied. Font names are case-sensitive! Keeps track - of font anme and size for metrics.""" - self._fontname = psfontname - self._fontsize = size - if leading is None: - leading = size * 1.2 - self._leading = leading - font = pdfmetrics.getFont(self._fontname) - self._dynamicFont = getattr(font, '_dynamicFont', 0) - if self._dynamicFont: - self._curSubset = -1 - else: - pdffontname = self._canvas._doc.getInternalFontName(psfontname) - self._code.append('%s %s Tf %s TL' % (pdffontname, fp_str(size), fp_str(leading))) - - def setCharSpace(self, charSpace): - """Adjusts inter-character spacing""" - self._charSpace = charSpace - self._code.append('%s Tc' % fp_str(charSpace)) - - def setWordSpace(self, wordSpace): - """Adjust inter-word spacing. This can be used - to flush-justify text - you get the width of the - words, and add some space between them.""" - self._wordSpace = wordSpace - self._code.append('%s Tw' % fp_str(wordSpace)) - - def setHorizScale(self, horizScale): - "Stretches text out horizontally" - self._horizScale = 100 + horizScale - self._code.append('%s Tz' % fp_str(horizScale)) - - def setLeading(self, leading): - "How far to move down at the end of a line." - self._leading = leading - self._code.append('%s TL' % fp_str(leading)) - - def setTextRenderMode(self, mode): - """Set the text rendering mode. - - 0 = Fill text - 1 = Stroke text - 2 = Fill then stroke - 3 = Invisible - 4 = Fill text and add to clipping path - 5 = Stroke text and add to clipping path - 6 = Fill then stroke and add to clipping path - 7 = Add to clipping path""" - - assert mode in (0,1,2,3,4,5,6,7), "mode must be in (0,1,2,3,4,5,6,7)" - self._textRenderMode = mode - self._code.append('%d Tr' % mode) - - def setRise(self, rise): - "Move text baseline up or down to allow superscrip/subscripts" - self._rise = rise - self._y = self._y - rise # + ? _textLineMatrix? - self._code.append('%s Ts' % fp_str(rise)) - - def setStrokeColorRGB(self, r, g, b): - self._strokeColorRGB = (r, g, b) - self._code.append('%s RG' % fp_str(r,g,b)) - - def setFillColorRGB(self, r, g, b): - self._fillColorRGB = (r, g, b) - self._code.append('%s rg' % fp_str(r,g,b)) - - def setFillColorCMYK(self, c, m, y, k): - """Takes 4 arguments between 0.0 and 1.0""" - self._fillColorCMYK = (c, m, y, k) - self._code.append('%s k' % fp_str(c, m, y, k)) - - def setStrokeColorCMYK(self, c, m, y, k): - """Takes 4 arguments between 0.0 and 1.0""" - self._strokeColorCMYK = (c, m, y, k) - self._code.append('%s K' % fp_str(c, m, y, k)) - - def setFillColor(self, aColor): - """Takes a color object, allowing colors to be referred to by name""" - if type(aColor) == ColorType: - rgb = (aColor.red, aColor.green, aColor.blue) - self._fillColorRGB = rgb - self._code.append('%s rg' % fp_str(rgb) ) - elif type(aColor) in _SeqTypes: - l = len(aColor) - if l==3: - self._fillColorRGB = aColor - self._code.append('%s rg' % fp_str(aColor) ) - elif l==4: - self.setFillColorCMYK(self, aColor[0], aColor[1], aColor[2], aColor[3]) - else: - raise 'Unknown color', str(aColor) - else: - raise 'Unknown color', str(aColor) - - def setStrokeColor(self, aColor): - """Takes a color object, allowing colors to be referred to by name""" - if type(aColor) == ColorType: - rgb = (aColor.red, aColor.green, aColor.blue) - self._strokeColorRGB = rgb - self._code.append('%s RG' % fp_str(rgb) ) - elif type(aColor) in _SeqTypes: - l = len(aColor) - if l==3: - self._strokeColorRGB = aColor - self._code.append('%s RG' % fp_str(aColor) ) - elif l==4: - self.setStrokeColorCMYK(self, aColor[0], aColor[1], aColor[2], aColor[3]) - else: - raise 'Unknown color', str(aColor) - else: - raise 'Unknown color', str(aColor) - - def setFillGray(self, gray): - """Sets the gray level; 0.0=black, 1.0=white""" - self._fillColorRGB = (gray, gray, gray) - self._code.append('%s g' % fp_str(gray)) - - def setStrokeGray(self, gray): - """Sets the gray level; 0.0=black, 1.0=white""" - self._strokeColorRGB = (gray, gray, gray) - self._code.append('%s G' % fp_str(gray)) - - def _formatText(self, text): - "Generates PDF text output operator(s)" - if self._dynamicFont: - #it's a truetype font and should be utf8. If an error is raised, - - results = [] - font = pdfmetrics.getFont(self._fontname) - try: #assume UTF8 - stuff = font.splitString(text, self._canvas._doc) - except UnicodeDecodeError: - #assume latin1 as fallback - from reportlab.pdfbase.ttfonts import latin1_to_utf8 - from reportlab.lib.logger import warnOnce - warnOnce('non-utf8 data fed to truetype font, assuming latin-1 data') - text = latin1_to_utf8(text) - stuff = font.splitString(text, self._canvas._doc) - for subset, chunk in stuff: - if subset != self._curSubset: - pdffontname = font.getSubsetInternalName(subset, self._canvas._doc) - results.append("%s %s Tf %s TL" % (pdffontname, fp_str(self._fontsize), fp_str(self._leading))) - self._curSubset = subset - chunk = self._canvas._escape(chunk) - results.append("(%s) Tj" % chunk) - return string.join(results, ' ') - else: - text = self._canvas._escape(text) - return "(%s) Tj" % text - - def _textOut(self, text, TStar=0): - "prints string at current point, ignores text cursor" - self._code.append('%s%s' % (self._formatText(text), (TStar and ' T*' or ''))) - - def textOut(self, text): - """prints string at current point, text cursor moves across.""" - self._x = self._x + self._canvas.stringWidth(text, self._fontname, self._fontsize) - self._code.append(self._formatText(text)) - - def textLine(self, text=''): - """prints string at current point, text cursor moves down. - Can work with no argument to simply move the cursor down.""" - - # Update the coordinates of the cursor - self._x = self._x0 - if self._canvas.bottomup: - self._y = self._y - self._leading - else: - self._y = self._y + self._leading - - # Update the location of the start of the line - # self._x0 is unchanged - self._y0 = self._y - - # Output the text followed by a PDF newline command - self._code.append('%s T*' % self._formatText(text)) - - def textLines(self, stuff, trim=1): - """prints multi-line or newlined strings, moving down. One - comon use is to quote a multi-line block in your Python code; - since this may be indented, by default it trims whitespace - off each line and from the beginning; set trim=0 to preserve - whitespace.""" - if type(stuff) == StringType: - lines = string.split(string.strip(stuff), '\n') - if trim==1: - lines = map(string.strip,lines) - elif type(stuff) == ListType: - lines = stuff - elif type(stuff) == TupleType: - lines = stuff - else: - assert 1==0, "argument to textlines must be string,, list or tuple" - - # Output each line one at a time. This used to be a long-hand - # copy of the textLine code, now called as a method. - for line in lines: - self.textLine(line) - - def __nonzero__(self): - 'PDFTextObject is true if it has something done after the init' - return self._code != ['BT'] diff --git a/bin/reportlab/platypus/__init__.py b/bin/reportlab/platypus/__init__.py deleted file mode 100644 index dffc03703ee..00000000000 --- a/bin/reportlab/platypus/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#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/platypus/__init__.py -__version__=''' $Id$ ''' -__doc__='' -from reportlab.platypus.flowables import Flowable, Image, Macro, PageBreak, Preformatted, Spacer, XBox, \ - CondPageBreak, KeepTogether, TraceInfo, FailOnWrap, FailOnDraw, PTOContainer -from reportlab.platypus.paragraph import Paragraph, cleanBlockQuotedText, ParaLines -from reportlab.platypus.paraparser import ParaFrag -from reportlab.platypus.tables import Table, TableStyle, CellStyle, LongTable -from reportlab.platypus.frames import Frame -from reportlab.platypus.doctemplate import BaseDocTemplate, NextPageTemplate, PageTemplate, ActionFlowable, \ - SimpleDocTemplate, FrameBreak, PageBegin, Indenter -from xpreformatted import XPreformatted diff --git a/bin/reportlab/platypus/doctemplate.py b/bin/reportlab/platypus/doctemplate.py deleted file mode 100644 index 9e3e48524ab..00000000000 --- a/bin/reportlab/platypus/doctemplate.py +++ /dev/null @@ -1,897 +0,0 @@ -#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/platypus/doctemplate.py - -__version__=''' $Id$ ''' - -__doc__=""" -This module contains the core structure of platypus. - -Platypus constructs documents. Document styles are determined by DocumentTemplates. - -Each DocumentTemplate contains one or more PageTemplates which defines the look of the -pages of the document. - -Each PageTemplate has a procedure for drawing the "non-flowing" part of the page -(for example the header, footer, page number, fixed logo graphic, watermark, etcetera) and -a set of Frames which enclose the flowing part of the page (for example the paragraphs, -tables, or non-fixed diagrams of the text). - -A document is built when a DocumentTemplate is fed a sequence of Flowables. -The action of the build consumes the flowables in order and places them onto -frames on pages as space allows. When a frame runs out of space the next frame -of the page is used. If no frame remains a new page is created. A new page -can also be created if a page break is forced. - -The special invisible flowable NextPageTemplate can be used to specify -the page template for the next page (which by default is the one being used -for the current frame). -""" - -from reportlab.platypus.flowables import * -from reportlab.platypus.paragraph import Paragraph -from reportlab.platypus.frames import Frame -from reportlab.rl_config import defaultPageSize, verbose -import reportlab.lib.sequencer - -from types import * -import sys - -class LayoutError(Exception): - pass - -def _doNothing(canvas, doc): - "Dummy callback for onPage" - pass - - -class IndexingFlowable(Flowable): - """Abstract interface definition for flowables which might - hold references to other pages or themselves be targets - of cross-references. XRefStart, XRefDest, Table of Contents, - Indexes etc.""" - def isIndexing(self): - return 1 - - def isSatisfied(self): - return 1 - - def notify(self, kind, stuff): - """This will be called by the framework wherever 'stuff' happens. - 'kind' will be a value that can be used to decide whether to - pay attention or not.""" - pass - - def beforeBuild(self): - """Called by multiBuild before it starts; use this to clear - old contents""" - pass - - def afterBuild(self): - """Called after build ends but before isSatisfied""" - pass - - -class ActionFlowable(Flowable): - '''This Flowable is never drawn, it can be used for data driven controls - For example to change a page template (from one column to two, for example) - use NextPageTemplate which creates an ActionFlowable. - ''' - def __init__(self,action=()): - if type(action) not in (ListType, TupleType): - action = (action,) - self.action = tuple(action) - - def wrap(self, availWidth, availHeight): - '''Should never be called.''' - raise NotImplementedError - - def draw(self): - '''Should never be called.''' - raise NotImplementedError - - def apply(self,doc): - ''' - This is called by the doc.build processing to allow the instance to - implement its behaviour - ''' - action = self.action[0] - args = tuple(self.action[1:]) - arn = 'handle_'+action - try: - apply(getattr(doc,arn), args) - except AttributeError, aerr: - if aerr.args[0]==arn: - raise NotImplementedError, "Can't handle ActionFlowable(%s)" % action - else: - raise - except "bogus": - t, v, unused = sys.exc_info() - raise t, "%s\n handle_%s args=%s"%(v,action,args) - - def __call__(self): - return self - - def identity(self, maxLen=None): - return "ActionFlowable: %s" % str(self.action) - -class NextFrameFlowable(ActionFlowable): - def __init__(self,ix,resume=0): - ActionFlowable.__init__(self,('nextFrame',ix,resume)) - -class CurrentFrameFlowable(ActionFlowable): - def __init__(self,ix,resume=0): - ActionFlowable.__init__(self,('currentFrame',ix,resume)) - -class _FrameBreak(ActionFlowable): - ''' - A special ActionFlowable that allows setting doc._nextFrameIndex - - eg story.append(FrameBreak('mySpecialFrame')) - ''' - def __call__(self,ix=None,resume=0): - r = self.__class__(self.action+(resume,)) - r._ix = ix - return r - - def apply(self,doc): - if getattr(self,'_ix',None): doc._nextFrameIndex = self._ix - ActionFlowable.apply(self,doc) - -FrameBreak = _FrameBreak('frameEnd') -PageBegin = ActionFlowable('pageBegin') - -def _evalMeasurement(n): - if type(n) is type(''): - from paraparser import _num - n = _num(n) - if type(n) is type(()): n = n[1] - return n - -class Indenter(ActionFlowable): - """Increases or decreases left and right margins of frame. - - This allows one to have a 'context-sensitive' indentation - and makes nested lists way easier. - """ - - def __init__(self, left=0, right=0): - self.left = _evalMeasurement(left) - self.right = _evalMeasurement(right) - - def apply(self, doc): - doc.frame._leftExtraIndent = doc.frame._leftExtraIndent + self.left - doc.frame._rightExtraIndent = doc.frame._rightExtraIndent + self.right - - -class NextPageTemplate(ActionFlowable): - """When you get to the next page, use the template specified (change to two column, for example) """ - def __init__(self,pt): - ActionFlowable.__init__(self,('nextPageTemplate',pt)) - - -class PageTemplate: - """ - essentially a list of Frames and an onPage routine to call at the start - of a page when this is selected. onPageEnd gets called at the end. - derived classes can also implement beforeDrawPage and afterDrawPage if they want - """ - def __init__(self,id=None,frames=[],onPage=_doNothing, onPageEnd=_doNothing, - pagesize=None): - if type(frames) not in (ListType,TupleType): frames = [frames] - assert filter(lambda x: not isinstance(x,Frame), frames)==[], "frames argument error" - self.id = id - self.frames = frames - self.onPage = onPage - self.onPageEnd = onPageEnd - self.pagesize = pagesize - - def beforeDrawPage(self,canv,doc): - """Override this if you want additional functionality or prefer - a class based page routine. Called before any flowables for - this page are processed.""" - pass - - def checkPageSize(self,canv,doc): - '''This gets called by the template framework - If canv size != template size then the canv size is set to - the template size or if that's not available to the - doc size. - ''' - #### NEVER EVER EVER COMPARE FLOATS FOR EQUALITY - #RGB converting pagesizes to ints means we are accurate to one point - #RGB I suggest we should be aiming a little better - cp = None - dp = None - sp = None - if canv._pagesize: cp = map(int, canv._pagesize) - if self.pagesize: sp = map(int, self.pagesize) - if doc.pagesize: dp = map(int, doc.pagesize) - if cp!=sp: - if sp: - canv.setPageSize(self.pagesize) - elif cp!=dp: - canv.setPageSize(doc.pagesize) - - def afterDrawPage(self, canv, doc): - """This is called after the last flowable for the page has - been processed. You might use this if the page header or - footer needed knowledge of what flowables were drawn on - this page.""" - pass - - -class BaseDocTemplate: - """ - First attempt at defining a document template class. - - The basic idea is simple. - 0) The document has a list of data associated with it - this data should derive from flowables. We'll have - special classes like PageBreak, FrameBreak to do things - like forcing a page end etc. - - 1) The document has one or more page templates. - - 2) Each page template has one or more frames. - - 3) The document class provides base methods for handling the - story events and some reasonable methods for getting the - story flowables into the frames. - - 4) The document instances can override the base handler routines. - - Most of the methods for this class are not called directly by the user, - but in some advanced usages they may need to be overridden via subclassing. - - EXCEPTION: doctemplate.build(...) must be called for most reasonable uses - since it builds a document using the page template. - - Each document template builds exactly one document into a file specified - by the filename argument on initialization. - - Possible keyword arguments for the initialization: - - pageTemplates: A list of templates. Must be nonempty. Names - assigned to the templates are used for referring to them so no two used - templates should have the same name. For example you might want one template - for a title page, one for a section first page, one for a first page of - a chapter and two more for the interior of a chapter on odd and even pages. - If this argument is omitted then at least one pageTemplate should be provided - using the addPageTemplates method before the document is built. - pageSize: a 2-tuple or a size constant from reportlab/lib/pagesizes.pu. - Used by the SimpleDocTemplate subclass which does NOT accept a list of - pageTemplates but makes one for you; ignored when using pageTemplates. - - showBoundary: if set draw a box around the frame boundaries. - leftMargin: - rightMargin: - topMargin: - bottomMargin: Margin sizes in points (default 1 inch) - These margins may be overridden by the pageTemplates. They are primarily of interest - for the SimpleDocumentTemplate subclass. - allowSplitting: If set flowables (eg, paragraphs) may be split across frames or pages - (default: 1) - title: Internal title for document (does not automatically display on any page) - author: Internal author for document (does not automatically display on any page) - """ - _initArgs = { 'pagesize':defaultPageSize, - 'pageTemplates':[], - 'showBoundary':0, - 'leftMargin':inch, - 'rightMargin':inch, - 'topMargin':inch, - 'bottomMargin':inch, - 'allowSplitting':1, - 'title':None, - 'author':None, - 'invariant':None, - '_pageBreakQuick':1} - _invalidInitArgs = () - _firstPageTemplateIndex = 0 - - def __init__(self, filename, **kw): - """create a document template bound to a filename (see class documentation for keyword arguments)""" - self.filename = filename - - for k in self._initArgs.keys(): - if not kw.has_key(k): - v = self._initArgs[k] - else: - if k in self._invalidInitArgs: - raise ValueError, "Invalid argument %s" % k - v = kw[k] - setattr(self,k,v) - #print "pagesize is", self.pagesize - - p = self.pageTemplates - self.pageTemplates = [] - self.addPageTemplates(p) - - # facility to assist multi-build and cross-referencing. - # various hooks can put things into here - key is what - # you want, value is a page number. This can then be - # passed to indexing flowables. - self._pageRefs = {} - self._indexingFlowables = [] - - - #callback facility for progress monitoring - self._onPage = None - self._onProgress = None - self._flowableCount = 0 # so we know how far to go - - - #infinite loop detection if we start doing lots of empty pages - self._curPageFlowableCount = 0 - self._emptyPages = 0 - self._emptyPagesAllowed = 10 - - #context sensitive margins - set by story, not from outside - self._leftExtraIndent = 0.0 - self._rightExtraIndent = 0.0 - - self._calc() - self.afterInit() - - def _calc(self): - self._rightMargin = self.pagesize[0] - self.rightMargin - self._topMargin = self.pagesize[1] - self.topMargin - self.width = self._rightMargin - self.leftMargin - self.height = self._topMargin - self.bottomMargin - - def setPageCallBack(self, func): - 'Simple progress monitor - func(pageNo) called on each new page' - self._onPage = func - - def setProgressCallBack(self, func): - '''Cleverer progress monitor - func(typ, value) called regularly''' - self._onProgress = func - - def clean_hanging(self): - 'handle internal postponed actions' - while len(self._hanging): - self.handle_flowable(self._hanging) - - def addPageTemplates(self,pageTemplates): - 'add one or a sequence of pageTemplates' - if type(pageTemplates) not in (ListType,TupleType): - pageTemplates = [pageTemplates] - #this test below fails due to inconsistent imports! - #assert filter(lambda x: not isinstance(x,PageTemplate), pageTemplates)==[], "pageTemplates argument error" - for t in pageTemplates: - self.pageTemplates.append(t) - - def handle_documentBegin(self): - '''implement actions at beginning of document''' - self._hanging = [PageBegin] - self.pageTemplate = self.pageTemplates[self._firstPageTemplateIndex] - self.page = 0 - self.beforeDocument() - - def handle_pageBegin(self): - '''Perform actions required at beginning of page. - shouldn't normally be called directly''' - self.page = self.page + 1 - self.pageTemplate.beforeDrawPage(self.canv,self) - self.pageTemplate.checkPageSize(self.canv,self) - self.pageTemplate.onPage(self.canv,self) - for f in self.pageTemplate.frames: f._reset() - self.beforePage() - #keep a count of flowables added to this page. zero indicates bad stuff - self._curPageFlowableCount = 0 - if hasattr(self,'_nextFrameIndex'): - del self._nextFrameIndex - self.frame = self.pageTemplate.frames[0] - self.handle_frameBegin() - - def handle_pageEnd(self): - ''' show the current page - check the next page template - hang a page begin - ''' - #detect infinite loops... - if self._curPageFlowableCount == 0: - self._emptyPages = self._emptyPages + 1 - else: - self._emptyPages = 0 - if self._emptyPages >= self._emptyPagesAllowed: - if 1: - raise LayoutError("More than %d pages generated without content - halting layout. Likely that a flowable is too large for any frame." % self._emptyPagesAllowed) - else: - pass #attempt to restore to good state - else: - if self._onProgress: - self._onProgress('PAGE', self.canv.getPageNumber()) - self.pageTemplate.afterDrawPage(self.canv, self) - self.pageTemplate.onPageEnd(self.canv, self) - self.afterPage() - self.canv.showPage() - if hasattr(self,'_nextPageTemplateIndex'): - self.pageTemplate = self.pageTemplates[self._nextPageTemplateIndex] - del self._nextPageTemplateIndex - if self._emptyPages==0: - pass #store good state here - self._hanging.append(PageBegin) - - def handle_pageBreak(self,slow=None): - '''some might choose not to end all the frames''' - if self._pageBreakQuick and not slow: - self.handle_pageEnd() - else: - n = len(self._hanging) - while len(self._hanging)==n: - self.handle_frameEnd() - - def handle_frameBegin(self,resume=0): - '''What to do at the beginning of a frame''' - f = self.frame - if f._atTop: - if self.showBoundary or self.frame.showBoundary: - self.frame.drawBoundary(self.canv) - f._leftExtraIndent = self._leftExtraIndent - f._rightExtraIndent = self._rightExtraIndent - - def handle_frameEnd(self,resume=0): - ''' Handles the semantics of the end of a frame. This includes the selection of - the next frame or if this is the last frame then invoke pageEnd. - ''' - - self._leftExtraIndent = self.frame._leftExtraIndent - self._rightExtraIndent = self.frame._rightExtraIndent - - if hasattr(self,'_nextFrameIndex'): - frame = self.pageTemplate.frames[self._nextFrameIndex] - del self._nextFrameIndex - self.handle_frameBegin(resume) - elif hasattr(self.frame,'lastFrame') or self.frame is self.pageTemplate.frames[-1]: - self.handle_pageEnd() - self.frame = None - else: - f = self.frame - self.frame = self.pageTemplate.frames[self.pageTemplate.frames.index(f) + 1] - self.handle_frameBegin() - - def handle_nextPageTemplate(self,pt): - '''On endPage chenge to the page template with name or index pt''' - if type(pt) is StringType: - for t in self.pageTemplates: - if t.id == pt: - self._nextPageTemplateIndex = self.pageTemplates.index(t) - return - raise ValueError, "can't find template('%s')"%pt - elif type(pt) is IntType: - self._nextPageTemplateIndex = pt - else: - raise TypeError, "argument pt should be string or integer" - - def handle_nextFrame(self,fx): - '''On endFrame chenge to the frame with name or index fx''' - if type(fx) is StringType: - for f in self.pageTemplate.frames: - if f.id == fx: - self._nextFrameIndex = self.pageTemplate.frames.index(f) - return - raise ValueError, "can't find frame('%s')"%fx - elif type(fx) is IntType: - self._nextFrameIndex = fx - else: - raise TypeError, "argument fx should be string or integer" - - def handle_currentFrame(self, fx, resume=0): - '''chenge to the frame with name or index fx''' - if type(fx) is StringType: - for f in self.pageTemplate.frames: - if f.id == fx: - self._nextFrameIndex = self.pageTemplate.frames.index(f) - return - raise ValueError, "can't find frame('%s')"%fx - elif type(fx) is IntType: - self._nextFrameIndex = fx - else: - raise TypeError, "argument fx should be string or integer" - - def handle_breakBefore(self, flowables): - '''preprocessing step to allow pageBreakBefore and frameBreakBefore attributes''' - first = flowables[0] - # if we insert a page break before, we'll process that, see it again, - # and go in an infinite loop. So we need to set a flag on the object - # saying 'skip me'. This should be unset on the next pass - if hasattr(first, '_skipMeNextTime'): - delattr(first, '_skipMeNextTime') - return - # this could all be made much quicker by putting the attributes - # in to the flowables with a defult value of 0 - if hasattr(first,'pageBreakBefore') and first.pageBreakBefore == 1: - first._skipMeNextTime = 1 - flowables.insert(0, PageBreak()) - return - if hasattr(first,'style') and hasattr(first.style, 'pageBreakBefore') and first.style.pageBreakBefore == 1: - first._skipMeNextTime = 1 - flowables.insert(0, PageBreak()) - return - if hasattr(first,'frameBreakBefore') and first.frameBreakBefore == 1: - first._skipMeNextTime = 1 - flowables.insert(0, FrameBreak()) - return - if hasattr(first,'style') and hasattr(first.style, 'frameBreakBefore') and first.style.frameBreakBefore == 1: - first._skipMeNextTime = 1 - flowables.insert(0, FrameBreak()) - return - - - def handle_keepWithNext(self, flowables): - "implements keepWithNext" - i = 0 - n = len(flowables) - while i maxPasses: - raise IndexError, "Index entries not resolved after %d passes" % maxPasses - - if verbose: print 'saved' - - #these are pure virtuals override in derived classes - #NB these get called at suitable places by the base class - #so if you derive and override the handle_xxx methods - #it's up to you to ensure that they maintain the needed consistency - def afterInit(self): - """This is called after initialisation of the base class.""" - pass - - def beforeDocument(self): - """This is called before any processing is - done on the document.""" - pass - - def beforePage(self): - """This is called at the beginning of page - processing, and immediately before the - beforeDrawPage method of the current page - template.""" - pass - - def afterPage(self): - """This is called after page processing, and - immediately after the afterDrawPage method - of the current page template.""" - pass - - def filterFlowables(self,flowables): - '''called to filter flowables at the start of the main handle_flowable method. - Upon return if flowables[0] has been set to None it is discarded and the main - method returns. - ''' - pass - - def afterFlowable(self, flowable): - '''called after a flowable has been rendered''' - pass - - -class SimpleDocTemplate(BaseDocTemplate): - """A special case document template that will handle many simple documents. - See documentation for BaseDocTemplate. No pageTemplates are required - for this special case. A page templates are inferred from the - margin information and the onFirstPage, onLaterPages arguments to the build method. - - A document which has all pages with the same look except for the first - page may can be built using this special approach. - """ - _invalidInitArgs = ('pageTemplates',) - - def handle_pageBegin(self): - '''override base method to add a change of page template after the firstpage. - ''' - self._handle_pageBegin() - self._handle_nextPageTemplate('Later') - - def build(self,flowables,onFirstPage=_doNothing, onLaterPages=_doNothing): - """build the document using the flowables. Annotate the first page using the onFirstPage - function and later pages using the onLaterPages function. The onXXX pages should follow - the signature - - def myOnFirstPage(canvas, document): - # do annotations and modify the document - ... - - The functions can do things like draw logos, page numbers, - footers, etcetera. They can use external variables to vary - the look (for example providing page numbering or section names). - """ - self._calc() #in case we changed margins sizes etc - frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal') - self.addPageTemplates([PageTemplate(id='First',frames=frameT, onPage=onFirstPage,pagesize=self.pagesize), - PageTemplate(id='Later',frames=frameT, onPage=onLaterPages,pagesize=self.pagesize)]) - if onFirstPage is _doNothing and hasattr(self,'onFirstPage'): - self.pageTemplates[0].beforeDrawPage = self.onFirstPage - if onLaterPages is _doNothing and hasattr(self,'onLaterPages'): - self.pageTemplates[1].beforeDrawPage = self.onLaterPages - BaseDocTemplate.build(self,flowables) - - -def progressCB(typ, value): - """Example prototype for progress monitoring. - - This aims to provide info about what is going on - during a big job. It should enable, for example, a reasonably - smooth progress bar to be drawn. We design the argument - signature to be predictable and conducive to programming in - other (type safe) languages. If set, this will be called - repeatedly with pairs of values. The first is a string - indicating the type of call; the second is a numeric value. - - typ 'STARTING', value = 0 - typ 'SIZE_EST', value = numeric estimate of job size - typ 'PASS', value = number of this rendering pass - typ 'PROGRESS', value = number between 0 and SIZE_EST - typ 'PAGE', value = page number of page - type 'FINISHED', value = 0 - - The sequence is - STARTING - always called once - SIZE_EST - always called once - PROGRESS - called often - PAGE - called often when page is emitted - FINISHED - called when really, really finished - - some juggling is needed to accurately estimate numbers of - pages in pageDrawing mode. - - NOTE: the SIZE_EST is a guess. It is possible that the - PROGRESS value may slightly exceed it, or may even step - back a little on rare occasions. The only way to be - really accurate would be to do two passes, and I don't - want to take that performance hit. - """ - print 'PROGRESS MONITOR: %-10s %d' % (typ, value) - -if __name__ == '__main__': - - 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-108, "TABLE OF CONTENTS DEMO") - 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() - - def run(): - objects_to_draw = [] - from reportlab.lib.styles import ParagraphStyle - #from paragraph import Paragraph - from doctemplate import SimpleDocTemplate - - #need a style - normal = ParagraphStyle('normal') - normal.firstLineIndent = 18 - normal.spaceBefore = 6 - from reportlab.lib.randomtext import randomText - import random - for i in range(15): - height = 0.5 + (2*random.random()) - box = XBox(6 * inch, height * inch, 'Box Number %d' % i) - objects_to_draw.append(box) - para = Paragraph(randomText(), normal) - objects_to_draw.append(para) - - SimpleDocTemplate('doctemplate.pdf').build(objects_to_draw, - onFirstPage=myFirstPage,onLaterPages=myLaterPages) - - run() diff --git a/bin/reportlab/platypus/figures.py b/bin/reportlab/platypus/figures.py deleted file mode 100644 index d5408fe9d4a..00000000000 --- a/bin/reportlab/platypus/figures.py +++ /dev/null @@ -1,372 +0,0 @@ -#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/platypus/figures.py -"""This includes some demos of platypus for use in the API proposal""" -__version__=''' $Id$ ''' - -import os - -from reportlab.lib import colors -from reportlab.pdfgen.canvas import Canvas -from reportlab.lib.styles import ParagraphStyle -from reportlab.lib.utils import recursiveImport -from reportlab.platypus import Frame -from reportlab.platypus import Flowable -from reportlab.platypus import Paragraph -from reportlab.lib.units import inch -from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER -from reportlab.lib.validators import isColor -from reportlab.lib.colors import toColor - -captionStyle = ParagraphStyle('Caption', fontName='Times-Italic', fontSize=10, alignment=TA_CENTER) - -class Figure(Flowable): - def __init__(self, width, height, caption="", - captionFont="Times-Italic", captionSize=12, - background=None, - captionTextColor=toColor('black'), - captionBackColor=None, - border=1): - Flowable.__init__(self) - self.width = width - self.figureHeight = height - self.caption = caption - self.captionFont = captionFont - self.captionSize = captionSize - self.captionTextColor = captionTextColor - self.captionBackColor = captionBackColor - self._captionData = None - self.captionHeight = 0 # work out later - self.background = background - self.border = border - self.spaceBefore = 12 - self.spaceAfter = 12 - - def _getCaptionPara(self): - caption = self.caption - captionFont = self.captionFont - captionSize = self.captionSize - captionTextColor = self.captionTextColor - captionBackColor = self.captionBackColor - if self._captionData!=(caption,captionFont,captionSize,captionTextColor,captionBackColor): - self._captionData = (caption,captionFont,captionSize,captionTextColor,captionBackColor) - self.captionStyle = ParagraphStyle( - 'Caption', - fontName=captionFont, - fontSize=captionSize, - leading=1.2*captionSize, - textColor = captionTextColor, - backColor = captionBackColor, - spaceBefore=captionSize * 0.5, - alignment=TA_CENTER) - #must build paragraph now to get sequencing in synch with rest of story - self.captionPara = Paragraph(self.caption, self.captionStyle) - - def wrap(self, availWidth, availHeight): - # try to get the caption aligned - if self.caption: - self._getCaptionPara() - (w, h) = self.captionPara.wrap(self.width, availHeight - self.figureHeight) - self.captionHeight = h - self.height = self.captionHeight + self.figureHeight - if w>self.width: self.width = w - else: - self.height = self.figureHeight - self.dx = 0.5 * (availWidth - self.width) - return (self.width, self.height) - - def draw(self): - self.canv.translate(self.dx, 0) - if self.caption: - self._getCaptionPara() - self.drawCaption() - self.canv.translate(0, self.captionHeight) - if self.background: - self.drawBackground() - if self.border: - self.drawBorder() - self.drawFigure() - - def drawBorder(self): - self.canv.rect(0, 0, self.width, self.figureHeight) - - def _doBackground(self, color): - self.canv.saveState() - self.canv.setFillColor(self.background) - self.canv.rect(0, 0, self.width, self.figureHeight, fill=1) - self.canv.restoreState() - - def drawBackground(self): - """For use when using a figure on a differently coloured background. - Allows you to specify a colour to be used as a background for the figure.""" - if isColor(self.background): - self._doBackground(self.background) - else: - try: - c = toColor(self.background) - self._doBackground(c) - except: - pass - - def drawCaption(self): - self.captionPara.drawOn(self.canv, 0, 0) - - def drawFigure(self): - pass - -def drawPage(canvas,x, y, width, height): - #draws something which looks like a page - pth = canvas.beginPath() - corner = 0.05*width - - # shaded backdrop offset a little - canvas.setFillColorRGB(0.5,0.5,0.5) - canvas.rect(x + corner, y - corner, width, height, stroke=0, fill=1) - - #'sheet of paper' in light yellow - canvas.setFillColorRGB(1,1,0.9) - canvas.setLineWidth(0) - canvas.rect(x, y, width, height, stroke=1, fill=1) - - #reset - canvas.setFillColorRGB(0,0,0) - canvas.setStrokeColorRGB(0,0,0) - -class PageFigure(Figure): - """Shows a blank page in a frame, and draws on that. Used in - illustrations of how PLATYPUS works.""" - def __init__(self, background=None): - Figure.__init__(self, 3*inch, 3*inch) - self.caption = 'Figure 1 - a blank page' - self.captionStyle = captionStyle - self.background = background - - def drawVirtualPage(self): - pass - - def drawFigure(self): - drawPage(self.canv, 0.625*inch, 0.25*inch, 1.75*inch, 2.5*inch) - self.canv.translate(0.625*inch, 0.25*inch) - self.canv.scale(1.75/8.27, 2.5/11.69) - self.drawVirtualPage() - -class PlatPropFigure1(PageFigure): - """This shows a page with a frame on it""" - def __init__(self): - PageFigure.__init__(self) - self.caption = "Figure 1 - a page with a simple frame" - def drawVirtualPage(self): - demo1(self.canv) - -class FlexFigure(Figure): - """Base for a figure class with a caption. Can grow or shrink in proportion""" - def __init__(self, width, height, caption, background=None): - Figure.__init__(self, width, height, caption, - captionFont="Helvetica-Oblique", captionSize=8, - background=None) - self.shrinkToFit = 1 #if set and wrap is too tight, shrinks - self.growToFit = 1 #if set and wrap is too tight, shrinks - self.scaleFactor = None - self.captionStyle = ParagraphStyle( - 'Caption', - fontName='Times', #'Helvetica-Oblique', - fontSize=4, #8, - spaceBefore=9, #3, - alignment=TA_CENTER - ) - self._scaleFactor = None - self.background = background - - def _scale(self,availWidth,availHeight): - "Rescale to fit according to the rules, but only once" - if self._scaleFactor is None or self.width>availWidth or self.height>availHeight: - w, h = Figure.wrap(self, availWidth, availHeight) - captionHeight = h - self.figureHeight - if self.scaleFactor is None: - #scale factor None means auto - self._scaleFactor = min(availWidth/self.width,(availHeight-captionHeight)/self.figureHeight) - else: #they provided a factor - self._scaleFactor = self.scaleFactor - if self._scaleFactor<1 and self.shrinkToFit: - self.width = self.width * self._scaleFactor - 0.0001 - self.figureHeight = self.figureHeight * self._scaleFactor - elif self._scaleFactor>1 and self.growToFit: - self.width = self.width*self._scaleFactor - 0.0001 - self.figureHeight = self.figureHeight * self._scaleFactor - - def wrap(self, availWidth, availHeight): - self._scale(availWidth,availHeight) - return Figure.wrap(self, availWidth, availHeight) - - def split(self, availWidth, availHeight): - self._scale(availWidth,availHeight) - return Figure.split(self, availWidth, availHeight) - -class ImageFigure(FlexFigure): - """Image with a caption below it""" - def __init__(self, filename, caption, background=None): - assert os.path.isfile(filename), 'image file %s not found' % filename - from reportlab.lib.utils import ImageReader - w, h = ImageReader(filename).getSize() - self.filename = filename - FlexFigure.__init__(self, w, h, caption, background) - - def drawFigure(self): - self.canv.drawInlineImage(self.filename, - 0, 0,self.width, self.figureHeight) - -class DrawingFigure(FlexFigure): - """Drawing with a caption below it. Clunky, scaling fails.""" - def __init__(self, modulename, classname, caption, baseDir=None, background=None): - module = recursiveImport(modulename, baseDir) - klass = getattr(module, classname) - self.drawing = klass() - FlexFigure.__init__(self, - self.drawing.width, - self.drawing.height, - caption, - background) - self.growToFit = 1 - - def drawFigure(self): - self.canv.scale(self._scaleFactor, self._scaleFactor) - self.drawing.drawOn(self.canv, 0, 0) - - -try: - from rlextra.pageCatcher.pageCatcher import restoreForms, storeForms, storeFormsInMemory, restoreFormsInMemory - _hasPageCatcher = 1 -except ImportError: - _hasPageCatcher = 0 -if _hasPageCatcher: - #################################################################### - # - # PageCatcher plugins - # These let you use our PageCatcher product to add figures - # to other documents easily. - #################################################################### - class PageCatcherCachingMixIn: - "Helper functions to cache pages for figures" - - def getFormName(self, pdfFileName, pageNo): - #naming scheme works within a directory only - dirname, filename = os.path.split(pdfFileName) - root, ext = os.path.splitext(filename) - return '%s_page%d' % (root, pageNo) - - - def needsProcessing(self, pdfFileName, pageNo): - "returns 1 if no forms or form is older" - formName = self.getFormName(pdfFileName, pageNo) - if os.path.exists(formName + '.frm'): - formModTime = os.stat(formName + '.frm')[8] - pdfModTime = os.stat(pdfFileName)[8] - return (pdfModTime > formModTime) - else: - return 1 - - def processPDF(self, pdfFileName, pageNo): - formName = self.getFormName(pdfFileName, pageNo) - storeForms(pdfFileName, formName + '.frm', - prefix= formName + '_', - pagenumbers=[pageNo]) - #print 'stored %s.frm' % formName - return formName + '.frm' - - class cachePageCatcherFigureNonA4(FlexFigure, PageCatcherCachingMixIn): - """PageCatcher page with a caption below it. Size to be supplied.""" - # This should merge with PageFigure into one class that reuses - # form information to determine the page orientation... - def __init__(self, filename, pageNo, caption, width, height, background=None): - self.dirname, self.filename = os.path.split(filename) - if self.dirname == '': - self.dirname = os.curdir - self.pageNo = pageNo - self.formName = self.getFormName(self.filename, self.pageNo) + '_' + str(pageNo) - FlexFigure.__init__(self, width, height, caption, background) - - def drawFigure(self): - self.canv.saveState() - if not self.canv.hasForm(self.formName): - restorePath = self.dirname + os.sep + self.filename - #does the form file exist? if not, generate it. - formFileName = self.getFormName(restorePath, self.pageNo) + '.frm' - if self.needsProcessing(restorePath, self.pageNo): - #print 'preprocessing PDF %s page %s' % (restorePath, self.pageNo) - self.processPDF(restorePath, self.pageNo) - names = restoreForms(formFileName, self.canv) - self.canv.scale(self._scaleFactor, self._scaleFactor) - self.canv.doForm(self.formName) - self.canv.restoreState() - - class cachePageCatcherFigure(cachePageCatcherFigureNonA4): - """PageCatcher page with a caption below it. Presumes A4, Portrait. - This needs our commercial PageCatcher product, or you'll get a blank.""" - def __init__(self, filename, pageNo, caption, width=595, height=842, background=None): - cachePageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background) - - class PageCatcherFigureNonA4(FlexFigure): - """PageCatcher page with a caption below it. Size to be supplied.""" - # This should merge with PageFigure into one class that reuses - # form information to determine the page orientation... - _cache = {} - def __init__(self, filename, pageNo, caption, width, height, background=None, caching=None): - fn = self.filename = filename - self.pageNo = pageNo - fn = fn.replace(os.sep,'_').replace('/','_').replace('\\','_').replace('-','_').replace(':','_') - self.prefix = fn.replace('.','_')+'_'+str(pageNo)+'_' - self.formName = self.prefix + str(pageNo) - self.caching = caching - FlexFigure.__init__(self, width, height, caption, background) - - def drawFigure(self): - if not self.canv.hasForm(self.formName): - if self.filename in self._cache: - f,data = self._cache[self.filename] - else: - f = open(self.filename,'rb') - pdf = f.read() - f.close() - f, data = storeFormsInMemory(pdf, pagenumbers=[self.pageNo], prefix=self.prefix) - if self.caching=='memory': - self._cache[self.filename] = f, data - f = restoreFormsInMemory(data, self.canv) - self.canv.saveState() - self.canv.scale(self._scaleFactor, self._scaleFactor) - self.canv.doForm(self.formName) - self.canv.restoreState() - - class PageCatcherFigure(PageCatcherFigureNonA4): - """PageCatcher page with a caption below it. Presumes A4, Portrait. - This needs our commercial PageCatcher product, or you'll get a blank.""" - def __init__(self, filename, pageNo, caption, width=595, height=842, background=None, caching=None): - PageCatcherFigureNonA4.__init__(self, filename, pageNo, caption, width, height, background=background, caching=caching) - -def demo1(canvas): - frame = Frame( - 2*inch, # x - 4*inch, # y at bottom - 4*inch, # width - 5*inch, # height - showBoundary = 1 # helps us see what's going on - ) - bodyStyle = ParagraphStyle('Body', fontName='Times-Roman', fontSize=24, leading=28, spaceBefore=6) - para1 = Paragraph('Spam spam spam spam. ' * 5, bodyStyle) - para2 = Paragraph('Eggs eggs eggs. ' * 5, bodyStyle) - mydata = [para1, para2] - - #this does the packing and drawing. The frame will consume - #items from the front of the list as it prints them - frame.addFromList(mydata,canvas) - -def test1(): - c = Canvas('figures.pdf') - f = Frame(inch, inch, 6*inch, 9*inch, showBoundary=1) - v = PlatPropFigure1() - v.captionTextColor = toColor('blue') - v.captionBackColor = toColor('lightyellow') - f.addFromList([v],c) - c.save() - -if __name__ == '__main__': - test1() diff --git a/bin/reportlab/platypus/flowables.py b/bin/reportlab/platypus/flowables.py deleted file mode 100644 index 670b9aa9d92..00000000000 --- a/bin/reportlab/platypus/flowables.py +++ /dev/null @@ -1,693 +0,0 @@ -#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/platypus/flowables.py -__version__=''' $Id$ ''' -__doc__=""" -A flowable is a "floating element" in a document whose exact position is determined by the -other elements that precede it, such as a paragraph, a diagram interspersed between paragraphs, -a section header, etcetera. Examples of non-flowables include page numbering annotations, -headers, footers, fixed diagrams or logos, among others. - -Flowables are defined here as objects which know how to determine their size and which -can draw themselves onto a page with respect to a relative "origin" position determined -at a higher level. The object's draw() method should assume that (0,0) corresponds to the -bottom left corner of the enclosing rectangle that will contain the object. The attributes -vAlign and hAlign may be used by 'packers' as hints as to how the object should be placed. - -Some Flowables also know how to "split themselves". For example a -long paragraph might split itself between one page and the next. - -Packers should set the canv attribute during wrap, split & draw operations to allow -the flowable to work out sizes etc in the proper context. - -The "text" of a document usually consists mainly of a sequence of flowables which -flow into a document from top to bottom (with column and page breaks controlled by -higher level components). -""" -import os -import string -from copy import deepcopy -from types import ListType, TupleType, StringType - -from reportlab.pdfgen import canvas -from reportlab.lib.units import inch -from reportlab.lib.colors import red, gray, lightgrey -from reportlab.pdfbase import pdfutils - -from reportlab.rl_config import defaultPageSize, _FUZZ -PAGE_HEIGHT = defaultPageSize[1] - - -class TraceInfo: - "Holder for info about where an object originated" - def __init__(self): - self.srcFile = '(unknown)' - self.startLineNo = -1 - self.startLinePos = -1 - self.endLineNo = -1 - self.endLinePos = -1 - -############################################################# -# Flowable Objects - a base class and a few examples. -# One is just a box to get some metrics. We also have -# a paragraph, an image and a special 'page break' -# object which fills the space. -############################################################# -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. - """ - _fixedWidth = 0 #assume wrap results depend on arguments? - _fixedHeight = 0 - - def __init__(self): - self.width = 0 - self.height = 0 - self.wrapped = 0 - - #these are hints to packers/frames as to how the floable should be positioned - self.hAlign = 'LEFT' #CENTER/CENTRE or RIGHT - self.vAlign = 'BOTTOM' #MIDDLE or TOP - - #optional holder for trace info - self._traceInfo = None - self._showBoundary = None - - - def _drawOn(self,canv): - '''ensure canv is set on and then draw''' - self.canv = canv - self.draw()#this is the bit you overload - del self.canv - - def drawOn(self, canvas, x, y, _sW=0): - "Tell it to draw itself on the canvas. Do not override" - if _sW and hasattr(self,'hAlign'): - a = self.hAlign - if a in ['CENTER','CENTRE']: - x = x + 0.5*_sW - elif a == 'RIGHT': - x = x + _sW - elif a != 'LEFT': - raise ValueError, "Bad hAlign value "+str(a) - canvas.saveState() - canvas.translate(x, y) - self._drawOn(canvas) - if hasattr(self, '_showBoundary') and self._showBoundary: - #diagnostic tool support - canvas.setStrokeColor(gray) - canvas.rect(0,0,self.width, self.height) - canvas.restoreState() - - def wrapOn(self, canv, aW, aH): - '''intended for use by packers allows setting the canvas on - during the actual wrap''' - self.canv = canv - w, h = self.wrap(aW,aH) - del self.canv - return w, h - - 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) - - def minWidth(self): - """This should return the minimum required width""" - return getattr(self,'_minWidth',self.width) - - def splitOn(self, canv, aW, aH): - '''intended for use by packers allows setting the canvas on - during the actual split''' - self.canv = canv - S = self.split(aW,aH) - del self.canv - return S - - def split(self, availWidth, availheight): - """This will be called by more sophisticated frames when - wrap fails. Stupid flowables should return []. Clever flowables - should split themselves and return a list of flowables""" - return [] - - def getKeepWithNext(self): - """returns boolean determining whether the next flowable should stay with this one""" - if hasattr(self,'keepWithNext'): return self.keepWithNext - elif hasattr(self,'style') and hasattr(self.style,'keepWithNext'): return self.style.keepWithNext - else: return 0 - - def getSpaceAfter(self): - """returns how much space should follow this item if another item follows on the same page.""" - if hasattr(self,'spaceAfter'): return self.spaceAfter - elif hasattr(self,'style') and hasattr(self.style,'spaceAfter'): return self.style.spaceAfter - else: return 0 - - def getSpaceBefore(self): - """returns how much space should precede this item if another item precedess on the same page.""" - if hasattr(self,'spaceBefore'): return self.spaceBefore - elif hasattr(self,'style') and hasattr(self.style,'spaceBefore'): return self.style.spaceBefore - else: return 0 - - def isIndexing(self): - """Hook for IndexingFlowables - things which have cross references""" - return 0 - - def identity(self, maxLen=None): - ''' - This method should attempt to return a string that can be used to identify - a particular flowable uniquely. The result can then be used for debugging - and or error printouts - ''' - if hasattr(self, 'getPlainText'): - r = self.getPlainText(identify=1) - elif hasattr(self, 'text'): - r = self.text - else: - r = '...' - if r and maxLen: - r = r[:maxLen] - return "<%s at %d>%s" % (self.__class__.__name__, id(self), r) - -class XBox(Flowable): - """Example flowable - a box with an x through it and a caption. - This has a known size, so does not need to respond to wrap().""" - def __init__(self, width, height, text = 'A Box'): - Flowable.__init__(self) - self.width = width - self.height = height - self.text = text - - def __repr__(self): - return "XBox(w=%s, h=%s, t=%s)" % (self.width, self.height, self.text) - - def draw(self): - self.canv.rect(0, 0, self.width, self.height) - self.canv.line(0, 0, self.width, self.height) - self.canv.line(0, self.height, self.width, 0) - - #centre the text - self.canv.setFont('Times-Roman',12) - self.canv.drawCentredString(0.5*self.width, 0.5*self.height, self.text) - -def _trimEmptyLines(lines): - #don't want the first or last to be empty - while len(lines) and string.strip(lines[0]) == '': - lines = lines[1:] - while len(lines) and string.strip(lines[-1]) == '': - lines = lines[:-1] - return lines - -def _dedenter(text,dedent=0): - ''' - tidy up text - carefully, it is probably code. If people want to - indent code within a source script, you can supply an arg to dedent - and it will chop off that many character, otherwise it leaves - left edge intact. - ''' - lines = string.split(text, '\n') - if dedent>0: - templines = _trimEmptyLines(lines) - lines = [] - for line in templines: - line = string.rstrip(line[dedent:]) - lines.append(line) - else: - lines = _trimEmptyLines(lines) - - return lines - -class Preformatted(Flowable): - """This is like the HTML
 tag.
-    It attempts to display text exactly as you typed it in a fixed width "typewriter" font.
-    The line breaks are exactly where you put
-    them, and it will not be wrapped."""
-    def __init__(self, text, style, bulletText = None, dedent=0):
-        """text is the text to display. If dedent is set then common leading space
-        will be chopped off the front (for example if the entire text is indented
-        6 spaces or more then each line will have 6 spaces removed from the front).
-        """
-        self.style = style
-        self.bulletText = bulletText
-        self.lines = _dedenter(text,dedent)
-
-    def __repr__(self):
-        bT = self.bulletText
-        H = "Preformatted("
-        if bT is not None:
-            H = "Preformatted(bulletText=%s," % repr(bT)
-        return "%s'''\\ \n%s''')" % (H, string.join(self.lines,'\n'))
-
-    def wrap(self, availWidth, availHeight):
-        self.width = availWidth
-        self.height = self.style.leading*len(self.lines)
-        return (self.width, self.height)
-
-    def split(self, availWidth, availHeight):
-        #returns two Preformatted objects
-
-        #not sure why they can be called with a negative height
-        if availHeight < self.style.leading:
-            return []
-
-        linesThatFit = int(availHeight * 1.0 / self.style.leading)
-
-        text1 = string.join(self.lines[0:linesThatFit], '\n')
-        text2 = string.join(self.lines[linesThatFit:], '\n')
-        style = self.style
-        if style.firstLineIndent != 0:
-            style = deepcopy(style)
-            style.firstLineIndent = 0
-        return [Preformatted(text1, self.style), Preformatted(text2, style)]
-
-
-    def draw(self):
-        #call another method for historical reasons.  Besides, I
-        #suspect I will be playing with alternate drawing routines
-        #so not doing it here makes it easier to switch.
-
-        cur_x = self.style.leftIndent
-        cur_y = self.height - self.style.fontSize
-        self.canv.addLiteral('%PreformattedPara')
-        if self.style.textColor:
-            self.canv.setFillColor(self.style.textColor)
-        tx = self.canv.beginText(cur_x, cur_y)
-        #set up the font etc.
-        tx.setFont( self.style.fontName,
-                    self.style.fontSize,
-                    self.style.leading)
-
-        for text in self.lines:
-            tx.textLine(text)
-        self.canv.drawText(tx)
-
-class Image(Flowable):
-    """an image (digital picture).  Formats supported by PIL/Java 1.4 (the Python/Java Imaging Library
-       are supported.  At the present time images as flowables are always centered horozontally
-       in the frame. We allow for two kinds of lazyness to allow for many images in a document
-       which could lead to file handle starvation.
-       lazy=1 don't open image until required.
-       lazy=2 open image when required then shut it.
-    """
-    _fixedWidth = 1
-    _fixedHeight = 1
-    def __init__(self, filename, width=None, height=None, kind='direct', mask="auto", lazy=1):
-        """If size to draw at not specified, get it from the image."""
-        self.hAlign = 'CENTER'
-        self._mask = mask
-        # if it is a JPEG, will be inlined within the file -
-        # but we still need to know its size now
-        fp = hasattr(filename,'read')
-        if fp:
-            self._file = filename
-            self.filename = `filename`
-        else:
-            self._file = self.filename = filename
-        if not fp and os.path.splitext(filename)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
-            from reportlab.lib.utils import open_for_read
-            f = open_for_read(filename, 'b')
-            info = pdfutils.readJPEGInfo(f)
-            f.close()
-            self.imageWidth = info[0]
-            self.imageHeight = info[1]
-            self._img = None
-            self._setup(width,height,kind,0)
-        elif fp:
-            self._setup(width,height,kind,0)
-        else:
-            self._setup(width,height,kind,lazy)
-
-    def _setup(self,width,height,kind,lazy):
-        self._lazy = lazy
-        self._width = width
-        self._height = height
-        self._kind = kind
-        if lazy<=0: self._setup_inner()
-
-    def _setup_inner(self):
-        width = self._width
-        height = self._height
-        kind = self._kind
-        img = self._img
-        if img: self.imageWidth, self.imageHeight = img.getSize()
-        if self._lazy>=2: del self._img
-        if kind in ['direct','absolute']:
-            self.drawWidth = width or self.imageWidth
-            self.drawHeight = height or self.imageHeight
-        elif kind in ['percentage','%']:
-            self.drawWidth = self.imageWidth*width*0.01
-            self.drawHeight = self.imageHeight*height*0.01
-        elif kind in ['bound','proportional']:
-            factor = min(float(width)/self.imageWidth,float(height)/self.imageHeight)
-            self.drawWidth = self.imageWidth*factor
-            self.drawHeight = self.imageHeight*factor
-
-    def __getattr__(self,a):
-        if a=='_img':
-            from reportlab.lib.utils import ImageReader  #this may raise an error
-            self._img = ImageReader(self._file)
-            del self._file
-            return self._img
-        elif a in ('drawWidth','drawHeight','imageWidth','imageHeight'):
-            self._setup_inner()
-            return self.__dict__[a]
-        raise AttributeError(a)
-
-    def wrap(self, availWidth, availHeight):
-        #the caller may decide it does not fit.
-        return (self.drawWidth, self.drawHeight)
-
-    def draw(self):
-        lazy = self._lazy
-        if lazy>=2: self._lazy = 1
-        self.canv.drawImage(    self._img or self.filename,
-                                0,
-                                0,
-                                self.drawWidth,
-                                self.drawHeight,
-                                mask=self._mask,
-                                )
-        if lazy>=2:
-            self._img = None
-            self._lazy = lazy
-
-    def identity(self,maxLen=None):
-        r = Flowable.identity(self,maxLen)
-        if r[-4:]=='>...' and type(self.filename) is StringType:
-            r = "%s filename=%s>" % (r[:-4],self.filename)
-        return r
-
-class Spacer(Flowable):
-    """A spacer just takes up space and doesn't draw anything - it guarantees
-       a gap between objects."""
-    _fixedWidth = 1
-    _fixedHeight = 1
-    def __init__(self, width, height):
-        self.width = width
-        self.height = height
-
-    def __repr__(self):
-        return "%s(%s, %s)" % (self.__class__.__name__,self.width, self.height)
-
-    def draw(self):
-        pass
-
-class PageBreak(Spacer):
-    """Move on to the next page in the document.
-       This works by consuming all remaining space in the frame!"""
-    def __init__(self):
-        pass
-
-    def __repr__(self):
-        return "PageBreak()"
-
-    def wrap(self, availWidth, availHeight):
-        self.width = availWidth
-        self.height = availHeight
-        return (availWidth,availHeight)  #step back a point
-
-class SlowPageBreak(PageBreak):
-    pass
-
-class CondPageBreak(Spacer):
-    """Throw a page if not enough vertical space"""
-    def __init__(self, height):
-        self.height = height
-
-    def __repr__(self):
-        return "CondPageBreak(%s)" %(self.height,)
-
-    def wrap(self, availWidth, availHeight):
-        if availHeightaH) and (not self._maxHeight or H<=self._maxHeight)
-        return W, 0xffffff  # force a split
-
-    def split(self, aW, aH):
-        S = getattr(self,'_CPage',1) and [CondPageBreak(aH+1)] or []
-        for f in self._flowables: S.append(f)
-        return S
-
-class Macro(Flowable):
-    """This is not actually drawn (i.e. it has zero height)
-    but is executed when it would fit in the frame.  Allows direct
-    access to the canvas through the object 'canvas'"""
-    def __init__(self, command):
-        self.command = command
-    def __repr__(self):
-        return "Macro(%s)" % repr(self.command)
-    def wrap(self, availWidth, availHeight):
-        return (0,0)
-    def draw(self):
-        exec self.command in globals(), {'canvas':self.canv}
-
-class CallerMacro(Flowable):
-    '''
-    like Macro, but with callable command(s)
-    drawCallable(self)
-    wrapCallable(self,aW,aH)
-    '''
-    def __init__(self, drawCallable=None, wrapCallable=None):
-        _ = lambda *args: None
-        self._drawCallable = drawCallable or _
-        self._wrapCallable = wrapCallable or _
-    def __repr__(self):
-        return "CallerMacro(%s)" % repr(self.command)
-    def wrap(self, aW, aH):
-        self._wrapCallable(self,aW,aH)
-        return (0,0)
-    def draw(self):
-        self._drawCallable(self)
-
-class ParagraphAndImage(Flowable):
-    '''combine a Paragraph and an Image'''
-    def __init__(self,P,I,xpad=3,ypad=3):
-        self.P, self.style, self.I, self.xpad, self.ypad = P,P.style,I,xpad,ypad
-
-    def wrap(self,availWidth,availHeight):
-        wI, hI = self.I.wrap(availWidth,availHeight)
-        self.wI, self.hI = wI, hI
-        # work out widths array for breaking
-        self.width = availWidth
-        P, style, xpad, ypad = self.P, self.style, self.xpad, self.ypad
-        leading = style.leading
-        leftIndent = style.leftIndent
-        later_widths = availWidth - leftIndent - style.rightIndent
-        intermediate_widths = later_widths - xpad - wI
-        first_line_width = intermediate_widths - style.firstLineIndent
-        P.width = 0
-        P.blPara = P.breakLines([first_line_width] + int((hI+ypad)/leading)*[intermediate_widths]+[later_widths])
-        P.height = len(P.blPara.lines)*leading
-        self.height = max(hI,P.height)
-        return (self.width, self.height)
-
-    def split(self,availWidth, availHeight):
-        P, wI, hI, ypad = self.P, self.wI, self.hI, self.ypad
-        if hI+ypad>availHeight or len(P.frags)<=0: return []
-        S = P.split(availWidth,availHeight)
-        if not S: return S
-        P = self.P = S[0]
-        del S[0]
-        style = self.style = P.style
-        P.height = len(self.P.blPara.lines)*style.leading
-        self.height = max(hI,P.height)
-        return [self]+S
-
-    def draw(self):
-        canv = self.canv
-        self.I.drawOn(canv,self.width-self.wI-self.xpad,self.height-self.hI)
-        self.P.drawOn(canv,0,0)
-
-class FailOnWrap(Flowable):
-    def wrap(self, availWidth, availHeight):
-        raise ValueError("FailOnWrap flowable wrapped and failing as ordered!")
-
-    def draw(self):
-        pass
-
-class FailOnDraw(Flowable):
-    def wrap(self, availWidth, availHeight):
-        return (0,0)
-
-    def draw(self):
-        raise ValueError("FailOnDraw flowable drawn, and failing as ordered!")
-
-class HRFlowable(Flowable):
-    '''Like the hr tag'''
-    def __init__(self,
-            width="80%",
-            thickness=1,
-            lineCap='round',
-            color=lightgrey,
-            spaceBefore=1, spaceAfter=1,
-            hAlign='CENTER', vAlign='BOTTOM'):
-        Flowable.__init__(self)
-        self.width = width
-        self.lineWidth = thickness
-        self.lineCap=lineCap
-        self.spaceBefore = spaceBefore
-        self.spaceAfter = spaceAfter
-        self.color = color
-        self.hAlign = hAlign
-        self.vAlign = vAlign
-
-    def __repr__(self):
-        return "HRFlowable(width=%s, height=%s)" % (self.width, self.height)
-
-    def wrap(self, availWidth, availHeight):
-        w = self.width
-        if type(w) is type(''):
-            w = w.strip()
-            if w.endswith('%'): w = availWidth*float(w[:-1])*0.01
-            else: w = float(w)
-        w = min(w,availWidth)
-        self._width = w
-        return w, self.lineWidth
-
-    def draw(self):
-        canv = self.canv
-        canv.saveState()
-        canv.setLineWidth(self.lineWidth)
-        canv.setLineCap({'butt':0,'round':1, 'square': 2}[self.lineCap.lower()])
-        canv.setStrokeColor(self.color)
-        canv.line(0, 0, self._width, self.height)
-        canv.restoreState()
-
-class _PTOInfo:
-    def __init__(self,trailer,header):
-        self.trailer = _makeIndexable(trailer)
-        self.header = _makeIndexable(header)
-
-class PTOContainer(Flowable):
-    '''PTOContainer(contentList,trailerList,headerList)
-    
-    A container for flowables decorated with trailer & header lists.
-    If the split operation would be called then the trailer and header
-    lists are injected before and after the split. This allows specialist
-    "please turn over" and "continued from previous" like behaviours.''' 
-    def __init__(self,content,trailer=None,header=None):
-        I = _PTOInfo(trailer,header)
-        self._content = C = []
-        for _ in _makeIndexable(content):
-            if isinstance(_,PTOContainer):
-                C.extend(_._content)
-            else:
-                C.append(_)
-                if not hasattr(_,'_ptoinfo'): _._ptoinfo = I
-
-    def wrap(self,availWidth,availHeight):
-        self.width, self.height = _listWrapOn(self._content,availWidth,self.canv)
-        return self.width,self.height
-
-    def getSpaceBefore(self):
-        return self._content[0].getSpaceBefore()
-
-    def getSpaceAfter(self):
-        return self._content[-1].getSpaceAfter()
-
-    def split(self, availWidth, availHeight):
-        canv = self.canv
-        C = self._content
-        x = i = H = pS = 0
-        n = len(C)
-        I2W = {}
-        for x in xrange(n):
-            c = C[x]
-            I = c._ptoinfo
-            if I not in I2W.keys():
-                T = I.trailer
-                Hdr = I.header
-                tW, tH = _listWrapOn(T, availWidth, self.canv)
-                tSB = T[0].getSpaceBefore()
-                I2W[I] = T,tW,tH,tSB
-            else:
-                T,tW,tH,tSB = I2W[I]
-            _, h = c.wrapOn(canv,availWidth,0xfffffff)
-            if x: h += max(c.getSpaceBefore()-pS,0)
-            pS = c.getSpaceAfter()
-            H += h+pS
-            if H+tH+max(tSB,pS)>=availHeight-_FUZZ: break
-            i += 1
-
-        #first retract last thing we tried
-        H -= (h+pS)
-
-        #attempt a sub split on the last one we have
-        aH = (availHeight - H - max(pS,tSB) - tH)*0.99
-        if aH>=0.05*availHeight:
-            SS = c.splitOn(canv,availWidth,aH)
-        else:
-            SS = []
-        if SS:
-            from doctemplate import  FrameBreak
-            F = [FrameBreak()]
-
-        if SS:
-            R1 = C[:i] + SS[:1] + T + F
-            R2 = Hdr + SS[1:]+C[i+1:]
-        elif not i:
-            return []
-        else:
-            R1 = C[:i-1]+T
-            R2 = Hdr + C[i:]
-        return R1 + [PTOContainer(R2,deepcopy(I.trailer),deepcopy(I.header))]
-
-    def drawOn(self, canv, x, y, _sW=0):
-        '''we simulate being added to a frame'''
-        pS = 0
-        aW = self.width+_sW
-        C = self._content
-        y += self.height
-        for c in C:
-            w, h = c.wrapOn(canv,aW,0xfffffff)
-            if c is not C[0]: h += max(c.getSpaceBefore()-pS,0)
-            y -= h
-            c.drawOn(canv,x,y,_sW=aW-w)
-            if c is not C[-1]:
-                pS = c.getSpaceAfter()
-                y -= pS
diff --git a/bin/reportlab/platypus/frames.py b/bin/reportlab/platypus/frames.py
deleted file mode 100644
index 53dbeea69de..00000000000
--- a/bin/reportlab/platypus/frames.py
+++ /dev/null
@@ -1,195 +0,0 @@
-#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/platypus/frames.py
-
-__version__=''' $Id$ '''
-
-__doc__="""
-"""
-
-_geomAttr=('x1', 'y1', 'width', 'height', 'leftPadding', 'bottomPadding', 'rightPadding', 'topPadding')
-from reportlab import rl_config
-_FUZZ=rl_config._FUZZ
-
-class Frame:
-    '''
-    A Frame is a piece of space in a document that is filled by the
-    "flowables" in the story.  For example in a book like document most
-    pages have the text paragraphs in one or two frames.  For generality
-    a page might have several frames (for example for 3 column text or
-    for text that wraps around a graphic).
-
-    After creation a Frame is not usually manipulated directly by the
-    applications program -- it is used internally by the platypus modules.
-
-    Here is a diagramatid abstraction for the definitional part of a Frame
-
-                width                    x2,y2
-        +---------------------------------+
-        | l  top padding                r | h
-        | e +-------------------------+ i | e
-        | f |                         | g | i
-        | t |                         | h | g
-        |   |                         | t | h
-        | p |                         |   | t
-        | a |                         | p |
-        | d |                         | a |
-        |   |                         | d |
-        |   +-------------------------+   |
-        |    bottom padding               |
-        +---------------------------------+
-        (x1,y1) <-- lower left corner
-
-        NOTE!! Frames are stateful objects.  No single frame should be used in
-        two documents at the same time (especially in the presence of multithreading.
-    '''
-    def __init__(self, x1, y1, width,height, leftPadding=6, bottomPadding=6,
-            rightPadding=6, topPadding=6, id=None, showBoundary=0,
-            overlapAttachedSpace=None):
-        self.id = id
-
-        #these say where it goes on the page
-        self.__dict__['_x1'] = x1
-        self.__dict__['_y1'] = y1
-        self.__dict__['_width'] = width
-        self.__dict__['_height'] = height
-
-        #these create some padding.
-        self.__dict__['_leftPadding'] = leftPadding
-        self.__dict__['_bottomPadding'] = bottomPadding
-        self.__dict__['_rightPadding'] = rightPadding
-        self.__dict__['_topPadding'] = topPadding
-
-        # these two should NOT be set on a frame.
-        # they are used when Indenter flowables want
-        # to adjust edges e.g. to do nested lists
-        self._leftExtraIndent = 0.0
-        self._rightExtraIndent = 0.0
-
-        # if we want a boundary to be shown
-        self.showBoundary = showBoundary
-
-        if overlapAttachedSpace is None: overlapAttachedSpace = rl_config.overlapAttachedSpace
-        self._oASpace = overlapAttachedSpace
-        self._geom()
-        self._reset()
-
-    def __getattr__(self,a):
-        if a in _geomAttr: return self.__dict__['_'+a]
-        raise AttributeError, a
-
-    def __setattr__(self,a,v):
-        if a in _geomAttr:
-            self.__dict__['_'+a] = v
-            self._geom()
-        else:
-            self.__dict__[a] = v
-
-    def _geom(self):
-        self._x2 = self._x1 + self._width
-        self._y2 = self._y1 + self._height
-        #efficiency
-        self._y1p = self._y1 + self._bottomPadding
-        #work out the available space
-        self._aW = self._x2 - self._x1 - self._leftPadding - self._rightPadding
-        self._aH = self._y2 - self._y1p - self._topPadding
-
-    def _reset(self):
-        #drawing starts at top left
-        self._x = self._x1 + self._leftPadding
-        self._y = self._y2 - self._topPadding
-        self._atTop = 1
-        self._prevASpace = 0
-
-    def _getAvailableWidth(self):
-        return self._aW - self._leftExtraIndent - self._rightExtraIndent
-
-    def _add(self, flowable, canv, trySplit=0):
-        """ Draws the flowable at the current position.
-        Returns 1 if successful, 0 if it would not fit.
-        Raises a LayoutError if the object is too wide,
-        or if it is too high for a totally empty frame,
-        to avoid infinite loops"""
-        y = self._y
-        p = self._y1p
-        s = 0
-        aW = self._getAvailableWidth()
-        if not self._atTop:
-            s =flowable.getSpaceBefore()
-            if self._oASpace:
-                s = max(s-self._prevASpace,0)
-        h = y - p - s
-        if h>0:
-            flowable.canv = canv #so they can use stringWidth etc
-            w, h = flowable.wrap(aW, h)
-            del flowable.canv
-        else:
-            return 0
-
-        h += s
-        y -= h
-
-        if y < p-_FUZZ:
-            if not rl_config.allowTableBoundsErrors and ((h>self._aH or w>aW) and not trySplit):
-                raise "LayoutError", "Flowable %s (%sx%s points) too large for frame (%sx%s points)." % (
-                    flowable.__class__, w,h, aW,self._aH)
-            return 0
-        else:
-            #now we can draw it, and update the current point.
-            flowable.drawOn(canv, self._x + self._leftExtraIndent, y, _sW=aW-w)
-            s = flowable.getSpaceAfter()
-            y -= s
-            if self._oASpace: self._prevASpace = s
-            if y!=self._y: self._atTop = 0
-            self._y = y
-            return 1
-
-    add = _add
-
-    def split(self,flowable,canv):
-        '''Ask the flowable to split using up the available space.'''
-        y = self._y
-        p = self._y1p
-        s = 0
-        if not self._atTop: s = flowable.getSpaceBefore()
-        flowable.canv = canv    #some flowables might need this
-
-        #print 'asked table to split.  _aW = %0.2f, y-p-s=%0.2f' % (self._aW, y-p-s)
-        r = flowable.split(self._aW, y-p-s)
-        del flowable.canv
-        return r
-
-    def drawBoundary(self,canv):
-        "draw the frame boundary as a rectangle (primarily for debugging)."
-        from reportlab.lib.colors import Color, CMYKColor, toColor
-        sb = self.showBoundary
-        isColor = type(sb) in (type(''),type(()),type([])) or isinstance(sb,Color)
-        if isColor:
-            sb = toColor(sb,self)
-            if sb is self: isColor = 0
-            else:
-                canv.saveState()
-                canv.setStrokeColor(sb)
-        canv.rect(
-                self._x1,
-                self._y1,
-                self._x2 - self._x1,
-                self._y2 - self._y1
-                )
-        if isColor: canv.restoreState()
-
-    def addFromList(self, drawlist, canv):
-        """Consumes objects from the front of the list until the
-        frame is full.  If it cannot fit one object, raises
-        an exception."""
-
-        if self.showBoundary:
-            self.drawBoundary(canv)
-
-        while len(drawlist) > 0:
-            head = drawlist[0]
-            if self.add(head,canv,trySplit=0):
-                del drawlist[0]
-            else:
-                #leave it in the list for later
-                break
diff --git a/bin/reportlab/platypus/para.py b/bin/reportlab/platypus/para.py
deleted file mode 100644
index a7f022df59f..00000000000
--- a/bin/reportlab/platypus/para.py
+++ /dev/null
@@ -1,2369 +0,0 @@
-"""new experimental paragraph implementation
-
-Intended to allow support for paragraphs in paragraphs, hotlinks,
-embedded flowables, and underlining.  The main entry point is the
-function
-
-def Paragraph(text, style, bulletText=None, frags=None)
-
-Which is intended to be plug compatible with the "usual" platypus
-paragraph except that it supports more functionality.
-
-In this implementation you may embed paragraphs inside paragraphs
-to create hierarchically organized documents.
-
-This implementation adds the following paragraph-like tags (which
-support the same attributes as paragraphs, for font specification, etc).
-
-- Unnumberred lists (ala html):
-
-    
    -
  • first one
  • -
  • second one
  • -
- -Also
    (default) or
      ,
        . - -- Numberred lists (ala html): - -
          -
        1. first one
        2. -
        3. second one
        4. -
        - -Also
          (default) or
            ,
              . - -- Display lists (ala HTML): - -For example - -
              -
              frogs
              Little green slimy things. Delicious with garlic
              -
              kittens
              cute, furry, not edible
              -
              bunnies
              cute, furry,. Delicious with garlic
              -
              - -ALSO the following additional internal paragraph markup tags are supported - -underlined text - -hyperlinked text -hyperlinked text - -Go to the end (go to document internal destination) -Go to the beginning - -This is the document start - (define document destination inside paragraph, color is optional) - -""" - -from reportlab.pdfbase.pdfmetrics import stringWidth -from reportlab.lib.utils import fp_str -from reportlab.platypus.flowables import Flowable -from reportlab.lib import colors - -from types import StringType, UnicodeType, InstanceType, TupleType, ListType, FloatType - -# SET THIS TO CAUSE A VIEWING BUG WITH ACROREAD 3 (for at least one input) -# CAUSEERROR = 0 - -debug = 0 - -DUMPPROGRAM = 0 - -TOOSMALLSPACE = 1e-5 - -from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY - -# indent changes effect the next line -# align changes effect the current line - -# need to fix spacing questions... if ends with space then space may be inserted - -# NEGATIVE SPACE SHOULD NEVER BE EXPANDED (IN JUSTIFICATION, EG) - -class paragraphEngine: - # text origin of 0,0 is upper left corner - def __init__(self, program = None): - from reportlab.lib.colors import black - if program is None: - program = [] - self.lineOpHandlers = [] # for handling underlining and hyperlinking, etc - self.program = program - self.indent = self.rightIndent = 0.0 - self.baseindent = 0.0 # adjust this to add more indentation for bullets, eg - self.fontName = "Helvetica" - self.fontSize = 10 - self.leading = 12 - self.fontColor = black - self.x = self.y = self.rise = 0.0 - from reportlab.lib.enums import TA_LEFT - self.alignment = TA_LEFT - self.textStateStack = [] - - TEXT_STATE_VARIABLES = ("indent", "rightIndent", "fontName", "fontSize", - "leading", "fontColor", "lineOpHandlers", "rise", - "alignment") - #"textStateStack") - - def pushTextState(self): - state = [] - for var in self.TEXT_STATE_VARIABLES: - val = getattr(self, var) - state.append(val) - #self.textStateStack.append(state) - self.textStateStack = self.textStateStack+[state] # fresh copy - #print "push", self.textStateStack - #print "push", len(self.textStateStack), state - return state - - def popTextState(self): - state = self.textStateStack[-1] - self.textStateStack = self.textStateStack[:-1] - #print "pop", self.textStateStack - state = state[:] # copy for destruction - #print "pop", len(self.textStateStack), state - #print "handlers before", self.lineOpHandlers - for var in self.TEXT_STATE_VARIABLES: - val = state[0] - del state[0] - setattr(self, var, val) - - def format(self, maxwidth, maxheight, program, leading=0): - "return program with line operations added if at least one line fits" - # note: a generated formatted segment should not be formatted again - startstate = self.__dict__.copy() - #remainder = self.cleanProgram(program) - remainder = program[:] - #program1 = remainder[:] # debug only - lineprogram = [] - #if maxheight=self.leading and remainder: - #print "getting line with statestack", len(self.textStateStack) - #heightremaining = heightremaining - self.leading - indent = self.indent - rightIndent = self.rightIndent - linewidth = maxwidth - indent - rightIndent - beforelinestate = self.__dict__.copy() - if linewidthleading: - heightremaining = heightremaining-leading - else: - room = 0 - #self.resetState(beforelinestate) - self.__dict__.update(beforelinestate) - break # no room for this line -## if debug: -## print "line", line -## if lineIsFull: print "is full" -## else: print "is partially full" -## print "consumes", cursor, "elements" -## print "covers", currentLength, "of", maxwidth - alignment = self.alignment # last declared alignment for this line used - # recompute linewidth using the used indent - #linewidth = maxwidth - usedIndent - rightIndent - remainder = remainder[cursor:] - if not remainder: - # trim off the extra end of line - del line[-1] - # do justification if any - #line = self.shrinkWrap(line - if alignment==TA_LEFT: - #if debug: - # print "ALIGN LEFT" - if justStrings: - line = stringLine(line, currentLength) - else: - line = self.shrinkWrap(line) - pass - elif alignment==TA_CENTER: - #if debug: - # print "ALIGN CENTER" - if justStrings: - line = stringLine(line, currentLength) - else: - line = self.shrinkWrap(line) - line = self.centerAlign(line, currentLength, maxLength) - elif alignment==TA_RIGHT: - #if debug: - # print "ALIGN RIGHT" - if justStrings: - line = stringLine(line, currentLength) - else: - line = self.shrinkWrap(line) - line = self.rightAlign(line, currentLength, maxLength) - elif alignment==TA_JUSTIFY: - #if debug: - # print "JUSTIFY" - if remainder and lineIsFull: - if justStrings: - line = simpleJustifyAlign(line, currentLength, maxLength) - else: - line = self.justifyAlign(line, currentLength, maxLength) - else: - if justStrings: - line = stringLine(line, currentLength) - else: - line = self.shrinkWrap(line) - if debug: - print "no justify because line is not full or end of para" - else: - raise ValueError, "bad alignment "+repr(alignment) - if not justStrings: - line = self.cleanProgram(line) - lineprogram.extend(line) - laststate = self.__dict__.copy() - #self.resetState(startstate) - self.__dict__.update(startstate) - heightused = maxheight - heightremaining - return (lineprogram, remainder, laststate, heightused) - - def getState(self): - # inlined - return self.__dict__.copy() - - def resetState(self, state): - # primarily inlined - self.__dict__.update(state) - -## def sizeOfWord(self, word): -## inlineThisFunctionForEfficiency -## return float(stringWidth(word, self.fontName, self.fontSize)) - - def fitLine(self, program, totalLength): - "fit words (and other things) onto a line" - # assuming word lengths and spaces have not been yet added - # fit words onto a line up to maxlength, adding spaces and respecting extra space - from reportlab.pdfbase.pdfmetrics import stringWidth - usedIndent = self.indent - maxLength = totalLength - usedIndent - self.rightIndent - done = 0 - line = [] - cursor = 0 - lineIsFull = 0 - currentLength = 0 - maxcursor = len(program) - needspace = 0 - first = 1 - terminated = None - fontName = self.fontName - fontSize = self.fontSize - spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ") - justStrings = 1 - while not done and cursormaxLength and not first: # always do at least one thing - # this word won't fit - #if debug: - # print "WORD", opcode, "wont fit, width", width, "fullwidth", fullwidth - # print " currentLength", currentLength, "newlength", newlength, "maxLength", maxLength - done = 1 - lineIsFull = 1 - else: - # fit the word: add a space then the word - if lastneedspace: - line.append( spacewidth ) # expandable space: positive - if opcode: - line.append( opcode ) - if abs(width)>TOOSMALLSPACE: - line.append( -width ) # non expanding space: negative - currentLength = newlength - #print line - #stop - first = 0 - elif topcode is FloatType: - justStrings = 0 - aopcode = abs(opcode) # negative means non expanding - if aopcode>TOOSMALLSPACE: - nextLength = currentLength+aopcode - if nextLength>maxLength and not first: # always do at least one thing - #if debug: print "EXPLICIT spacer won't fit", maxLength, nextLength, opcode - done = 1 - else: - if aopcode>TOOSMALLSPACE: - currentLength = nextLength - line.append(opcode) - first = 0 - elif topcode is TupleType: - justStrings = 0 - indicator = opcode[0] - #line.append(opcode) - if indicator=="nextLine": - # advance to nextLine - #(i, endallmarks) = opcode - line.append(opcode) - cursor = cursor+1 # consume this element - terminated = done = 1 - #if debug: - # print "nextLine encountered" - elif indicator=="color": - # change fill color - oldcolor = self.fontColor - (i, colorname) = opcode - #print "opcode", opcode - if type(colorname) in (StringType, UnicodeType): - color = self.fontColor = getattr(colors, colorname) - else: - color = self.fontColor = colorname # assume its something sensible :) - line.append(opcode) - elif indicator=="face": - # change font face - (i, fontname) = opcode - fontName = self.fontName = fontname - spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ") - line.append(opcode) - elif indicator=="size": - # change font size - (i, fontsize) = opcode - size = abs(float(fontsize)) - if type(fontsize) in (StringType, UnicodeType): - if fontsize[:1]=="+": - fontSize = self.fontSize = self.fontSize + size - elif fontsize[:1]=="-": - fontSize = self.fontSize = self.fontSize - size - else: - fontSize = self.fontSize = size - else: - fontSize = self.fontSize = size - spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ") - line.append(opcode) - elif indicator=="leading": - # change font leading - (i, leading) = opcode - self.leading = leading - line.append(opcode) - elif indicator=="indent": - # increase the indent - (i, increment) = opcode - indent = self.indent = self.indent + increment - if first: - usedIndent = max(indent, usedIndent) - maxLength = totalLength - usedIndent - self.rightIndent - line.append(opcode) - elif indicator=="push": - self.pushTextState() - line.append(opcode) - elif indicator=="pop": - try: - self.popTextState() - except: -## print "stack fault near", cursor -## for i in program[max(0, cursor-10):cursor+10]: -## if i==cursor: -## print "***>>>", -## print i - raise - fontName = self.fontName - fontSize = self.fontSize - spacewidth = stringWidth(" ", fontName, fontSize) #self.sizeOfWord(" ") - line.append(opcode) - elif indicator=="bullet": - (i, bullet, indent, font, size) = opcode - # adjust for base indent (only at format time -- only execute once) - indent = indent + self.baseindent - opcode = (i, bullet, indent, font, size) - if not first: - raise ValueError, "bullet not at beginning of line" - bulletwidth = float(stringWidth(bullet, font, size)) - spacewidth = float(stringWidth(" ", font, size)) - bulletmin = indent+spacewidth+bulletwidth - # decrease the line size to allow bullet - usedIndent = max(bulletmin, usedIndent) - if first: - maxLength = totalLength - usedIndent - self.rightIndent - line.append(opcode) - elif indicator=="rightIndent": - # increase the right indent - (i, increment) = opcode - self.rightIndent = self.rightIndent+increment - if first: - maxLength = totalLength - usedIndent - self.rightIndent - line.append(opcode) - elif indicator=="rise": - (i, rise) = opcode - newrise = self.rise = self.rise+rise - line.append(opcode) - elif indicator=="align": - (i, alignment) = opcode - #if debug: - # print "SETTING ALIGNMENT", alignment - self.alignment = alignment - line.append(opcode) - elif indicator=="lineOperation": - (i, handler) = opcode - line.append(opcode) - self.lineOpHandlers = self.lineOpHandlers + [handler] # fresh copy - elif indicator=="endLineOperation": - (i, handler) = opcode - h = self.lineOpHandlers[:] # fresh copy - h.remove(handler) - self.lineOpHandlers = h - line.append(opcode) - - else: - raise ValueError, "at format time don't understand indicator "+repr(indicator) - else: - raise ValueError, "op must be string, float, instance, or tuple "+repr(opcode) - if not done: - cursor = cursor+1 - #first = 0 -## if debug: -## if done: -## print "DONE FLAG IS SET" -## if cursor>=maxcursor: -## print "AT END OF PROGRAM" - if not terminated: - line.append( ("nextLine", 0) ) - #print "fitline", line - return (lineIsFull, line, cursor, currentLength, usedIndent, maxLength, justStrings) - - def centerAlign(self, line, lineLength, maxLength): - diff = maxLength-lineLength - shift = diff/2.0 - if shift>TOOSMALLSPACE: - return self.insertShift(line, shift) - return line - - def rightAlign(self, line, lineLength, maxLength): - shift = maxLength-lineLength - #die - if shift>TOOSMALLSPACE: - return self.insertShift(line, shift) - return line - - def insertShift(self, line, shift): - # insert shift just before first visible element in line - result = [] - first = 1 - for e in line: - te = type(e) - if first and (te in (StringType, UnicodeType, InstanceType)): - result.append(shift) - first = 0 - result.append(e) - return result - - def justifyAlign(self, line, lineLength, maxLength): - diff = maxLength-lineLength - # count EXPANDABLE SPACES AFTER THE FIRST VISIBLE - spacecount = 0 - visible = 0 - for e in line: - te = type(e) - if te is FloatType and e>TOOSMALLSPACE and visible: - spacecount = spacecount+1 - elif te in (StringType, UnicodeType, InstanceType): - visible = 1 - #if debug: print "diff is", diff, "wordcount", wordcount #; die - if spacecount<1: - return line - shift = diff/float(spacecount) - if shift<=TOOSMALLSPACE: - #if debug: print "shift too small", shift - return line - first = 1 - visible = 0 - result = [] - cursor = 0 - nline = len(line) - while cursorTOOSMALLSPACE and visible: - expanded = e+shift - result[-1] = expanded - cursor = cursor+1 - return result - -## if not first: -## #if debug: print "shifting", shift, e -## #result.append(shift) -## # add the shift in result before any start markers before e -## insertplace = len(result)-1 -## done = 0 -## myshift = shift -## while insertplace>0 and not done: -## beforeplace = insertplace-1 -## beforething = result[beforeplace] -## thingtype = type(beforething) -## if thingtype is TupleType: -## indicator = beforething[0] -## if indicator=="endLineOperation": -## done = 1 -## elif debug: -## print "adding shift before", beforething -## elif thingtype is FloatType: -## myshift = myshift + beforething -## del result[beforeplace] -## else: -## done = 1 -## if not done: -## insertplace = beforeplace -## result.insert(insertplace, myshift) -## first = 0 -## cursor = cursor+1 -## return result - - def shrinkWrap(self, line): - # for non justified text, collapse adjacent text/shift's into single operations - result = [] - index = 0 - maxindex = len(line) - while index0: - thefloats = -thefloats - if nexte<0 and thefloats>0: - nexte = -nexte - thefloats = thefloats + nexte - elif tnexte in (StringType, UnicodeType): - thestrings.append(nexte) - index = index+1 - if index0: - last = -last - if e<0 and last>0: - e = -e - last = float(last)+e - else: - if abs(last)>TOOSMALLSPACE: - result.append(last) - result.append(e) - last = 0 - if last: - result.append(last) - # now go backwards and delete all floats occurring after all visible elements -## count = len(result)-1 -## done = 0 -## while count>0 and not done: -## e = result[count] -## te = type(e) -## if te is StringType or te is InstanceType or te is TupleType: -## done = 1 -## elif te is FloatType: -## del result[count] -## count = count-1 - # move end operations left and start operations left up to visibles - change = 1 - rline = range(len(result)-1) - while change: - #print line - change = 0 - for index in rline: - nextindex = index+1 - this = result[index] - next = result[nextindex] - doswap = 0 - tthis = type(this) - tnext = type(next) - # don't swap visibles - if tthis in (StringType, UnicodeType) or \ - tnext in (StringType, UnicodeType) or \ - this is InstanceType or tnext is InstanceType: - doswap = 0 - # only swap two tuples if the second one is an end operation and the first is something else - elif tthis is TupleType: - thisindicator = this[0] - if tnext is TupleType: - nextindicator = next[0] - doswap = 0 - if (nextindicator=="endLineOperation" and thisindicator!="endLineOperation" - and thisindicator!="lineOperation"): - doswap = 1 # swap nonend<>end - elif tnext==FloatType: - if thisindicator=="lineOperation": - doswap = 1 # begin <> space - if doswap: - #print "swap", line[index],line[nextindex] - result[index] = next - result[nextindex] = this - change = 1 - return result - - def runOpCodes(self, program, canvas, textobject): - "render the line(s)" - - escape = canvas._escape - code = textobject._code - startstate = self.__dict__.copy() - font = None - size = None - # be sure to set them before using them (done lazily below) - #textobject.setFont(self.fontName, self.fontSize) - textobject.setFillColor(self.fontColor) - xstart = self.x - thislineindent = self.indent - thislinerightIndent = self.rightIndent - indented = 0 - for opcode in program: - topcode = type(opcode) - if topcode in (StringType, UnicodeType, InstanceType): - if not indented: - if abs(thislineindent)>TOOSMALLSPACE: - #if debug: print "INDENTING", thislineindent - #textobject.moveCursor(thislineindent, 0) - code.append('%s Td' % fp_str(thislineindent, 0)) - self.x = self.x + thislineindent - for handler in self.lineOpHandlers: - #handler.end_at(x, y, self, canvas, textobject) # finish, eg, underlining this line - handler.start_at(self.x, self.y, self, canvas, textobject) # start underlining the next - indented = 1 - # lazily set font (don't do it again if not needed) - if font!=self.fontName or size!=self.fontSize: - font = self.fontName - size = self.fontSize - textobject.setFont(font, size) - if topcode in (StringType, UnicodeType): - #textobject.textOut(opcode) - text = escape(opcode) - code.append('(%s) Tj' % text) - else: - # drawable thing - opcode.execute(self, textobject, canvas) - elif topcode is FloatType: - # use abs value (ignore expandable marking) - opcode = abs(opcode) - if opcode>TOOSMALLSPACE: - #textobject.moveCursor(opcode, 0) - code.append('%s Td' % fp_str(opcode, 0)) - self.x = self.x + opcode - elif topcode is TupleType: - indicator = opcode[0] - if indicator=="nextLine": - # advance to nextLine - (i, endallmarks) = opcode - x = self.x - y = self.y - newy = self.y = self.y-self.leading - newx = self.x = xstart - thislineindent = self.indent - thislinerightIndent = self.rightIndent - indented = 0 - for handler in self.lineOpHandlers: - handler.end_at(x, y, self, canvas, textobject) # finish, eg, underlining this line - #handler.start_at(newx, newy, self, canvas, textobject)) # start underlining the next - textobject.setTextOrigin(newx, newy) - elif indicator=="color": - # change fill color - oldcolor = self.fontColor - (i, colorname) = opcode - #print "opcode", opcode - if type(colorname) in (StringType, UnicodeType): - color = self.fontColor = getattr(colors, colorname) - else: - color = self.fontColor = colorname # assume its something sensible :) - #if debug: - # print color.red, color.green, color.blue - # print dir(color) - #print "color is", color - #from reportlab.lib.colors import green - #if color is green: print "color is green" - if color!=oldcolor: - textobject.setFillColor(color) - elif indicator=="face": - # change font face - (i, fontname) = opcode - self.fontName = fontname - #textobject.setFont(self.fontName, self.fontSize) - elif indicator=="size": - # change font size - (i, fontsize) = opcode - size = abs(float(fontsize)) - if type(fontsize) in (StringType, UnicodeType): - if fontsize[:1]=="+": - fontSize = self.fontSize = self.fontSize + size - elif fontsize[:1]=="-": - fontSize = self.fontSize = self.fontSize - size - else: - fontSize = self.fontSize = size - else: - fontSize = self.fontSize = size - #(i, fontsize) = opcode - self.fontSize = fontSize - textobject.setFont(self.fontName, self.fontSize) - elif indicator=="leading": - # change font leading - (i, leading) = opcode - self.leading = leading - elif indicator=="indent": - # increase the indent - (i, increment) = opcode - indent = self.indent = self.indent + increment - thislineindent = max(thislineindent, indent) - elif indicator=="push": - self.pushTextState() - elif indicator=="pop": - oldcolor = self.fontColor - oldfont = self.fontName - oldsize = self.fontSize - self.popTextState() - #if CAUSEERROR or oldfont!=self.fontName or oldsize!=self.fontSize: - # textobject.setFont(self.fontName, self.fontSize) - if oldcolor!=self.fontColor: - textobject.setFillColor(self.fontColor) - elif indicator=="wordSpacing": - (i, ws) = opcode - textobject.setWordSpace(ws) - elif indicator=="bullet": - (i, bullet, indent, font, size) = opcode - if abs(self.x-xstart)>TOOSMALLSPACE: - raise ValueError, "bullet not at beginning of line" - bulletwidth = float(stringWidth(bullet, font, size)) - spacewidth = float(stringWidth(" ", font, size)) - bulletmin = indent+spacewidth+bulletwidth - # decrease the line size to allow bullet as needed - if bulletmin > thislineindent: - #if debug: print "BULLET IS BIG", bullet, bulletmin, thislineindent - thislineindent = bulletmin - textobject.moveCursor(indent, 0) - textobject.setFont(font, size) - textobject.textOut(bullet) - textobject.moveCursor(-indent, 0) - #textobject.textOut("M") - textobject.setFont(self.fontName, self.fontSize) - elif indicator=="rightIndent": - # increase the right indent - (i, increment) = opcode - self.rightIndent = self.rightIndent+increment - elif indicator=="rise": - (i, rise) = opcode - newrise = self.rise = self.rise+rise - textobject.setRise(newrise) - elif indicator=="align": - (i, alignment) = opcode - self.alignment = alignment - elif indicator=="lineOperation": - (i, handler) = opcode - handler.start_at(self.x, self.y, self, canvas, textobject) - #self.lineOpHandlers.append(handler) - #if debug: print "adding", handler, self.lineOpHandlers - self.lineOpHandlers = self.lineOpHandlers + [handler] # fresh copy! - elif indicator=="endLineOperation": - (i, handler) = opcode - handler.end_at(self.x, self.y, self, canvas, textobject) - newh = self.lineOpHandlers = self.lineOpHandlers[:] # fresh copy - #if debug: print "removing", handler, self.lineOpHandlers - if handler in newh: - self.lineOpHandlers.remove(handler) - else: - pass - #print "WARNING: HANDLER", handler, "NOT IN", newh - else: - raise ValueError, "don't understand indicator "+repr(indicator) - else: - raise ValueError, "op must be string float or tuple "+repr(opcode) - laststate = self.__dict__.copy() - #self.resetState(startstate) - self.__dict__.update(startstate) - return laststate - -def stringLine(line, length): - "simple case: line with just strings and spacings which can be ignored" - - strings = [] - for x in line: - if type(x) in (StringType, UnicodeType): - strings.append(x) - text = ' '.join(strings) - result = [text, float(length)] - nextlinemark = ("nextLine", 0) - if line and line[-1]==nextlinemark: - result.append( nextlinemark ) - return result - -def simpleJustifyAlign(line, currentLength, maxLength): - "simple justification with only strings" - - strings = [] - for x in line[:-1]: - if type(x) in (StringType, UnicodeType): - strings.append(x) - nspaces = len(strings)-1 - slack = maxLength-currentLength - text = ' '.join(strings) - if nspaces>0 and slack>0: - wordspacing = slack/float(nspaces) - result = [("wordSpacing", wordspacing), text, maxLength, ("wordSpacing", 0)] - else: - result = [text, currentLength, ("nextLine", 0)] - nextlinemark = ("nextLine", 0) - if line and line[-1]==nextlinemark: - result.append( nextlinemark ) - return result - -from reportlab.lib.colors import black - -def readBool(text): - if text.upper() in ("Y", "YES", "TRUE", "1"): - return 1 - elif text.upper() in ("N", "NO", "FALSE", "0"): - return 0 - else: - raise RMLError, "true/false attribute has illegal value '%s'" % text - -def readAlignment(text): - up = text.upper() - if up == 'LEFT': - return TA_LEFT - elif up == 'RIGHT': - return TA_RIGHT - elif up in ['CENTER', 'CENTRE']: - return TA_CENTER - elif up == 'JUSTIFY': - return TA_JUSTIFY - -def readLength(text): - """Read a dimension measurement: accept "3in", "5cm", - "72 pt" and so on.""" - text = text.strip() - try: - return float(text) - except ValueError: - text = text.lower() - numberText, units = text[:-2],text[-2:] - numberText = numberText.strip() - try: - number = float(numberText) - except ValueError: - raise ValueError, "invalid length attribute '%s'" % text - try: - multiplier = { - 'in':72, - 'cm':28.3464566929, #72/2.54; is this accurate? - 'mm':2.83464566929, - 'pt':1 - }[units] - except KeyError: - raise RMLError, "invalid length attribute '%s'" % text - - return number * multiplier - -def lengthSequence(s, converter=readLength): - """from "(2, 1)" or "2,1" return [2,1], for example""" - s = s.strip() - if s[:1]=="(" and s[-1:]==")": - s = s[1:-1] - sl = s.split(',') - sl = [s.strip() for s in sl] - sl = [converter(s) for s in sl] - return sl - - -def readColor(text): - """Read color names or tuples, RGB or CMYK, and return a Color object.""" - if not text: - return None - from reportlab.lib import colors - from string import letters - if text[0] in letters: - return colors.__dict__[text] - tup = lengthSequence(text) - - msg = "Color tuple must have 3 (or 4) elements for RGB (or CMYC)." - assert 3 <= len(tup) <= 4, msg - msg = "Color tuple must have all elements <= 1.0." - for i in range(len(tup)): - assert tup[i] <= 1.0, msg - - if len(tup) == 3: - colClass = colors.Color - elif len(tup) == 4: - colClass = colors.CMYKColor - return apply(colClass, tup) - -class StyleAttributeConverters: - fontSize=[readLength] - leading=[readLength] - leftIndent=[readLength] - rightIndent=[readLength] - firstLineIndent=[readLength] - alignment=[readAlignment] - spaceBefore=[readLength] - spaceAfter=[readLength] - bulletFontSize=[readLength] - bulletIndent=[readLength] - textColor=[readColor] - backColor=[readColor] - -class SimpleStyle: - "simplified paragraph style without all the fancy stuff" - name = "basic" - fontName='Times-Roman' - fontSize=10 - leading=12 - leftIndent=0 - rightIndent=0 - firstLineIndent=0 - alignment=TA_LEFT - spaceBefore=0 - spaceAfter=0 - bulletFontName='Times-Roman' - bulletFontSize=10 - bulletIndent=0 - textColor=black - backColor=None - - def __init__(self, name, parent=None, **kw): - mydict = self.__dict__ - if parent: - for (a,b) in parent.__dict__.items(): - mydict[a]=b - for (a,b) in kw.items(): - mydict[a] = b - - def addAttributes(self, dictionary): - for key in dictionary.keys(): - value = dictionary[key] - if value is not None: - if hasattr(StyleAttributeConverters, key): - converter = getattr(StyleAttributeConverters, key)[0] - value = converter(value) - setattr(self, key, value) - - -DEFAULT_ALIASES = { - "h1.defaultStyle": "Heading1", - "h2.defaultStyle": "Heading2", - "h3.defaultStyle": "Heading3", - "h4.defaultStyle": "Heading4", - "h5.defaultStyle": "Heading5", - "h6.defaultStyle": "Heading6", - "title.defaultStyle": "Title", - "subtitle.defaultStyle": "SubTitle", - "para.defaultStyle": "Normal", - "pre.defaultStyle": "Code", - "ul.defaultStyle": "UnorderedList", - "ol.defaultStyle": "OrderedList", - "li.defaultStyle": "Definition", - } - -class FastPara(Flowable): - "paragraph with no special features (not even a single ampersand!)" - - def __init__(self, style, simpletext): - #if debug: - # print "FAST", id(self) - if "&" in simpletext: - raise ValueError, "no ampersands please!" - self.style = style - self.simpletext = simpletext - self.lines = None - - def wrap(self, availableWidth, availableHeight): - simpletext = self.simpletext - self.availableWidth = availableWidth - style = self.style - text = self.simpletext - rightIndent = style.rightIndent - leftIndent = style.leftIndent - leading = style.leading - font = style.fontName - size = style.fontSize - firstindent = style.firstLineIndent - #textcolor = style.textColor - words = simpletext.split() - lines = [] - from reportlab.pdfbase.pdfmetrics import stringWidth - spacewidth = stringWidth(" ", font, size) - currentline = [] - currentlength = 0 - firstmaxlength = availableWidth - rightIndent - firstindent - maxlength = availableWidth - rightIndent - leftIndent - if maxlengthavailableHeight: - done = 1 - if currentlength and not done: - lines.append( (' '.join(currentline), currentlength, len(currentline) )) - heightused = heightused+leading - self.lines = lines - self.height = heightused - remainder = self.remainder = ' '.join(words[cursor:]) - #print "lines", lines - #print "remainder is", remainder - else: - remainder = None - heightused = self.height - lines = self.lines - if remainder: - result = (availableWidth, availableHeight+leading) # need to split - else: - result = (availableWidth, heightused) - #if debug: print "wrap is", (availableWidth, availableHeight), result, len(lines) - return result - - def split(self, availableWidth, availableHeight): - style = self.style - leading = style.leading - if availableHeight1: - # patch from doug@pennatus.com, 9 Nov 2002, no extraspace on last line - textobject.setWordSpace((basicWidth-length)/(nwords-1.0)) - else: - textobject.setWordSpace(0.0) - textobject.setTextOrigin(x,y) - text = escape(text) - code.append('(%s) Tj' % text) - #textobject.textOut(text) - y = y-leading - c.drawText(textobject) - - def getSpaceBefore(self): - #if debug: - # print "got space before", self.spaceBefore - return self.style.spaceBefore - - def getSpaceAfter(self): - #print "got space after", self.spaceAfter - return self.style.spaceAfter - -def defaultContext(): - result = {} - from reportlab.lib.styles import getSampleStyleSheet - styles = getSampleStyleSheet() - for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items(): - result[stylenamekey] = styles[stylenamevalue] - return result - -def buildContext(stylesheet=None): - result = {} - from reportlab.lib.styles import getSampleStyleSheet - if stylesheet is not None: - # Copy styles with the same name as aliases - for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items(): - if stylesheet.has_key(stylenamekey): - result[stylenamekey] = stylesheet[stylenamekey] - # Then make aliases - for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items(): - if stylesheet.has_key(stylenamevalue): - result[stylenamekey] = stylesheet[stylenamevalue] - - styles = getSampleStyleSheet() - # Then, fill in defaults if they were not filled yet. - for (stylenamekey, stylenamevalue) in DEFAULT_ALIASES.items(): - if not result.has_key(stylenamekey) and styles.has_key(stylenamevalue): - result[stylenamekey] = styles[stylenamevalue] - return result - -class Para(Flowable): - - spaceBefore = 0 - spaceAfter = 0 - - def __init__(self, style, parsedText=None, bulletText=None, state=None, context=None, baseindent=0): - #print id(self), "para", parsedText - self.baseindent = baseindent - self.context = buildContext(context) - self.parsedText = parsedText - self.bulletText = bulletText - self.style1 = style # make sure Flowable doesn't use this unless wanted! call it style1 NOT style - #self.spaceBefore = self.spaceAfter = 0 - self.program = [] # program before layout - self.formattedProgram = [] # after layout - self.remainder = None # follow on paragraph if any - self.state = state # initial formatting state (for completions) - if not state: - self.spaceBefore = style.spaceBefore - self.spaceAfter = style.spaceAfter - #self.spaceBefore = "invalid value" - #if hasattr(self, "spaceBefore") and debug: - # print "spaceBefore is", self.spaceBefore, self.parsedText - self.bold = 0 - self.italic = 0 - self.face = style.fontName - self.size = style.fontSize - - def getSpaceBefore(self): - #if debug: - # print "got space before", self.spaceBefore - return self.spaceBefore - - def getSpaceAfter(self): - #print "got space after", self.spaceAfter - return self.spaceAfter - - def wrap(self, availableWidth, availableHeight): - if debug: - print "WRAPPING", id(self), availableWidth, availableHeight - print " ", self.formattedProgram - print " ", self.program - self.availableHeight = availableHeight - self.myengine = p = paragraphEngine() - p.baseindent = self.baseindent # for shifting bullets as needed - parsedText = self.parsedText - formattedProgram = self.formattedProgram - state = self.state - if state: - leading = state["leading"] - else: - leading = self.style1.leading - program = self.program - self.cansplit = 1 # until proven otherwise - if state: - p.resetState(state) - p.x = 0 - p.y = 0 - needatleast = state["leading"] - else: - needatleast = self.style1.leading - if availableHeight<=needatleast: - self.cansplit = 0 - #if debug: - # print "CANNOT COMPILE, NEED AT LEAST", needatleast, 'AVAILABLE', availableHeight - return (availableHeight+1, availableWidth) # cannot split - if parsedText is None and program is None: - raise ValueError, "need parsedText for formatting" - if not program: - self.program = program = self.compileProgram(parsedText) - if not self.formattedProgram: - (formattedProgram, remainder, \ - laststate, heightused) = p.format(availableWidth, availableHeight, program, leading) - self.formattedProgram = formattedProgram - self.height = heightused - self.laststate = laststate - self.remainderProgram = remainder - else: - heightused = self.height - remainder = None - # too big if there is a remainder - if remainder: - # lie about the height: it must be split anyway - #if debug: - # print "I need to split", self.formattedProgram - # print "heightused", heightused, "available", availableHeight, "remainder", len(remainder) - height = availableHeight + 1 - #print "laststate is", laststate - #print "saving remainder", remainder - self.remainder = Para(self.style1, parsedText=None, bulletText=None, \ - state=laststate, context=self.context) - self.remainder.program = remainder - self.remainder.spaceAfter = self.spaceAfter - self.spaceAfter = 0 - else: - self.remainder = None # no extra - height = heightused - if height>availableHeight: - height = availableHeight-0.1 - #if debug: - # print "giving height", height, "of", availableHeight, self.parsedText - result = (availableWidth, height) - if debug: - (w, h) = result - if abs(availableHeight-h)<0.2: - print "exact match???" + repr(availableHeight, h) - print "wrap is", (availableWidth, availableHeight), result - return result - - def split(self, availableWidth, availableHeight): - #if debug: - # print "SPLITTING", id(self), availableWidth, availableHeight - if availableHeight<=0 or not self.cansplit: - #if debug: - # print "cannot split", availableWidth, "too small" - return [] # wrap failed to find a split - self.availableHeight = availableHeight - formattedProgram = self.formattedProgram - #print "formattedProgram is", formattedProgram - if formattedProgram is None: - raise ValueError, "must call wrap before split" - elif not formattedProgram: - # no first line in self: fail to split - return [] - remainder = self.remainder - if remainder: - #print "SPLITTING" - result= [self, remainder] - else: - result= [self] - #if debug: print "split is", result - return result - - def draw(self): - p = self.myengine #paragraphEngine() - formattedProgram = self.formattedProgram - if formattedProgram is None: - raise ValueError, "must call wrap before draw" - state = self.state - laststate = self.laststate - if state: - p.resetState(state) - p.x = 0 - p.y = 0 - c = self.canv - #if debug: - # print id(self), "page number", c.getPageNumber() - height = self.height - if state: - leading = state["leading"] - else: - leading = self.style1.leading - #if debug: - # c.rect(0,0,-1, height-self.size, fill=1, stroke=1) - c.translate(0, height-self.size) - t = c.beginText() - #t.setTextOrigin(0,0) - if DUMPPROGRAM or debug: - print "="*44, "now running program" - for x in formattedProgram: - print x - print "-"*44 - laststate = p.runOpCodes(formattedProgram, c, t) - #print laststate["x"], laststate["y"] - c.drawText(t) - - def compileProgram(self, parsedText, program=None): - style = self.style1 - # standard parameters - #program = self.program - if program is None: - program = [] - a = program.append - fn = style.fontName - # add style information if there was no initial state - a( ("face", fn ) ) - from reportlab.lib.fonts import ps2tt - (self.face, self.bold, self.italic) = ps2tt(fn) - a( ("size", style.fontSize ) ) - self.size = style.fontSize - a( ("align", style.alignment ) ) - a( ("indent", style.leftIndent ) ) - if style.firstLineIndent: - a( ("indent", style.firstLineIndent ) ) # must be undone later - a( ("rightIndent", style.rightIndent ) ) - a( ("leading", style.leading) ) - if style.textColor: - a( ("color", style.textColor) ) - #a( ("nextLine", 0) ) # clear for next line - if self.bulletText: - self.do_bullet(self.bulletText, program) - self.compileComponent(parsedText, program) - # now look for a place where to insert the unindent after the first line - if style.firstLineIndent: - count = 0 - for x in program: - count = count+1 - tx = type(x) - if tx in (StringType, UnicodeType, InstanceType): - break - program.insert( count, ("indent", -style.firstLineIndent ) ) # defaults to end if no visibles - #print "="*8, id(self), "program is" - #for x in program: - # print x -## print "="*11 -## # check pushes and pops -## stackcount = 0 -## dump = 0 -## for x in program: -## if dump: -## print "dump:", x -## if type(x) is TupleType: -## i = x[0] -## if i=="push": -## stackcount = stackcount+1 -## print " "*stackcount, "push", stackcount -## if i=="pop": -## stackcount = stackcount-1 -## print " "*stackcount, "pop", stackcount -## if stackcount<0: -## dump=1 -## print "STACK UNDERFLOW!" -## if dump: stop - return program - - def linearize(self, program = None, parsedText=None): - #print "LINEARIZING", self - #program = self.program = [] - if parsedText is None: - parsedText = self.parsedText - style = self.style1 - if program is None: - program = [] - program.append( ("push",) ) - if style.spaceBefore: - program.append( ("leading", style.spaceBefore+style.leading) ) - else: - program.append( ("leading", style.leading) ) - program.append( ("nextLine", 0) ) - program = self.compileProgram(parsedText, program=program) - program.append( ("pop",) ) - # go to old margin - program.append( ("push",) ) - if style.spaceAfter: - program.append( ("leading", style.spaceAfter) ) - else: - program.append( ("leading", 0) ) - program.append( ("nextLine", 0) ) - program.append( ("pop",) ) - - def compileComponent(self, parsedText, program): - import types - ttext = type(parsedText) - #program = self.program - if ttext in (StringType, UnicodeType): - # handle special characters here... - # short cut - if parsedText: - stext = parsedText.strip() - if not stext: - program.append(" ") # contract whitespace to single space - else: - handleSpecialCharacters(self, parsedText, program) - elif ttext is ListType: - for e in parsedText: - self.compileComponent(e, program) - elif ttext is TupleType: - (tagname, attdict, content, extra) = parsedText - if not attdict: - attdict = {} - compilername = "compile_"+tagname - compiler = getattr(self, compilername, None) - if compiler is not None: - compiler(attdict, content, extra, program) - else: - # just pass the tag through - if debug: - L = [ "<" + tagname ] - a = L.append - if not attdict: attdict = {} - for (k, v) in attdict.items(): - a(" %s=%s" % (k,v)) - if content: - a(">") - a(str(content)) - a("" % tagname) - else: - a("/>") - t = ''.join(L) - handleSpecialCharacters(self, t, program) - else: - raise ValueError, "don't know how to handle tag " + repr(tagname) - - def shiftfont(self, program, face=None, bold=None, italic=None): - oldface = self.face - oldbold = self.bold - olditalic = self.italic - oldfontinfo = (oldface, oldbold, olditalic) - if face is None: face = oldface - if bold is None: bold = oldbold - if italic is None: italic = olditalic - self.face = face - self.bold = bold - self.italic = italic - from reportlab.lib.fonts import tt2ps - font = tt2ps(face,bold,italic) - oldfont = tt2ps(oldface,oldbold,olditalic) - if font!=oldfont: - program.append( ("face", font ) ) - return oldfontinfo - - def compile_(self, attdict, content, extra, program): - # "anonymous" tag: just do the content - for e in content: - self.compileComponent(e, program) - #compile_para = compile_ # at least for now... - - def compile_pageNumber(self, attdict, content, extra, program): - program.append(PageNumberObject()) - - def compile_b(self, attdict, content, extra, program): - (f,b,i) = self.shiftfont(program, bold=1) - for e in content: - self.compileComponent(e, program) - self.shiftfont(program, bold=b) - - def compile_i(self, attdict, content, extra, program): - (f,b,i) = self.shiftfont(program, italic=1) - for e in content: - self.compileComponent(e, program) - self.shiftfont(program, italic=i) - - def compile_u(self, attdict, content, extra, program): - # XXXX must eventually add things like alternative colors - #program = self.program - program.append( ('lineOperation', UNDERLINE) ) - for e in content: - self.compileComponent(e, program) - program.append( ('endLineOperation', UNDERLINE) ) - - def compile_sub(self, attdict, content, extra, program): - size = self.size - self.size = newsize = size * 0.7 - rise = size*0.5 - #program = self.program - program.append( ('size', newsize) ) - self.size = size - program.append( ('rise', -rise) ) - for e in content: - self.compileComponent(e, program) - program.append( ('size', size) ) - program.append( ('rise', rise) ) - - def compile_ul(self, attdict, content, extra, program, tagname="ul"): - # by transformation - #print "compile", tagname, attdict - atts = attdict.copy() - bulletmaker = bulletMaker(tagname, atts, self.context) - # now do each element as a separate paragraph - for e in content: - te = type(e) - if te in (StringType, UnicodeType): - if e.strip(): - raise ValueError, "don't expect CDATA between list elements" - elif te is TupleType: - (tagname, attdict1, content1, extra) = e - if tagname!="li": - raise ValueError, "don't expect %s inside list" % repr(tagname) - newatts = atts.copy() - if attdict1: - newatts.update(attdict1) - bulletmaker.makeBullet(newatts) - self.compile_para(newatts, content1, extra, program) - - def compile_ol(self, attdict, content, extra, program): - return self.compile_ul(attdict, content, extra, program, tagname="ol") - - def compile_dl(self, attdict, content, extra, program): - # by transformation - #print "compile", tagname, attdict - atts = attdict.copy() - # by transformation - #print "compile", tagname, attdict - atts = attdict.copy() - bulletmaker = bulletMaker("dl", atts, self.context) - # now do each element as a separate paragraph - contentcopy = list(content) # copy for destruction - bullet = "" - while contentcopy: - e = contentcopy[0] - del contentcopy[0] - te = type(e) - if te in (StringType, UnicodeType): - if e.strip(): - raise ValueError, "don't expect CDATA between list elements" - elif not contentcopy: - break # done at ending whitespace - else: - continue # ignore intermediate whitespace - elif te is TupleType: - (tagname, attdict1, content1, extra) = e - if tagname!="dd" and tagname!="dt": - raise ValueError, "don't expect %s here inside list, expect 'dd' or 'dt'" % \ - repr(tagname) - if tagname=="dt": - if bullet: - raise ValueError, "dt will not be displayed unless followed by a dd: "+repr(bullet) - if content1: - self.compile_para(attdict1, content1, extra, program) - # raise ValueError, \ - # "only simple strings supported in dd content currently: "+repr(content1) - elif tagname=="dd": - newatts = atts.copy() - if attdict1: - newatts.update(attdict1) - bulletmaker.makeBullet(newatts, bl=bullet) - self.compile_para(newatts, content1, extra, program) - bullet = "" # don't use this bullet again - if bullet: - raise ValueError, "dt will not be displayed unless followed by a dd"+repr(bullet) - - def compile_super(self, attdict, content, extra, program): - size = self.size - self.size = newsize = size * 0.7 - rise = size*0.5 - #program = self.program - program.append( ('size', newsize) ) - program.append( ('rise', rise) ) - for e in content: - self.compileComponent(e, program) - program.append( ('size', size) ) - self.size = size - program.append( ('rise', -rise) ) - - def compile_font(self, attdict, content, extra, program): - #program = self.program - program.append( ("push",) ) # store current data - if attdict.has_key("face"): - face = attdict["face"] - from reportlab.lib.fonts import tt2ps - try: - font = tt2ps(face,self.bold,self.italic) - except: - font = face # better work! - program.append( ("face", font ) ) - if attdict.has_key("color"): - colorname = attdict["color"] - program.append( ("color", colorname) ) - if attdict.has_key("size"): - #size = float(attdict["size"]) # really should convert int, cm etc here! - size = attdict["size"] - program.append( ("size", size) ) - for e in content: - self.compileComponent(e, program) - program.append( ("pop",) ) # restore as before - - def compile_a(self, attdict, content, extra, program): - url = attdict["href"] - colorname = attdict.get("color", "blue") - #program = self.program - Link = HotLink(url) - program.append( ("push",) ) # store current data - program.append( ("color", colorname) ) - program.append( ('lineOperation', Link) ) - program.append( ('lineOperation', UNDERLINE) ) - for e in content: - self.compileComponent(e, program) - program.append( ('endLineOperation', UNDERLINE) ) - program.append( ('endLineOperation', Link) ) - program.append( ("pop",) ) # restore as before - - def compile_link(self, attdict, content, extra, program): - dest = attdict["destination"] - colorname = attdict.get("color", None) - #program = self.program - Link = InternalLink(dest) - program.append( ("push",) ) # store current data - if colorname: - program.append( ("color", colorname) ) - program.append( ('lineOperation', Link) ) - program.append( ('lineOperation', UNDERLINE) ) - for e in content: - self.compileComponent(e, program) - program.append( ('endLineOperation', UNDERLINE) ) - program.append( ('endLineOperation', Link) ) - program.append( ("pop",) ) # restore as before - - def compile_setLink(self, attdict, content, extra, program): - dest = attdict["destination"] - colorname = attdict.get("color", "blue") - #program = self.program - Link = DefDestination(dest) - program.append( ("push",) ) # store current data - if colorname: - program.append( ("color", colorname) ) - program.append( ('lineOperation', Link) ) - if colorname: - program.append( ('lineOperation', UNDERLINE) ) - for e in content: - self.compileComponent(e, program) - if colorname: - program.append( ('endLineOperation', UNDERLINE) ) - program.append( ('endLineOperation', Link) ) - program.append( ("pop",) ) # restore as before - - #def compile_p(self, attdict, content, extra, program): - # # have to be careful about base indent here! - # not finished - - def compile_bullet(self, attdict, content, extra, program): - ### eventually should allow things like images and graphics in bullets too XXXX - if len(content)!=1 or type(content[0]) not in (StringType, UnicodeType): - raise ValueError, "content for bullet must be a single string" - text = content[0] - self.do_bullet(text, program) - - def do_bullet(self, text, program): - style = self.style1 - #program = self.program - indent = style.bulletIndent + self.baseindent - font = style.bulletFontName - size = style.bulletFontSize - program.append( ("bullet", text, indent, font, size) ) - - def compile_tt(self, attdict, content, extra, program): - (f,b,i) = self.shiftfont(program, face="Courier") - for e in content: - self.compileComponent(e, program) - self.shiftfont(program, face=f) - - def compile_greek(self, attdict, content, extra, program): - self.compile_font({"face": "symbol"}, content, extra, program) - - def compile_evalString(self, attdict, content, extra, program): - program.append( EvalStringObject(attdict, content, extra, self.context) ) - - def compile_name(self, attdict, content, extra, program): - program.append( NameObject(attdict, content, extra, self.context) ) - - def compile_getName(self, attdict, content, extra, program): - program.append( GetNameObject(attdict, content, extra, self.context) ) - - def compile_seq(self, attdict, content, extra, program): - program.append( SeqObject(attdict, content, extra, self.context) ) - - def compile_seqReset(self, attdict, content, extra, program): - program.append( SeqResetObject(attdict, content, extra, self.context) ) - - def compile_seqDefault(self, attdict, content, extra, program): - program.append( SeqDefaultObject(attdict, content, extra, self.context) ) - - def compile_para(self, attdict, content, extra, program, stylename = "para.defaultStyle"): - if attdict is None: - attdict = {} - context = self.context - stylename = attdict.get("style", stylename) - style = context[stylename] - newstyle = SimpleStyle(name="rml2pdf internal embedded style", parent=style) - newstyle.addAttributes(attdict) - bulletText = attdict.get("bulletText", None) - mystyle = self.style1 - thepara = Para(newstyle, content, context=context, bulletText=bulletText) - # possible ref loop on context, break later - # now compile it and add it to the program - mybaseindent = self.baseindent - self.baseindent = thepara.baseindent = mystyle.leftIndent + self.baseindent - thepara.linearize(program=program) - program.append( ("nextLine", 0) ) - self.baseindent = mybaseindent - -class bulletMaker: - def __init__(self, tagname, atts, context): - self.tagname = tagname - #print "context is", context - style = "li.defaultStyle" - self.style = style = atts.get("style", style) - typ = {"ul": "disc", "ol": "1", "dl": None}[tagname] - #print tagname, "bulletmaker type is", typ - self.typ =typ = atts.get("type", typ) - #print tagname, "bulletmaker type is", typ - if not atts.has_key("leftIndent"): - # get the style so you can choose an indent length - thestyle = context[style] - from reportlab.pdfbase.pdfmetrics import stringWidth - size = thestyle.fontSize - indent = stringWidth("XXX", "Courier", size) - atts["leftIndent"] = str(indent) - self.count = 0 - - def makeBullet(self, atts, bl=None): - count = self.count = self.count+1 - typ = self.typ - tagname = self.tagname - #print "makeBullet", tagname, typ, count - # forget space before for non-first elements - if count>1: - atts["spaceBefore"] = "0" - if bl is None: - if tagname=="ul": - if typ=="disc": bl = chr(109) - elif typ=="circle": bl = chr(108) - elif typ=="square": bl = chr(110) - else: - raise ValueError, "unordered list type %s not implemented" % repr(typ) - if not atts.has_key("bulletFontName"): - atts["bulletFontName"] = "ZapfDingbats" - elif tagname=="ol": - if typ=="1": bl = repr(count) - elif typ=="a": - theord = ord("a")+count-1 - bl = chr(theord) - elif typ=="A": - theord = ord("A")+count-1 - bl = chr(theord) - else: - raise ValueError, "ordered bullet type %s not implemented" % repr(typ) - else: - raise ValueError, "bad tagname "+repr(tagname) - if not atts.has_key("bulletText"): - atts["bulletText"] = bl - if not atts.has_key("style"): - atts["style"] = self.style - -class EvalStringObject: - "this will only work if rml2pdf is present" - - tagname = "evalString" - - def __init__(self, attdict, content, extra, context): - if not attdict: - attdict = {} - self.attdict = attdict - self.content = content - self.context = context - self.extra = extra - - def getOp(self, tuple, engine): - from rlextra.rml2pdf.rml2pdf import Controller - #print "tuple", tuple - op = self.op = Controller.processTuple(tuple, self.context, {}) - return op - - def width(self, engine): - from reportlab.pdfbase.pdfmetrics import stringWidth - content = self.content - if not content: - content = [] - tuple = (self.tagname, self.attdict, content, self.extra) - op = self.op = self.getOp(tuple, engine) - #print op.__class__ - #print op.pcontent - #print self - s = str(op) - return stringWidth(s, engine.fontName, engine.fontSize) - - def execute(self, engine, textobject, canvas): - textobject.textOut(str(self.op)) - -class SeqObject(EvalStringObject): - - def getOp(self, tuple, engine): - from reportlab.lib.sequencer import getSequencer - globalsequencer = getSequencer() - attr = self.attdict - #if it has a template, use that; otherwise try for id; - #otherwise take default sequence - if attr.has_key('template'): - templ = attr['template'] - op = self.op = templ % globalsequencer - return op - elif attr.has_key('id'): - id = attr['id'] - else: - id = None - op = self.op = globalsequencer.nextf(id) - return op - -class NameObject(EvalStringObject): - tagname = "name" - def execute(self, engine, textobject, canvas): - pass # name doesn't produce any output - -class SeqDefaultObject(NameObject): - - def getOp(self, tuple, engine): - from reportlab.lib.sequencer import getSequencer - globalsequencer = getSequencer() - attr = self.attdict - try: - default = attr['id'] - except KeyError: - default = None - globalsequencer.setDefaultCounter(default) - self.op = "" - return "" - -class SeqResetObject(NameObject): - - def getOp(self, tuple, engine): - from reportlab.lib.sequencer import getSequencer - import math - globalsequencer = getSequencer() - attr = self.attdict - try: - id = attr['id'] - except KeyError: - id = None - try: - base = math.atoi(attr['base']) - except: - base=0 - globalsequencer.reset(id, base) - self.op = "" - return "" - -class GetNameObject(EvalStringObject): - tagname = "getName" - -class PageNumberObject: - - def __init__(self, example="XXX"): - self.example = example # XXX SHOULD ADD THE ABILITY TO PASS IN EXAMPLES - - def width(self, engine): - from reportlab.pdfbase.pdfmetrics import stringWidth - return stringWidth(self.example, engine.fontName, engine.fontSize) - - def execute(self, engine, textobject, canvas): - n = canvas.getPageNumber() - textobject.textOut(str(n)) - -### this should be moved into rml2pdf -def EmbedInRml2pdf(): - "make the para the default para implementation in rml2pdf" - from rlextra.rml2pdf.rml2pdf import MapNode, Controller # may not need to use superclass? - global paraMapper, theParaMapper, ulMapper - - class paraMapper(MapNode): - #stylename = "para.defaultStyle" - def translate(self, nodetuple, controller, context, overrides): - (tagname, attdict, content, extra) = nodetuple - stylename = tagname+".defaultStyle" - stylename = attdict.get("style", stylename) - style = context[stylename] - mystyle = SimpleStyle(name="rml2pdf internal style", parent=style) - mystyle.addAttributes(attdict) - bulletText = attdict.get("bulletText", None) - # can we use the fast implementation? - import types - result = None - if not bulletText and len(content)==1: - text = content[0] - if type(text) in (StringType, UnicodeType) and "&" not in text: - result = FastPara(mystyle, text) - if result is None: - result = Para(mystyle, content, context=context, bulletText=bulletText) # possible ref loop on context, break later - return result - - theParaMapper = paraMapper() - - class ulMapper(MapNode): - # wrap in a default para and let the para do it - def translate(self, nodetuple, controller, context, overrides): - thepara = ("para", {}, [nodetuple], None) - return theParaMapper.translate(thepara, controller, context, overrides) - - # override rml2pdf interpreters (should be moved to rml2pdf) - theListMapper = ulMapper() - Controller["ul"] = theListMapper - Controller["ol"] = theListMapper - Controller["dl"] = theListMapper - Controller["para"] = theParaMapper - Controller["h1"] = theParaMapper - Controller["h2"] = theParaMapper - Controller["h3"] = theParaMapper - Controller["title"] = theParaMapper - - -testparagraph = """ -This is Text. -This is bold text. -This is Text. -This is italic text. - -
                -
              • this is an element at 1 -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth -more text monospaced and back to normal - -
                  -
                • this is an element at 2 - -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth - -
                    -
                  • this is an element at 3 - -more text and even more text and on and on and so forth - - -
                    -
                    frogs
                    Little green slimy things. Delicious with garlic
                    -
                    kittens
                    cute, furry, not edible
                    -
                    bunnies
                    cute, furry,. Delicious with garlic
                    -
                    - -more text and even more text and on and on and so forth - -
                      -
                    • this is an element at 4 - -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth - -
                    • -
                    - -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth - -
                  • -
                  -more text and even more text and on and on and so forth -more text and even more text and on and on and so forth - -
                • - -
                -UNDERLINED more text and even more text and on and on and so forth -more text and even more text and on and on and so forth - -
                  -
                1. first element of the alpha list - -
                    -
                  • first element of the square unnumberred list
                  • - -
                  • second element of the unnumberred list
                  • - -
                  • third element of the unnumberred list - third element of the unnumberred list - third element of the unnumberred list - third element of the unnumberred list - third element of the unnumberred list - third element of the unnumberred list - third element of the unnumberred list -
                  • - -
                  • fourth element of the unnumberred list
                  • - -
                  - -
                2. - -
                3. second element of the alpha list
                4. - -
                5. third element of the alpha list - third element of the unnumberred list &#33; --> ! - third element of the unnumberred list &#8704; --> ∀ - third element of the unnumberred list &exist; --> ∃ - third element of the unnumberred list - third element of the unnumberred list - third element of the unnumberred list -
                6. - -
                7. fourth element of the alpha list
                8. - -
                - - -
              • -
              - -goto www.reportlab.com. - - - -Red letter. thisisareallylongword andsoisthis andthisislonger -justified text paragraph example -justified text paragraph example -justified text paragraph example - - -""" - -def test2(canv): - #print test_program; return - from reportlab.lib.units import inch - from reportlab.lib.styles import ParagraphStyle - from reportlab.lib import rparsexml - parsedpara = rparsexml.parsexmlSimple(testparagraph,entityReplacer=None) - S = ParagraphStyle("Normal", None) - P = Para(S, parsedpara) - (w, h) = P.wrap(5*inch, 10*inch) - print "wrapped as", (h,w) - canv.translate(1*inch, 1*inch) - canv.rect(0,0,5*inch,10*inch, fill=0, stroke=1) - P.canv = canv - P.draw() - canv.setStrokeColorRGB(1, 0, 0) - #canv.translate(0, 3*inch) - canv.rect(0,0,w,-h, fill=0, stroke=1) - -def handleSpecialCharacters(engine, text, program=None): - from paraparser import greeks, symenc - from string import whitespace, atoi, atoi_error - standard={'lt':'<', 'gt':'>', 'amp':'&'} - # add space prefix if space here - if text[0:1] in whitespace: - program.append(" ") - #print "handling", repr(text) - # shortcut - if 0 and "&" not in text: - result = [] - for x in text.split(): - result.append(x+" ") - if result: - last = result[-1] - if text[-1:] not in whitespace: - result[-1] = last.strip() - program.extend(result) - return program - if program is None: - program = [] - amptext = text.split("&") - first = 1 - lastfrag = amptext[-1] - for fragment in amptext: - if not first: - # check for special chars - semi = fragment.find(";") - if semi>0: - name = fragment[:semi] - if name[0]=='#': - try: - if name[1] == 'x': - n = atoi(name[2:], 16) - else: - n = atoi(name[1:]) - except atoi_error: - n = -1 - if 0<=n<=255: fragment = chr(n)+fragment[semi+1:] - elif symenc.has_key(n): - fragment = fragment[semi+1:] - (f,b,i) = engine.shiftfont(program, face="symbol") - program.append(symenc[n]) - engine.shiftfont(program, face=f) - if fragment and fragment[0] in whitespace: - program.append(" ") # follow with a space - else: - fragment = "&"+fragment - elif standard.has_key(name): - fragment = standard[name]+fragment[semi+1:] - elif greeks.has_key(name): - fragment = fragment[semi+1:] - greeksub = greeks[name] - (f,b,i) = engine.shiftfont(program, face="symbol") - program.append(greeksub) - engine.shiftfont(program, face=f) - if fragment and fragment[0] in whitespace: - program.append(" ") # follow with a space - else: - # add back the & - fragment = "&"+fragment - else: - # add back the & - fragment = "&"+fragment - # add white separated components of fragment followed by space - sfragment = fragment.split() - for w in sfragment[:-1]: - program.append(w+" ") - # does the last one need a space? - if sfragment and fragment: - # reader 3 used to go nuts if you don't special case the last frag, but it's fixed? - if fragment[-1] in whitespace: # or fragment==lastfrag: - program.append( sfragment[-1]+" " ) - else: - last = sfragment[-1].strip() - if last: - #print "last is", repr(last) - program.append( last ) - first = 0 - #print "HANDLED", program - return program - -def Paragraph(text, style, bulletText=None, frags=None, context=None): - """ Paragraph(text, style, bulletText=None) - intended to be like a platypus Paragraph but better. - """ - # if there is no & or < in text then use the fast paragraph - if "&" not in text and "<" not in text: - return FastPara(style, simpletext=text) - else: - # use the fully featured one. - from reportlab.lib import rparsexml - parsedpara = rparsexml.parsexmlSimple(text,entityReplacer=None) - return Para(style, parsedText=parsedpara, bulletText=bulletText, state=None, context=context) - -class UnderLineHandler: - def __init__(self, color=None): - self.color = color - def start_at(self, x,y, para, canvas, textobject): - self.xStart = x - self.yStart = y - def end_at(self, x, y, para, canvas, textobject): - offset = para.fontSize/8.0 - canvas.saveState() - color = self.color - if self.color is None: - color = para.fontColor - canvas.setStrokeColor(color) - canvas.line(self.xStart, self.yStart-offset, x,y-offset) - canvas.restoreState() - -UNDERLINE = UnderLineHandler() - -class HotLink(UnderLineHandler): - - def __init__(self, url): - self.url = url - - def end_at(self, x, y, para, canvas, textobject): - fontsize = para.fontSize - rect = [self.xStart, self.yStart, x,y+fontsize] - if debug: - print "LINKING RECTANGLE", rect - #canvas.rect(self.xStart, self.yStart, x-self.xStart,y+fontsize-self.yStart, stroke=1) - self.link(rect, canvas) - - def link(self, rect, canvas): - canvas.linkURL(self.url, rect, relative=1) - -class InternalLink(HotLink): - - def link(self, rect, canvas): - destinationname = self.url - contents = "" - canvas.linkRect(contents, destinationname, rect, Border="[0 0 0]") - -class DefDestination(HotLink): - - defined = 0 - - def link(self, rect, canvas): - destinationname = self.url - if not self.defined: - [x, y, x1, y1] = rect - canvas.bookmarkHorizontal(destinationname, x, y1) # use the upper y - self.defined = 1 - -def splitspace(text): - # split on spacing but include spaces at element ends - stext = text.split() - result = [] - for e in stext: - result.append(e+" ") - return result - -testlink = HotLink("http://www.reportlab.com") - -test_program = [ - ('push',), - ('indent', 100), - ('rightIndent', 200), - ('bullet', 'very long bullet', 50, 'Courier', 14), - ('align', TA_CENTER), - ('face', "Times-Roman"), - ('size', 12), - ('leading', 14), - ] + splitspace("This is the first segment of the first paragraph.") + [ - ('lineOperation', testlink), - ]+splitspace("HOTLINK This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. ") + [ - ('endLineOperation', testlink), - ('nextLine', 0), - ('align', TA_LEFT), - ('bullet', 'Bullet', 10, 'Courier', 8), - ('face', "Times-Roman"), - ('size', 12), - ('leading', 14), - ] + splitspace("This is the SECOND!!! segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. ") + [ - ('nextLine', 0), - ('align', TA_JUSTIFY), - ('bullet', 'Bullet not quite as long this time', 50, 'Courier', 8), - ('face', "Helvetica-Oblique"), - ('size', 12), - ('leading', 14), - ('push',), - ('color', 'red'), - ] + splitspace("This is the THIRD!!! segment of the first paragraph." - ) + [ - ('lineOperation', UNDERLINE), - ] + splitspace("This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. This is the first segment of the first paragraph. ") + [ - ('endLineOperation', UNDERLINE), - ('rise', 5), - "raised ", "text ", - ('rise', -10), - "lowered ", "text ", - ('rise', 5), - "normal ", "text ", - ('pop',), - ('indent', 100), - ('rightIndent', 50), - ('nextLine', 0), - ('align', TA_RIGHT), - ('bullet', 'O', 50, 'Courier', 14), - ('face', "Helvetica"), - ('size', 12), - ('leading', 14), - ] + splitspace("And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a And this is the remainder of the paragraph indented further. a a a a a a a a ") + [ - ('pop',), - ('nextLine', 0),] - - -def test(): - from pprint import pprint - #print test_program; return - from reportlab.pdfgen import canvas - from reportlab.lib.units import inch - fn = "paratest0.pdf" - c = canvas.Canvas(fn) - test2(c) - c.showPage() - if 1: - remainder = test_program + test_program + test_program - laststate = {} - while remainder: - print "NEW PAGE" - c.translate(inch, 8*inch) - t = c.beginText() - t.setTextOrigin(0,0) - p = paragraphEngine() - p.resetState(laststate) - p.x = 0 - p.y = 0 - maxwidth = 7*inch - maxheight = 500 - (formattedprogram, remainder, laststate, height) = p.format(maxwidth, maxheight, remainder) - if debug: - pprint( formattedprogram )#; return - laststate = p.runOpCodes(formattedprogram, c, t) - c.drawText(t) - c.showPage() - print "="*30, "x=", laststate["x"], "y=", laststate["y"] - c.save() - print fn - -if __name__=="__main__": - test() diff --git a/bin/reportlab/platypus/paragraph.py b/bin/reportlab/platypus/paragraph.py deleted file mode 100644 index ebf48b02db6..00000000000 --- a/bin/reportlab/platypus/paragraph.py +++ /dev/null @@ -1,944 +0,0 @@ -#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/platypus/paragraph.py -__version__=''' $Id$ ''' -from string import split, strip, join, whitespace, find -from operator import truth -from types import StringType, ListType -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.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY -from reportlab.lib.utils import _className -from copy import deepcopy -from reportlab.lib.abag import ABag - -class ParaLines(ABag): - """ - class ParaLines contains the broken into lines representation of Paragraphs - kind=0 Simple - fontName, fontSize, textColor apply to whole Paragraph - lines [(extraSpace1,words1),....,(extraspaceN,wordsN)] - - - kind==1 Complex - lines [FragLine1,...,FragLineN] - """ - -class FragLine(ABag): - """class FragLine contains a styled line (ie a line with more than one style) - - extraSpace unused space for justification only - wordCount 1+spaces in line for justification purposes - words [ParaFrags] style text lumps to be concatenated together - fontSize maximum fontSize seen on the line; not used at present, - but could be used for line spacing. - """ - -#our one and only parser -# XXXXX if the parser has any internal state using only one is probably a BAD idea! -_parser=ParaParser() - -def _lineClean(L): - return join(filter(truth,split(strip(L)))) - -def cleanBlockQuotedText(text,joiner=' '): - """This is an internal utility which takes triple- - quoted text form within the document and returns - (hopefully) the paragraph the user intended originally.""" - L=filter(truth,map(_lineClean, split(text, '\n'))) - return join(L, joiner) - -def setXPos(tx,dx): - if dx>1e-6 or dx<-1e-6: - tx.setXPos(dx) - -def _leftDrawParaLine( tx, offset, extraspace, words, last=0): - setXPos(tx,offset) - tx._textOut(join(words),1) - setXPos(tx,-offset) - return offset - -def _centerDrawParaLine( tx, offset, extraspace, words, last=0): - m = offset + 0.5 * extraspace - setXPos(tx,m) - tx._textOut(join(words),1) - setXPos(tx,-m) - return m - -def _rightDrawParaLine( tx, offset, extraspace, words, last=0): - m = offset + extraspace - setXPos(tx,m) - tx._textOut(join(words),1) - setXPos(tx,-m) - return m - -def _justifyDrawParaLine( tx, offset, extraspace, words, last=0): - setXPos(tx,offset) - text = join(words) - if last: - #last one, left align - tx._textOut(text,1) - else: - nSpaces = len(words)-1 - if nSpaces: - tx.setWordSpace(extraspace / float(nSpaces)) - tx._textOut(text,1) - tx.setWordSpace(0) - else: - tx._textOut(text,1) - setXPos(tx,-offset) - return offset - -def _putFragLine(tx,words): - cur_x = 0 - for f in words: - if hasattr(f,'cbDefn'): - func = getattr(tx._canvas,f.cbDefn.name,None) - if not func: - raise AttributeError, "Missing %s callback attribute '%s'" % (f.cbDefn.kind,f.cbDefn.name) - func(tx._canvas,f.cbDefn.kind,f.cbDefn.label) - if f is words[-1]: tx._textOut('',1) - else: - if (tx._fontname,tx._fontsize)!=(f.fontName,f.fontSize): - tx._setFont(f.fontName, f.fontSize) - if tx.XtraState.textColor!=f.textColor: - tx.XtraState.textColor = f.textColor - tx.setFillColor(f.textColor) - if tx.XtraState.rise!=f.rise: - tx.XtraState.rise=f.rise - tx.setRise(f.rise) - tx._textOut(f.text,f is words[-1]) # cheap textOut - txtlen = tx._canvas.stringWidth(f.text, tx._fontname, tx._fontsize) - if not tx.XtraState.underline and f.underline: - tx.XtraState.underline = 1 - tx.XtraState.underline_x = cur_x - elif tx.XtraState.underline and not f.underline: - tx.XtraState.underline = 0 - spacelen = tx._canvas.stringWidth(' ', tx._fontname, tx._fontsize) - tx.XtraState.underlines.append( (tx.XtraState.underline_x, cur_x-spacelen) ) - cur_x = cur_x + txtlen - if tx.XtraState.underline: - tx.XtraState.underlines.append( (tx.XtraState.underline_x, cur_x) ) - -def _leftDrawParaLineX( tx, offset, line, last=0): - setXPos(tx,offset) - _putFragLine(tx, line.words) - setXPos(tx,-offset) - return offset - -def _centerDrawParaLineX( tx, offset, line, last=0): - m = offset+0.5*line.extraSpace - setXPos(tx,m) - _putFragLine(tx, line.words) - setXPos(tx,-m) - return m - -def _rightDrawParaLineX( tx, offset, line, last=0): - m = offset+line.extraSpace - setXPos(tx,m) - _putFragLine(tx, line.words) - setXPos(tx,-m) - return m - -def _justifyDrawParaLineX( tx, offset, line, last=0): - setXPos(tx,offset) - if last: - #last one, left align - _putFragLine(tx, line.words) - else: - nSpaces = line.wordCount - 1 - if nSpaces: - tx.setWordSpace(line.extraSpace / float(nSpaces)) - _putFragLine(tx, line.words) - tx.setWordSpace(0) - else: - _putFragLine(tx, line.words) - setXPos(tx,-offset) - return offset - -try: - from _rl_accel import _sameFrag -except ImportError: - try: - from reportlab.lib._rl_accel import _sameFrag - except ImportError: - def _sameFrag(f,g): - 'returns 1 if two ParaFrags map out the same' - if hasattr(f,'cbDefn') or hasattr(g,'cbDefn'): return 0 - for a in ('fontName', 'fontSize', 'textColor', 'rise', 'underline'): - if getattr(f,a)!=getattr(g,a): return 0 - return 1 - -def _getFragWords(frags): - ''' given a Parafrag list return a list of fragwords - [[size, (f00,w00), ..., (f0n,w0n)],....,[size, (fm0,wm0), ..., (f0n,wmn)]] - each pair f,w represents a style and some string - each sublist represents a word - ''' - R = [] - W = [] - n = 0 - for f in frags: - text = f.text - #del f.text # we can't do this until we sort out splitting - # of paragraphs - if text!='': - S = split(text) - if S==[]: S = [''] - if W!=[] and text[0] in whitespace: - W.insert(0,n) - R.append(W) - W = [] - n = 0 - - for w in S[:-1]: - W.append((f,w)) - n = n + stringWidth(w, f.fontName, f.fontSize) - W.insert(0,n) - R.append(W) - W = [] - n = 0 - - w = S[-1] - W.append((f,w)) - n = n + stringWidth(w, f.fontName, f.fontSize) - if text[-1] in whitespace: - W.insert(0,n) - R.append(W) - W = [] - n = 0 - elif hasattr(f,'cbDefn'): - W.append((f,'')) - - if W!=[]: - W.insert(0,n) - R.append(W) - - return R - -def _split_blParaSimple(blPara,start,stop): - f = blPara.clone() - for a in ('lines', 'kind', 'text'): - if hasattr(f,a): delattr(f,a) - - f.words = [] - for l in blPara.lines[start:stop]: - for w in l[1]: - f.words.append(w) - return [f] - -def _split_blParaHard(blPara,start,stop): - f = [] - lines = blPara.lines[start:stop] - for l in lines: - for w in l.words: - f.append(w) - if l is not lines[-1]: - i = len(f)-1 - while hasattr(f[i],'cbDefn'): i = i-1 - g = f[i] - if g.text and g.text[-1]!=' ': g.text = g.text+' ' - return f - -def _drawBullet(canvas, offset, cur_y, bulletText, style): - '''draw a bullet text could be a simple string or a frag list''' - tx2 = canvas.beginText(style.bulletIndent, cur_y) - tx2.setFont(style.bulletFontName, style.bulletFontSize) - tx2.setFillColor(hasattr(style,'bulletColor') and style.bulletColor or style.textColor) - if type(bulletText) is StringType: - tx2.textOut(bulletText) - else: - for f in bulletText: - tx2.setFont(f.fontName, f.fontSize) - tx2.setFillColor(f.textColor) - tx2.textOut(f.text) - - canvas.drawText(tx2) - #AR making definition lists a bit less ugly - #bulletEnd = tx2.getX() - bulletEnd = tx2.getX() + style.bulletFontSize * 0.6 - offset = max(offset,bulletEnd - style.leftIndent) - return offset - -def _handleBulletWidth(bulletText,style,maxWidths): - '''work out bullet width and adjust maxWidths[0] if neccessary - ''' - if bulletText <> None: - if type(bulletText) is StringType: - bulletWidth = stringWidth( bulletText, style.bulletFontName, style.bulletFontSize) - else: - #it's a list of fragments - bulletWidth = 0 - for f in bulletText: - bulletWidth = bulletWidth + stringWidth(f.text, f.fontName, f.fontSize) - bulletRight = style.bulletIndent + bulletWidth + 0.6 * style.bulletFontSize - indent = style.leftIndent+style.firstLineIndent - if bulletRight > indent: - #..then it overruns, and we have less space available on line 1 - maxWidths[0] = maxWidths[0] - (bulletRight - indent) - -def splitLines0(frags,widths): - ''' - given a list of ParaFrags we return a list of ParaLines - - each ParaLine has - 1) ExtraSpace - 2) blankCount - 3) [textDefns....] - each text definition is a (ParaFrag, start, limit) triplet - ''' - #initialise the algorithm - lines = [] - lineNum = 0 - maxW = widths[lineNum] - i = -1 - l = len(frags) - lim = start = 0 - while 1: - #find a non whitespace character - while imaxW and line!=[]: - cLen = cLen-w - #this is the end of the line - while g.text[lim]==' ': - lim = lim - 1 - nSpaces = nSpaces-1 - break - if j<0: j = lim - if g[0] is f: g[2] = j #extend - else: - g = (f,start,j) - line.append(g) - if j==lim: - i=i+1 - -def _do_under_lines(i, t_off, tx): - y = tx.XtraState.cur_y - i*tx.XtraState.style.leading - tx.XtraState.f.fontSize/8.0 # 8.0 factor copied from para.py - text = join(tx.XtraState.lines[i][1]) - textlen = tx._canvas.stringWidth(text, tx._fontname, tx._fontsize) - tx._canvas.line(t_off, y, t_off+textlen, y) - -def _do_under(i, t_off, tx): - y = tx.XtraState.cur_y - i*tx.XtraState.style.leading - tx.XtraState.f.fontSize/8.0 # 8.0 factor copied from para.py - for x1,x2 in tx.XtraState.underlines: - tx._canvas.line(t_off+x1, y, t_off+x2, y) - tx.XtraState.underlines = [] - tx.XtraState.underline=0 - -class Paragraph(Flowable): - """ Paragraph(text, style, bulletText=None, caseSensitive=1) - text a string of stuff to go into the paragraph. - style is a style definition as in reportlab.lib.styles. - bulletText is an optional bullet defintion. - caseSensitive set this to 0 if you want the markup tags and their attributes to be case-insensitive. - - This class is a flowable that can format a block of text - into a paragraph with a given style. - - The paragraph Text can contain XML-like markup including the tags: - ... - bold - ... - italics - ... - underline - ... - superscript - ... - subscript - - - - The whole may be surrounded by tags - - It will also be able to handle any MathML specified Greek characters. - """ - def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1): - self.caseSensitive = caseSensitive - self._setup(text, style, bulletText, frags, cleanBlockQuotedText) - - - def __repr__(self): - import string - n = self.__class__.__name__ - L = [n+"("] - keys = self.__dict__.keys() - for k in keys: - v = getattr(self, k) - rk = repr(k) - rv = repr(v) - rk = " "+string.replace(rk, "\n", "\n ") - rv = " "+string.replace(rv, "\n", "\n ") - L.append(rk) - L.append(rv) - L.append(") #"+n) - return string.join(L, "\n") - - def _setup(self, text, style, bulletText, frags, cleaner): - if frags is None: - text = cleaner(text) - _parser.caseSensitive = self.caseSensitive - style, frags, bulletTextFrags = _parser.parse(text,style) - if frags is None: - raise "xml parser error (%s) in paragraph beginning\n'%s'"\ - % (_parser.errors[0],text[:min(30,len(text))]) - if bulletTextFrags: bulletText = bulletTextFrags - - #AR hack - self.text = text - self.frags = frags - self.style = style - self.bulletText = bulletText - self.debug = 0 #turn this on to see a pretty one with all the margins etc. - - def wrap(self, availWidth, availHeight): - # work out widths array for breaking - self.width = availWidth - leftIndent = self.style.leftIndent - first_line_width = availWidth - (leftIndent+self.style.firstLineIndent) - self.style.rightIndent - later_widths = availWidth - leftIndent - self.style.rightIndent - self.blPara = self.breakLines([first_line_width, later_widths]) - self.height = len(self.blPara.lines) * self.style.leading - return (self.width, self.height) - - def minWidth(self): - 'Attempt to determine a minimum sensible width' - frags = self.frags - nFrags= len(frags) - if nFrags==1: - f = frags[0] - fS = f.fontSize - fN = f.fontName - words = hasattr(f,'text') and split(f.text, ' ') or f.words - func = lambda w, fS=fS, fN=fN: stringWidth(w,fN,fS) - else: - words = _getFragWords(frags) - func = lambda x: x[0] - return max(map(func,words)) - - def _get_split_blParaFunc(self): - return self.blPara.kind==0 and _split_blParaSimple or _split_blParaHard - - def split(self,availWidth, availHeight): - if len(self.frags)<=0: return [] - - #the split information is all inside self.blPara - if not hasattr(self,'blPara'): - self.wrap(availWidth,availHeight) - blPara = self.blPara - style = self.style - leading = style.leading - lines = blPara.lines - n = len(lines) - s = int(availHeight/leading) - if s<=1: - del self.blPara - return [] - if n<=s: return [self] - func = self._get_split_blParaFunc() - - P1=self.__class__(None,style,bulletText=self.bulletText,frags=func(blPara,0,s)) - #this is a major hack - P1.blPara = ParaLines(kind=1,lines=blPara.lines[0:s],aH=availHeight,aW=availWidth) - P1._JustifyLast = 1 - if style.firstLineIndent != 0: - style = deepcopy(style) - style.firstLineIndent = 0 - P2=self.__class__(None,style,bulletText=None,frags=func(blPara,s,n)) - return [P1,P2] - - def draw(self): - #call another method for historical reasons. Besides, I - #suspect I will be playing with alternate drawing routines - #so not doing it here makes it easier to switch. - self.drawPara(self.debug) - - def breakLines(self, width): - """ - Returns a broken line structure. There are two cases - - A) For the simple case of a single formatting input fragment the output is - A fragment specifier with - kind = 0 - fontName, fontSize, leading, textColor - lines= A list of lines - Each line has two items. - 1) unused width in points - 2) word list - - B) When there is more than one input formatting fragment the out put is - A fragment specifier with - kind = 1 - lines= A list of fragments each having fields - extraspace (needed for justified) - fontSize - words=word list - each word is itself a fragment with - various settings - - This structure can be used to easily draw paragraphs with the various alignments. - You can supply either a single width or a list of widths; the latter will have its - last item repeated until necessary. A 2-element list is useful when there is a - different first line indent; a longer list could be created to facilitate custom wraps - around irregular objects.""" - - if type(width) <> ListType: maxWidths = [width] - else: maxWidths = width - lines = [] - lineno = 0 - style = self.style - fFontSize = float(style.fontSize) - - #for bullets, work out width and ensure we wrap the right amount onto line one - _handleBulletWidth(self.bulletText,style,maxWidths) - - maxWidth = maxWidths[0] - - self.height = 0 - frags = self.frags - nFrags= len(frags) - if nFrags==1: - f = frags[0] - fontSize = f.fontSize - fontName = f.fontName - words = hasattr(f,'text') and split(f.text, ' ') or f.words - spaceWidth = stringWidth(' ', fontName, fontSize) - cLine = [] - currentWidth = - spaceWidth # hack to get around extra space for word 1 - for word in words: - wordWidth = stringWidth(word, fontName, fontSize) - newWidth = currentWidth + spaceWidth + wordWidth - if newWidth<=maxWidth or len(cLine)==0: - # fit one more on this line - cLine.append(word) - currentWidth = newWidth - else: - if currentWidth>self.width: self.width = currentWidth - #end of line - lines.append((maxWidth - currentWidth, cLine)) - cLine = [word] - currentWidth = wordWidth - lineno = lineno + 1 - try: - maxWidth = maxWidths[lineno] - except IndexError: - maxWidth = maxWidths[-1] # use the last one - - #deal with any leftovers on the final line - if cLine!=[]: - if currentWidth>self.width: self.width = currentWidth - lines.append((maxWidth - currentWidth, cLine)) - - return f.clone(kind=0, lines=lines) - elif nFrags<=0: - return ParaLines(kind=0, fontSize=style.fontSize, fontName=style.fontName, - textColor=style.textColor, lines=[]) - else: - if hasattr(self,'blPara'): - #NB this is an utter hack that awaits the proper information - #preserving splitting algorithm - return self.blPara - n = 0 - nSp = 0 - for w in _getFragWords(frags): - spaceWidth = stringWidth(' ',w[-1][0].fontName, w[-1][0].fontSize) - - if n==0: - currentWidth = -spaceWidth # hack to get around extra space for word 1 - words = [] - maxSize = 0 - - wordWidth = w[0] - f = w[1][0] - if wordWidth>0: - newWidth = currentWidth + spaceWidth + wordWidth - else: - newWidth = currentWidth - if newWidth<=maxWidth or n==0: - # fit one more on this line - n = n + 1 - maxSize = max(maxSize,f.fontSize) - nText = w[1][1] - if words==[]: - g = f.clone() - words = [g] - g.text = nText - elif not _sameFrag(g,f): - if currentWidth>0 and ((nText!='' and nText[0]!=' ') or hasattr(f,'cbDefn')): - if hasattr(g,'cbDefn'): - i = len(words)-1 - while hasattr(words[i],'cbDefn'): i = i-1 - words[i].text = words[i].text + ' ' - else: - g.text = g.text + ' ' - nSp = nSp + 1 - g = f.clone() - words.append(g) - g.text = nText - else: - if nText!='' and nText[0]!=' ': - g.text = g.text + ' ' + nText - - for i in w[2:]: - g = i[0].clone() - g.text=i[1] - words.append(g) - maxSize = max(maxSize,g.fontSize) - - currentWidth = newWidth - else: - if currentWidth>self.width: self.width = currentWidth - #end of line - lines.append(FragLine(extraSpace=(maxWidth - currentWidth),wordCount=n, - words=words, fontSize=maxSize)) - - #start new line - lineno = lineno + 1 - try: - maxWidth = maxWidths[lineno] - except IndexError: - maxWidth = maxWidths[-1] # use the last one - currentWidth = wordWidth - n = 1 - maxSize = f.fontSize - g = f.clone() - words = [g] - g.text = w[1][1] - - for i in w[2:]: - g = i[0].clone() - g.text=i[1] - words.append(g) - maxSize = max(maxSize,g.fontSize) - - #deal with any leftovers on the final line - if words<>[]: - if currentWidth>self.width: self.width = currentWidth - lines.append(ParaLines(extraSpace=(maxWidth - currentWidth),wordCount=n, - words=words, fontSize=maxSize)) - return ParaLines(kind=1, lines=lines) - - return lines - - def beginText(self, x, y): - return self.canv.beginText(x, y) - - def drawPara(self,debug=0): - """Draws a paragraph according to the given style. - Returns the final y position at the bottom. Not safe for - paragraphs without spaces e.g. Japanese; wrapping - algorithm will go infinite.""" - - #stash the key facts locally for speed - canvas = self.canv - style = self.style - blPara = self.blPara - lines = blPara.lines - - #work out the origin for line 1 - leftIndent = style.leftIndent - cur_x = leftIndent - - #if has a background, draw it - if style.backColor: - canvas.saveState() - canvas.setFillColor(style.backColor) - canvas.rect(leftIndent, - 0, - self.width - (leftIndent+style.rightIndent), - self.height, - fill=1, - stroke=0) - canvas.restoreState() - - if debug: - # This boxes and shades stuff to show how the paragraph - # uses its space. Useful for self-documentation so - # the debug code stays! - # box the lot - canvas.rect(0, 0, self.width, self.height) - #left and right margins - canvas.saveState() - canvas.setFillColor(Color(0.9,0.9,0.9)) - canvas.rect(0, 0, leftIndent, self.height) - canvas.rect(self.width - style.rightIndent, 0, style.rightIndent, self.height) - # shade above and below - canvas.setFillColor(Color(1.0,1.0,0.0)) - canvas.restoreState() - #self.drawLine(x + leftIndent, y, x + leftIndent, cur_y) - - - nLines = len(lines) - bulletText = self.bulletText - if nLines > 0: - canvas.saveState() - #canvas.addLiteral('%% %s.drawPara' % _className(self)) - alignment = style.alignment - offset = style.firstLineIndent - lim = nLines-1 - noJustifyLast = not (hasattr(self,'_JustifyLast') and self._JustifyLast) - - if blPara.kind==0: - if alignment == TA_LEFT: - dpl = _leftDrawParaLine - elif alignment == TA_CENTER: - dpl = _centerDrawParaLine - elif self.style.alignment == TA_RIGHT: - dpl = _rightDrawParaLine - elif self.style.alignment == TA_JUSTIFY: - dpl = _justifyDrawParaLine - f = blPara - cur_y = self.height - f.fontSize - if bulletText <> None: - offset = _drawBullet(canvas,offset,cur_y,bulletText,style) - - #set up the font etc. - canvas.setFillColor(f.textColor) - - tx = self.beginText(cur_x, cur_y) - - #now the font for the rest of the paragraph - tx.setFont(f.fontName, f.fontSize, style.leading) - t_off = dpl( tx, offset, lines[0][0], lines[0][1], noJustifyLast and nLines==1) - if f.underline: - tx.XtraState=ABag() - tx.XtraState.cur_y = cur_y - tx.XtraState.f = f - tx.XtraState.style = style - tx.XtraState.lines = lines - _do_under_lines(0, t_off+leftIndent, tx) - - #now the middle of the paragraph, aligned with the left margin which is our origin. - for i in range(1, nLines): - t_off = dpl( tx, 0, lines[i][0], lines[i][1], noJustifyLast and i==lim) - if f.underline: - _do_under_lines(i, t_off+leftIndent, tx) - else: - for i in range(1, nLines): - dpl( tx, 0, lines[i][0], lines[i][1], noJustifyLast and i==lim) - else: - f = lines[0] - cur_y = self.height - f.fontSize - # default? - dpl = _leftDrawParaLineX - if bulletText <> None: - offset = _drawBullet(canvas,offset,cur_y,bulletText,style) - if alignment == TA_LEFT: - dpl = _leftDrawParaLineX - elif alignment == TA_CENTER: - dpl = _centerDrawParaLineX - elif self.style.alignment == TA_RIGHT: - dpl = _rightDrawParaLineX - elif self.style.alignment == TA_JUSTIFY: - dpl = _justifyDrawParaLineX - else: - raise ValueError, "bad align %s" % repr(alignment) - - #set up the font etc. - tx = self.beginText(cur_x, cur_y) - tx.XtraState=ABag() - tx.XtraState.textColor=None - tx.XtraState.rise=0 - tx.XtraState.underline=0 - tx.XtraState.underlines=[] - tx.setLeading(style.leading) - tx.XtraState.cur_y = cur_y - tx.XtraState.f = f - tx.XtraState.style = style - #f = lines[0].words[0] - #tx._setFont(f.fontName, f.fontSize) - - - tx._fontname,tx._fontsize = None, None - t_off = dpl( tx, offset, lines[0], noJustifyLast and nLines==1) - _do_under(0, t_off+leftIndent, tx) - - #now the middle of the paragraph, aligned with the left margin which is our origin. - for i in range(1, nLines): - f = lines[i] - t_off = dpl( tx, 0, f, noJustifyLast and i==lim) - _do_under(i, t_off+leftIndent, tx) - - canvas.drawText(tx) - canvas.restoreState() - - def getPlainText(self,identify=None): - """Convenience function for templates which want access - to the raw text, without XML tags. """ - frags = getattr(self,'frags',None) - if frags: - plains = [] - for frag in frags: - plains.append(frag.text) - return join(plains, '') - elif identify: - text = getattr(self,'text',None) - if text is None: text = repr(self) - return text - else: - return '' - - def getActualLineWidths0(self): - """Convenience function; tells you how wide each line - actually is. For justified styles, this will be - the same as the wrap width; for others it might be - useful for seeing if paragraphs will fit in spaces.""" - assert hasattr(self, 'width'), "Cannot call this method before wrap()" - if self.blPara.kind: - func = lambda frag, w=self.width: w - frag.extraSpace - else: - func = lambda frag, w=self.width: w - frag[0] - return map(func,self.blPara.lines) - -if __name__=='__main__': #NORUNTESTS - def dumpParagraphLines(P): - print 'dumpParagraphLines()' % id(P) - lines = P.blPara.lines - n =len(lines) - for l in range(n): - line = lines[l] - if hasattr(line,'words'): - words = line.words - else: - words = line[1] - nwords = len(words) - print 'line%d: %d(%s)\n ' % (l,nwords,str(getattr(line,'wordCount','Unknown'))), - for w in range(nwords): - print "%d:'%s'"%(w,getattr(words[w],'text',words[w])), - print - - def dumpParagraphFrags(P): - print 'dumpParagraphFrags() minWidth() = %.2f' % (id(P), P.minWidth()) - frags = P.frags - n =len(frags) - for l in range(n): - print "frag%d: '%s'" % (l, frags[l].text) - - l = 0 - cum = 0 - for W in _getFragWords(frags): - cum = cum + W[0] - print "fragword%d: cum=%3d size=%d" % (l, cum, W[0]), - for w in W[1:]: - print "'%s'" % w[1], - print - l = l + 1 - - - from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle - import sys - TESTS = sys.argv[1:] - if TESTS==[]: TESTS=['4'] - def flagged(i,TESTS=TESTS): - return 'all' in TESTS or '*' in TESTS or str(i) in TESTS - - styleSheet = getSampleStyleSheet() - B = styleSheet['BodyText'] - style = ParagraphStyle("discussiontext", parent=B) - style.fontName= 'Helvetica' - if flagged(1): - text='''The CMYK or subtractive method follows the way a printer -mixes three pigments (cyan, magenta, and yellow) to form colors. -Because mixing chemicals is more difficult than combining light there -is a fourth parameter for darkness. For example a chemical -combination of the CMY pigments generally never makes a perfect -black -- instead producing a muddy color -- so, to get black printers -don't use the CMY pigments but use a direct black ink. Because -CMYK maps more directly to the way printer hardware works it may -be the case that &| & | colors specified in CMYK will provide better fidelity -and better control when printed. -''' - P=Paragraph(text,style) - dumpParagraphFrags(P) - aW, aH = 456.0, 42.8 - w,h = P.wrap(aW, aH) - dumpParagraphLines(P) - S = P.split(aW,aH) - for s in S: - s.wrap(aW,aH) - dumpParagraphLines(s) - aH = 500 - - if flagged(2): - P=Paragraph("""Price*""", styleSheet['Normal']) - dumpParagraphFrags(P) - w,h = P.wrap(24, 200) - dumpParagraphLines(P) - - if flagged(3): - text = """Dieses Kapitel bietet eine schnelle Programme :: starten - -Eingabeaufforderung :: (>>>) - ->>> (Eingabeaufforderung) - -Einf\374hrung in Python Python :: Einf\374hrung -. -Das Ziel ist, die grundlegenden Eigenschaften von Python darzustellen, ohne -sich zu sehr in speziellen Regeln oder Details zu verstricken. Dazu behandelt -dieses Kapitel kurz die wesentlichen Konzepte wie Variablen, Ausdr\374cke, -Kontrollfluss, Funktionen sowie Ein- und Ausgabe. Es erhebt nicht den Anspruch, -umfassend zu sein.""" - P=Paragraph(text, styleSheet['Code']) - dumpParagraphFrags(P) - w,h = P.wrap(6*72, 9.7*72) - dumpParagraphLines(P) - - if flagged(4): - text='''Die eingebaute Funktion range(i, j [, stride]) erzeugt eine Liste von Ganzzahlen und f\374llt sie mit Werten k, f\374r die gilt: i <= k < j. Man kann auch eine optionale Schrittweite angeben. Die eingebaute Funktion xrange() erf\374llt einen \344hnlichen Zweck, gibt aber eine unver\344nderliche Sequenz vom Typ XRangeType zur\374ck. Anstatt alle Werte in der Liste abzuspeichern, berechnet diese Liste ihre Werte, wann immer sie angefordert werden. Das ist sehr viel speicherschonender, wenn mit sehr langen Listen von Ganzzahlen gearbeitet wird. XRangeType kennt eine einzige Methode, s.tolist(), die seine Werte in eine Liste umwandelt.''' - aW = 420 - aH = 64.4 - P=Paragraph(text, B) - dumpParagraphFrags(P) - w,h = P.wrap(aW,aH) - print 'After initial wrap',w,h - dumpParagraphLines(P) - S = P.split(aW,aH) - dumpParagraphFrags(S[0]) - w0,h0 = S[0].wrap(aW,aH) - print 'After split wrap',w0,h0 - dumpParagraphLines(S[0]) - - if flagged(5): - text = ' %s
              & %s < >]]>' % (chr(163),chr(163)) - P=Paragraph(text, styleSheet['Code']) - dumpParagraphFrags(P) - w,h = P.wrap(6*72, 9.7*72) - dumpParagraphLines(P) - - if flagged(6): - for text in ['''Here comes Helvetica 14 with strong emphasis.''', - '''Here comes Helvetica 14 with strong emphasis.''', - '''Here comes Courier 3cm and normal again.''', - ]: - P=Paragraph(text, styleSheet['Normal'], caseSensitive=0) - dumpParagraphFrags(P) - w,h = P.wrap(6*72, 9.7*72) - dumpParagraphLines(P) - - if flagged(7): - text = """Generated by:\tDilbert""" - P=Paragraph(text, styleSheet['Code']) - dumpParagraphFrags(P) - w,h = P.wrap(6*72, 9.7*72) - dumpParagraphLines(P) diff --git a/bin/reportlab/platypus/paraparser.py b/bin/reportlab/platypus/paraparser.py deleted file mode 100644 index 68547079711..00000000000 --- a/bin/reportlab/platypus/paraparser.py +++ /dev/null @@ -1,938 +0,0 @@ -#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/platypus/paraparser.py -__version__=''' $Id$ ''' -import string -import re -from types import TupleType -import sys -import os -import copy - -import reportlab.lib.sequencer -from reportlab.lib.abag import ABag - -from reportlab.lib import xmllib -_xmllib_newStyle = 1 - -from reportlab.lib.colors import toColor, white, black, red, Color -from reportlab.lib.fonts import tt2ps, ps2tt -from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY -from reportlab.lib.units import inch,mm,cm,pica -_re_para = re.compile(r'^\s*<\s*para(?:\s+|>|/>)') - -sizeDelta = 2 # amount to reduce font size by for super and sub script -subFraction = 0.5 # fraction of font size that a sub script should be lowered -superFraction = 0.5 # fraction of font size that a super script should be raised - -def _num(s, unit=1): - """Convert a string like '10cm' to an int or float (in points). - The default unit is point, but optionally you can use other - default units like mm. - """ - if s[-2:]=='cm': - unit=cm - s = s[:-2] - if s[-2:]=='in': - unit=inch - s = s[:-2] - if s[-2:]=='pt': - unit=1 - s = s[:-2] - if s[-1:]=='i': - unit=inch - s = s[:-1] - if s[-2:]=='mm': - unit=mm - s = s[:-2] - if s[-4:]=='pica': - unit=pica - s = s[:-4] - if s[0] in ['+','-']: - try: - return ('relative',int(s)*unit) - except ValueError: - return ('relative',float(s)*unit) - else: - try: - return int(s)*unit - except ValueError: - return float(s)*unit - -def _align(s): - s = string.lower(s) - if s=='left': return TA_LEFT - elif s=='right': return TA_RIGHT - elif s=='justify': return TA_JUSTIFY - elif s in ('centre','center'): return TA_CENTER - else: raise ValueError - -_paraAttrMap = {'font': ('fontName', None), - 'face': ('fontName', None), - 'fontsize': ('fontSize', _num), - 'size': ('fontSize', _num), - 'leading': ('leading', _num), - 'lindent': ('leftIndent', _num), - 'rindent': ('rightIndent', _num), - 'findent': ('firstLineIndent', _num), - 'align': ('alignment', _align), - 'spaceb': ('spaceBefore', _num), - 'spacea': ('spaceAfter', _num), - 'bfont': ('bulletFontName', None), - 'bfontsize': ('bulletFontSize',_num), - 'bindent': ('bulletIndent',_num), - 'bcolor': ('bulletColor',toColor), - 'color':('textColor',toColor), - 'backcolor':('backColor',toColor), - 'bgcolor':('backColor',toColor), - 'bg':('backColor',toColor), - 'fg': ('textColor',toColor), - } - -_bulletAttrMap = { - 'font': ('bulletFontName', None), - 'face': ('bulletFontName', None), - 'size': ('bulletFontSize',_num), - 'fontsize': ('bulletFontSize',_num), - 'indent': ('bulletIndent',_num), - 'color': ('bulletColor',toColor), - 'fg': ('bulletColor',toColor), - } - -#things which are valid font attributes -_fontAttrMap = {'size': ('fontSize', _num), - 'face': ('fontName', None), - 'name': ('fontName', None), - 'fg': ('textColor', toColor), - 'color':('textColor', toColor), - } - -def _addAttributeNames(m): - K = m.keys() - for k in K: - n = m[k][0] - if not m.has_key(n): m[n] = m[k] - n = string.lower(n) - if not m.has_key(n): m[n] = m[k] - -_addAttributeNames(_paraAttrMap) -_addAttributeNames(_fontAttrMap) -_addAttributeNames(_bulletAttrMap) - -def _applyAttributes(obj, attr): - for k, v in attr.items(): - if type(v) is TupleType and v[0]=='relative': - #AR 20/5/2000 - remove 1.5.2-ism - #v = v[1]+getattr(obj,k,0) - if hasattr(obj, k): - v = v[1]+getattr(obj,k) - else: - v = v[1] - setattr(obj,k,v) - -#Named character entities intended to be supported from the special font -#with additions suggested by Christoph Zwerschke who also suggested the -#numeric entity names that follow. -greeks = { - 'Alpha': 'A', - 'Beta': 'B', - 'Chi': 'C', - 'Delta': 'D', - 'Epsilon': 'E', - 'Eta': 'H', - 'Gamma': 'G', - 'Iota': 'I', - 'Kappa': 'K', - 'Lambda': 'L', - 'Mu': 'M', - 'Nu': 'N', - 'Omega': 'W', - 'Omicron': 'O', - 'Phi': 'F', - 'Pi': 'P', - 'Psi': 'Y', - 'Rho': 'R', - 'Sigma': 'S', - 'Tau': 'T', - 'Theta': 'Q', - 'Upsilon': 'U', - 'Xi': 'X', - 'Zeta': 'Z', - 'alefsym': '\xc0', - 'alpha': 'a', - 'and': '\xd9', - 'ang': '\xd0', - 'asymp': '\xbb', - 'beta': 'b', - 'bull': '\xb7', - 'cap': '\xc7', - 'chi': 'c', - 'clubs': '\xa7', - 'cong': '@', - 'cup': '\xc8', - 'dArr': '\xdf', - 'darr': '\xaf', - 'delta': 'd', - 'diams': '\xa8', - 'empty': '\xc6', - 'epsilon': 'e', - 'epsiv': 'e', - 'equiv': '\xba', - 'eta': 'h', - 'euro': '\xa0', - 'exist': '$', - 'forall': '"', - 'frasl': '\xa4', - 'gamma': 'g', - 'ge': '\xb3', - 'hArr': '\xdb', - 'harr': '\xab', - 'hearts': '\xa9', - 'hellip': '\xbc', - 'image': '\xc1', - 'infin': '\xa5', - 'int': '\xf2', - 'iota': 'i', - 'isin': '\xce', - 'kappa': 'k', - 'lArr': '\xdc', - 'lambda': 'l', - 'lang': '\xe1', - 'larr': '\xac', - 'lceil': '\xe9', - 'le': '\xa3', - 'lfloor': '\xeb', - 'lowast': '*', - 'loz': '\xe0', - 'minus': '-', - 'mu': 'm', - 'nabla': '\xd1', - 'ne': '\xb9', - 'ni': "'", - 'notin': '\xcf', - 'nsub': '\xcb', - 'nu': 'n', - 'oline': '`', - 'omega': 'w', - 'omicron': 'o', - 'oplus': '\xc5', - 'or': '\xda', - 'otimes': '\xc4', - 'part': '\xb6', - 'perp': '^', - 'phi': 'j', - 'phis': 'f', - 'pi': 'p', - 'piv': 'v', - 'prime': '\xa2', - 'prod': '\xd5', - 'prop': '\xb5', - 'psi': 'y', - 'rArr': '\xde', - 'radic': '\xd6', - 'rang': '\xf1', - 'rarr': '\xae', - 'rceil': '\xf9', - 'real': '\xc2', - 'rfloor': '\xfb', - 'rho': 'r', - 'sdot': '\xd7', - 'sigma': 's', - 'sigmaf': 'V', - 'sigmav': 'V', - 'sim': '~', - 'spades': '\xaa', - 'sub': '\xcc', - 'sube': '\xcd', - 'sum': '\xe5', - 'sup': '\xc9', - 'supe': '\xca', - 'tau': 't', - 'there4': '\\', - 'theta': 'q', - 'thetasym': 'J', - 'thetav': 'J', - 'trade': '\xe4', - 'uArr': '\xdd', - 'uarr': '\xad', - 'upsih': '\xa1', - 'upsilon': 'u', - 'weierp': '\xc3', - 'xi': 'x', - 'zeta': 'z', - } - -# mapping of xml character entities to symbol encoding -symenc = { - # greek letters - 913:'A', # Alpha - 914:'B', # Beta - 915:'G', # Gamma - 916:'D', # Delta - 917:'E', # Epsilon - 918:'Z', # Zeta - 919:'H', # Eta - 920:'Q', # Theta - 921:'I', # Iota - 922:'K', # Kappa - 923:'L', # Lambda - 924:'M', # Mu - 925:'N', # Nu - 926:'X', # Xi - 927:'O', # Omicron - 928:'P', # Pi - 929:'R', # Rho - 931:'S', # Sigma - 932:'T', # Tau - 933:'U', # Upsilon - 934:'F', # Phi - 935:'C', # Chi - 936:'Y', # Psi - 937:'W', # Omega - 945:'a', # alpha - 946:'b', # beta - 947:'g', # gamma - 948:'d', # delta - 949:'e', # epsilon - 950:'z', # zeta - 951:'h', # eta - 952:'q', # theta - 953:'i', # iota - 954:'k', # kappa - 955:'l', # lambda - 956:'m', # mu - 957:'n', # nu - 958:'x', # xi - 959:'o', # omicron - 960:'p', # pi - 961:'r', # rho - 962:'V', # sigmaf - 963:'s', # sigma - 964:'t', # tau - 965:'u', # upsilon - 966:'j', # phi - 967:'c', # chi - 968:'y', # psi - 969:'w', # omega - 977:'J', # thetasym - 978:'\241', # upsih - 981:'f', # phis - 982:'v', # piv - # mathematical symbols - 8704:'"', # forall - 8706:'\266', # part - 8707:'$', # exist - 8709:'\306', # empty - 8711:'\321', # nabla - 8712:'\316', # isin - 8713:'\317', # notin - 8715:'\'', # ni - 8719:'\325', # prod - 8721:'\345', # sum - 8722:'-', # minus - 8727:'*', # lowast - 8730:'\326', # radic - 8733:'\265', # prop - 8734:'\245', # infin - 8736:'\320', # ang - 8869:'\331', # and - 8870:'\332', # or - 8745:'\307', # cap - 8746:'\310', # cup - 8747:'\362', # int - 8756:'\\', # there4 - 8764:'~', # sim - 8773:'@', # cong - 8776:'\273', #asymp - 8800:'\271', # ne - 8801:'\272', # equiv - 8804:'\243', # le - 8805:'\263', # ge - 8834:'\314', # sub - 8835:'\311', # sup - 8836:'\313', # nsub - 8838:'\315', # sube - 8839:'\312', # supe - 8853:'\305', # oplus - 8855:'\304', # otimes - 8869:'^', # perp - 8901:'\327', # sdot - 9674:'\340', # loz - # technical symbols - 8968:'\351', # lceil - 8969:'\371', # rceil - 8970:'\353', # lfloor - 8971:'\373', # rfloor - 9001:'\341', # lang - 9002:'\361', # rang - # arrow symbols - 8592:'\254', # larr - 8593:'\255', # uarr - 8594:'\256', # rarr - 8595:'\257', # darr - 8596:'\253', # harr - 8656:'\334', # lArr - 8657:'\335', # uArr - 8658:'\336', # rArr - 8659:'\337', # dArr - 8660:'\333', # hArr - # divers symbols - 8226:'\267', # bull - 8230:'\274', # hellip - 8242:'\242', # prime - 8254:'`', # oline - 8260:'\244', # frasl - 8472:'\303', # weierp - 8465:'\301', # image - 8476:'\302', # real - 8482:'\344', # trade - 8364:'\240', # euro - 8501:'\300', # alefsym - 9824:'\252', # spades - 9827:'\247', # clubs - 9829:'\251', # hearts - 9830:'\250' # diams - } - -#------------------------------------------------------------------------ -class ParaFrag(ABag): - """class ParaFrag contains the intermediate representation of string - segments as they are being parsed by the XMLParser. - fontname, fontSize, rise, textColor, cbDefn - """ - -#------------------------------------------------------------------ -# !!! NOTE !!! THIS TEXT IS NOW REPLICATED IN PARAGRAPH.PY !!! -# The ParaFormatter will be able to format the following xml -# tags: -# < /b > - bold -# < /i > - italics -# < u > < /u > - underline -# < super > < /super > - superscript -# < sup > < /sup > - superscript -# < sub > < /sub > - subscript -# -# < bullet > - bullet text (at head of para only) -# -# -# The whole may be surrounded by tags -# -# It will also be able to handle any MathML specified Greek characters. -#------------------------------------------------------------------ -class ParaParser(xmllib.XMLParser): - - #---------------------------------------------------------- - # First we will define all of the xml tag handler functions. - # - # start_(attributes) - # end_() - # - # While parsing the xml ParaFormatter will call these - # functions to handle the string formatting tags. - # At the start of each tag the corresponding field will - # be set to 1 and at the end tag the corresponding field will - # be set to 0. Then when handle_data is called the options - # for that data will be aparent by the current settings. - #---------------------------------------------------------- - - def __getattr__( self, attrName ): - """This way we can handle the same way as (ignoring case).""" - if attrName!=attrName.lower() and attrName!="caseSensitive" and not self.caseSensitive and \ - (attrName.startswith("start_") or attrName.startswith("end_")): - return getattr(self,attrName.lower()) - raise AttributeError, attrName - - #### bold - def start_b( self, attributes ): - self._push(bold=1) - - def end_b( self ): - self._pop(bold=1) - - def start_strong( self, attributes ): - self._push(bold=1) - - def end_strong( self ): - self._pop(bold=1) - - #### italics - def start_i( self, attributes ): - self._push(italic=1) - - def end_i( self ): - self._pop(italic=1) - - def start_em( self, attributes ): - self._push(italic=1) - - def end_em( self ): - self._pop(italic=1) - - #### underline - def start_u( self, attributes ): - self._push(underline=1) - - def end_u( self ): - self._pop(underline=1) - - #### super script - def start_super( self, attributes ): - self._push(super=1) - - def end_super( self ): - self._pop(super=1) - - start_sup = start_super - end_sup = end_super - - #### sub script - def start_sub( self, attributes ): - self._push(sub=1) - - def end_sub( self ): - self._pop(sub=1) - - #### greek script - #### add symbol encoding - def handle_charref(self, name): - try: - if name[0] == 'x': - n = string.atoi(name[1:], 16) - else: - n = string.atoi(name) - except string.atoi_error: - self.unknown_charref(name) - return - if 0 <=n<=255: - self.handle_data(chr(n)) - elif symenc.has_key(n): - self._push(greek=1) - self.handle_data(symenc[n]) - self._pop(greek=1) - else: - self.unknown_charref(name) - - def handle_entityref(self,name): - if greeks.has_key(name): - self._push(greek=1) - self.handle_data(greeks[name]) - self._pop(greek=1) - else: - xmllib.XMLParser.handle_entityref(self,name) - - def syntax_error(self,lineno,message): - self._syntax_error(message) - - def _syntax_error(self,message): - if message[:10]=="attribute " and message[-17:]==" value not quoted": return - self.errors.append(message) - - def start_greek(self, attributes): - self._push(greek=1) - - def end_greek(self): - self._pop(greek=1) - - def start_font(self,attr): - apply(self._push,(),self.getAttributes(attr,_fontAttrMap)) - - def end_font(self): - self._pop() - - def _initial_frag(self,attr,attrMap,bullet=0): - style = self._style - if attr!={}: - style = copy.deepcopy(style) - _applyAttributes(style,self.getAttributes(attr,attrMap)) - self._style = style - - # initialize semantic values - frag = ParaFrag() - frag.sub = 0 - frag.super = 0 - frag.rise = 0 - frag.underline = 0 - frag.greek = 0 - if bullet: - frag.fontName, frag.bold, frag.italic = ps2tt(style.bulletFontName) - frag.fontSize = style.bulletFontSize - frag.textColor = hasattr(style,'bulletColor') and style.bulletColor or style.textColor - else: - frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName) - frag.fontSize = style.fontSize - frag.textColor = style.textColor - return frag - - def start_para(self,attr): - self._stack = [self._initial_frag(attr,_paraAttrMap)] - - def end_para(self): - self._pop() - - def start_bullet(self,attr): - if hasattr(self,'bFragList'): - self._syntax_error('only one tag allowed') - self.bFragList = [] - frag = self._initial_frag(attr,_bulletAttrMap,1) - frag.isBullet = 1 - self._stack.append(frag) - - def end_bullet(self): - self._pop() - - #--------------------------------------------------------------- - def start_seqdefault(self, attr): - try: - default = attr['id'] - except KeyError: - default = None - self._seq.setDefaultCounter(default) - - def end_seqdefault(self): - pass - - def start_seqreset(self, attr): - try: - id = attr['id'] - except KeyError: - id = None - try: - base = int(attr['base']) - except: - base=0 - self._seq.reset(id, base) - - def end_seqreset(self): - pass - - def start_seqchain(self, attr): - try: - order = attr['order'] - except KeyError: - order = '' - order = order.split() - seq = self._seq - for p,c in zip(order[:-1],order[1:]): - seq.chain(p, c) - end_seqchain = end_seqreset - - def start_seqformat(self, attr): - try: - id = attr['id'] - except KeyError: - id = None - try: - value = attr['value'] - except KeyError: - value = '1' - self._seq.setFormat(id,value) - end_seqformat = end_seqreset - - # AR hacking in aliases to allow the proper casing for RML. - # the above ones should be deprecated over time. 2001-03-22 - start_seqDefault = start_seqdefault - end_seqDefault = end_seqdefault - start_seqReset = start_seqreset - end_seqReset = end_seqreset - start_seqChain = start_seqchain - end_seqChain = end_seqchain - start_seqFormat = start_seqformat - end_seqFormat = end_seqformat - - def start_seq(self, attr): - #if it has a template, use that; otherwise try for id; - #otherwise take default sequence - if attr.has_key('template'): - templ = attr['template'] - self.handle_data(templ % self._seq) - return - elif attr.has_key('id'): - id = attr['id'] - else: - id = None - output = self._seq.nextf(id) - self.handle_data(output) - - def end_seq(self): - pass - - def start_onDraw(self,attr): - defn = ABag() - if attr.has_key('name'): defn.name = attr['name'] - else: self._syntax_error(' needs at least a name attribute') - - if attr.has_key('label'): defn.label = attr['label'] - defn.kind='onDraw' - self._push(cbDefn=defn) - self.handle_data('') - self._pop() - - #--------------------------------------------------------------- - def _push(self,**attr): - frag = copy.copy(self._stack[-1]) - _applyAttributes(frag,attr) - self._stack.append(frag) - - def _pop(self,**kw): - frag = self._stack[-1] - del self._stack[-1] - for k, v in kw.items(): - assert getattr(frag,k)==v - return frag - - def getAttributes(self,attr,attrMap): - A = {} - for k, v in attr.items(): - if not self.caseSensitive: - k = string.lower(k) - if k in attrMap.keys(): - j = attrMap[k] - func = j[1] - try: - A[j[0]] = (func is None) and v or apply(func,(v,)) - except: - self._syntax_error('%s: invalid value %s'%(k,v)) - else: - self._syntax_error('invalid attribute name %s'%k) - return A - - #---------------------------------------------------------------- - - def __init__(self,verbose=0): - self.caseSensitive = 0 - xmllib.XMLParser.__init__(self,verbose=verbose) - - def _iReset(self): - self.fragList = [] - if hasattr(self, 'bFragList'): delattr(self,'bFragList') - - def _reset(self, style): - '''reset the parser''' - xmllib.XMLParser.reset(self) - - # initialize list of string segments to empty - self.errors = [] - self._style = style - self._iReset() - - #---------------------------------------------------------------- - def handle_data(self,data): - "Creates an intermediate representation of string segments." - - frag = copy.copy(self._stack[-1]) - if hasattr(frag,'cbDefn'): - if data!='': syntax_error('Only tag allowed') - else: - # if sub and super are both on they will cancel each other out - if frag.sub == 1 and frag.super == 1: - frag.sub = 0 - frag.super = 0 - - if frag.sub: - frag.rise = -frag.fontSize*subFraction - frag.fontSize = max(frag.fontSize-sizeDelta,3) - elif frag.super: - frag.rise = frag.fontSize*superFraction - frag.fontSize = max(frag.fontSize-sizeDelta,3) - - if frag.greek: frag.fontName = 'symbol' - - # bold, italic, and underline - x = frag.fontName = tt2ps(frag.fontName,frag.bold,frag.italic) - - #save our data - frag.text = data - - if hasattr(frag,'isBullet'): - delattr(frag,'isBullet') - self.bFragList.append(frag) - else: - self.fragList.append(frag) - - def handle_cdata(self,data): - self.handle_data(data) - - def _setup_for_parse(self,style): - self._seq = reportlab.lib.sequencer.getSequencer() - self._reset(style) # reinitialise the parser - - def parse(self, text, style): - """Given a formatted string will return a list of - ParaFrag objects with their calculated widths. - If errors occur None will be returned and the - self.errors holds a list of the error messages. - """ - self._setup_for_parse(style) - # the xmlparser requires that all text be surrounded by xml - # tags, therefore we must throw some unused flags around the - # given string - if not(len(text)>=6 and text[0]=='<' and _re_para.match(text)): - text = ""+text+"" - self.feed(text) - self.close() # force parsing to complete - return self._complete_parse() - - def _complete_parse(self): - del self._seq - style = self._style - del self._style - if len(self.errors)==0: - fragList = self.fragList - bFragList = hasattr(self,'bFragList') and self.bFragList or None - self._iReset() - else: - fragList = bFragList = None - return style, fragList, bFragList - - def _tt_parse(self,tt): - tag = tt[0] - try: - start = getattr(self,'start_'+tag) - end = getattr(self,'end_'+tag) - except AttributeError: - raise ValueError('Invalid tag "%s"' % tag) - start(tt[1] or {}) - C = tt[2] - if C: - M = self._tt_handlers - for c in C: - M[type(c) is TupleType](c) - end() - - def tt_parse(self,tt,style): - '''parse from tupletree form''' - self._setup_for_parse(style) - self._tt_handlers = self.handle_data,self._tt_parse - self._tt_parse(tt) - return self._complete_parse() - -if __name__=='__main__': - from reportlab.platypus import cleanBlockQuotedText - _parser=ParaParser() - def check_text(text,p=_parser): - print '##########' - text = cleanBlockQuotedText(text) - l,rv,bv = p.parse(text,style) - if rv is None: - for l in _parser.errors: - print l - else: - print 'ParaStyle', l.fontName,l.fontSize,l.textColor - for l in rv: - print l.fontName,l.fontSize,l.textColor,l.bold, l.rise, '|%s|'%l.text[:25], - if hasattr(l,'cbDefn'): - print 'cbDefn',l.cbDefn.name,l.cbDefn.label,l.cbDefn.kind - else: print - - style=ParaFrag() - style.fontName='Times-Roman' - style.fontSize = 12 - style.textColor = black - style.bulletFontName = black - style.bulletFontName='Times-Roman' - style.bulletFontSize=12 - - text=''' - aDβ - - Tell me, O muse, of that ingenious hero who travelled far and wide - after he had sacked the famous town of Troy. Many cities did he visit, - and many were the nations with whose manners and customs he was acquainted; - moreover he suffered much by sea while trying to save his own life - and bring his men safely home; but do what he might he could not save - his men, for they perished through their own sheer folly in eating - the cattle of the Sun-god Hyperion; so the god prevented them from - ever reaching home. Tell me, too, about all these things, O daughter - of Jove, from whatsoever source you1 may know them. - ''' - check_text(text) - check_text(' ') - check_text('ReportLab -- Reporting for the Internet Age') - check_text(''' - τTell me, O muse, of that ingenious hero who travelled far and wide - after he had sacked the famous town of Troy. Many cities did he visit, - and many were the nations with whose manners and customs he was acquainted; - moreover he suffered much by sea while trying to save his own life - and bring his men safely home; but do what he might he could not save - his men, for they perished through their own sheer folly in eating - the cattle of the Sun-god Hyperion; so the god prevented them from - ever reaching home. Tell me, too, about all these things, O daughter - of Jove, from whatsoever source you may know them.''') - check_text(''' - Telemachus took this speech as of good omen and rose at once, for - he was bursting with what he had to say. He stood in the middle of - the assembly and the good herald Pisenor brought him his staff. Then, - turning to Aegyptius, "Sir," said he, "it is I, as you will shortly - learn, who have convened you, for it is I who am the most aggrieved. - I have not got wind of any host approaching about which I would warn - you, nor is there any matter of public moment on which I would speak. - My grieveance is purely personal, and turns on two great misfortunes - which have fallen upon my house. The first of these is the loss of - my excellent father, who was chief among all you here present, and - was like a father to every one of you; the second is much more serious, - and ere long will be the utter ruin of my estate. The sons of all - the chief men among you are pestering my mother to marry them against - her will. They are afraid to go to her father Icarius, asking him - to choose the one he likes best, and to provide marriage gifts for - his daughter, but day by day they keep hanging about my father's house, - sacrificing our oxen, sheep, and fat goats for their banquets, and - never giving so much as a thought to the quantity of wine they drink. - No estate can stand such recklessness; we have now no Ulysses to ward - off harm from our doors, and I cannot hold my own against them. I - shall never all my days be as good a man as he was, still I would - indeed defend myself if I had power to do so, for I cannot stand such - treatment any longer; my house is being disgraced and ruined. Have - respect, therefore, to your own consciences and to public opinion. - Fear, too, the wrath of heaven, lest the gods should be displeased - and turn upon you. I pray you by Jove and Themis, who is the beginning - and the end of councils, [do not] hold back, my friends, and leave - me singlehanded- unless it be that my brave father Ulysses did some - wrong to the Achaeans which you would now avenge on me, by aiding - and abetting these suitors. Moreover, if I am to be eaten out of house - and home at all, I had rather you did the eating yourselves, for I - could then take action against you to some purpose, and serve you - with notices from house to house till I got paid in full, whereas - now I have no remedy."''') - - check_text(''' -But as the sun was rising from the fair sea into the firmament of -heaven to shed light on mortals and immortals, they reached Pylos -the city of Neleus. Now the people of Pylos were gathered on the sea -shore to offer sacrifice of black bulls to Neptune lord of the Earthquake. -There were nine guilds with five hundred men in each, and there were -nine bulls to each guild. As they were eating the inward meats and -burning the thigh bones [on the embers] in the name of Neptune, Telemachus -and his crew arrived, furled their sails, brought their ship to anchor, -and went ashore. ''') - check_text(''' -So the neighbours and kinsmen of Menelaus were feasting and making -merry in his house. There was a bard also to sing to them and play -his lyre, while two tumblers went about performing in the midst of -them when the man struck up with his tune.]''') - check_text(''' -"When we had passed the [Wandering] rocks, with Scylla and terrible -Charybdis, we reached the noble island of the sun-god, where were -the goodly cattle and sheep belonging to the sun Hyperion. While still -at sea in my ship I could bear the cattle lowing as they came home -to the yards, and the sheep bleating. Then I remembered what the blind -Theban prophet Teiresias had told me, and how carefully Aeaean Circe -had warned me to shun the island of the blessed sun-god. So being -much troubled I said to the men, 'My men, I know you are hard pressed, -but listen while I tell you the prophecy that Teiresias made me, and -how carefully Aeaean Circe warned me to shun the island of the blessed -sun-god, for it was here, she said, that our worst danger would lie. -Head the ship, therefore, away from the island.''') - check_text('''A<B>C&D"E'F''') - check_text('''A< B> C& D" E' F''') - check_text('''&'"]]>''') - check_text('''+ -There was a bard also to sing to them and play -his lyre, while two tumblers went about performing in the midst of -them when the man struck up with his tune.]''') - check_text('''A paragraph''') - check_text('''B paragraph''') - # HVB, 30.05.2003: Test for new features - _parser.caseSensitive=0 - check_text('''Here comes Helvetica 14 with strong emphasis.''') - check_text('''Here comes Helvetica 14 with strong emphasis.''') - check_text('''Here comes Courier 3cm and normal again.''') diff --git a/bin/reportlab/platypus/tableofcontents.py b/bin/reportlab/platypus/tableofcontents.py deleted file mode 100644 index d7f18393d89..00000000000 --- a/bin/reportlab/platypus/tableofcontents.py +++ /dev/null @@ -1,329 +0,0 @@ -#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/platypus/tableofcontents.py -""" -This module defines a single TableOfContents() class that can be used to -create automatically a table of tontents for Platypus documents like -this: - - story = [] - toc = TableOfContents() - story.append(toc) - # some heading paragraphs here... - doc = MyTemplate(path) - doc.multiBuild(story) - -The data needed to create the table is a list of (level, text, pageNum) -triplets, plus some paragraph styles for each level of the table itself. -The triplets will usually be created in a document template's method -like afterFlowable(), making notification calls using the notify() -method with appropriate data like this: - - (level, text, pageNum) = ... - self.notify('TOCEntry', (level, text, pageNum)) - -As the table of contents need at least two passes over the Platypus -story which is why the moultiBuild0() method must be called. - -The levelParaStyle variables are the paragraph styles used -to format the entries in the table of contents. Their indentation -is calculated like this: each entry starts at a multiple of some -constant named delta. If one entry spans more than one line, all -lines after the first are indented by the same constant named -epsilon. -""" -__version__=''' $Id$ ''' -import string - -from reportlab.lib import enums -from reportlab.lib.units import cm -from reportlab.lib.styles import ParagraphStyle -from reportlab.platypus.paragraph import Paragraph -from reportlab.platypus.doctemplate import IndexingFlowable -from reportlab.platypus.tables import TableStyle, Table - - -# Default paragraph styles for tables of contents. -# (This could also be generated automatically or even -# on-demand if it is not known how many levels the -# TOC will finally need to display...) - -delta = 1*cm -epsilon = 0.5*cm - -levelZeroParaStyle = \ - ParagraphStyle(name='LevelZero', - fontName='Times-Roman', - fontSize=10, - leading=11, - firstLineIndent = -epsilon, - leftIndent = 0*delta + epsilon) - -levelOneParaStyle = \ - ParagraphStyle(name='LevelOne', - parent = levelZeroParaStyle, - leading=11, - firstLineIndent = -epsilon, - leftIndent = 1*delta + epsilon) - -levelTwoParaStyle = \ - ParagraphStyle(name='LevelTwo', - parent = levelOneParaStyle, - leading=11, - firstLineIndent = -epsilon, - leftIndent = 2*delta + epsilon) - -levelThreeParaStyle = \ - ParagraphStyle(name='LevelThree', - parent = levelTwoParaStyle, - leading=11, - firstLineIndent = -epsilon, - leftIndent = 3*delta + epsilon) - -levelFourParaStyle = \ - ParagraphStyle(name='LevelFour', - parent = levelTwoParaStyle, - leading=11, - firstLineIndent = -epsilon, - leftIndent = 4*delta + epsilon) - -defaultTableStyle = \ - TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP')]) - - -class TableOfContents(IndexingFlowable): - """This creates a formatted table of contents. - - It presumes a correct block of data is passed in. - The data block contains a list of (level, text, pageNumber) - triplets. You can supply a paragraph style for each level - (starting at zero). - """ - - def __init__(self): - self.entries = [] - self.rightColumnWidth = 72 - self.levelStyles = [levelZeroParaStyle, - levelOneParaStyle, - levelTwoParaStyle, - levelThreeParaStyle, - levelFourParaStyle] - self.tableStyle = defaultTableStyle - self._table = None - self._entries = [] - self._lastEntries = [] - - - def beforeBuild(self): - # keep track of the last run - self._lastEntries = self._entries[:] - self.clearEntries() - - - def isIndexing(self): - return 1 - - - def isSatisfied(self): - return (self._entries == self._lastEntries) - - def notify(self, kind, stuff): - """The notification hook called to register all kinds of events. - - Here we are interested in 'TOCEntry' events only. - """ - if kind == 'TOCEntry': - (level, text, pageNum) = stuff - self.addEntry(level, text, pageNum) - - - def clearEntries(self): - self._entries = [] - - - def addEntry(self, level, text, pageNum): - """Adds one entry to the table of contents. - - This allows incremental buildup by a doctemplate. - Requires that enough styles are defined.""" - - assert type(level) == type(1), "Level must be an integer" - assert level < len(self.levelStyles), \ - "Table of contents must have a style defined " \ - "for paragraph level %d before you add an entry" % level - - self._entries.append((level, text, pageNum)) - - - def addEntries(self, listOfEntries): - """Bulk creation of entries in the table of contents. - - If you knew the titles but not the page numbers, you could - supply them to get sensible output on the first run.""" - - for (level, text, pageNum) in listOfEntries: - self.addEntry(level, text, pageNum) - - - def wrap(self, availWidth, availHeight): - "All table properties should be known by now." - - widths = (availWidth - self.rightColumnWidth, - self.rightColumnWidth) - - # makes an internal table which does all the work. - # we draw the LAST RUN's entries! If there are - # none, we make some dummy data to keep the table - # from complaining - if len(self._lastEntries) == 0: - _tempEntries = [(0,'Placeholder for table of contents',0)] - else: - _tempEntries = self._lastEntries - - tableData = [] - for (level, text, pageNum) in _tempEntries: - leftColStyle = self.levelStyles[level] - #right col style is right aligned - rightColStyle = ParagraphStyle(name='leftColLevel%d' % level, - parent=leftColStyle, - leftIndent=0, - alignment=enums.TA_RIGHT) - leftPara = Paragraph(text, leftColStyle) - rightPara = Paragraph(str(pageNum), rightColStyle) - tableData.append([leftPara, rightPara]) - - self._table = Table(tableData, colWidths=widths, - style=self.tableStyle) - - self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight) - return (self.width, self.height) - - - def split(self, availWidth, availHeight): - """At this stage we do not care about splitting the entries, - we will just return a list of platypus tables. Presumably the - calling app has a pointer to the original TableOfContents object; - Platypus just sees tables. - """ - return self._table.splitOn(self.canv,availWidth, availHeight) - - - def drawOn(self, canvas, x, y, _sW=0): - """Don't do this at home! The standard calls for implementing - draw(); we are hooking this in order to delegate ALL the drawing - work to the embedded table object. - """ - self._table.drawOn(canvas, x, y, _sW) - - -class SimpleIndex(IndexingFlowable): - """This creates a very simple index. - - Entries have a string key, and appear with a page number on - the right. Prototype for more sophisticated multi-level index.""" - def __init__(self): - #keep stuff in a dictionary while building - self._entries = {} - self._lastEntries = {} - self._table = None - self.textStyle = ParagraphStyle(name='index', - fontName='Times-Roman', - fontSize=12) - def isIndexing(self): - return 1 - - def isSatisfied(self): - return (self._entries == self._lastEntries) - - def beforeBuild(self): - # keep track of the last run - self._lastEntries = self._entries.copy() - self.clearEntries() - - def clearEntries(self): - self._entries = {} - - def notify(self, kind, stuff): - """The notification hook called to register all kinds of events. - - Here we are interested in 'IndexEntry' events only. - """ - if kind == 'IndexEntry': - (text, pageNum) = stuff - self.addEntry(text, pageNum) - - def addEntry(self, text, pageNum): - """Allows incremental buildup""" - if self._entries.has_key(text): - self._entries[text].append(str(pageNum)) - else: - self._entries[text] = [pageNum] - - def split(self, availWidth, availHeight): - """At this stage we do not care about splitting the entries, - we will just return a list of platypus tables. Presumably the - calling app has a pointer to the original TableOfContents object; - Platypus just sees tables. - """ - return self._table.splitOn(self.canv,availWidth, availHeight) - - def wrap(self, availWidth, availHeight): - "All table properties should be known by now." - # makes an internal table which does all the work. - # we draw the LAST RUN's entries! If there are - # none, we make some dummy data to keep the table - # from complaining - if len(self._lastEntries) == 0: - _tempEntries = [('Placeholder for index',[0,1,2])] - else: - _tempEntries = self._lastEntries.items() - _tempEntries.sort() - - tableData = [] - for (text, pageNumbers) in _tempEntries: - #right col style is right aligned - allText = text + ': ' + string.join(map(str, pageNumbers), ', ') - para = Paragraph(allText, self.textStyle) - tableData.append([para]) - - self._table = Table(tableData, colWidths=[availWidth]) - - self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight) - return (self.width, self.height) - - def drawOn(self, canvas, x, y, _sW=0): - """Don't do this at home! The standard calls for implementing - draw(); we are hooking this in order to delegate ALL the drawing - work to the embedded table object. - """ - self._table.drawOn(canvas, x, y, _sW) - -class ReferenceText(IndexingFlowable): - """Fakery to illustrate how a reference would work if we could - put it in a paragraph.""" - def __init__(self, textPattern, targetKey): - self.textPattern = textPattern - self.target = targetKey - self.paraStyle = ParagraphStyle('tmp') - self._lastPageNum = None - self._pageNum = -999 - self._para = None - - def beforeBuild(self): - self._lastPageNum = self._pageNum - - def notify(self, kind, stuff): - if kind == 'Target': - (key, pageNum) = stuff - if key == self.target: - self._pageNum = pageNum - - def wrap(self, availWidth, availHeight): - text = self.textPattern % self._lastPageNum - self._para = Paragraph(text, self.paraStyle) - return self._para.wrap(availWidth, availHeight) - - def drawOn(self, canvas, x, y, _sW=0): - self._para.drawOn(canvas, x, y, _sW) - - diff --git a/bin/reportlab/platypus/tables.py b/bin/reportlab/platypus/tables.py deleted file mode 100644 index a97eb38c4bf..00000000000 --- a/bin/reportlab/platypus/tables.py +++ /dev/null @@ -1,1184 +0,0 @@ -#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/platypus/tables.py -__version__=''' $Id$ ''' - -__doc__=""" -Tables are created by passing the constructor a tuple of column widths, a tuple of row heights and the data in -row order. Drawing of the table can be controlled by using a TableStyle instance. This allows control of the -color and weight of the lines (if any), and the font, alignment and padding of the text. - -None values in the sequence of row heights or column widths, mean that the corresponding rows -or columns should be automatically sized. - -All the cell values should be convertible to strings; embedded newline '\\n' characters -cause the value to wrap (ie are like a traditional linefeed). - -See the test output from running this module as a script for a discussion of the method for constructing -tables and table styles. -""" -from reportlab.platypus.flowables import Flowable, Preformatted -from reportlab import rl_config -from reportlab.lib.styles import PropertySet, ParagraphStyle -from reportlab.lib import colors -from reportlab.lib.utils import fp_str -from reportlab.pdfbase import pdfmetrics -import operator, string -from types import TupleType, ListType, StringType - -class CellStyle(PropertySet): - defaults = { - 'fontname':'Times-Roman', - 'fontsize':10, - 'leading':12, - 'leftPadding':6, - 'rightPadding':6, - 'topPadding':3, - 'bottomPadding':3, - 'firstLineIndent':0, - 'color':colors.black, - 'alignment': 'LEFT', - 'background': (1,1,1), - 'valign': 'BOTTOM', - } - -LINECAPS={'butt':0,'round':1,'projecting':2,'squared':2} -LINEJOINS={'miter':0,'round':1,'bevel':2} - -# experimental replacement -class CellStyle1(PropertySet): - fontname = "Times-Roman" - fontsize = 10 - leading = 12 - leftPadding = 6 - rightPadding = 6 - topPadding = 3 - bottomPadding = 3 - firstLineIndent = 0 - color = colors.black - alignment = 'LEFT' - background = (1,1,1) - valign = "BOTTOM" - def __init__(self, name, parent=None): - self.name = name - if parent is not None: - parent.copy(self) - def copy(self, result=None): - if result is None: - result = CellStyle1() - for name in dir(self): - setattr(result, name, gettattr(self, name)) - return result -CellStyle = CellStyle1 - -class TableStyle: - def __init__(self, cmds=None, parent=None, **kw): - #handle inheritance from parent first. - commands = [] - if parent: - # copy the parents list at construction time - commands = commands + parent.getCommands() - self._opts = parent._opts - if cmds: - commands = commands + list(cmds) - self._cmds = commands - self._opts={} - self._opts.update(kw) - - def add(self, *cmd): - self._cmds.append(cmd) - def __repr__(self): - L = map(repr, self._cmds) - import string - L = string.join(L, " \n") - return "TableStyle(\n%s\n) # end TableStyle" % L - def getCommands(self): - return self._cmds - -TableStyleType = type(TableStyle()) -_SeqTypes = (TupleType, ListType) - -def _rowLen(x): - return type(x) not in _SeqTypes and 1 or len(x) - -def _calc_pc(V,avail): - '''check list V for percentage or * values - 1) absolute values go through unchanged - 2) percentages are used as weights for unconsumed space - 3) if no None values were seen '*' weights are - set equally with unclaimed space - otherwise * weights are assigned as None''' - R = [] - r = R.append - I = [] - i = I.append - J = [] - j = J.append - s = avail - w = n = 0. - for v in V: - if type(v) is type(""): - v = v.strip() - if not v: - v = None - n += 1 - elif v.endswith('%'): - v = float(v[:-1]) - w += v - i(len(R)) - elif v=='*': - j(len(R)) - else: - v = float(v) - s -= v - elif v is None: - n += 1 - else: - s -= v - r(v) - s = max(0.,s) - f = s/max(100.,w) - for i in I: - R[i] *= f - s -= R[i] - s = max(0.,s) - m = len(J) - if m: - v = n==0 and s/m or None - for j in J: - R[j] = v - return R - -def _hLine(canvLine, scp, ecp, y, hBlocks, FUZZ=rl_config._FUZZ): - ''' - Draw horizontal lines; do not draw through regions specified in hBlocks - This also serves for vertical lines with a suitable canvLine - ''' - if hBlocks: hBlocks = hBlocks.get(y,None) - if not hBlocks or scp>=hBlocks[-1][1]-FUZZ or ecp<=hBlocks[0][0]+FUZZ: - canvLine(scp,y,ecp,y) - else: - i = 0 - n = len(hBlocks) - while scp=ecp-FUZZ: - i += 1 - continue - i0 = max(scp,x0) - i1 = min(ecp,x1) - if i0>scp: canvLine(scp,y,i0,y) - scp = i1 - if scp%s" % (self.__class__.__name__, id(self), nr, nc, vx) - - def _listCellGeom(self, V,w,s,W=None,H=None,aH=72000): - aW = w-s.leftPadding-s.rightPadding - aH = aH - s.topPadding - s.bottomPadding - t = 0 - w = 0 - canv = getattr(self,'canv',None) - for v in V: - vw, vh = v.wrapOn(canv,aW, aH) - if W is not None: W.append(vw) - if H is not None: H.append(vh) - w = max(w,vw) - t = t + vh + v.getSpaceBefore()+v.getSpaceAfter() - return w, t - V[0].getSpaceBefore()-V[-1].getSpaceAfter() - - def _calc_width(self,availWidth,W=None): - if getattr(self,'_width_calculated_once',None): return - #comments added by Andy to Robin's slightly terse variable names - if not W: W = _calc_pc(self._argW,availWidth) #widths array - if None in W: #some column widths are not given - canv = getattr(self,'canv',None) - saved = None - colSpanCells = self._spanCmds and self._colSpanCells or () - if W is self._argW: W = W[:] - while None in W: - j = W.index(None) #find first unspecified column - f = lambda x,j=j: operator.getitem(x,j) - V = map(f,self._cellvalues) #values for this column - S = map(f,self._cellStyles) #styles for this column - w = 0 - i = 0 - - for v, s in map(None, V, S): - #if the current cell is part of a spanned region, - #assume a zero size. - if (j, i) in colSpanCells: - t = 0.0 - else:#work out size - t = self._elementWidth(v,s) - if t is None: - raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width\n%s" % (v.identity(30),i,j,self.identity(30)) - t = t + s.leftPadding+s.rightPadding - if t>w: w = t #record a new maximum - i = i + 1 - - W[j] = w - - self._colWidths = W - width = 0 - self._colpositions = [0] #index -1 is right side boundary; we skip when processing cells - for w in W: - width = width + w - self._colpositions.append(width) - - self._width = width - self._width_calculated_once = 1 - - def _elementWidth(self,v,s): - t = type(v) - if t in _SeqTypes: - w = 0 - for e in v: - ew = self._elementWidth(self,v) - if ew is None: return None - w = max(w,ew) - return w - elif isinstance(v,Flowable) and v._fixedWidth: - return v.width - else: - if t is not StringType: v = v is None and '' or str(v) - v = string.split(v, "\n") - return max(map(lambda a, b=s.fontname, c=s.fontsize,d=pdfmetrics.stringWidth: d(a,b,c), v)) - - def _calc_height(self, availHeight, availWidth, H=None, W=None): - - H = self._argH - if not W: W = _calc_pc(self._argW,availWidth) #widths array - - hmax = lim = len(H) - longTable = getattr(self,'_longTableOptimize',None) - - if None in H: - canv = getattr(self,'canv',None) - saved = None - #get a handy list of any cells which span rows. should be ignored for sizing - if self._spanCmds: - rowSpanCells = self._rowSpanCells - colSpanCells = self._colSpanCells - spanRanges = self._spanRanges - colpositions = self._colpositions - else: - rowSpanCells = colSpanCells = () - if canv: saved = canv._fontname, canv._fontsize, canv._leading - H = H[:] #make a copy as we'll change it - self._rowHeights = H - while None in H: - i = H.index(None) - if longTable: - hmax = i - height = reduce(operator.add, H[:i], 0) - # we can stop if we have filled up all available room - if height > availHeight: break - V = self._cellvalues[i] # values for row i - S = self._cellStyles[i] # styles for row i - h = 0 - j = 0 - for v, s, w in map(None, V, S, W): # value, style, width (lengths must match) - ji = j,i - if ji in rowSpanCells: - t = 0.0 # don't count it, it's either occluded or unreliable - else: - t = type(v) - if t in _SeqTypes or isinstance(v,Flowable): - if not t in _SeqTypes: v = (v,) - if w is None: - raise ValueError, "Flowable %s in cell(%d,%d) can't have auto width in\n%s" % (v[0].identity(30),i,j,self.identity(30)) - if canv: canv._fontname, canv._fontsize, canv._leading = s.fontname, s.fontsize, s.leading or 1.2*s.fontsize - if ji in colSpanCells: - t = spanRanges[ji] - w = max(colpositions[t[2]+1]-colpositions[t[0]],w) - dW,t = self._listCellGeom(v,w,s) - if canv: canv._fontname, canv._fontsize, canv._leading = saved - dW = dW + s.leftPadding + s.rightPadding - if not rl_config.allowTableBoundsErrors and dW>w: - raise "LayoutError", "Flowable %s (%sx%s points) too wide for cell(%d,%d) (%sx* points) in\n%s" % (v[0].identity(30),fp_str(dW),fp_str(t),i,j, fp_str(w), self.identity(30)) - else: - if t is not StringType: - v = v is None and '' or str(v) - v = string.split(v, "\n") - t = s.leading*len(v) - t = t+s.bottomPadding+s.topPadding - if t>h: h = t #record a new maximum - j = j + 1 - H[i] = h - if None not in H: hmax = lim - - height = self._height = reduce(operator.add, H[:hmax], 0) - self._rowpositions = [height] # index 0 is actually topline; we skip when processing cells - for h in H[:hmax]: - height = height - h - self._rowpositions.append(height) - assert abs(height)<1e-8, 'Internal height error' - self._hmax = hmax - - def _calc(self, availWidth, availHeight): - #if hasattr(self,'_width'): return - - #in some cases there are unsizable things in - #cells. If so, apply a different algorithm - #and assign some withs in a dumb way. - #this CHANGES the widths array. - if (None in self._colWidths or '*' in self._colWidths) and self._hasVariWidthElements(): - W = self._calcPreliminaryWidths(availWidth) #widths - else: - W = None - - # need to know which cells are part of spanned - # ranges, so _calc_height and _calc_width can ignore them - # in sizing - if self._spanCmds: - self._calcSpanRanges() - if None in self._argH: - self._calc_width(availWidth,W=W) - - # calculate the full table height - self._calc_height(availHeight,availWidth,W=W) - - # calculate the full table width - self._calc_width(availWidth,W=W) - - if self._spanCmds: - #now work out the actual rect for each spanned cell from the underlying grid - self._calcSpanRects() - - def _hasVariWidthElements(self, upToRow=None): - """Check for flowables in table cells and warn up front. - - Allow a couple which we know are fixed size such as - images and graphics.""" - bad = 0 - if upToRow is None: upToRow = self._nrows - for row in range(min(self._nrows, upToRow)): - for col in range(self._ncols): - value = self._cellvalues[row][col] - if not self._canGetWidth(value): - bad = 1 - #raise Exception('Unsizable elements found at row %d column %d in table with content:\n %s' % (row, col, value)) - return bad - - def _canGetWidth(self, thing): - "Can we work out the width quickly?" - if type(thing) in (ListType, TupleType): - for elem in thing: - if not self._canGetWidth(elem): - return 0 - return 1 - elif isinstance(thing, Flowable): - return thing._fixedWidth # must loosen this up - else: #string, number, None etc. - #anything else gets passed to str(...) - # so should be sizable - return 1 - - def _calcPreliminaryWidths(self, availWidth): - """Fallback algorithm for when main one fails. - - Where exact width info not given but things like - paragraphs might be present, do a preliminary scan - and assign some sensible values - just divide up - all unsizeable columns by the remaining space.""" - - W = _calc_pc(self._argW,availWidth) #widths array - verbose = 0 - totalDefined = 0.0 - numberUndefined = 0 - for w in W: - if w is None: - numberUndefined = numberUndefined + 1 - else: - totalDefined = totalDefined + w - if verbose: print 'prelim width calculation. %d columns, %d undefined width, %0.2f units remain' % ( - self._ncols, numberUndefined, availWidth - totalDefined) - - #check columnwise in each None column to see if they are sizable. - given = [] - sizeable = [] - unsizeable = [] - for colNo in range(self._ncols): - if W[colNo] is None: - siz = 1 - for rowNo in range(self._nrows): - value = self._cellvalues[rowNo][colNo] - if not self._canGetWidth(value): - siz = 0 - break - if siz: - sizeable.append(colNo) - else: - unsizeable.append(colNo) - else: - given.append(colNo) - if len(given) == self._ncols: - return - if verbose: print 'predefined width: ',given - if verbose: print 'uncomputable width: ',unsizeable - if verbose: print 'computable width: ',sizeable - - #how much width is left: - # on the next iteration we could size the sizeable ones, for now I'll just - # divide up the space - newColWidths = list(W) - guessColWidth = (availWidth - totalDefined) / (len(unsizeable)+len(sizeable)) - assert guessColWidth >= 0, "table is too wide already, cannot choose a sane width for undefined columns" - if verbose: print 'assigning width %0.2f to all undefined columns' % guessColWidth - for colNo in sizeable: - newColWidths[colNo] = guessColWidth - for colNo in unsizeable: - newColWidths[colNo] = guessColWidth - - if verbose: print 'new widths are:', newColWidths - self._argW = self._colWidths = newColWidths - return newColWidths - - def _calcSpanRanges(self): - """Work out rects for tables which do row and column spanning. - - This creates some mappings to let the later code determine - if a cell is part of a "spanned" range. - self._spanRanges shows the 'coords' in integers of each - 'cell range', or None if it was clobbered: - (col, row) -> (col0, row0, col1, row1) - - Any cell not in the key is not part of a spanned region - """ - self._spanRanges = spanRanges = {} - for x in xrange(self._ncols): - for y in xrange(self._nrows): - spanRanges[x,y] = (x, y, x, y) - self._colSpanCells = [] - self._rowSpanCells = [] - csa = self._colSpanCells.append - rsa = self._rowSpanCells.append - for (cmd, start, stop) in self._spanCmds: - x0, y0 = start - x1, y1 = stop - - #normalize - if x0 < 0: x0 = x0 + self._ncols - if x1 < 0: x1 = x1 + self._ncols - if y0 < 0: y0 = y0 + self._nrows - if y1 < 0: y1 = y1 + self._nrows - if x0 > x1: x0, x1 = x1, x0 - if y0 > y1: y0, y1 = y1, y0 - - if x0!=x1 or y0!=y1: - #column span - if x0!=x1: - for y in xrange(y0, y1+1): - for x in xrange(x0,x1+1): - csa((x,y)) - #row span - if y0!=y1: - for y in xrange(y0, y1+1): - for x in xrange(x0,x1+1): - rsa((x,y)) - - for y in xrange(y0, y1+1): - for x in xrange(x0,x1+1): - spanRanges[x,y] = None - # set the main entry - spanRanges[x0,y0] = (x0, y0, x1, y1) - - def _calcSpanRects(self): - """Work out rects for tables which do row and column spanning. - - Based on self._spanRanges, which is already known, - and the widths which were given or previously calculated, - self._spanRects shows the real coords for drawing: - (col, row) -> (x, y, width, height) - - for each cell. Any cell which 'does not exist' as another - has spanned over it will get a None entry on the right - """ - if getattr(self,'_spanRects',None): return - colpositions = self._colpositions - rowpositions = self._rowpositions - self._spanRects = spanRects = {} - self._vBlocks = vBlocks = {} - self._hBlocks = hBlocks = {} - for (coord, value) in self._spanRanges.items(): - if value is None: - spanRects[coord] = None - else: - col,row = coord - col0, row0, col1, row1 = value - if col1-col0>0: - for _ in xrange(col0+1,col1+1): - vBlocks.setdefault(colpositions[_],[]).append((rowpositions[row1+1],rowpositions[row0])) - if row1-row0>0: - for _ in xrange(row0+1,row1+1): - hBlocks.setdefault(rowpositions[_],[]).append((colpositions[col0],colpositions[col1+1])) - x = colpositions[col0] - y = rowpositions[row1+1] - width = colpositions[col1+1] - x - height = rowpositions[row0] - y - spanRects[coord] = (x, y, width, height) - - for _ in hBlocks, vBlocks: - for value in _.values(): - value.sort() - - def setStyle(self, tblstyle): - if type(tblstyle) is not TableStyleType: - tblstyle = TableStyle(tblstyle) - for cmd in tblstyle.getCommands(): - self._addCommand(cmd) - for k,v in tblstyle._opts.items(): - setattr(self,k,v) - - def _addCommand(self,cmd): - if cmd[0] in ('BACKGROUND','ROWBACKGROUNDS','COLBACKGROUNDS'): - self._bkgrndcmds.append(cmd) - elif cmd[0] == 'SPAN': - self._spanCmds.append(cmd) - elif _isLineCommand(cmd): - # we expect op, start, stop, weight, colour, cap, dashes, join - cmd = tuple(cmd) - if len(cmd)<5: raise ValueError('bad line command '+str(cmd)) - - #determine line cap value at position 5. This can be string or numeric. - if len(cmd)<6: - cmd = cmd+(1,) - else: - cap = cmd[5] - try: - if type(cap) is not type(int): - cap = LINECAPS[cap] - elif cap<0 or cap>2: - raise ValueError - cmd = cmd[:5]+(cap,)+cmd[6:] - except: - ValueError('Bad cap value %s in %s'%(cap,str(cmd))) - #dashes at index 6 - this is a dash array: - if len(cmd)<7: cmd = cmd+(None,) - - #join mode at index 7 - can be string or numeric, look up as for caps - if len(cmd)<8: cmd = cmd+(1,) - else: - join = cmd[7] - try: - if type(join) is not type(int): - join = LINEJOINS[cap] - elif join<0 or join>2: - raise ValueError - cmd = cmd[:7]+(join,) - except: - ValueError('Bad join value %s in %s'%(join,str(cmd))) - - #linecount at index 8. Default is 1, set to 2 for double line. - if len(cmd)<9: - lineCount = 1 - cmd = cmd + (lineCount,) - else: - lineCount = cmd[8] - assert lineCount >= 1 - #linespacing at index 9. Not applicable unless 2+ lines, defaults to line - #width so you get a visible gap between centres - if len(cmd)<10: cmd = cmd + (cmd[3],) - - assert len(cmd) == 10 - - self._linecmds.append(cmd) - else: - (op, (sc, sr), (ec, er)), values = cmd[:3] , cmd[3:] - if sc < 0: sc = sc + self._ncols - if ec < 0: ec = ec + self._ncols - if sr < 0: sr = sr + self._nrows - if er < 0: er = er + self._nrows - for i in range(sr, er+1): - for j in range(sc, ec+1): - _setCellStyle(self._cellStyles, i, j, op, values) - - def _drawLines(self): - ccap, cdash, cjoin = None, None, None - self.canv.saveState() - for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds: - if type(sr) is type('') and sr.startswith('split'): continue - if sc < 0: sc = sc + self._ncols - if ec < 0: ec = ec + self._ncols - if sr < 0: sr = sr + self._nrows - if er < 0: er = er + self._nrows - if cap!=None and ccap!=cap: - self.canv.setLineCap(cap) - ccap = cap - getattr(self,_LineOpMap.get(op, '_drawUnknown' ))( (sc, sr), (ec, er), weight, color, count, space) - self.canv.restoreState() - self._curcolor = None - - def _drawUnknown(self, (sc, sr), (ec, er), weight, color, count, space): - raise ValueError, "Unknown line command '%s'" % op - - def _drawGrid(self, (sc, sr), (ec, er), weight, color, count, space): - self._drawBox( (sc, sr), (ec, er), weight, color, count, space) - self._drawInnerGrid( (sc, sr), (ec, er), weight, color, count, space) - - def _drawBox(self, (sc, sr), (ec, er), weight, color, count, space): - self._drawHLines((sc, sr), (ec, sr), weight, color, count, space) - self._drawHLines((sc, er+1), (ec, er+1), weight, color, count, space) - self._drawVLines((sc, sr), (sc, er), weight, color, count, space) - self._drawVLines((ec+1, sr), (ec+1, er), weight, color, count, space) - - def _drawInnerGrid(self, (sc, sr), (ec, er), weight, color, count, space): - self._drawHLines((sc, sr+1), (ec, er), weight, color, count, space) - self._drawVLines((sc+1, sr), (ec, er), weight, color, count, space) - - def _prepLine(self, weight, color): - if color != self._curcolor: - self.canv.setStrokeColor(color) - self._curcolor = color - if weight != self._curweight: - self.canv.setLineWidth(weight) - self._curweight = weight - - def _drawHLines(self, (sc, sr), (ec, er), weight, color, count, space): - ecp = self._colpositions[sc:ec+2] - rp = self._rowpositions[sr:er+1] - if len(ecp)<=1 or len(rp)<1: return - self._prepLine(weight, color) - scp = ecp[0] - ecp = ecp[-1] - hBlocks = getattr(self,'_hBlocks',{}) - canvLine = self.canv.line - if count == 1: - for y in rp: - _hLine(canvLine, scp, ecp, y, hBlocks) - else: - lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count) - for y in rp: - _hLine(lf, scp, ecp, y, hBlocks) - - def _drawHLinesB(self, (sc, sr), (ec, er), weight, color, count, space): - self._drawHLines((sc, sr+1), (ec, er+1), weight, color, count, space) - - def _drawVLines(self, (sc, sr), (ec, er), weight, color, count, space): - erp = self._rowpositions[sr:er+2] - cp = self._colpositions[sc:ec+1] - if len(erp)<=1 or len(cp)<1: return - self._prepLine(weight, color) - srp = erp[0] - erp = erp[-1] - vBlocks = getattr(self,'_vBlocks',{}) - canvLine = lambda y0, x0, y1, x1, _line=self.canv.line: _line(x0,y0,x1,y1) - if count == 1: - for x in cp: - _hLine(canvLine, erp, srp, x, vBlocks) - else: - lf = lambda x0,y0,x1,y1,canvLine=canvLine, ws=weight+space, count=count: _multiLine(x0,x1,y0,canvLine,ws,count) - for x in cp: - _hLine(lf, erp, srp, x, vBlocks) - - def _drawVLinesA(self, (sc, sr), (ec, er), weight, color, count, space): - self._drawVLines((sc+1, sr), (ec+1, er), weight, color, count, space) - - def wrap(self, availWidth, availHeight): - self._calc(availWidth, availHeight) - #nice and easy, since they are predetermined size - self.availWidth = availWidth - return (self._width, self._height) - - def onSplit(self,T,byRow=1): - ''' - This method will be called when the Table is split. - Special purpose tables can override to do special stuff. - ''' - pass - - def _cr_0(self,n,cmds): - for c in cmds: - c = tuple(c) - (sc,sr), (ec,er) = c[1:3] - if sr>=n: continue - if er>=n: er = n-1 - self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:]) - - def _cr_1_1(self,n,repeatRows, cmds): - for c in cmds: - c = tuple(c) - (sc,sr), (ec,er) = c[1:3] - if sr in ('splitfirst','splitlast'): self._addCommand(c) - else: - if sr>=0 and sr>=repeatRows and sr=0 and er=repeatRows and sr=repeatRows and sr>=n: sr=sr+repeatRows-n - if er>=repeatRows and er=repeatRows and er>=n: er=er+repeatRows-n - self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:]) - - def _cr_1_0(self,n,cmds): - for c in cmds: - c = tuple(c) - (sc,sr), (ec,er) = c[1:3] - if sr in ('splitfirst','splitlast'): self._addCommand(c) - else: - if er>=0 and er=0 and sr=n: sr = sr-n - if er>=n: er = er-n - self._addCommand((c[0],)+((sc, sr), (ec, er))+c[3:]) - - def _splitRows(self,availHeight): - h = 0 - n = 0 - lim = len(self._rowHeights) - while navailHeight: break - h = hn - n = n + 1 - - if n<=self.repeatRows: - return [] - - if n==lim: return [self] - - repeatRows = self.repeatRows - repeatCols = self.repeatCols - splitByRow = self.splitByRow - data = self._cellvalues - - #we're going to split into two superRows - #R0 = slelf.__class__( data[:n], self._argW, self._argH[:n], - R0 = self.__class__( data[:n], self._colWidths, self._argH[:n], - repeatRows=repeatRows, repeatCols=repeatCols, - splitByRow=splitByRow) - - #copy the styles and commands - R0._cellStyles = self._cellStyles[:n] - - A = [] - # hack up the line commands - for op, (sc,sr), (ec,er), weight, color, cap, dash, join, count, space in self._linecmds: - if type(sr)is type('') and sr.startswith('split'): - A.append((op,(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space)) - if sr=='splitlast': - sr = er = n-1 - elif sr=='splitfirst': - sr = n - er = n - - if sc < 0: sc = sc + self._ncols - if ec < 0: ec = ec + self._ncols - if sr < 0: sr = sr + self._nrows - if er < 0: er = er + self._nrows - - if op in ('BOX','OUTLINE','GRID'): - if sr=n: - # we have to split the BOX - A.append(('LINEABOVE',(sc,sr), (ec,sr), weight, color, cap, dash, join, count, space)) - A.append(('LINEBEFORE',(sc,sr), (sc,er), weight, color, cap, dash, join, count, space)) - A.append(('LINEAFTER',(ec,sr), (ec,er), weight, color, cap, dash, join, count, space)) - A.append(('LINEBELOW',(sc,er), (ec,er), weight, color, cap, dash, join, count, space)) - if op=='GRID': - A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space)) - A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space)) - A.append(('INNERGRID',(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) - else: - A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) - elif op in ('INNERGRID','LINEABOVE'): - if sr=n: - A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space)) - A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space)) - A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) - elif op == 'LINEBELOW': - if sr=(n-1): - A.append(('LINEABOVE',(sc,n), (ec,n), weight, color, cap, dash, join, count, space)) - A.append((op,(sc,sr), (ec,er), weight, color)) - elif op == 'LINEABOVE': - if sr<=n and er>=n: - A.append(('LINEBELOW',(sc,n-1), (ec,n-1), weight, color, cap, dash, join, count, space)) - A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) - else: - A.append((op,(sc,sr), (ec,er), weight, color, cap, dash, join, count, space)) - - R0._cr_0(n,A) - R0._cr_0(n,self._bkgrndcmds) - - if repeatRows: - #R1 = slelf.__class__(data[:repeatRows]+data[n:],self._argW, - R1 = self.__class__(data[:repeatRows]+data[n:],self._colWidths, - self._argH[:repeatRows]+self._argH[n:], - repeatRows=repeatRows, repeatCols=repeatCols, - splitByRow=splitByRow) - R1._cellStyles = self._cellStyles[:repeatRows]+self._cellStyles[n:] - R1._cr_1_1(n,repeatRows,A) - R1._cr_1_1(n,repeatRows,self._bkgrndcmds) - else: - #R1 = slelf.__class__(data[n:], self._argW, self._argH[n:], - R1 = self.__class__(data[n:], self._colWidths, self._argH[n:], - repeatRows=repeatRows, repeatCols=repeatCols, - splitByRow=splitByRow) - R1._cellStyles = self._cellStyles[n:] - R1._cr_1_0(n,A) - R1._cr_1_0(n,self._bkgrndcmds) - - - R0.hAlign = R1.hAlign = self.hAlign - R0.vAlign = R1.vAlign = self.vAlign - self.onSplit(R0) - self.onSplit(R1) - return [R0,R1] - - def split(self, availWidth, availHeight): - self._calc(availWidth, availHeight) - if self.splitByRow: - if not rl_config.allowTableBoundsErrors and self._width>availWidth: return [] - return self._splitRows(availHeight) - else: - raise NotImplementedError - - def draw(self): - self._curweight = self._curcolor = self._curcellstyle = None - self._drawBkgrnd() - if self._spanCmds == []: - # old fashioned case, no spanning, steam on and do each cell - for row, rowstyle, rowpos, rowheight in map(None, self._cellvalues, self._cellStyles, self._rowpositions[1:], self._rowHeights): - for cellval, cellstyle, colpos, colwidth in map(None, row, rowstyle, self._colpositions[:-1], self._colWidths): - self._drawCell(cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight)) - else: - # we have some row or col spans, need a more complex algorithm - # to find the rect for each - for rowNo in range(self._nrows): - for colNo in range(self._ncols): - cellRect = self._spanRects[colNo, rowNo] - if cellRect is not None: - (x, y, width, height) = cellRect - cellval = self._cellvalues[rowNo][colNo] - cellstyle = self._cellStyles[rowNo][colNo] - self._drawCell(cellval, cellstyle, (x, y), (width, height)) - self._drawLines() - - - def _drawBkgrnd(self): - nrows = self._nrows - ncols = self._ncols - for cmd, (sc, sr), (ec, er), arg in self._bkgrndcmds: - if sc < 0: sc = sc + ncols - if ec < 0: ec = ec + ncols - if sr < 0: sr = sr + nrows - if er < 0: er = er + nrows - x0 = self._colpositions[sc] - y0 = self._rowpositions[sr] - x1 = self._colpositions[min(ec+1,ncols)] - y1 = self._rowpositions[min(er+1,nrows)] - w, h = x1-x0, y1-y0 - canv = self.canv - if callable(arg): - apply(arg,(self,canv, x0, y0, w, h)) - elif cmd == 'ROWBACKGROUNDS': - #Need a list of colors to cycle through. The arguments - #might be already colours, or convertible to colors, or - # None, or the string 'None'. - #It's very common to alternate a pale shade with None. - #print 'rowHeights=', self._rowHeights - colorCycle = map(colors.toColorOrNone, arg) - count = len(colorCycle) - rowCount = er - sr + 1 - for i in range(rowCount): - color = colorCycle[i%count] - h = self._rowHeights[sr + i] - if color: - canv.setFillColor(color) - canv.rect(x0, y0, w, -h, stroke=0,fill=1) - #print ' draw %0.0f, %0.0f, %0.0f, %0.0f' % (x0,y0,w,-h) - y0 = y0 - h - - elif cmd == 'COLBACKGROUNDS': - #cycle through colours columnwise - colorCycle = map(colors.toColorOrNone, arg) - count = len(colorCycle) - colCount = ec - sc + 1 - for i in range(colCount): - color = colorCycle[i%count] - w = self._colWidths[sc + i] - if color: - canv.setFillColor(color) - canv.rect(x0, y0, w, h, stroke=0,fill=1) - x0 = x0 +w - else: #cmd=='BACKGROUND' - canv.setFillColor(colors.toColor(arg)) - canv.rect(x0, y0, w, h, stroke=0,fill=1) - - def _drawCell(self, cellval, cellstyle, (colpos, rowpos), (colwidth, rowheight)): - if self._curcellstyle is not cellstyle: - cur = self._curcellstyle - if cur is None or cellstyle.color != cur.color: - self.canv.setFillColor(cellstyle.color) - if cur is None or cellstyle.leading != cur.leading or cellstyle.fontname != cur.fontname or cellstyle.fontsize != cur.fontsize: - self.canv.setFont(cellstyle.fontname, cellstyle.fontsize, cellstyle.leading) - self._curcellstyle = cellstyle - - just = cellstyle.alignment - valign = cellstyle.valign - n = type(cellval) - if n in _SeqTypes or isinstance(cellval,Flowable): - if not n in _SeqTypes: cellval = (cellval,) - # we assume it's a list of Flowables - W = [] - H = [] - w, h = self._listCellGeom(cellval,colwidth,cellstyle,W=W, H=H,aH=rowheight) - if valign=='TOP': - y = rowpos + rowheight - cellstyle.topPadding - elif valign=='BOTTOM': - y = rowpos+cellstyle.bottomPadding + h - else: - y = rowpos+(rowheight+cellstyle.bottomPadding-cellstyle.topPadding+h)/2.0 - y = y+cellval[0].getSpaceBefore() - for v, w, h in map(None,cellval,W,H): - if just=='LEFT': x = colpos+cellstyle.leftPadding - elif just=='RIGHT': x = colpos+colwidth-cellstyle.rightPadding - w - elif just in ('CENTRE', 'CENTER'): - x = colpos+(colwidth+cellstyle.leftPadding-cellstyle.rightPadding-w)/2.0 - else: - raise ValueError, 'Invalid justification %s' % just - y = y - v.getSpaceBefore() - y = y - h - v.drawOn(self.canv,x,y) - y = y - v.getSpaceAfter() - else: - if just == 'LEFT': - draw = self.canv.drawString - x = colpos + cellstyle.leftPadding - elif just in ('CENTRE', 'CENTER'): - draw = self.canv.drawCentredString - x = colpos + colwidth * 0.5 - elif just == 'RIGHT': - draw = self.canv.drawRightString - x = colpos + colwidth - cellstyle.rightPadding - elif just == 'DECIMAL': - draw = self.canv.drawAlignedString - x = colpos + colwidth - cellstyle.rightPadding - else: - raise ValueError, 'Invalid justification %s' % just - if n is StringType: val = cellval - else: val = str(cellval) - vals = string.split(val, "\n") - n = len(vals) - leading = cellstyle.leading - fontsize = cellstyle.fontsize - if valign=='BOTTOM': - y = rowpos + cellstyle.bottomPadding+n*leading-fontsize - elif valign=='TOP': - y = rowpos + rowheight - cellstyle.topPadding - fontsize - elif valign=='MIDDLE': - #tim roberts pointed out missing fontsize correction 2004-10-04 - y = rowpos + (cellstyle.bottomPadding + rowheight-cellstyle.topPadding+n*leading)/2.0 - fontsize - else: - raise ValueError, "Bad valign: '%s'" % str(valign) - - for v in vals: - draw(x, y, v) - y = y-leading - -# for text, -# drawCentredString(self, x, y, text) where x is center -# drawRightString(self, x, y, text) where x is right -# drawString(self, x, y, text) where x is left - -_LineOpMap = { 'GRID':'_drawGrid', - 'BOX':'_drawBox', - 'OUTLINE':'_drawBox', - 'INNERGRID':'_drawInnerGrid', - 'LINEBELOW':'_drawHLinesB', - 'LINEABOVE':'_drawHLines', - 'LINEBEFORE':'_drawVLines', - 'LINEAFTER':'_drawVLinesA', } - -class LongTable(Table): - '''Henning von Bargen's changes will be active''' - _longTableOptimize = 1 - -LINECOMMANDS = _LineOpMap.keys() - -def _isLineCommand(cmd): - return cmd[0] in LINECOMMANDS - -def _setCellStyle(cellStyles, i, j, op, values): - #new = CellStyle('<%d, %d>' % (i,j), cellStyles[i][j]) - #cellStyles[i][j] = new - ## modify in place!!! - new = cellStyles[i][j] - if op == 'FONT': - n = len(values) - new.fontname = values[0] - if n>1: - new.fontsize = values[1] - if n>2: - new.leading = values[2] - else: - new.leading = new.fontsize*1.2 - elif op in ('FONTNAME', 'FACE'): - new.fontname = values[0] - elif op in ('SIZE', 'FONTSIZE'): - new.fontsize = values[0] - elif op == 'LEADING': - new.leading = values[0] - elif op == 'TEXTCOLOR': - new.color = colors.toColor(values[0], colors.Color(0,0,0)) - elif op in ('ALIGN', 'ALIGNMENT'): - new.alignment = values[0] - elif op == 'VALIGN': - new.valign = values[0] - elif op == 'LEFTPADDING': - new.leftPadding = values[0] - elif op == 'RIGHTPADDING': - new.rightPadding = values[0] - elif op == 'TOPPADDING': - new.topPadding = values[0] - elif op == 'BOTTOMPADDING': - new.bottomPadding = values[0] - -GRID_STYLE = TableStyle( - [('GRID', (0,0), (-1,-1), 0.25, colors.black), - ('ALIGN', (1,1), (-1,-1), 'RIGHT')] - ) -BOX_STYLE = TableStyle( - [('BOX', (0,0), (-1,-1), 0.50, colors.black), - ('ALIGN', (1,1), (-1,-1), 'RIGHT')] - ) -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')] - ) -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')] - ) -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')] - ) - - -# experimental iterator which can apply a sequence -# of colors e.g. Blue, None, Blue, None as you move -# down. - - -if __name__ == '__main__': - from reportlab.test.test_platypus_tables import old_tables_test - old_tables_test() diff --git a/bin/reportlab/platypus/xpreformatted.py b/bin/reportlab/platypus/xpreformatted.py deleted file mode 100644 index 6d18c99aba9..00000000000 --- a/bin/reportlab/platypus/xpreformatted.py +++ /dev/null @@ -1,316 +0,0 @@ -#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/platypus/xpreformatted.py -__version__=''' $Id$ ''' - -import string -from types import StringType, ListType - -from reportlab.lib import PyFontify -from paragraph import Paragraph, cleanBlockQuotedText, _handleBulletWidth, \ - ParaLines, _getFragWords, stringWidth, _sameFrag -from flowables import _dedenter - - -def _getFragLines(frags): - lines = [] - cline = [] - W = frags[:] - while W != []: - w = W[0] - t = w.text - del W[0] - i = string.find(t,'\n') - if i>=0: - tleft = t[i+1:] - cline.append(w.clone(text=t[:i])) - lines.append(cline) - cline = [] - if tleft!='': - W.insert(0,w.clone(text=tleft)) - else: - cline.append(w) - if cline!=[]: - lines.append(cline) - return lines - -def _split_blPara(blPara,start,stop): - f = blPara.clone() - for a in ('lines', 'text'): - if hasattr(f,a): delattr(f,a) - f.lines = blPara.lines[start:stop] - return [f] - -# Will be removed shortly. -def _countSpaces(text): - return string.count(text, ' ') -## i = 0 -## s = 0 -## while 1: -## j = string.find(text,' ',i) -## if j<0: return s -## s = s + 1 -## i = j + 1 - -def _getFragWord(frags): - ''' given a fragment list return a list of lists - [size, spaces, (f00,w00), ..., (f0n,w0n)] - each pair f,w represents a style and some string - ''' - W = [] - n = 0 - s = 0 - for f in frags: - text = f.text[:] - W.append((f,text)) - n = n + stringWidth(text, f.fontName, f.fontSize) - - #s = s + _countSpaces(text) - s = s + string.count(text, ' ') # much faster for many blanks - - #del f.text # we can't do this until we sort out splitting - # of paragraphs - return n, s, W - - -class XPreformatted(Paragraph): - def __init__(self, text, style, bulletText = None, frags=None, caseSensitive=1, dedent=0): - self.caseSensitive = caseSensitive - cleaner = lambda text, dedent=dedent: string.join(_dedenter(text or '',dedent),'\n') - self._setup(text, style, bulletText, frags, cleaner) - - def breakLines(self, width): - """ - Returns a broken line structure. There are two cases - - A) For the simple case of a single formatting input fragment the output is - A fragment specifier with - kind = 0 - fontName, fontSize, leading, textColor - lines= A list of lines - Each line has two items. - 1) unused width in points - 2) a list of words - - B) When there is more than one input formatting fragment the out put is - A fragment specifier with - kind = 1 - lines= A list of fragments each having fields - extraspace (needed for justified) - fontSize - words=word list - each word is itself a fragment with - various settings - - This structure can be used to easily draw paragraphs with the various alignments. - You can supply either a single width or a list of widths; the latter will have its - last item repeated until necessary. A 2-element list is useful when there is a - different first line indent; a longer list could be created to facilitate custom wraps - around irregular objects.""" - - if type(width) <> ListType: maxWidths = [width] - else: maxWidths = width - lines = [] - lineno = 0 - maxWidth = maxWidths[lineno] - style = self.style - fFontSize = float(style.fontSize) - requiredWidth = 0 - - #for bullets, work out width and ensure we wrap the right amount onto line one - _handleBulletWidth(self.bulletText,style,maxWidths) - - self.height = 0 - frags = self.frags - nFrags= len(frags) - if nFrags==1: - f = frags[0] - if hasattr(f,'text'): - fontSize = f.fontSize - fontName = f.fontName - kind = 0 - L=string.split(f.text, '\n') - for l in L: - currentWidth = stringWidth(l,fontName,fontSize) - requiredWidth = max(currentWidth,requiredWidth) - extraSpace = maxWidth-currentWidth - lines.append((extraSpace,string.split(l,' '),currentWidth)) - lineno = lineno+1 - maxWidth = lineno', ''), - 'keyword' : ('', ''), - 'parameter' : ('', ''), - 'identifier' : ('', ''), - 'string' : ('', '') } - - def __init__(self, text, style, bulletText = None, dedent=0, frags=None): - if text: - text = self.fontify(self.escapeHtml(text)) - apply(XPreformatted.__init__, - (self, text, style), - {'bulletText':bulletText, 'dedent':dedent, 'frags':frags}) - - def escapeHtml(self, text): - s = string.replace(text, '&', '&') - s = string.replace(s, '<', '<') - s = string.replace(s, '>', '>') - return s - - def fontify(self, code): - "Return a fontified version of some Python code." - - if code[0] == '\n': - code = code[1:] - - tags = PyFontify.fontify(code) - fontifiedCode = '' - pos = 0 - for k, i, j, dummy in tags: - fontifiedCode = fontifiedCode + code[pos:i] - s, e = self.formats[k] - fontifiedCode = fontifiedCode + s + code[i:j] + e - pos = j - - fontifiedCode = fontifiedCode + code[pos:] - - return fontifiedCode - - -if __name__=='__main__': #NORUNTESTS - def dumpXPreformattedLines(P): - print '\n############dumpXPreforemattedLines(%s)' % str(P) - lines = P.blPara.lines - n =len(lines) - for l in range(n): - line = lines[l] - words = line.words - nwords = len(words) - print 'line%d: %d(%d)\n ' % (l,nwords,line.wordCount), - for w in range(nwords): - print "%d:'%s'"%(w,words[w].text), - print - - def dumpXPreformattedFrags(P): - print '\n############dumpXPreforemattedFrags(%s)' % str(P) - frags = P.frags - n =len(frags) - for l in range(n): - print "frag%d: '%s'" % (l, frags[l].text) - - l = 0 - for L in _getFragLines(frags): - n=0 - for W in _getFragWords(L): - print "frag%d.%d: size=%d" % (l, n, W[0]), - n = n + 1 - for w in W[1:]: - print "'%s'" % w[1], - print - l = l + 1 - - def try_it(text,style,dedent,aW,aH): - P=XPreformatted(text,style,dedent=dedent) - dumpXPreformattedFrags(P) - w,h = P.wrap(aW, aH) - dumpXPreformattedLines(P) - S = P.split(aW,aH) - dumpXPreformattedLines(P) - for s in S: - s.wrap(aW,aH) - dumpXPreformattedLines(s) - aH = 500 - - from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle - styleSheet = getSampleStyleSheet() - B = styleSheet['BodyText'] - DTstyle = ParagraphStyle("discussiontext", parent=B) - DTstyle.fontName= 'Helvetica' - for (text,dedent,style, aW, aH, active) in [(''' - - -The CMYK or subtractive - -method follows the way a printer -mixes three pigments (cyan, magenta, and yellow) to form colors. -Because mixing chemicals is more difficult than combining light there -is a fourth parameter for darkness. For example a chemical -combination of the CMY pigments generally never makes a perfect - -black -- instead producing a muddy color -- so, to get black printers -don't use the CMY pigments but use a direct black ink. Because -CMYK maps more directly to the way printer hardware works it may -be the case that &| & | colors specified in CMYK will provide better fidelity -and better control when printed. - - -''',0,DTstyle, 456.0, 42.8, 0), -(''' - - This is a non rearranging form of the Paragraph class; - XML tags are allowed in text and have the same - - meanings as for the Paragraph class. - As for Preformatted, if dedent is non zero dedent - common leading spaces will be removed from the - front of each line. - -''',3, DTstyle, 456.0, 42.8, 0), -("""\ - class FastXMLParser: - # Nonsense method - def nonsense(self): - self.foo = 'bar' -""",0, styleSheet['Code'], 456.0, 4.8, 1), -]: - if active: try_it(text,style,dedent,aW,aH) diff --git a/bin/reportlab/rl_config.py b/bin/reportlab/rl_config.py deleted file mode 100644 index 617fa595969..00000000000 --- a/bin/reportlab/rl_config.py +++ /dev/null @@ -1,132 +0,0 @@ -#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/rl_config.py -__version__=''' $Id$ ''' - -allowTableBoundsErrors = 1 # set to 0 to die on too large elements in tables in debug (recommend 1 for production use) -shapeChecking = 1 -defaultEncoding = 'WinAnsiEncoding' # 'WinAnsi' or 'MacRoman' -pageCompression = 1 # default page compression mode -defaultPageSize = 'A4' #default page size -defaultImageCaching = 0 #set to zero to remove those annoying cached images -ZLIB_WARNINGS = 1 -warnOnMissingFontGlyphs = 0 #if 1, warns of each missing glyph -verbose = 0 -showBoundary = 0 # turns on and off boundary behaviour in Drawing -emptyTableAction= 'error' # one of 'error', 'indicate', 'ignore' -invariant= 0 #produces repeatable,identical PDFs with same timestamp info (for regression testing) -eps_preview_transparent= None #set to white etc -overlapAttachedSpace= 1 #if set non false then adajacent flowable space after - #and space before are merged (max space is used). -_FUZZ= 1e-6 #fuzz for layout arithmetic - -# places to look for T1Font information -T1SearchPath = ( - 'c:/Program Files/Adobe/Acrobat 6.0/Resource/Font', #Win32, Acrobat 6 - 'c:/Program Files/Adobe/Acrobat 5.0/Resource/Font', #Win32, Acrobat 5 - 'c:/Program Files/Adobe/Acrobat 4.0/Resource/Font', #Win32, Acrobat 4 - '%(disk)s/Applications/Python %(sys_version)s/reportlab/fonts', #Mac? - '/usr/lib/Acrobat5/Resource/Font', #Linux, Acrobat 5? - '/usr/lib/Acrobat4/Resource/Font', #Linux, Acrobat 4 - '/usr/local/Acrobat6/Resource/Font', #Linux, Acrobat 5? - '/usr/local/Acrobat5/Resource/Font', #Linux, Acrobat 5? - '/usr/local/Acrobat4/Resource/Font', #Linux, Acrobat 4 - '%(REPORTLAB_DIR)s/fonts', #special - '%(REPORTLAB_DIR)s/../fonts', #special - '%(REPORTLAB_DIR)s/../../fonts', #special - '%(HOME)s/fonts', #special - ) - -# places to look for TT Font information -TTFSearchPath = ( - 'c:/winnt/fonts', - 'c:/windows/fonts', - '/usr/lib/X11/fonts/TrueType/', - '%(REPORTLAB_DIR)s/fonts', #special - '%(REPORTLAB_DIR)s/../fonts', #special - '%(REPORTLAB_DIR)s/../../fonts',#special - '%(HOME)s/fonts', #special - ) - -# places to look for CMap files - should ideally merge with above -CMapSearchPath = ('/usr/lib/Acrobat6/Resource/CMap', - '/usr/lib/Acrobat5/Resource/CMap', - '/usr/lib/Acrobat4/Resource/CMap', - '/usr/local/Acrobat6/Resource/CMap', - '/usr/local/Acrobat5/Resource/CMap', - '/usr/local/Acrobat4/Resource/CMap', - 'C:\\Program Files\\Adobe\\Acrobat\\Resource\\CMap', - 'C:\\Program Files\\Adobe\\Acrobat 6.0\\Resource\\CMap', - 'C:\\Program Files\\Adobe\\Acrobat 5.0\\Resource\\CMap', - 'C:\\Program Files\\Adobe\\Acrobat 4.0\\Resource\\CMap' - '%(REPORTLAB_DIR)s/fonts/CMap', #special - '%(REPORTLAB_DIR)s/../fonts/CMap', #special - '%(REPORTLAB_DIR)s/../../fonts/CMap', #special - '%(HOME)s/fonts/CMap', #special - ) - -#### Normally don't need to edit below here #### -try: - from local_rl_config import * -except: - pass - -_SAVED = {} -sys_version=None - -def _setOpt(name, value, conv=None): - '''set a module level value from environ/default''' - from os import environ - ename = 'RL_'+name - if environ.has_key(ename): - value = environ[ename] - if conv: value = conv(value) - globals()[name] = value - -def _startUp(): - '''This function allows easy resetting to the global defaults - If the environment contains 'RL_xxx' then we use the value - else we use the given default''' - V = ('T1SearchPath','CMapSearchPath', 'TTFSearchPath', - 'shapeChecking', 'defaultEncoding', - 'pageCompression', 'defaultPageSize', 'defaultImageCaching', - 'ZLIB_WARNINGS', 'warnOnMissingFontGlyphs', 'verbose', 'emptyTableAction', - 'invariant','eps_preview_transparent', - ) - import os, sys, string - global sys_version, _unset_ - sys_version = string.split(sys.version)[0] #strip off the other garbage - from reportlab.lib import pagesizes - from reportlab.lib.utils import rl_isdir - - if _SAVED=={}: - _unset_ = getattr(sys,'_rl_config__unset_',None) - if _unset_ is None: - class _unset_: pass - sys._rl_config__unset_ = _unset_ = _unset_() - for k in V: - _SAVED[k] = globals()[k] - - #places to search for Type 1 Font files - import reportlab - D = {'REPORTLAB_DIR': os.path.abspath(os.path.dirname(reportlab.__file__)), - 'HOME': os.environ.get('HOME',os.getcwd()), - 'disk': string.split(os.getcwd(), ':')[0], - 'sys_version': sys_version, - } - - for name in ('T1SearchPath','TTFSearchPath','CMapSearchPath'): - P=[] - for p in _SAVED[name]: - d = string.replace(p % D,'/',os.sep) - if rl_isdir(d): P.append(d) - _setOpt(name,P) - - for k in V[3:]: - v = _SAVED[k] - if type(v)==type(1): conv = int - elif k=='defaultPageSize': conv = lambda v,M=pagesizes: getattr(M,v) - else: conv = None - _setOpt(k,v,conv) - -_startUp() diff --git a/bin/reportlab/tools/README b/bin/reportlab/tools/README deleted file mode 100644 index f5f75d08de3..00000000000 --- a/bin/reportlab/tools/README +++ /dev/null @@ -1,10 +0,0 @@ -This directory is the home of various ReportLab tools. -They are packaged such that they can be used more easily. - -Tool candidates are: - - - PythonPoint - - docpy.py - - graphdocpy.py - - ... - diff --git a/bin/reportlab/tools/__init__.py b/bin/reportlab/tools/__init__.py deleted file mode 100644 index da52b52d322..00000000000 --- a/bin/reportlab/tools/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/__init__.py diff --git a/bin/reportlab/tools/docco/README b/bin/reportlab/tools/docco/README deleted file mode 100644 index b23b5661348..00000000000 --- a/bin/reportlab/tools/docco/README +++ /dev/null @@ -1,8 +0,0 @@ -This directory contains a number of tools to do with documentation and -documentation building. - -Some of these are our own internal tools which we use for building the -ReportLab documentation. Some tools are obsolete and will be removed -in due course. - -In the mean time, use at your own risk. diff --git a/bin/reportlab/tools/docco/__init__.py b/bin/reportlab/tools/docco/__init__.py deleted file mode 100644 index 3a0b73da24d..00000000000 --- a/bin/reportlab/tools/docco/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/__init__.py diff --git a/bin/reportlab/tools/docco/codegrab.py b/bin/reportlab/tools/docco/codegrab.py deleted file mode 100644 index 87a580a2a3e..00000000000 --- a/bin/reportlab/tools/docco/codegrab.py +++ /dev/null @@ -1,228 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/codegrab.py -#codegrab.py -""" -This grabs various Python class, method and function -headers and their doc strings to include in documents -""" - -import imp -import types -import string -import os -import sys - -class Struct: - pass - -def getObjectsDefinedIn(modulename, directory=None): - """Returns two tuple of (functions, classes) defined - in the given module. 'directory' must be the directory - containing the script; modulename should not include - the .py suffix""" - - if directory: - searchpath = [directory] - else: - searchpath = sys.path # searches usual Python path - - #might be a package. If so, check the top level - #package is there, then recalculate the path needed - words = string.split(modulename, '.') - if len(words) > 1: - packagename = words[0] - packagefound = imp.find_module(packagename, searchpath) - assert packagefound, "Package %s not found" % packagename - (file, packagepath, description) = packagefound - #now the full path should be known, if it is in the - #package - - directory = apply(os.path.join, tuple([packagepath] + words[1:-1])) - modulename = words[-1] - searchpath = [directory] - - - - #find and import the module. - found = imp.find_module(modulename, searchpath) - assert found, "Module %s not found" % modulename - (file, pathname, description) = found - mod = imp.load_module(modulename, file, pathname, description) - - #grab the code too, minus trailing newlines - lines = open(pathname, 'r').readlines() - lines = map(string.rstrip, lines) - - result = Struct() - result.functions = [] - result.classes = [] - result.doc = mod.__doc__ - for name in dir(mod): - value = getattr(mod, name) - if type(value) is types.FunctionType: - path, file = os.path.split(value.func_code.co_filename) - root, ext = os.path.splitext(file) - #we're possibly interested in it - if root == modulename: - #it was defined here - funcObj = value - fn = Struct() - fn.name = name - fn.proto = getFunctionPrototype(funcObj, lines) - if funcObj.__doc__: - fn.doc = dedent(funcObj.__doc__) - else: - fn.doc = '(no documentation string)' - #is it official? - if name[0:1] == '_': - fn.status = 'private' - elif name[-1] in '0123456789': - fn.status = 'experimental' - else: - fn.status = 'official' - - result.functions.append(fn) - elif type(value) == types.ClassType: - if value.__module__ == modulename: - cl = Struct() - cl.name = name - if value.__doc__: - cl.doc = dedent(value.__doc__) - else: - cl.doc = "(no documentation string)" - - cl.bases = [] - for base in value.__bases__: - cl.bases.append(base.__name__) - if name[0:1] == '_': - cl.status = 'private' - elif name[-1] in '0123456789': - cl.status = 'experimental' - else: - cl.status = 'official' - - cl.methods = [] - #loop over dict finding methods defined here - # Q - should we show all methods? - # loop over dict finding methods defined here - items = value.__dict__.items() - items.sort() - for (key2, value2) in items: - if type(value2) <> types.FunctionType: - continue # not a method - elif os.path.splitext(value2.func_code.co_filename)[0] == modulename: - continue # defined in base class - else: - #we want it - meth = Struct() - meth.name = key2 - name2 = value2.func_code.co_name - meth.proto = getFunctionPrototype(value2, lines) - if name2!=key2: - meth.doc = 'pointer to '+name2 - meth.proto = string.replace(meth.proto,name2,key2) - else: - if value2.__doc__: - meth.doc = dedent(value2.__doc__) - else: - meth.doc = "(no documentation string)" - #is it official? - if key2[0:1] == '_': - meth.status = 'private' - elif key2[-1] in '0123456789': - meth.status = 'experimental' - else: - meth.status = 'official' - cl.methods.append(meth) - result.classes.append(cl) - return result - -def getFunctionPrototype(f, lines): - """Pass in the function object and list of lines; - it extracts the header as a multiline text block.""" - firstLineNo = f.func_code.co_firstlineno - 1 - lineNo = firstLineNo - brackets = 0 - while 1: - line = lines[lineNo] - for char in line: - if char == '(': - brackets = brackets + 1 - elif char == ')': - brackets = brackets - 1 - if brackets == 0: - break - else: - lineNo = lineNo + 1 - - usefulLines = lines[firstLineNo:lineNo+1] - return string.join(usefulLines, '\n') - - -def dedent(comment): - """Attempts to dedent the lines to the edge. Looks at no. - of leading spaces in line 2, and removes up to that number - of blanks from other lines.""" - commentLines = string.split(comment, '\n') - if len(commentLines) < 2: - cleaned = map(string.lstrip, commentLines) - else: - spc = 0 - for char in commentLines[1]: - if char in string.whitespace: - spc = spc + 1 - else: - break - #now check other lines - cleaned = [] - for line in commentLines: - for i in range(min(len(line),spc)): - if line[0] in string.whitespace: - line = line[1:] - cleaned.append(line) - return string.join(cleaned, '\n') - - - -def dumpDoc(modulename, directory=None): - """Test support. Just prints docco on the module - to standard output.""" - docco = getObjectsDefinedIn(modulename, directory) - print 'codegrab.py - ReportLab Documentation Utility' - print 'documenting', modulename + '.py' - print '-------------------------------------------------------' - print - if docco.functions == []: - print 'No functions found' - else: - print 'Functions:' - for f in docco.functions: - print f.proto - print ' ' + f.doc - - if docco.classes == []: - print 'No classes found' - else: - print 'Classes:' - for c in docco.classes: - print c.name - print ' ' + c.doc - for m in c.methods: - print m.proto # it is already indented in the file! - print ' ' + m.doc - print - -def test(m='reportlab.platypus.paragraph'): - dumpDoc(m) - -if __name__=='__main__': - import sys - print 'Path to search:' - for line in sys.path: - print ' ',line - M = sys.argv[1:] - if M==[]: - M.append('reportlab.platypus.paragraph') - for m in M: - test(m) \ No newline at end of file diff --git a/bin/reportlab/tools/docco/docpy.py b/bin/reportlab/tools/docco/docpy.py deleted file mode 100755 index 8d0e78300ea..00000000000 --- a/bin/reportlab/tools/docco/docpy.py +++ /dev/null @@ -1,1247 +0,0 @@ -#! /usr/bin/python2.3 -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/docpy.py - -"""Generate documentation from live Python objects. - -This is an evolving module that allows to generate documentation -for python modules in an automated fashion. The idea is to take -live Python objects and inspect them in order to use as much mean- -ingful information as possible to write in some formatted way into -different types of documents. - -In principle a skeleton captures the gathered information and -makes it available via a certain API to formatters that use it -in whatever way they like to produce something of interest. The -API allows for adding behaviour in subclasses of these formatters, -such that, e.g. for certain classes it is possible to trigger -special actions like displaying a sample image of a class that -represents some graphical widget, say. - -Type the following for usage info: - - python docpy.py -h -""" - -# Much inspired by Ka-Ping Yee's htmldoc.py. -# Needs the inspect module. - -# Dinu Gherman - - -__version__ = '0.8' - - -import sys, os, re, types, string, getopt, copy, time -from string import find, join, split, replace, expandtabs, rstrip - -from reportlab.pdfgen import canvas -from reportlab.lib import colors -from reportlab.lib.units import inch, cm -from reportlab.lib.pagesizes import A4 -from reportlab.lib import enums -from reportlab.lib.enums import TA_CENTER, TA_LEFT -from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle -from reportlab.platypus.flowables import Flowable, Spacer -from reportlab.platypus.paragraph import Paragraph -from reportlab.platypus.flowables \ - import Flowable, Preformatted,Spacer, Image, KeepTogether, PageBreak -from reportlab.platypus.tableofcontents import TableOfContents -from reportlab.platypus.xpreformatted import XPreformatted -from reportlab.platypus.frames import Frame -from reportlab.platypus.doctemplate \ - import PageTemplate, BaseDocTemplate -from reportlab.platypus.tables import TableStyle, Table -import inspect - -#################################################################### -# -# Stuff needed for building PDF docs. -# -#################################################################### - - -def mainPageFrame(canvas, doc): - "The page frame used for all PDF documents." - - canvas.saveState() - - pageNumber = canvas.getPageNumber() - canvas.line(2*cm, A4[1]-2*cm, A4[0]-2*cm, A4[1]-2*cm) - canvas.line(2*cm, 2*cm, A4[0]-2*cm, 2*cm) - if pageNumber > 1: - canvas.setFont('Times-Roman', 12) - canvas.drawString(4 * inch, cm, "%d" % pageNumber) - if hasattr(canvas, 'headerLine'): # hackish - headerline = string.join(canvas.headerLine, ' \215 ') # bullet - canvas.drawString(2*cm, A4[1]-1.75*cm, headerline) - - canvas.setFont('Times-Roman', 8) - msg = "Generated with reportlab.lib.docpy. See http://www.reportlab.com!" - canvas.drawString(2*cm, 1.65*cm, msg) - - canvas.restoreState() - - -class MyTemplate(BaseDocTemplate): - "The document template used for all PDF documents." - - _invalidInitArgs = ('pageTemplates',) - - def __init__(self, filename, **kw): - frame1 = Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1') - self.allowSplitting = 0 - apply(BaseDocTemplate.__init__, (self, filename), kw) - self.addPageTemplates(PageTemplate('normal', [frame1], mainPageFrame)) - - - def afterFlowable(self, flowable): - "Takes care of header line, TOC and outline entries." - - if flowable.__class__.__name__ == 'Paragraph': - f = flowable - name7 = f.style.name[:7] - name8 = f.style.name[:8] - - # Build a list of heading parts. - # So far, this is the *last* item on the *previous* page... - if name7 == 'Heading' and not hasattr(self.canv, 'headerLine'): - self.canv.headerLine = [] - - if name8 == 'Heading0': - self.canv.headerLine = [f.text] # hackish - elif name8 == 'Heading1': - if len(self.canv.headerLine) == 2: - del self.canv.headerLine[-1] - elif len(self.canv.headerLine) == 3: - del self.canv.headerLine[-1] - del self.canv.headerLine[-1] - self.canv.headerLine.append(f.text) - elif name8 == 'Heading2': - if len(self.canv.headerLine) == 3: - del self.canv.headerLine[-1] - self.canv.headerLine.append(f.text) - - if name7 == 'Heading': - # Register TOC entries. - headLevel = int(f.style.name[7:]) - self.notify('TOCEntry', (headLevel, flowable.getPlainText(), self.page)) - - # Add PDF outline entries. - c = self.canv - title = f.text - key = str(hash(f)) - - try: - if headLevel == 0: - isClosed = 0 - else: - isClosed = 1 - - c.bookmarkPage(key) - c.addOutlineEntry(title, key, level=headLevel, - closed=isClosed) - except ValueError: - pass - - -#################################################################### -# -# Utility functions (Ka-Ping Yee). -# -#################################################################### - -def htmlescape(text): - "Escape special HTML characters, namely &, <, >." - return replace(replace(replace(text, '&', '&'), - '<', '<'), - '>', '>') - -def htmlrepr(object): - return htmlescape(repr(object)) - - -def defaultformat(object): - return '=' + htmlrepr(object) - - -def getdoc(object): - result = inspect.getdoc(object) - if not result: - try: - result = inspect.getcomments(object) - except: - pass - return result and rstrip(result) + '\n' or '' - - -def reduceDocStringLength(docStr): - "Return first line of a multiline string." - - return split(docStr, '\n')[0] - - -#################################################################### -# -# More utility functions -# -#################################################################### - -def makeHtmlSection(text, bgcolor='#FFA0FF'): - """Create HTML code for a section. - - This is usually a header for all classes or functions. -u """ - text = htmlescape(expandtabs(text)) - result = [] - result.append("""""") - result.append("""
              """ % bgcolor) - result.append("""

              %s

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

              %s

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

              %s

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

              %s

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

              Method Interface

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

              %s

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

              Classes

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

              Example

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

              Demo

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

              Demo

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

              Example Code

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

              Properties of Example Widget

              ') - self.outLines.append('
              %s
              ' % text) - self.outLines.append('') - - -# Highly experimental! -class PlatypusDocBuilder0(DocBuilder0): - "Document the skeleton of a Python module as a Platypus story." - - fileSuffix = '.pps' # A pickled Platypus story. - - def begin(self, name='', typ=''): - styleSheet = getSampleStyleSheet() - self.code = styleSheet['Code'] - self.bt = styleSheet['BodyText'] - self.story = [] - - - def end(self): - if self.packageName: - self.outPath = self.packageName + self.fileSuffix - elif self.skeleton: - self.outPath = self.skeleton.getModuleName() + self.fileSuffix - else: - self.outPath = '' - - if self.outPath: - f = open(self.outPath, 'w') - pickle.dump(self.story, f) - - - def beginPackage(self, name): - DocBuilder0.beginPackage(self, name) - self.story.append(Paragraph(name, self.bt)) - - - def beginModule(self, name, doc, imported): - story = self.story - bt = self.bt - - story.append(Paragraph(name, bt)) - story.append(XPreformatted(doc, bt)) - - - def beginClasses(self, names): - self.story.append(Paragraph('Classes', self.bt)) - - - def beginClass(self, name, doc, bases): - bt = self.bt - story = self.story - if bases: - bases = map(lambda b:b.__name__, bases) # hack - story.append(Paragraph('%s(%s)' % (name, join(bases, ', ')), bt)) - else: - story.append(Paragraph(name, bt)) - - story.append(XPreformatted(doc, bt)) - - - def beginMethod(self, name, doc, sig): - bt = self.bt - story = self.story - story.append(Paragraph(name+sig, bt)) - story.append(XPreformatted(doc, bt)) - - - def beginFunctions(self, names): - if names: - self.story.append(Paragraph('Functions', self.bt)) - - - def beginFunction(self, name, doc, sig): - bt = self.bt - story = self.story - story.append(Paragraph(name+sig, bt)) - story.append(XPreformatted(doc, bt)) - - -#################################################################### -# -# Main -# -#################################################################### - -def printUsage(): - """graphdocpy.py - Automated documentation for the RL Graphics library. - -Usage: python graphdocpy.py [options] - - [options] - -h Print this help message. - - -f name Use the document builder indicated by 'name', - e.g. Html, Pdf. - - -m module Generate document for module named 'module'. - 'module' may follow any of these forms: - - docpy.py - - docpy - - c:\\test\\docpy - and can be any of these: - - standard Python modules - - modules in the Python search path - - modules in the current directory - - -p package Generate document for package named 'package' - (default is 'reportlab.graphics'). - 'package' may follow any of these forms: - - reportlab - - reportlab.graphics.charts - - c:\\test\\reportlab - and can be any of these: - - standard Python packages (?) - - packages in the Python search path - - packages in the current directory - - -s Silent mode (default is unset). - -Examples: - - python graphdocpy.py reportlab.graphics - python graphdocpy.py -m signsandsymbols.py -f Pdf - python graphdocpy.py -m flags.py -f Html - python graphdocpy.py -m barchart1.py -""" - - -# The following functions, including main(), are actually -# the same as in docpy.py (except for some defaults). - -def documentModule0(pathOrName, builder, opts={}): - """Generate documentation for one Python file in some format. - - This handles Python standard modules like string, custom modules - on the Python search path like e.g. docpy as well as modules - specified with their full path like C:/tmp/junk.py. - - The doc file will always be saved in the current directory with - a basename equal to that of the module, e.g. docpy. - """ - cwd = os.getcwd() - - # Append directory to Python search path if we get one. - dirName = os.path.dirname(pathOrName) - if dirName: - sys.path.append(dirName) - - # Remove .py extension from module name. - if pathOrName[-3:] == '.py': - modname = pathOrName[:-3] - else: - modname = pathOrName - - # Remove directory paths from module name. - if dirName: - modname = os.path.basename(modname) - - # Load the module. - try: - module = __import__(modname) - except: - print 'Failed to import %s.' % modname - os.chdir(cwd) - return - - # Do the real documentation work. - s = ModuleSkeleton0() - s.inspect(module) - builder.write(s) - - # Remove appended directory from Python search path if we got one. - if dirName: - del sys.path[-1] - - os.chdir(cwd) - - -def _packageWalkCallback((builder, opts), dirPath, files): - "A callback function used when waking over a package tree." - #must CD into a directory to document the module correctly - cwd = os.getcwd() - os.chdir(dirPath) - - - # Skip __init__ files. - files = filter(lambda f:f != '__init__.py', files) - - files = filter(lambda f:f[-3:] == '.py', files) - for f in files: - path = os.path.join(dirPath, f) -## if not opts.get('isSilent', 0): -## print path - builder.indentLevel = builder.indentLevel + 1 - #documentModule0(path, builder) - documentModule0(f, builder) - builder.indentLevel = builder.indentLevel - 1 - #CD back out - os.chdir(cwd) - -def documentPackage0(pathOrName, builder, opts={}): - """Generate documentation for one Python package in some format. - - 'pathOrName' can be either a filesystem path leading to a Python - package or package name whose path will be resolved by importing - the top-level module. - - The doc file will always be saved in the current directory with - a basename equal to that of the package, e.g. reportlab.lib. - """ - - # Did we get a package path with OS-dependant seperators...? - if os.sep in pathOrName: - path = pathOrName - name = os.path.splitext(os.path.basename(path))[0] - # ... or rather a package name? - else: - name = pathOrName - package = __import__(name) - # Some special care needed for dotted names. - if '.' in name: - subname = 'package' + name[find(name, '.'):] - package = eval(subname) - path = os.path.dirname(package.__file__) - - cwd = os.getcwd() - os.chdir(path) - builder.beginPackage(name) - os.path.walk(path, _packageWalkCallback, (builder, opts)) - builder.endPackage(name) - os.chdir(cwd) - - -def makeGraphicsReference(outfilename): - "Make graphics_reference.pdf" - builder = GraphPdfDocBuilder0() - - builder.begin(name='reportlab.graphics', typ='package') - documentPackage0('reportlab.graphics', builder, {'isSilent': 0}) - builder.end(outfilename) - print 'made graphics reference in %s' % outfilename - -def main(): - "Handle command-line options and trigger corresponding action." - - opts, args = getopt.getopt(sys.argv[1:], 'hsf:m:p:') - - # Make an options dictionary that is easier to use. - optsDict = {} - for k, v in opts: - optsDict[k] = v - hasOpt = optsDict.has_key - - # On -h print usage and exit immediately. - if hasOpt('-h'): - print printUsage.__doc__ - sys.exit(0) - - # On -s set silent mode. - isSilent = hasOpt('-s') - - # On -f set the appropriate DocBuilder to use or a default one. - builder = { 'Pdf': GraphPdfDocBuilder0, - 'Html': GraphHtmlDocBuilder0, - }[optsDict.get('-f', 'Pdf')]() - - # Set default module or package to document. - if not hasOpt('-p') and not hasOpt('-m'): - optsDict['-p'] = 'reportlab.graphics' - - # Save a few options for further use. - options = {'isSilent':isSilent} - - # Now call the real documentation functions. - if hasOpt('-m'): - nameOrPath = optsDict['-m'] - if not isSilent: - print "Generating documentation for module %s..." % nameOrPath - builder.begin(name=nameOrPath, typ='module') - documentModule0(nameOrPath, builder, options) - elif hasOpt('-p'): - nameOrPath = optsDict['-p'] - if not isSilent: - print "Generating documentation for package %s..." % nameOrPath - builder.begin(name=nameOrPath, typ='package') - documentPackage0(nameOrPath, builder, options) - builder.end() - - if not isSilent: - print "Saved %s." % builder.outPath - - #if doing the usual, put a copy in docs - if builder.outPath == 'reportlab.graphics.pdf': - import shutil, reportlab - dst = os.path.join(os.path.dirname(reportlab.__file__),'docs','graphics_reference.pdf') - shutil.copyfile('reportlab.graphics.pdf', dst) - if not isSilent: - print 'copied to '+dst - -def makeSuite(): - "standard test harness support - run self as separate process" - from reportlab.test.utils import ScriptThatMakesFileTest - return ScriptThatMakesFileTest('tools/docco', - 'graphdocpy.py', - 'reportlab.graphics.pdf') - -if __name__ == '__main__': - main() diff --git a/bin/reportlab/tools/docco/rl_doc_utils.py b/bin/reportlab/tools/docco/rl_doc_utils.py deleted file mode 100755 index c3e107642e1..00000000000 --- a/bin/reportlab/tools/docco/rl_doc_utils.py +++ /dev/null @@ -1,411 +0,0 @@ -#! /usr/bin/python2.3 -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/rl_doc_utils.py -__version__=''' $Id$ ''' - - -__doc__ = """ -This module contains utilities for generating guides -""" - -import os, sys, glob -import string - -from rltemplate import RLDocTemplate -from stylesheet import getStyleSheet -styleSheet = getStyleSheet() - -#from reportlab.platypus.doctemplate import SimpleDocTemplate -from reportlab.lib.units import inch -from reportlab.lib.pagesizes import letter, A4, A5, A3 # latter two for testing -from reportlab.rl_config import defaultPageSize -from reportlab.platypus import figures -from reportlab.platypus import Paragraph, Spacer, Preformatted,\ - PageBreak, CondPageBreak, Flowable, Table, TableStyle, \ - NextPageTemplate, KeepTogether, Image, XPreformatted -from reportlab.lib.styles import ParagraphStyle -from reportlab.lib import colors -from reportlab.lib.sequencer import getSequencer - -import examples - -appmode=0 - - -from t_parse import Template -QFcodetemplate = Template("X$X$", "X") -QFreptemplate = Template("X^X^", "X") -codesubst = "%s%s" -QFsubst = "%s%s" - - -def quickfix(text): - """inside text find any subsequence of form $subsequence$. - Format the subsequence as code. If similarly if text contains ^arg^ - format the arg as replaceable. The escape sequence for literal - $ is $\\$ (^ is ^\\^. - """ - from string import join - for (template,subst) in [(QFcodetemplate, codesubst), (QFreptemplate, QFsubst)]: - fragment = text - parts = [] - try: - while fragment: - try: - (matches, index) = template.PARSE(fragment) - except: raise ValueError - else: - [prefix, code] = matches - if code == "\\": - part = fragment[:index] - else: - part = subst % (prefix, code) - parts.append(part) - fragment = fragment[index:] - except ValueError: - parts.append(fragment) - text = join(parts, "") - return text -#print quickfix("$testing$ testing $one$ ^two^ $three(^four^)$") - - - -H1 = styleSheet['Heading1'] -H2 = styleSheet['Heading2'] -H3 = styleSheet['Heading3'] -H4 = styleSheet['Heading4'] -B = styleSheet['BodyText'] -BU = styleSheet['Bullet'] -Comment = styleSheet['Comment'] -Centred = styleSheet['Centred'] -Caption = styleSheet['Caption'] - -#set up numbering -seq = getSequencer() -seq.setFormat('Chapter','1') -seq.setFormat('Section','1') -seq.setFormat('Appendix','A') -seq.setFormat('Figure', '1') -seq.chain('Chapter','Section') -seq.chain('Chapter','Figure') - -lessonnamestyle = H2 -discussiontextstyle = B -exampletextstyle = styleSheet['Code'] -# size for every example -examplefunctionxinches = 5.5 -examplefunctionyinches = 3 -examplefunctiondisplaysizes = (examplefunctionxinches*inch, examplefunctionyinches*inch) - -def getJustFontPaths(): - '''return afm and pfb for Just's files''' - import reportlab - folder = os.path.dirname(reportlab.__file__) + os.sep + 'fonts' - return os.path.join(folder, 'Wargames.afm'), os.path.join(folder, 'Wargames.pfb') - -# for testing -def NOP(*x,**y): - return None - -def CPage(inches): - getStory().append(CondPageBreak(inches*inch)) - -def newPage(): - getStory().append(PageBreak()) - -def nextTemplate(templName): - f = NextPageTemplate(templName) - getStory().append(f) - -def disc(text, klass=Paragraph, style=discussiontextstyle): - text = quickfix(text) - P = klass(text, style) - getStory().append(P) - -def restartList(): - getSequencer().reset('list1') - -def list(text, doBullet=1): - text=quickfix(text) - if doBullet: - text='.'+text - P = Paragraph(text, BU) - getStory().append(P) - -def bullet(text): - text=''+chr(183)+'' + quickfix(text) - P = Paragraph(text, BU) - getStory().append(P) - -def eg(text,before=0.1,after=0): - space(before) - disc(text, klass=Preformatted, style=exampletextstyle) - space(after) - -def space(inches=1./6): - if inches: getStory().append(Spacer(0,inches*inch)) - -def EmbeddedCode(code,name='t'): - eg(code) - disc("produces") - exec code+("\ngetStory().append(%s)\n"%name) - -def startKeep(): - return len(getStory()) - -def endKeep(s): - S = getStory() - k = KeepTogether(S[s:]) - S[s:] = [k] - -def title(text): - """Use this for the document title only""" - disc(text,style=styleSheet['Title']) - -#AR 3/7/2000 - defining three new levels of headings; code -#should be swapped over to using them. - -def heading1(text): - """Use this for chapters. Lessons within a big chapter - should now use heading2 instead. Chapters get numbered.""" - getStory().append(PageBreak()) - p = Paragraph('Chapter ' + quickfix(text), H1) - getStory().append(p) - -def Appendix1(text,): - global appmode - getStory().append(PageBreak()) - if not appmode: - seq.setFormat('Chapter','A') - seq.reset('Chapter') - appmode = 1 - p = Paragraph('Appendix ' + quickfix(text), H1) - getStory().append(p) - -def heading2(text): - """Used to be 'lesson'""" - getStory().append(CondPageBreak(inch)) - p = Paragraph('' + quickfix(text), H2) - getStory().append(p) - -def heading3(text): - """Used to be most of the plain old 'head' sections""" - getStory().append(CondPageBreak(inch)) - p = Paragraph(quickfix(text), H3) - getStory().append(p) - -def image(path, width=None, height=None ): - s = startKeep() - space(.2) - import reportlab - rlDocImageDir = os.path.join(os.path.dirname(reportlab.__file__), 'docs','images') - getStory().append(Image(os.path.join(rlDocImageDir,path),width,height)) - space(.2) - endKeep(s) - -def heading4(text): - """Used to be most of the plain old 'head' sections""" - getStory().append(CondPageBreak(inch)) - p = Paragraph(quickfix(text), H4) - getStory().append(p) - -def todo(text): - """Used for notes to ourselves""" - getStory().append(Paragraph(quickfix(text), Comment)) - -def centred(text): - getStory().append(Paragraph(quickfix(text), Centred)) - -def caption(text): - getStory().append(Paragraph(quickfix(text), Caption)) - -class Illustration(figures.Figure): - """The examples are all presented as functions which do - something to a canvas, with a constant height and width - used. This puts them inside a figure box with a caption.""" - - def __init__(self, operation, caption, width=None, height=None): - stdwidth, stdheight = examplefunctiondisplaysizes - if not width: - width = stdwidth - if not height: - height = stdheight - #figures.Figure.__init__(self, stdwidth * 0.75, stdheight * 0.75) - figures.Figure.__init__(self, width, height, - 'Figure : ' + quickfix(caption)) - self.operation = operation - - def drawFigure(self): - #shrink it a little... - #self.canv.scale(0.75, 0.75) - self.operation(self.canv) - - -def illust(operation, caption, width=None, height=None): - i = Illustration(operation, caption, width=width, height=height) - getStory().append(i) - - -class GraphicsDrawing(Illustration): - """Lets you include reportlab/graphics drawings seamlessly, - with the right numbering.""" - def __init__(self, drawing, caption): - figures.Figure.__init__(self, - drawing.width, - drawing.height, - 'Figure : ' + quickfix(caption) - ) - self.drawing = drawing - - def drawFigure(self): - d = self.drawing - d.wrap(d.width, d.height) - d.drawOn(self.canv, 0, 0) - -def draw(drawing, caption): - d = GraphicsDrawing(drawing, caption) - getStory().append(d) - -class ParaBox(figures.Figure): - """Illustrates paragraph examples, with style attributes on the left""" - descrStyle = ParagraphStyle('description', - fontName='Courier', - fontSize=8, - leading=9.6) - - def __init__(self, text, style, caption): - figures.Figure.__init__(self, 0, 0, caption) - self.text = text - self.style = style - self.para = Paragraph(text, style) - - styleText = self.getStyleText(style) - self.pre = Preformatted(styleText, self.descrStyle) - - def wrap(self, availWidth, availHeight): - """Left 30% is for attributes, right 50% for sample, - 10% gutter each side.""" - self.x0 = availWidth * 0.05 #left of box - self.x1 = availWidth * 0.1 #left of descriptive text - self.x2 = availWidth * 0.5 #left of para itself - self.x3 = availWidth * 0.9 #right of para itself - self.x4 = availWidth * 0.95 #right of box - self.width = self.x4 - self.x0 - self.dx = 0.5 * (availWidth - self.width) - - paw, self.pah = self.para.wrap(self.x3 - self.x2, availHeight) - self.pah = self.pah + self.style.spaceBefore + self.style.spaceAfter - prw, self.prh = self.pre.wrap(self.x2 - self.x1, availHeight) - self.figureHeight = max(self.prh, self.pah) * 10.0/9.0 - return figures.Figure.wrap(self, availWidth, availHeight) - - def getStyleText(self, style): - """Converts style to preformatted block of text""" - lines = [] - for (key, value) in style.__dict__.items(): - lines.append('%s = %s' % (key, value)) - lines.sort() - return string.join(lines, '\n') - - def drawFigure(self): - - #now we fill in the bounding box and before/after boxes - self.canv.saveState() - self.canv.setFillGray(0.95) - self.canv.setDash(1,3) - self.canv.rect(self.x2 - self.x0, - self.figureHeight * 0.95 - self.pah, - self.x3-self.x2, self.para.height, - fill=1,stroke=1) - - self.canv.setFillGray(0.90) - self.canv.rect(self.x2 - self.x0, #spaceBefore - self.figureHeight * 0.95 - self.pah + self.para.height, - self.x3-self.x2, self.style.spaceBefore, - fill=1,stroke=1) - - self.canv.rect(self.x2 - self.x0, #spaceBefore - self.figureHeight * 0.95 - self.pah - self.style.spaceAfter, - self.x3-self.x2, self.style.spaceAfter, - fill=1,stroke=1) - - self.canv.restoreState() - #self.canv.setFillColor(colors.yellow) - self.para.drawOn(self.canv, self.x2 - self.x0, - self.figureHeight * 0.95 - self.pah) - self.pre.drawOn(self.canv, self.x1 - self.x0, - self.figureHeight * 0.95 - self.prh) - - - def getStyleText(self, style): - """Converts style to preformatted block of text""" - lines = [] - for (key, value) in style.__dict__.items(): - if key not in ('name','parent'): - lines.append('%s = %s' % (key, value)) - return string.join(lines, '\n') - - -class ParaBox2(figures.Figure): - """Illustrates a paragraph side-by-side with the raw - text, to show how the XML works.""" - def __init__(self, text, caption): - figures.Figure.__init__(self, 0, 0, caption) - descrStyle = ParagraphStyle('description', - fontName='Courier', - fontSize=8, - leading=9.6) - textStyle = B - self.text = text - self.left = Paragraph('', descrStyle) - self.right = Paragraph(text, B) - - - def wrap(self, availWidth, availHeight): - self.width = availWidth * 0.9 - colWidth = 0.4 * self.width - lw, self.lh = self.left.wrap(colWidth, availHeight) - rw, self.rh = self.right.wrap(colWidth, availHeight) - self.figureHeight = max(self.lh, self.rh) * 10.0/9.0 - return figures.Figure.wrap(self, availWidth, availHeight) - - def drawFigure(self): - self.left.drawOn(self.canv, - self.width * 0.05, - self.figureHeight * 0.95 - self.lh - ) - self.right.drawOn(self.canv, - self.width * 0.55, - self.figureHeight * 0.95 - self.rh - ) - -def parabox(text, style, caption): - p = ParaBox(text, style, - 'Figure : ' + quickfix(caption) - ) - getStory().append(p) - -def parabox2(text, caption): - p = ParaBox2(text, - 'Figure : ' + quickfix(caption) - ) - getStory().append(p) - -def pencilnote(): - getStory().append(examples.NoteAnnotation()) - - -from reportlab.lib.colors import tan, green -def handnote(xoffset=0, size=None, fillcolor=tan, strokecolor=green): - getStory().append(examples.HandAnnotation(xoffset,size,fillcolor,strokecolor)) - - -#make a singleton, created when requested rather -#than each time a chapter imports it. -_story = [] -def setStory(story=[]): - global _story - _story = story -def getStory(): - return _story diff --git a/bin/reportlab/tools/docco/rltemplate.py b/bin/reportlab/tools/docco/rltemplate.py deleted file mode 100644 index 2d71c26d6ba..00000000000 --- a/bin/reportlab/tools/docco/rltemplate.py +++ /dev/null @@ -1,140 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/rltemplate.py -# doc template for RL manuals. Currently YAML is hard-coded -#to use this, which is wrong. - - -from reportlab.platypus import PageTemplate, \ - BaseDocTemplate, Frame, Paragraph -from reportlab.lib.units import inch, cm -from reportlab.rl_config import defaultPageSize - - -class FrontCoverTemplate(PageTemplate): - def __init__(self, id, pageSize=defaultPageSize): - self.pageWidth = pageSize[0] - self.pageHeight = pageSize[1] - frame1 = Frame(inch, - 3*inch, - self.pageWidth - 2*inch, - self.pageHeight - 518, id='cover') - PageTemplate.__init__(self, id, [frame1]) # note lack of onPage - - def afterDrawPage(self, canvas, doc): - canvas.saveState() - canvas.drawImage('../images/replogo.gif',2*inch, 8*inch) - - - canvas.setFont('Times-Roman', 10) - canvas.line(inch, 120, self.pageWidth - inch, 120) - - canvas.drawString(inch, 100, 'Lombard Business Park') - canvas.drawString(inch, 88, '8 Lombard Road') - canvas.drawString(inch, 76, 'Wimbledon') - canvas.drawString(inch, 64, 'London, ENGLAND SW19 3TZ') - - canvas.drawRightString(self.pageWidth - inch, 100, '103 Bayard Street') - canvas.drawRightString(self.pageWidth - inch, 88, 'New Brunswick') - canvas.drawRightString(self.pageWidth - inch, 76, 'New Jersey, 08904)') - canvas.drawRightString(self.pageWidth - inch, 64, 'USA') - - canvas.restoreState() - - -class OneColumnTemplate(PageTemplate): - def __init__(self, id, pageSize=defaultPageSize): - self.pageWidth = pageSize[0] - self.pageHeight = pageSize[1] - frame1 = Frame(inch, - inch, - self.pageWidth - 2*inch, - self.pageHeight - 2*inch, - id='normal') - PageTemplate.__init__(self, id, [frame1]) # note lack of onPage - - def afterDrawPage(self, canvas, doc): - y = self.pageHeight - 50 - canvas.saveState() - canvas.setFont('Times-Roman', 10) - canvas.drawString(inch, y+8, doc.title) - canvas.drawRightString(self.pageWidth - inch, y+8, doc.chapter) - canvas.line(inch, y, self.pageWidth - inch, y) - canvas.drawCentredString(doc.pagesize[0] / 2, 0.75*inch, 'Page %d' % canvas.getPageNumber()) - canvas.restoreState() - -class TwoColumnTemplate(PageTemplate): - def __init__(self, id, pageSize=defaultPageSize): - self.pageWidth = pageSize[0] - self.pageHeight = pageSize[1] - colWidth = 0.5 * (self.pageWidth - 2.25*inch) - frame1 = Frame(inch, - inch, - colWidth, - self.pageHeight - 2*inch, - id='leftCol') - frame2 = Frame(0.5 * self.pageWidth + 0.125, - inch, - colWidth, - self.pageHeight - 2*inch, - id='rightCol') - PageTemplate.__init__(self, id, [frame1, frame2]) # note lack of onPage - - def afterDrawPage(self, canvas, doc): - y = self.pageHeight - 50 - canvas.saveState() - canvas.setFont('Times-Roman', 10) - canvas.drawString(inch, y+8, doc.title) - canvas.drawRightString(self.pageWidth - inch, y+8, doc.chapter) - canvas.line(inch, y, self.pageWidth - inch, y*inch) - canvas.drawCentredString(doc.pagesize[0] / 2, 0.75*inch, 'Page %d' % canvas.getPageNumber()) - canvas.restoreState() - - -class RLDocTemplate(BaseDocTemplate): - def afterInit(self): - self.addPageTemplates(FrontCoverTemplate('Cover', self.pagesize)) - self.addPageTemplates(OneColumnTemplate('Normal', self.pagesize)) - self.addPageTemplates(TwoColumnTemplate('TwoColumn', self.pagesize)) - - #just playing - self.title = "(Document Title Goes Here)" - self.chapter = "(No chapter yet)" - self.chapterNo = 1 #unique keys - self.sectionNo = 1 # unique keys - -## # AR hack -## self.counter = 1 - def beforeDocument(self): - self.canv.showOutline() - - def afterFlowable(self, flowable): - """Detect Level 1 and 2 headings, build outline, - and track chapter title.""" - if isinstance(flowable, Paragraph): - style = flowable.style.name - -## #AR debug text -## try: -## print '%d: %s...' % (self.counter, flowable.getPlainText()[0:40]) -## except AttributeError: -## print '%d: (something with ABag)' % self.counter -## self.counter = self.counter + 1 - - if style == 'Title': - self.title = flowable.getPlainText() - elif style == 'Heading1': - self.chapter = flowable.getPlainText() - key = 'ch%d' % self.chapterNo - self.canv.bookmarkPage(key) - self.canv.addOutlineEntry(flowable.getPlainText(), - key, 0, 0) - self.chapterNo = self.chapterNo + 1 - self.sectionNo = 1 - elif style == 'Heading2': - self.section = flowable.text - key = 'ch%ds%d' % (self.chapterNo, self.sectionNo) - self.canv.bookmarkPage(key) - self.canv.addOutlineEntry(flowable.getPlainText(), - key, 1, 0) - self.sectionNo = self.sectionNo + 1 \ No newline at end of file diff --git a/bin/reportlab/tools/docco/stylesheet.py b/bin/reportlab/tools/docco/stylesheet.py deleted file mode 100644 index f011fe4b86b..00000000000 --- a/bin/reportlab/tools/docco/stylesheet.py +++ /dev/null @@ -1,147 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/stylesheet.py -#standard stylesheet for our manuals -from reportlab.lib.styles import StyleSheet1, ParagraphStyle -from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY -from reportlab.lib import colors - - -def getStyleSheet(): - """Returns a stylesheet object""" - stylesheet = StyleSheet1() - - stylesheet.add(ParagraphStyle(name='Normal', - fontName='Times-Roman', - fontSize=10, - leading=12, - spaceBefore=6) - ) - - stylesheet.add(ParagraphStyle(name='Comment', - fontName='Times-Italic') - ) - - stylesheet.add(ParagraphStyle(name='Indent1', - leftIndent=36, - firstLineIndent=0) - ) - - stylesheet.add(ParagraphStyle(name='BodyText', - parent=stylesheet['Normal'], - spaceBefore=6) - ) - stylesheet.add(ParagraphStyle(name='Italic', - parent=stylesheet['BodyText'], - fontName = 'Times-Italic') - ) - - stylesheet.add(ParagraphStyle(name='Heading1', - parent=stylesheet['Normal'], - fontName = 'Times-Bold', - alignment=TA_CENTER, - fontSize=18, - leading=22, - spaceAfter=6), - alias='h1') - - stylesheet.add(ParagraphStyle(name='Heading2', - parent=stylesheet['Normal'], - fontName = 'Times-Bold', - fontSize=14, - leading=17, - spaceBefore=12, - spaceAfter=6), - alias='h2') - - stylesheet.add(ParagraphStyle(name='Heading3', - parent=stylesheet['Normal'], - fontName = 'Times-BoldItalic', - fontSize=12, - leading=14, - spaceBefore=12, - spaceAfter=6), - alias='h3') - - stylesheet.add(ParagraphStyle(name='Heading4', - parent=stylesheet['Normal'], - fontName = 'Times-BoldItalic', - spaceBefore=10, - spaceAfter=4), - alias='h4') - - stylesheet.add(ParagraphStyle(name='Title', - parent=stylesheet['Normal'], - fontName = 'Times-Bold', - fontSize=48, - leading=56, - spaceAfter=72, - alignment=TA_CENTER - ), - alias='t') - - stylesheet.add(ParagraphStyle(name='Bullet', - parent=stylesheet['Normal'], - firstLineIndent=0, - leftIndent=54, - bulletIndent=18, - spaceBefore=0, - bulletFontName='Symbol'), - alias='bu') - - stylesheet.add(ParagraphStyle(name='Definition', - parent=stylesheet['Normal'], - firstLineIndent=0, - leftIndent=36, - bulletIndent=0, - spaceBefore=6, - bulletFontName='Times-BoldItalic'), - alias='df') - - stylesheet.add(ParagraphStyle(name='Code', - parent=stylesheet['Normal'], - fontName='Courier', - textColor=colors.navy, - fontSize=8, - leading=8.8, - leftIndent=36, - firstLineIndent=0)) - - stylesheet.add(ParagraphStyle(name='FunctionHeader', - parent=stylesheet['Normal'], - fontName='Courier-Bold', - fontSize=8, - leading=8.8)) - - stylesheet.add(ParagraphStyle(name='DocString', - parent=stylesheet['Normal'], - fontName='Courier', - fontSize=8, - leftIndent=18, - leading=8.8)) - - stylesheet.add(ParagraphStyle(name='DocStringIndent', - parent=stylesheet['Normal'], - fontName='Courier', - fontSize=8, - leftIndent=36, - leading=8.8)) - - stylesheet.add(ParagraphStyle(name='URL', - parent=stylesheet['Normal'], - fontName='Courier', - textColor=colors.navy, - alignment=TA_CENTER), - alias='u') - - stylesheet.add(ParagraphStyle(name='Centred', - parent=stylesheet['Normal'], - alignment=TA_CENTER - )) - - stylesheet.add(ParagraphStyle(name='Caption', - parent=stylesheet['Centred'], - fontName='Times-Italic' - )) - - return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/docco/t_parse.py b/bin/reportlab/tools/docco/t_parse.py deleted file mode 100644 index 0dc66c3c879..00000000000 --- a/bin/reportlab/tools/docco/t_parse.py +++ /dev/null @@ -1,247 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/t_parse.py -""" -Template parsing module inspired by REXX (with thanks to Donn Cave for discussion). - -Template initialization has the form: - T = Template(template_string, wild_card_marker, single_char_marker, - x = regex_x, y = regex_y, ...) -Parsing has the form - ([match1, match2, ..., matchn], lastindex) = T.PARSE(string) - -Only the first argument is mandatory. - -The resultant object efficiently parses strings that match the template_string, -giving a list of substrings that correspond to each "directive" of the template. - -Template directives: - - Wildcard: - The template may be initialized with a wildcard that matches any string - up to the string matching the next directive (which may not be a wild - card or single character marker) or the next literal sequence of characters - of the template. The character that represents a wildcard is specified - by the wild_card_marker parameter, which has no default. - - For example, using X as the wildcard: - - - >>> T = Template("prefixXinteriorX", "X") - >>> T.PARSE("prefix this is before interior and this is after") - ([' this is before ', ' and this is after'], 47) - >>> T = Template("X", "X") - >>> T.PARSE('go to index') - (['A HREF="index.html"', 'go to index', '/A'], 36) - - Obviously the character used to represent the wildcard must be distinct - from the characters used to represent literals or other directives. - - Fixed length character sequences: - The template may have a marker character which indicates a fixed - length field. All adjacent instances of this marker will be matched - by a substring of the same length in the parsed string. For example: - - >>> T = Template("NNN-NN-NNNN", single_char_marker="N") - >>> T.PARSE("1-2-34-5-12") - (['1-2', '34', '5-12'], 11) - >>> T.PARSE("111-22-3333") - (['111', '22', '3333'], 11) - >>> T.PARSE("1111-22-3333") - ValueError: literal not found at (3, '-') - - A template may have multiple fixed length markers, which allows fixed - length fields to be adjacent, but recognized separately. For example: - - >>> T = Template("MMDDYYX", "X", "MDY") - >>> T.PARSE("112489 Somebody's birthday!") - (['11', '24', '89', " Somebody's birthday!"], 27) - - Regular expression markers: - The template may have markers associated with regular expressions. - the regular expressions may be either string represenations of compiled. - For example: - >>> T = Template("v: s i", v=id, s=str, i=int) - >>> T.PARSE("this_is_an_identifier: 'a string' 12344") - (['this_is_an_identifier', "'a string'", '12344'], 39) - >>> - Here id, str, and int are regular expression conveniences provided by - this module. - - Directive markers may be mixed and matched, except that wildcards cannot precede - wildcards or single character markers. - Example: ->>> T = Template("ssnum: NNN-NN-NNNN, fn=X, ln=X, age=I, quote=Q", "X", "N", I=int, Q=str) ->>> T.PARSE("ssnum: 123-45-6789, fn=Aaron, ln=Watters, age=13, quote='do be do be do'") -(['123', '45', '6789', 'Aaron', 'Watters', '13', "'do be do be do'"], 72) ->>> - -""" - -import re, string -from types import StringType -from string import find - -# -# template parsing -# -# EG: T = Template("(NNN)NNN-NNNN X X", "X", "N") -# ([area, exch, ext, fn, ln], index) = T.PARSE("(908)949-2726 Aaron Watters") -# -class Template: - - def __init__(self, - template, - wild_card_marker=None, - single_char_marker=None, - **marker_to_regex_dict): - self.template = template - self.wild_card = wild_card_marker - self.char = single_char_marker - # determine the set of markers for this template - markers = marker_to_regex_dict.keys() - if wild_card_marker: - markers.append(wild_card_marker) - if single_char_marker: - for ch in single_char_marker: # allow multiple scm's - markers.append(ch) - self.char = single_char_primary = single_char_marker[0] - self.markers = markers - for mark in markers: - if len(mark)>1: - raise ValueError, "Marks must be single characters: "+`mark` - # compile the regular expressions if needed - self.marker_dict = marker_dict = {} - for (mark, rgex) in marker_to_regex_dict.items(): - if type(rgex) == StringType: - rgex = re.compile(rgex) - marker_dict[mark] = rgex - # determine the parse sequence - parse_seq = [] - # dummy last char - lastchar = None - index = 0 - last = len(template) - # count the number of directives encountered - ndirectives = 0 - while index s blah", s=str) - s = "' <-- a string --> ' --> 'blah blah another string blah' blah" - print T1.PARSE(s) - - T2 = Template("s --> NNNiX", "X", "N", s=str, i=int) - print T2.PARSE("'A STRING' --> 15964653alpha beta gamma") - - T3 = Template("XsXi", "X", "N", s=str, i=int) - print T3.PARSE("prefix'string'interior1234junk not parsed") - - T4 = Template("MMDDYYX", "X", "MDY") - print T4.PARSE("122961 Somebody's birthday!") - - -if __name__=="__main__": test() \ No newline at end of file diff --git a/bin/reportlab/tools/docco/yaml.py b/bin/reportlab/tools/docco/yaml.py deleted file mode 100644 index af31788622e..00000000000 --- a/bin/reportlab/tools/docco/yaml.py +++ /dev/null @@ -1,201 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/yaml.py -# parses "Yet Another Markup Language" into a list of tuples. -# Each tuple says what the data is e.g. -# ('Paragraph', 'Heading1', 'Why Reportlab Rules') -# and the pattern depends on type. -""" -Parser for "Aaron's Markup Language" - a markup language -which is easier to type in than XML, yet gives us a -reasonable selection of formats. - -The general rule is that if a line begins with a '.', -it requires special processing. Otherwise lines -are concatenated to paragraphs, and blank lines -separate paragraphs. - -If the line ".foo bar bletch" is encountered, -it immediately ends and writes out any current -paragraph. - -It then looks for a parser method called 'foo'; -if found, it is called with arguments (bar, bletch). - -If this is not found, it assumes that 'foo' is a -paragraph style, and the text for the first line -of the paragraph is 'bar bletch'. It would be -up to the formatter to decide whether on not 'foo' -was a valid paragraph. - -Special commands understood at present are: -.image filename -- adds the image to the document -.beginPre Code -- begins a Preformatted object in style 'Code' -.endPre -- ends a preformatted object. -""" - - -import sys -import string -import imp -import codegrab - -#modes: -PLAIN = 1 -PREFORMATTED = 2 - -BULLETCHAR = '\267' # assumes font Symbol, but works on all platforms - -class Parser: - def __init__(self): - self.reset() - - def reset(self): - self._lineNo = 0 - self._style = 'Normal' # the default - self._results = [] - self._buf = [] - self._mode = PLAIN - - def parseFile(self, filename): - #returns list of objects - data = open(filename, 'r').readlines() - - for line in data: - #strip trailing newlines - self.readLine(line[:-1]) - self.endPara() - return self._results - - def readLine(self, line): - #this is the inner loop - self._lineNo = self._lineNo + 1 - stripped = string.lstrip(line) - if len(stripped) == 0: - if self._mode == PLAIN: - self.endPara() - else: #preformatted, append it - self._buf.append(line) - elif line[0]=='.': - # we have a command of some kind - self.endPara() - words = string.split(stripped[1:]) - cmd, args = words[0], words[1:] - - #is it a parser method? - if hasattr(self.__class__, cmd): - method = eval('self.'+cmd) - #this was very bad; any type error in the method was hidden - #we have to hack the traceback - try: - apply(method, tuple(args)) - except TypeError, err: - sys.stderr.write("Parser method: apply(%s,%s) %s at line %d\n" % (cmd, tuple(args), err, self._lineNo)) - raise - else: - # assume it is a paragraph style - - # becomes the formatter's problem - self.endPara() #end the last one - words = string.split(stripped, ' ', 1) - assert len(words)==2, "Style %s but no data at line %d" % (words[0], self._lineNo) - (styletag, data) = words - self._style = styletag[1:] - self._buf.append(data) - else: - #we have data, add to para - self._buf.append(line) - - def endPara(self): - #ends the current paragraph, or preformatted block - - text = string.join(self._buf, ' ') - if text: - if self._mode == PREFORMATTED: - #item 3 is list of lines - self._results.append(('Preformatted', self._style, - string.join(self._buf,'\n'))) - else: - self._results.append(('Paragraph', self._style, text)) - self._buf = [] - self._style = 'Normal' - - def beginPre(self, stylename): - self._mode = PREFORMATTED - self._style = stylename - - def endPre(self): - self.endPara() - self._mode = PLAIN - - def image(self, filename): - self.endPara() - self._results.append(('Image', filename)) - - def vSpace(self, points): - """Inserts a vertical spacer""" - self._results.append(('VSpace', points)) - - def pageBreak(self): - """Inserts a frame break""" - self._results.append(('PageBreak','blah')) # must be a tuple - - def custom(self, moduleName, funcName): - """Goes and gets the Python object and adds it to the story""" - self.endPara() - self._results.append(('Custom',moduleName, funcName)) - - - - def getModuleDoc(self, modulename, pathname=None): - """Documents the entire module at this point by making - paragraphs and preformatted objects""" - docco = codegrab.getObjectsDefinedIn(modulename, pathname) - if docco.doc <> None: - self._results.append(('Paragraph', 'DocString', docco.doc)) - if len(docco.functions) > 0: - for fn in docco.functions: - if fn.status == 'official': - self._results.append(('Preformatted','FunctionHeader', fn.proto)) - self._results.append(('Preformatted','DocString', fn.doc)) - - if len(docco.classes) > 0: - for cls in docco.classes: - if cls.status == 'official': - self._results.append(('Preformatted','FunctionHeader', 'Class %s:' % cls.name)) - self._results.append(('Preformatted','DocString', cls.doc)) - for mth in cls.methods: - if mth.status == 'official': - self._results.append(('Preformatted','FunctionHeader', mth.proto)) - self._results.append(('Preformatted','DocStringIndent', mth.doc)) - - - def getClassDoc(self, modulename, classname, pathname=None): - """Documents the class and its public methods""" - docco = codegrab.getObjectsDefinedIn(modulename, pathname) - found = 0 - for cls in docco.classes: - if cls.name == classname: - found = 1 - self._results.append(('Preformatted','FunctionHeader', 'Class %s:' % cls.name)) - self._results.append(('Preformatted','DocString', cls.doc)) - for mth in cls.methods: - if mth.status == 'official': - self._results.append(('Preformatted','FunctionHeader', mth.proto)) - self._results.append(('Preformatted','DocStringIndent', mth.doc)) - break - assert found, 'No Classes Defined in ' + modulename - - def nextPageTemplate(self, templateName): - self._results.append(('NextPageTemplate',templateName)) - -if __name__=='__main__': #NORUNTESTS - if len(sys.argv) <> 2: - print 'usage: yaml.py source.txt' - else: - p = Parser() - results = p.parseFile(sys.argv[1]) - import pprint - pprint.pprint(results) \ No newline at end of file diff --git a/bin/reportlab/tools/docco/yaml2pdf.py b/bin/reportlab/tools/docco/yaml2pdf.py deleted file mode 100644 index 454bf53cf71..00000000000 --- a/bin/reportlab/tools/docco/yaml2pdf.py +++ /dev/null @@ -1,104 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/docco/yaml2pdf.py -# yaml2pdf - turns stuff in Yet Another Markup Language -# into PDF documents. Very crude - it assumes a -# doc template and stylesheet (hard coded for now) -# and basically cranks out paragraphs in each style -"""yaml2pdf.py - converts Yet Another Markup Language -to reasonable PDF documents. This is ReportLab's -basic documentation tool. - -Usage: -. "yaml2pdf.py filename.ext" will create "filename.pdf" -""" - -import sys -import os -import imp - -import yaml -from rltemplate import RLDocTemplate -from reportlab.lib.styles import ParagraphStyle -from reportlab.lib.enums import * -from reportlab.lib.pagesizes import A4 -from reportlab.platypus import * -from reportlab.lib import colors -from reportlab.lib.units import inch - - -from stylesheet import getStyleSheet - - -def run(infilename, outfilename): - p = yaml.Parser() - results = p.parseFile(infilename) - - ss = getStyleSheet() - - #now make flowables from the results - story = [] - for thingy in results: - typ = thingy[0] - if typ == 'Paragraph': - (typ2, stylename, text) = thingy - if stylename == 'bu': - bulletText='\267' - else: - bulletText=None - try: - style = ss[stylename] - except KeyError: - print 'Paragraph style "%s" not found in stylesheet, using Normal instead' % stylename - style = ss['Normal'] - story.append(Paragraph(text, style, bulletText=bulletText)) - elif typ == 'Preformatted': - (typ2, stylename, text) = thingy - try: - style = ss[stylename] - except KeyError: - print 'Preformatted style "%s" not found in stylesheet, using Normal instead' % stylename - style = ss['Normal'] - story.append(Preformatted(text, style, bulletText=bulletText)) - elif typ == 'Image': - filename = thingy[1] - img = Image(filename) - story.append(img) - elif typ == 'PageBreak': - story.append(PageBreak()) - elif typ == 'VSpace': - height = thingy[1] - story.append(Spacer(0, height)) - elif typ == 'NextPageTemplate': - story.append(NextPageTemplate(thingy[1])) - elif typ == 'Custom': - # go find it - searchPath = [os.getcwd()+'\\'] - (typ2, moduleName, funcName) = thingy - found = imp.find_module(moduleName, searchPath) - assert found, "Custom object module %s not found" % moduleName - (file, pathname, description) = found - mod = imp.load_module(moduleName, file, pathname, description) - - #now get the function - func = getattr(mod, funcName) - story.append(func()) - - else: - print 'skipping',typ, 'for now' - - - #print it - doc = RLDocTemplate(outfilename, pagesize=A4) - doc.build(story) - -if __name__ == '__main__': #NORUNTESTS - if len(sys.argv) == 2: - infilename = sys.argv[1] - outfilename = os.path.splitext(infilename)[0] + '.pdf' - if os.path.isfile(infilename): - run(infilename, outfilename) - else: - print 'File not found %s' % infilename - else: - print __doc__ \ No newline at end of file diff --git a/bin/reportlab/tools/py2pdf/README b/bin/reportlab/tools/py2pdf/README deleted file mode 100644 index 9a3e9c8fb7e..00000000000 --- a/bin/reportlab/tools/py2pdf/README +++ /dev/null @@ -1,8 +0,0 @@ -py2pdf - converts Python source code to PDF -with a LOT of options. - -See the header of py2pdf.py for full details. -execute demo.py to see some output. - -Contributed by Dinu Gherman and copyrighted by him. -Uses Just van Rossum's PyFontify. diff --git a/bin/reportlab/tools/py2pdf/__init__.py b/bin/reportlab/tools/py2pdf/__init__.py deleted file mode 100644 index e0226c4a6fd..00000000000 --- a/bin/reportlab/tools/py2pdf/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/py2pdf/__init__.py diff --git a/bin/reportlab/tools/py2pdf/demo-config.txt b/bin/reportlab/tools/py2pdf/demo-config.txt deleted file mode 100644 index 4c5ed1a314b..00000000000 --- a/bin/reportlab/tools/py2pdf/demo-config.txt +++ /dev/null @@ -1,11 +0,0 @@ ---bgCol=(1,.9,.9) ---lineNum -#--fontSize=12 -#--paperFormat=B5 -#--fontName=Helvetica -#--landscape -#--restCol=(0,1,0) -#--mode=mono ---kwCol=(1,0,1) -#--kwCol=#dd00ff -#--kwCol=(.5,.5,.5) diff --git a/bin/reportlab/tools/py2pdf/demo.py b/bin/reportlab/tools/py2pdf/demo.py deleted file mode 100755 index 22c6ac0a7ee..00000000000 --- a/bin/reportlab/tools/py2pdf/demo.py +++ /dev/null @@ -1,198 +0,0 @@ -#! /usr/bin/python2.3 - -"""demo.py - Demo script for py2pdf 0.5. - -The main idea is: take one Python file and make a whole -bunch of PDFs out of it for test purposes. - -Dinu Gherman -""" - - -import string, re, os, os.path, sys, shutil -from py2pdf import * - - -### Custom layouter class used with test3(). - -class ImgPDFLayouter (PythonPDFLayouter): - "A custom layouter displaying an image on each page." - - def setMainFrame(self, frame=None): - "Make a frame in the right half of the page." - - width, height = self.options.realPaperFormat.size - self.frame = height - 2*cm, 2*cm, 250, width-1*cm - - self.makeForm() - - - def makeForm(self): - "Use the experimental ReportLab form support." - - width, height = self.options.realPaperFormat.size - tm, bm, lm, rm = self.frame - c = self.canvas - - # Define a PDF form containing an image frame - # that will be included on every page, but - # stored only once in the resulting file. - c.beginForm("imageFrame") - c.saveState() - x, y = 219.0, 655.0 # Known size of the picture. - c.scale((lm - 1*cm)/x, height/y) - path = 'vertpython.jpg' - c.drawImage(path, 0, 0) - c.restoreState() - c.endForm() - - - def putPageDecoration(self): - "Draw the left border image and page number." - - width, height = self.options.realPaperFormat.size - tm, bm, lm, rm = self.frame - c = self.canvas - - # Footer. - x, y = lm + 0.5 * (rm - lm), 0.5 * bm - c.setFillColor(Color(0, 0, 0)) - c.setFont('Times-Italic', 12) - label = "Page %d" % self.pageNum - c.drawCentredString(x, y, label) - - # Call the previously stored form. - c.doForm("imageFrame") - - -### Helpers. - -def modifyPath(path, new, ext='.py'): - "Modifying the base name of a file." - - rest, ext = os.path.splitext(path) - path, base = os.path.split(rest) - format = "%s-%s%s" % (base, new, ext) - return os.path.join(path, format) - - -def getAllTestFunctions(): - "Return a list of all test functions available." - - globs = globals().keys() - tests = filter(lambda g: re.match('test[\d]+', g), globs) - tests.sort() - return map(lambda t: globals()[t], tests) - - -### Test functions. -### -### In order to be automatically found and applied to -### a Python file all test functions must follow the -### following naming pattern: 'test[0-9]+' and contain -### a doc string. - -def test0(path): - "Creating a PDF assuming an ASCII file." - - p = PDFPrinter() - p.process(path) - - -def test1(path): - "Creating a PDF using only default options." - - p = PythonPDFPrinter() - p.process(path) - - -def test2(path): - "Creating a PDF with some modified options." - - p = PythonPDFPrinter() - p.options.updateOption('landscape', 1) - p.options.updateOption('fontName', 'Helvetica') - p.options.updateOption('fontSize', '14') - p.options.display() - p.process(path) - - -def test3(path): - "Creating several PDFs as 'magazine listings'." - - p = PythonPDFPrinter() - p.Layouter = EmptyPythonPDFLayouter - p.options.updateOption('paperSize', '(250,400)') - p.options.updateOption('multiPage', 1) - p.options.updateOption('lineNum', 1) - p.process(path) - - -def test4(path): - "Creating a PDF in monochrome mode." - - p = PythonPDFPrinter() - p.options.updateOption('mode', 'mono') - p.process(path) - - -def test5(path): - "Creating a PDF with options from a config file." - - p = PythonPDFPrinter() - i = string.find(path, 'test5') - newPath = modifyPath(path[:i-1], 'config') + '.txt' - - try: - p.options.updateWithContentsOfFile(newPath) - p.options.display() - p.process(path) - except IOError: - print "Skipping test5() due to IOError." - - -def test6(path): - "Creating a PDF with modified layout." - - p = PythonPDFPrinter() - p.Layouter = ImgPDFLayouter - p.options.updateOption('fontName', 'Helvetica') - p.options.updateOption('fontSize', '12') - p.options.display() - p.process(path) - - -### Main. - -def main(inPath, *tests): - "Apply various tests to one Python source file." - - for t in tests: - newPath = modifyPath(inPath, t.__name__) - shutil.copyfile(inPath, newPath) - try: - print t.__doc__ - t(newPath) - finally: - os.remove(newPath) - print - - -if __name__=='__main__': - # Usage: "python demo.py [ ...]" - try: - try: - tests = map(lambda a: globals()[a], sys.argv[2:]) - except IndexError: - tests = getAllTestFunctions() - - fileName = sys.argv[1] - apply(main, [fileName]+tests) - - # Usage: "python demo.py" (implicitly does this: - # "python demo.py demo.py" ) - except IndexError: - print "Performing self-test..." - fileName = sys.argv[0] - tests = getAllTestFunctions() - apply(main, [fileName]+tests) diff --git a/bin/reportlab/tools/py2pdf/idle_print.py b/bin/reportlab/tools/py2pdf/idle_print.py deleted file mode 100644 index 735e092c1ab..00000000000 --- a/bin/reportlab/tools/py2pdf/idle_print.py +++ /dev/null @@ -1,56 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/py2pdf/idle_print.py - -# idle_print [py2pdf_options] filename -__version__=''' $Id$ ''' -# you should adjust the globals below to configure for your system - -import sys, os, py2pdf, string, time -#whether we remove input/output files; if you get trouble on windows try setting _out to 0 -auto_rm_in = 1 -auto_rm_out = 1 -viewOnly = 0 - -#how to call up your acrobat reader -if sys.platform=='win32': - acrord = 'C:\\Program Files\\Adobe\\Acrobat 4.0\\Reader\\AcroRd32.exe' - def printpdf(pdfname): - args = [acrord, pdfname] - os.spawnv(os.P_WAIT, args[0], args) -else: - acrord = 'acroread' - def printpdf(pdfname): - if viewOnly: - cmd = "%s %s" % (acrord,pdfname) - else: - cmd = "%s -toPostScript < %s | lpr" % (acrord,pdfname) - os.system(cmd) - -args = ['--input=python'] -files = [] -for f in sys.argv[1:]: - if f[:2]=='--': - opt = f[2:] - if opt =='no_auto_rm_in': - auto_rm_in = 0 - elif opt =='auto_rm_in': - auto_rm_in = 1 - elif opt =='no_auto_rm_out': - auto_rm_out = 0 - elif opt =='auto_rm_out': - auto_rm_out = 1 - elif opt =='viewonly': - viewOnly = 1 - elif opt[:9] =='acroread=': - acrord = opt[9:] - else: - args.append(f) - else: files.append(f) - -for f in files: - py2pdf.main(args+[f]) - if auto_rm_in: os.remove(f) - pdfname = os.path.splitext(f)[0]+'.pdf' - printpdf(pdfname) - if auto_rm_out: os.remove(pdfname) \ No newline at end of file diff --git a/bin/reportlab/tools/py2pdf/py2pdf.py b/bin/reportlab/tools/py2pdf/py2pdf.py deleted file mode 100755 index 9fcddeaadb6..00000000000 --- a/bin/reportlab/tools/py2pdf/py2pdf.py +++ /dev/null @@ -1,1567 +0,0 @@ -#! /usr/bin/python2.3 - -""" Python Highlighter for PDF Version: 0.6 - - py2pdf.py [options] [ ...] - - options: - -h or --help print help (this message) - - read from stdin (writes to stdout) - --stdout read from file, write to stdout - (restricted to first file only) - --title= specify title - --config=<file> read configuration options from <file> - --input=<type> set input file type - 'python': Python code (default) - 'ascii': arbitrary ASCII files (b/w) - --mode=<mode> set output mode - 'color': output in color (default) - 'mono': output in b/w - --paperFormat= set paper format (ISO A, B, C series, - <format> US legal & letter; default: 'A4') - e.g. 'letter', 'A3', 'A4', 'B5', 'C6', ... - --paperSize= set paper size in points (size being a valid - <size> numeric 2-tuple (x,y) w/o any whitespace) - --landscape set landscape format (default: portrait) - --bgCol=<col> set page background-color in hex code like - '#FFA024' or '0xFFA024' for RGB components - (overwrites mono mode) - --<cat>Col=<col> set color of certain code categories, i.e. - <cat> can be the following: - 'comm': comments - 'ident': identifiers - 'kw': keywords - 'strng': strings - 'param': parameters - 'rest': all the rest - --fontName=<name> set base font name (default: 'Courier') - like 'Helvetica', 'Times-Roman' - --fontSize=<size> set font size (default: 8) - --tabSize=<size> set tab size (default: 4) - --lineNum print line numbers - --multiPage generate one file per page (with filenames - tagged by 1, 2...), disables PDF outline - --noOutline don't generate PDF outline (default: unset) - -v or --verbose set verbose mode - - Takes the input, assuming it is Python source code and formats - it into PDF. - - * Uses Just van Rossum's PyFontify version 0.4 to tag Python - scripts, now included in reportlab.lib. - - * Uses the ReportLab library (version 0.92 or higher) to - generate PDF. You can get it without charge from ReportLab: - http://www.reportlab.com - - * Parts of this code still borrow heavily from Marc-Andre - Lemburg's py2html who has kindly given permission to - include them in py2pdf. Thanks, M.-A.! -""" - -__copyright__ = """ ----------------------------------------------------------------------- -(c) Copyright by Dinu C. Gherman, 2000 (gherman@europemail.com) - - Permission to use, copy, modify, and distribute this - software and its documentation for any purpose and - without fee or royalty is hereby granted, provided - that the above copyright notice appear in all copies - and that both that copyright notice and this permission - notice appear in supporting documentation or portions - thereof, including modifications, that you make. - - THE AUTHOR DINU C. GHERMAN DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT - SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT - OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER - RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS - ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE - OR PERFORMANCE OF THIS SOFTWARE! -""" - -__version__ = '0.6' -__author__ = 'Dinu C. Gherman' -__date__ = '2001-08-17' -__url__ = 'http://starship.python.net/crew/gherman/programs/py2pdf' - - -import sys, string, re, os, getopt - -from reportlab.pdfgen import canvas -from reportlab.lib.colors import Color, HexColor -from reportlab.lib import fonts -from reportlab.lib.units import cm, inch - - -### Helpers functions. - -def makeTuple(aString): - """Evaluate a string securely into a tuple. - - Match the string and return an evaluated object thereof if and - only if we think it is a tuple of two or more numeric decimal(!) - values, integers or floats. E-notation, hex or octal is not - supported, though! Shorthand notation (omitting leading or trail- - zeros, before or after the decimal point) like .25 or 25. is - supported. - """ - - c = string.count(aString, ',') - num = '(\d*?\.?\d+?)|(\d+?\.?\d*?)' - tup = string.join([num]*c, ',') - - if re.match('\(' + tup + '\)', aString): - return eval(aString) - else: - details = '%s cannot be parsed into a numeric tuple!' % aString - raise 'ValueError', details - - -def loadFontifier(options=None): - "Load a tagging module and return a corresponding function." - - # We can't use 'if options' because of the modified - # attribute lookup it seems. - - if type(options) != type(None) and options.marcs: - # Use mxTextTool's tagging engine. - - from mxTextTools import tag - from mxTextTools.Examples.Python import python_script - tagFunc = lambda text, tag=tag, pytable=python_script: \ - tag(text,pytable)[1] - - else: - # Load Just's. - - try: - from reportlab.lib import PyFontify - - if PyFontify.__version__ < '0.3': - raise ValueError - - tagFunc = PyFontify.fontify - - except: - print """ - Sorry, but this script needs the PyFontify.py module version 0.3 - or higher; You can download it from Just's homepage at - - URL: http://starship.python.net/crew/just -""" - sys.exit() - - return tagFunc - - -def makeColorFromString(aString): - """Convert a string to a ReportLab RGB color object. - - Supported formats are: '0xFFFFFF', '#FFFFFF' and '(R,G,B)' - where the latter is a tuple of three floats in the range - [0.0, 1.0]. - """ - - s = aString - - if s[0] == '#' or s[:2] in ('0x', '0X'): - if s[:2] in ('0x', '0X'): - return HexColor('#' + s[2:]) - - elif s[0] == '(': - r, g, b = makeTuple(aString) - return Color(r, g, b) - - -### Utility classes. - -# For py2pdf this is some kind of overkill, but it's fun, too. -class PaperFormat: - """Class to capture a paper format/size and its orientation. - - This class represents an abstract paper format, including - the size and orientation of a sheet of paper in some formats - plus a few operations to change its size and orientation. - """ - - _A4W, _A4H = 21*cm, 29.7*cm - A0 = (4*_A4W, 4*_A4H) - - _B4W, _B4H = 25*cm, 35.3*cm - B0 = (4*_B4W, 4*_B4H) - - _C4W, _C4H = 22.9*cm, 32.4*cm - C0 = (4*_C4W, 4*_C4H) - - letter = (8.5*inch, 11*inch) - legal = (8.5*inch, 14*inch) - - - def __init__(self, nameOrSize='A4', landscape=0): - "Initialisation." - - t = type(nameOrSize) - - if t == type(''): - self.setFormatName(nameOrSize, landscape) - elif t == type((1,)): - self.setSize(nameOrSize) - self.setLandscape(landscape) - - - def __repr__(self): - """Return a string representation of ourself. - - The returned string can also be used to recreate - the same PaperFormat object again. - """ - - if self.name != 'custom': - nos = `self.name` - else: - nos = `self.size` - - format = "PaperFormat(nameOrSize=%s, landscape=%d)" - tuple = (nos, self.landscape) - - return format % tuple - - - def setSize(self, size=None): - "Set explicit paper size." - - self.name = 'custom' - x, y = self.size = size - self.landscape = x < y - - - def setLandscape(self, flag): - "Set paper orientation as desired." - - # Swap paper orientation if needed. - - self.landscape = flag - x, y = self.size - - ls = self.landscape - if (ls and x < y) or (not ls and x > y): - self.size = y, x - - - def setFormatName(self, name='A4', landscape=0): - "Set paper size derived from a format name." - - if name[0] in 'ABC': - # Assume ISO-A, -B, -C series. - # (Hmm, are B and C really ISO standards - # or just DIN? Well...) - c, f = name[0], int(name[1:]) - self.size = getattr(self, c + '0') - self.name = c + '0' - self.makeHalfSize(f) - - elif name == 'letter': - self.size = self.letter - self.name = 'letter' - - elif name == 'legal': - self.size = self.legal - self.name = 'legal' - - self.setLandscape(landscape) - - - def makeHalfSize(self, times=1): - "Reduce paper size (surface) by 50% multiple times." - - # Orientation remains unchanged. - - # Iterates only for times >= 1. - for i in xrange(times): - s = self.size - self.size = s[1] / 2.0, s[0] - - if self.name[0] in 'ABC': - self.name = self.name[0] + `int(self.name[1:]) + 1` - else: - self.name = 'custom' - - - def makeDoubleSize(self, times=1): - "Increase paper size (surface) by 50% multiple times." - - # Orientation remains unchanged. - - # Iterates only for times >= 1. - for i in xrange(times): - s = self.size - self.size = s[1], s[0] * 2.0 - - if self.name[0] in 'ABC': - self.name = self.name[0] + `int(self.name[1:]) - 1` - else: - self.name = 'custom' - - -class Options: - """Container class for options from command line and config files. - - This class is a container for options as they are specified - when a program is called from a command line, but also from - the same kind of options that are saved in configuration - files. Both the short UNIX style (e.g. '-v') as well as the - extended GNU style (e.g. '--help') are supported. - - An 'option' is a <name>/<value> pair with both parts being - strings at the beginning, but where <value> might be conver- - ted later into any other Python object. - - Option values can be accessed by using their name as an attri- - bute of an Options object, returning None if no such option - name exists (that makes None values impossible, but so what?). - """ - - # Hmm, could also use UserDict... maybe later. - - def __init__(self): - "Initialize with some default options name/values." - - # Hmm, could also pass an initial optional dict... - - # Create a dictionary containing the options - # and populate it with defaults. - self.pool = {} - self.setDefaults() - - - def __getattr__(self, name): - "Turn attribute access into dictionary lookup." - - return self.pool.get(name) - - - def setDefaults(self): - "Set default options." - - ### Maybe get these from a site config file... - self.pool.update({'fontName' : 'Courier', - 'fontSize' : 8, - 'bgCol' : Color(1, 1, 1), - 'mode' : 'color', - 'lineNum' : 0, - 'tabSize' : 4, - 'paperFormat': 'A4', - 'landscape' : 0, - 'title' : None, - 'multiPage' : 0}) - - # Default colors (for color mode), mostly taken from py2html. - self.pool.update({'commCol' : HexColor('#1111CC'), - 'kwCol' : HexColor('#3333CC'), - 'identCol' : HexColor('#CC0000'), - 'paramCol' : HexColor('#000066'), - 'strngCol' : HexColor('#119911'), - 'restCol' : HexColor('#000000')}) - - # Add a default 'real' paper format object. - pf = PaperFormat(self.paperFormat, self.landscape) - self.pool.update({'realPaperFormat' : pf}) - self.pool.update({'files' : []}) - - - def display(self): - "Display all current option names and values." - - self.saveToFile(sys.stdout) - - - def saveToFile(self, path): - "Save options as a log file." - - if type(path) == type(''): - f = open(path, 'w') - else: - # Assume a file-like object. - f = path - - items = self.pool.items() - items.sort() - - for n, v in items: - f.write("%-15s : %s\n" % (n, `v`)) - - - def updateWithContentsOfFile(self, path): - """Update options as specified in a config file. - - The file is expected to contain one option name/value pair - (seperated by an equal sign) per line, but no other whitespace. - Option values may contain equal signs, but no whitespace. - Option names must be valid Python strings, preceeded by one - or two dashes. - """ - - config = open(path).read() - config = string.split(config, '\n') - - for cfg in config: - if cfg == None: - break - - if cfg == '' or cfg[0] == '#': - continue - - # GNU long options - if '=' in cfg and cfg[:2] == '--': - p = string.find(cfg, '=') - opt, arg = cfg[2:p], cfg[p+1:] - self.updateOption(opt, arg) - - # Maybe treat single-letter options as well? - # elif ':' in cfg and cfg[0] == '-' and cfg[1] != '-': - # pass - - else: - self.updateOption(cfg[2:], None) - - - def updateWithContentsOfArgv(self, argv): - "Update options as specified in a (command line) argument vector." - - # Specify accepted short option names (UNIX style). - shortOpts = 'hv' - - # Specify accepted long option names (GNU style). - lo = 'tabSize= paperFormat= paperSize= landscape stdout title= fontName= fontSize=' - lo = lo + ' bgCol= verbose lineNum marcs help multiPage noOutline config= input= mode=' - lo = lo + ' commCol= identCol= kwCol= strngCol= paramCol= restCol=' - longOpts = string.split(lo, ' ') - - try: - optList, args = getopt.getopt(argv, shortOpts, longOpts) - except getopt.error, msg: - sys.stderr.write("%s\nuse -h or --help for help\n" % str(msg)) - sys.exit(2) - - self.updateOption('files', args) # Hmm, really needed? - - for o, v in optList: - # Remove leading dashes (max. two). - if o[0] == '-': - o = o[1:] - if o[0] == '-': - o = o[1:] - - self.updateOption(o, v) - - - def updateOption(self, name, value): - "Update an option from a string value." - - # Special treatment for coloring options... - if name[-3:] == 'Col': - if name[:-3] in string.split('bg comm ident kw strng param rest', ' '): - self.pool[name] = makeColorFromString(value) - - elif name == 'paperSize': - tup = makeTuple(value) - self.pool['paperSize'] = tup - if not self.realPaperFormat: - pf = PaperFormat(self.paperFormat, self.landscape) - self.pool['realPaperFormat'] = pf - self.pool['realPaperFormat'].setSize(tup) - - elif name == 'paperFormat': - self.pool['paperFormat'] = value - if not self.realPaperFormat: - pf = PaperFormat(self.paperFormat, self.landscape) - self.pool['realPaperFormat'] = pf - self.pool['realPaperFormat'].setFormatName(self.paperFormat, self.landscape) - - elif name == 'landscape': - self.pool['landscape'] = 1 - if self.realPaperFormat: - self.pool['realPaperFormat'].setLandscape(1) - - elif name == 'fontSize': - self.pool['fontSize'] = int(value) - - elif name == 'tabSize': - self.pool['tabSize'] = int(value) - - elif name == 'mode': - self.pool['mode'] = value - if value == 'mono': - cats = 'comm ident kw strng param rest' - for cat in string.split(cats, ' '): - self.pool[cat + 'Col'] = Color(0, 0, 0) - - # Parse configuration file... - elif name == 'config': - self.updateWithContentsOfFile(value) - - elif name == 'stdout': - self.pool['stdout'] = 1 - - elif name == 'files': - self.pool['files'] = value - - else: - # Set the value found or 1 for options without values. - self.pool[name] = value or 1 - - - def update(self, **options): - "Update options." - - # Not much tested and/or used, yet!! - - for n, v in options.items(): - self.pool[n] = v - - -### Layouting classes. - -class PDFLayouter: - """A class to layout a simple PDF document. - - This is intended to help generate PDF documents where all pages - follow the same kind of 'template' which is supposed to be the - same adornments (header, footer, etc.) on each page plus a main - 'frame' on each page. These frames are 'connected' such that one - can add individual text lines one by one with automatic line - wrapping, page breaks and text flow between frames. - """ - - def __init__(self, options): - "Initialisation." - - self.options = options - self.canvas = None - self.multiLineStringStarted = 0 - self.lineNum = 0 - - # Set a default color and font. - o = self.options - self.currColor = o.restCol - self.currFont = (o.fontName, o.fontSize) - - - ### Helper methods. - - def setMainFrame(self, frame=None): - "Define the main drawing frame of interest for each page." - - if frame: - self.frame = frame - else: - # Maybe a long-term candidate for additional options... - width, height = self.options.realPaperFormat.size - self.frame = height - 3*cm, 3*cm, 2*cm, width - 2*cm - - # self.frame is a 4-tuple: - # (topMargin, bottomMargin, leftMargin, rightMargin) - - - def setPDFMetaInfo(self): - "Set PDF meta information." - - o = self.options - c = self.canvas - c.setAuthor('py2pdf %s' % __version__) - c.setSubject('') - - # Set filename. - filename = '' - - # For stdin use title option or empty... - if self.srcPath == sys.stdin: - if o.title: - filename = o.title - # otherwise take the input file's name. - else: - path = os.path.basename(self.srcPath) - filename = o.title or path - - c.setTitle(filename) - - - def setFillColorAndFont(self, color, font): - "Set new color/font (maintaining the current 'state')." - - self.currFont = font - self.currColor = color - - fontName, fontSize = font - self.text.setFont(fontName, fontSize) - self.text.setFillColor(color) - - - ### API - - def begin(self, srcPath, numLines): - "Things to do before doing anything else." - - self.lineNum = 0 - self.pageNum = 0 - self.numLines = numLines - self.srcPath = srcPath - - # Set output filename (stdout if desired). - o = self.options - if o.stdout: - self.pdfPath = sys.stdout - else: - if srcPath != sys.stdin: - self.pdfPath = os.path.splitext(srcPath)[0] + '.pdf' - else: - self.pdfPath = sys.stdout - - - def beginDocument(self): - """Things to do when a new document should be started. - - The initial page counter is 0, meaning that beginPage() - will be called by beginLine()... - """ - - # Set initial page number and store file name. - self.pageNum = 0 - o = self.options - - if not o.multiPage: - # Create canvas. - size = o.realPaperFormat.size - self.canvas = canvas.Canvas(self.pdfPath, size, verbosity=0) - c = self.canvas - c.setPageCompression(1) - c.setFont(o.fontName, o.fontSize) - - # Create document meta information. - self.setPDFMetaInfo() - - # Set drawing frame. - self.setMainFrame() - - # Determine the left text margin by adding the with - # of the line number to the left margin of the main frame. - format = "%%%dd " % len(`self.numLines`) - fn, fs = self.currFont - text = format % self.lineNum - tm, bm, lm, rm = self.frame - self.txm = lm - if o.lineNum: - self.txm = self.txm + c.stringWidth(text, fn, fs) - - - def beginPage(self): - "Things to do when a new page has to be added." - - o = self.options - self.pageNum = self.pageNum + 1 - - if not o.multiPage: - tm, bm, lm, rm = self.frame - self.text = self.canvas.beginText(lm, tm - o.fontSize) - self.setFillColorAndFont(self.currColor, self.currFont) - else: - # Fail if stdout desired (with multiPage). - if o.stdout: - raise "IOError", "Can't create multiple pages on stdout!" - - # Create canvas with a modified path name. - base, ext = os.path.splitext(self.pdfPath) - newPath = "%s-%d%s" % (base, self.pageNum, ext) - size = o.realPaperFormat.size - self.canvas = canvas.Canvas(newPath, size, verbosity=0) - c = self.canvas - c.setPageCompression(1) - c.setFont(o.fontName, o.fontSize) - - # Create document meta information. - self.setPDFMetaInfo() - - # Set drawing frame. - self.setMainFrame() - - tm, bm, lm, rm = self.frame - self.text = self.canvas.beginText(lm, tm - o.fontSize) - self.setFillColorAndFont(self.currColor, self.currFont) - - self.putPageDecoration() - - - def beginLine(self, wrapped=0): - "Things to do when a new line has to be added." - - # If there is no page yet, create the first one. - if self.pageNum == 0: - self.beginPage() - - # If bottom of current page reached, do a page break. - # (This works only with one text object being used - # for the entire page. Otherwise we need to maintain - # the vertical position of the current line ourself.) - y = self.text.getY() - tm, bm, lm, rm = self.frame - if y < bm: - self.endPage() - self.beginPage() - - # Print line number label, if needed. - o = self.options - if o.lineNum: - #self.putLineNumLabel() - font = ('Courier', o.fontSize) - - if not wrapped: - # Print a label containing the line number. - self.setFillColorAndFont(o.restCol, font) - format = "%%%dd " % len(`self.numLines`) - self.text.textOut(format % self.lineNum) - else: - # Print an empty label (using bgCol). Hackish! - currCol = self.currColor - currFont = self.currFont - self.setFillColorAndFont(o.bgCol, font) - self.text.textOut(' '*(len(`self.numLines`) + 1)) - self.setFillColorAndFont(currCol, currFont) - - - def endLine(self, wrapped=0): - "Things to do after a line is basically done." - - # End the current line by adding an 'end of line'. - # (Actually done by the text object...) - self.text.textLine('') - - if not wrapped: - self.lineNum = self.lineNum + 1 - - - def endPage(self): - "Things to do after a page is basically done." - - c = self.canvas - - # Draw the current text object (later we might want - # to do that after each line...). - c.drawText(self.text) - c.showPage() - - if self.options.multiPage: - c.save() - - - def endDocument(self): - "Things to do after the document is basically done." - - c = self.canvas - - # Display rest of last page and save it. - c.drawText(self.text) - c.showPage() - c.save() - - - def end(self): - "Things to do after everything has been done." - - pass - - - ### The real meat: methods writing something to a canvas. - - def putLineNumLabel(self, text, wrapped=0): - "Add a long text that can't be split into chunks." - - o = self.options - font = ('Courier', o.fontSize) - - if not wrapped: - # Print a label containing the line number. - self.setFillColorAndFont(o.restCol, font) - format = "%%%dd " % len(`self.numLines`) - self.text.textOut(format % self.lineNum) - else: - # Print an empty label (using bgCol). Hackish! - currCol = self.currColor - currFont = self.currFont - self.setFillColorAndFont(o.bgCol, font) - self.text.textOut(' '*(len(`self.numLines`) + 1)) - self.setFillColorAndFont(currCol, currFont) - - - # Tried this recursively before, in order to determine - # an appropriate string limit rapidly, but this wasted - # much space and showed very poor results... - # This linear method is very slow, but such lines should - # be very rare, too! - - def putSplitLongText(self, text): - "Add a long text that can't be split into chunks." - - # Now, the splitting will be with 'no mercy', - # at the right margin of the main drawing area. - - M = len(text) - t = self.text - x = t.getX() - o = self.options - tm, bm, lm, rm = self.frame - - width = self.canvas.stringWidth - fn, fs = self.currFont - tw = width(text, fn, fs) - - tx = self.text - if tw > rm - lm - x: - i = 1 - T = '' - while text: - T = text[:i] - tx = self.text # Can change after a page break. - tw = width(T, fn, fs) - - if x + tw > rm: - tx.textOut(T[:-1]) - self.endLine(wrapped=1) - self.beginLine(wrapped=1) - x = tx.getX() - text = text[i-1:] - M = len(text) - i = 0 - - i = i + 1 - - if i > M: - break - - tx.textOut(T) - - else: - t.textOut(text) - - - def putLongText(self, text): - "Add a long text by gracefully splitting it into chunks." - - # Splitting is currently done only at blanks, but other - # characters such as '.' or braces are also good - # possibilities... later... - - o = self.options - tm, bm, lm, rm = self.frame - - width = self.canvas.stringWidth - fn, fs = self.currFont - tw = width(text, fn, fs) - - arr = string.split(text, ' ') - - for i in range(len(arr)): - a = arr[i] - t = self.text # Can change during the loop... - tw = width(a, fn, fs) - x = t.getX() - - # If current item does not fit on current line, have it - # split and then put before/after a line (maybe also - # page) break. - if x + tw > rm: - self.putSplitLongText(a) - t = self.text # Can change after a page break... - - # If it fits, just add it to the current text object. - else: - t.textOut(a) - - # Add the character we used to split th original text. - if i < len(arr) - 1: - t.textOut(' ') - - - def putText(self, text): - "Add some text to the current line." - - t = self.text - x = t.getX() - o = self.options - fn, fs = o.fontName, o.fontSize - tw = self.canvas.stringWidth(text, fn, fs) - rm = self.frame[3] - - if x + tw < rm: - t.textOut(text) - else: - self.putLongText(text) - - - # Not yet tested. - def putLine(self, text): - "Add a line to the current text." - - self.putText(text) - self.endLine() - - - def putPageDecoration(self): - "Draw some decoration on each page." - - # Use some abbreviations. - o = self.options - c = self.canvas - tm, bm, lm, rm = self.frame - - # Restore default font. - c.setFont(o.fontName, o.fontSize) - - c.setLineWidth(0.5) # in pt. - - # Background color. - c.setFillColor(o.bgCol) - pf = o.realPaperFormat.size - c.rect(0, 0, pf[0], pf[1], stroke=0, fill=1) - - # Header. - c.setFillColorRGB(0, 0, 0) - c.line(lm, tm + .5*cm, rm, tm + .5*cm) - c.setFont('Times-Italic', 12) - - if self.pdfPath == sys.stdout: - filename = o.title or ' ' - else: - path = os.path.basename(self.srcPath) - filename = o.title or path - - c.drawString(lm, tm + 0.75*cm + 2, filename) - - # Footer. - c.line(lm, bm - .5*cm, rm, bm - .5*cm) - c.drawCentredString(0.5 * pf[0], 0.5*bm, "Page %d" % self.pageNum) - - # Box around main frame. - # c.rect(lm, bm, rm - lm, tm - bm) - - -class PythonPDFLayouter (PDFLayouter): - """A class to layout a simple multi-page PDF document. - """ - - ### API for adding specific Python entities. - - def addKw(self, t): - "Add a keyword." - - o = self.options - - # Make base font bold. - fam, b, i = fonts.ps2tt(o.fontName) - ps = fonts.tt2ps(fam, 1, i) - font = (ps, o.fontSize) - - self.setFillColorAndFont(o.kwCol, font) - - # Do bookmarking... - if not o.noOutline and not o.multiPage: - if t in ('class', 'def'): - tm, bm, lm, rm = self.frame - pos = self.text.getX() - - if pos == self.txm: - self.startPositions = [] - - self.startPos = pos - - if not hasattr(self, 'startPositions'): - self.startPositions = [] - - if pos not in self.startPositions: - self.startPositions.append(pos) - - # Memorize certain keywords. - self.itemFound = t - - else: - self.itemFound = None - self.startPos = None - - self.putText(t) - - - def addIdent(self, t): - "Add an identifier." - - o = self.options - - # Make base font bold. - fam, b, i = fonts.ps2tt(o.fontName) - ps = fonts.tt2ps(fam, 1, i) - font = (ps, o.fontSize) - - self.setFillColorAndFont(o.identCol, font) - self.putText(t) - - # Bookmark certain identifiers (class and function names). - if not o.noOutline and not o.multiPage: - item = self.itemFound - if item: - # Add line height to current vert. position. - pos = self.text.getY() + o.fontSize - - nameTag = "p%sy%s" % (self.pageNum, pos) - c = self.canvas - i = self.startPositions.index(self.startPos) - c.bookmarkHorizontalAbsolute(nameTag, pos) - c.addOutlineEntry('%s %s' % (item, t), nameTag, i) - - - def addParam(self, t): - "Add a parameter." - - o = self.options - font = (o.fontName, o.fontSize) - self.setFillColorAndFont(o.paramCol, font) - self.text.putText(t) - - - def addSimpleString(self, t): - "Add a simple string." - - o = self.options - font = (o.fontName, o.fontSize) - self.setFillColorAndFont(o.strngCol, font) - self.putText(t) - - - def addTripleStringBegin(self): - "Memorize begin of a multi-line string." - - # Memorise that we started a multi-line string. - self.multiLineStringStarted = 1 - - - def addTripleStringEnd(self, t): - "Add a multi-line string." - - self.putText(t) - - # Forget about the multi-line string again. - self.multiLineStringStarted = 0 - - - def addComm(self, t): - "Add a comment." - - o = self.options - - # Make base font slanted. - fam, b, i = fonts.ps2tt(o.fontName) - ps = fonts.tt2ps(fam, b, 1) - font = (ps, o.fontSize) - - self.setFillColorAndFont(o.commCol, font) - self.putText(t) - - - def addRest(self, line, eol): - "Add a regular thing." - - o = self.options - - # Nothing else to be done, print line as-is... - if line: - font = (o.fontName, o.fontSize) - self.setFillColorAndFont(o.restCol, font) - - # ... except if the multi-line-string flag is set, then we - # decide to change the current color to that of strings and - # just go on. - if self.multiLineStringStarted: - self.setFillColorAndFont(o.strngCol, font) - - self.putText(line) - - # Print an empty line. - else: - if eol != -1: - self.putText('') - - ### End of API. - - -class EmptyPythonPDFLayouter (PythonPDFLayouter): - """A PDF layout with no decoration and no margins. - - The main frame extends fully to all paper edges. This is - useful for creating PDFs when writing one page per file, - in order to provide pre-rendered, embellished Python - source code to magazine publishers, who can include the - individual files and only need to add their own captures. - """ - - def setMainFrame(self, frame=None): - "Make a frame extending to all paper edges." - - width, height = self.options.realPaperFormat.size - self.frame = height, 0, 0, width - - - def putPageDecoration(self): - "Draw no decoration at all." - - pass - - -### Pretty-printing classes. - -class PDFPrinter: - """Generic PDF Printer class. - - Does not do much, but write a PDF file created from - any ASCII input file. - """ - - outFileExt = '.pdf' - - - def __init__(self, options=None): - "Initialisation." - - self.data = None # Contains the input file. - self.inPath = None # Path of input file. - self.Layouter = PDFLayouter - - if type(options) != type(None): - self.options = options - else: - self.options = Options() - - - ### I/O. - - def readFile(self, path): - "Read the content of a file." - - if path == sys.stdin: - f = path - else: - f = open(path) - - self.inPath = path - - data = f.read() - o = self.options - self.data = re.sub('\t', ' '*o.tabSize, data) - f.close() - - - def formatLine(self, line, eol=0): - "Format one line of Python source code." - - font = ('Courier', 8) - self.layouter.setFillColorAndFont(Color(0, 0, 0), font) - self.layouter.putText(line) - - - def writeData(self, srcCodeLines, inPath, outPath=None): - "Convert Python source code lines into a PDF document." - - # Create a layouter object. - self.layouter = self.Layouter(self.options) - l = self.layouter - - # Loop over all tagged source lines, dissect them into - # Python entities ourself and let the layouter do the - # rendering. - splitCodeLines = string.split(srcCodeLines, '\n') - - ### Must also handle the case of outPath being sys.stdout!! - l.begin(inPath, len(splitCodeLines)) - l.beginDocument() - - for line in splitCodeLines: - l.beginLine() - self.formatLine(line) - l.endLine() - - l.endDocument() - l.end() - - - def writeFile(self, data, inPath=None, outPath=None): - "Write some data into a file." - - if inPath == sys.stdin: - self.outPath = sys.stdout - else: - if not outPath: - path = os.path.splitext(self.inPath)[0] - self.outPath = path + self.outFileExt - - self.writeData(data, inPath, outPath or self.outPath) - - - def process(self, inPath, outPath=None): - "The real 'action point' for working with Pretty-Printers." - - self.readFile(inPath) - self.writeFile(self.data, inPath, outPath) - - -class PythonPDFPrinter (PDFPrinter): - """A class to nicely format tagged Python source code. - - """ - - comm = 'COMMENT' - kw = 'KEYWORD' - strng = 'STRING' - ident = 'IDENT' - param = 'PARAMETER' - - outFileExt = '.pdf' - - - def __init__(self, options=None): - "Initialisation, calling self._didInit() at the end." - - if type(options) != type(None): - self.options = options - else: - self.options = Options() - - self._didInit() - - - def _didInit(self): - "Post-Initialising" - - # Define regular expression patterns. - s = self - comp = re.compile - - s.commPat = comp('(.*?)<' + s.comm + '>(.*?)</' + s.comm + '>(.*)') - s.kwPat = comp('(.*?)<' + s.kw + '>(.*?)</' + s.kw + '>(.*)') - s.identPat = comp('(.*?)<' + s.ident + '>(.*?)</' + s.ident + '>(.*)') - s.paramPat = comp('(.*?)<' + s.param + '>(.*?)</' + s.param + '>(.*)') - s.stPat = comp('(.*?)<' + s.strng + '>(.*?)</' + s.strng + '>(.*)') - s.strng1Pat = comp('(.*?)<' + s.strng + '>(.*)') - s.strng2Pat = comp('(.*?)</' + s.strng + '>(.*)') - s.allPat = comp('(.*)') - - cMatch = s.commPat.match - kMatch = s.kwPat.match - iMatch = s.identPat.match - pMatch = s.paramPat.match - sMatch = s.stPat.match - s1Match = s.strng1Pat.match - s2Match = s.strng2Pat.match - aMatch = s.allPat.match - - self.matchList = ((cMatch, 'Comm'), - (kMatch, 'Kw'), - (iMatch, 'Ident'), - (pMatch, 'Param'), - (sMatch, 'SimpleString'), - (s1Match, 'TripleStringBegin'), - (s2Match, 'TripleStringEnd'), - (aMatch, 'Rest')) - - self.Layouter = PythonPDFLayouter - - # Load fontifier. - self.tagFunc = loadFontifier(self.options) - - - ### - - def formatLine(self, line, eol=0): - "Format one line of Python source code." - - # Values for eol: -1:no-eol, 0:dunno-yet, 1:do-eol. - - for match, meth in self.matchList: - res = match(line) - - if res: - groups = res.groups() - method = getattr(self, '_format%s' % meth) - method(groups, eol) - break - - - def _formatIdent(self, groups, eol): - "Format a Python identifier." - - before, id, after = groups - self.formatLine(before, -1) - self.layouter.addIdent(id) - self.formatLine(after, eol) - - - def _formatParam(self, groups, eol): - "Format a Python parameter." - - before, param, after = groups - self.formatLine(before, -1) - self.layouter.addParam(before) - self.formatLine(after, eol) - - - def _formatSimpleString(self, groups, eol): - "Format a Python one-line string." - - before, s, after = groups - self.formatLine(before, -1) - self.layouter.addSimpleString(s) - self.formatLine(after, eol) - - - def _formatTripleStringBegin(self, groups, eol): - "Format a Python multi-line line string (1)." - - before, after = groups - self.formatLine(before, -1) - self.layouter.addTripleStringBegin() - self.formatLine(after, 1) - - - def _formatTripleStringEnd(self, groups, eol): - "Format a Python multi-line line string (2)." - - before, after = groups - self.layouter.addTripleStringEnd(before) - self.formatLine(after, 1) - - - def _formatKw(self, groups, eol): - "Format a Python keyword." - - before, kw, after = groups - self.formatLine(before, -1) - self.layouter.addKw(kw) - self.formatLine(after, eol) - - - def _formatComm(self, groups, eol): - "Format a Python comment." - - before, comment, after = groups - self.formatLine(before, -1) - self.layouter.addComm(comment) - self.formatLine(after, 1) - - - def _formatRest(self, groups, eol): - "Format a piece of a Python line w/o anything special." - - line = groups[0] - self.layouter.addRest(line, eol) - - - ### - - def writeData(self, srcCodeLines, inPath, outPath=None): - "Convert Python source code lines into a PDF document." - - # Create a layouter object. - self.layouter = self.Layouter(self.options) - l = self.layouter - - # Loop over all tagged source lines, dissect them into - # Python entities ourself and let the layouter do the - # rendering. - splitCodeLines = string.split(srcCodeLines, '\n') - l.begin(inPath, len(splitCodeLines)) - l.beginDocument() - - for line in splitCodeLines: - l.beginLine() - self.formatLine(line) - l.endLine() - - l.endDocument() - l.end() - - - def process(self, inPath, outPath=None): - "The real 'action point' for working with Pretty-Printers." - - self.readFile(inPath) - self.taggedData = self._fontify(self.data) - self.writeFile(self.taggedData, inPath, outPath) - - - ### Fontifying. - - def _fontify(self, pytext): - "" - - formats = { - 'rest' : ('', ''), - 'comment' : ('<%s>' % self.comm, '</%s>' % self.comm), - 'keyword' : ('<%s>' % self.kw, '</%s>' % self.kw), - 'parameter' : ('<%s>' % self.param, '</%s>' % self.param), - 'identifier' : ('<%s>' % self.ident, '</%s>' % self.ident), - 'string' : ('<%s>' % self.strng, '</%s>' % self.strng) } - - # Parse. - taglist = self.tagFunc(pytext) - - # Prepend special 'rest' tag. - taglist[:0] = [('rest', 0, len(pytext), None)] - - # Prepare splitting. - splits = [] - self._addSplits(splits, pytext, formats, taglist) - - # Do splitting & inserting. - splits.sort() - l = [] - li = 0 - - for ri, dummy, insert in splits: - if ri > li: - l.append(pytext[li:ri]) - - l.append(insert) - li = ri - - if li < len(pytext): - l.append(pytext[li:]) - - return string.join(l, '') - - - def _addSplits(self, splits, text, formats, taglist): - "" - - # Helper for fontify(). - for id, left, right, sublist in taglist: - - try: - pre, post = formats[id] - except KeyError: - # msg = 'Warning: no format ' - # msg = msg + 'for %s specified\n'%repr(id) - # sys.stderr.write(msg) - pre, post = '', '' - - if type(pre) != type(''): - pre = pre(text[left:right]) - - if type(post) != type(''): - post = post(text[left:right]) - - # len(splits) is a dummy used to make sorting stable. - splits.append((left, len(splits), pre)) - - if sublist: - self._addSplits(splits, text, formats, sublist) - - splits.append((right, len(splits), post)) - - -### Main - -def main(cmdline): - "Process command line as if it were sys.argv" - - # Create default options and initialize with argv - # from the command line. - options = Options() - options.updateWithContentsOfArgv(cmdline[1:]) - - # Print help message if desired, then exit. - if options.h or options.help: - print __doc__ - sys.exit() - - # Apply modest consistency checks and exit if needed. - cmdStr = string.join(cmdline, ' ') - find = string.find - if find(cmdStr, 'paperSize') >= 0 and find(cmdStr, 'paperFormat') >= 0: - details = "You can specify either paperSize or paperFormat, " - details = detail + "but not both!" - raise 'ValueError', details - - # Create PDF converter and pass options to it. - if options.input: - input = string.lower(options.input) - - if input == 'python': - P = PythonPDFPrinter - elif input == 'ascii': - P = PDFPrinter - else: - details = "Input file type must be 'python' or 'ascii'." - raise 'ValueError', details - - else: - P = PythonPDFPrinter - - p = P(options) - - # Display options if needed. - if options.v or options.verbose: - pass # p.options.display() - - # Start working. - verbose = options.v or options.verbose - - if options.stdout: - if len(options.files) > 1 and verbose: - print "Warning: will only convert first file on command line." - f = options.files[0] - p.process(f, sys.stdout) - else: - if verbose: - print 'py2pdf: working on:' - - for f in options.files: - try: - if verbose: - print ' %s' % f - if f != '-': - p.process(f) - else: - p.process(sys.stdin, sys.stdout) - except IOError: - if verbose: - print '(IOError!)', - - if verbose: - print - print 'Done.' - - -def process(*args, **kwargs): - "Provides a way of using py2pdf from within other Python scripts." - - noValOpts = 'h v verbose landscape stdout lineNum marcs help multiPage noOutline' - noValOpts = string.split(noValOpts, ' ') - - s = 'py2pdf.py' - for k, v in kwargs.items(): - if len(k) == 1: - s = s + ' -%s' % k - if k not in noValOpts: - s = s + ' %s' % v - elif len(k) > 1: - s = s + ' --%s' % k - if k not in noValOpts: - s = s + '=%s' % v - s = s + ' ' + string.join(args, ' ') - s = string.split(s, ' ') - - main(s) - - -### - -if __name__=='__main__': #NORUNTESTS - main(sys.argv) \ No newline at end of file diff --git a/bin/reportlab/tools/py2pdf/vertpython.jpg b/bin/reportlab/tools/py2pdf/vertpython.jpg deleted file mode 100644 index 8655230bf43d2a8532cefb9f3c44e222ad066447..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22872 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Ð?;C+>QEZ+hrwG>qzn^DlsX)<Ac(y(E(epL9GQ9 z#YjWWmr0_#WT}Yc&LpXoBVm%?F93^0HdW73|LtaWry}cI5zeP()D@8c06m%Jzhc}U zs>ff<9RE+uqu2e-0(5K0Xn*`fk69J2tGKmgW`1G5wmAo45Z^B9(G^#zNDU&jc3?sR zd7CEQnY|6lmA2AAuzR_sWOhhw7;z!Omv*WRPJ*zqilDW<o;zzcANV*Q9jay}6pXaK z8*g5SW4^he6_AjM%+iSrZU8W5Rc|xOoZ?RAr_kX;9)%A2&2Yl?d-c%8J&?>;z!v?W z79@S+Iizop|2|uv$T)CLgf?>Sa;uboImf}ImF($rwVsZ48=v5{Xw_c;3$VC#I+Rg! z+bQlB;A7dB!ZmxnC$=$O4%P$74<BM%{r2&7<{5oTJZ6O!UvFXQvmv%kX^bW9hqDN$ zisF@!7Pc8gmY!GmdR$0;AWI6H#J?^fe?qSR=I;ONh2)QM>0xHd%+xC<g}xAux#Re% zb6_Iyp39v_UWZKBw-2kyHKeNXe9S6AT=)@YLC>zt$^jQJu_0DntI(pCG|q(qCIKFx zsQ?}9$8BFEzY4!0Y;#aidXF%Zaen<#wgzq+ANqVhTccLTOlO2|IWd?NJc+||%f}s7 zeh)**IW;cW@rS}D+#Nc&g+96S-o(MVe1h8XNJbF;(?vq$IoV9%D#%f>wCQk_qE8fQ za$WN}c2F#eI2&c@i?+{T5*tWsyx)0%-^H(y){fjo$0WLTLCKSb)wv2((z}U8^<eP< zxJ~FexnlSyiuh;$S0wRwTixHA9&TFEg)`gx#=8UTgFP^@V5<5z7uP;H?q7fuaQ7+o zFTg!kEOAVv)6EwWBZl!}1ug*yAQ-18m(R7xY3cIj4>`)C7q1=y7RlSh@9KUozNh7- zQ6&A!)+9v{1MRL;q~=U!-L&P`$dT48!P}Lp6l!+o)g4rXignXI?x&gK0Q@Z2Nj8vm zLv1S~4z0Pa*0cwd*3P(*`vmLv?$hf+2ynNYZ{|14tsl+^zPp@MN(YB9Ji))^-r0)Z z_hckiyeIb6Y}_>f470LRM3N`lp6;$$sfv~;v;24L|8pt)&G7r@8Q|80emWgpysF;L zk~A*@#dXutQ1I=PvVA#UrCA%p1YW2IX1;w6;R8ea$i=k!4R-dGS198MD60qGTziUk zz)zIoCYHJ7y==mrI18O{N2Q7x(Gz4?dTc}Z{ZkjmY8tmCVWep+gO)c_Lt=ZhMXr{O z{kW00heSO;B*dbkx0Z%UKhPeLm}g%uzA;u9!qjPtC+tEU<0@{|k}kcm9$^>R&5s)N zd8@)Wh}boI&{ETo=0XSWVHSlj%e6ftJkVy#xZDZpcJ6&Xb`%N2*FW|@6*nm0F_t`m zWfj|WU7q#hh)HgZ3`4`pi-(`cvD&{j;*)=#(RGVnuaEOEIa9AFLr-@gw^(AH$w4gV z3&wP*-V?UJG1kVHY~BpdRL<93?|wbsV48OCe)_jq23^k1^>YqnYGsDArNJ%OU&$1; zfm!irxYmd<rH`ZvU)hv4ev_Tia&;cfdrN~v7B6z?9Va$i+W%BnF1Go#!g;ydNdEt* zJkswN7GwMaPGw7i%$ewE4Jp?=UPbrieV{@NCBtd@{-%os7pJ~j4Bh_n$AcR0B~P6$ z0WV$TWPBOW!C>T^SY_2Tt5GRKR||$MQLpl+S#$~q=D}<TxFj;U7G`<2%y?IWr2c0t zhbktK`AR#PdGUDy$~X0iCtp~JcL>%O?;-DwM4hL}#5gfZcUC^`r3A)1Mcr|cINw0} z*Z-mp|6dzT^n0>^+ClM1lDZuuN>__1Dq+_!TYcrabWL{15VHvY+C5yz|6ErqUOl=! zeu$NV8>yo#(U_Bhc$YC=6><5@Rl~s%6!^BsHB1wyJ!^RWSuTMfHT&+@$!8(C1aRPf yXOOEb64vBNBHB0q0&pmv2{u;b=WS`o#cjI@8fF#iE3=lQ{2M>=ham84;=cflPq#<_ diff --git a/bin/reportlab/tools/pythonpoint/README b/bin/reportlab/tools/pythonpoint/README deleted file mode 100644 index f6d8808bdab..00000000000 --- a/bin/reportlab/tools/pythonpoint/README +++ /dev/null @@ -1,29 +0,0 @@ -PythonPoint is a utility for generating PDF slides from -a simple XML format - "PythonPoint Markup Language". - -This is early days. It lets you produce quite sophisticated -output, but the DTD will undoubtedly evolve and change. -However, I want people to be able to use it this summer so am -releasing now. - -It is part of the ReportLab distribution; if you have managed -to run the ReportLab tests, and have installed the Python -Imaging Library, usage should be straightforward. PIL is not -required to make slides, but the demo will have a few blanks if -you omit it. - -To use, cd to the pythonpoint directory and execute: - pythonpoint.py pythonpoint.xml -This will create pythonpoint.pdf, which is your manual. - -You can also try 'monterey.xml', which is a talk I gave -at the O'Reilly Open Source conference in Monterey -in summer 1999. - -We have issues to resolve over module load paths; -the easiest solution for now is to put the PythonPoint -directory on your path; work in a new directory; -and explicitly include the paths to any custom shapes, -and style sheets you use. - -- Andy Robinson, 6 April 2000 \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/__init__.py b/bin/reportlab/tools/pythonpoint/__init__.py deleted file mode 100644 index c4b994f848b..00000000000 --- a/bin/reportlab/tools/pythonpoint/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/__init__.py diff --git a/bin/reportlab/tools/pythonpoint/customshapes.py b/bin/reportlab/tools/pythonpoint/customshapes.py deleted file mode 100644 index 792823e3cb8..00000000000 --- a/bin/reportlab/tools/pythonpoint/customshapes.py +++ /dev/null @@ -1,298 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/customshapes.py -__version__=''' $Id$ ''' - -# xml parser stuff for PythonPoint -# PythonPoint Markup Language! - -__doc__=""" -This demonstrates a custom shape for use with the <customshape> tag. -The shape must fulfil a very simple interface, which may change in -future. - -The XML tag currently has this form: - <customshape - module="customshapes.py" - class = "MyShape" - initargs="(100,200,3)" - /> - -PythonPoint will look in the given module for the given class, -evaluate the arguments string and pass it to the constructor. -Then, it will call - - object.drawOn(canvas) - -Thus your object must be fully defined by the constructor. -For this one, we pass three argumenyts: x, y and scale. -This does a five-tile jigsaw over which words can be overlaid; -based on work done for a customer's presentation. -""" - - -import reportlab.pdfgen.canvas -from reportlab.lib import colors -from reportlab.lib.corp import RL_CorpLogo -from reportlab.graphics.shapes import Drawing - -## custom shape for use with PythonPoint. - -class Jigsaw: - """This draws a jigsaw patterm. By default it is centred on 0,0 - and has dimensions of 200 x 140; use the x/y/scale attributes - to move it around.""" - #Using my usual bulldozer coding style - I am sure a mathematician could - #derive an elegant way to draw this, but I just took a ruler, guessed at - #the control points, and reflected a few lists at the interactive prompt. - - def __init__(self, x, y, scale=1): - self.width = 200 - self.height = 140 - self.x = x - self.y = y - self.scale = scale - - - def drawOn(self, canvas): - canvas.saveState() - - canvas.setFont('Helvetica-Bold',24) - canvas.drawString(600, 100, 'A Custom Shape') - - canvas.translate(self.x, self.y) - canvas.scale(self.scale, self.scale) - self.drawBounds(canvas) - - self.drawCentre(canvas) - self.drawTopLeft(canvas) - self.drawBottomLeft(canvas) - self.drawBottomRight(canvas) - self.drawTopRight(canvas) - - canvas.restoreState() - - - def curveThrough(self, path, pointlist): - """Helper to curve through set of control points.""" - assert len(pointlist) % 3 == 1, "No. of points must be 3n+1 for integer n" - (x,y) = pointlist[0] - path.moveTo(x, y) - idx = 1 - while idx < len(pointlist)-2: - p1, p2, p3 = pointlist[idx:idx+3] - path.curveTo(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]) - idx = idx + 3 - - - def drawShape(self, canvas, controls, color): - """Utlity to draw a closed shape through a list of control points; - extends the previous proc""" - canvas.setFillColor(color) - p = canvas.beginPath() - self.curveThrough(p, controls) - p.close() - canvas.drawPath(p, stroke=1, fill=1) - - - def drawBounds(self, canvas): - """Guidelines to help me draw - not needed in production""" - canvas.setStrokeColor(colors.red) - canvas.rect(-100,-70,200,140) - canvas.line(-100,0,100,0) - canvas.line(0,70,0,-70) - canvas.setStrokeColor(colors.black) - - - def drawCentre(self, canvas): - controls = [ (0,50), #top - - #top right edge - duplicated for that corner piece - (5,50),(10,45),(10,40), - (10,35),(15,30),(20,30), - (25,30),(30,25),(30,20), - (30,15),(35,10),(40,10), - (45,10),(50,5),(50,0), - - #bottom right edge - (50, -5), (45,-10), (40,-10), - (35,-10), (30,-15), (30, -20), - (30,-25), (25,-30), (20,-30), - (15,-30), (10,-35), (10,-40), - (10,-45),(5,-50),(0,-50), - - #bottom left - (-5,-50),(-10,-45),(-10,-40), - (-10,-35),(-15,-30),(-20,-30), - (-25,-30),(-30,-25),(-30,-20), - (-30,-15),(-35,-10),(-40,-10), - (-45,-10),(-50,-5),(-50,0), - - #top left - (-50,5),(-45,10),(-40,10), - (-35,10),(-30,15),(-30,20), - (-30,25),(-25,30),(-20,30), - (-15,30),(-10,35),(-10,40), - (-10,45),(-5,50),(0,50) - - ] - - self.drawShape(canvas, controls, colors.yellow) - - - def drawTopLeft(self, canvas): - controls = [(-100,70), - (-100,69),(-100,1),(-100,0), - (-99,0),(-91,0),(-90,0), - - #jigsaw interlock - 4 sections - (-90,5),(-92,5),(-92,10), - (-92,15), (-85,15), (-80,15), - (-75,15),(-68,15),(-68,10), - (-68,5),(-70,5),(-70,0), - (-69,0),(-51,0),(-50,0), - - #five distinct curves - (-50,5),(-45,10),(-40,10), - (-35,10),(-30,15),(-30,20), - (-30,25),(-25,30),(-20,30), - (-15,30),(-10,35),(-10,40), - (-10,45),(-5,50),(0,50), - - (0,51),(0,69),(0,70), - (-1,70),(-99,70),(-100,70) - ] - self.drawShape(canvas, controls, colors.teal) - - - def drawBottomLeft(self, canvas): - - controls = [(-100,-70), - (-99,-70),(-1,-70),(0,-70), - (0,-69),(0,-51),(0,-50), - - #wavyline - (-5,-50),(-10,-45),(-10,-40), - (-10,-35),(-15,-30),(-20,-30), - (-25,-30),(-30,-25),(-30,-20), - (-30,-15),(-35,-10),(-40,-10), - (-45,-10),(-50,-5),(-50,0), - - #jigsaw interlock - 4 sections - - (-51, 0), (-69, 0), (-70, 0), - (-70, 5), (-68, 5), (-68, 10), - (-68, 15), (-75, 15), (-80, 15), - (-85, 15), (-92, 15), (-92, 10), - (-92, 5), (-90, 5), (-90, 0), - - (-91,0),(-99,0),(-100,0) - - ] - self.drawShape(canvas, controls, colors.green) - - - def drawBottomRight(self, canvas): - - controls = [ (100,-70), - (100,-69),(100,-1),(100,0), - (99,0),(91,0),(90,0), - - #jigsaw interlock - 4 sections - (90, -5), (92, -5), (92, -10), - (92, -15), (85, -15), (80, -15), - (75, -15), (68, -15), (68, -10), - (68, -5), (70, -5), (70, 0), - (69, 0), (51, 0), (50, 0), - - #wavyline - (50, -5), (45,-10), (40,-10), - (35,-10), (30,-15), (30, -20), - (30,-25), (25,-30), (20,-30), - (15,-30), (10,-35), (10,-40), - (10,-45),(5,-50),(0,-50), - - (0,-51), (0,-69), (0,-70), - (1,-70),(99,-70),(100,-70) - - ] - self.drawShape(canvas, controls, colors.navy) - - - def drawBottomLeft(self, canvas): - - controls = [(-100,-70), - (-99,-70),(-1,-70),(0,-70), - (0,-69),(0,-51),(0,-50), - - #wavyline - (-5,-50),(-10,-45),(-10,-40), - (-10,-35),(-15,-30),(-20,-30), - (-25,-30),(-30,-25),(-30,-20), - (-30,-15),(-35,-10),(-40,-10), - (-45,-10),(-50,-5),(-50,0), - - #jigsaw interlock - 4 sections - - (-51, 0), (-69, 0), (-70, 0), - (-70, 5), (-68, 5), (-68, 10), - (-68, 15), (-75, 15), (-80, 15), - (-85, 15), (-92, 15), (-92, 10), - (-92, 5), (-90, 5), (-90, 0), - - (-91,0),(-99,0),(-100,0) - - ] - self.drawShape(canvas, controls, colors.green) - - - def drawTopRight(self, canvas): - controls = [(100, 70), - (99, 70), (1, 70), (0, 70), - (0, 69), (0, 51), (0, 50), - (5, 50), (10, 45), (10, 40), - (10, 35), (15, 30), (20, 30), - (25, 30), (30, 25), (30, 20), - (30, 15), (35, 10), (40, 10), - (45, 10), (50, 5), (50, 0), - (51, 0), (69, 0), (70, 0), - (70, -5), (68, -5), (68, -10), - (68, -15), (75, -15), (80, -15), - (85, -15), (92, -15), (92, -10), - (92, -5), (90, -5), (90, 0), - (91, 0), (99, 0), (100, 0) - ] - - self.drawShape(canvas, controls, colors.magenta) - - -class Logo: - """This draws a ReportLab Logo.""" - - def __init__(self, x, y, width, height): - logo = RL_CorpLogo() - logo.x = x - logo.y = y - logo.width = width - logo.height = height - self.logo = logo - - def drawOn(self, canvas): - logo = self.logo - x, y = logo.x, logo.y - w, h = logo.width, logo.height - D = Drawing(w, h) - D.add(logo) - D.drawOn(canvas, 0, 0) - - -def run(): - c = reportlab.pdfgen.canvas.Canvas('customshape.pdf') - - J = Jigsaw(300, 540, 2) - J.drawOn(c) - c.save() - - -if __name__ == '__main__': - run() \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/demos/htu.xml b/bin/reportlab/tools/pythonpoint/demos/htu.xml deleted file mode 100644 index 56cb92457dc..00000000000 --- a/bin/reportlab/tools/pythonpoint/demos/htu.xml +++ /dev/null @@ -1,96 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no"?> -<!DOCTYPE presentation SYSTEM "../pythonpoint.dtd"> -<presentation filename="htu.pdf"> - <stylesheet module="htu" function="getParagraphStyles"/> - <section name="Main"> - <rectangle x="20" width="96" y="20" height="555" fill="ReportLabBlue"/> - <infostring size="14" align="right" x="800" y="36"> - © 2002, H. Turgut UYAR - </infostring> - <slide title="New Features" id="Slide000"> - <frame x="120" y="72" width="700" height="418" leftmargin="36" rightmargin="36"> - <image filename="pplogo.gif" width="200" height="150"/> - <spacer height="30"/> - <para style="Title">New features in PythonPoint</para> - <para style="Author">H. Turgut Uyar</para> - <para style="EMail">uyar@cs.itu.edu.tr</para> - </frame> - </slide> - <slide title="TrueType Support" id="Slide002"> - <frame x="120" y="515" width="700" height="60" leftmargin="36" rightmargin="36"> - <para style="Heading1">TrueType Support</para> - </frame> - <frame x="120" y="72" width="700" height="418" leftmargin="36" rightmargin="36"> - <para>PythonPoint can read UTF-8 encoded input files and produce - correct output (provided your font has all necessary - characters). That enables you, for example, to have native (in my - case, Turkish) characters in your document:</para> - <para style="Indent"> - <!-- if you have any trouble with these, execute the lines below - to find out what we really intended - >>> turkishChars = u"\u011e \u011f \u0130 \u0131 \u015e \u015f" - >>> print turkishChars - >>> print turkishChars.encode("utf-8") - þ ø Ä° ı ľ ĸ - >>> - --> - <font name="Serif" size="32"> 0 ^  1 _</font> - </para> - </frame> - </slide> - <slide title="Effects" id="Slide003"> - <frame x="120" y="515" width="700" height="60" leftmargin="36" rightmargin="36"> - <para style="Heading1">Effects</para> - </frame> - <frame x="120" y="72" width="700" height="418" leftmargin="36" rightmargin="36"> - <para>Paragraphs, images, tables and geometric shapes can now have - effectname, effectdirection, effectdimension, effectmotion and - effectduration attributes:</para> - <para style="Indent" effectname="Dissolve">A paragraph</para> - <image filename="pplogo.gif" width="80" height="60" effectname="Box"/> - <table style="table1" effectname="Split"> - Col1,Col2,Col3 - Row1Col1,Row1Col2,Row1Col3 - Row2Col1,Row2Col2,Row2Col3 - </table> - </frame> - <roundrect x="180" y="150" width="110" height="50" radius="15" fill="(0,1,0)" effectname="Glitter"/> - <string font="Helvetica" size="32" x="200" y="165" align="left" color="(1,0,0)" effectname="Blinds">String</string> - <line x1="248" y1="84" x2="520" y2="84" stroke="(1,0,1)" effectname="Wipe"/> - </slide> - <slide title="Printing" id="Slide004" outlinelevel="1"> - <frame x="120" y="515" width="700" height="60" leftmargin="36" rightmargin="36"> - <para style="Heading1">Printing</para> - </frame> - <frame x="120" y="72" width="700" height="418" leftmargin="36" rightmargin="36"> - <para>Be careful when using effects: A new slide is created for - each effect, so DON'T print the resulting PDF file.</para> - <para effectname="Box">new command-line option: --printout</para> - <para style="Indent">produces printable PDF</para> - </frame> - </slide> - <slide title="New Paragraph Styles" id="Slide005"> - <frame x="120" y="515" width="700" height="60" leftmargin="36" rightmargin="36"> - <para style="Heading1">New Paragraph Styles</para> - </frame> - <frame x="120" y="72" width="700" height="418" leftmargin="36" rightmargin="36"> - <para style="Bullet">Bullet2 - Second level bullets</para> - <para style="Bullet2">Here's an example</para> - <para style="Bullet2">Or an example with a longer text to see - how it wraps at the end of each line</para> - <para style="Bullet">Author and EMail</para> - <para style="Bullet2">See the cover page for examples</para> - <para style="Bullet">They have to be in the style file, so either use - the htu style file or edit your style file</para> - </frame> - </slide> - <slide title="ToDo" id="Slide006"> - <frame x="120" y="515" width="700" height="60" leftmargin="36" rightmargin="36"> - <para style="Heading1">ToDo</para> - </frame> - <frame x="120" y="72" width="700" height="418" leftmargin="36" rightmargin="36"> - <para style="Bullet">Unicode chars in the outline</para> - </frame> - </slide> - </section> -</presentation> diff --git a/bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 b/bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 deleted file mode 100644 index 7d55afcf348..00000000000 --- a/bin/reportlab/tools/pythonpoint/demos/leftlogo.a85 +++ /dev/null @@ -1,53 +0,0 @@ -BI -/W 103 /H 68 /BPC 8 /CS /RGB /F [/A85 /Fl] -ID -Gb"/*cYjIe']/l*^;)UQj:Ec+nd!,-,*G/5;0"E.+@qg.\92h2HbiE)jC5*M -$a,@:RoXB@WPjMu=[F;88l[?@OO=6qV,HNNiL#(cac+!&rF)2^DHJ?_:WQkn -1[fb`hOBr--B>FoSDXMt35Gnj6n\3XT8sf/Y%-NPLqg'pmQ+*EiV6D:,$akf -!i2MA/A-"n[R.R)S!ePi$u('8mrCG8[3:WLlB[amBYAS4hR2*f7tR;&\%]Ld -ZX5^f6Qj@\h7^FuQ(">[ml32FW<uUbK'hr2?[K]^D"^NBW@O0*-#'S9X\g_F -SYE1G3(8PLW#-KN>hJG;Z)t2oADV4[H6GbW.mH`MDlesQZO5+Q5)]O<r(Z![ -VmGDC"b6h]?JZB+V33e(TZD-HKE7<2d\OKT7macAe_OQDp+mO))_2[22<Jb% -\k]o7Be+6"nn[fbQR6Dm77(ij1n4FYflokM/!o_(c;V/"XKVZ![CCJOTo*G. -`L+#cL'lYP!_1B2q!p=#[76oJ;FF]do.OhJK1R!%SC-8k#a*AX1,`02mU%7f -4ap-1K!c.O!DQb:1k61fiAer=f)fd@jQ]"MQAu[\kB-D?g2*"JR/O2_H!SD2 -_UR(81;)KaB24OhoM!X%1D9gR5[b2,[#9dJ@_(:B_l^(Z.ae[g,)m+0jml?6 -1`=Z#U4jpgmeoc&B&Bh*B5]<?;J'XU$Cj3P@rO\[Dpm@6'?OID%os>Wf2G9[ -U9W99*2'#PD+cIXnp"J"(BrY^=(rg;ITf("c#0"S<;fjK,APV+p9p+05LO&X -jQNf`8c26f]J54N'k#I9=Bt4]e&6gN'#S[nK3*VhQ!`PpXcJ0i<sYK4+orF( --qFEU#H1b4O<Lc=k3J0Qc(!7M1PjL=,1VdG4FR'urjd<)3iWt^plJ*3A8+L' -[RqBu+MoI1>:J_2rC@=<l=3,MSD5$[mUNC$200Kld\2C9?P]=X&kbd;iMr:d -o'[-mD9bK"`5ki%Rn/GH-sj\3d>>X\5dUbEcCZ?\6O)+F/35ZJD6R$H^-Z!R -5;8=l//9SUkC;LhQ*?di<'6e&/BNIqm(%(S<Q9IJ\ug@o;(c@0gW3p.>DYXJ -p%%Z+J.IshkX$tLPOD'"jSV#/6iY?QI>8P-%o0k3`e#X33RJS`f'D!D2rTJ- -(4Znb03&koo>X[bKs.qpBOkU,K<BP+F35$P(IIZK&F^U8SNceK*`.Lp3\Yth -DiF4Hr/;0#,ub"@Q:)]U%,YT(ckW`\_GIkW$"ms@es$i8@I16iOCr.d$$Y.g -lt!Gp#$'#T\$t,Q\[.aW7+H@h2YX0iR)rr%kf!@*YlEtPBAVgU?LDQKOonm1 -B')C@,\ZZ]hm%8:#FPcb@"6UVZ;n1r]SVJ@31k_=Ze=g?f.0oD@hah4[KNj@ -Y?=oK%;PJ-r]m9E!9l7$=njO\OC'mrIFY!$\9b]:i58?larmF9MMaHLqiJg# -IB0KR59(W[_5%Vd#snt]Q_.Xfi4?Di=(.*eYNQ=`K+75LcsK4LY:J+1W2<%\ -WMqcR6r^&[74V%[O)#1M;LC>CCO3S$2.Gr@<NBm*k!PRPNjsj2TdGq%F$)'# -BB8Yk"/]I+#fCt.!D^Rm3_Or$I#HSsYqbZn*2g3lO-N1eSNoo+g"G+^J1U7' -nar#WZZk-_KTbZXJDIN#>e8E=$On&hUiAB2[.;G@FQf7_<g_,@/;qjYEf-l" -#p0YU5nQ`u=mBQ:q.h>K1&D6RJ.Ih@-56Ss<i&p->&B1q:\)MgJ8P4Sa-(Fm -J"d8Z]Mf`Q$G^\tK@pQWQs3iRB[KP=+H&@+<eJVa9[CPgFV$Fi[oV1K)4R#' -c<\Qi:XK&nEqpb'1e<d^hQhLY)Tul_%_ng0bnt*d%h<7*Qs?U)'_dL=Cd,sO -0&V5g\%1)CmnbV\;N*DE54\tJOeXg4rPH2DR5(:!'u#RAS?,"T/H%+"M[]cm -`\R90YD#$o6A*TZ?>iH@XGFQ'#n!DJ$)Mc?"U#od!oYl3En7nT"7UC,,Q&Hr -9.SPlHak6s)J0;k;hQC:k82'=COeskp&dB-#@RrY1B=H^'L=,;9uJssq%XGf -h/fXmq>1G\?k6ZH*GSk&M)?&TJ?rYW.9f!gdM2&:Kr51A3^MX?GV'T@$h)Da --RIIcpl8fhiQr=`\k[7;3`X"d!or<JS3FXO\QVoW+fhW8&9W*[dZTpJJq9`f -6/.R;@NP%LrsQia^)8D2Q/J^E"YFmbQNj[eIj8-teIq#\bkMZYNP]`Q,`+Fc -^kG%9V@ulcF&5&o0][1B%)LoGUI()Z`kMbB+q&dLTdLnu?sC!U[nC@bMJ8S( -UJsA@G[Jh[0ebiL<eAkS@8(tI.Xj"IO4#17pl<F>(jb1YM>"Ppi`,67@]M"d -+mXd;WRE"IKX@n&dNNV/55(]`==+_r?K5bp?$^4Pn/[2ncL(/l#CfSYAg:iN -@3j5R$qtm;AIF7k%^ak'%4Z1354d"1VT\g]cm[^hI.Yf>DcLS\rq+As2F?LI -'gq+/_o9&kK,4@]qbdfA8FauP;!N'$5J@1P/\Th=9i^\b$/-6-kL#$E&39t% -/O[1Fm`/F?!q;%bB')DM`KY&D!,6)lG5TQ88ejHiTWKt;3_8:@M!NnP'D2s$ -2Q25"dX)7pQ%VUq+r1%.iMQiLR#&.@\:Pe96FEbkgLu`Fb;!#d,DPC-H/d?u -^']=P86?6qr/@_/T-T0?,+>brI`?=13["Y(3DYSMX7UAUk0'!l!aPN<dOAp/ -2\gYH7?Iq;ln?q8(%Ag:SW86?Kti9[-7,Lh5fc*A\=4l^Pg&oj%apAq0i?"b -jdfr6IBIK$,+s'>4$e\f&(i2TTrbVuOYhc05Rko:?UP0</LE?hksnprYNQW[ -MB4QH,ADP93Y%HB:l-XOR0[N<?s68?2);KK4cL5m>9gLc(-/ni'Q6T.B&8]k -#a_e\0FTMV`IT;W7M>_'NT_f=~> -EI diff --git a/bin/reportlab/tools/pythonpoint/demos/leftlogo.gif b/bin/reportlab/tools/pythonpoint/demos/leftlogo.gif deleted file mode 100644 index 2d9c990ba7aa34a17f5fc2f36c931a4f300aec76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2198 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 Y%U`dMsl5^P=RyZeV7hck6@dT%J4FQNyZ`_I diff --git a/bin/reportlab/tools/pythonpoint/demos/lj8100.jpg b/bin/reportlab/tools/pythonpoint/demos/lj8100.jpg deleted file mode 100644 index be3c6183d3b1cc5fd964caaf427007f9974844ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12463 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 AJOBUy diff --git a/bin/reportlab/tools/pythonpoint/demos/monterey.xml b/bin/reportlab/tools/pythonpoint/demos/monterey.xml deleted file mode 100644 index 41bd979fa67..00000000000 --- a/bin/reportlab/tools/pythonpoint/demos/monterey.xml +++ /dev/null @@ -1,306 +0,0 @@ -<?xml version="1.0" encoding="iso-8859-1" standalone="no"?> -<!-- edited with XMLSPY v2004 rel. 3 U (http://www.xmlspy.com) by Andy Robinson (ReportLab Europe Ltd.) --> -<!DOCTYPE presentation SYSTEM "../pythonpoint.dtd"> -<presentation filename="monterey.pdf"> - <stylesheet module="modern" function="getParagraphStyles"/> - <!-- - sections define a colection of stuff to be drawn on all pages they contain. - They must begin with a graphics list - --> - <section name="Main"> - <fixedimage filename="vertpython.gif" height="595" width="144" x="0" y="0"/> - <slide title="Cover" id="Slide001" effectname="Wipe"> - <!-- - put stuff here to be drawn on the page directly - --> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para/> - <para/> - <para/> - <para style="Title"> - Printing with Python - </para> - <image filename="lj8100.jpg"/> - <para style="BigCentered"> - Andy Robinson, Robinson Analytics Ltd. - </para> - <para style="BigCentered"> - O'Reilly Python Conference, Monterey, 24th August 1999 - </para> - </frame> - </slide> - <slide title="Background" id="Slide002" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Background to the project: - </para> - <para style="Bullet"> - London-based consultant and corporate developer - </para> - <para style="Bullet"> - want to do neat Python stuff in the daytime - </para> - <para style="Bullet"> - working for many years on financial modelling - </para> - <para style="Bullet"> - this is one of 6 modules in that system - </para> - <para style="Bullet"> - quickest to deliver, offers very wide benefits - </para> - <para style="Bullet"> - 25% of architecture done, but already very useful - </para> - <para style="Bullet"> - Release early, release often! - </para> - </frame> - </slide> - <slide title="Goal" id="Slide003" effectname="Wipe"> - <fixedimage filename="vertpython.gif" height="595" width="144" x="0" y="0"/> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Goal: - </para> - <para style="BodyText"> - A Reporting Package on the Next Curve... - </para> - <para style="Bullet"> - Report on objects, not databases - </para> - <para style="Bullet"> - Scalable to million page runs - </para> - <para style="Bullet"> - Light enough to embed in any application - </para> - <para style="Bullet"> - Allow reuse of graphical objects across reports - </para> - <para style="Bullet"> - Open and extensible on several levels - </para> - <para style="Bullet"> - Publication quality - </para> - <para style="Bullet"> - Support all the world's languages - one day - </para> - </frame> - </slide> - <slide title="Portable Document Format" id="Slide004" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Portable Document Format - </para> - <para style="Italic"> - The New PostScript - </para> - <para style="Bullet"> - Free readers on all platforms - </para> - <para style="Bullet"> - Better than paper - view it, email it, print it - </para> - <para style="Bullet"> - 'Final Form' for documents - </para> - <para style="Bullet"> - High end solution - no limits to quality - </para> - <para style="Italic"> - ...but you can't learn it in Notepad! - </para> - </frame> - </slide> - <slide title="PDFgen and PIDDLE" id="Slide005" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para/> - <para/> - <para style="Title"> - PDFgen and PIDDLE - </para> - </frame> - </slide> - <slide title="PDFgen layer" id="Slide006" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Layer One - PDFgen - </para> - <para style="Bullet"> - makes PDF documents from pure Python - </para> - <para style="Bullet"> - wraps up PDF document structure - </para> - <para style="Bullet"> - exposes nice effects - page transitions, outline trees (RSN!) - </para> - <para style="Bullet"> - low level graphics promitives (postscript imaging model) - </para> - <para style="Bullet"> - Fine control of text placement - </para> - <para style="Bullet"> - Supports Asian text - </para> - <para style="Bullet"> - Supports coordinate transformations and clipping - </para> - <para style="Italic"> - ...a foundation for other apps to build on - </para> - </frame> - </slide> - <slide title="PDF Image Suport" id="Slide007" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - PDFgen Image Support - </para> - <para> - Python Imaging Library and zlib do all the work - many formats. - Images cached (like .pyc files) - very fast builds possible. - </para> - <image filename="python.gif" width="588" height="200"/> - </frame> - </slide> - <slide title="Layer Two: PIDDLE" id="Slide008" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Layer Two: PIDDLE - </para> - <para style="Italic"> - Plug In Drawing, Does Little Else - </para> - <para style="Bullet"> - Easy Graphics Library - </para> - <para style="Bullet"> - Abstract Canvas Interface - </para> - <para style="Bullet"> - Pluggable Back Ends - </para> - <para style="Bullet"> - Same code can do viewing and printing - </para> - <para style="Bullet"> - Standard set of test patterns - </para> - <para style="Bullet"> - Uses Python Imaging Library - </para> - <para style="BodyText"> - Back ends includeTkinter, wxPython, Mac, Pythonwin, PDF, PostScript, - OpenGL, Adobe Illustrator and PIL. Really easy to add a new one! - </para> - </frame> - </slide> - <slide title="Layer Three: PLATYPUS" id="Slide009" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Layer Three: PLATYPUS - </para> - <para style="Italic"> - "Page Layout And Typography Using Scripts" - </para> - <para style="BodyText"> - Trying to work out the API now. Key Concepts: - </para> - <para style="Bullet"> - Drawable objects - can 'wrap to fit' - </para> - <para style="Bullet"> - Frames on page - </para> - <para style="Bullet"> - Frame consumes from a list of drawables until full - </para> - <para style="Bullet"> - Document Models e.g. SimpleFlowDocument - </para> - <para style="BodyText"> - XSL Flow Object model may be a good target - </para> - </frame> - </slide> - <slide title="Drawable Objects" id="Slide010" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Drawable Objects - </para> - <para style="BodyText"> - Next layer of PIDDLE extensibility. - Each draws in its own coodinate system - </para> - <para style="Bullet"> - paragraph, image, table - </para> - <para style="Bullet"> - chart libraries - </para> - <para style="Bullet"> - diagrams - </para> - <para style="BodyText"> - Open Source - let people contribute new ones. - Anything you could have in a view can be a new - drawable type. - </para> - </frame> - </slide> - <slide title="Style Sheets" id="Slide011" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Style Sheet Driven - </para> - <para style="BodyText"> - Styles use instance inheritance - </para> - <para style="Bullet"> - Paragraph Styles - Style Sheet Compulsory! - </para> - <para style="Bullet"> - Text Styles within a paragraph - </para> - <para style="Bullet"> - Table and Table Cell Styles - </para> - </frame> - </slide> - <slide title="Vision" id="Slide012" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - Vision - </para> - <para style="Bullet"> - XML to PDF in one step - </para> - <para style="Bullet"> - Publish to web and print from same source - </para> - <para style="Bullet"> - Financial and Scientific reporting tool - </para> - <para style="Bullet"> - Embedded reporting engine - </para> - <para style="Bullet"> - Volume reporting tool for business - </para> - </frame> - </slide> - <slide title="PythonPoint" id="Slide013" effectname="Wipe"> - <frame height="432" x="160" y="72" rightmargin="0" width="600" leftmargin="36"> - <para style="Heading2"> - PythonPoint - </para> - <para style="Italic"> - How I made this presentation... - </para> - </frame> - </slide> - </section> -</presentation> diff --git a/bin/reportlab/tools/pythonpoint/demos/outline.gif b/bin/reportlab/tools/pythonpoint/demos/outline.gif deleted file mode 100644 index 4a294b592776088abf34d7ec109dc6f0e2af4210..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12918 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 CPpwq| diff --git a/bin/reportlab/tools/pythonpoint/demos/pplogo.gif b/bin/reportlab/tools/pythonpoint/demos/pplogo.gif deleted file mode 100644 index d0344497ca50900105b2671887a3410eb718ffa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3429 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 diff --git a/bin/reportlab/tools/pythonpoint/demos/python.gif b/bin/reportlab/tools/pythonpoint/demos/python.gif deleted file mode 100644 index c68e1e4ec37622e4affb9cdbb79c28de1399a738..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125666 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%0AxFJg z<~#pVly~q=>gz@j83Mdl8*QK()!fGxd6DC^^tALCj;46)s{oD)wTQdzjLxPFoMpdc z>owH=&R6qCcod_z$;|-qCx$&4i!W583iYIID%@kuwx^?{_J3X4TIVNxOa609a<kgD z#Xtz)LTugvuYSB+BGIQrFqCir(IxumLcH|Qn0$ew9*iA>9Jn*kJPE#<=io%&dEk`u zr<Kd{x`>ayIJpUy<#h2+6fvw=&X(sZt;hIxXttWxyoIlcF6UWbkL?6)=Ucu*u>lu2 z=8$i2FmAy4OHuy1xQGMho~lDpG$D2lDkHYlUxPkW>6ns{F4Gd!)X)alY4sT4!hz7t z9O4Ne%%OqRs1F+%C9IhutnWFT?@inV5st{ir*3jeRV%K2!KICHLY+kOZ#d`fcglW@ z-l-u6AW0o5V6rmS6+kCyQ6R6xm4~Jv1X%4+5*0uYvG-J@xW)j17A2HolNgu|7@Z)- zL{rHkz|$MX1WT|B)Q}P1%Tz@Jbflj%x`H+d%36(ylK>uK62$?{=Sl5{F=Jx%kA+|; z)hI{{S~1q~sep~j3n`7817JFXTuH;MR-C}1P*0|6zGgsy04k49%VXAh>mZL?bb5$U zlCI|K16tQ>I*kgZ9EAeJ_=nEdVtNS2BzWaz+%5oLsKM=2;X)%e{uf%RJ%x@2B$0Zw z!?W0yiH#0syPF)$IZ{yknQg8<6%}iu?c1~zW%;XNvz)dUv#tK?MYE;lW^1a=x1bz} z$F%rJc3gqEyp#IPVQy~uCWgWs<27&R<X;%C#HD#g1%ucU5O+zFxJ6~uqqQK(0xtjQ z<0SFtMxw%R5@u?�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 diff --git a/bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml b/bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml deleted file mode 100644 index 15d1ad709b5..00000000000 --- a/bin/reportlab/tools/pythonpoint/demos/pythonpoint.xml +++ /dev/null @@ -1,1051 +0,0 @@ -<?xml version="1.0" encoding="iso-8859-1" standalone="no"?> -<!DOCTYPE presentation SYSTEM "../pythonpoint.dtd"> -<presentation filename="pythonpoint.pdf" pageDuration="3"> - <stylesheet module="standard" function="getParagraphStyles"/> - <title>PythonPoint Demonstration - Andy Robinson - Reportlab Sample Applications -
              - - - -
              - diff --git a/bin/reportlab/tools/pythonpoint/demos/spectrum.png b/bin/reportlab/tools/pythonpoint/demos/spectrum.png deleted file mode 100644 index 06747655056873c295693ed3f2e8db42f6909ad1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1855 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 diff --git a/bin/reportlab/tools/pythonpoint/pythonpoint.dtd b/bin/reportlab/tools/pythonpoint/pythonpoint.dtd deleted file mode 100644 index ec876f68387..00000000000 --- a/bin/reportlab/tools/pythonpoint/pythonpoint.dtd +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bin/reportlab/tools/pythonpoint/pythonpoint.py b/bin/reportlab/tools/pythonpoint/pythonpoint.py deleted file mode 100755 index 18ef842be0a..00000000000 --- a/bin/reportlab/tools/pythonpoint/pythonpoint.py +++ /dev/null @@ -1,1129 +0,0 @@ -#! /usr/bin/python2.3 - -""" -This is PythonPoint! - -The idea is a simple markup languages for describing presentation -slides, and other documents which run page by page. I expect most -of it will be reusable in other page layout stuff. - -Look at the sample near the top, which shows how the presentation -should be coded up. - -The parser, which is in a separate module to allow for multiple -parsers, turns the XML sample into an object tree. There is a -simple class hierarchy of items, the inner levels of which create -flowable objects to go in the frames. These know how to draw -themselves. - -The currently available 'Presentation Objects' are: - - The main hierarchy... - PPPresentation - PPSection - PPSlide - PPFrame - - PPAuthor, PPTitle and PPSubject are optional - - Things to flow within frames... - PPPara - flowing text - PPPreformatted - text with line breaks and tabs, for code.. - PPImage - PPTable - bulk formatted tabular data - PPSpacer - - Things to draw directly on the page... - PPRect - PPRoundRect - PPDrawingElement - user base class for graphics - PPLine - PPEllipse - -Features added by H. Turgut Uyar -- TrueType support (actually, just an import in the style file); - this also enables the use of Unicode symbols -- para, image, table, line, rectangle, roundrect, ellipse, polygon - and string elements can now have effect attributes - (careful: new slide for each effect!) -- added printout mode (no new slides for effects, see item above) -- added a second-level bullet: Bullet2 -- small bugfixes in handleHiddenSlides: - corrected the outlineEntry of included hidden slide - and made sure to include the last slide even if hidden - -Recently added features are: - -- file globbing -- package structure -- named colors throughout (using names from reportlab/lib/colors.py) -- handout mode with arbitrary number of columns per page -- stripped off pages hidden in the outline tree (hackish) -- new tag for speaker notes (paragraphs only) -- new tag for syntax-colorized Python code -- reformatted pythonpoint.xml and monterey.xml demos -- written/extended DTD -- arbitrary font support -- print proper speaker notes (TODO) -- fix bug with partially hidden graphics (TODO) -- save in combined presentation/handout mode (TODO) -- add pyRXP support (TODO) -""" - -import os, sys, imp, string, pprint, getopt, glob - -from reportlab import rl_config -from reportlab.lib import styles -from reportlab.lib import colors -from reportlab.lib.units import cm -from reportlab.lib.utils import getStringIO -from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfgen import canvas -from reportlab.platypus.doctemplate import SimpleDocTemplate -from reportlab.platypus.flowables import Flowable -from reportlab.platypus.xpreformatted import PythonPreformatted -from reportlab.platypus import Preformatted, Paragraph, Frame, \ - Image, Table, TableStyle, Spacer - - -USAGE_MESSAGE = """\ -PythonPoint - a tool for making presentations in PDF. - -Usage: - pythonpoint.py [options] file1.xml [file2.xml [...]] - - where options can be any of these: - - -h / --help prints this message - -n / --notes leave room for comments - -v / --verbose verbose mode - -s / --silent silent mode (NO output) - --handout produce handout document - --printout produce printout document - --cols specify number of columns - on handout pages (default: 2) - -To create the PythonPoint user guide, do: - pythonpoint.py pythonpoint.xml -""" - - -##################################################################### -# This should probably go into reportlab/lib/fonts.py... -##################################################################### - -class FontNameNotFoundError(Exception): - pass - - -class FontFilesNotFoundError(Exception): - pass - - -##def findFontName(path): -## "Extract a Type-1 font name from an AFM file." -## -## f = open(path) -## -## found = 0 -## while not found: -## line = f.readline()[:-1] -## if not found and line[:16] == 'StartCharMetrics': -## raise FontNameNotFoundError, path -## if line[:8] == 'FontName': -## fontName = line[9:] -## found = 1 -## -## return fontName -## -## -##def locateFilesForFontWithName(name): -## "Search known paths for AFM/PFB files describing T1 font with given name." -## -## join = os.path.join -## splitext = os.path.splitext -## -## afmFile = None -## pfbFile = None -## -## found = 0 -## while not found: -## for p in rl_config.T1SearchPath: -## afmFiles = glob.glob(join(p, '*.[aA][fF][mM]')) -## for f in afmFiles: -## T1name = findFontName(f) -## if T1name == name: -## afmFile = f -## found = 1 -## break -## if afmFile: -## break -## break -## -## if afmFile: -## pfbFile = glob.glob(join(splitext(afmFile)[0] + '.[pP][fF][bB]'))[0] -## -## return afmFile, pfbFile -## -## -##def registerFont(name): -## "Register Type-1 font for future use." -## -## rl_config.warnOnMissingFontGlyphs = 0 -## rl_config.T1SearchPath.append(r'C:\Programme\Python21\reportlab\test') -## -## afmFile, pfbFile = locateFilesForFontWithName(name) -## if not afmFile and not pfbFile: -## raise FontFilesNotFoundError -## -## T1face = pdfmetrics.EmbeddedType1Face(afmFile, pfbFile) -## T1faceName = name -## pdfmetrics.registerTypeFace(T1face) -## T1font = pdfmetrics.Font(name, T1faceName, 'WinAnsiEncoding') -## pdfmetrics.registerFont(T1font) - - -def registerFont0(sourceFile, name, path): - "Register Type-1 font for future use, simple version." - - rl_config.warnOnMissingFontGlyphs = 0 - - p = os.path.join(os.path.dirname(sourceFile), path) - afmFiles = glob.glob(p + '.[aA][fF][mM]') - pfbFiles = glob.glob(p + '.[pP][fF][bB]') - assert len(afmFiles) == len(pfbFiles) == 1, FontFilesNotFoundError - - T1face = pdfmetrics.EmbeddedType1Face(afmFiles[0], pfbFiles[0]) - T1faceName = name - pdfmetrics.registerTypeFace(T1face) - T1font = pdfmetrics.Font(name, T1faceName, 'WinAnsiEncoding') - pdfmetrics.registerFont(T1font) - -##################################################################### - - -def checkColor(col): - "Converts a color name to an RGB tuple, if possible." - - if type(col) == type('') and col in dir(colors): - col = getattr(colors, col) - col = (col.red, col.green, col.blue) - - return col - - -def handleHiddenSlides(slides): - """Filters slides from a list of slides. - - In a sequence of hidden slides all but the last one are - removed. Also, the slide before the sequence of hidden - ones is removed. - - This assumes to leave only those slides in the handout - that also appear in the outline, hoping to reduce se- - quences where each new slide only adds one new line - to a list of items... - """ - - itd = indicesToDelete = map(lambda s:s.outlineEntry == None, slides) - - for i in range(len(itd)-1): - if itd[i] == 1: - if itd[i+1] == 0: - itd[i] = 0 - if i > 0 and itd[i-1] == 0: - itd[i-1] = 1 - - itd[len(itd)-1] = 0 - - for i in range(len(itd)): - if slides[i].outlineEntry: - curOutlineEntry = slides[i].outlineEntry - if itd[i] == 1: - slides[i].delete = 1 - else: - slides[i].outlineEntry = curOutlineEntry - slides[i].delete = 0 - - slides = filter(lambda s:s.delete == 0, slides) - - return slides - - -def makeSlideTable(slides, pageSize, docWidth, numCols): - """Returns a table containing a collection of SlideWrapper flowables. - """ - - slides = handleHiddenSlides(slides) - - # Set table style. - tabStyle = TableStyle( - [('GRID', (0,0), (-1,-1), 0.25, colors.black), - ('ALIGN', (0,0), (-1,-1), 'CENTRE') - ]) - - # Build table content. - width = docWidth/numCols - height = width * pageSize[1]/pageSize[0] - matrix = [] - row = [] - for slide in slides: - sw = SlideWrapper(width, height, slide, pageSize) - if (len(row)) < numCols: - row.append(sw) - else: - matrix.append(row) - row = [] - row.append(sw) - if len(row) > 0: - for i in range(numCols-len(row)): - row.append('') - matrix.append(row) - - # Make Table flowable. - t = Table(matrix, - [width + 5]*len(matrix[0]), - [height + 5]*len(matrix)) - t.setStyle(tabStyle) - - return t - - -class SlideWrapper(Flowable): - """A Flowable wrapping a PPSlide object. - """ - - def __init__(self, width, height, slide, pageSize): - Flowable.__init__(self) - self.width = width - self.height = height - self.slide = slide - self.pageSize = pageSize - - - def __repr__(self): - return "SlideWrapper(w=%s, h=%s)" % (self.width, self.height) - - - def draw(self): - "Draw the slide in our relative coordinate system." - - slide = self.slide - pageSize = self.pageSize - canv = self.canv - - canv.saveState() - canv.scale(self.width/pageSize[0], self.height/pageSize[1]) - slide.effectName = None - slide.drawOn(self.canv) - canv.restoreState() - - -class PPPresentation: - def __init__(self): - self.sourceFilename = None - self.filename = None - self.outDir = None - self.description = None - self.title = None - self.author = None - self.subject = None - self.notes = 0 # different printing mode - self.handout = 0 # prints many slides per page - self.printout = 0 # remove hidden slides - self.cols = 0 # columns per handout page - self.slides = [] - self.effectName = None - self.showOutline = 1 #should it be displayed when opening? - self.compression = rl_config.pageCompression - self.pageDuration = None - #assume landscape - self.pageWidth = rl_config.defaultPageSize[1] - self.pageHeight = rl_config.defaultPageSize[0] - self.verbose = rl_config.verbose - - - def saveAsPresentation(self): - """Write the PDF document, one slide per page.""" - if self.verbose: - print 'saving presentation...' - pageSize = (self.pageWidth, self.pageHeight) - if self.sourceFilename: - filename = os.path.splitext(self.sourceFilename)[0] + '.pdf' - if self.outDir: filename = os.path.join(self.outDir,os.path.basename(filename)) - if self.verbose: - print filename - #canv = canvas.Canvas(filename, pagesize = pageSize) - outfile = getStringIO() - if self.notes: - #translate the page from landscape to portrait - pageSize= pageSize[1], pageSize[0] - canv = canvas.Canvas(outfile, pagesize = pageSize) - canv.setPageCompression(self.compression) - canv.setPageDuration(self.pageDuration) - if self.title: - canv.setTitle(self.title) - if self.author: - canv.setAuthor(self.author) - if self.subject: - canv.setSubject(self.subject) - - slideNo = 0 - for slide in self.slides: - #need diagnostic output if something wrong with XML - slideNo = slideNo + 1 - if self.verbose: - print 'doing slide %d, id = %s' % (slideNo, slide.id) - if self.notes: - #frame and shift the slide - #canv.scale(0.67, 0.67) - scale_amt = (min(pageSize)/float(max(pageSize)))*.95 - #canv.translate(self.pageWidth / 6.0, self.pageHeight / 3.0) - #canv.translate(self.pageWidth / 2.0, .025*self.pageHeight) - canv.translate(.025*self.pageHeight, (self.pageWidth/2.0) + 5) - #canv.rotate(90) - canv.scale(scale_amt, scale_amt) - canv.rect(0,0,self.pageWidth, self.pageHeight) - slide.drawOn(canv) - canv.showPage() - - #ensure outline visible by default - if self.showOutline: - canv.showOutline() - - canv.save() - return self.savetofile(outfile, filename) - - - def saveAsHandout(self): - """Write the PDF document, multiple slides per page.""" - - styleSheet = getSampleStyleSheet() - h1 = styleSheet['Heading1'] - bt = styleSheet['BodyText'] - - if self.sourceFilename : - filename = os.path.splitext(self.sourceFilename)[0] + '.pdf' - - outfile = getStringIO() - doc = SimpleDocTemplate(outfile, pagesize=rl_config.defaultPageSize, showBoundary=0) - doc.leftMargin = 1*cm - doc.rightMargin = 1*cm - doc.topMargin = 2*cm - doc.bottomMargin = 2*cm - multiPageWidth = rl_config.defaultPageSize[0] - doc.leftMargin - doc.rightMargin - 50 - - story = [] - orgFullPageSize = (self.pageWidth, self.pageHeight) - t = makeSlideTable(self.slides, orgFullPageSize, multiPageWidth, self.cols) - story.append(t) - -## #ensure outline visible by default -## if self.showOutline: -## doc.canv.showOutline() - - doc.build(story) - return self.savetofile(outfile, filename) - - def savetofile(self, pseudofile, filename): - """Save the pseudo file to disk and return its content as a - string of text.""" - pseudofile.flush() - content = pseudofile.getvalue() - pseudofile.close() - if filename : - outf = open(filename, "wb") - outf.write(content) - outf.close() - return content - - - - def save(self): - "Save the PDF document." - - if self.handout: - return self.saveAsHandout() - else: - return self.saveAsPresentation() - - -#class PPSection: -# """A section can hold graphics which will be drawn on all -# pages within it, before frames and other content are done. -# In other words, a background template.""" -# def __init__(self, name): -# self.name = name -# self.graphics = [] -# -# def drawOn(self, canv): -# for graphic in self.graphics: -### graphic.drawOn(canv) -# -# name = str(hash(graphic)) -# internalname = canv._doc.hasForm(name) -# -# canv.saveState() -# if not internalname: -# canv.beginForm(name) -# graphic.drawOn(canv) -# canv.endForm() -# canv.doForm(name) -# else: -# canv.doForm(name) -# canv.restoreState() - - -definedForms = {} - -class PPSection: - """A section can hold graphics which will be drawn on all - pages within it, before frames and other content are done. - In other words, a background template.""" - - def __init__(self, name): - self.name = name - self.graphics = [] - - def drawOn(self, canv): - for graphic in self.graphics: - graphic.drawOn(canv) - continue - name = str(hash(graphic)) - #internalname = canv._doc.hasForm(name) - if definedForms.has_key(name): - internalname = 1 - else: - internalname = None - definedForms[name] = 1 - if not internalname: - canv.beginForm(name) - canv.saveState() - graphic.drawOn(canv) - canv.restoreState() - canv.endForm() - canv.doForm(name) - else: - canv.doForm(name) - - -class PPNotes: - def __init__(self): - self.content = [] - - def drawOn(self, canv): - print self.content - - -class PPSlide: - def __init__(self): - self.id = None - self.title = None - self.outlineEntry = None - self.outlineLevel = 0 # can be higher for sub-headings - self.effectName = None - self.effectDirection = 0 - self.effectDimension = 'H' - self.effectMotion = 'I' - self.effectDuration = 1 - self.frames = [] - self.notes = [] - self.graphics = [] - self.section = None - - def drawOn(self, canv): - if self.effectName: - canv.setPageTransition( - effectname=self.effectName, - direction = self.effectDirection, - dimension = self.effectDimension, - motion = self.effectMotion, - duration = self.effectDuration - ) - - if self.outlineEntry: - #gets an outline automatically - self.showOutline = 1 - #put an outline entry in the left pane - tag = self.title - canv.bookmarkPage(tag) - canv.addOutlineEntry(tag, tag, self.outlineLevel) - - if self.section: - self.section.drawOn(canv) - - for graphic in self.graphics: - graphic.drawOn(canv) - - for frame in self.frames: - frame.drawOn(canv) - -## # Need to draw the notes *somewhere*... -## for note in self.notes: -## print note - - -class PPFrame: - def __init__(self, x, y, width, height): - self.x = x - self.y = y - self.width = width - self.height = height - self.content = [] - self.showBoundary = 0 - - def drawOn(self, canv): - #make a frame - frame = Frame( self.x, - self.y, - self.width, - self.height - ) - frame.showBoundary = self.showBoundary - - #build a story for the frame - story = [] - for thingy in self.content: - #ask it for any flowables - story.append(thingy.getFlowable()) - #draw it - frame.addFromList(story,canv) - - -class PPPara: - """This is a placeholder for a paragraph.""" - def __init__(self): - self.rawtext = '' - self.style = None - - def escapeAgain(self, text): - """The XML has been parsed once, so '>' became '>' - in rawtext. We need to escape this to get back to - something the Platypus parser can accept""" - pass - - def getFlowable(self): -## print 'rawText for para:' -## print repr(self.rawtext) - p = Paragraph( - self.rawtext, - getStyles()[self.style], - self.bulletText - ) - return p - - -class PPPreformattedText: - """Use this for source code, or stuff you do not want to wrap""" - def __init__(self): - self.rawtext = '' - self.style = None - - def getFlowable(self): - return Preformatted(self.rawtext, getStyles()[self.style]) - - -class PPPythonCode: - """Use this for colored Python source code""" - def __init__(self): - self.rawtext = '' - self.style = None - - def getFlowable(self): - return PythonPreformatted(self.rawtext, getStyles()[self.style]) - - -class PPImage: - """Flowing image within the text""" - def __init__(self): - self.filename = None - self.width = None - self.height = None - - def getFlowable(self): - return Image(self.filename, self.width, self.height) - - -class PPTable: - """Designed for bulk loading of data for use in presentations.""" - def __init__(self): - self.rawBlocks = [] #parser stuffs things in here... - self.fieldDelim = ',' #tag args can override - self.rowDelim = '\n' #tag args can override - self.data = None - self.style = None #tag args must specify - self.widths = None #tag args can override - self.heights = None #tag args can override - - def getFlowable(self): - self.parseData() - t = Table( - self.data, - self.widths, - self.heights) - if self.style: - t.setStyle(getStyles()[self.style]) - - return t - - def parseData(self): - """Try to make sense of the table data!""" - rawdata = string.strip(string.join(self.rawBlocks, '')) - lines = string.split(rawdata, self.rowDelim) - #clean up... - lines = map(string.strip, lines) - self.data = [] - for line in lines: - cells = string.split(line, self.fieldDelim) - self.data.append(cells) - - #get the width list if not given - if not self.widths: - self.widths = [None] * len(self.data[0]) - if not self.heights: - self.heights = [None] * len(self.data) - -## import pprint -## print 'table data:' -## print 'style=',self.style -## print 'widths=',self.widths -## print 'heights=',self.heights -## print 'fieldDelim=',repr(self.fieldDelim) -## print 'rowDelim=',repr(self.rowDelim) -## pprint.pprint(self.data) - - -class PPSpacer: - def __init__(self): - self.height = 24 #points - - def getFlowable(self): - return Spacer(72, self.height) - - - ############################################################# - # - # The following are things you can draw on a page directly. - # - ############################################################## - -##class PPDrawingElement: -## """Base class for something which you draw directly on the page.""" -## def drawOn(self, canv): -## raise "NotImplementedError", "Abstract base class!" - - -class PPFixedImage: - """You place this on the page, rather than flowing it""" - def __init__(self): - self.filename = None - self.x = 0 - self.y = 0 - self.width = None - self.height = None - - def drawOn(self, canv): - if self.filename: - x, y = self.x, self.y - w, h = self.width, self.height - canv.drawImage(self.filename, x, y, w, h) - - -class PPRectangle: - def __init__(self, x, y, width, height): - self.x = x - self.y = y - self.width = width - self.height = height - self.fillColor = None - self.strokeColor = (1,1,1) - self.lineWidth=0 - - def drawOn(self, canv): - canv.saveState() - canv.setLineWidth(self.lineWidth) - if self.fillColor: - r,g,b = checkColor(self.fillColor) - canv.setFillColorRGB(r,g,b) - if self.strokeColor: - r,g,b = checkColor(self.strokeColor) - canv.setStrokeColorRGB(r,g,b) - canv.rect(self.x, self.y, self.width, self.height, - stroke=(self.strokeColor<>None), - fill = (self.fillColor<>None) - ) - canv.restoreState() - - -class PPRoundRect: - def __init__(self, x, y, width, height, radius): - self.x = x - self.y = y - self.width = width - self.height = height - self.radius = radius - self.fillColor = None - self.strokeColor = (1,1,1) - self.lineWidth=0 - - def drawOn(self, canv): - canv.saveState() - canv.setLineWidth(self.lineWidth) - if self.fillColor: - r,g,b = checkColor(self.fillColor) - canv.setFillColorRGB(r,g,b) - if self.strokeColor: - r,g,b = checkColor(self.strokeColor) - canv.setStrokeColorRGB(r,g,b) - canv.roundRect(self.x, self.y, self.width, self.height, - self.radius, - stroke=(self.strokeColor<>None), - fill = (self.fillColor<>None) - ) - canv.restoreState() - - -class PPLine: - def __init__(self, x1, y1, x2, y2): - self.x1 = x1 - self.y1 = y1 - self.x2 = x2 - self.y2 = y2 - self.fillColor = None - self.strokeColor = (1,1,1) - self.lineWidth=0 - - def drawOn(self, canv): - canv.saveState() - canv.setLineWidth(self.lineWidth) - if self.strokeColor: - r,g,b = checkColor(self.strokeColor) - canv.setStrokeColorRGB(r,g,b) - canv.line(self.x1, self.y1, self.x2, self.y2) - canv.restoreState() - - -class PPEllipse: - def __init__(self, x1, y1, x2, y2): - self.x1 = x1 - self.y1 = y1 - self.x2 = x2 - self.y2 = y2 - self.fillColor = None - self.strokeColor = (1,1,1) - self.lineWidth=0 - - def drawOn(self, canv): - canv.saveState() - canv.setLineWidth(self.lineWidth) - if self.strokeColor: - r,g,b = checkColor(self.strokeColor) - canv.setStrokeColorRGB(r,g,b) - if self.fillColor: - r,g,b = checkColor(self.fillColor) - canv.setFillColorRGB(r,g,b) - canv.ellipse(self.x1, self.y1, self.x2, self.y2, - stroke=(self.strokeColor<>None), - fill = (self.fillColor<>None) - ) - canv.restoreState() - - -class PPPolygon: - def __init__(self, pointlist): - self.points = pointlist - self.fillColor = None - self.strokeColor = (1,1,1) - self.lineWidth=0 - - def drawOn(self, canv): - canv.saveState() - canv.setLineWidth(self.lineWidth) - if self.strokeColor: - r,g,b = checkColor(self.strokeColor) - canv.setStrokeColorRGB(r,g,b) - if self.fillColor: - r,g,b = checkColor(self.fillColor) - canv.setFillColorRGB(r,g,b) - - path = canv.beginPath() - (x,y) = self.points[0] - path.moveTo(x,y) - for (x,y) in self.points[1:]: - path.lineTo(x,y) - path.close() - canv.drawPath(path, - stroke=(self.strokeColor<>None), - fill=(self.fillColor<>None)) - canv.restoreState() - - -class PPString: - def __init__(self, x, y): - self.text = '' - self.x = x - self.y = y - self.align = TA_LEFT - self.font = 'Times-Roman' - self.size = 12 - self.color = (0,0,0) - self.hasInfo = 0 # these can have data substituted into them - - def normalizeText(self): - """It contains literal XML text typed over several lines. - We want to throw away - tabs, newlines and so on, and only accept embedded string - like '\n'""" - lines = string.split(self.text, '\n') - newtext = [] - for line in lines: - newtext.append(string.strip(line)) - #accept all the '\n' as newlines - - self.text = newtext - - def drawOn(self, canv): - # for a string in a section, this will be drawn several times; - # so any substitution into the text should be in a temporary - # variable - if self.hasInfo: - # provide a dictionary of stuff which might go into - # the string, so they can number pages, do headers - # etc. - info = {} - info['title'] = canv._doc.info.title - info['author'] = canv._doc.info.author - info['subject'] = canv._doc.info.subject - info['page'] = canv.getPageNumber() - drawText = self.text % info - else: - drawText = self.text - - if self.color is None: - return - lines = string.split(string.strip(drawText), '\\n') - canv.saveState() - - canv.setFont(self.font, self.size) - - r,g,b = checkColor(self.color) - canv.setFillColorRGB(r,g,b) - cur_y = self.y - for line in lines: - if self.align == TA_LEFT: - canv.drawString(self.x, cur_y, line) - elif self.align == TA_CENTER: - canv.drawCentredString(self.x, cur_y, line) - elif self.align == TA_RIGHT: - canv.drawRightString(self.x, cur_y, line) - cur_y = cur_y - 1.2*self.size - - canv.restoreState() - -class PPDrawing: - def __init__(self): - self.drawing = None - def getFlowable(self): - return self.drawing - -class PPFigure: - def __init__(self): - self.figure = None - def getFlowable(self): - return self.figure - -def getSampleStyleSheet(): - from reportlab.tools.pythonpoint.styles.standard import getParagraphStyles - return getParagraphStyles() - - -#make a singleton and a function to access it -_styles = None -def getStyles(): - global _styles - if not _styles: - _styles = getSampleStyleSheet() - return _styles - - -def setStyles(newStyleSheet): - global _styles - _styles = newStyleSheet - - -##def test(): -## p = stdparser.PPMLParser() -## p.feed(sample) -## p.getPresentation().save() -## p.close() - -_pyRXP_Parser = None -def validate(rawdata): - global _pyRXP_Parser - if not _pyRXP_Parser: - try: - import pyRXP - except ImportError: - return - from reportlab.lib.utils import open_and_read, _RL_DIR, rl_isfile - dtd = 'pythonpoint.dtd' - if not rl_isfile(dtd): - dtd = os.path.join(_RL_DIR,'tools','pythonpoint','pythonpoint.dtd') - if not rl_isfile(dtd): return - def eocb(URI,dtdText=open_and_read(dtd),dtd=dtd): - if os.path.basename(URI)=='pythonpoint.dtd': return dtd,dtdText - return URI - _pyRXP_Parser = pyRXP.Parser(eoCB=eocb) - return _pyRXP_Parser.parse(rawdata) - -def process(datafile, notes=0, handout=0, printout=0, cols=0, verbose=0, outDir=None, datafilename=None, fx=1): - "Process one PythonPoint source file." - if not hasattr(datafile, "read"): - if not datafilename: datafilename = datafile - datafile = open(datafile) - else: - if not datafilename: datafilename = "PseudoFile" - rawdata = datafile.read() - - #if pyRXP present, use it to check and get line numbers for errors... - validate(rawdata) - - return _process(rawdata, datafilename, notes, handout, printout, cols, verbose, outDir, fx) - -def _process(rawdata, datafilename, notes=0, handout=0, printout=0, cols=0, verbose=0, outDir=None, fx=1): - #print 'inner process fx=%d' % fx - from reportlab.tools.pythonpoint.stdparser import PPMLParser - parser = PPMLParser() - parser.fx = fx - parser.sourceFilename = datafilename - parser.feed(rawdata) - pres = parser.getPresentation() - pres.sourceFilename = datafilename - pres.outDir = outDir - pres.notes = notes - pres.handout = handout - pres.printout = printout - pres.cols = cols - pres.verbose = verbose - - if printout: - pres.slides = handleHiddenSlides(pres.slides) - - #this does all the work - pdfcontent = pres.save() - - if verbose: - print 'saved presentation %s.pdf' % os.path.splitext(datafilename)[0] - parser.close() - - return pdfcontent -##class P: -## def feed(self, text): -## parser = stdparser.PPMLParser() -## d = pyRXP.parse(text) -## -## -##def process2(datafilename, notes=0, handout=0, cols=0): -## "Process one PythonPoint source file." -## -## import pyRXP, pprint -## -## rawdata = open(datafilename).read() -## d = pyRXP.parse(rawdata) -## pprint.pprint(d) - - -def handleOptions(): - # set defaults - from reportlab import rl_config - options = {'cols':2, - 'handout':0, - 'printout':0, - 'help':0, - 'notes':0, - 'fx':1, - 'verbose':rl_config.verbose, - 'silent':0, - 'outDir': None} - - args = sys.argv[1:] - args = filter(lambda x: x and x[0]=='-',args) + filter(lambda x: not x or x[0]!='-',args) - try: - shortOpts = 'hnvsx' - longOpts = string.split('cols= outdir= handout help notes printout verbose silent nofx') - optList, args = getopt.getopt(args, shortOpts, longOpts) - except getopt.error, msg: - options['help'] = 1 - - if not args and os.path.isfile('pythonpoint.xml'): - args = ['pythonpoint.xml'] - - # Remove leading dashes (max. two). - for i in range(len(optList)): - o, v = optList[i] - while o[0] == '-': - o = o[1:] - optList[i] = (o, v) - - if o == 'cols': options['cols'] = int(v) - elif o=='outdir': options['outDir'] = v - - if filter(lambda ov: ov[0] == 'handout', optList): - options['handout'] = 1 - - if filter(lambda ov: ov[0] == 'printout', optList): - options['printout'] = 1 - - if optList == [] and args == [] or \ - filter(lambda ov: ov[0] in ('h', 'help'), optList): - options['help'] = 1 - - if filter(lambda ov: ov[0] in ('n', 'notes'), optList): - options['notes'] = 1 - - if filter(lambda ov: ov[0] in ('x', 'nofx'), optList): - options['fx'] = 0 - - if filter(lambda ov: ov[0] in ('v', 'verbose'), optList): - options['verbose'] = 1 - - #takes priority over verbose. Used by our test suite etc. - #to ensure no output at all - if filter(lambda ov: ov[0] in ('s', 'silent'), optList): - options['silent'] = 1 - options['verbose'] = 0 - - - return options, args - -def main(): - options, args = handleOptions() - - if options['help']: - print USAGE_MESSAGE - sys.exit(0) - - if options['verbose'] and options['notes']: - print 'speaker notes mode' - - if options['verbose'] and options['handout']: - print 'handout mode' - - if options['verbose'] and options['printout']: - print 'printout mode' - - if not options['fx']: - print 'suppressing special effects' - for fileGlobs in args: - files = glob.glob(fileGlobs) - for datafile in files: - if os.path.isfile(datafile): - file = os.path.join(os.getcwd(), datafile) - notes, handout, printout, cols, verbose, fx = options['notes'], options['handout'], options['printout'], options['cols'], options['verbose'], options['fx'] - process(file, notes, handout, printout, cols, verbose, options['outDir'], fx=fx) - else: - print 'Data file not found:', datafile - -if __name__ == '__main__': - main() diff --git a/bin/reportlab/tools/pythonpoint/stdparser.py b/bin/reportlab/tools/pythonpoint/stdparser.py deleted file mode 100644 index dffe1693d0e..00000000000 --- a/bin/reportlab/tools/pythonpoint/stdparser.py +++ /dev/null @@ -1,834 +0,0 @@ -""" -Parser for PythonPoint using the xmllib.py in the standard Python -distribution. Slow, but always present. We intend to add new parsers -as Python 2.x and the XML package spread in popularity and stabilise. - -The parser has a getPresentation method; it is called from -pythonpoint.py. -""" - -import string, imp, sys, os, copy -from reportlab.lib.utils import SeqTypes -from reportlab.lib import xmllib -from reportlab.lib import colors -from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY -from reportlab.lib.utils import recursiveImport -from reportlab.tools.pythonpoint import pythonpoint -from reportlab.platypus import figures - - -def getModule(modulename,fromPath='reportlab.tools.pythonpoint.styles'): - """Get a module containing style declarations. - - Search order is: - reportlab/tools/pythonpoint/ - reportlab/tools/pythonpoint/styles/ - ./ - """ - - try: - exec 'from reportlab.tools.pythonpoint import '+modulename - return eval(modulename) - except ImportError: - try: - exec 'from reportlab.tools.pythonpoint.styles import '+modulename - return eval(modulename) - except ImportError: - exec 'import '+modulename - return eval(modulename) - - -class PPMLParser(xmllib.XMLParser): - attributes = { - #this defines the available attributes for all objects, - #and their default values. Although these don't have to - #be strings, the ones parsed from the XML do, so - #everything is a quoted string and the parser has to - #convert these to numbers where appropriate. - 'stylesheet': { - 'path':'None', - 'module':'None', - 'function':'getParagraphStyles' - }, - 'frame': { - 'x':'0', - 'y':'0', - 'width':'0', - 'height':'0', - 'border':'false', - 'leftmargin':'0', #this is ignored - 'topmargin':'0', #this is ignored - 'rightmargin':'0', #this is ignored - 'bottommargin':'0', #this is ignored - }, - 'slide': { - 'id':'None', - 'title':'None', - 'effectname':'None', # Split, Blinds, Box, Wipe, Dissolve, Glitter - 'effectdirection':'0', # 0,90,180,270 - 'effectdimension':'H', # H or V - horizontal or vertical - 'effectmotion':'I', # Inwards or Outwards - 'effectduration':'1', #seconds, - 'outlineentry':'None', - 'outlinelevel':'0' # 1 is a child, 2 is a grandchild etc. - }, - 'para': { - 'style':'Normal', - 'bullettext':'', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'image': { - 'filename':'', - 'width':'None', - 'height':'None', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'table': { - 'widths':'None', - 'heights':'None', - 'fieldDelim':',', - 'rowDelim':'\n', - 'style':'None', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'rectangle': { - 'x':'0', - 'y':'0', - 'width':'100', - 'height':'100', - 'fill':'None', - 'stroke':'(0,0,0)', - 'linewidth':'0', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'roundrect': { - 'x':'0', - 'y':'0', - 'width':'100', - 'height':'100', - 'radius':'6', - 'fill':'None', - 'stroke':'(0,0,0)', - 'linewidth':'0', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'line': { - 'x1':'0', - 'y1':'0', - 'x2':'100', - 'y2':'100', - 'stroke':'(0,0,0)', - 'width':'0', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'ellipse': { - 'x1':'0', - 'y1':'0', - 'x2':'100', - 'y2':'100', - 'stroke':'(0,0,0)', - 'fill':'None', - 'linewidth':'0', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'polygon': { - 'points':'(0,0),(50,0),(25,25)', - 'stroke':'(0,0,0)', - 'linewidth':'0', - 'stroke':'(0,0,0)', - 'fill':'None', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'string':{ - 'x':'0', - 'y':'0', - 'color':'(0,0,0)', - 'font':'Times-Roman', - 'size':'12', - 'align':'left', - 'effectname':'None', - 'effectdirection':'0', - 'effectdimension':'H', - 'effectmotion':'I', - 'effectduration':'1' - }, - 'customshape':{ - 'path':'None', - 'module':'None', - 'class':'None', - 'initargs':'None' - } - } - - def __init__(self): - self.presentations = [] - #yes, I know a generic stack would be easier... - #still, testing if we are 'in' something gives - #a degree of validation. - self._curPres = None - self._curSection = None - self._curSlide = None - self._curFrame = None - self._curPara = None #the only places we are interested in - self._curPrefmt = None - self._curPyCode = None - self._curString = None - self._curTable = None - self._curTitle = None - self._curAuthor = None - self._curSubject = None - self.fx = 1 - xmllib.XMLParser.__init__(self) - - - def _arg(self,tag,args,name): - "What's this for???" - if args.has_key(name): - v = args[name] - else: - if self.attributes.has_key(tag): - v = self.attributes[tag][name] - else: - v = None - return v - - - def ceval(self,tag,args,name): - if args.has_key(name): - v = args[name] - else: - if self.attributes.has_key(tag): - v = self.attributes[tag][name] - else: - return None - - # handle named colors (names from reportlab.lib.colors) - if name in ('color', 'stroke', 'fill'): - v = str(pythonpoint.checkColor(v)) - - return eval(v) - - - def getPresentation(self): - return self._curPres - - - def handle_data(self, data): - #the only data should be paragraph text, preformatted para - #text, 'string text' for a fixed string on the page, - #or table data - - if self._curPara: - self._curPara.rawtext = self._curPara.rawtext + data - elif self._curPrefmt: - self._curPrefmt.rawtext = self._curPrefmt.rawtext + data - elif self._curPyCode: - self._curPyCode.rawtext = self._curPyCode.rawtext + data - elif self._curString: - self._curString.text = self._curString.text + data - elif self._curTable: - self._curTable.rawBlocks.append(data) - elif self._curTitle <> None: # need to allow empty strings, - # hence explicitly testing for None - self._curTitle = self._curTitle + data - elif self._curAuthor <> None: - self._curAuthor = self._curAuthor + data - elif self._curSubject <> None: - self._curSubject = self._curSubject + data - - - def handle_cdata(self, data): - #just append to current paragraph text, so we can quote XML - if self._curPara: - self._curPara.rawtext = self._curPara.rawtext + data - elif self._curPrefmt: - self._curPrefmt.rawtext = self._curPrefmt.rawtext + data - elif self._curPyCode: - self._curPyCode.rawtext = self._curPyCode.rawtext + data - elif self._curString: - self._curString.text = self._curString.text + data - elif self._curTable: - self._curTable.rawBlocks.append(data) - elif self._curAuthor <> None: - self._curAuthor = self._curAuthor + data - elif self._curSubject <> None: - self._curSubject = self._curSubject + data - - - def start_presentation(self, args): - self._curPres = pythonpoint.PPPresentation() - self._curPres.filename = self._arg('presentation',args,'filename') - self._curPres.effectName = self._arg('presentation',args,'effect') - self._curPres.pageDuration = self._arg('presentation',args,'pageDuration') - - h = self._arg('presentation',args,'pageHeight') - if h: - self._curPres.pageHeight = h - w = self._arg('presentation',args,'pageWidth') - if w: - self._curPres.pageWidth = w - #print 'page size =', self._curPres.pageSize - - - def end_presentation(self): - pass -## print 'Fully parsed presentation',self._curPres.filename - - - def start_title(self, args): - self._curTitle = '' - - - def end_title(self): - self._curPres.title = self._curTitle - self._curTitle = None - - - def start_author(self, args): - self._curAuthor = '' - - - def end_author(self): - self._curPres.author = self._curAuthor - self._curAuthor = None - - - def start_subject(self, args): - self._curSubject = '' - - - def end_subject(self): - self._curPres.subject = self._curSubject - self._curSubject = None - - - def start_stylesheet(self, args): - #makes it the current style sheet. - path = self._arg('stylesheet',args,'path') - if path=='None': path = [] - if type(path) not in SeqTypes: path = [path] - path.append('styles') - path.append(os.getcwd()) - modulename = self._arg('stylesheet', args, 'module') - funcname = self._arg('stylesheet', args, 'function') - try: - found = imp.find_module(modulename, path) - (file, pathname, description) = found - mod = imp.load_module(modulename, file, pathname, description) - except ImportError: - #last gasp - mod = getModule(modulename) - - #now get the function - func = getattr(mod, funcname) - pythonpoint.setStyles(func()) -## print 'set global stylesheet to %s.%s()' % (modulename, funcname) - - - def end_stylesheet(self): - pass - - - def start_section(self, args): - name = self._arg('section',args,'name') - self._curSection = pythonpoint.PPSection(name) - - - def end_section(self): - self._curSection = None - - - def start_slide(self, args): - s = pythonpoint.PPSlide() - s.id = self._arg('slide',args,'id') - s.title = self._arg('slide',args,'title') - a = self._arg('slide',args,'effectname') - if a <> 'None': - s.effectName = a - s.effectDirection = self.ceval('slide',args,'effectdirection') - s.effectDimension = self._arg('slide',args,'effectdimension') - s.effectDuration = self.ceval('slide',args,'effectduration') - s.effectMotion = self._arg('slide',args,'effectmotion') - - #HACK - may not belong here in the long run... - #by default, use the slide title for the outline entry, - #unless it is specified as an arg. - a = self._arg('slide',args,'outlineentry') - if a == "Hide": - s.outlineEntry = None - elif a <> 'None': - s.outlineEntry = a - else: - s.outlineEntry = s.title - - s.outlineLevel = self.ceval('slide',args,'outlinelevel') - - #let it know its section, which may be none - s.section = self._curSection - self._curSlide = s - - - def end_slide(self): - self._curPres.slides.append(self._curSlide) - self._curSlide = None - - - def start_frame(self, args): - self._curFrame = pythonpoint.PPFrame( - self.ceval('frame',args,'x'), - self.ceval('frame',args,'y'), - self.ceval('frame',args,'width'), - self.ceval('frame',args,'height') - ) - if self._arg('frame',args,'border')=='true': - self._curFrame.showBoundary = 1 - - - def end_frame(self): - self._curSlide.frames.append(self._curFrame) - self._curFrame = None - - - def start_notes(self, args): - name = self._arg('notes',args,'name') - self._curNotes = pythonpoint.PPNotes() - - - def end_notes(self): - self._curSlide.notes.append(self._curNotes) - self._curNotes = None - - - def start_registerFont(self, args): - name = self._arg('font',args,'name') - path = self._arg('font',args,'path') - pythonpoint.registerFont0(self.sourceFilename, name, path) - - - def end_registerFont(self): - pass - - - def pack_slide(self, element, args): - if self.fx: - effectName = self._arg(element,args,'effectname') - if effectName <> 'None': - curSlide = copy.deepcopy(self._curSlide) - if self._curFrame: - curFrame = copy.deepcopy(self._curFrame) - curSlide.frames.append(curFrame) - self._curPres.slides.append(curSlide) - self._curSlide.effectName = effectName - self._curSlide.effectDirection = self.ceval(element,args,'effectdirection') - self._curSlide.effectDimension = self._arg(element,args,'effectdimension') - self._curSlide.effectDuration = self.ceval(element,args,'effectduration') - self._curSlide.effectMotion = self._arg(element,args,'effectmotion') - self._curSlide.outlineEntry = None - - def start_para(self, args): - self.pack_slide('para', args) - self._curPara = pythonpoint.PPPara() - self._curPara.style = self._arg('para',args,'style') - - # hack - bullet character if bullet style - bt = self._arg('para',args,'bullettext') - if bt == '': - if self._curPara.style == 'Bullet': - bt = '\267' # Symbol Font bullet character, reasonable default - elif self._curPara.style == 'Bullet2': - bt = '\267' # second-level bullet - else: - bt = None - - self._curPara.bulletText = bt - - - def end_para(self): - if self._curFrame: - self._curFrame.content.append(self._curPara) - self._curPara = None - elif self._curNotes: - self._curNotes.content.append(self._curPara) - self._curPara = None - - - def start_prefmt(self, args): - self._curPrefmt = pythonpoint.PPPreformattedText() - self._curPrefmt.style = self._arg('prefmt',args,'style') - - - def end_prefmt(self): - self._curFrame.content.append(self._curPrefmt) - self._curPrefmt = None - - - def start_pycode(self, args): - self._curPyCode = pythonpoint.PPPythonCode() - self._curPyCode.style = self._arg('pycode',args,'style') - - - def end_pycode(self): - self._curFrame.content.append(self._curPyCode) - self._curPyCode = None - - - def start_image(self, args): - self.pack_slide('image',args) - sourceFilename = self.sourceFilename # XXX - filename = self._arg('image',args,'filename') - filename = os.path.join(os.path.dirname(sourceFilename), filename) - self._curImage = pythonpoint.PPImage() - self._curImage.filename = filename - self._curImage.width = self.ceval('image',args,'width') - self._curImage.height = self.ceval('image',args,'height') - - - def end_image(self): - self._curFrame.content.append(self._curImage) - self._curImage = None - - - def start_table(self, args): - self.pack_slide('table',args) - self._curTable = pythonpoint.PPTable() - self._curTable.widths = self.ceval('table',args,'widths') - self._curTable.heights = self.ceval('table',args,'heights') - #these may contain escapes like tabs - handle with - #a bit more care. - if args.has_key('fieldDelim'): - self._curTable.fieldDelim = eval('"' + args['fieldDelim'] + '"') - if args.has_key('rowDelim'): - self._curTable.rowDelim = eval('"' + args['rowDelim'] + '"') - if args.has_key('style'): - self._curTable.style = args['style'] - - - def end_table(self): - self._curFrame.content.append(self._curTable) - self._curTable = None - - - def start_spacer(self, args): - """No contents so deal with it here.""" - sp = pythonpoint.PPSpacer() - sp.height = eval(args['height']) - self._curFrame.content.append(sp) - - - def end_spacer(self): - pass - - - ## the graphics objects - go into either the current section - ## or the current slide. - def start_fixedimage(self, args): - sourceFilename = self.sourceFilename - filename = self._arg('image',args,'filename') - filename = os.path.join(os.path.dirname(sourceFilename), filename) - img = pythonpoint.PPFixedImage() - img.filename = filename - img.x = self.ceval('fixedimage',args,'x') - img.y = self.ceval('fixedimage',args,'y') - img.width = self.ceval('fixedimage',args,'width') - img.height = self.ceval('fixedimage',args,'height') - self._curFixedImage = img - - - def end_fixedimage(self): - if self._curSlide: - self._curSlide.graphics.append(self._curFixedImage) - elif self._curSection: - self._curSection.graphics.append(self._curFixedImage) - self._curFixedImage = None - - - def start_rectangle(self, args): - self.pack_slide('rectangle', args) - rect = pythonpoint.PPRectangle( - self.ceval('rectangle',args,'x'), - self.ceval('rectangle',args,'y'), - self.ceval('rectangle',args,'width'), - self.ceval('rectangle',args,'height') - ) - rect.fillColor = self.ceval('rectangle',args,'fill') - rect.strokeColor = self.ceval('rectangle',args,'stroke') - self._curRectangle = rect - - - def end_rectangle(self): - if self._curSlide: - self._curSlide.graphics.append(self._curRectangle) - elif self._curSection: - self._curSection.graphics.append(self._curRectangle) - self._curRectangle = None - - - def start_roundrect(self, args): - self.pack_slide('roundrect', args) - rrect = pythonpoint.PPRoundRect( - self.ceval('roundrect',args,'x'), - self.ceval('roundrect',args,'y'), - self.ceval('roundrect',args,'width'), - self.ceval('roundrect',args,'height'), - self.ceval('roundrect',args,'radius') - ) - rrect.fillColor = self.ceval('roundrect',args,'fill') - rrect.strokeColor = self.ceval('roundrect',args,'stroke') - self._curRoundRect = rrect - - - def end_roundrect(self): - if self._curSlide: - self._curSlide.graphics.append(self._curRoundRect) - elif self._curSection: - self._curSection.graphics.append(self._curRoundRect) - self._curRoundRect = None - - - def start_line(self, args): - self.pack_slide('line', args) - self._curLine = pythonpoint.PPLine( - self.ceval('line',args,'x1'), - self.ceval('line',args,'y1'), - self.ceval('line',args,'x2'), - self.ceval('line',args,'y2') - ) - self._curLine.strokeColor = self.ceval('line',args,'stroke') - - - def end_line(self): - if self._curSlide: - self._curSlide.graphics.append(self._curLine) - elif self._curSection: - self._curSection.graphics.append(self._curLine) - self._curLine = None - - - def start_ellipse(self, args): - self.pack_slide('ellipse', args) - self._curEllipse = pythonpoint.PPEllipse( - self.ceval('ellipse',args,'x1'), - self.ceval('ellipse',args,'y1'), - self.ceval('ellipse',args,'x2'), - self.ceval('ellipse',args,'y2') - ) - self._curEllipse.strokeColor = self.ceval('ellipse',args,'stroke') - self._curEllipse.fillColor = self.ceval('ellipse',args,'fill') - - - def end_ellipse(self): - if self._curSlide: - self._curSlide.graphics.append(self._curEllipse) - elif self._curSection: - self._curSection.graphics.append(self._curEllipse) - self._curEllipse = None - - - def start_polygon(self, args): - self.pack_slide('polygon', args) - self._curPolygon = pythonpoint.PPPolygon(self.ceval('polygon',args,'points')) - self._curPolygon.strokeColor = self.ceval('polygon',args,'stroke') - self._curPolygon.fillColor = self.ceval('polygon',args,'fill') - - - def end_polygon(self): - if self._curSlide: - self._curSlide.graphics.append(self._curPolygon) - elif self._curSection: - self._curSection.graphics.append(self._curPolygon) - self._curEllipse = None - - - def start_string(self, args): - self.pack_slide('string', args) - self._curString = pythonpoint.PPString( - self.ceval('string',args,'x'), - self.ceval('string',args,'y') - ) - self._curString.color = self.ceval('string',args,'color') - self._curString.font = self._arg('string',args,'font') - self._curString.size = self.ceval('string',args,'size') - if args['align'] == 'left': - self._curString.align = TA_LEFT - elif args['align'] == 'center': - self._curString.align = TA_CENTER - elif args['align'] == 'right': - self._curString.align = TA_RIGHT - elif args['align'] == 'justify': - self._curString.align = TA_JUSTIFY - #text comes later within the tag - - - def end_string(self): - #controller should have set the text - if self._curSlide: - self._curSlide.graphics.append(self._curString) - elif self._curSection: - self._curSection.graphics.append(self._curString) - self._curString = None - - - def start_infostring(self, args): - # like a string, but lets them embed page no, author etc. - self.start_string(args) - self._curString.hasInfo = 1 - - - def end_infostring(self): - self.end_string() - - - def start_customshape(self, args): - #loads one - path = self._arg('customshape',args,'path') - if path=='None': - path = [] - else: - path=[path] - - # add package root folder and input file's folder to path - path.append(os.path.dirname(self.sourceFilename)) - path.append(os.path.dirname(pythonpoint.__file__)) - - modulename = self._arg('customshape',args,'module') - funcname = self._arg('customshape',args,'class') - try: - found = imp.find_module(modulename, path) - (file, pathname, description) = found - mod = imp.load_module(modulename, file, pathname, description) - except ImportError: - mod = getModule(modulename) - - #now get the function - - func = getattr(mod, funcname) - initargs = self.ceval('customshape',args,'initargs') - self._curCustomShape = apply(func, initargs) - - - def end_customshape(self): - if self._curSlide: - self._curSlide.graphics.append(self._curCustomShape) - elif self._curSection: - self._curSection.graphics.append(self._curCustomShape) - self._curCustomShape = None - - - - def start_drawing(self, args): - #loads one - - moduleName = args["module"] - funcName = args["constructor"] - - showBoundary = int(args.get("showBoundary", "0")) - - hAlign = args.get("hAlign", "CENTER") - - - # the path for the imports should include: - # 1. document directory - # 2. python path if baseDir not given, or - # 3. baseDir if given - try: - dirName = sdict["baseDir"] - except: - dirName = None - importPath = [os.getcwd()] - if dirName is None: - importPath.extend(sys.path) - else: - importPath.insert(0, dirName) - - modul = recursiveImport(moduleName, baseDir=importPath) - func = getattr(modul, funcName) - drawing = func() - - drawing.hAlign = hAlign - if showBoundary: - drawing._showBoundary = 1 - - self._curDrawing = pythonpoint.PPDrawing() - self._curDrawing.drawing = drawing - - - def end_drawing(self): - self._curFrame.content.append(self._curDrawing) - self._curDrawing = None - - - def start_pageCatcherFigure(self, args): - filename = args["filename"] - pageNo = int(args["pageNo"]) - width = float(args.get("width", "595")) - height = float(args.get("height", "842")) - - - fig = figures.PageCatcherFigureNonA4(filename, pageNo, args.get("caption", ""), width, height) - sf = args.get('scaleFactor', None) - if sf: sf = float(sf) - border = not (args.get('border', None) in ['0','no']) - - fig.scaleFactor = sf - fig.border = border - - #self.ceval('pageCatcherFigure',args,'scaleFactor'), - #initargs = self.ceval('customshape',args,'initargs') - self._curFigure = pythonpoint.PPFigure() - self._curFigure.figure = fig - - - - def end_pageCatcherFigure(self): - self._curFrame.content.append(self._curFigure) - self._curFigure = None - - ## intra-paragraph XML should be allowed through into PLATYPUS - def unknown_starttag(self, tag, attrs): - if self._curPara: - echo = '<%s' % tag - for (key, value) in attrs.items(): - echo = echo + ' %s="%s"' % (key, value) - echo = echo + '>' - self._curPara.rawtext = self._curPara.rawtext + echo - else: - print 'Unknown start tag %s' % tag - - - def unknown_endtag(self, tag): - if self._curPara: - self._curPara.rawtext = self._curPara.rawtext + ''% tag - else: - print 'Unknown end tag %s' % tag diff --git a/bin/reportlab/tools/pythonpoint/styles/__init__.py b/bin/reportlab/tools/pythonpoint/styles/__init__.py deleted file mode 100644 index e67468fd9ee..00000000000 --- a/bin/reportlab/tools/pythonpoint/styles/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/styles/__init__.py diff --git a/bin/reportlab/tools/pythonpoint/styles/horrible.py b/bin/reportlab/tools/pythonpoint/styles/horrible.py deleted file mode 100644 index b608fdb5771..00000000000 --- a/bin/reportlab/tools/pythonpoint/styles/horrible.py +++ /dev/null @@ -1,101 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/styles/horrible.py -__version__=''' $Id$ ''' -# style_modern.py -__doc__="""This is an example style sheet. You can create your own, and -have them loaded by the presentation. A style sheet is just a -dictionary, where they keys are style names and the values are -ParagraphStyle objects. - -You must provide a function called "getParagraphStyles()" to -return it. In future, we can put things like LineStyles, -TableCellStyles etc. in the same modules. - -You might wish to have two parallel style sheets, one for colour -and one for black and white, so you can switch your presentations -easily. - -A style sheet MUST define a style called 'Normal'. -""" - -from reportlab.lib import styles, enums -def getParagraphStyles(): - """Returns a dictionary of styles based on Helvetica""" - stylesheet = {} - - para = styles.ParagraphStyle('Normal', None) #the ancestor of all - para.fontName = 'Courier' - para.fontSize = 24 - para.leading = 28 - stylesheet['Normal'] = para - - para = ParagraphStyle('BodyText', stylesheet['Normal']) - para.spaceBefore = 12 - stylesheet['BodyText'] = para - - para = ParagraphStyle('BigCentered', stylesheet['Normal']) - para.spaceBefore = 12 - para.alignment = enums.TA_CENTER - stylesheet['BigCentered'] = para - - para = ParagraphStyle('Italic', stylesheet['BodyText']) - para.fontName = 'Courier-Oblique' - stylesheet['Italic'] = para - - para = ParagraphStyle('Title', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 48 - para.Leading = 58 - para.spaceAfter = 36 - para.alignment = enums.TA_CENTER - stylesheet['Title'] = para - - para = ParagraphStyle('Heading1', stylesheet['Normal']) - para.fontName = 'Courier-Bold' - para.fontSize = 36 - para.leading = 44 - para.spaceAfter = 36 - para.alignment = enums.TA_CENTER - stylesheet['Heading1'] = para - - para = ParagraphStyle('Heading2', stylesheet['Normal']) - para.fontName = 'Courier-Bold' - para.fontSize = 28 - para.leading = 34 - para.spaceBefore = 24 - para.spaceAfter = 12 - stylesheet['Heading2'] = para - - para = ParagraphStyle('Heading3', stylesheet['Normal']) - para.fontName = 'Courier-BoldOblique' - para.spaceBefore = 24 - para.spaceAfter = 12 - stylesheet['Heading3'] = para - - para = ParagraphStyle('Bullet', stylesheet['Normal']) - para.firstLineIndent = -18 - para.leftIndent = 72 - para.spaceBefore = 6 - #para.bulletFontName = 'Symbol' - para.bulletFontSize = 24 - para.bulletIndent = 36 - stylesheet['Bullet'] = para - - para = ParagraphStyle('Definition', stylesheet['Normal']) - #use this for definition lists - para.firstLineIndent = 0 - para.leftIndent = 72 - para.bulletIndent = 0 - para.spaceBefore = 12 - para.bulletFontName = 'Couruer-BoldOblique' - stylesheet['Definition'] = para - - para = ParagraphStyle('Code', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - stylesheet['Code'] = para - - return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/styles/htu.py b/bin/reportlab/tools/pythonpoint/styles/htu.py deleted file mode 100644 index bc79a7ba68e..00000000000 --- a/bin/reportlab/tools/pythonpoint/styles/htu.py +++ /dev/null @@ -1,158 +0,0 @@ -from reportlab.lib import styles -from reportlab.lib import colors -from reportlab.lib.units import cm -from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY -from reportlab.platypus import Preformatted, Paragraph, Frame, \ - Image, Table, TableStyle, Spacer -from reportlab.pdfbase import pdfmetrics -from reportlab.pdfbase.ttfonts import TTFont - - -def getParagraphStyles(): - """Returns a dictionary of styles to get you started. - - We will provide a way to specify a module of these. Note that - this just includes TableStyles as well as ParagraphStyles for any - tables you wish to use. - """ - - pdfmetrics.registerFont(TTFont('Verdana','verdana.ttf')) - pdfmetrics.registerFont(TTFont('Verdana-Bold','verdanab.ttf')) - pdfmetrics.registerFont(TTFont('Verdana-Italic','verdanai.ttf')) - pdfmetrics.registerFont(TTFont('Verdana-BoldItalic','verdanaz.ttf')) - pdfmetrics.registerFont(TTFont('Arial Narrow','arialn.ttf')) - pdfmetrics.registerFont(TTFont('Arial Narrow-Bold','arialnb.ttf')) - pdfmetrics.registerFont(TTFont('Arial Narrow-Italic','arialni.ttf')) - pdfmetrics.registerFont(TTFont('Arial Narrow-BoldItalic','arialnbi.ttf')) - - stylesheet = {} - ParagraphStyle = styles.ParagraphStyle - - para = ParagraphStyle('Normal', None) #the ancestor of all - para.fontName = 'Verdana' - para.fontSize = 28 - para.leading = 32 - para.spaceAfter = 6 - stylesheet['Normal'] = para - - #This one is spaced out a bit... - para = ParagraphStyle('BodyText', stylesheet['Normal']) - para.spaceBefore = 12 - stylesheet['BodyText'] = para - - #Indented, for lists - para = ParagraphStyle('Indent', stylesheet['Normal']) - para.leftIndent = 60 - para.firstLineIndent = 0 - stylesheet['Indent'] = para - - para = ParagraphStyle('Centered', stylesheet['Normal']) - para.alignment = TA_CENTER - stylesheet['Centered'] = para - - para = ParagraphStyle('BigCentered', stylesheet['Normal']) - para.fontSize = 32 - para.alignment = TA_CENTER - para.spaceBefore = 12 - para.spaceAfter = 12 - stylesheet['BigCentered'] = para - - para = ParagraphStyle('Italic', stylesheet['BodyText']) - para.fontName = 'Verdana-Italic' - stylesheet['Italic'] = para - - para = ParagraphStyle('Title', stylesheet['Normal']) - para.fontName = 'Arial Narrow-Bold' - para.fontSize = 48 - para.leading = 58 - para.alignment = TA_CENTER - stylesheet['Title'] = para - - para = ParagraphStyle('Heading1', stylesheet['Normal']) - para.fontName = 'Arial Narrow-Bold' - para.fontSize = 40 - para.leading = 44 - para.alignment = TA_CENTER - stylesheet['Heading1'] = para - - para = ParagraphStyle('Heading2', stylesheet['Normal']) - para.fontName = 'Verdana' - para.fontSize = 32 - para.leading = 36 - para.spaceBefore = 32 - para.spaceAfter = 12 - stylesheet['Heading2'] = para - - para = ParagraphStyle('Heading3', stylesheet['Normal']) - para.fontName = 'Verdana' - para.spaceBefore = 20 - para.spaceAfter = 6 - stylesheet['Heading3'] = para - - para = ParagraphStyle('Heading4', stylesheet['Normal']) - para.fontName = 'Verdana-BoldItalic' - para.spaceBefore = 6 - stylesheet['Heading4'] = para - - para = ParagraphStyle('Bullet', stylesheet['Normal']) - para.firstLineIndent = 0 - para.leftIndent = 56 - para.spaceBefore = 6 - para.bulletFontName = 'Symbol' - para.bulletFontSize = 24 - para.bulletIndent = 20 - stylesheet['Bullet'] = para - - para = ParagraphStyle('Bullet2', stylesheet['Normal']) - para.firstLineIndent = 0 - para.leftIndent = 80 - para.spaceBefore = 6 - para.fontSize = 24 - para.bulletFontName = 'Symbol' - para.bulletFontSize = 20 - para.bulletIndent = 60 - stylesheet['Bullet2'] = para - - para = ParagraphStyle('Definition', stylesheet['Normal']) - #use this for definition lists - para.firstLineIndent = 0 - para.leftIndent = 60 - para.bulletIndent = 0 - para.bulletFontName = 'Verdana-BoldItalic' - para.bulletFontSize = 24 - stylesheet['Definition'] = para - - para = ParagraphStyle('Code', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - stylesheet['Code'] = para - - para = ParagraphStyle('PythonCode', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - stylesheet['Code'] = para - - para = ParagraphStyle('Small', stylesheet['Normal']) - para.fontSize = 12 - para.leading = 14 - stylesheet['Small'] = para - - #now for a table - ts = TableStyle([ - ('FONT', (0,0), (-1,-1), 'Arial Narrow', 22), - ('LINEABOVE', (0,1), (-1,1), 2, colors.green), - ('LINEABOVE', (0,2), (-1,-1), 0.25, colors.black), - ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green), - ('LINEBEFORE', (0,1), (-1,-1), 2, colors.black), - ('LINEAFTER', (0,1), (-1,-1), 2, colors.black), - ('ALIGN', (4,1), (-1,-1), 'RIGHT'), #all numeric cells right aligned - ('TEXTCOLOR', (0,2), (0,-1), colors.black), - ('BACKGROUND', (0,1), (-1,1), colors.Color(0,0.7,0.7)) - ]) - stylesheet['table1'] = ts - - return stylesheet diff --git a/bin/reportlab/tools/pythonpoint/styles/modern.py b/bin/reportlab/tools/pythonpoint/styles/modern.py deleted file mode 100644 index a2fbca19ef0..00000000000 --- a/bin/reportlab/tools/pythonpoint/styles/modern.py +++ /dev/null @@ -1,120 +0,0 @@ -#Copyright ReportLab Europe Ltd. 2000-2004 -#see license.txt for license details -#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/tools/pythonpoint/styles/modern.py -__version__=''' $Id$ ''' -# style_modern.py -__doc__="""This is an example style sheet. You can create your own, and -have them loaded by the presentation. A style sheet is just a -dictionary, where they keys are style names and the values are -ParagraphStyle objects. - -You must provide a function called "getParagraphStyles()" to -return it. In future, we can put things like LineStyles, -TableCellStyles etc. in the same modules. - -You might wish to have two parallel style sheets, one for colour -and one for black and white, so you can switch your presentations -easily. - -A style sheet MUST define a style called 'Normal'. -""" - -from reportlab.lib import styles -from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY - -def getParagraphStyles(): - """Returns a dictionary of styles based on Helvetica""" - stylesheet = {} - ParagraphStyle = styles.ParagraphStyle - - para = ParagraphStyle('Normal', None) #the ancestor of all - para.fontName = 'Helvetica' - para.fontSize = 24 - para.leading = 28 - stylesheet['Normal'] = para - - para = ParagraphStyle('BodyText', stylesheet['Normal']) - para.spaceBefore = 12 - stylesheet['BodyText'] = para - - para = ParagraphStyle('Indent', stylesheet['Normal']) - para.leftIndent = 36 - para.firstLineIndent = 0 - stylesheet['Indent'] = para - - para = ParagraphStyle('Centered', stylesheet['Normal']) - para.alignment = TA_CENTER - stylesheet['Centered'] = para - - para = ParagraphStyle('BigCentered', stylesheet['Normal']) - para.spaceBefore = 12 - para.alignment = TA_CENTER - stylesheet['BigCentered'] = para - - para = ParagraphStyle('Italic', stylesheet['BodyText']) - para.fontName = 'Helvetica-Oblique' - stylesheet['Italic'] = para - - para = ParagraphStyle('Title', stylesheet['Normal']) - para.fontName = 'Helvetica' - para.fontSize = 48 - para.Leading = 58 - para.spaceAfter = 36 - para.alignment = TA_CENTER - stylesheet['Title'] = para - - para = ParagraphStyle('Heading1', stylesheet['Normal']) - para.fontName = 'Helvetica-Bold' - para.fontSize = 36 - para.leading = 44 - para.spaceAfter = 36 - para.alignment = TA_CENTER - stylesheet['Heading1'] = para - - para = ParagraphStyle('Heading2', stylesheet['Normal']) - para.fontName = 'Helvetica-Bold' - para.fontSize = 28 - para.leading = 34 - para.spaceBefore = 24 - para.spaceAfter = 12 - stylesheet['Heading2'] = para - - para = ParagraphStyle('Heading3', stylesheet['Normal']) - para.fontName = 'Helvetica-BoldOblique' - para.spaceBefore = 24 - para.spaceAfter = 12 - stylesheet['Heading3'] = para - - para = ParagraphStyle('Bullet', stylesheet['Normal']) - para.firstLineIndent = -18 - para.leftIndent = 72 - para.spaceBefore = 6 - para.bulletFontName = 'Symbol' - para.bulletFontSize = 24 - para.bulletIndent = 36 - stylesheet['Bullet'] = para - - para = ParagraphStyle('Bullet2', stylesheet['Bullet']) - para.firstLineIndent = 0 - para.bulletIndent = 72 - para.leftIndent = 108 - stylesheet['Bullet2'] = para - - - para = ParagraphStyle('Definition', stylesheet['Normal']) - #use this for definition lists - para.firstLineIndent = 0 - para.leftIndent = 72 - para.bulletIndent = 0 - para.spaceBefore = 12 - para.bulletFontName = 'Helvetica-BoldOblique' - stylesheet['Definition'] = para - - para = ParagraphStyle('Code', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - stylesheet['Code'] = para - - return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/styles/projection.py b/bin/reportlab/tools/pythonpoint/styles/projection.py deleted file mode 100644 index a3c818556d0..00000000000 --- a/bin/reportlab/tools/pythonpoint/styles/projection.py +++ /dev/null @@ -1,106 +0,0 @@ -"""This is an example style sheet. You can create your own, and -have them loaded by the presentation. A style sheet is just a -dictionary, where they keys are style names and the values are -ParagraphStyle objects. - -You must provide a function called "getParagraphStyles()" to -return it. In future, we can put things like LineStyles, -TableCellStyles etc. in the same modules. - -You might wish to have two parallel style sheets, one for colour -and one for black and white, so you can switch your presentations -easily. - -A style sheet MUST define a style called 'Normal'. -""" - -from reportlab.lib import styles -from reportlab.lib.colors import * -from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY - - -def getParagraphStyles(): - """Returns a dictionary of styles based on Helvetica""" - - stylesheet = {} - ParagraphStyle = styles.ParagraphStyle - - para = ParagraphStyle('Normal', None) #the ancestor of all - para.fontName = 'Helvetica-Bold' - para.fontSize = 24 - para.leading = 28 - para.textColor = white - stylesheet['Normal'] = para - - para = ParagraphStyle('BodyText', stylesheet['Normal']) - para.spaceBefore = 12 - stylesheet['BodyText'] = para - - para = ParagraphStyle('BigCentered', stylesheet['Normal']) - para.spaceBefore = 12 - para.alignment = TA_CENTER - stylesheet['BigCentered'] = para - - para = ParagraphStyle('Italic', stylesheet['BodyText']) - para.fontName = 'Helvetica-Oblique' - para.textColor = white - stylesheet['Italic'] = para - - para = ParagraphStyle('Title', stylesheet['Normal']) - para.fontName = 'Helvetica' - para.fontSize = 48 - para.Leading = 58 - para.spaceAfter = 36 - para.alignment = TA_CENTER - stylesheet['Title'] = para - - para = ParagraphStyle('Heading1', stylesheet['Normal']) - para.fontName = 'Helvetica-Bold' - para.fontSize = 48# 36 - para.leading = 44 - para.spaceAfter = 36 - para.textColor = green - para.alignment = TA_LEFT - stylesheet['Heading1'] = para - - para = ParagraphStyle('Heading2', stylesheet['Normal']) - para.fontName = 'Helvetica-Bold' - para.fontSize = 28 - para.leading = 34 - para.spaceBefore = 24 - para.spaceAfter = 12 - stylesheet['Heading2'] = para - - para = ParagraphStyle('Heading3', stylesheet['Normal']) - para.fontName = 'Helvetica-BoldOblique' - para.spaceBefore = 24 - para.spaceAfter = 12 - stylesheet['Heading3'] = para - - para = ParagraphStyle('Bullet', stylesheet['Normal']) - para.firstLineIndent = -18 - para.leftIndent = 72 - para.spaceBefore = 6 - para.bulletFontName = 'Symbol' - para.bulletFontSize = 24 - para.bulletIndent = 36 - stylesheet['Bullet'] = para - - para = ParagraphStyle('Definition', stylesheet['Normal']) - #use this for definition lists - para.firstLineIndent = 0 - para.leftIndent = 72 - para.bulletIndent = 0 - para.spaceBefore = 12 - para.bulletFontName = 'Helvetica-BoldOblique' - stylesheet['Definition'] = para - - para = ParagraphStyle('Code', stylesheet['Normal']) - para.fontName = 'Courier-Bold' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - para.textColor = chartreuse - stylesheet['Code'] = para - - return stylesheet \ No newline at end of file diff --git a/bin/reportlab/tools/pythonpoint/styles/standard.py b/bin/reportlab/tools/pythonpoint/styles/standard.py deleted file mode 100644 index c525964585d..00000000000 --- a/bin/reportlab/tools/pythonpoint/styles/standard.py +++ /dev/null @@ -1,132 +0,0 @@ -from reportlab.lib import styles -from reportlab.lib import colors -from reportlab.lib.units import cm -from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY -from reportlab.platypus import Preformatted, Paragraph, Frame, \ - Image, Table, TableStyle, Spacer - - -def getParagraphStyles(): - """Returns a dictionary of styles to get you started. - - We will provide a way to specify a module of these. Note that - this just includes TableStyles as well as ParagraphStyles for any - tables you wish to use. - """ - - stylesheet = {} - ParagraphStyle = styles.ParagraphStyle - - para = ParagraphStyle('Normal', None) #the ancestor of all - para.fontName = 'Times-Roman' - para.fontSize = 24 - para.leading = 28 - stylesheet['Normal'] = para - - #This one is spaced out a bit... - para = ParagraphStyle('BodyText', stylesheet['Normal']) - para.spaceBefore = 12 - stylesheet['BodyText'] = para - - #Indented, for lists - para = ParagraphStyle('Indent', stylesheet['Normal']) - para.leftIndent = 36 - para.firstLineIndent = 0 - stylesheet['Indent'] = para - - para = ParagraphStyle('Centered', stylesheet['Normal']) - para.alignment = TA_CENTER - stylesheet['Centered'] = para - - para = ParagraphStyle('BigCentered', stylesheet['Normal']) - para.spaceBefore = 12 - para.alignment = TA_CENTER - stylesheet['BigCentered'] = para - - para = ParagraphStyle('Italic', stylesheet['BodyText']) - para.fontName = 'Times-Italic' - stylesheet['Italic'] = para - - para = ParagraphStyle('Title', stylesheet['Normal']) - para.fontName = 'Times-Roman' - para.fontSize = 48 - para.leading = 58 - para.alignment = TA_CENTER - stylesheet['Title'] = para - - para = ParagraphStyle('Heading1', stylesheet['Normal']) - para.fontName = 'Times-Bold' - para.fontSize = 36 - para.leading = 44 - para.alignment = TA_CENTER - stylesheet['Heading1'] = para - - para = ParagraphStyle('Heading2', stylesheet['Normal']) - para.fontName = 'Times-Bold' - para.fontSize = 28 - para.leading = 34 - para.spaceBefore = 24 - stylesheet['Heading2'] = para - - para = ParagraphStyle('Heading3', stylesheet['Normal']) - para.fontName = 'Times-BoldItalic' - para.spaceBefore = 24 - stylesheet['Heading3'] = para - - para = ParagraphStyle('Heading4', stylesheet['Normal']) - para.fontName = 'Times-BoldItalic' - para.spaceBefore = 6 - stylesheet['Heading4'] = para - - para = ParagraphStyle('Bullet', stylesheet['Normal']) - para.firstLineIndent = 0 - para.leftIndent = 56 - para.spaceBefore = 6 - para.bulletFontName = 'Symbol' - para.bulletFontSize = 24 - para.bulletIndent = 20 - stylesheet['Bullet'] = para - - para = ParagraphStyle('Definition', stylesheet['Normal']) - #use this for definition lists - para.firstLineIndent = 0 - para.leftIndent = 72 - para.bulletIndent = 0 - para.spaceBefore = 12 - para.bulletFontName = 'Helvetica-BoldOblique' - para.bulletFontSize = 24 - stylesheet['Definition'] = para - - para = ParagraphStyle('Code', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - stylesheet['Code'] = para - - para = ParagraphStyle('PythonCode', stylesheet['Normal']) - para.fontName = 'Courier' - para.fontSize = 16 - para.leading = 18 - para.leftIndent = 36 - stylesheet['PythonCode'] = para - - para = ParagraphStyle('Small', stylesheet['Normal']) - para.fontSize = 12 - para.leading = 14 - stylesheet['Small'] = para - - #now for a table - ts = TableStyle([ - ('FONT', (0,0), (-1,-1), 'Times-Roman', 24), - ('LINEABOVE', (0,0), (-1,0), 2, colors.green), - ('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), - ('LINEBELOW', (0,-1), (-1,-1), 2, colors.green), - ('LINEBEFORE', (-1,0), (-1,-1), 2, colors.black), - ('ALIGN', (1,1), (-1,-1), 'RIGHT'), #all numeric cells right aligned - ('TEXTCOLOR', (0,1), (0,-1), colors.red), - ('BACKGROUND', (0,0), (-1,0), colors.Color(0,0.7,0.7)) - ]) - stylesheet['table1'] = ts - - return stylesheet