RL2
bzr revid: pinky-9ee609e92f665d717ac2a51b921e52f7f73390c4
This commit is contained in:
parent
ac5f844444
commit
87b2a6bebc
|
@ -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__="""
|
||||
"""
|
|
@ -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()
|
||||
|
|
@ -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
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
|
@ -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()
|
|
@ -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, "")
|
||||
|
||||
|
|
@ -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
|
@ -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__=''
|
File diff suppressed because it is too large
Load Diff
|
@ -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')
|
|
@ -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
|
|
@ -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)
|
|
@ -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'
|
|
@ -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']
|
Loading…
Reference in New Issue