bzr revid: pinky-9ee609e92f665d717ac2a51b921e52f7f73390c4
This commit is contained in:
pinky 2007-01-07 23:35:02 +00:00
parent ac5f844444
commit 87b2a6bebc
19 changed files with 12172 additions and 0 deletions

View File

@ -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__="""
"""

View File

@ -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()

View File

@ -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, ''))

File diff suppressed because it is too large Load Diff

View File

@ -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()

1892
bin/reportlab/pdfbase/pdfdoc.py Executable file

File diff suppressed because it is too large Load Diff

View File

@ -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()

View File

@ -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()

View File

@ -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, "")

460
bin/reportlab/pdfbase/pdfutils.py Executable file
View File

@ -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]))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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__=''

1462
bin/reportlab/pdfgen/canvas.py Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
#Copyright ReportLab Europe Ltd. 2000-2004
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/pdfgen/pathobject.py
__version__=''' $Id: pathobject.py 2537 2005-03-15 14:19:29Z rgbecker $ '''
__doc__="""
PDFPathObject is an efficient way to draw paths on a Canvas. Do not
instantiate directly, obtain one from the Canvas instead.
Progress Reports:
8.83, 2000-01-13, gmcm:
created from pdfgen.py
"""
import string
from reportlab.pdfgen import pdfgeom
from reportlab.lib.utils import fp_str
class PDFPathObject:
"""Represents a graphic path. There are certain 'modes' to PDF
drawing, and making a separate object to expose Path operations
ensures they are completed with no run-time overhead. Ask
the Canvas for a PDFPath with getNewPathObject(); moveto/lineto/
curveto wherever you want; add whole shapes; and then add it back
into the canvas with one of the relevant operators.
Path objects are probably not long, so we pack onto one line"""
def __init__(self):
self._code = []
#self._code.append('n') #newpath
self._code_append = self._init_code_append
def _init_code_append(self,c):
assert c.endswith(' m') or c.endswith(' re'), 'path must start with a moveto or rect'
code_append = self._code.append
code_append('n')
code_append(c)
self._code_append = code_append
def getCode(self):
"pack onto one line; used internally"
return string.join(self._code, ' ')
def moveTo(self, x, y):
self._code_append('%s m' % fp_str(x,y))
def lineTo(self, x, y):
self._code_append('%s l' % fp_str(x,y))
def curveTo(self, x1, y1, x2, y2, x3, y3):
self._code_append('%s c' % fp_str(x1, y1, x2, y2, x3, y3))
def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
"""Contributed to piddlePDF by Robert Kern, 28/7/99.
Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
starting at startAng degrees and covering extent degrees. Angles
start with 0 to the right (+x) and increase counter-clockwise.
These should have x1<x2 and y1<y2.
The algorithm is an elliptical generalization of the formulae in
Jim Fitzsimmon's TeX tutorial <URL: http://www.tinaja.com/bezarc1.pdf>."""
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
#move to first point
self._code_append('%s m' % fp_str(pointList[0][:2]))
for curve in pointList:
self._code_append('%s c' % fp_str(curve[2:]))
def arcTo(self, x1,y1, x2,y2, startAng=0, extent=90):
"""Like arc, but draws a line from the current point to
the start if the start is not the current point."""
pointList = pdfgeom.bezierArc(x1,y1, x2,y2, startAng, extent)
self._code_append('%s l' % fp_str(pointList[0][:2]))
for curve in pointList:
self._code_append('%s c' % fp_str(curve[2:]))
def rect(self, x, y, width, height):
"""Adds a rectangle to the path"""
self._code_append('%s re' % fp_str((x, y, width, height)))
def ellipse(self, x, y, width, height):
"""adds an ellipse to the path"""
pointList = pdfgeom.bezierArc(x, y, x + width,y + height, 0, 360)
self._code_append('%s m' % fp_str(pointList[0][:2]))
for curve in pointList:
self._code_append('%s c' % fp_str(curve[2:]))
def circle(self, x_cen, y_cen, r):
"""adds a circle to the path"""
x1 = x_cen - r
#x2 = x_cen + r
y1 = y_cen - r
#y2 = y_cen + r
width = height = 2*r
#self.ellipse(x_cen - r, y_cen - r, x_cen + r, y_cen + r)
self.ellipse(x1, y1, width, height)
def close(self):
"draws a line back to where it started"
self._code_append('h')

