summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorpinky <>2007-01-07 23:35:02 +0000
committerpinky <>2007-01-07 23:35:02 +0000
commit87b2a6bebceba02de5b98c2bd963cbb2e363c453 (patch)
treef884d8d6599d5d7988167827bb081442d11a96af /bin
parentac5f8444449ad5cae6a7ca5f4c7e313c9caa9fdb (diff)
RL2
bzr revid: pinky-9ee609e92f665d717ac2a51b921e52f7f73390c4
Diffstat (limited to 'bin')
-rwxr-xr-xbin/reportlab/pdfbase/__init__.py6
-rw-r--r--bin/reportlab/pdfbase/_can_cmap_data.py58
-rw-r--r--bin/reportlab/pdfbase/_cidfontdata.py483
-rw-r--r--bin/reportlab/pdfbase/_fontdata.py2590
-rw-r--r--bin/reportlab/pdfbase/cidfonts.py516
-rwxr-xr-xbin/reportlab/pdfbase/pdfdoc.py1892
-rw-r--r--bin/reportlab/pdfbase/pdfform.py632
-rwxr-xr-xbin/reportlab/pdfbase/pdfmetrics.py796
-rw-r--r--bin/reportlab/pdfbase/pdfpattern.py59
-rwxr-xr-xbin/reportlab/pdfbase/pdfutils.py460
-rw-r--r--bin/reportlab/pdfbase/rl_codecs.py1027
-rw-r--r--bin/reportlab/pdfbase/ttfonts.py1113
-rwxr-xr-xbin/reportlab/pdfgen/__init__.py5
-rwxr-xr-xbin/reportlab/pdfgen/canvas.py1462
-rwxr-xr-xbin/reportlab/pdfgen/pathobject.py101
-rwxr-xr-xbin/reportlab/pdfgen/pdfgeom.py77
-rw-r--r--bin/reportlab/pdfgen/pdfimages.py188
-rw-r--r--bin/reportlab/pdfgen/pycanvas.py309
-rwxr-xr-xbin/reportlab/pdfgen/textobject.py398
19 files changed, 12172 insertions, 0 deletions
diff --git a/bin/reportlab/pdfbase/__init__.py b/bin/reportlab/pdfbase/__init__.py
new file mode 100755
index 00000000000..e76fe85ee22
--- /dev/null
+++ b/bin/reportlab/pdfbase/__init__.py
@@ -0,0 +1,6 @@
+#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/__init__.py
+__version__=''' $Id: __init__.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
+__doc__="""
+""" \ No newline at end of file
diff --git a/bin/reportlab/pdfbase/_can_cmap_data.py b/bin/reportlab/pdfbase/_can_cmap_data.py
new file mode 100644
index 00000000000..c4a7100f5cb
--- /dev/null
+++ b/bin/reportlab/pdfbase/_can_cmap_data.py
@@ -0,0 +1,58 @@
+#
+"""
+This is a utility to 'can' the widths data for certain CID fonts.
+Now we're using Unicode, we don't need 20 CMAP files for each Asian
+language, nor the widths of the non-normal characters encoded in each
+font. we just want a dictionary of the character widths in a given
+font which are NOT 1000 ems wide, keyed on Unicode character (not CID).
+
+Running off CMAP files we get the following widths...
+>>> font.stringWidth(unicode(','), 10)
+2.5
+>>> font.stringWidth(unicode('m'), 10)
+7.7800000000000002
+>>> font.stringWidth(u'\u6771\u4EAC', 10)
+20.0
+>>>
+
+"""
+
+from pprint import pprint as pp
+
+from reportlab.pdfbase._cidfontdata import defaultUnicodeEncodings
+from reportlab.pdfbase.cidfonts import UnicodeCIDFont
+
+
+def run():
+
+ buf = []
+ buf.append('widthsByUnichar = {}')
+ for (fontName, (language, encName)) in defaultUnicodeEncodings.items():
+ print 'handling %s : %s : %s' % (fontName, language, encName)
+
+ #this does just about all of it for us, as all the info
+ #we need is present.
+ font = UnicodeCIDFont(fontName)
+
+ widthsByCID = font.face._explicitWidths
+ cmap = font.encoding._cmap
+ nonStandardWidthsByUnichar = {}
+ for (codePoint, cid) in cmap.items():
+ width = widthsByCID.get(cid, 1000)
+ if width <> 1000:
+ nonStandardWidthsByUnichar[unichr(codePoint)] = width
+
+
+
+ print 'created font width map (%d items). ' % len(nonStandardWidthsByUnichar)
+
+ buf.append('widthsByUnichar["%s"] = %s' % (fontName, repr(nonStandardWidthsByUnichar)))
+
+
+ src = '\n'.join(buf) + '\n'
+ open('canned_widths.py','w').write(src)
+ print 'wrote canned_widths.py'
+
+if __name__=='__main__':
+ run()
+ \ No newline at end of file
diff --git a/bin/reportlab/pdfbase/_cidfontdata.py b/bin/reportlab/pdfbase/_cidfontdata.py
new file mode 100644
index 00000000000..870055d0ab3
--- /dev/null
+++ b/bin/reportlab/pdfbase/_cidfontdata.py
@@ -0,0 +1,483 @@
+#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/_cidfontdata.py
+#$Header $
+__version__=''' $Id: _cidfontdata.py 2901 2006-05-22 23:02:22Z andy $ '''
+__doc__="""
+This defines additional static data to support CID fonts.
+
+Canned data is provided for the Japanese fonts supported by Adobe. We
+can add Chinese, Korean and Vietnamese in due course. The data was
+extracted by creating very simple postscript documents and running
+through Distiller, then examining the resulting PDFs.
+
+Each font is described as a big nested dictionary. This lets us keep
+code out of the module altogether and avoid circular dependencies.
+
+The encoding and font data are grouped by some standard 'language
+prefixes':
+ chs = Chinese Simplified (mainland)
+ cht = Chinese Traditional (Taiwan)
+ kor = Korean
+ jpn = Japanese
+"""
+
+
+languages = ['jpn', 'kor', 'cht', 'chs']
+
+#breaking down the lists let us check if something is present
+#for a specific language
+typeFaces_chs = ['STSong-Light'] # to do
+typeFaces_cht = ['MSung-Light'] #, 'MHei-Medium'] # to do
+typeFaces_jpn = ['HeiseiMin-W3', 'HeiseiKakuGo-W5']
+typeFaces_kor = ['HYSMyeongJo-Medium','HYGothic-Medium']
+
+allowedTypeFaces = typeFaces_chs + typeFaces_cht + typeFaces_jpn + typeFaces_kor
+
+
+
+
+encodings_jpn = [
+ # official encoding names, comments taken verbatim from PDF Spec
+ '83pv-RKSJ-H', #Macintosh, JIS X 0208 character set with KanjiTalk6
+ #extensions, Shift-JIS encoding, Script Manager code 1
+ '90ms-RKSJ-H', #Microsoft Code Page 932 (lfCharSet 0x80), JIS X 0208
+ #character set with NEC and IBM extensions
+ '90ms-RKSJ-V', #Vertical version of 90ms-RKSJ-H
+ '90msp-RKSJ-H', #Same as 90ms-RKSJ-H, but replaces half-width Latin
+ #characters with proportional forms
+ '90msp-RKSJ-V', #Vertical version of 90msp-RKSJ-H
+ '90pv-RKSJ-H', #Macintosh, JIS X 0208 character set with KanjiTalk7
+ #extensions, Shift-JIS encoding, Script Manager code 1
+ 'Add-RKSJ-H', #JIS X 0208 character set with Fujitsu FMR extensions,
+ #Shift-JIS encoding
+ 'Add-RKSJ-V', #Vertical version of Add-RKSJ-H
+ 'EUC-H', #JIS X 0208 character set, EUC-JP encoding
+ 'EUC-V', #Vertical version of EUC-H
+ 'Ext-RKSJ-H', #JIS C 6226 (JIS78) character set with NEC extensions,
+ #Shift-JIS encoding
+ 'Ext-RKSJ-V', #Vertical version of Ext-RKSJ-H
+ 'H', #JIS X 0208 character set, ISO-2022-JP encoding,
+ 'V', #Vertical version of H
+ 'UniJIS-UCS2-H', #Unicode (UCS-2) encoding for the Adobe-Japan1 character
+ #collection
+ 'UniJIS-UCS2-V', #Vertical version of UniJIS-UCS2-H
+ 'UniJIS-UCS2-HW-H', #Same as UniJIS-UCS2-H, but replaces proportional Latin
+ #characters with half-width forms
+ 'UniJIS-UCS2-HW-V' #Vertical version of UniJIS-UCS2-HW-H
+ ]
+encodings_kor = [
+ 'KSC-EUC-H', # KS X 1001:1992 character set, EUC-KR encoding
+ 'KSC-EUC-V', # Vertical version of KSC-EUC-H
+ 'KSCms-UHC-H', # Microsoft Code Page 949 (lfCharSet 0x81), KS X 1001:1992
+ #character set plus 8,822 additional hangul, Unified Hangul
+ #Code (UHC) encoding
+ 'KSCms-UHC-V', #Vertical version of KSCms-UHC-H
+ 'KSCms-UHC-HW-H', #Same as KSCms-UHC-H, but replaces proportional Latin
+ # characters with halfwidth forms
+ 'KSCms-UHC-HW-V', #Vertical version of KSCms-UHC-HW-H
+ 'KSCpc-EUC-H', #Macintosh, KS X 1001:1992 character set with MacOS-KH
+ #extensions, Script Manager Code 3
+ 'UniKS-UCS2-H', #Unicode (UCS-2) encoding for the Adobe-Korea1 character collection
+ 'UniKS-UCS2-V' #Vertical version of UniKS-UCS2-H
+
+ ]
+
+encodings_chs = [
+
+ 'GB-EUC-H', # Microsoft Code Page 936 (lfCharSet 0x86), GB 2312-80
+ # character set, EUC-CN encoding
+ 'GB-EUC-V', # Vertical version of GB-EUC-H
+ 'GBpc-EUC-H', # Macintosh, GB 2312-80 character set, EUC-CN encoding,
+ # Script Manager code 2
+ 'GBpc-EUC-V', # Vertical version of GBpc-EUC-H
+ 'GBK-EUC-H', # Microsoft Code Page 936 (lfCharSet 0x86), GBK character
+ # set, GBK encoding
+ 'GBK-EUC-V', # Vertical version of GBK-EUC-V
+ 'UniGB-UCS2-H', # Unicode (UCS-2) encoding for the Adobe-GB1
+ # character collection
+ 'UniGB-UCS2-V' # Vertical version of UniGB-UCS2-H.
+ ]
+
+encodings_cht = [
+ 'B5pc-H', # Macintosh, Big Five character set, Big Five encoding,
+ # Script Manager code 2
+ 'B5pc-V', # Vertical version of B5pc-H
+ 'ETen-B5-H', # Microsoft Code Page 950 (lfCharSet 0x88), Big Five
+ # character set with ETen extensions
+ 'ETen-B5-V', # Vertical version of ETen-B5-H
+ 'ETenms-B5-H', # Microsoft Code Page 950 (lfCharSet 0x88), Big Five
+ # character set with ETen extensions; this uses proportional
+ # forms for half-width Latin characters.
+ 'ETenms-B5-V', # Vertical version of ETenms-B5-H
+ 'CNS-EUC-H', # CNS 11643-1992 character set, EUC-TW encoding
+ 'CNS-EUC-V', # Vertical version of CNS-EUC-H
+ 'UniCNS-UCS2-H', # Unicode (UCS-2) encoding for the Adobe-CNS1
+ # character collection
+ 'UniCNS-UCS2-V' # Vertical version of UniCNS-UCS2-H.
+ ]
+
+# the Identity encodings simply dump out all character
+# in the font in the order they were defined.
+allowedEncodings = (['Identity-H', 'Identity-V'] +
+ encodings_chs +
+ encodings_cht +
+ encodings_jpn +
+ encodings_kor
+ )
+
+defaultUnicodeEncodings = {
+ #we ddefine a default Unicode encoding for each face name;
+ #this should be the most commonly used horizontal unicode encoding;
+ #also define a 3-letter language code.
+ 'HeiseiMin-W3': ('jpn','UniJIS-UCS2-H'),
+ 'HeiseiKakuGo-W5': ('jpn','UniJIS-UCS2-H'),
+ 'STSong-Light': ('chs', 'UniGB-UCS2-H'),
+ 'MSung-Light': ('cht', 'UniGB-UCS2-H'),
+ #'MHei-Medium': ('cht', 'UniGB-UCS2-H'),
+ 'HYSMyeongJo-Medium': ('kor', 'UniKS-UCS2-H'),
+ 'HYGothic-Medium': ('kor','UniKS-UCS2-H'),
+ }
+
+typeFaces_chs = ['STSong-Light'] # to do
+typeFaces_cht = ['MSung-Light', 'MHei-Medium'] # to do
+typeFaces_jpn = ['HeiseiMin-W3', 'HeiseiKakuGo-W5']
+typeFaces_kor = ['HYSMyeongJo-Medium','HYGothic-Medium']
+
+
+#declare separately those used for unicode
+unicode_encodings = [enc for enc in allowedEncodings if 'UCS2' in enc]
+
+
+CIDFontInfo = {}
+#statically describe the fonts in Adobe's Japanese Language Packs
+CIDFontInfo['HeiseiMin-W3'] = {
+ 'Type':'/Font',
+ 'Subtype':'/Type0',
+ 'Name': '/%(internalName)s' , #<-- the internal name
+ 'BaseFont': '/HeiseiMin-W3',
+ 'Encoding': '/%(encodings)s',
+
+ #there could be several descendant fonts if it is an old-style
+ #type 0 compound font. For CID fonts there is just one.
+ 'DescendantFonts': [{
+ 'Type':'/Font',
+ 'Subtype':'/CIDFontType0',
+ 'BaseFont':'/HeiseiMin-W3',
+ 'FontDescriptor': {
+ 'Type': '/FontDescriptor',
+ 'Ascent': 723,
+ 'CapHeight': 709,
+ 'Descent': -241,
+ 'Flags': 6,
+ 'FontBBox': (-123, -257, 1001, 910),
+ 'FontName': '/HeiseiMin-W3',
+ 'ItalicAngle': 0,
+ 'StemV': 69,
+ 'XHeight': 450#,
+# 'Style': {'Panose': '<010502020400000000000000>'}
+ },
+ 'CIDSystemInfo': {
+ 'Registry': '(Adobe)',
+ 'Ordering': '(Japan1)',
+ 'Supplement': 2
+ },
+ #default width is 1000 em units
+ 'DW': 1000,
+ #widths of any which are not the default.
+ 'W': [1, [250, 333, 408, 500],
+ 5, [500, 833, 778, 180, 333],
+ 10, [333, 500, 564, 250, 333, 250, 278, 500],
+ 18, 26, 500, 27, 28, 278, 29, 31, 564,
+ 32, [444, 921, 722, 667],
+ 36, [667, 722, 611, 556, 722],
+ 41, [722, 333, 389, 722, 611, 889, 722],
+ 48, [722, 556, 722, 667, 556, 611, 722],
+ 55, [722, 944, 722],
+ 58, [722, 611, 333, 500, 333, 469, 500, 333,
+ 444, 500, 444, 500, 444, 333, 500],
+ 73, [500, 278],
+ 75, [278, 500, 278, 778, 500], 80, 82, 500,
+ 83, [333, 389, 278, 500],
+ 87, [500, 722, 500],
+ 90, [500, 444, 480, 200, 480, 333],
+ 97, [278], 99, [200], 101, [333, 500], 103, [500, 167],
+ 107, [500], 109, [500, 333], 111, [333, 556],
+ 113, [556, 500], 117, [250], 119, [350, 333, 444],
+ 123, [500], 126, [444, 333], 128, 137, 333,
+ 138, [1000, 889, 276, 611, 722, 889, 310, 667, 278],
+ 147, [278, 500, 722, 500, 564, 760, 564, 760],
+ 157, 158, 300, 159, [500, 300, 750], 162, 163, 750,
+ 164, 169, 722, 170, [667, 611], 172, 174, 611, 175,
+ 178, 333, 179, 185, 722, 187, 191, 722, 192,
+ [556, 444], 194, 203, 444, 204, 207, 278, 208,
+ 214, 500, 216, 222, 500,
+ 223, [556, 722, 611, 500, 389, 980, 444],
+ 231, [500], 323, [500], 325, [500],
+ 327, 389, 500]
+## 'W': (
+## # 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
+## )
+ }]# end list of descendant fonts
+ } #end HeiseiMin-W3
+
+CIDFontInfo['HeiseiKakuGo-W5'] = {'Type':'/Font',
+ 'Subtype':'/Type0',
+ 'Name': '/%(internalName)s', #<-- the internal name
+ 'BaseFont': '/HeiseiKakuGo-W5',
+ 'Encoding': '/%(encodings)s',
+ 'DescendantFonts': [{'Type':'/Font',
+ 'Subtype':'/CIDFontType0',
+ 'BaseFont':'/HeiseiKakuGo-W5',
+ 'FontDescriptor': {
+ 'Type': '/FontDescriptor',
+ 'Ascent': 752,
+ 'CapHeight': 737,
+ 'Descent': -221,
+ 'Flags': 4,
+ 'FontBBox': [-92, -250, 1010, 922],
+ 'FontName': '/HeiseKakuGo-W5',
+ 'ItalicAngle': 0,
+ 'StemH': 0,
+ 'StemV': 114,
+ 'XHeight': 553,
+## 'Style': {'Panose': '<0801020b0600000000000000>'}
+ },
+ 'CIDSystemInfo': {
+ 'Registry': '(Adobe)',
+ 'Ordering': '(Japan1)',
+ 'Supplement': 2
+ },
+ 'DW': 1000,
+ 'W': (
+ 1, (277,305,500,668,668,906,727,305,445,445,508,668,305,379,305,539),
+ 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),
+ 231, 632, 500
+ )
+ }] # end descendant fonts
+ }
+
+CIDFontInfo['HYGothic-Medium'] = {'Type':'/Font',
+ 'Subtype':'/Type0',
+ 'Name': '/%(internalName)s', #<-- the internal name
+ 'BaseFont': '/' + 'HYGothic-Medium',
+ 'Encoding': '/%(encodings)s',
+ 'DescendantFonts': [{'Type':'/Font',
+ 'Subtype':'/CIDFontType0',
+ 'BaseFont':'/'+'HYGothic-Medium',
+ 'FontDescriptor': {
+ 'Type': '/FontDescriptor',
+ 'Ascent': 752,
+ 'AvgWidth': -271,
+ 'CapHeight': 737,
+ 'Descent': -142,
+ 'Flags': 6,
+ 'FontBBox': [-6, -145, 1003, 880],
+ 'FontName': '/'+'HYSMyeongJo-Medium',
+ 'ItalicAngle': 0,
+ 'Leading': 148,
+ 'MaxWidth': 1000,
+ 'MissingWidth': 500,
+ 'StemH': 0,
+ 'StemV': 58,
+ 'XHeight': 553
+ },
+ 'CIDSystemInfo': {
+ 'Registry': '(Adobe)',
+ 'Ordering': '(Korea1)',
+ 'Supplement': 1
+ },
+ 'DW': 1000,
+ 'W': (1, 94, 500)
+ }] # end descendant fonts
+ }
+
+CIDFontInfo['HYSMyeongJo-Medium'] = {'Type':'/Font',
+ 'Subtype':'/Type0',
+ 'Name': '/%(internalName)s', #<-- the internal name
+ 'BaseFont': '/' + 'HYSMyeongJo-Medium',
+ 'Encoding': '/%(encodings)s',
+ 'DescendantFonts': [{'Type':'/Font',
+ 'Subtype':'/CIDFontType2',
+ 'BaseFont':'/'+'HYSMyeongJo-Medium',
+ 'FontDescriptor': {
+ 'Type': '/FontDescriptor',
+ 'Ascent': 752,
+ 'AvgWidth': 500,
+ 'CapHeight': 737,
+ 'Descent': -271,
+ 'Flags': 6,
+ 'FontBBox': [0, -148, 1001, 880],
+ 'FontName': '/'+'HYSMyeongJo-Medium',
+ 'ItalicAngle': 0,
+ 'Leading': 148,
+ 'MaxWidth': 1000,
+ 'MissingWidth': 500,
+ 'StemH': 91,
+ 'StemV': 58,
+ 'XHeight': 553
+ },
+ 'CIDSystemInfo': {
+ 'Registry': '(Adobe)',
+ 'Ordering': '(Korea1)',
+ 'Supplement': 1
+ },
+ 'DW': 1000,
+ 'W': [1, [333, 416],
+ 3, [416, 833, 625, 916, 833, 250, 500],
+ 10, 11, 500,
+ 12, [833, 291, 833, 291, 375, 625],
+ 18, 26, 625, 27, 28, 333, 29, 30, 833,
+ 31, [916, 500, 1000, 791, 708],
+ 36, [708, 750, 708, 666, 750, 791, 375,
+ 500, 791, 666, 916, 791, 750, 666,
+ 750, 708, 666, 791],
+ 54, [791, 750, 1000, 708],
+ 58, [708, 666, 500, 375, 500],
+ 63, 64, 500,
+ 65, [333, 541, 583, 541, 583],
+ 70, [583, 375, 583],
+ 73, [583, 291, 333, 583, 291, 875, 583],
+ 80, 82, 583,
+ 83, [458, 541, 375, 583],
+ 87, [583, 833, 625],
+ 90, [625, 500, 583], 93, 94, 583,
+ 95, [750]
+ ]
+ }] # end descendant fonts
+ }
+
+#WARNING - not checked, just copied Korean to get some output
+
+CIDFontInfo['STSong-Light'] = {'Type':'/Font',
+ 'Subtype':'/Type0',
+ 'Name': '/%(internalName)s', #<-- the internal name
+ 'BaseFont': '/' + 'STSong-Light',
+ 'Encoding': '/%(encodings)s',
+ 'DescendantFonts': [{'Type':'/Font',
+ 'Subtype':'/CIDFontType0',
+ 'BaseFont':'/'+'STSong-Light',
+ 'FontDescriptor': {
+ 'Type': '/FontDescriptor',
+ 'Ascent': 752,
+ 'CapHeight': 737,
+ 'Descent': -271,
+ 'Flags': 6,
+ 'FontBBox': [-25, -254, 1000, 880],
+ 'FontName': '/'+'STSongStd-Light',
+ 'ItalicAngle': 0,
+ 'Leading': 148,
+ 'MaxWidth': 1000,
+ 'MissingWidth': 500,
+ 'StemH': 91,
+ 'StemV': 58,
+ 'XHeight': 553
+ },
+ 'CIDSystemInfo': {
+ 'Registry': '(Adobe)',
+ 'Ordering': '(GB1)',
+ 'Supplement': 0
+ },
+ 'DW': 1000,
+ 'W': [1, [207, 270, 342, 467, 462, 797, 710, 239, 374],
+ 10, [374, 423, 605, 238, 375, 238, 334, 462],
+ 18, 26, 462, 27, 28, 238, 29, 31, 605,
+ 32, [344, 748, 684, 560, 695, 739, 563, 511, 729,
+ 793, 318, 312, 666, 526, 896, 758, 772, 544,
+ 772, 628, 465, 607, 753, 711, 972, 647, 620,
+ 607, 374, 333, 374, 606, 500, 239, 417, 503,
+ 427, 529, 415, 264, 444, 518, 241, 230, 495,
+ 228, 793, 527, 524],
+ 81, [524, 504, 338, 336, 277, 517, 450, 652, 466,
+ 452, 407, 370, 258, 370, 605]
+ ]
+ }] # end descendant fonts
+ }
+CIDFontInfo['MSung-Light'] = {'Type':'/Font',
+ 'Subtype':'/Type0',
+ 'Name': '/%(internalName)s', #<-- the internal name
+ 'BaseFont': '/' + 'MSung-Light',
+ 'Encoding': '/%(encodings)s',
+ 'DescendantFonts': [{'Type':'/Font',
+ 'Subtype':'/CIDFontType0',
+ 'BaseFont':'/'+'MSung-Light',
+ 'FontDescriptor': {
+ 'Type': '/FontDescriptor',
+ 'Ascent': 752,
+ 'CapHeight': 737,
+ 'Descent': -271,
+ 'Flags': 6,
+ 'FontBBox': [-160, -249, 1015, 888],
+ 'FontName': '/'+'MSung-Light',
+ 'ItalicAngle': 0,
+ 'Leading': 148,
+ 'MaxWidth': 1000,
+ 'MissingWidth': 500,
+ 'StemH': 45,
+ 'StemV': 58,
+ 'XHeight': 553
+ },
+ 'CIDSystemInfo': {
+ 'Registry': '(Adobe)',
+ 'Ordering': '(CNS1)',
+ 'Supplement': 1
+ },
+ 'DW': 1000,
+ 'W': [1, 2, 250, 3, [408, 668, 490, 875, 698, 250, 240],
+ 10, [240, 417, 667, 250, 313, 250, 520, 500],
+ 18, 26, 500, 27, 28, 250, 29, 31, 667,
+ 32, [396, 921, 677, 615, 719, 760, 625, 552, 771,
+ 802, 354],
+ 43, [354, 781, 604, 927, 750, 823, 563, 823, 729,
+ 542, 698, 771, 729, 948, 771, 677, 635, 344,
+ 520, 344, 469, 500, 250, 469, 521, 427, 521,
+ 438, 271, 469, 531, 250],
+ 75, [250, 458, 240, 802, 531, 500, 521],
+ 82, [521, 365, 333, 292, 521, 458, 677, 479, 458,
+ 427, 480, 496, 480, 667]]
+
+ }] # end descendant fonts
+ }
+
+
+#this data was derived from the above width information and removes all dependency on CMAP files as long as we only use the unicode fonts.
+widthsByUnichar = {}
+widthsByUnichar["MSung-Light"] = {u' ': 250, u'$': 490, u'(': 240, u',': 250, u'0': 500, u'4': 500, u'8': 500, u'<': 667, u'@': 921, u'D': 760, u'H': 802, u'L': 604, u'P': 563, u'T': 698, u'X': 771, u'\\': 520, u'`': 250, u'd': 521, u'h': 531, u'l': 240, u'p': 521, u't': 292, u'x': 479, u'|': 496, u'#': 668, u"'": 250, u'+': 667, u'/': 520, u'3': 500, u'7': 500, u';': 250, u'?': 396, u'C': 719, u'G': 771, u'K': 781, u'O': 823, u'S': 542, u'W': 948, u'[': 344, u'_': 500, u'c': 427, u'g': 469, u'k': 458, u'o': 500, u's': 333, u'w': 677, u'{': 480, u'"': 408, u'&': 698, u'*': 417, u'.': 250, u'2': 500, u'6': 500, u':': 250, u'>': 667, u'B': 615, u'F': 552, u'J': 354, u'N': 750, u'R': 729, u'V': 729, u'Z': 635, u'^': 469, u'b': 521, u'f': 271, u'j': 250, u'n': 531, u'r': 365, u'v': 458, u'z': 427, u'~': 667, u'!': 250, u'%': 875, u')': 240, u'-': 313, u'1': 500, u'5': 500, u'9': 500, u'=': 667, u'A': 677, u'E': 625, u'I': 354, u'M': 927, u'Q': 823, u'U': 771, u'Y': 677, u']': 344, u'a': 469, u'e': 438, u'i': 250, u'm': 802, u'q': 521, u'u': 521, u'y': 458, u'}': 480}
+widthsByUnichar["HeiseiKakuGo-W5"] = {u'\uff81': 500, u'\uff85': 500, u'\uff89': 500, u'\uff8d': 500, u'\uff91': 500, u'\uff95': 500, u'\uff99': 500, u'\uff9d': 500, u' ': 277, u'$': 668, u'(': 445, u',': 305, u'0': 668, u'\u0332': 668, u'4': 668, u'8': 668, u'<': 668, u'@': 871, u'D': 699, u'H': 687, u'L': 582, u'P': 582, u'T': 641, u'X': 609, u'`': 590, u'\uff62': 500, u'd': 602, u'\uff66': 500, u'h': 582, u'\uff6a': 500, u'l': 234, u'\uff6e': 500, u'p': 602, u'\uff72': 500, u't': 441, u'\uff76': 500, u'x': 531, u'\uff7a': 500, u'|': 246, u'\uff7e': 500, u'\uff82': 500, u'\uff86': 500, u'\uff8a': 500, u'\uff8e': 500, u'\uff92': 500, u'\uff96': 500, u'\uff9a': 500, u'\uff9e': 500, u'#': 668, u"'": 305, u'+': 668, u'/': 539, u'3': 668, u'7': 668, u';': 305, u'?': 566, u'C': 652, u'G': 676, u'K': 664, u'O': 734, u'S': 605, u'W': 945, u'[': 445, u'_': 668, u'\uff61': 500, u'c': 547, u'\uff65': 500, u'g': 609, u'\uff69': 500, u'k': 539, u'\uff6d': 500, u'o': 605, u'\uff71': 500, u's': 508, u'\uff75': 500, u'w': 781, u'\uff79': 500, u'{': 449, u'\uff7d': 500, u'\u0300': 590, u'\uff83': 500, u'\u2002': 500, u'\uff87': 500, u'\uff8b': 500, u'\uff8f': 500, u'\uff93': 500, u'\uff97': 500, u'\uff9b': 500, u'\uff9f': 500, u'"': 500, u'\xa5': 668, u'&': 727, u'*': 508, u'.': 305, u'2': 668, u'6': 668, u':': 305, u'>': 668, u'B': 637, u'F': 555, u'J': 492, u'N': 707, u'\u203e': 500, u'R': 605, u'V': 727, u'Z': 574, u'^': 668, u'b': 609, u'\uff64': 500, u'f': 391, u'\uff68': 500, u'j': 277, u'\uff6c': 500, u'n': 582, u'\uff70': 500, u'r': 387, u'\uff74': 500, u'v': 562, u'\uff78': 500, u'z': 555, u'\uff7c': 500, u'~': 668, u'\uff80': 500, u'\u0303': 668, u'\uff84': 500, u'\uff88': 500, u'\uff8c': 500, u'\u2011': 379, u'\uff90': 500, u'\uff94': 500, u'\uff98': 500, u'\uff9c': 500, u'!': 305, u'%': 906, u')': 445, u'-': 379, u'1': 668, u'5': 668, u'9': 668, u'=': 668, u'A': 727, u'E': 574, u'I': 242, u'M': 789, u'Q': 734, u'U': 668, u'Y': 609, u']': 445, u'a': 555, u'\uff63': 500, u'e': 574, u'\uff67': 500, u'i': 234, u'\uffe8': 500, u'\uff6b': 500, u'm': 895, u'\uff6f': 500, u'q': 602, u'\uff73': 500, u'u': 582, u'\uff77': 500, u'y': 570, u'\uff7b': 500, u'}': 449, u'\uff7f': 500}
+widthsByUnichar["HYSMyeongJo-Medium"] = {u' ': 333, u'$': 625, u'(': 500, u',': 291, u'0': 625, u'4': 625, u'8': 625, u'<': 833, u'D': 750, u'H': 791, u'L': 666, u'P': 666, u'T': 791, u'X': 708, u'\\': 375, u'`': 333, u'd': 583, u'h': 583, u'l': 291, u'p': 583, u't': 375, u'x': 625, u'|': 583, u'#': 833, u"'": 250, u'+': 833, u'/': 375, u'3': 625, u'7': 625, u';': 333, u'?': 500, u'C': 708, u'G': 750, u'K': 791, u'O': 750, u'S': 666, u'[': 500, u'_': 500, u'c': 541, u'g': 583, u'k': 583, u'o': 583, u's': 541, u'w': 833, u'{': 583, u'"': 416, u'&': 833, u'*': 500, u'.': 291, u'2': 625, u'6': 625, u':': 333, u'>': 916, u'B': 708, u'F': 666, u'J': 500, u'N': 791, u'R': 708, u'V': 750, u'Z': 666, u'^': 500, u'b': 583, u'f': 375, u'j': 333, u'n': 583, u'r': 458, u'v': 583, u'z': 500, u'~': 750, u'!': 416, u'%': 916, u')': 500, u'-': 833, u'1': 625, u'5': 625, u'9': 625, u'=': 833, u'A': 791, u'E': 708, u'I': 375, u'M': 916, u'Q': 750, u'U': 791, u'Y': 708, u']': 500, u'a': 541, u'e': 583, u'i': 291, u'm': 875, u'q': 583, u'u': 583, u'y': 625, u'}': 583}
+widthsByUnichar["STSong-Light"] = {u' ': 207, u'$': 462, u'(': 374, u',': 238, u'0': 462, u'4': 462, u'8': 462, u'<': 605, u'@': 748, u'D': 739, u'H': 793, u'L': 526, u'P': 544, u'T': 607, u'X': 647, u'\\': 333, u'`': 239, u'd': 529, u'h': 518, u'l': 228, u'p': 524, u't': 277, u'x': 466, u'|': 258, u'#': 467, u"'": 239, u'+': 605, u'/': 334, u'3': 462, u'7': 462, u';': 238, u'?': 344, u'C': 695, u'G': 729, u'K': 666, u'O': 772, u'S': 465, u'W': 972, u'[': 374, u'_': 500, u'c': 427, u'g': 444, u'k': 495, u'o': 524, u's': 336, u'w': 652, u'{': 370, u'"': 342, u'&': 710, u'*': 423, u'.': 238, u'2': 462, u'6': 462, u':': 238, u'>': 605, u'B': 560, u'F': 511, u'J': 312, u'N': 758, u'R': 628, u'V': 711, u'Z': 607, u'^': 606, u'b': 503, u'f': 264, u'j': 230, u'n': 527, u'r': 338, u'v': 450, u'z': 407, u'~': 605, u'!': 270, u'%': 797, u')': 374, u'-': 375, u'1': 462, u'5': 462, u'9': 462, u'=': 605, u'A': 684, u'E': 563, u'I': 318, u'M': 896, u'Q': 772, u'U': 753, u'Y': 620, u']': 374, u'a': 417, u'e': 415, u'i': 241, u'm': 793, u'q': 504, u'u': 517, u'y': 452, u'}': 370}
+widthsByUnichar["HeiseiMin-W3"] = {u'\uff81': 500, u'\u0302': 333, u'\uff85': 500, u'\u0306': 333, u'\uff89': 500, u'\u030a': 333, u'\uff8d': 500, u'\uff91': 500, u'\ufb02': 556, u'\uff95': 500, u'\uff99': 500, u'\uff9d': 500, u' ': 250, u'\xa3': 500, u'\u2122': 980, u'$': 500, u'(': 333, u'\xab': 500, u',': 250, u'\xaf': 333, u'0': 500, u'\xb3': 300, u'\u0332': 500, u'4': 500, u'\xb7': 250, u'8': 500, u'\xbb': 500, u'<': 564, u'\xbf': 444, u'@': 921, u'\xc3': 722, u'\u0142': 278, u'D': 722, u'\xc7': 667, u'H': 722, u'\xcb': 611, u'L': 611, u'\xcf': 333, u'P': 556, u'\xd3': 722, u'\u0152': 889, u'T': 611, u'X': 722, u'\xdb': 722, u'\\': 278, u'\xdf': 500, u'\uff64': 500, u'`': 333, u'\xe3': 444, u'\uff62': 500, u'd': 500, u'\xe7': 444, u'\uff66': 500, u'h': 500, u'\xeb': 444, u'\uff6a': 500, u'l': 278, u'\xef': 278, u'\uff6e': 500, u'p': 500, u'\xf3': 500, u'\uff72': 500, u't': 278, u'\uff76': 500, u'x': 500, u'\xfb': 500, u'\uff7a': 500, u'|': 200, u'\xff': 500, u'\u017e': 444, u'\u0301': 333, u'\uff82': 500, u'\u0305': 500, u'\uff86': 500, u'\uff8a': 500, u'\uff8e': 500, u'\u2013': 500, u'\uff92': 500, u'\uff96': 500, u'\uff9a': 500, u'\uff9e': 500, u'#': 500, u'\xa4': 500, u"'": 180, u'\u203a': 333, u'+': 564, u'\xac': 564, u'/': 278, u'\u0131': 278, u'3': 500, u'7': 500, u'\xb8': 333, u';': 278, u'\xbc': 750, u'?': 444, u'\u0141': 611, u'\xc0': 722, u'C': 667, u'\xc4': 722, u'G': 722, u'\xc8': 611, u'K': 722, u'\xcc': 333, u'O': 722, u'\xd0': 722, u'S': 556, u'\u2022': 350, u'\xd4': 722, u'W': 944, u'\uff78': 500, u'\xd8': 722, u'[': 333, u'\xdc': 722, u'_': 500, u'\u0161': 389, u'\xe0': 444, u'c': 444, u'\uff65': 500, u'\xe4': 444, u'g': 500, u'\uff69': 500, u'\xe8': 444, u'k': 500, u'\uff6d': 500, u'\xec': 278, u'o': 500, u'\uff71': 500, u'\xf0': 500, u's': 389, u'\uff75': 500, u'\xf4': 500, u'w': 722, u'\uff79': 500, u'\xf8': 500, u'{': 480, u'\uff7e': 500, u'\u017d': 611, u'\xfc': 500, u'\u0300': 333, u'\uff83': 500, u'\u2002': 500, u'\u0304': 333, u'\uff87': 500, u'\u0308': 333, u'\uff8b': 500, u'\u030c': 333, u'\uff8f': 500, u'\uff93': 500, u'\u2012': 500, u'\uff97': 500, u'\uff9b': 500, u'\u201a': 333, u'\uff9f': 500, u'\u201e': 444, u'\xa1': 333, u'"': 408, u'\xa5': 500, u'&': 778, u'\xa9': 760, u'\u0328': 333, u'*': 500, u'\xad': 564, u'.': 250, u'\uffe8': 500, u'2': 500, u'\xb5': 500, u'6': 500, u'\xb9': 300, u':': 278, u'\xbd': 750, u'>': 564, u'\xc1': 722, u'\uff61': 500, u'B': 667, u'\xc5': 722, u'F': 556, u'\xc9': 611, u'J': 389, u'\xcd': 333, u'N': 722, u'\xd1': 722, u'\u203e': 500, u'R': 667, u'\xd5': 722, u'V': 722, u'\xd9': 722, u'Z': 611, u'\xdd': 722, u'^': 469, u'\xe1': 444, u'\u0160': 556, u'b': 500, u'\xe5': 444, u'\u2039': 333, u'f': 333, u'\xe9': 444, u'\uff68': 500, u'j': 278, u'\xed': 278, u'\uff6c': 500, u'n': 500, u'\xf1': 500, u'\uff70': 500, u'r': 333, u'\xf5': 500, u'\uff74': 500, u'v': 500, u'\xf9': 500, u'\u0178': 722, u'z': 444, u'\xfd': 500, u'\uff7c': 500, u'~': 333, u'\uff80': 500, u'\u0303': 333, u'\uff84': 500, u'\u0307': 333, u'\uff88': 500, u'\u030b': 333, u'\uff8c': 500, u'\u2011': 333, u'\uff90': 500, u'\uff94': 500, u'\uff98': 500, u'\uff9c': 500, u'\u2044': 167, u'!': 333, u'\xa2': 500, u'%': 833, u'\u0327': 333, u'\xa6': 200, u')': 333, u'\xaa': 276, u'-': 333, u'\xae': 760, u'1': 500, u'\xb2': 300, u'5': 500, u'9': 500, u'\xba': 310, u'=': 564, u'\xbe': 750, u'A': 722, u'\u01c0': 200, u'\xc2': 722, u'E': 611, u'\xc6': 889, u'I': 333, u'\xca': 611, u'M': 889, u'\xce': 333, u'Q': 722, u'\u0153': 722, u'\xd2': 722, u'U': 722, u'\xd6': 722, u'Y': 722, u'\ufb01': 556, u'\xda': 722, u']': 333, u'\xde': 556, u'a': 444, u'\uff63': 500, u'\xe2': 444, u'e': 444, u'\uff67': 500, u'\xe6': 667, u'i': 278, u'\uff7d': 500, u'\uff6b': 500, u'\xea': 444, u'm': 778, u'\uff6f': 500, u'\xee': 278, u'q': 500, u'\uff73': 500, u'\xf2': 500, u'u': 500, u'\uff77': 500, u'\xf6': 500, u'y': 500, u'\uff7b': 500, u'\xfa': 500, u'}': 480, u'\uff7f': 500, u'\xfe': 500}
+widthsByUnichar["HYGothic-Medium"] = {u' ': 500, u'$': 500, u'(': 500, u',': 500, u'0': 500, u'4': 500, u'8': 500, u'<': 500, u'@': 500, u'D': 500, u'H': 500, u'L': 500, u'P': 500, u'T': 500, u'X': 500, u'\\': 500, u'`': 500, u'd': 500, u'h': 500, u'l': 500, u'p': 500, u't': 500, u'x': 500, u'|': 500, u'#': 500, u"'": 500, u'+': 500, u'/': 500, u'3': 500, u'7': 500, u';': 500, u'?': 500, u'C': 500, u'G': 500, u'K': 500, u'O': 500, u'S': 500, u'W': 500, u'[': 500, u'_': 500, u'c': 500, u'g': 500, u'k': 500, u'o': 500, u's': 500, u'w': 500, u'{': 500, u'"': 500, u'&': 500, u'*': 500, u'.': 500, u'2': 500, u'6': 500, u':': 500, u'>': 500, u'B': 500, u'F': 500, u'J': 500, u'N': 500, u'R': 500, u'V': 500, u'Z': 500, u'^': 500, u'b': 500, u'f': 500, u'j': 500, u'n': 500, u'r': 500, u'v': 500, u'z': 500, u'!': 500, u'%': 500, u')': 500, u'-': 500, u'1': 500, u'5': 500, u'9': 500, u'=': 500, u'A': 500, u'E': 500, u'I': 500, u'M': 500, u'Q': 500, u'U': 500, u'Y': 500, u']': 500, u'a': 500, u'e': 500, u'i': 500, u'm': 500, u'q': 500, u'u': 500, u'y': 500, u'}': 500}
+
+
+#shift-jis saying 'This is Heisei-Minchou'
+message1 = '\202\261\202\352\202\315\225\275\220\254\226\276\222\251\202\305\202\267\201B'
+message2 = '\202\261\202\352\202\315\225\275\220\254\212p\203S\203V\203b\203N\202\305\202\267\201B'
+
+##def pswidths(text):
+## import string
+## words = string.split(text)
+## out = []
+## for word in words:
+## if word == '[':
+## out.append(word)
+## else:
+## out.append(word + ',')
+## return eval(string.join(out, ''))
diff --git a/bin/reportlab/pdfbase/_fontdata.py b/bin/reportlab/pdfbase/_fontdata.py
new file mode 100644
index 00000000000..c4538f4d467
--- /dev/null
+++ b/bin/reportlab/pdfbase/_fontdata.py
@@ -0,0 +1,2590 @@
+#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/_fontdata.py
+#$Header $
+__version__=''' $Id: _fontdata.py 2392 2004-06-23 13:56:44Z rgbecker $ '''
+__doc__="""
+ database of font related things
+ standardFonts tuple of the 14 standard string font names
+ standardEncodings tuple of the known standard font names
+ encodings a mapping object from standard encoding names (and minor variants)
+ to the encoding vectors ie the tuple of string glyph names
+ widthsByFontGlyph fontname x glyphname --> 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
new file mode 100644
index 00000000000..ba438c40f53
--- /dev/null
+++ b/bin/reportlab/pdfbase/cidfonts.py
@@ -0,0 +1,516 @@
+#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: cidfonts.py 2905 2006-05-23 14:49:28Z andy $ '''
+__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, \
+ defaultUnicodeEncodings, widthsByUnichar
+from reportlab.pdfgen.canvas import Canvas
+from reportlab.pdfbase import pdfdoc
+from reportlab.pdfbase.pdfutils import _escape
+from reportlab.rl_config import CMapSearchPath
+
+
+#quick hackery for 2.0 release. Now we always do unicode, and have built in
+#the CMAP data, any code to load CMap files is not needed.
+DISABLE_CMAP = True
+
+
+def findCMapFile(name):
+ "Returns full filename, or raises error"
+ for dirname in CMapSearchPath:
+ cmapfile = dirname + os.sep + name
+ if os.path.isfile(cmapfile):
+ #print "found", 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 not DISABLE_CMAP:
+ 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)
+
+ def getData(self):
+ """Simple persistence helper. Return a dict with all that matters."""
+ return {
+ 'mapFileHash': self._mapFileHash,
+ 'codeSpaceRanges': self._codeSpaceRanges,
+ 'notDefRanges': self._notDefRanges,
+ 'cmap': self._cmap,
+
+ }
+
+
+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')
+
+
+ #no substitutes initially
+ self.substitutionFonts = []
+
+ def formatForPdf(self, text):
+ encoded = _escape(text)
+ #print 'encoded CIDFont:', encoded
+ return encoded
+
+ def stringWidth(self, text, size, encoding=None):
+ """This presumes non-Unicode input. UnicodeCIDFont wraps it for that context"""
+ 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
+
+
+class UnicodeCIDFont(CIDFont):
+ """Wraps up CIDFont to hide explicit encoding choice;
+ encodes text for output as UTF16.
+
+ lang should be one of 'jpn',chs','cht','kor' for now.
+ if vertical is set, it will select a different widths array
+ and possibly glyphs for some punctuation marks.
+
+ halfWidth is only for Japanese.
+
+
+ >>> dodgy = UnicodeCIDFont('nonexistent')
+ Traceback (most recent call last):
+ ...
+ KeyError: "don't know anything about CID font nonexistent"
+ >>> heisei = UnicodeCIDFont('HeiseiMin-W3')
+ >>> heisei.name
+ 'HeiseiMin-W3'
+ >>> heisei.language
+ 'jpn'
+ >>> heisei.encoding.name
+ 'UniJIS-UCS2-H'
+ >>> #This is how PDF data gets encoded.
+ >>> print heisei.formatForPdf('hello')
+ \\377\\376h\\000e\\000l\\000l\\000o\\000
+ >>> tokyo = u'\u6771\u4AEC'
+ >>> print heisei.formatForPdf(tokyo)
+ \\377\\376qg\\354J
+
+ """
+
+ def __init__(self, face, isVertical=False, isHalfWidth=False):
+ #pass
+ try:
+ lang, defaultEncoding = defaultUnicodeEncodings[face]
+ except KeyError:
+ raise KeyError("don't know anything about CID font %s" % face)
+
+ #we know the languages now.
+ self.language = lang
+
+ #rebuilt encoding string. They follow rules which work
+ #for the 7 fonts provided.
+ enc = defaultEncoding[:-1]
+ if isHalfWidth:
+ enc = enc + 'HW-'
+ if isVertical:
+ enc = enc + 'V'
+ else:
+ enc = enc + 'H'
+
+ #now we can do the more general case
+ CIDFont.__init__(self, face, enc)
+ #self.encName = 'utf_16_le'
+ #it's simpler for unicode, just use the face name
+ self.name = self.fontName = face
+ self.vertical = isVertical
+ self.isHalfWidth = isHalfWidth
+
+ self.unicodeWidths = widthsByUnichar[self.name]
+
+
+ def formatForPdf(self, text):
+ #these ones should be encoded asUTF16 minus the BOM
+ from codecs import utf_16_be_encode
+ #print 'formatting %s: %s' % (type(text), repr(text))
+ if type(text) is not unicode:
+ text = text.decode('utf8')
+ utfText = utf_16_be_encode(text)[0]
+ encoded = _escape(utfText)
+ #print ' encoded:',encoded
+ return encoded
+ #
+ #result = _escape(encoded)
+ #print ' -> %s' % repr(result)
+ #return result
+
+
+ def stringWidth(self, text, size, encoding=None):
+ "Just ensure we do width test on characters, not bytes..."
+ if type(text) is type(''):
+ text = text.decode('utf8')
+
+ widths = self.unicodeWidths
+ return size * 0.001 * sum([widths.get(uch, 1000) for uch in text])
+ #return CIDFont.stringWidth(self, text, size, encoding)
+
+
+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__':
+ import doctest, cidfonts
+ doctest.testmod(cidfonts)
+ #test()
+
+
+
+
diff --git a/bin/reportlab/pdfbase/pdfdoc.py b/bin/reportlab/pdfbase/pdfdoc.py
new file mode 100755
index 00000000000..e3a12ff4d1e
--- /dev/null
+++ b/bin/reportlab/pdfbase/pdfdoc.py
@@ -0,0 +1,1892 @@
+#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: pdfdoc.py 2854 2006-05-10 12:57:21Z rgbecker $ '''
+__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, binascii, codecs
+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 reportlab.pdfbase import pdfmetrics
+
+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": "%"}
+
+from types import InstanceType
+def format(element, document, toplevel=0, InstanceType=InstanceType):
+ """Indirection step for formatting.
+ Ensures that document parameters alter behaviour
+ of formatting for all elements.
+ """
+ t=type(element)
+ if t 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
+ return document.Reference(element).format(document)
+ else:
+ f = element.format(document)
+ if not rl_config.invariant and DoComments and hasattr(element, __Comment__):
+ f = "%s%s%s%s" % ("% ", element.__Comment__, LINEEND, f)
+ return f
+ elif t in (float, int):
+ #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 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,
+ dummyoutline=0,
+ compression=rl_config.pageCompression,
+ invariant=rl_config.invariant,
+ filename=None):
+
+ # 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)
+ # 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,enc='raw')
+ 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
+ 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 setDateFormatter(self, dateFormatter):
+ self.info._dateFormatter = dateFormatter
+
+ 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 += 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, InstanceType=InstanceType):
+ ### 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!
+ #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):
+ result = binascii.hexlify(document.encrypt.encode(self.t))
+ return "<%s>" % result
+ def __str__(self):
+ dummydoc = DummyDoc()
+ return self.format(dummydoc)
+
+def PDFnumber(n):
+ return n
+
+import re
+_re_cleanparens=re.compile('[^()]')
+del re
+def _isbalanced(s):
+ '''test whether a string is balanced in parens'''
+ s = _re_cleanparens.sub('',s)
+ n = 0
+ for c in s:
+ if c=='(': n+=1
+ else:
+ n -= 1
+ if n<0: return 0
+ return not n and 1 or 0
+
+def _checkPdfdoc(utext):
+ '''return true if no Pdfdoc encoding errors'''
+ try:
+ utext.encode('pdfdoc')
+ return 1
+ except UnicodeEncodeError, e:
+ return 0
+
+class PDFString:
+ def __init__(self, s, escape=1, enc='auto'):
+ '''s can be unicode/utf8 or a PDFString
+ if escape is true then the output will be passed through escape
+ if enc is raw then the string will be left alone
+ if enc is auto we'll try and automatically adapt to utf_16_be if the
+ effective string is not entirely in pdfdoc
+ '''
+ if isinstance(s,PDFString):
+ self.s = s.s
+ self.escape = s.escape
+ self.enc = s.enc
+ else:
+ self.s = s
+ self.escape = escape
+ self.enc = enc
+ def format(self, document):
+ s = self.s
+ enc = getattr(self,'enc','auto')
+ if type(s) is str:
+ if enc is 'auto':
+ try:
+ u = s.decode('utf8')
+ except:
+ print s
+ raise
+ if _checkPdfdoc(u):
+ s = u.encode('pdfdoc')
+ else:
+ s = codecs.BOM_UTF16_BE+u.encode('utf_16_be')
+ elif type(s) is unicode:
+ if enc is 'auto':
+ if _checkPdfdoc(s):
+ s = s.encode('pdfdoc')
+ else:
+ s = codecs.BOM_UTF16_BE+s.encode('utf_16_be')
+ else:
+ s = codecs.BOM_UTF16_BE+s.encode('utf_16_be')
+ else:
+ raise ValueError('PDFString argument must be str/unicode not %s' % type(s))
+
+ escape = getattr(self,'escape',1)
+ if not isinstance(document.encrypt,NoEncryption):
+ s = document.encrypt.encode(s)
+ escape = 1
+ if escape:
+ try:
+ es = "(%s)" % pdfutils._escape(s)
+ except:
+ raise ValueError("cannot escape %s %s" % (s, repr(s)))
+ if escape&2:
+ es = es.replace('\\012','\n')
+ if escape&4 and _isbalanced(s):
+ es = es.replace('\\(','(').replace('\\)',')')
+ return es
+ else:
+ return '(%s)' % s
+ def __str__(self):
+ return "(%s)" % pdfutils._escape(self.s)
+
+def PDFName(data,lo=chr(0x21),hi=chr(0x7e)):
+ # might need to change this to class for encryption
+ # NOTE: RESULT MUST ALWAYS SUPPORT MEANINGFUL COMPARISONS (EQUALITY) AND HASH
+ # first convert the name
+ L = list(data)
+ for i,c in enumerate(L):
+ if c<lo or c>hi or c in "%()<>{}[]#":
+ L[i] = "#"+hex(ord(c))[2:] # forget the 0x thing...
+ return "/"+(''.join(L))
+
+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):
+ self.dict[name] = document.Reference(self.dict[name])
+ def format(self, document,IND=LINEEND+' '):
+ dict = self.dict
+ keys = dict.keys()
+ keys.sort()
+ L = [(format(PDFName(k),document)+" "+format(dict[k],document)) for k in keys]
+ if self.multiline:
+ L = IND.join(L)
+ else:
+ # break up every 6 elements anyway
+ t=L.insert
+ for i in xrange(6, len(L), 6):
+ t(i,LINEEND)
+ L = " ".join(L)
+ return "<< %s >>" % L
+
+# 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, IND=LINEEND+' '):
+ L = [format(e, document) for e in self.sequence]
+ if self.multiline:
+ L = IND.join(L)
+ else:
+ # break up every 10 elements anyway
+ breakline = LINEEND+" "
+ for i in xrange(10, len(L), 10):
+ L.insert(i,breakline)
+ L = ' '.join(L)
+ return "[ %s ]" % L
+
+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):
+ try:
+ return "%s %s R" % document.idToObjectNumberAndVersion[self.name]
+ except:
+ raise KeyError, "forward reference to %s not resolved upon final formatting" % repr(self.name)
+
+### 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.write = self.strings.append
+ self.offset = 0
+ self.add(PDFHeader)
+
+ def closeOrReset(self):
+ pass
+
+ def add(self, s):
+ """should be constructed as late as possible, return position where placed"""
+ result = self.offset
+ self.offset = result+len(s)
+ self.write(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<firstentrynumber:
+ raise ValueError, "object number %s not in range %s..%s" % (num, firstentrynumber, lastentrynumber)
+ # compute position in list
+ rnum = num-firstentrynumber
+ taken[num] = id
+ offset = idToOffset[id]
+ entries[num] = XREFFMT % (offset, version)
+ # now add the initial line
+ firstline = "%s %s" % (firstentrynumber, nentries)
+ entries.insert(0, firstline)
+ # make sure it ends with a LINEEND
+ entries.append("")
+ if LINEEND=="\n" or LINEEND=="\r":
+ reflineend = " "+LINEEND # as per spec
+ elif LINEEND=="\r\n":
+ reflineend = LINEEND
+ else:
+ raise ValueError, "bad end of line! %s" % repr(LINEEND)
+ return string.join(entries, LINEEND)
+
+class PDFCrossReferenceTable:
+
+ def __init__(self):
+ self.sections = []
+ def addsection(self, firstentry, ids):
+ section = PDFCrossReferenceSubsection(firstentry, ids)
+ self.sections.append(section)
+ def format(self, document):
+ sections = self.sections
+ if not sections:
+ raise ValueError, "no crossref sections"
+ L = ["xref"+LINEEND]
+ for s in self.sections:
+ fs = format(s, document)
+ L.append(fs)
+ return string.join(L, "")
+
+TRAILERFMT = ("trailer%(LINEEND)s"
+ "%(dict)s%(LINEEND)s"
+ "startxref%(LINEEND)s"
+ "%(startxref)s%(LINEEND)s"
+ "%(PERCENT)s%(PERCENT)sEOF%(LINEEND)s")
+
+class PDFTrailer:
+
+ def __init__(self, startxref, Size=None, Prev=None, Root=None, Info=None, ID=None, Encrypt=None):
+ self.startxref = startxref
+ if Size is None or Root is None:
+ raise ValueError, "Size and Root keys required"
+ dict = self.dict = PDFDictionary()
+ for (n,v) in [("Size", Size), ("Prev", Prev), ("Root", Root),
+ ("Info", Info), ("ID", ID), ("Encrypt", Encrypt)]:
+ if v is not None:
+ dict[n] = v
+ def format(self, document):
+ fdict = format(self.dict, document)
+ D = LINEENDDICT.copy()
+ D["dict"] = fdict
+ D["startxref"] = self.startxref
+ return TRAILERFMT % D
+
+#### XXXX skipping incremental update,
+#### encryption
+
+#### chapter 6, doc structure
+
+class PDFCatalog:
+ __Comment__ = "Document Root"
+ __RefOnly__ = 1
+ # to override, set as attributes
+ __Defaults__ = {"Type": PDFName("Catalog"),
+ "PageMode": PDFName("UseNone"),
+ }
+ __NoDefault__ = string.split("""
+ Dests Outlines Pages Threads AcroForm Names OpenActions PageMode URI
+ ViewerPreferences PageLabels PageLayout JavaScript StructTreeRoot SpiderInfo"""
+ )
+ __Refs__ = __NoDefault__ # make these all into references, if present
+
+ def format(self, document):
+ self.check_format(document)
+ defaults = self.__Defaults__
+ Refs = self.__Refs__
+ D = {}
+ for k in defaults.keys():
+ default = defaults[k]
+ v = None
+ if hasattr(self, k) and getattr(self,k) is not None:
+ v = getattr(self, k)
+ elif default is not None:
+ v = default
+ if v is not None:
+ D[k] = v
+ for k in self.__NoDefault__:
+ if hasattr(self, k):
+ v = getattr(self,k)
+ if v is not None:
+ D[k] = v
+ # force objects to be references where required
+ for k in Refs:
+ if D.has_key(k):
+ #print"k is", k, "value", D[k]
+ D[k] = document.Reference(D[k])
+ dict = PDFDictionary(D)
+ return format(dict, document)
+
+ def showOutline(self):
+ self.PageMode = PDFName("UseOutlines")
+
+ def showFullScreen(self):
+ self.PageMode = PDFName("FullScreen")
+
+ def check_format(self, document):
+ """for use in subclasses"""
+ pass
+
+# not yet implementing
+# ViewerPreferences, PageLabelDictionaries,
+
+class PDFPages(PDFCatalog):
+ """PAGES TREE WITH ONE INTERNAL NODE, FOR "BALANCING" CHANGE IMPLEMENTATION"""
+ __Comment__ = "page tree"
+ __RefOnly__ = 1
+ # note: could implement page attribute inheritance...
+ __Defaults__ = {"Type": PDFName("Pages"),
+ }
+ __NoDefault__ = string.split("Kids Count Parent")
+ __Refs__ = ["Parent"]
+ def __init__(self):
+ self.pages = []
+ def __getitem__(self, item):
+ return self.pages[item]
+ def addPage(self, page):
+ self.pages.append(page)
+ def check_format(self, document):
+ # convert all pages to page references
+ pages = self.pages
+ kids = PDFArray(pages)
+ # make sure all pages are references
+ kids.References(document)
+ self.Kids = kids
+ self.Count = len(pages)
+
+class PDFPage(PDFCatalog):
+ __Comment__ = "Page dictionary"
+ # all PDF attributes can be set explicitly
+ # if this flag is set, the "usual" behavior will be suppressed
+ Override_default_compilation = 0
+ __RefOnly__ = 1
+ __Defaults__ = {"Type": PDFName("Page"),
+ # "Parent": PDFObjectReference(Pages), # no! use document.Pages
+ }
+ __NoDefault__ = string.split(""" Parent
+ MediaBox Resources Contents CropBox Rotate Thumb Annots B Dur Hid Trans AA
+ PieceInfo LastModified SeparationInfo ArtBox TrimBox BleedBox ID PZ
+ Trans
+ """)
+ __Refs__ = string.split("""
+ Contents Parent ID
+ """)
+ pagewidth = 595
+ pageheight = 842
+ stream = None
+ hasImages = 0
+ compression = 0
+ XObjects = None
+ Trans = None
+ # transitionstring?
+ # xobjects?
+ # annotations
+ def __init__(self):
+ # set all nodefaults to None
+ for name in self.__NoDefault__:
+ setattr(self, name, None)
+ def setCompression(self, onoff):
+ self.compression = onoff
+ def setStream(self, code):
+ if self.Override_default_compilation:
+ raise ValueError, "overridden! must set stream explicitly"
+ from types import ListType
+ if type(code) is ListType:
+ code = string.join(code, LINEEND)+LINEEND
+ self.stream = code
+
+ def setPageTransition(self, tranDict):
+ self.Trans = PDFDictionary(tranDict)
+
+ def check_format(self, document):
+ # set up parameters unless usual behaviour is suppressed
+ if self.Override_default_compilation:
+ return
+ self.MediaBox = self.MediaBox or PDFArray([0, 0, self.pagewidth, self.pageheight])
+ if not self.Annots:
+ self.Annots = None
+ else:
+ #print self.Annots
+ #raise ValueError, "annotations not reimplemented yet"
+ if type(self.Annots) is not types.InstanceType:
+ self.Annots = PDFArray(self.Annots)
+ if not self.Contents:
+ stream = self.stream
+ if not stream:
+ self.Contents = teststream()
+ else:
+ S = PDFStream()
+ if self.compression:
+ S.filters = [PDFBase85Encode, PDFZCompress]
+ S.content = stream
+ S.__Comment__ = "page 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
+ self.Resources = resources
+ if not self.Parent:
+ pages = document.Pages
+ self.Parent = document.Reference(pages)
+
+def testpage(document):
+ P = PDFPage()
+ P.Contents = teststream()
+ pages = document.Pages
+ P.Parent = document.Reference(pages)
+ P.MediaBox = PDFArray([0, 0, 595, 841])
+ resources = PDFResourceDictionary()
+ resources.allProcs() # enable all procsets
+ resources.basicFonts()
+ P.Resources = resources
+ pages.addPage(P)
+
+#### DUMMY OUTLINES IMPLEMENTATION FOR testing
+
+DUMMYOUTLINE = """
+<<
+ /Count
+ 0
+ /Type
+ /Outlines
+>>"""
+
+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 level<currentlevel:
+ # pop off levels to match
+ current = stack[-1]
+ del stack[-1]
+ previous = stack[-1]
+ lastinprevious = previous[-1]
+ if type(lastinprevious) is TupleType:
+ (name, sectionlist) = lastinprevious
+ raise ValueError, "cannot reset existing sections: " + repr(lastinprevious)
+ else:
+ name = lastinprevious
+ sectionlist = current
+ previous[-1] = (name, sectionlist)
+ #sectionlist.append(current)
+ currentlevel = currentlevel-1
+ if destinationname is None: return
+ stack[-1].append(destinationname)
+ self.destinationnamestotitles[destinationname] = title
+ if closed: self.closedict[destinationname] = 1
+ self.currentlevel = level
+
+ def setDestinations(self, destinationtree):
+ self.mydestinations = destinationtree
+
+ def format(self, document):
+ D = {}
+ D["Type"] = PDFName("Outlines")
+ c = self.count
+ D["Count"] = c
+ if c!=0:
+ D["First"] = self.first
+ D["Last"] = self.last
+ PD = PDFDictionary(D)
+ return PD.format(document)
+
+ def setNames(self, canvas, *nametree):
+ desttree = self.translateNames(canvas, nametree)
+ self.setDestinations(desttree)
+
+ def setNameList(self, canvas, nametree):
+ "Explicit list so I don't need to do apply(...) in the caller"
+ desttree = self.translateNames(canvas, nametree)
+ self.setDestinations(desttree)
+
+ def translateNames(self, canvas, object):
+ "recursively translate tree of names into tree of destinations"
+ from types import StringType, ListType, TupleType
+ Ot = type(object)
+ destinationnamestotitles = self.destinationnamestotitles
+ destinationstotitles = self.destinationstotitles
+ closedict = self.closedict
+ if Ot is StringType:
+ destination = canvas._bookmarkReference(object)
+ title = object
+ if destinationnamestotitles.has_key(object):
+ title = destinationnamestotitles[object]
+ else:
+ destinationnamestotitles[title] = title
+ destinationstotitles[destination] = title
+ if closedict.has_key(object):
+ closedict[destination] = 1 # mark destination closed
+ return {object: canvas._bookmarkReference(object)} # name-->ref
+ 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
+
+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."""
+ producer = "ReportLab http://www.reportlab.com"
+ title = "untitled"
+ author = "anonymous"
+ subject = "unspecified"
+ _dateFormatter = None
+
+ def __init__(self):
+ self.invariant = rl_config.invariant
+
+ 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,dateFormatter=self._dateFormatter)
+ D["Producer"] = PDFString(self.producer)
+ 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", "AP")
+ def cvtdict(self, d, escape=1):
+ """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"],escape)
+ return d
+ def AnnotationDict(self, **kw):
+ if 'escape' in kw:
+ escape = kw['escape']
+ del kw['escape']
+ else:
+ escape = 1
+ 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,escape=escape)
+ 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")
+ 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 self.AnnotationDict(**d)
+
+class FreeTextAnnotation(Annotation):
+ permitted = Annotation.permitted + ("DA",)
+ def __init__(self, Rect, Contents, DA, **kw):
+ self.Rect = Rect
+ self.Contents = Contents
+ self.DA = DA
+ self.otherkw = kw
+ def Dict(self):
+ d = {}
+ d.update(self.otherkw)
+ d["Rect"] = self.Rect
+ d["Contents"] = self.Contents
+ d["DA"] = self.DA
+ d["Subtype"] = "/FreeText"
+ return 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,dateFormatter=None):
+ 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.date=(yyyy,mm,dd,hh,m,s)
+ self.dateFormatter = dateFormatter
+
+ def format(self, doc):
+ dfmt = self.dateFormatter or (
+ lambda yyyy,mm,dd,hh,m,s: '%04d%02d%02d%02d%02d%02d' % (yyyy,mm,dd,hh,m,s))
+ return format(PDFString(dfmt(*self.date)), 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)
+
+_mode2CS={'RGB':'DeviceRGB', 'L':'DeviceGray', 'CMYK':'DeviceCMYK'}
+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])
+ src = open_for_read(source)
+ if not(ext in ('.jpg', '.jpeg') and self.loadImageFromJPEG(src)):
+ self.loadImageFromA85(src)
+ else: # it is already a PIL Image
+ self.loadImageFromSRC(source)
+
+ def loadImageFromA85(self,source):
+ IMG=[]
+ imagedata = map(string.strip,pdfutils.makeA85Image(source,IMG=IMG))
+ words = string.split(imagedata[1])
+ self.width, self.height = map(string.atoi,(words[1],words[3]))
+ self.colorSpace = {'/RGB':'DeviceRGB', '/G':'DeviceGray', '/CMYK':'DeviceCMYK'}[words[7]]
+ 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):
+ try:
+ try:
+ info = pdfutils.readJPEGInfo(imageFile)
+ finally:
+ imageFile.seek(0) #reset file pointer
+ except:
+ return False
+ 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'
+ self._dotrans = 1
+ self.streamContent = pdfutils._AsciiBase85Encode(imageFile.read())
+ self._filters = 'ASCII85Decode','DCTDecode' #'A85','DCT'
+ self.mask = None
+ return True
+
+ 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"
+ fp = im.jpeg_fh()
+ if fp:
+ 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= _mode2CS[im.mode]
+ 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)
+ if self.colorSpace=='DeviceCMYK' and getattr(self,'_dotrans',0):
+ dict["Decode"] = PDFArray([1,0,1,0,1,0,1,0])
+ 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
new file mode 100644
index 00000000000..9fca917021e
--- /dev/null
+++ b/bin/reportlab/pdfbase/pdfform.py
@@ -0,0 +1,632 @@
+
+"""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
new file mode 100755
index 00000000000..58cf3f051b6
--- /dev/null
+++ b/bin/reportlab/pdfbase/pdfmetrics.py
@@ -0,0 +1,796 @@
+#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: pdfmetrics.py 2873 2006-05-17 10:59:59Z rgbecker $ '''
+__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, rl_glob, rl_isdir, open_and_read, open_and_readlines
+from reportlab.rl_config import defaultEncoding
+import rl_codecs
+
+rl_codecs.RL_Codecs.register()
+standardFonts = _fontdata.standardFonts
+standardEncodings = _fontdata.standardEncodings
+
+_typefaces = {}
+_encodings = {}
+_fonts = {}
+
+def _py_unicode2T1(utext,fonts):
+ '''return a list of (font,string) pairs representing the unicode text'''
+ #print 'unicode2t1(%s, %s): %s' % (utext, fonts, type(utext))
+ #if type(utext)
+ R = []
+ font, fonts = fonts[0], fonts[1:]
+ enc = font.encName
+ if 'UCS-2' in enc:
+ enc = 'UTF16'
+ while utext:
+ try:
+ R.append((font,utext.encode(enc)))
+ break
+ except UnicodeEncodeError, e:
+ i0, il = e.args[2:4]
+ if i0:
+ R.append((font,utext[:i0].encode(enc)))
+ if fonts:
+ R.extend(_py_unicode2T1(utext[i0:il],fonts))
+ else:
+ R.append((_notdefFont,_notdefChar*(il-i0)))
+ utext = utext[il:]
+ return R
+
+try:
+ from _rl_accel import unicode2T1
+except ImportError:
+ unicode2T1 = _py_unicode2T1
+
+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
+ if lines: lines = string.split(lines[0],'\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 bruteForceSearchForFile(fn,searchPath=None):
+ if searchPath is None: from reportlab.rl_config import T1SearchPath as searchPath
+ if rl_isfile(fn): return fn
+ bfn = os.path.basename(fn)
+ for dirname in searchPath:
+ if not rl_isdir(dirname): continue
+ tfn = os.path.join(dirname,bfn)
+ if rl_isfile(tfn): return tfn
+ return fn
+
+def bruteForceSearchForAFM(faceName):
+ """Looks in all AFM files on path for face with given name.
+
+ Returns AFM file name or None. Ouch!"""
+ from reportlab.rl_config import T1SearchPath
+
+ for dirname in T1SearchPath:
+ if not rl_isdir(dirname): continue
+ possibles = rl_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))
+
+standardT1SubstitutionFonts = []
+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
+ face = self.face = getTypeFace(faceName)
+ self.encoding= getEncoding(encName)
+ self.encName = encName
+ if face.builtIn and face.requiredEncoding is None:
+ _ = standardT1SubstitutionFonts
+ else:
+ _ = []
+ self.substitutionFonts = _
+ self._calcWidths()
+
+ # multi byte fonts do their own stringwidth calculations.
+ # signal this here.
+ self._multiByte = 0
+
+
+ def __repr__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.face.name)
+
+ 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
+
+ def _py_stringWidth(self, text, size, encoding='utf8'):
+ """This is the "purist" approach to width. The practical approach
+ is to use the stringWidth function, which may be swapped in for one
+ written in C."""
+ if not isinstance(text,unicode): text = text.decode(encoding)
+ return sum([sum(map(f.widths.__getitem__,map(ord,t))) for f, t in unicode2T1(text,[self]+self.substitutionFonts)])*0.001*size
+ stringWidth = _py_stringWidth
+
+ 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"""
+ pfbFileName = bruteForceSearchForFile(pfbFileName)
+ 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
+ afmFileName = bruteForceSearchForFile(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)
+
+
+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 findFontAndRegister(fontName):
+ '''search for and register a font given it's name'''
+ #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 _py_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:
+ return findFontAndRegister(fontName)
+
+try:
+ from _rl_accel import getFontU as getFont
+except ImportError:
+ getFont = _py_getFont
+
+_notdefFont,_notdefChar = getFont('ZapfDingbats'),chr(110)
+standardT1SubstitutionFonts.extend([getFont('Symbol'),getFont('ZapfDingbats')])
+
+def getAscentDescent(fontName):
+ font = getFont(fontName)
+ try:
+ return font.ascent,font.descent
+ except:
+ return font.face.ascent,font.face.descent
+
+def getAscent(fontName):
+ return getAscentDescent(fontName)[0]
+
+def getDescent(fontName):
+ return getAscentDescent(fontName)[1]
+
+def getRegisteredFontNames():
+ "Returns what's in there"
+ reg = _fonts.keys()
+ reg.sort()
+ return reg
+
+def _py_stringWidth(text, fontName, fontSize, encoding='utf8'):
+ """Define this anyway so it can be tested, but whether it is used or not depends on _rl_accel"""
+ return getFont(fontName).stringWidth(text, fontSize, encoding=encoding)
+
+try:
+ from _rl_accel import stringWidthU as stringWidth
+except ImportError:
+ stringWidth = _py_stringWidth
+
+try:
+ from _rl_accel import _instanceStringWidthU
+ import new
+ Font.stringWidth = new.instancemethod(_instanceStringWidthU,None,Font)
+except ImportError:
+ pass
+
+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
new file mode 100644
index 00000000000..0ba62c9bed6
--- /dev/null
+++ b/bin/reportlab/pdfbase/pdfpattern.py
@@ -0,0 +1,59 @@
+"""
+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
new file mode 100755
index 00000000000..8a13c894d76
--- /dev/null
+++ b/bin/reportlab/pdfbase/pdfutils.py
@@ -0,0 +1,460 @@
+#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: pdfutils.py 2765 2006-02-02 18:48:12Z rgbecker $ '''
+__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.
+#
+##########################################################
+
+_mode2cs = {'RGB':'RGB', 'CMYK': 'CMYK', 'L': 'G'}
+
+def makeA85Image(filename,IMG=None):
+ import zlib
+ img = ImageReader(filename)
+ if IMG is not None: IMG.append(img)
+
+ imgwidth, imgheight = img.getSize()
+ raw = img.getRGBData()
+
+ code = []
+ append = code.append
+ # this describes what is in the image itself
+ append('BI')
+ append('/W %s /H %s /BPC 8 /CS /%s /F [/A85 /Fl]' % (imgwidth, imgheight,_mode2cs[img.mode]))
+ 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)
+
+ append('EI')
+ return code
+
+def cacheImageFile(filename, returnInMemory=0, IMG=None):
+ "Processes image as if for encoding, saves to a file with .a85 extension."
+
+ cachedname = os.path.splitext(filename)[0] + '.a85'
+ if filename==cachedname:
+ if cachedImageExists(filename):
+ from reportlab.lib.utils import open_for_read
+ if returnInMemory: return split(open_for_read(cachedname).read(),LINEEND)[:-1]
+ else:
+ raise IOError, 'No such cached image %s' % filename
+ else:
+ code = makeA85Image(filename,IMG)
+ 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
+ from pdfdoc import PDFError
+
+ #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('JPEG 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/rl_codecs.py b/bin/reportlab/pdfbase/rl_codecs.py
new file mode 100644
index 00000000000..326ed09fbd2
--- /dev/null
+++ b/bin/reportlab/pdfbase/rl_codecs.py
@@ -0,0 +1,1027 @@
+#codecs support
+__all__=['RL_Codecs']
+class RL_Codecs:
+ __rl_codecs_data = {
+ 'winansi':({
+ 0x007f: 0x2022, # BULLET
+ 0x0080: 0x20ac, # EURO SIGN
+ 0x0081: 0x2022, # BULLET
+ 0x0082: 0x201a, # SINGLE LOW-9 QUOTATION MARK
+ 0x0083: 0x0192, # LATIN SMALL LETTER F WITH HOOK
+ 0x0084: 0x201e, # DOUBLE LOW-9 QUOTATION MARK
+ 0x0085: 0x2026, # HORIZONTAL ELLIPSIS
+ 0x0086: 0x2020, # DAGGER
+ 0x0087: 0x2021, # DOUBLE DAGGER
+ 0x0088: 0x02c6, # MODIFIER LETTER CIRCUMFLEX ACCENT
+ 0x0089: 0x2030, # PER MILLE SIGN
+ 0x008a: 0x0160, # LATIN CAPITAL LETTER S WITH CARON
+ 0x008b: 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 0x008c: 0x0152, # LATIN CAPITAL LIGATURE OE
+ 0x008d: 0x2022, # BULLET
+ 0x008e: 0x017d, # LATIN CAPITAL LETTER Z WITH CARON
+ 0x008f: 0x2022, # BULLET
+ 0x0090: 0x2022, # BULLET
+ 0x0091: 0x2018, # LEFT SINGLE QUOTATION MARK
+ 0x0092: 0x2019, # RIGHT SINGLE QUOTATION MARK
+ 0x0093: 0x201c, # LEFT DOUBLE QUOTATION MARK
+ 0x0094: 0x201d, # RIGHT DOUBLE QUOTATION MARK
+ 0x0095: 0x2022, # BULLET
+ 0x0096: 0x2013, # EN DASH
+ 0x0097: 0x2014, # EM DASH
+ 0x0098: 0x02dc, # SMALL TILDE
+ 0x0099: 0x2122, # TRADE MARK SIGN
+ 0x009a: 0x0161, # LATIN SMALL LETTER S WITH CARON
+ 0x009b: 0x203a, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 0x009c: 0x0153, # LATIN SMALL LIGATURE OE
+ 0x009d: 0x2022, # BULLET
+ 0x009e: 0x017e, # LATIN SMALL LETTER Z WITH CARON
+ 0x009f: 0x0178, # LATIN CAPITAL LETTER Y WITH DIAERESIS
+ 0x00a0: 0x0020, # SPACE
+ 0x00ad: 0x002d, # HYPHEN-MINUS
+ }, {0x2022:0x7f,0x20:0x20,0x2d:0x2d,0xa0:0x20}),
+ 'macroman':({
+ 0x007f: None, # UNDEFINED
+ 0x0080: 0x00c4, # LATIN CAPITAL LETTER A WITH DIAERESIS
+ 0x0081: 0x00c5, # LATIN CAPITAL LETTER A WITH RING ABOVE
+ 0x0082: 0x00c7, # LATIN CAPITAL LETTER C WITH CEDILLA
+ 0x0083: 0x00c9, # LATIN CAPITAL LETTER E WITH ACUTE
+ 0x0084: 0x00d1, # LATIN CAPITAL LETTER N WITH TILDE
+ 0x0085: 0x00d6, # LATIN CAPITAL LETTER O WITH DIAERESIS
+ 0x0086: 0x00dc, # LATIN CAPITAL LETTER U WITH DIAERESIS
+ 0x0087: 0x00e1, # LATIN SMALL LETTER A WITH ACUTE
+ 0x0088: 0x00e0, # LATIN SMALL LETTER A WITH GRAVE
+ 0x0089: 0x00e2, # LATIN SMALL LETTER A WITH CIRCUMFLEX
+ 0x008a: 0x00e4, # LATIN SMALL LETTER A WITH DIAERESIS
+ 0x008b: 0x00e3, # LATIN SMALL LETTER A WITH TILDE
+ 0x008c: 0x00e5, # LATIN SMALL LETTER A WITH RING ABOVE
+ 0x008d: 0x00e7, # LATIN SMALL LETTER C WITH CEDILLA
+ 0x008e: 0x00e9, # LATIN SMALL LETTER E WITH ACUTE
+ 0x008f: 0x00e8, # LATIN SMALL LETTER E WITH GRAVE
+ 0x0090: 0x00ea, # LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 0x0091: 0x00eb, # LATIN SMALL LETTER E WITH DIAERESIS
+ 0x0092: 0x00ed, # LATIN SMALL LETTER I WITH ACUTE
+ 0x0093: 0x00ec, # LATIN SMALL LETTER I WITH GRAVE
+ 0x0094: 0x00ee, # LATIN SMALL LETTER I WITH CIRCUMFLEX
+ 0x0095: 0x00ef, # LATIN SMALL LETTER I WITH DIAERESIS
+ 0x0096: 0x00f1, # LATIN SMALL LETTER N WITH TILDE
+ 0x0097: 0x00f3, # LATIN SMALL LETTER O WITH ACUTE
+ 0x0098: 0x00f2, # LATIN SMALL LETTER O WITH GRAVE
+ 0x0099: 0x00f4, # LATIN SMALL LETTER O WITH CIRCUMFLEX
+ 0x009a: 0x00f6, # LATIN SMALL LETTER O WITH DIAERESIS
+ 0x009b: 0x00f5, # LATIN SMALL LETTER O WITH TILDE
+ 0x009c: 0x00fa, # LATIN SMALL LETTER U WITH ACUTE
+ 0x009d: 0x00f9, # LATIN SMALL LETTER U WITH GRAVE
+ 0x009e: 0x00fb, # LATIN SMALL LETTER U WITH CIRCUMFLEX
+ 0x009f: 0x00fc, # LATIN SMALL LETTER U WITH DIAERESIS
+ 0x00a0: 0x2020, # DAGGER
+ 0x00a1: 0x00b0, # DEGREE SIGN
+ 0x00a4: 0x00a7, # SECTION SIGN
+ 0x00a5: 0x2022, # BULLET
+ 0x00a6: 0x00b6, # PILCROW SIGN
+ 0x00a7: 0x00df, # LATIN SMALL LETTER SHARP S
+ 0x00a8: 0x00ae, # REGISTERED SIGN
+ 0x00aa: 0x2122, # TRADE MARK SIGN
+ 0x00ab: 0x00b4, # ACUTE ACCENT
+ 0x00ac: 0x00a8, # DIAERESIS
+ 0x00ad: None, # UNDEFINED
+ 0x00ae: 0x00c6, # LATIN CAPITAL LETTER AE
+ 0x00af: 0x00d8, # LATIN CAPITAL LETTER O WITH STROKE
+ 0x00b0: None, # UNDEFINED
+ 0x00b2: None, # UNDEFINED
+ 0x00b3: None, # UNDEFINED
+ 0x00b4: 0x00a5, # YEN SIGN
+ 0x00b6: None, # UNDEFINED
+ 0x00b7: None, # UNDEFINED
+ 0x00b8: None, # UNDEFINED
+ 0x00b9: None, # UNDEFINED
+ 0x00ba: None, # UNDEFINED
+ 0x00bb: 0x00aa, # FEMININE ORDINAL INDICATOR
+ 0x00bc: 0x00ba, # MASCULINE ORDINAL INDICATOR
+ 0x00bd: None, # UNDEFINED
+ 0x00be: 0x00e6, # LATIN SMALL LETTER AE
+ 0x00bf: 0x00f8, # LATIN SMALL LETTER O WITH STROKE
+ 0x00c0: 0x00bf, # INVERTED QUESTION MARK
+ 0x00c1: 0x00a1, # INVERTED EXCLAMATION MARK
+ 0x00c2: 0x00ac, # NOT SIGN
+ 0x00c3: None, # UNDEFINED
+ 0x00c4: 0x0192, # LATIN SMALL LETTER F WITH HOOK
+ 0x00c5: None, # UNDEFINED
+ 0x00c6: None, # UNDEFINED
+ 0x00c7: 0x00ab, # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00c8: 0x00bb, # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00c9: 0x2026, # HORIZONTAL ELLIPSIS
+ 0x00ca: 0x0020, # SPACE
+ 0x00cb: 0x00c0, # LATIN CAPITAL LETTER A WITH GRAVE
+ 0x00cc: 0x00c3, # LATIN CAPITAL LETTER A WITH TILDE
+ 0x00cd: 0x00d5, # LATIN CAPITAL LETTER O WITH TILDE
+ 0x00ce: 0x0152, # LATIN CAPITAL LIGATURE OE
+ 0x00cf: 0x0153, # LATIN SMALL LIGATURE OE
+ 0x00d0: 0x2013, # EN DASH
+ 0x00d1: 0x2014, # EM DASH
+ 0x00d2: 0x201c, # LEFT DOUBLE QUOTATION MARK
+ 0x00d3: 0x201d, # RIGHT DOUBLE QUOTATION MARK
+ 0x00d4: 0x2018, # LEFT SINGLE QUOTATION MARK
+ 0x00d5: 0x2019, # RIGHT SINGLE QUOTATION MARK
+ 0x00d6: 0x00f7, # DIVISION SIGN
+ 0x00d7: None, # UNDEFINED
+ 0x00d8: 0x00ff, # LATIN SMALL LETTER Y WITH DIAERESIS
+ 0x00d9: 0x0178, # LATIN CAPITAL LETTER Y WITH DIAERESIS
+ 0x00da: 0x2044, # FRACTION SLASH
+ 0x00db: 0x00a4, # CURRENCY SIGN
+ 0x00dc: 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 0x00dd: 0x203a, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 0x00de: 0xfb01, # LATIN SMALL LIGATURE FI
+ 0x00df: 0xfb02, # LATIN SMALL LIGATURE FL
+ 0x00e0: 0x2021, # DOUBLE DAGGER
+ 0x00e1: 0x00b7, # MIDDLE DOT
+ 0x00e2: 0x201a, # SINGLE LOW-9 QUOTATION MARK
+ 0x00e3: 0x201e, # DOUBLE LOW-9 QUOTATION MARK
+ 0x00e4: 0x2030, # PER MILLE SIGN
+ 0x00e5: 0x00c2, # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ 0x00e6: 0x00ca, # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ 0x00e7: 0x00c1, # LATIN CAPITAL LETTER A WITH ACUTE
+ 0x00e8: 0x00cb, # LATIN CAPITAL LETTER E WITH DIAERESIS
+ 0x00e9: 0x00c8, # LATIN CAPITAL LETTER E WITH GRAVE
+ 0x00ea: 0x00cd, # LATIN CAPITAL LETTER I WITH ACUTE
+ 0x00eb: 0x00ce, # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ 0x00ec: 0x00cf, # LATIN CAPITAL LETTER I WITH DIAERESIS
+ 0x00ed: 0x00cc, # LATIN CAPITAL LETTER I WITH GRAVE
+ 0x00ee: 0x00d3, # LATIN CAPITAL LETTER O WITH ACUTE
+ 0x00ef: 0x00d4, # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ 0x00f0: None, # UNDEFINED
+ 0x00f1: 0x00d2, # LATIN CAPITAL LETTER O WITH GRAVE
+ 0x00f2: 0x00da, # LATIN CAPITAL LETTER U WITH ACUTE
+ 0x00f3: 0x00db, # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ 0x00f4: 0x00d9, # LATIN CAPITAL LETTER U WITH GRAVE
+ 0x00f5: 0x0131, # LATIN SMALL LETTER DOTLESS I
+ 0x00f6: 0x02c6, # MODIFIER LETTER CIRCUMFLEX ACCENT
+ 0x00f7: 0x02dc, # SMALL TILDE
+ 0x00f8: 0x00af, # MACRON
+ 0x00f9: 0x02d8, # BREVE
+ 0x00fa: 0x02d9, # DOT ABOVE
+ 0x00fb: 0x02da, # RING ABOVE
+ 0x00fc: 0x00b8, # CEDILLA
+ 0x00fd: 0x02dd, # DOUBLE ACUTE ACCENT
+ 0x00fe: 0x02db, # OGONEK
+ 0x00ff: 0x02c7, # CARON
+ },None),
+ 'standard':({
+ 0x0027: 0x2019, # RIGHT SINGLE QUOTATION MARK
+ 0x0060: 0x2018, # LEFT SINGLE QUOTATION MARK
+ 0x007f: None, # UNDEFINED
+ 0x0080: None, # UNDEFINED
+ 0x0081: None, # UNDEFINED
+ 0x0082: None, # UNDEFINED
+ 0x0083: None, # UNDEFINED
+ 0x0084: None, # UNDEFINED
+ 0x0085: None, # UNDEFINED
+ 0x0086: None, # UNDEFINED
+ 0x0087: None, # UNDEFINED
+ 0x0088: None, # UNDEFINED
+ 0x0089: None, # UNDEFINED
+ 0x008a: None, # UNDEFINED
+ 0x008b: None, # UNDEFINED
+ 0x008c: None, # UNDEFINED
+ 0x008d: None, # UNDEFINED
+ 0x008e: None, # UNDEFINED
+ 0x008f: None, # UNDEFINED
+ 0x0090: None, # UNDEFINED
+ 0x0091: None, # UNDEFINED
+ 0x0092: None, # UNDEFINED
+ 0x0093: None, # UNDEFINED
+ 0x0094: None, # UNDEFINED
+ 0x0095: None, # UNDEFINED
+ 0x0096: None, # UNDEFINED
+ 0x0097: None, # UNDEFINED
+ 0x0098: None, # UNDEFINED
+ 0x0099: None, # UNDEFINED
+ 0x009a: None, # UNDEFINED
+ 0x009b: None, # UNDEFINED
+ 0x009c: None, # UNDEFINED
+ 0x009d: None, # UNDEFINED
+ 0x009e: None, # UNDEFINED
+ 0x009f: None, # UNDEFINED
+ 0x00a0: None, # UNDEFINED
+ 0x00a4: 0x2044, # FRACTION SLASH
+ 0x00a6: 0x0192, # LATIN SMALL LETTER F WITH HOOK
+ 0x00a8: 0x00a4, # CURRENCY SIGN
+ 0x00a9: 0x0027, # APOSTROPHE
+ 0x00aa: 0x201c, # LEFT DOUBLE QUOTATION MARK
+ 0x00ac: 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 0x00ad: 0x203a, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 0x00ae: 0xfb01, # LATIN SMALL LIGATURE FI
+ 0x00af: 0xfb02, # LATIN SMALL LIGATURE FL
+ 0x00b0: None, # UNDEFINED
+ 0x00b1: 0x2013, # EN DASH
+ 0x00b2: 0x2020, # DAGGER
+ 0x00b3: 0x2021, # DOUBLE DAGGER
+ 0x00b4: 0x00b7, # MIDDLE DOT
+ 0x00b5: None, # UNDEFINED
+ 0x00b7: 0x2022, # BULLET
+ 0x00b8: 0x201a, # SINGLE LOW-9 QUOTATION MARK
+ 0x00b9: 0x201e, # DOUBLE LOW-9 QUOTATION MARK
+ 0x00ba: 0x201d, # RIGHT DOUBLE QUOTATION MARK
+ 0x00bc: 0x2026, # HORIZONTAL ELLIPSIS
+ 0x00bd: 0x2030, # PER MILLE SIGN
+ 0x00be: None, # UNDEFINED
+ 0x00c0: None, # UNDEFINED
+ 0x00c1: 0x0060, # GRAVE ACCENT
+ 0x00c2: 0x00b4, # ACUTE ACCENT
+ 0x00c3: 0x02c6, # MODIFIER LETTER CIRCUMFLEX ACCENT
+ 0x00c4: 0x02dc, # SMALL TILDE
+ 0x00c5: 0x00af, # MACRON
+ 0x00c6: 0x02d8, # BREVE
+ 0x00c7: 0x02d9, # DOT ABOVE
+ 0x00c8: 0x00a8, # DIAERESIS
+ 0x00c9: None, # UNDEFINED
+ 0x00ca: 0x02da, # RING ABOVE
+ 0x00cb: 0x00b8, # CEDILLA
+ 0x00cc: None, # UNDEFINED
+ 0x00cd: 0x02dd, # DOUBLE ACUTE ACCENT
+ 0x00ce: 0x02db, # OGONEK
+ 0x00cf: 0x02c7, # CARON
+ 0x00d0: 0x2014, # EM DASH
+ 0x00d1: None, # UNDEFINED
+ 0x00d2: None, # UNDEFINED
+ 0x00d3: None, # UNDEFINED
+ 0x00d4: None, # UNDEFINED
+ 0x00d5: None, # UNDEFINED
+ 0x00d6: None, # UNDEFINED
+ 0x00d7: None, # UNDEFINED
+ 0x00d8: None, # UNDEFINED
+ 0x00d9: None, # UNDEFINED
+ 0x00da: None, # UNDEFINED
+ 0x00db: None, # UNDEFINED
+ 0x00dc: None, # UNDEFINED
+ 0x00dd: None, # UNDEFINED
+ 0x00de: None, # UNDEFINED
+ 0x00df: None, # UNDEFINED
+ 0x00e0: None, # UNDEFINED
+ 0x00e1: 0x00c6, # LATIN CAPITAL LETTER AE
+ 0x00e2: None, # UNDEFINED
+ 0x00e3: 0x00aa, # FEMININE ORDINAL INDICATOR
+ 0x00e4: None, # UNDEFINED
+ 0x00e5: None, # UNDEFINED
+ 0x00e6: None, # UNDEFINED
+ 0x00e7: None, # UNDEFINED
+ 0x00e8: 0x0141, # LATIN CAPITAL LETTER L WITH STROKE
+ 0x00e9: 0x00d8, # LATIN CAPITAL LETTER O WITH STROKE
+ 0x00ea: 0x0152, # LATIN CAPITAL LIGATURE OE
+ 0x00eb: 0x00ba, # MASCULINE ORDINAL INDICATOR
+ 0x00ec: None, # UNDEFINED
+ 0x00ed: None, # UNDEFINED
+ 0x00ee: None, # UNDEFINED
+ 0x00ef: None, # UNDEFINED
+ 0x00f0: None, # UNDEFINED
+ 0x00f1: 0x00e6, # LATIN SMALL LETTER AE
+ 0x00f2: None, # UNDEFINED
+ 0x00f3: None, # UNDEFINED
+ 0x00f4: None, # UNDEFINED
+ 0x00f5: 0x0131, # LATIN SMALL LETTER DOTLESS I
+ 0x00f6: None, # UNDEFINED
+ 0x00f7: None, # UNDEFINED
+ 0x00f8: 0x0142, # LATIN SMALL LETTER L WITH STROKE
+ 0x00f9: 0x00f8, # LATIN SMALL LETTER O WITH STROKE
+ 0x00fa: 0x0153, # LATIN SMALL LIGATURE OE
+ 0x00fb: 0x00df, # LATIN SMALL LETTER SHARP S
+ 0x00fc: None, # UNDEFINED
+ 0x00fd: None, # UNDEFINED
+ 0x00fe: None, # UNDEFINED
+ 0x00ff: None, # UNDEFINED
+ },None),
+ 'symbol':({
+ 0x0022: 0x2200, # FOR ALL
+ 0x0024: 0x2203, # THERE EXISTS
+ 0x0027: 0x220b, # CONTAINS AS MEMBER
+ 0x002a: 0x2217, # ASTERISK OPERATOR
+ 0x002d: 0x2212, # MINUS SIGN
+ 0x0040: 0x2245, # APPROXIMATELY EQUAL TO
+ 0x0041: 0x0391, # GREEK CAPITAL LETTER ALPHA
+ 0x0042: 0x0392, # GREEK CAPITAL LETTER BETA
+ 0x0043: 0x03a7, # GREEK CAPITAL LETTER CHI
+ 0x0044: 0x2206, # INCREMENT
+ 0x0045: 0x0395, # GREEK CAPITAL LETTER EPSILON
+ 0x0046: 0x03a6, # GREEK CAPITAL LETTER PHI
+ 0x0047: 0x0393, # GREEK CAPITAL LETTER GAMMA
+ 0x0048: 0x0397, # GREEK CAPITAL LETTER ETA
+ 0x0049: 0x0399, # GREEK CAPITAL LETTER IOTA
+ 0x004a: 0x03d1, # GREEK THETA SYMBOL
+ 0x004b: 0x039a, # GREEK CAPITAL LETTER KAPPA
+ 0x004c: 0x039b, # GREEK CAPITAL LETTER LAMDA
+ 0x004d: 0x039c, # GREEK CAPITAL LETTER MU
+ 0x004e: 0x039d, # GREEK CAPITAL LETTER NU
+ 0x004f: 0x039f, # GREEK CAPITAL LETTER OMICRON
+ 0x0050: 0x03a0, # GREEK CAPITAL LETTER PI
+ 0x0051: 0x0398, # GREEK CAPITAL LETTER THETA
+ 0x0052: 0x03a1, # GREEK CAPITAL LETTER RHO
+ 0x0053: 0x03a3, # GREEK CAPITAL LETTER SIGMA
+ 0x0054: 0x03a4, # GREEK CAPITAL LETTER TAU
+ 0x0055: 0x03a5, # GREEK CAPITAL LETTER UPSILON
+ 0x0056: 0x03c2, # GREEK SMALL LETTER FINAL SIGMA
+ 0x0057: 0x2126, # OHM SIGN
+ 0x0058: 0x039e, # GREEK CAPITAL LETTER XI
+ 0x0059: 0x03a8, # GREEK CAPITAL LETTER PSI
+ 0x005a: 0x0396, # GREEK CAPITAL LETTER ZETA
+ 0x005c: 0x2234, # THEREFORE
+ 0x005e: 0x22a5, # UP TACK
+ 0x0060: 0xf8e5, # [unknown unicode name for radicalex]
+ 0x0061: 0x03b1, # GREEK SMALL LETTER ALPHA
+ 0x0062: 0x03b2, # GREEK SMALL LETTER BETA
+ 0x0063: 0x03c7, # GREEK SMALL LETTER CHI
+ 0x0064: 0x03b4, # GREEK SMALL LETTER DELTA
+ 0x0065: 0x03b5, # GREEK SMALL LETTER EPSILON
+ 0x0066: 0x03c6, # GREEK SMALL LETTER PHI
+ 0x0067: 0x03b3, # GREEK SMALL LETTER GAMMA
+ 0x0068: 0x03b7, # GREEK SMALL LETTER ETA
+ 0x0069: 0x03b9, # GREEK SMALL LETTER IOTA
+ 0x006a: 0x03d5, # GREEK PHI SYMBOL
+ 0x006b: 0x03ba, # GREEK SMALL LETTER KAPPA
+ 0x006c: 0x03bb, # GREEK SMALL LETTER LAMDA
+ 0x006d: 0x00b5, # MICRO SIGN
+ 0x006e: 0x03bd, # GREEK SMALL LETTER NU
+ 0x006f: 0x03bf, # GREEK SMALL LETTER OMICRON
+ 0x0070: 0x03c0, # GREEK SMALL LETTER PI
+ 0x0071: 0x03b8, # GREEK SMALL LETTER THETA
+ 0x0072: 0x03c1, # GREEK SMALL LETTER RHO
+ 0x0073: 0x03c3, # GREEK SMALL LETTER SIGMA
+ 0x0074: 0x03c4, # GREEK SMALL LETTER TAU
+ 0x0075: 0x03c5, # GREEK SMALL LETTER UPSILON
+ 0x0076: 0x03d6, # GREEK PI SYMBOL
+ 0x0077: 0x03c9, # GREEK SMALL LETTER OMEGA
+ 0x0078: 0x03be, # GREEK SMALL LETTER XI
+ 0x0079: 0x03c8, # GREEK SMALL LETTER PSI
+ 0x007a: 0x03b6, # GREEK SMALL LETTER ZETA
+ 0x007e: 0x223c, # TILDE OPERATOR
+ 0x007f: None, # UNDEFINED
+ 0x0080: None, # UNDEFINED
+ 0x0081: None, # UNDEFINED
+ 0x0082: None, # UNDEFINED
+ 0x0083: None, # UNDEFINED
+ 0x0084: None, # UNDEFINED
+ 0x0085: None, # UNDEFINED
+ 0x0086: None, # UNDEFINED
+ 0x0087: None, # UNDEFINED
+ 0x0088: None, # UNDEFINED
+ 0x0089: None, # UNDEFINED
+ 0x008a: None, # UNDEFINED
+ 0x008b: None, # UNDEFINED
+ 0x008c: None, # UNDEFINED
+ 0x008d: None, # UNDEFINED
+ 0x008e: None, # UNDEFINED
+ 0x008f: None, # UNDEFINED
+ 0x0090: None, # UNDEFINED
+ 0x0091: None, # UNDEFINED
+ 0x0092: None, # UNDEFINED
+ 0x0093: None, # UNDEFINED
+ 0x0094: None, # UNDEFINED
+ 0x0095: None, # UNDEFINED
+ 0x0096: None, # UNDEFINED
+ 0x0097: None, # UNDEFINED
+ 0x0098: None, # UNDEFINED
+ 0x0099: None, # UNDEFINED
+ 0x009a: None, # UNDEFINED
+ 0x009b: None, # UNDEFINED
+ 0x009c: None, # UNDEFINED
+ 0x009d: None, # UNDEFINED
+ 0x009e: None, # UNDEFINED
+ 0x009f: None, # UNDEFINED
+ 0x00a0: 0x20ac, # EURO SIGN
+ 0x00a1: 0x03d2, # GREEK UPSILON WITH HOOK SYMBOL
+ 0x00a2: 0x2032, # PRIME
+ 0x00a3: 0x2264, # LESS-THAN OR EQUAL TO
+ 0x00a4: 0x2044, # FRACTION SLASH
+ 0x00a5: 0x221e, # INFINITY
+ 0x00a6: 0x0192, # LATIN SMALL LETTER F WITH HOOK
+ 0x00a7: 0x2663, # BLACK CLUB SUIT
+ 0x00a8: 0x2666, # BLACK DIAMOND SUIT
+ 0x00a9: 0x2665, # BLACK HEART SUIT
+ 0x00aa: 0x2660, # BLACK SPADE SUIT
+ 0x00ab: 0x2194, # LEFT RIGHT ARROW
+ 0x00ac: 0x2190, # LEFTWARDS ARROW
+ 0x00ad: 0x2191, # UPWARDS ARROW
+ 0x00ae: 0x2192, # RIGHTWARDS ARROW
+ 0x00af: 0x2193, # DOWNWARDS ARROW
+ 0x00b2: 0x2033, # DOUBLE PRIME
+ 0x00b3: 0x2265, # GREATER-THAN OR EQUAL TO
+ 0x00b4: 0x00d7, # MULTIPLICATION SIGN
+ 0x00b5: 0x221d, # PROPORTIONAL TO
+ 0x00b6: 0x2202, # PARTIAL DIFFERENTIAL
+ 0x00b7: 0x2022, # BULLET
+ 0x00b8: 0x00f7, # DIVISION SIGN
+ 0x00b9: 0x2260, # NOT EQUAL TO
+ 0x00ba: 0x2261, # IDENTICAL TO
+ 0x00bb: 0x2248, # ALMOST EQUAL TO
+ 0x00bc: 0x2026, # HORIZONTAL ELLIPSIS
+ 0x00bd: 0xf8e6, # [unknown unicode name for arrowvertex]
+ 0x00be: 0xf8e7, # [unknown unicode name for arrowhorizex]
+ 0x00bf: 0x21b5, # DOWNWARDS ARROW WITH CORNER LEFTWARDS
+ 0x00c0: 0x2135, # ALEF SYMBOL
+ 0x00c1: 0x2111, # BLACK-LETTER CAPITAL I
+ 0x00c2: 0x211c, # BLACK-LETTER CAPITAL R
+ 0x00c3: 0x2118, # SCRIPT CAPITAL P
+ 0x00c4: 0x2297, # CIRCLED TIMES
+ 0x00c5: 0x2295, # CIRCLED PLUS
+ 0x00c6: 0x2205, # EMPTY SET
+ 0x00c7: 0x2229, # INTERSECTION
+ 0x00c8: 0x222a, # UNION
+ 0x00c9: 0x2283, # SUPERSET OF
+ 0x00ca: 0x2287, # SUPERSET OF OR EQUAL TO
+ 0x00cb: 0x2284, # NOT A SUBSET OF
+ 0x00cc: 0x2282, # SUBSET OF
+ 0x00cd: 0x2286, # SUBSET OF OR EQUAL TO
+ 0x00ce: 0x2208, # ELEMENT OF
+ 0x00cf: 0x2209, # NOT AN ELEMENT OF
+ 0x00d0: 0x2220, # ANGLE
+ 0x00d1: 0x2207, # NABLA
+ 0x00d2: 0xf6da, # [unknown unicode name for registerserif]
+ 0x00d3: 0xf6d9, # [unknown unicode name for copyrightserif]
+ 0x00d4: 0xf6db, # [unknown unicode name for trademarkserif]
+ 0x00d5: 0x220f, # N-ARY PRODUCT
+ 0x00d6: 0x221a, # SQUARE ROOT
+ 0x00d7: 0x22c5, # DOT OPERATOR
+ 0x00d8: 0x00ac, # NOT SIGN
+ 0x00d9: 0x2227, # LOGICAL AND
+ 0x00da: 0x2228, # LOGICAL OR
+ 0x00db: 0x21d4, # LEFT RIGHT DOUBLE ARROW
+ 0x00dc: 0x21d0, # LEFTWARDS DOUBLE ARROW
+ 0x00dd: 0x21d1, # UPWARDS DOUBLE ARROW
+ 0x00de: 0x21d2, # RIGHTWARDS DOUBLE ARROW
+ 0x00df: 0x21d3, # DOWNWARDS DOUBLE ARROW
+ 0x00e0: 0x25ca, # LOZENGE
+ 0x00e1: 0x2329, # LEFT-POINTING ANGLE BRACKET
+ 0x00e2: 0xf8e8, # [unknown unicode name for registersans]
+ 0x00e3: 0xf8e9, # [unknown unicode name for copyrightsans]
+ 0x00e4: 0xf8ea, # [unknown unicode name for trademarksans]
+ 0x00e5: 0x2211, # N-ARY SUMMATION
+ 0x00e6: 0xf8eb, # [unknown unicode name for parenlefttp]
+ 0x00e7: 0xf8ec, # [unknown unicode name for parenleftex]
+ 0x00e8: 0xf8ed, # [unknown unicode name for parenleftbt]
+ 0x00e9: 0xf8ee, # [unknown unicode name for bracketlefttp]
+ 0x00ea: 0xf8ef, # [unknown unicode name for bracketleftex]
+ 0x00eb: 0xf8f0, # [unknown unicode name for bracketleftbt]
+ 0x00ec: 0xf8f1, # [unknown unicode name for bracelefttp]
+ 0x00ed: 0xf8f2, # [unknown unicode name for braceleftmid]
+ 0x00ee: 0xf8f3, # [unknown unicode name for braceleftbt]
+ 0x00ef: 0xf8f4, # [unknown unicode name for braceex]
+ 0x00f0: None, # UNDEFINED
+ 0x00f1: 0x232a, # RIGHT-POINTING ANGLE BRACKET
+ 0x00f2: 0x222b, # INTEGRAL
+ 0x00f3: 0x2320, # TOP HALF INTEGRAL
+ 0x00f4: 0xf8f5, # [unknown unicode name for integralex]
+ 0x00f5: 0x2321, # BOTTOM HALF INTEGRAL
+ 0x00f6: 0xf8f6, # [unknown unicode name for parenrighttp]
+ 0x00f7: 0xf8f7, # [unknown unicode name for parenrightex]
+ 0x00f8: 0xf8f8, # [unknown unicode name for parenrightbt]
+ 0x00f9: 0xf8f9, # [unknown unicode name for bracketrighttp]
+ 0x00fa: 0xf8fa, # [unknown unicode name for bracketrightex]
+ 0x00fb: 0xf8fb, # [unknown unicode name for bracketrightbt]
+ 0x00fc: 0xf8fc, # [unknown unicode name for bracerighttp]
+ 0x00fd: 0xf8fd, # [unknown unicode name for bracerightmid]
+ 0x00fe: 0xf8fe, # [unknown unicode name for bracerightbt]
+ 0x00ff: None, # UNDEFINED
+ },None),
+ 'zapfdingbats':({
+ 0x0021: 0x2701, # UPPER BLADE SCISSORS
+ 0x0022: 0x2702, # BLACK SCISSORS
+ 0x0023: 0x2703, # LOWER BLADE SCISSORS
+ 0x0024: 0x2704, # WHITE SCISSORS
+ 0x0025: 0x260e, # BLACK TELEPHONE
+ 0x0026: 0x2706, # TELEPHONE LOCATION SIGN
+ 0x0027: 0x2707, # TAPE DRIVE
+ 0x0028: 0x2708, # AIRPLANE
+ 0x0029: 0x2709, # ENVELOPE
+ 0x002a: 0x261b, # BLACK RIGHT POINTING INDEX
+ 0x002b: 0x261e, # WHITE RIGHT POINTING INDEX
+ 0x002c: 0x270c, # VICTORY HAND
+ 0x002d: 0x270d, # WRITING HAND
+ 0x002e: 0x270e, # LOWER RIGHT PENCIL
+ 0x002f: 0x270f, # PENCIL
+ 0x0030: 0x2710, # UPPER RIGHT PENCIL
+ 0x0031: 0x2711, # WHITE NIB
+ 0x0032: 0x2712, # BLACK NIB
+ 0x0033: 0x2713, # CHECK MARK
+ 0x0034: 0x2714, # HEAVY CHECK MARK
+ 0x0035: 0x2715, # MULTIPLICATION X
+ 0x0036: 0x2716, # HEAVY MULTIPLICATION X
+ 0x0037: 0x2717, # BALLOT X
+ 0x0038: 0x2718, # HEAVY BALLOT X
+ 0x0039: 0x2719, # OUTLINED GREEK CROSS
+ 0x003a: 0x271a, # HEAVY GREEK CROSS
+ 0x003b: 0x271b, # OPEN CENTRE CROSS
+ 0x003c: 0x271c, # HEAVY OPEN CENTRE CROSS
+ 0x003d: 0x271d, # LATIN CROSS
+ 0x003e: 0x271e, # SHADOWED WHITE LATIN CROSS
+ 0x003f: 0x271f, # OUTLINED LATIN CROSS
+ 0x0040: 0x2720, # MALTESE CROSS
+ 0x0041: 0x2721, # STAR OF DAVID
+ 0x0042: 0x2722, # FOUR TEARDROP-SPOKED ASTERISK
+ 0x0043: 0x2723, # FOUR BALLOON-SPOKED ASTERISK
+ 0x0044: 0x2724, # HEAVY FOUR BALLOON-SPOKED ASTERISK
+ 0x0045: 0x2725, # FOUR CLUB-SPOKED ASTERISK
+ 0x0046: 0x2726, # BLACK FOUR POINTED STAR
+ 0x0047: 0x2727, # WHITE FOUR POINTED STAR
+ 0x0048: 0x2605, # BLACK STAR
+ 0x0049: 0x2729, # STRESS OUTLINED WHITE STAR
+ 0x004a: 0x272a, # CIRCLED WHITE STAR
+ 0x004b: 0x272b, # OPEN CENTRE BLACK STAR
+ 0x004c: 0x272c, # BLACK CENTRE WHITE STAR
+ 0x004d: 0x272d, # OUTLINED BLACK STAR
+ 0x004e: 0x272e, # HEAVY OUTLINED BLACK STAR
+ 0x004f: 0x272f, # PINWHEEL STAR
+ 0x0050: 0x2730, # SHADOWED WHITE STAR
+ 0x0051: 0x2731, # HEAVY ASTERISK
+ 0x0052: 0x2732, # OPEN CENTRE ASTERISK
+ 0x0053: 0x2733, # EIGHT SPOKED ASTERISK
+ 0x0054: 0x2734, # EIGHT POINTED BLACK STAR
+ 0x0055: 0x2735, # EIGHT POINTED PINWHEEL STAR
+ 0x0056: 0x2736, # SIX POINTED BLACK STAR
+ 0x0057: 0x2737, # EIGHT POINTED RECTILINEAR BLACK STAR
+ 0x0058: 0x2738, # HEAVY EIGHT POINTED RECTILINEAR BLACK STAR
+ 0x0059: 0x2739, # TWELVE POINTED BLACK STAR
+ 0x005a: 0x273a, # SIXTEEN POINTED ASTERISK
+ 0x005b: 0x273b, # TEARDROP-SPOKED ASTERISK
+ 0x005c: 0x273c, # OPEN CENTRE TEARDROP-SPOKED ASTERISK
+ 0x005d: 0x273d, # HEAVY TEARDROP-SPOKED ASTERISK
+ 0x005e: 0x273e, # SIX PETALLED BLACK AND WHITE FLORETTE
+ 0x005f: 0x273f, # BLACK FLORETTE
+ 0x0060: 0x2740, # WHITE FLORETTE
+ 0x0061: 0x2741, # EIGHT PETALLED OUTLINED BLACK FLORETTE
+ 0x0062: 0x2742, # CIRCLED OPEN CENTRE EIGHT POINTED STAR
+ 0x0063: 0x2743, # HEAVY TEARDROP-SPOKED PINWHEEL ASTERISK
+ 0x0064: 0x2744, # SNOWFLAKE
+ 0x0065: 0x2745, # TIGHT TRIFOLIATE SNOWFLAKE
+ 0x0066: 0x2746, # HEAVY CHEVRON SNOWFLAKE
+ 0x0067: 0x2747, # SPARKLE
+ 0x0068: 0x2748, # HEAVY SPARKLE
+ 0x0069: 0x2749, # BALLOON-SPOKED ASTERISK
+ 0x006a: 0x274a, # EIGHT TEARDROP-SPOKED PROPELLER ASTERISK
+ 0x006b: 0x274b, # HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK
+ 0x006c: 0x25cf, # BLACK CIRCLE
+ 0x006d: 0x274d, # SHADOWED WHITE CIRCLE
+ 0x006e: 0x25a0, # BLACK SQUARE
+ 0x006f: 0x274f, # LOWER RIGHT DROP-SHADOWED WHITE SQUARE
+ 0x0070: 0x2750, # UPPER RIGHT DROP-SHADOWED WHITE SQUARE
+ 0x0071: 0x2751, # LOWER RIGHT SHADOWED WHITE SQUARE
+ 0x0072: 0x2752, # UPPER RIGHT SHADOWED WHITE SQUARE
+ 0x0073: 0x25b2, # BLACK UP-POINTING TRIANGLE
+ 0x0074: 0x25bc, # BLACK DOWN-POINTING TRIANGLE
+ 0x0075: 0x25c6, # BLACK DIAMOND
+ 0x0076: 0x2756, # BLACK DIAMOND MINUS WHITE X
+ 0x0077: 0x25d7, # RIGHT HALF BLACK CIRCLE
+ 0x0078: 0x2758, # LIGHT VERTICAL BAR
+ 0x0079: 0x2759, # MEDIUM VERTICAL BAR
+ 0x007a: 0x275a, # HEAVY VERTICAL BAR
+ 0x007b: 0x275b, # HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT
+ 0x007c: 0x275c, # HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT
+ 0x007d: 0x275d, # HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT
+ 0x007e: 0x275e, # HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT
+ 0x007f: None, # UNDEFINED
+ 0x0080: 0x2768, # MEDIUM LEFT PARENTHESIS ORNAMENT
+ 0x0081: 0x2769, # MEDIUM RIGHT PARENTHESIS ORNAMENT
+ 0x0082: 0x276a, # MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT
+ 0x0083: 0x276b, # MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT
+ 0x0084: 0x276c, # MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT
+ 0x0085: 0x276d, # MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT
+ 0x0086: 0x276e, # HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT
+ 0x0087: 0x276f, # HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT
+ 0x0088: 0x2770, # HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT
+ 0x0089: 0x2771, # HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT
+ 0x008a: 0x2772, # LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT
+ 0x008b: 0x2773, # LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT
+ 0x008c: 0x2774, # MEDIUM LEFT CURLY BRACKET ORNAMENT
+ 0x008d: 0x2775, # MEDIUM RIGHT CURLY BRACKET ORNAMENT
+ 0x008e: None, # UNDEFINED
+ 0x008f: None, # UNDEFINED
+ 0x0090: None, # UNDEFINED
+ 0x0091: None, # UNDEFINED
+ 0x0092: None, # UNDEFINED
+ 0x0093: None, # UNDEFINED
+ 0x0094: None, # UNDEFINED
+ 0x0095: None, # UNDEFINED
+ 0x0096: None, # UNDEFINED
+ 0x0097: None, # UNDEFINED
+ 0x0098: None, # UNDEFINED
+ 0x0099: None, # UNDEFINED
+ 0x009a: None, # UNDEFINED
+ 0x009b: None, # UNDEFINED
+ 0x009c: None, # UNDEFINED
+ 0x009d: None, # UNDEFINED
+ 0x009e: None, # UNDEFINED
+ 0x009f: None, # UNDEFINED
+ 0x00a0: None, # UNDEFINED
+ 0x00a1: 0x2761, # CURVED STEM PARAGRAPH SIGN ORNAMENT
+ 0x00a2: 0x2762, # HEAVY EXCLAMATION MARK ORNAMENT
+ 0x00a3: 0x2763, # HEAVY HEART EXCLAMATION MARK ORNAMENT
+ 0x00a4: 0x2764, # HEAVY BLACK HEART
+ 0x00a5: 0x2765, # ROTATED HEAVY BLACK HEART BULLET
+ 0x00a6: 0x2766, # FLORAL HEART
+ 0x00a7: 0x2767, # ROTATED FLORAL HEART BULLET
+ 0x00a8: 0x2663, # BLACK CLUB SUIT
+ 0x00a9: 0x2666, # BLACK DIAMOND SUIT
+ 0x00aa: 0x2665, # BLACK HEART SUIT
+ 0x00ab: 0x2660, # BLACK SPADE SUIT
+ 0x00ac: 0x2460, # CIRCLED DIGIT ONE
+ 0x00ad: 0x2461, # CIRCLED DIGIT TWO
+ 0x00ae: 0x2462, # CIRCLED DIGIT THREE
+ 0x00af: 0x2463, # CIRCLED DIGIT FOUR
+ 0x00b0: 0x2464, # CIRCLED DIGIT FIVE
+ 0x00b1: 0x2465, # CIRCLED DIGIT SIX
+ 0x00b2: 0x2466, # CIRCLED DIGIT SEVEN
+ 0x00b3: 0x2467, # CIRCLED DIGIT EIGHT
+ 0x00b4: 0x2468, # CIRCLED DIGIT NINE
+ 0x00b5: 0x2469, # CIRCLED NUMBER TEN
+ 0x00b6: 0x2776, # DINGBAT NEGATIVE CIRCLED DIGIT ONE
+ 0x00b7: 0x2777, # DINGBAT NEGATIVE CIRCLED DIGIT TWO
+ 0x00b8: 0x2778, # DINGBAT NEGATIVE CIRCLED DIGIT THREE
+ 0x00b9: 0x2779, # DINGBAT NEGATIVE CIRCLED DIGIT FOUR
+ 0x00ba: 0x277a, # DINGBAT NEGATIVE CIRCLED DIGIT FIVE
+ 0x00bb: 0x277b, # DINGBAT NEGATIVE CIRCLED DIGIT SIX
+ 0x00bc: 0x277c, # DINGBAT NEGATIVE CIRCLED DIGIT SEVEN
+ 0x00bd: 0x277d, # DINGBAT NEGATIVE CIRCLED DIGIT EIGHT
+ 0x00be: 0x277e, # DINGBAT NEGATIVE CIRCLED DIGIT NINE
+ 0x00bf: 0x277f, # DINGBAT NEGATIVE CIRCLED NUMBER TEN
+ 0x00c0: 0x2780, # DINGBAT CIRCLED SANS-SERIF DIGIT ONE
+ 0x00c1: 0x2781, # DINGBAT CIRCLED SANS-SERIF DIGIT TWO
+ 0x00c2: 0x2782, # DINGBAT CIRCLED SANS-SERIF DIGIT THREE
+ 0x00c3: 0x2783, # DINGBAT CIRCLED SANS-SERIF DIGIT FOUR
+ 0x00c4: 0x2784, # DINGBAT CIRCLED SANS-SERIF DIGIT FIVE
+ 0x00c5: 0x2785, # DINGBAT CIRCLED SANS-SERIF DIGIT SIX
+ 0x00c6: 0x2786, # DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN
+ 0x00c7: 0x2787, # DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT
+ 0x00c8: 0x2788, # DINGBAT CIRCLED SANS-SERIF DIGIT NINE
+ 0x00c9: 0x2789, # DINGBAT CIRCLED SANS-SERIF NUMBER TEN
+ 0x00ca: 0x278a, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE
+ 0x00cb: 0x278b, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO
+ 0x00cc: 0x278c, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE
+ 0x00cd: 0x278d, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR
+ 0x00ce: 0x278e, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE
+ 0x00cf: 0x278f, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX
+ 0x00d0: 0x2790, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN
+ 0x00d1: 0x2791, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT
+ 0x00d2: 0x2792, # DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE
+ 0x00d3: 0x2793, # DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN
+ 0x00d4: 0x2794, # HEAVY WIDE-HEADED RIGHTWARDS ARROW
+ 0x00d5: 0x2192, # RIGHTWARDS ARROW
+ 0x00d6: 0x2194, # LEFT RIGHT ARROW
+ 0x00d7: 0x2195, # UP DOWN ARROW
+ 0x00d8: 0x2798, # HEAVY SOUTH EAST ARROW
+ 0x00d9: 0x2799, # HEAVY RIGHTWARDS ARROW
+ 0x00da: 0x279a, # HEAVY NORTH EAST ARROW
+ 0x00db: 0x279b, # DRAFTING POINT RIGHTWARDS ARROW
+ 0x00dc: 0x279c, # HEAVY ROUND-TIPPED RIGHTWARDS ARROW
+ 0x00dd: 0x279d, # TRIANGLE-HEADED RIGHTWARDS ARROW
+ 0x00de: 0x279e, # HEAVY TRIANGLE-HEADED RIGHTWARDS ARROW
+ 0x00df: 0x279f, # DASHED TRIANGLE-HEADED RIGHTWARDS ARROW
+ 0x00e0: 0x27a0, # HEAVY DASHED TRIANGLE-HEADED RIGHTWARDS ARROW
+ 0x00e1: 0x27a1, # BLACK RIGHTWARDS ARROW
+ 0x00e2: 0x27a2, # THREE-D TOP-LIGHTED RIGHTWARDS ARROWHEAD
+ 0x00e3: 0x27a3, # THREE-D BOTTOM-LIGHTED RIGHTWARDS ARROWHEAD
+ 0x00e4: 0x27a4, # BLACK RIGHTWARDS ARROWHEAD
+ 0x00e5: 0x27a5, # HEAVY BLACK CURVED DOWNWARDS AND RIGHTWARDS ARROW
+ 0x00e6: 0x27a6, # HEAVY BLACK CURVED UPWARDS AND RIGHTWARDS ARROW
+ 0x00e7: 0x27a7, # SQUAT BLACK RIGHTWARDS ARROW
+ 0x00e8: 0x27a8, # HEAVY CONCAVE-POINTED BLACK RIGHTWARDS ARROW
+ 0x00e9: 0x27a9, # RIGHT-SHADED WHITE RIGHTWARDS ARROW
+ 0x00ea: 0x27aa, # LEFT-SHADED WHITE RIGHTWARDS ARROW
+ 0x00eb: 0x27ab, # BACK-TILTED SHADOWED WHITE RIGHTWARDS ARROW
+ 0x00ec: 0x27ac, # FRONT-TILTED SHADOWED WHITE RIGHTWARDS ARROW
+ 0x00ed: 0x27ad, # HEAVY LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW
+ 0x00ee: 0x27ae, # HEAVY UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW
+ 0x00ef: 0x27af, # NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW
+ 0x00f0: None, # UNDEFINED
+ 0x00f1: 0x27b1, # NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW
+ 0x00f2: 0x27b2, # CIRCLED HEAVY WHITE RIGHTWARDS ARROW
+ 0x00f3: 0x27b3, # WHITE-FEATHERED RIGHTWARDS ARROW
+ 0x00f4: 0x27b4, # BLACK-FEATHERED SOUTH EAST ARROW
+ 0x00f5: 0x27b5, # BLACK-FEATHERED RIGHTWARDS ARROW
+ 0x00f6: 0x27b6, # BLACK-FEATHERED NORTH EAST ARROW
+ 0x00f7: 0x27b7, # HEAVY BLACK-FEATHERED SOUTH EAST ARROW
+ 0x00f8: 0x27b8, # HEAVY BLACK-FEATHERED RIGHTWARDS ARROW
+ 0x00f9: 0x27b9, # HEAVY BLACK-FEATHERED NORTH EAST ARROW
+ 0x00fa: 0x27ba, # TEARDROP-BARBED RIGHTWARDS ARROW
+ 0x00fb: 0x27bb, # HEAVY TEARDROP-SHANKED RIGHTWARDS ARROW
+ 0x00fc: 0x27bc, # WEDGE-TAILED RIGHTWARDS ARROW
+ 0x00fd: 0x27bd, # HEAVY WEDGE-TAILED RIGHTWARDS ARROW
+ 0x00fe: 0x27be, # OPEN-OUTLINED RIGHTWARDS ARROW
+ 0x00ff: None, # UNDEFINED
+ },None),
+ 'pdfdoc':({
+ 0x007f: None, # UNDEFINED
+ 0x0080: 0x2022, # BULLET
+ 0x0081: 0x2020, # DAGGER
+ 0x0082: 0x2021, # DOUBLE DAGGER
+ 0x0083: 0x2026, # HORIZONTAL ELLIPSIS
+ 0x0084: 0x2014, # EM DASH
+ 0x0085: 0x2013, # EN DASH
+ 0x0086: 0x0192, # LATIN SMALL LETTER F WITH HOOK
+ 0x0087: 0x2044, # FRACTION SLASH
+ 0x0088: 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 0x0089: 0x203a, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 0x008a: 0x2212, # MINUS SIGN
+ 0x008b: 0x2030, # PER MILLE SIGN
+ 0x008c: 0x201e, # DOUBLE LOW-9 QUOTATION MARK
+ 0x008d: 0x201c, # LEFT DOUBLE QUOTATION MARK
+ 0x008e: 0x201d, # RIGHT DOUBLE QUOTATION MARK
+ 0x008f: 0x2018, # LEFT SINGLE QUOTATION MARK
+ 0x0090: 0x2019, # RIGHT SINGLE QUOTATION MARK
+ 0x0091: 0x201a, # SINGLE LOW-9 QUOTATION MARK
+ 0x0092: 0x2122, # TRADE MARK SIGN
+ 0x0093: 0xfb01, # LATIN SMALL LIGATURE FI
+ 0x0094: 0xfb02, # LATIN SMALL LIGATURE FL
+ 0x0095: 0x0141, # LATIN CAPITAL LETTER L WITH STROKE
+ 0x0096: 0x0152, # LATIN CAPITAL LIGATURE OE
+ 0x0097: 0x0160, # LATIN CAPITAL LETTER S WITH CARON
+ 0x0098: 0x0178, # LATIN CAPITAL LETTER Y WITH DIAERESIS
+ 0x0099: 0x017d, # LATIN CAPITAL LETTER Z WITH CARON
+ 0x009a: 0x0131, # LATIN SMALL LETTER DOTLESS I
+ 0x009b: 0x0142, # LATIN SMALL LETTER L WITH STROKE
+ 0x009c: 0x0153, # LATIN SMALL LIGATURE OE
+ 0x009d: 0x0161, # LATIN SMALL LETTER S WITH CARON
+ 0x009e: 0x017e, # LATIN SMALL LETTER Z WITH CARON
+ 0x009f: None, # UNDEFINED
+ 0x00a0: 0x20ac, # EURO SIGN
+ 0x00ad: None, # UNDEFINED
+ 24: 0x02d8, #breve
+ 25: 0x02c7, #caron
+ 26: 0x02c6, #circumflex
+ 27: 0x02d9, #dotaccent
+ 28: 0x02dd, #hungarumlaut
+ 29: 0x02db, #ogonek
+ 30: 0x02da, #ring
+ 31: 0x02dc, #tilde
+ },None),
+ 'macexpert':({
+ 0x0021: 0xf721, # [unknown unicode name for exclamsmall]
+ 0x0022: 0xf6f8, # [unknown unicode name for Hungarumlautsmall]
+ 0x0023: 0xf7a2, # [unknown unicode name for centoldstyle]
+ 0x0024: 0xf724, # [unknown unicode name for dollaroldstyle]
+ 0x0025: 0xf6e4, # [unknown unicode name for dollarsuperior]
+ 0x0026: 0xf726, # [unknown unicode name for ampersandsmall]
+ 0x0027: 0xf7b4, # [unknown unicode name for Acutesmall]
+ 0x0028: 0x207d, # SUPERSCRIPT LEFT PARENTHESIS
+ 0x0029: 0x207e, # SUPERSCRIPT RIGHT PARENTHESIS
+ 0x002a: 0x2025, # TWO DOT LEADER
+ 0x002b: 0x2024, # ONE DOT LEADER
+ 0x002f: 0x2044, # FRACTION SLASH
+ 0x0030: 0xf730, # [unknown unicode name for zerooldstyle]
+ 0x0031: 0xf731, # [unknown unicode name for oneoldstyle]
+ 0x0032: 0xf732, # [unknown unicode name for twooldstyle]
+ 0x0033: 0xf733, # [unknown unicode name for threeoldstyle]
+ 0x0034: 0xf734, # [unknown unicode name for fouroldstyle]
+ 0x0035: 0xf735, # [unknown unicode name for fiveoldstyle]
+ 0x0036: 0xf736, # [unknown unicode name for sixoldstyle]
+ 0x0037: 0xf737, # [unknown unicode name for sevenoldstyle]
+ 0x0038: 0xf738, # [unknown unicode name for eightoldstyle]
+ 0x0039: 0xf739, # [unknown unicode name for nineoldstyle]
+ 0x003c: None, # UNDEFINED
+ 0x003d: 0xf6de, # [unknown unicode name for threequartersemdash]
+ 0x003e: None, # UNDEFINED
+ 0x003f: 0xf73f, # [unknown unicode name for questionsmall]
+ 0x0040: None, # UNDEFINED
+ 0x0041: None, # UNDEFINED
+ 0x0042: None, # UNDEFINED
+ 0x0043: None, # UNDEFINED
+ 0x0044: 0xf7f0, # [unknown unicode name for Ethsmall]
+ 0x0045: None, # UNDEFINED
+ 0x0046: None, # UNDEFINED
+ 0x0047: 0x00bc, # VULGAR FRACTION ONE QUARTER
+ 0x0048: 0x00bd, # VULGAR FRACTION ONE HALF
+ 0x0049: 0x00be, # VULGAR FRACTION THREE QUARTERS
+ 0x004a: 0x215b, # VULGAR FRACTION ONE EIGHTH
+ 0x004b: 0x215c, # VULGAR FRACTION THREE EIGHTHS
+ 0x004c: 0x215d, # VULGAR FRACTION FIVE EIGHTHS
+ 0x004d: 0x215e, # VULGAR FRACTION SEVEN EIGHTHS
+ 0x004e: 0x2153, # VULGAR FRACTION ONE THIRD
+ 0x004f: 0x2154, # VULGAR FRACTION TWO THIRDS
+ 0x0050: None, # UNDEFINED
+ 0x0051: None, # UNDEFINED
+ 0x0052: None, # UNDEFINED
+ 0x0053: None, # UNDEFINED
+ 0x0054: None, # UNDEFINED
+ 0x0055: None, # UNDEFINED
+ 0x0056: 0xfb00, # LATIN SMALL LIGATURE FF
+ 0x0057: 0xfb01, # LATIN SMALL LIGATURE FI
+ 0x0058: 0xfb02, # LATIN SMALL LIGATURE FL
+ 0x0059: 0xfb03, # LATIN SMALL LIGATURE FFI
+ 0x005a: 0xfb04, # LATIN SMALL LIGATURE FFL
+ 0x005b: 0x208d, # SUBSCRIPT LEFT PARENTHESIS
+ 0x005c: None, # UNDEFINED
+ 0x005d: 0x208e, # SUBSCRIPT RIGHT PARENTHESIS
+ 0x005e: 0xf6f6, # [unknown unicode name for Circumflexsmall]
+ 0x005f: 0xf6e5, # [unknown unicode name for hypheninferior]
+ 0x0060: 0xf760, # [unknown unicode name for Gravesmall]
+ 0x0061: 0xf761, # [unknown unicode name for Asmall]
+ 0x0062: 0xf762, # [unknown unicode name for Bsmall]
+ 0x0063: 0xf763, # [unknown unicode name for Csmall]
+ 0x0064: 0xf764, # [unknown unicode name for Dsmall]
+ 0x0065: 0xf765, # [unknown unicode name for Esmall]
+ 0x0066: 0xf766, # [unknown unicode name for Fsmall]
+ 0x0067: 0xf767, # [unknown unicode name for Gsmall]
+ 0x0068: 0xf768, # [unknown unicode name for Hsmall]
+ 0x0069: 0xf769, # [unknown unicode name for Ismall]
+ 0x006a: 0xf76a, # [unknown unicode name for Jsmall]
+ 0x006b: 0xf76b, # [unknown unicode name for Ksmall]
+ 0x006c: 0xf76c, # [unknown unicode name for Lsmall]
+ 0x006d: 0xf76d, # [unknown unicode name for Msmall]
+ 0x006e: 0xf76e, # [unknown unicode name for Nsmall]
+ 0x006f: 0xf76f, # [unknown unicode name for Osmall]
+ 0x0070: 0xf770, # [unknown unicode name for Psmall]
+ 0x0071: 0xf771, # [unknown unicode name for Qsmall]
+ 0x0072: 0xf772, # [unknown unicode name for Rsmall]
+ 0x0073: 0xf773, # [unknown unicode name for Ssmall]
+ 0x0074: 0xf774, # [unknown unicode name for Tsmall]
+ 0x0075: 0xf775, # [unknown unicode name for Usmall]
+ 0x0076: 0xf776, # [unknown unicode name for Vsmall]
+ 0x0077: 0xf777, # [unknown unicode name for Wsmall]
+ 0x0078: 0xf778, # [unknown unicode name for Xsmall]
+ 0x0079: 0xf779, # [unknown unicode name for Ysmall]
+ 0x007a: 0xf77a, # [unknown unicode name for Zsmall]
+ 0x007b: 0x20a1, # COLON SIGN
+ 0x007c: 0xf6dc, # [unknown unicode name for onefitted]
+ 0x007d: 0xf6dd, # [unknown unicode name for rupiah]
+ 0x007e: 0xf6fe, # [unknown unicode name for Tildesmall]
+ 0x007f: None, # UNDEFINED
+ 0x0080: None, # UNDEFINED
+ 0x0081: 0xf6e9, # [unknown unicode name for asuperior]
+ 0x0082: 0xf6e0, # [unknown unicode name for centsuperior]
+ 0x0083: None, # UNDEFINED
+ 0x0084: None, # UNDEFINED
+ 0x0085: None, # UNDEFINED
+ 0x0086: None, # UNDEFINED
+ 0x0087: 0xf7e1, # [unknown unicode name for Aacutesmall]
+ 0x0088: 0xf7e0, # [unknown unicode name for Agravesmall]
+ 0x0089: 0xf7e2, # [unknown unicode name for Acircumflexsmall]
+ 0x008a: 0xf7e4, # [unknown unicode name for Adieresissmall]
+ 0x008b: 0xf7e3, # [unknown unicode name for Atildesmall]
+ 0x008c: 0xf7e5, # [unknown unicode name for Aringsmall]
+ 0x008d: 0xf7e7, # [unknown unicode name for Ccedillasmall]
+ 0x008e: 0xf7e9, # [unknown unicode name for Eacutesmall]
+ 0x008f: 0xf7e8, # [unknown unicode name for Egravesmall]
+ 0x0090: 0xf7ea, # [unknown unicode name for Ecircumflexsmall]
+ 0x0091: 0xf7eb, # [unknown unicode name for Edieresissmall]
+ 0x0092: 0xf7ed, # [unknown unicode name for Iacutesmall]
+ 0x0093: 0xf7ec, # [unknown unicode name for Igravesmall]
+ 0x0094: 0xf7ee, # [unknown unicode name for Icircumflexsmall]
+ 0x0095: 0xf7ef, # [unknown unicode name for Idieresissmall]
+ 0x0096: 0xf7f1, # [unknown unicode name for Ntildesmall]
+ 0x0097: 0xf7f3, # [unknown unicode name for Oacutesmall]
+ 0x0098: 0xf7f2, # [unknown unicode name for Ogravesmall]
+ 0x0099: 0xf7f4, # [unknown unicode name for Ocircumflexsmall]
+ 0x009a: 0xf7f6, # [unknown unicode name for Odieresissmall]
+ 0x009b: 0xf7f5, # [unknown unicode name for Otildesmall]
+ 0x009c: 0xf7fa, # [unknown unicode name for Uacutesmall]
+ 0x009d: 0xf7f9, # [unknown unicode name for Ugravesmall]
+ 0x009e: 0xf7fb, # [unknown unicode name for Ucircumflexsmall]
+ 0x009f: 0xf7fc, # [unknown unicode name for Udieresissmall]
+ 0x00a0: None, # UNDEFINED
+ 0x00a1: 0x2078, # SUPERSCRIPT EIGHT
+ 0x00a2: 0x2084, # SUBSCRIPT FOUR
+ 0x00a3: 0x2083, # SUBSCRIPT THREE
+ 0x00a4: 0x2086, # SUBSCRIPT SIX
+ 0x00a5: 0x2088, # SUBSCRIPT EIGHT
+ 0x00a6: 0x2087, # SUBSCRIPT SEVEN
+ 0x00a7: 0xf6fd, # [unknown unicode name for Scaronsmall]
+ 0x00a8: None, # UNDEFINED
+ 0x00a9: 0xf6df, # [unknown unicode name for centinferior]
+ 0x00aa: 0x2082, # SUBSCRIPT TWO
+ 0x00ab: None, # UNDEFINED
+ 0x00ac: 0xf7a8, # [unknown unicode name for Dieresissmall]
+ 0x00ad: None, # UNDEFINED
+ 0x00ae: 0xf6f5, # [unknown unicode name for Caronsmall]
+ 0x00af: 0xf6f0, # [unknown unicode name for osuperior]
+ 0x00b0: 0x2085, # SUBSCRIPT FIVE
+ 0x00b1: None, # UNDEFINED
+ 0x00b2: 0xf6e1, # [unknown unicode name for commainferior]
+ 0x00b3: 0xf6e7, # [unknown unicode name for periodinferior]
+ 0x00b4: 0xf7fd, # [unknown unicode name for Yacutesmall]
+ 0x00b5: None, # UNDEFINED
+ 0x00b6: 0xf6e3, # [unknown unicode name for dollarinferior]
+ 0x00b7: None, # UNDEFINED
+ 0x00b8: None, # UNDEFINED
+ 0x00b9: 0xf7fe, # [unknown unicode name for Thornsmall]
+ 0x00ba: None, # UNDEFINED
+ 0x00bb: 0x2089, # SUBSCRIPT NINE
+ 0x00bc: 0x2080, # SUBSCRIPT ZERO
+ 0x00bd: 0xf6ff, # [unknown unicode name for Zcaronsmall]
+ 0x00be: 0xf7e6, # [unknown unicode name for AEsmall]
+ 0x00bf: 0xf7f8, # [unknown unicode name for Oslashsmall]
+ 0x00c0: 0xf7bf, # [unknown unicode name for questiondownsmall]
+ 0x00c1: 0x2081, # SUBSCRIPT ONE
+ 0x00c2: 0xf6f9, # [unknown unicode name for Lslashsmall]
+ 0x00c3: None, # UNDEFINED
+ 0x00c4: None, # UNDEFINED
+ 0x00c5: None, # UNDEFINED
+ 0x00c6: None, # UNDEFINED
+ 0x00c7: None, # UNDEFINED
+ 0x00c8: None, # UNDEFINED
+ 0x00c9: 0xf7b8, # [unknown unicode name for Cedillasmall]
+ 0x00ca: None, # UNDEFINED
+ 0x00cb: None, # UNDEFINED
+ 0x00cc: None, # UNDEFINED
+ 0x00cd: None, # UNDEFINED
+ 0x00ce: None, # UNDEFINED
+ 0x00cf: 0xf6fa, # [unknown unicode name for OEsmall]
+ 0x00d0: 0x2012, # FIGURE DASH
+ 0x00d1: 0xf6e6, # [unknown unicode name for hyphensuperior]
+ 0x00d2: None, # UNDEFINED
+ 0x00d3: None, # UNDEFINED
+ 0x00d4: None, # UNDEFINED
+ 0x00d5: None, # UNDEFINED
+ 0x00d6: 0xf7a1, # [unknown unicode name for exclamdownsmall]
+ 0x00d7: None, # UNDEFINED
+ 0x00d8: 0xf7ff, # [unknown unicode name for Ydieresissmall]
+ 0x00d9: None, # UNDEFINED
+ 0x00da: 0x00b9, # SUPERSCRIPT ONE
+ 0x00db: 0x00b2, # SUPERSCRIPT TWO
+ 0x00dc: 0x00b3, # SUPERSCRIPT THREE
+ 0x00dd: 0x2074, # SUPERSCRIPT FOUR
+ 0x00de: 0x2075, # SUPERSCRIPT FIVE
+ 0x00df: 0x2076, # SUPERSCRIPT SIX
+ 0x00e0: 0x2077, # SUPERSCRIPT SEVEN
+ 0x00e1: 0x2079, # SUPERSCRIPT NINE
+ 0x00e2: 0x2070, # SUPERSCRIPT ZERO
+ 0x00e3: None, # UNDEFINED
+ 0x00e4: 0xf6ec, # [unknown unicode name for esuperior]
+ 0x00e5: 0xf6f1, # [unknown unicode name for rsuperior]
+ 0x00e6: 0xf6f3, # [unknown unicode name for tsuperior]
+ 0x00e7: None, # UNDEFINED
+ 0x00e8: None, # UNDEFINED
+ 0x00e9: 0xf6ed, # [unknown unicode name for isuperior]
+ 0x00ea: 0xf6f2, # [unknown unicode name for ssuperior]
+ 0x00eb: 0xf6eb, # [unknown unicode name for dsuperior]
+ 0x00ec: None, # UNDEFINED
+ 0x00ed: None, # UNDEFINED
+ 0x00ee: None, # UNDEFINED
+ 0x00ef: None, # UNDEFINED
+ 0x00f0: None, # UNDEFINED
+ 0x00f1: 0xf6ee, # [unknown unicode name for lsuperior]
+ 0x00f2: 0xf6fb, # [unknown unicode name for Ogoneksmall]
+ 0x00f3: 0xf6f4, # [unknown unicode name for Brevesmall]
+ 0x00f4: 0xf7af, # [unknown unicode name for Macronsmall]
+ 0x00f5: 0xf6ea, # [unknown unicode name for bsuperior]
+ 0x00f6: 0x207f, # SUPERSCRIPT LATIN SMALL LETTER N
+ 0x00f7: 0xf6ef, # [unknown unicode name for msuperior]
+ 0x00f8: 0xf6e2, # [unknown unicode name for commasuperior]
+ 0x00f9: 0xf6e8, # [unknown unicode name for periodsuperior]
+ 0x00fa: 0xf6f7, # [unknown unicode name for Dotaccentsmall]
+ 0x00fb: 0xf6fc, # [unknown unicode name for Ringsmall]
+ 0x00fc: None, # UNDEFINED
+ 0x00fd: None, # UNDEFINED
+ 0x00fe: None, # UNDEFINED
+ 0x00ff: None, # UNDEFINED
+ },None),
+ }
+ #for k,v in __rl_codecs_data.items():
+ # __rl_codecs_data[k+'enc'] = __rl_codecs_data[k+'encoding'] = v
+ #del k,v
+
+ def __init__(self):
+ raise NotImplementedError
+
+ def _256_exception_codec((exceptions,rexceptions)):
+ import codecs
+ decoding_map = codecs.make_identity_dict(xrange(32,256))
+ decoding_map.update(exceptions)
+ encoding_map = codecs.make_encoding_map(decoding_map)
+ if rexceptions: encoding_map.update(rexceptions)
+ ### Codec APIs
+ class Codec(codecs.Codec):
+ def encode(self,input,errors='strict',charmap_encode=codecs.charmap_encode,encoding_map=encoding_map):
+ return charmap_encode(input,errors,encoding_map)
+
+ def decode(self,input,errors='strict',charmap_decode=codecs.charmap_decode,decoding_map=decoding_map):
+ return charmap_decode(input,errors,decoding_map)
+
+ class StreamWriter(Codec,codecs.StreamWriter):
+ pass
+
+ class StreamReader(Codec,codecs.StreamReader):
+ pass
+ C = Codec()
+ return (C.encode,C.decode,StreamReader,StreamWriter)
+ _256_exception_codec=staticmethod(_256_exception_codec)
+
+ __rl_codecs_cache = {}
+
+ def __rl_codecs(name,cache=__rl_codecs_cache,data=__rl_codecs_data):
+ try:
+ return cache[name]
+ except KeyError:
+ cache[name] = c = RL_Codecs._256_exception_codec(
+ data[name])
+ return c
+ __rl_codecs=staticmethod(__rl_codecs)
+
+ def _rl_codecs(name):
+ name = name.lower()
+ from pdfmetrics import standardEncodings
+ for e in standardEncodings:
+ e = e[:-8].lower()
+ if name.startswith(e): return RL_Codecs.__rl_codecs(e)
+ return None
+ _rl_codecs=staticmethod(_rl_codecs)
+
+ def register():
+ import codecs
+ codecs.register(RL_Codecs._rl_codecs)
+ register=staticmethod(register)
diff --git a/bin/reportlab/pdfbase/ttfonts.py b/bin/reportlab/pdfbase/ttfonts.py
new file mode 100644
index 00000000000..c6f9c922dd4
--- /dev/null
+++ b/bin/reportlab/pdfbase/ttfonts.py
@@ -0,0 +1,1113 @@
+#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: ttfonts.py 2865 2006-05-15 16:37:44Z rgbecker $'
+
+import string
+from types import StringType, UnicodeType
+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
+
+
+def SUBSETN(n,table=string.maketrans('0123456789','ABCDEFGHIJ')):
+ return ('%6.6d'%n).translate(table)
+#
+# 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]),
+ xrange(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"
+ ttfVersions = (0x00010000,0x74727565,0x74746366)
+ ttcVersions = (0x00010000,0x00020000)
+ fileKind='TTF'
+
+ def __init__(self, file, validate=0,subfontIndex=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.
+ """
+ self.validate = validate
+ self.readFile(file)
+ isCollection = self.readHeader()
+ if isCollection:
+ self.readTTCHeader()
+ self.getSubfont(subfontIndex)
+ else:
+ if self.validate: self.checksumFile()
+ self.readTableDirectory()
+ self.subfontNameX = ''
+
+ def readTTCHeader(self):
+ self.ttcVersion = self.read_ulong()
+ self.fileKind = 'TTC'
+ self.ttfVersions = self.ttfVersions[:-1]
+ if self.ttcVersion not in self.ttcVersions:
+ raise TTFError('"%s" is not a %s file: can\'t read version 0x%8.8x' %(self.filename,self.fileKind,self.ttcVersion))
+ self.numSubfonts = self.read_ulong()
+ self.subfontOffsets = []
+ a = self.subfontOffsets.append
+ for i in xrange(self.numSubfonts):
+ a(self.read_ulong())
+
+ def getSubfont(self,subfontIndex):
+ if self.fileKind!='TTC':
+ raise TTFError('"%s" is not a TTC file: use this method' % (self.filename,self.fileKind))
+ try:
+ pos = self.subfontOffsets[subfontIndex]
+ except IndexError:
+ raise TTFError('TTC file "%s": bad subfontIndex %s not in [0,%d]' % (self.filename,subfontIndex,self.numSubfonts-1))
+ self.seek(pos)
+ self.readHeader()
+ self.readTableDirectory()
+ self.subfontNameX = '-'+str(subfontIndex)
+
+ def readTableDirectory(self):
+ 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 xrange(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 %s file "%s" cannot read Table Directory' % (self.fileKind, self.filename))
+ if self.validate: self.checksumTables()
+
+ def readHeader(self):
+ '''read the sfnt header at the current position'''
+ try:
+ self.version = version = self.read_ulong()
+ except:
+ raise TTFError('"%s" is not a %s file: can\'t read version' %(self.filename,self.fileKind))
+
+ if version==0x4F54544F:
+ raise TTFError('%s file "%s": postscript outlines are not supported'%(self.fileKind,self.filename))
+
+ if version not in self.ttfVersions:
+ raise TTFError('Not a TrueType font: version=0x%8.8X' % version)
+ return version==self.ttfVersions[-1]
+
+ def readFile(self,file):
+ if type(file) is StringType:
+ self.filename, file = TTFOpenFile(file)
+ else:
+ self.filename = '(ttf)'
+
+ self._ttf_data = file.read()
+ self._pos = 0
+
+ def checksumTables(self):
+ # 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('TTF file "%s": invalid checksum %s table: %s' % (self.filename,hex32(checkSum),t['tag']))
+
+ def checksumFile(self):
+ # Check the checksums for the whole file
+ checkSum = calcChecksum(self._ttf_data)
+ if add32(_L2U32(0xB1B0AFBAL), -checkSum) != 0:
+ raise TTFError('TTF file "%s": invalid checksum %s len: %d &3: %d' % (self.filename,hex32(checkSum),len(self._ttf_data),(len(self._ttf_data)&3)))
+
+ 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 += 4
+ return self._ttf_data[self._pos - 4:self._pos]
+
+ def read_ushort(self):
+ "Reads an unsigned short"
+ self._pos += 2
+ return unpack('>H',self._ttf_data[self._pos-2:self._pos])[0]
+
+ def read_ulong(self):
+ "Reads an unsigned long"
+ self._pos += 4
+ return unpack('>l',self._ttf_data[self._pos - 4:self._pos])[0]
+
+ def read_short(self):
+ "Reads a signed short"
+ self._pos += 2
+ return unpack('>h',self._ttf_data[self._pos-2:self._pos])[0]
+
+ def get_ushort(self, pos):
+ "Return an unsigned short at given position"
+ return unpack('>H',self._ttf_data[pos:pos+2])[0]
+
+ 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,subfontIndex=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,subfontIndex=subfontIndex)
+ 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 xrange(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
+ opos = self._pos
+ try:
+ 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)
+ finally:
+ self._pos = opos
+ 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(58) #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(16) #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(4) #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 xrange(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(), xrange(segCount))
+ self.skip(2)
+ startCount = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
+ idDelta = map(lambda x, self=self: self.read_short(), xrange(segCount))
+ idRangeOffset_start = self._pos
+ idRangeOffset = map(lambda x, self=self: self.read_ushort(), xrange(segCount))
+
+ # Now it gets tricky.
+ glyphToChar = {}
+ charToGlyph = {}
+ for n in xrange(segCount):
+ for unichar in xrange(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 xrange(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 xrange(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 xrange(numGlyphs + 1):
+ self.glyphPos.append(self.read_ushort() << 1)
+ elif indexToLocFormat == 1:
+ for n in xrange(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 xrange(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 xrange(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, subfontIndex=0):
+ "Loads a TrueType font from filename."
+ pdfmetrics.TypeFace.__init__(self, None)
+ TTFontFile.__init__(self, filename, validate=validate, subfontIndex=subfontIndex)
+
+ def getCharWidth(self, code):
+ "Returns the width of character U+<code>"
+ 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.internalName = None
+ self.frozen = 0
+
+ # Let's add the first 128 unicodes to the 0th subset, so ' '
+ # always has code 32 (for word spacing to work) and the ASCII
+ # output is readable
+ subset0 = range(128)
+ self.subsets = [subset0]
+ for n in subset0:
+ self.assignments[n] = n
+ self.nextCode = 128
+
+ def __init__(self, name, filename, validate=0, subfontIndex=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, subfontIndex=subfontIndex)
+ self.encoding = TTEncoding()
+ self._multiByte = 1 # We want our own stringwidth
+ self._dynamicFont = 1 # We want dynamic subsetting
+ self.state = {}
+
+ def _py_stringWidth(self, text, size, encoding='utf-8'):
+ "Calculate text width"
+ if type(text) is not UnicodeType:
+ text = unicode(text, encoding or 'utf-8') # encoding defaults to utf-8
+ g = self.face.charWidths.get
+ dw = self.face.defaultWidth
+ return 0.001*size*sum([g(ord(u),dw) for u in text])
+ stringWidth = _py_stringWidth
+
+ def splitString(self, text, doc, encoding='utf-8'):
+ """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 = []
+ if type(text) is not UnicodeType:
+ text = unicode(text, encoding or 'utf-8') # encoding defaults to utf-8
+ for code in map(ord,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
+ if n & 0xFF == 32:
+ # make code 32 always be a space character
+ state.subsets[n >> 8].append(32)
+ state.nextCode += 1
+ n = 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 xrange(len(state.subsets)):
+ subset = state.subsets[n]
+ internalName = self.getSubsetInternalName(n, doc)[1:]
+ baseFontName = "%s+%s%s" % (SUBSETN(n),self.face.name,self.face.subfontNameX)
+
+ 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]
+try:
+ from _rl_accel import _instanceStringWidthTTF
+ import new
+ TTFont.stringWidth = new.instancemethod(_instanceStringWidthTTF,None,TTFont)
+except ImportError:
+ pass
diff --git a/bin/reportlab/pdfgen/__init__.py b/bin/reportlab/pdfgen/__init__.py
new file mode 100755
index 00000000000..9dc4437c817
--- /dev/null
+++ b/bin/reportlab/pdfgen/__init__.py
@@ -0,0 +1,5 @@
+#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: __init__.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
+__doc__='' \ No newline at end of file
diff --git a/bin/reportlab/pdfgen/canvas.py b/bin/reportlab/pdfgen/canvas.py
new file mode 100755
index 00000000000..d06e9670997
--- /dev/null
+++ b/bin/reportlab/pdfgen/canvas.py
@@ -0,0 +1,1462 @@
+#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: canvas.py 2854 2006-05-10 12:57:21Z rgbecker $ '''
+__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
+import re
+from string import join, split, strip, atoi, replace, upper, digits
+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.utils import import_zlib
+from reportlab.lib.utils import fp_str
+
+
+digitPat = re.compile('\d') #used in decimal alignment
+
+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()), '')
+
+def _annFormat(D,color,thickness,dashArray):
+ from reportlab.pdfbase.pdfdoc import PDFArray
+ if color:
+ D["C"] = PDFArray([color.red, color.green, color.blue])
+ border = [0,0,0]
+ if thickness:
+ border[2] = thickness
+ if dashArray:
+ border.append(PDFArray(dashArray))
+ D["Border"] = PDFArray(border)
+
+class Canvas(textobject._PDFColorSetter):
+ """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,
+ invariant = None,
+ verbosity=0):
+ """Create a canvas of a given size. etc.
+
+ You may pass a file-like object to filename as an alternative to
+ a string.
+
+ 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 invariant is None: invariant = rl_config.invariant
+ self._filename = filename
+
+ self._doc = pdfdoc.PDFDocument(compression=pageCompression,
+ invariant=invariant, filename=filename)
+
+
+ #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, but is visible
+ in document properties etc etc."""
+ self._doc.setAuthor(author)
+
+ def setDateFormatter(self, dateFormatter):
+ """accepts a func(yyyy,mm,dd,hh,m,s) used to create embedded formatted date"""
+ self._doc.setDateFormatter(dateFormatter)
+
+ 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.
+ """
+ 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,
+ fit="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 fit == "XYZ":
+ dest.xyz(left,top,zoom)
+ elif fit == "Fit":
+ dest.fit()
+ elif fit == "FitH":
+ dest.fith(top)
+ elif fit == "FitV":
+ dest.fitv(left)
+ elif fit == "FitR":
+ dest.fitr(left,bottom,right,top)
+ #Do we need these (version 1.1 / Acrobat 3 versions)?
+ elif fit == "FitB":
+ dest.fitb()
+ elif fit == "FitBH":
+ dest.fitbh(top)
+ elif fit == "FitBV":
+ dest.fitbv(left)
+ else:
+ raise "Unknown Fit type %s" % (fit,)
+
+ dest.setPage(pageref)
+ return dest
+
+ def bookmarkHorizontalAbsolute(self, key, top, left=0, fit='XYZ', **kw):
+ """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, fit=fit, top=top, left=left, zoom=0)
+
+ def bookmarkHorizontal(self, key, relativeX, relativeY, **kw):
+ """w.r.t. the current transformation, bookmark this horizontal."""
+ (left, top) = self.absolutePosition(relativeX,relativeY)
+ self.bookmarkHorizontalAbsolute(key, top, left=left, **kw)
+
+ #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 _absRect(self,rect,relative=0):
+ if not rect:
+ w,h = self._pagesize
+ rect = (0,0,w,h)
+ elif relative:
+ 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)
+ rect = xmin, ymin, xmax, ymax
+ return rect
+
+ def freeTextAnnotation(self, contents, DA, Rect=None, addtopage=1, name=None, relative=0, **kw):
+ """DA is the default appearance string???"""
+ Rect = self._absRect(Rect,relative)
+ self._addAnnotation(pdfdoc.FreeTextAnnotation(Rect, contents, DA, **kw), name, addtopage)
+
+ def textAnnotation(self, contents, Rect=None, addtopage=1, name=None, relative=0, **kw):
+ """Experimental, but works.
+ """
+ Rect = self._absRect(Rect,relative)
+ self._addAnnotation(pdfdoc.TextAnnotation(Rect, contents, **kw), name, addtopage)
+ textAnnotation0 = textAnnotation #deprecated
+
+ def inkAnnotation(self, contents, InkList=None, Rect=None, addtopage=1, name=None, relative=0, **kw):
+ raise NotImplementedError
+ "Experimental"
+ Rect = self._absRect(Rect,relative)
+ if not InkList:
+ InkList = ((100,100,100,h-100,w-100,h-100,w-100,100),)
+ self._addAnnotation(pdfdoc.InkAnnotation(Rect, contents, InkList, **kw), name, addtopage)
+ inkAnnotation0 = inkAnnotation #deprecated
+
+ def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None,
+ thickness=0, color=None, dashArray=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."""
+ return self.linkRect(contents, destinationname, Rect, addtopage, name, relative=0,
+ thickness=thickness, color=color, dashArray=dashArray, **kw)
+
+ def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, relative=0,
+ thickness=0, color=None, dashArray=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
+ """
+ destination = self._bookmarkReference(destinationname) # permitted to be undefined... must bind later...
+ Rect = self._absRect(Rect,relative)
+ kw["Rect"] = Rect
+ kw["Contents"] = contents
+ kw["Destination"] = destination
+ _annFormat(kw,color,thickness,dashArray)
+ return self._addAnnotation(pdfdoc.LinkAnnotation(**kw), name, addtopage)
+
+ def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None, kind="URI", **kw):
+ """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!
+
+ ann = PDFDictionary(dict=kw)
+ ann["Type"] = PDFName("Annot")
+ ann["Subtype"] = PDFName("Link")
+ ann["Rect"] = PDFArray(self._absRect(rect,relative)) # the whole page for testing
+
+ # the action is a separate dictionary
+ A = PDFDictionary()
+ A["Type"] = PDFName("Action") # not needed?
+ uri = PDFString(url)
+ A['S'] = PDFName(kind)
+ if kind=="URI":
+ A["URI"] = uri
+ elif kind=='GoToR':
+ A["F"] = uri
+ A["D"] = "[ 0 /XYZ null null null ]"
+ else:
+ raise ValueError("Unknown linkURI kind '%s'" % kind)
+
+ ann["A"] = A
+ _annFormat(ann,color,thickness,dashArray)
+ 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<x2 and y1<y2.
+
+ Contributed to piddlePDF by Robert Kern, 28/7/99.
+ Trimmed down by AR to remove color stuff for pdfgen.canvas and
+ revert to positive coordinates.
+
+ The algorithm is an elliptical generalization of the formulae in
+ Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
+
+ 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 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.
+ So, you could draw a straight line down through all the decimals in a
+ column of numbers, and anything without a decimal should be
+ optically aligned with those that have.
+
+ There is one special rule to help with accounting formatting. Here's
+ how normal numbers should be aligned on the 'dot'. Look at the
+ LAST two:
+ 12,345,67
+ 987.15
+ 42
+ -1,234.56
+ (456.78)
+ (456)
+ 27 inches
+ 13cm
+ Since the last three do not contain a dot, a crude dot-finding
+ rule would place them wrong. So we test for the special case
+ where no pivot is found, digits are present, but the last character
+ is not a digit. We then work back from the end of the string
+ This case is a tad slower but hopefully rare.
+
+ """
+ parts = text.split(pivotChar,1)
+ pivW = self.stringWidth(pivotChar, self._fontname, self._fontsize)
+
+ if len(parts) == 1 and digitPat.search(text) is not None and text[-1] not in digits:
+ #we have no decimal but it ends in a bracket, or 'in' or something.
+ #the cut should be after the last digit.
+ leftText = parts[0][0:-1]
+ rightText = parts[0][-1]
+ #any more?
+ while leftText[-1] not in digits:
+ rightText = leftText[-1] + rightText
+ leftText = leftText[0:-1]
+
+ self.drawRightString(x-0.5*pivW, y, leftText)
+ self.drawString(x-0.5*pivW, y, rightText)
+
+ else:
+ #normal case
+ 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 setFontSize(self, size=None, leading=None):
+ '''Sets font size or leading without knowing the font face'''
+ if size is None: size = self._fontsize
+ if leading is None: leading = self._leading
+ self.setFont(self._fontname, size, leading)
+
+ def stringWidth(self, text, fontName, fontSize):
+ "gets width of a string in the given font and size"
+ 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))
+
+ # 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
+
+ def getCurrentPageContent(self):
+ """Return uncompressed contents of current page buffer.
+
+ This is useful in creating test cases and assertions of what
+ got drawn, without necessarily saving pages to disk"""
+ return '\n'.join(self._code)
+
+
+
+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
new file mode 100755
index 00000000000..2eaacc8d8e2
--- /dev/null
+++ b/bin/reportlab/pdfgen/pathobject.py
@@ -0,0 +1,101 @@
+#Copyright ReportLab Europe Ltd. 2000-2004
+#see license.txt for license details
+#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pathobject.py
+__version__=''' $Id: pathobject.py 2537 2005-03-15 14:19:29Z rgbecker $ '''
+__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
+ self._code_append = self._init_code_append
+
+ def _init_code_append(self,c):
+ assert c.endswith(' m') or c.endswith(' re'), 'path must start with a moveto or rect'
+ code_append = self._code.append
+ code_append('n')
+ code_append(c)
+ self._code_append = code_append
+
+ 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<x2 and y1<y2.
+
+ The algorithm is an elliptical generalization of the formulae in
+ Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
+
+ 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
new file mode 100755
index 00000000000..34ef826b0b8
--- /dev/null
+++ b/bin/reportlab/pdfgen/pdfgeom.py
@@ -0,0 +1,77 @@
+#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: pdfgeom.py 2385 2004-06-17 15:26:05Z rgbecker $ '''
+__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
new file mode 100644
index 00000000000..f9e010314aa
--- /dev/null
+++ b/bin/reportlab/pdfgen/pdfimages.py
@@ -0,0 +1,188 @@
+#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: pdfimages.py 2744 2005-12-15 12:28:18Z rgbecker $ '''
+__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"""
+ if self.width<1e-6 or self.height<1e-6: return False
+ (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')
+ return True
+
+ 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
new file mode 100644
index 00000000000..849690eb1f2
--- /dev/null
+++ b/bin/reportlab/pdfgen/pycanvas.py
@@ -0,0 +1,309 @@
+# a Pythonesque Canvas v0.8
+# Author : Jerome Alet - <alet@librelogiciel.com>
+# License : ReportLab's license
+#
+# $Id: pycanvas.py 1821 2002-11-06 17:11:31Z rgbecker $
+#
+__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
new file mode 100755
index 00000000000..32affe605d3
--- /dev/null
+++ b/bin/reportlab/pdfgen/textobject.py
@@ -0,0 +1,398 @@
+#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: textobject.py 2830 2006-04-05 15:18:32Z rgbecker $ '''
+__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.colors import Color, CMYKColor, toColor
+from reportlab.lib.utils import fp_str
+from reportlab.pdfbase import pdfmetrics
+
+_SeqTypes=(TupleType,ListType)
+
+class _PDFColorSetter:
+ '''Abstracts the color setting operations; used in Canvas and Textobject
+ asseumes we have a _code object'''
+ 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 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):
+ 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))
+
+class PDFTextObject(_PDFColorSetter):
+ """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 _formatText(self, text):
+ "Generates PDF text output operator(s)"
+ canv = self._canvas
+ font = pdfmetrics.getFont(self._fontname)
+ R = []
+ if self._dynamicFont:
+ #it's a truetype font and should be utf8. If an error is raised,
+ for subset, t in font.splitString(text, canv._doc):
+ if subset != self._curSubset:
+ pdffontname = font.getSubsetInternalName(subset, canv._doc)
+ R.append("%s %s Tf %s TL" % (pdffontname, fp_str(self._fontsize), fp_str(self._leading)))
+ self._curSubset = subset
+ R.append("(%s) Tj" % canv._escape(t))
+ elif font._multiByte:
+ #all the fonts should really work like this - let them know more about PDF...
+ R.append("%s %s Tf %s TL" % (
+ canv._doc.getInternalFontName(font.fontName),
+ fp_str(self._fontsize),
+ fp_str(self._leading)
+ ))
+ R.append("(%s) Tj" % font.formatForPdf(text))
+
+ else:
+ #convert to T1 coding
+ fc = font
+ if not isinstance(text,unicode):
+ try:
+ text = text.decode('utf8')
+ except UnicodeDecodeError,e:
+ i,j = e.args[2:4]
+ raise UnicodeDecodeError(*(e.args[:4]+('%s\n%s-->%s<--%s' % (e.args[4],text[i-10:i],text[i:j],text[j:j+10]),)))
+
+ for f, t in pdfmetrics.unicode2T1(text,[font]+font.substitutionFonts):
+ if f!=fc:
+ R.append("%s %s Tf %s TL" % (canv._doc.getInternalFontName(f.fontName), fp_str(self._fontsize), fp_str(self._leading)))
+ fc = f
+ R.append("(%s) Tj" % canv._escape(t))
+ if font!=fc:
+ R.append("%s %s Tf %s TL" % (canv._doc.getInternalFontName(self._fontname), fp_str(self._fontsize), fp_str(self._leading)))
+ return ' '.join(R)
+
+ 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']