From 87b2a6bebceba02de5b98c2bd963cbb2e363c453 Mon Sep 17 00:00:00 2001 From: pinky <> Date: Sun, 7 Jan 2007 23:35:02 +0000 Subject: [PATCH] RL2 bzr revid: pinky-9ee609e92f665d717ac2a51b921e52f7f73390c4 --- bin/reportlab/pdfbase/__init__.py | 6 + bin/reportlab/pdfbase/_can_cmap_data.py | 58 + bin/reportlab/pdfbase/_cidfontdata.py | 483 +++++ bin/reportlab/pdfbase/_fontdata.py | 2590 +++++++++++++++++++++++ bin/reportlab/pdfbase/cidfonts.py | 516 +++++ bin/reportlab/pdfbase/pdfdoc.py | 1892 +++++++++++++++++ bin/reportlab/pdfbase/pdfform.py | 632 ++++++ bin/reportlab/pdfbase/pdfmetrics.py | 796 +++++++ bin/reportlab/pdfbase/pdfpattern.py | 59 + bin/reportlab/pdfbase/pdfutils.py | 460 ++++ bin/reportlab/pdfbase/rl_codecs.py | 1027 +++++++++ bin/reportlab/pdfbase/ttfonts.py | 1113 ++++++++++ bin/reportlab/pdfgen/__init__.py | 5 + bin/reportlab/pdfgen/canvas.py | 1462 +++++++++++++ bin/reportlab/pdfgen/pathobject.py | 101 + bin/reportlab/pdfgen/pdfgeom.py | 77 + bin/reportlab/pdfgen/pdfimages.py | 188 ++ bin/reportlab/pdfgen/pycanvas.py | 309 +++ bin/reportlab/pdfgen/textobject.py | 398 ++++ 19 files changed, 12172 insertions(+) create mode 100755 bin/reportlab/pdfbase/__init__.py create mode 100644 bin/reportlab/pdfbase/_can_cmap_data.py create mode 100644 bin/reportlab/pdfbase/_cidfontdata.py create mode 100644 bin/reportlab/pdfbase/_fontdata.py create mode 100644 bin/reportlab/pdfbase/cidfonts.py create mode 100755 bin/reportlab/pdfbase/pdfdoc.py create mode 100644 bin/reportlab/pdfbase/pdfform.py create mode 100755 bin/reportlab/pdfbase/pdfmetrics.py create mode 100644 bin/reportlab/pdfbase/pdfpattern.py create mode 100755 bin/reportlab/pdfbase/pdfutils.py create mode 100644 bin/reportlab/pdfbase/rl_codecs.py create mode 100644 bin/reportlab/pdfbase/ttfonts.py create mode 100755 bin/reportlab/pdfgen/__init__.py create mode 100755 bin/reportlab/pdfgen/canvas.py create mode 100755 bin/reportlab/pdfgen/pathobject.py create mode 100755 bin/reportlab/pdfgen/pdfgeom.py create mode 100644 bin/reportlab/pdfgen/pdfimages.py create mode 100644 bin/reportlab/pdfgen/pycanvas.py create mode 100755 bin/reportlab/pdfgen/textobject.py 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 chi 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>""" + +class PDFOutlines0: + __Comment__ = "TEST OUTLINE!" + text = string.replace(DUMMYOUTLINE, "\n", LINEEND) + __RefOnly__ = 1 + def format(self, document): + return self.text + + +class OutlineEntryObject: + "an entry in an outline" + Title = Dest = Parent = Prev = Next = First = Last = Count = None + def format(self, document): + D = {} + D["Title"] = PDFString(self.Title) + D["Parent"] = self.Parent + D["Dest"] = self.Dest + for n in ("Prev", "Next", "First", "Last", "Count"): + v = getattr(self, n) + if v is not None: + D[n] = v + PD = PDFDictionary(D) + return PD.format(document) + + +class PDFOutlines: + """takes a recursive list of outline destinations + like + out = PDFOutline1() + out.setNames(canvas, # requires canvas for name resolution + "chapter1dest", + ("chapter2dest", + ["chapter2section1dest", + "chapter2section2dest", + "chapter2conclusiondest"] + ), # end of chapter2 description + "chapter3dest", + ("chapter4dest", ["c4s1", "c4s2"]) + ) + Higher layers may build this structure incrementally. KISS at base level. + """ + # first attempt, many possible features missing. + #no init for now + mydestinations = ready = None + counter = 0 + currentlevel = -1 # ie, no levels yet + + def __init__(self): + self.destinationnamestotitles = {} + self.destinationstotitles = {} + self.levelstack = [] + self.buildtree = [] + self.closedict = {} # dictionary of "closed" destinations in the outline + + def addOutlineEntry(self, destinationname, level=0, title=None, closed=None): + """destinationname of None means "close the tree" """ + from types import IntType, TupleType + if destinationname is None and level!=0: + raise ValueError, "close tree must have level of 0" + if type(level) is not IntType: raise ValueError, "level must be integer, got %s" % type(level) + if level<0: raise ValueError, "negative levels not allowed" + if title is None: title = destinationname + currentlevel = self.currentlevel + stack = self.levelstack + tree = self.buildtree + # adjust currentlevel and stack to match level + if level>currentlevel: + if level>currentlevel+1: + raise ValueError, "can't jump from outline level %s to level %s, need intermediates" %(currentlevel, level) + level = currentlevel = currentlevel+1 + stack.append([]) + while levelref + if Ot is ListType or Ot is TupleType: + L = [] + for o in object: + L.append(self.translateNames(canvas, o)) + if Ot is TupleType: + return tuple(L) + return L + raise "in outline, destination name must be string: got a %s" % Ot + + def prepare(self, document, canvas): + """prepare all data structures required for save operation (create related objects)""" + if self.mydestinations is None: + if self.levelstack: + self.addOutlineEntry(None) # close the tree + destnames = self.levelstack[0] + #from pprint import pprint; pprint(destnames); stop + self.mydestinations = self.translateNames(canvas, destnames) + else: + self.first = self.last = None + self.count = 0 + self.ready = 1 + return + #self.first = document.objectReference("Outline.First") + #self.last = document.objectReference("Outline.Last") + # XXXX this needs to be generalized for closed entries! + self.count = count(self.mydestinations, self.closedict) + (self.first, self.last) = self.maketree(document, self.mydestinations, toplevel=1) + self.ready = 1 + + def maketree(self, document, destinationtree, Parent=None, toplevel=0): + from types import ListType, TupleType, DictType + tdestinationtree = type(destinationtree) + if toplevel: + levelname = "Outline" + Parent = document.Reference(document.Outlines) + else: + self.count = self.count+1 + levelname = "Outline.%s" % self.count + if Parent is None: + raise ValueError, "non-top level outline elt parent must be specified" + if tdestinationtree is not ListType and tdestinationtree is not TupleType: + raise ValueError, "destinationtree must be list or tuple, got %s" + nelts = len(destinationtree) + lastindex = nelts-1 + lastelt = firstref = lastref = None + destinationnamestotitles = self.destinationnamestotitles + closedict = self.closedict + for index in range(nelts): + eltobj = OutlineEntryObject() + eltobj.Parent = Parent + eltname = "%s.%s" % (levelname, index) + eltref = document.Reference(eltobj, eltname) + #document.add(eltname, eltobj) + if lastelt is not None: + lastelt.Next = eltref + eltobj.Prev = lastref + if firstref is None: + firstref = eltref + lastref = eltref + lastelt = eltobj # advance eltobj + lastref = eltref + elt = destinationtree[index] + te = type(elt) + if te is DictType: + # simple leaf {name: dest} + leafdict = elt + elif te is TupleType: + # leaf with subsections: ({name: ref}, subsections) XXXX should clean up (see count(...)) + try: + (leafdict, subsections) = elt + except: + raise ValueError, "destination tree elt tuple should have two elts, got %s" % len(elt) + eltobj.Count = count(subsections, closedict) + (eltobj.First, eltobj.Last) = self.maketree(document, subsections, eltref) + else: + raise ValueError, "destination tree elt should be dict or tuple, got %s" % te + try: + [(Title, Dest)] = leafdict.items() + except: + raise ValueError, "bad outline leaf dictionary, should have one entry "+str(elt) + eltobj.Title = destinationnamestotitles[Title] + eltobj.Dest = Dest + if te is TupleType and closedict.has_key(Dest): + # closed subsection, count should be negative + eltobj.Count = -eltobj.Count + return (firstref, lastref) + +def count(tree, closedict=None): + """utility for outline: recursively count leaves in a tuple/list tree""" + from operator import add + from types import TupleType, ListType + tt = type(tree) + if tt is TupleType: + # leaf with subsections XXXX should clean up this structural usage + (leafdict, subsections) = tree + [(Title, Dest)] = leafdict.items() + if closedict and closedict.has_key(Dest): + return 1 # closed tree element + if tt is TupleType or tt is ListType: + #return reduce(add, map(count, tree)) + counts = [] + for e in tree: + counts.append(count(e, closedict)) + return reduce(add, counts) + return 1 + +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+" + 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.""" + + 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.""" + + 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 - +# 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']