77
bin/reportlab/pdfgen/pdfgeom.py Executable file
View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,309 @@
# a Pythonesque Canvas v0.8
# Author : Jerome Alet - <alet@librelogiciel.com>
# License : ReportLab's license
#
# $Id: pycanvas.py 1821 2002-11-06 17:11:31Z rgbecker $
#
__doc__ = """pycanvas.Canvas : a Canvas class which can also output Python source code.
pycanvas.Canvas class works exactly like canvas.Canvas, but you can
call str() on pycanvas.Canvas instances. Doing so will return the
Python source code equivalent to your own program, which would, when
run, produce the same PDF document as your original program.
Generated Python source code defines a doIt() function which accepts
a filename or file-like object as its first parameter, and an
optional boolean parameter named "regenerate".
The doIt() function will generate a PDF document and save it in the
file you specified in this argument. If the regenerate parameter is
set then it will also return an automatically generated equivalent
Python source code as a string of text, which you can run again to
produce the very same PDF document and the Python source code, which
you can run again... ad nauseam ! If the regenerate parameter is
unset or not used at all (it then defaults to being unset) then None
is returned and the doIt() function is much much faster, it is also
much faster than the original non-serialized program.
the reportlab/test/test_pdfgen_pycanvas.py program is the test suite
for pycanvas, you can do the following to run it :
First set verbose=1 in reportlab/rl_config.py
then from the command interpreter :
$ cd reportlab/test
$ python test_pdfgen_pycanvas.py >n1.py
this will produce both n1.py and test_pdfgen_pycanvas.pdf
then :
$ python n1.py n1.pdf >n2.py
$ python n2.py n2.pdf >n3.py
$ ...
n1.py, n2.py, n3.py and so on will be identical files.
they eventually may end being a bit different because of
rounding problems, mostly in the comments, but this
doesn't matter since the values really are the same
(e.g. 0 instead of 0.0, or .53 instead of 0.53)
n1.pdf, n2.pdf, n3.pdf and so on will be PDF files
similar to test_pdfgen_pycanvas.pdf.
Alternatively you can import n1.py (or n3.py, or n16384.py if you prefer)
in your own program, and then call its doIt function :
import n1
pythonsource = n1.doIt("myfile.pdf", regenerate=1)
Or if you don't need the python source code and want a faster result :
import n1
n1.doIt("myfile.pdf")
When the generated source code is run directly as an independant program,
then the equivalent python source code is printed to stdout, e.g. :
python n1.py
will print the python source code equivalent to n1.py
Why would you want to use such a beast ?
- To linearize (serialize?) a program : optimizing some complex
parts for example.
- To debug : reading the generated Python source code may help you or
the ReportLab team to diagnose problems. The generated code is now
clearly commented and shows nesting levels, page numbers, and so
on. You can use the generated script when asking for support : we
can see the results you obtain without needing your datas or complete
application.
- To create standalone scripts : say your program uses a high level
environment to generate its output (databases, RML, etc...), using
this class would give you an equivalent program but with complete
independance from the high level environment (e.g. if you don't
have Oracle).
- To contribute some nice looking PDF documents to the ReportLab website
without having to send a complete application you don't want to
distribute.
- ... Insert your own ideas here ...
- For fun because you can do it !
"""
import cStringIO
from reportlab.pdfgen import canvas
from reportlab.pdfgen import pathobject
from reportlab.pdfgen import textobject
PyHeader = '''#! /usr/bin/env python
#
# This code was entirely generated by ReportLab (http://www.reportlab.com)
#
import sys
from reportlab.pdfgen import pathobject
from reportlab.pdfgen import textobject
from reportlab.lib.colors import Color
def doIt(file, regenerate=0) :
"""Generates a PDF document, save it into file.
file : either a filename or a file-like object.
regenerate : if set then this function returns the Python source
code which when run will produce the same result.
if unset then this function returns None, and is
much faster.
"""
if regenerate :
from reportlab.pdfgen.pycanvas import Canvas
else :
from reportlab.pdfgen.canvas import Canvas
'''
PyFooter = '''
# if we want the equivalent Python source code, then send it back
if regenerate :
return str(c)
if __name__ == "__main__" :
if len(sys.argv) != 2 :
# second argument must be the name of the PDF file to create
sys.stderr.write("%s needs one and only one argument\\n" % sys.argv[0])
sys.exit(-1)
else :
# we've got a filename, we can proceed.
print doIt(sys.argv[1], regenerate=1)
sys.exit(0)'''
def buildargs(*args, **kwargs) :
"""Constructs a printable list of arguments suitable for use in source function calls."""
arguments = ""
for arg in args :
arguments = arguments + ("%s, " % repr(arg))
for (kw, val) in kwargs.items() :
arguments = arguments+ ("%s=%s, " % (kw, repr(val)))
if arguments[-2:] == ", " :
arguments = arguments[:-2]
return arguments
class PDFAction :
"""Base class to fake method calls or attributes on PDF objects (Canvas, PDFPathObject, PDFTextObject)."""
def __init__(self, parent, action) :
"""Saves a pointer to the parent object, and the method name."""
self._parent = parent
self._action = action
def __getattr__(self, name) :
"""Probably a method call on an attribute, returns the real one."""
return getattr(getattr(self._parent._object, self._action), name)
def __call__(self, *args, **kwargs) :
"""The fake method is called, print it then call the real one."""
if not self._parent._parent._in :
self._precomment()
self._parent._parent._PyWrite(" %s.%s(%s)" % (self._parent._name, self._action, apply(buildargs, args, kwargs)))
self._postcomment()
self._parent._parent._in = self._parent._parent._in + 1
retcode = apply(getattr(self._parent._object, self._action), args, kwargs)
self._parent._parent._in = self._parent._parent._in - 1
return retcode
def __hash__(self) :
return hash(getattr(self._parent._object, self._action))
def __coerce__(self, other) :
"""Needed."""
return coerce(getattr(self._parent._object, self._action), other)
def _precomment(self) :
"""To be overriden."""
pass
def _postcomment(self) :
"""To be overriden."""
pass
class PDFObject :
"""Base class for PDF objects like PDFPathObject and PDFTextObject."""
_number = 0
def __init__(self, parent) :
"""Saves a pointer to the parent Canvas."""
self._parent = parent
self._initdone = 0
def __getattr__(self, name) :
"""The user's programs wants to call one of our methods or get an attribute, fake it."""
return PDFAction(self, name)
def __repr__(self) :
"""Returns the name used in the generated source code (e.g. 'p' or 't')."""
return self._name
def __call__(self, *args, **kwargs) :
"""Real object initialisation is made here, because now we've got the arguments."""
if not self._initdone :
self.__class__._number = self.__class__._number + 1
methodname = apply(self._postinit, args, kwargs)
self._parent._PyWrite("\n # create PDF%sObject number %i\n %s = %s.%s(%s)" % (methodname[5:], self.__class__._number, self._name, self._parent._name, methodname, apply(buildargs, args, kwargs)))
self._initdone = 1
return self
class Canvas :
"""Our fake Canvas class, which will intercept each and every method or attribute access."""
class TextObject(PDFObject) :
_name = "t"
def _postinit(self, *args, **kwargs) :
self._object = apply(textobject.PDFTextObject, (self._parent, ) + args, kwargs)
return "beginText"
class PathObject(PDFObject) :
_name = "p"
def _postinit(self, *args, **kwargs) :
self._object = apply(pathobject.PDFPathObject, args, kwargs)
return "beginPath"
class Action(PDFAction) :
"""Class called for every Canvas method call."""
def _precomment(self) :
"""Outputs comments before the method call."""
if self._action == "showPage" :
self._parent._PyWrite("\n # Ends page %i" % self._parent._pagenumber)
elif self._action == "saveState" :
state = {}
d = self._parent._object.__dict__
for name in self._parent._object.STATE_ATTRIBUTES:
state[name] = d[name]
self._parent._PyWrite("\n # Saves context level %i %s" % (self._parent._contextlevel, state))
self._parent._contextlevel = self._parent._contextlevel + 1
elif self._action == "restoreState" :
self._parent._contextlevel = self._parent._contextlevel - 1
self._parent._PyWrite("\n # Restores context level %i %s" % (self._parent._contextlevel, self._parent._object.state_stack[-1]))
elif self._action == "beginForm" :
self._parent._formnumber = self._parent._formnumber + 1
self._parent._PyWrite("\n # Begins form %i" % self._parent._formnumber)
elif self._action == "endForm" :
self._parent._PyWrite("\n # Ends form %i" % self._parent._formnumber)
elif self._action == "save" :
self._parent._PyWrite("\n # Saves the PDF document to disk")
def _postcomment(self) :
"""Outputs comments after the method call."""
if self._action == "showPage" :
self._parent._pagenumber = self._parent._pagenumber + 1
self._parent._PyWrite("\n # Begins page %i" % self._parent._pagenumber)
elif self._action in [ "endForm", "drawPath", "clipPath" ] :
self._parent._PyWrite("")
_name = "c"
def __init__(self, *args, **kwargs) :
"""Initialize and begins source code."""
self._parent = self # nice trick, isn't it ?
self._in = 0
self._contextlevel = 0
self._pagenumber = 1
self._formnumber = 0
self._footerpresent = 0
self._object = apply(canvas.Canvas, args, kwargs)
self._pyfile = cStringIO.StringIO()
self._PyWrite(PyHeader)
try :
del kwargs["filename"]
except KeyError :
pass
self._PyWrite(" # create the PDF document\n %s = Canvas(file, %s)\n\n # Begins page 1" % (self._name, apply(buildargs, args[1:], kwargs)))
def __nonzero__(self) :
"""This is needed by platypus' tables."""
return 1
def __str__(self) :
"""Returns the equivalent Python source code."""
if not self._footerpresent :
self._PyWrite(PyFooter)
self._footerpresent = 1
return self._pyfile.getvalue()
def __getattr__(self, name) :
"""Method or attribute access."""
if name == "beginPath" :
return self.PathObject(self)
elif name == "beginText" :
return self.TextObject(self)
else :
return self.Action(self, name)
def _PyWrite(self, pycode) :
"""Outputs the source code with a trailing newline."""
self._pyfile.write("%s\n" % pycode)
if __name__ == '__main__':
print 'For test scripts, look in reportlab/test'

View File

@ -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']