sss - init new pc

master
cherif 2022-11-15 19:27:51 +01:00
parent 69c75e9325
commit 7107d326c5
37 changed files with 7680 additions and 7170 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (venv)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (MySy_Back_Office)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser"> <component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" /> <option name="shown" value="true" />
</component> </component>

View File

@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.10 (venv)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.11 (MySy_Back_Office)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

34
requirements.txt Normal file
View File

@ -0,0 +1,34 @@
flask
Pillow
textdistance
unidecode
wrapper
google-search-results
sib_api_v3_sdk
xhtml2pdf
pysftp
pymongo
flask_mongoengine
unicodecsv
pdfminer
flask_cors
spacy
nltk
pyspellchecker
textblob
autocorrect
pandas
stripe
python-dotenv
pyopenssl
arabic-reshaper
coverage
html5lib
Pillow
PyPDF3
python-bidi
reportlab
svglib
pyHanko
pyhanko-certvalidator

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python
__version__ = '1.3.1' __version__ = '20191125'
if __name__ == '__main__': print(__version__) if __name__ == '__main__':
print(__version__)

View File

@ -1,10 +1,25 @@
#!/usr/bin/env python
""" Python implementation of Arcfour encryption algorithm. """ Python implementation of Arcfour encryption algorithm.
This code is in the public domain. This code is in the public domain.
""" """
## Arcfour
##
class Arcfour: class Arcfour:
"""
>>> Arcfour(b'Key').process(b'Plaintext').hex()
'bbf316e8d940af0ad3'
>>> Arcfour(b'Wiki').process(b'pedia').hex()
'1021bf0420'
>>> Arcfour(b'Secret').process(b'Attack at dawn').hex()
'45a01f645fc35b383552544b9bf5'
"""
def __init__(self, key): def __init__(self, key):
s = list(range(256)) s = list(range(256))
j = 0 j = 0
@ -14,6 +29,7 @@ class Arcfour:
(s[i], s[j]) = (s[j], s[i]) (s[i], s[j]) = (s[j], s[i])
self.s = s self.s = s
(self.i, self.j) = (0, 0) (self.i, self.j) = (0, 0)
return
def process(self, data): def process(self, data):
(i, j) = (self.i, self.j) (i, j) = (self.i, self.j)
@ -27,3 +43,12 @@ class Arcfour:
r.append(c ^ k) r.append(c ^ k)
(self.i, self.j) = (i, j) (self.i, self.j) = (i, j)
return bytes(r) return bytes(r)
encrypt = decrypt = process
new = Arcfour
# test
if __name__ == '__main__':
import doctest
print('pdfminer.arcfour:', doctest.testmod())

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
""" Python implementation of ASCII85/ASCIIHex decoder (Adobe version). """ Python implementation of ASCII85/ASCIIHex decoder (Adobe version).
@ -9,43 +9,51 @@ This code is in the public domain.
import re import re
import struct import struct
# ascii85decode(data)
def ascii85decode(data): def ascii85decode(data):
""" """
In ASCII85 encoding, every four bytes are encoded with five ASCII In ASCII85 encoding, every four bytes are encoded with five ASCII
letters, using 85 different types of characters (as 256**4 < 85**5). letters, using 85 different types of characters (as 256**4 < 85**5).
When the length of the original bytes is not a multiple of 4, a special When the length of the original bytes is not a multiple of 4, a special
rule is used for round up. rule is used for round up.
The Adobe's ASCII85 implementation is slightly different from The Adobe's ASCII85 implementation is slightly different from
its original in handling the last characters. its original in handling the last characters.
The sample string is taken from: The sample string is taken from:
http://en.wikipedia.org/w/index.php?title=Ascii85 http://en.wikipedia.org/w/index.php?title=Ascii85
>>> ascii85decode(b'9jqo^BlbD-BleB1DJ+*+F(f,q')
b'Man is distinguished'
>>> ascii85decode(b'E,9)oF*2M7/c~>')
b'pleasure.'
""" """
if isinstance(data, str):
data = data.encode('ascii')
n = b = 0 n = b = 0
out = bytearray() out = b''
for c in data: for c in data:
if ord('!') <= c and c <= ord('u'): if 33 <= c and c <= 117: # b'!' <= c and c <= b'u'
n += 1 n += 1
b = b*85+(c-33) b = b*85+(c-33)
if n == 5: if n == 5:
out += struct.pack(b'>L',b) out += struct.pack('>L', b)
n = b = 0 n = b = 0
elif c == ord('z'): elif c == 122: # b'z'
assert n == 0 assert n == 0
out += b'\0\0\0\0' out += b'\0\0\0\0'
elif c == ord('~'): elif c == 126: # b'~'
if n: if n:
for _ in range(5-n): for _ in range(5-n):
b = b*85+84 b = b*85+84
out += struct.pack(b'>L',b)[:n-1] out += struct.pack('>L', b)[:n-1]
break break
return bytes(out) return out
# asciihexdecode(data)
hex_re = re.compile(r'([a-f\d]{2})', re.IGNORECASE) hex_re = re.compile(r'([a-f\d]{2})', re.IGNORECASE)
trail_re = re.compile(r'^(?:[a-f\d]{2}|\s)*([a-f\d])[\s>]*$', re.IGNORECASE) trail_re = re.compile(r'^(?:[a-f\d]{2}|\s)*([a-f\d])[\s>]*$', re.IGNORECASE)
def asciihexdecode(data): def asciihexdecode(data):
""" """
ASCIIHexDecode filter: PDFReference v1.4 section 3.3.1 ASCIIHexDecode filter: PDFReference v1.4 section 3.3.1
@ -55,10 +63,22 @@ def asciihexdecode(data):
EOD. Any other characters will cause an error. If the filter encounters EOD. Any other characters will cause an error. If the filter encounters
the EOD marker after reading an odd number of hexadecimal digits, it the EOD marker after reading an odd number of hexadecimal digits, it
will behave as if a 0 followed the last digit. will behave as if a 0 followed the last digit.
>>> asciihexdecode(b'61 62 2e6364 65')
b'ab.cde'
>>> asciihexdecode(b'61 62 2e6364 657>')
b'ab.cdep'
>>> asciihexdecode(b'7>')
b'p'
""" """
decode = (lambda hx: chr(int(hx, 16))) data = data.decode('latin1')
out = list(map(decode, hex_re.findall(data))) out = [ int(hx,16) for hx in hex_re.findall(data) ]
m = trail_re.search(data) m = trail_re.search(data)
if m: if m:
out.append(decode("%c0" % m.group(1))) out.append(int(m.group(1),16) << 4)
return ''.join(out) return bytes(out)
if __name__ == '__main__':
import doctest
print('pdfminer.ascii85', doctest.testmod())

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
""" Adobe character mapping (CMap) support. """ Adobe character mapping (CMap) support.
@ -15,37 +15,69 @@ import sys
import os import os
import os.path import os.path
import gzip import gzip
import pickle as pickle import codecs
import marshal
import struct import struct
import logging import logging
from . import cmap
from .psparser import PSStackParser from .psparser import PSStackParser
from .psparser import PSSyntaxError, PSEOF from .psparser import PSSyntaxError
from .psparser import PSEOF
from .psparser import PSLiteral from .psparser import PSLiteral
from .psparser import literal_name from .psparser import literal_name
from .psparser import KWD
from .encodingdb import name2unicode from .encodingdb import name2unicode
from .utils import choplist, nunpack from .utils import choplist
from .utils import nunpack
logger = logging.getLogger(__name__) class CMapError(Exception):
pass
class CMapError(Exception): pass ## CMapBase
##
class CMapBase:
debug = 0
class CMap: def __init__(self, **kwargs):
self.attrs = kwargs.copy()
def __init__(self, code2cid=None): return
self.code2cid = code2cid or {}
def is_vertical(self): def is_vertical(self):
return False return self.attrs.get('WMode', 0) != 0
def set_attr(self, k, v):
self.attrs[k] = v
return
def add_code2cid(self, code, cid):
return
def add_cid2unichr(self, cid, code):
return
def use_cmap(self, cmap):
return
## CMap
##
class CMap(CMapBase):
def __init__(self, **kwargs):
CMapBase.__init__(self, **kwargs)
self.code2cid = {}
return
def __repr__(self):
return '<CMap: %s>' % self.attrs.get('CMapName')
def use_cmap(self, cmap): def use_cmap(self, cmap):
assert isinstance(cmap, CMap) assert isinstance(cmap, CMap)
def copy(dst, src): def copy(dst, src):
for (k,v) in src.items(): for (k, v) in src.items():
if isinstance(v, dict): if isinstance(v, dict):
d = {} d = {}
dst[k] = d dst[k] = d
@ -53,11 +85,11 @@ class CMap:
else: else:
dst[k] = v dst[k] = v
copy(self.code2cid, cmap.code2cid) copy(self.code2cid, cmap.code2cid)
return
def decode(self, code): def decode(self, code):
logger.debug('decode: %r, %r', self, code) if self.debug:
if isinstance(code, str): logging.debug('decode: %r, %r' % (self, code))
code = code.encode('latin-1')
d = self.code2cid d = self.code2cid
for c in code: for c in code:
if c in d: if c in d:
@ -67,73 +99,62 @@ class CMap:
d = self.code2cid d = self.code2cid
else: else:
d = self.code2cid d = self.code2cid
return
def dump(self, out=sys.stdout, code2cid=None, code=None): def dump(self, out=sys.stdout, code2cid=None, code=None):
if code2cid is None: if code2cid is None:
code2cid = self.code2cid code2cid = self.code2cid
code = () code = ()
for (k,v) in sorted(code2cid.items()): for (k, v) in sorted(code2cid.items()):
c = code+(k,) c = code+(k,)
if isinstance(v, int): if isinstance(v, int):
out.write('code %r = cid %d\n' % (c,v)) out.write('code %r = cid %d\n' % (c, v))
else: else:
self.dump(out=out, code2cid=v, code=c) self.dump(out=out, code2cid=v, code=c)
return
class IdentityCMap:
def __init__(self, vertical): ## IdentityCMap
self.vertical = vertical ##
class IdentityCMap(CMapBase):
def is_vertical(self):
return self.vertical
def decode(self, code): def decode(self, code):
if isinstance(code, str):
code = code.encode('latin-1')
if len(code) % 2 != 0:
# Something's wrong, but we have to at least prevent a crash by removing the last char
logger.warning("The code %r has an uneven length, trimming last byte.", code)
code = code[:-1]
n = len(code)//2 n = len(code)//2
if n: if n:
return struct.unpack('>%dH' % n, code) return struct.unpack('>%dH' % n, code)
else: else:
return () return ()
class UnicodeMap:
## UnicodeMap
def __init__(self, cid2unichr=None): ##
self.cid2unichr = cid2unichr or {} class UnicodeMap(CMapBase):
def __init__(self, **kwargs):
CMapBase.__init__(self, **kwargs)
self.cid2unichr = {}
return
def __repr__(self):
return '<UnicodeMap: %s>' % self.attrs.get('CMapName')
def get_unichr(self, cid): def get_unichr(self, cid):
logger.debug('get_unichr: %r, %r', self, cid) if self.debug:
logging.debug('get_unichr: %r, %r' % (self, cid))
return self.cid2unichr[cid] return self.cid2unichr[cid]
def dump(self, out=sys.stdout): def dump(self, out=sys.stdout):
for (k,v) in sorted(self.cid2unichr.items()): for (k, v) in sorted(self.cid2unichr.items()):
out.write('cid %d = unicode %r\n' % (k,v)) out.write('cid %d = unicode %r\n' % (k, v))
return
## FileCMap
##
class FileCMap(CMap): class FileCMap(CMap):
def __init__(self):
CMap.__init__(self)
self.attrs = {}
def __repr__(self):
return '<CMap: %s>' % self.attrs.get('CMapName')
def is_vertical(self):
return self.attrs.get('WMode', 0) != 0
def set_attr(self, k, v):
self.attrs[k] = v
def add_code2cid(self, code, cid): def add_code2cid(self, code, cid):
assert isinstance(code, str) and isinstance(cid, int) assert isinstance(code, bytes) and isinstance(cid, int)
d = self.code2cid d = self.code2cid
for c in code[:-1]: for c in code[:-1]:
c = ord(c) c = ord(c)
@ -142,28 +163,18 @@ class FileCMap(CMap):
else: else:
t = {} t = {}
d[c] = t d[c] = t
d =t d = t
c = ord(code[-1]) c = ord(code[-1])
d[c] = cid d[c] = cid
return
## FileUnicodeMap
##
class FileUnicodeMap(UnicodeMap): class FileUnicodeMap(UnicodeMap):
def __init__(self):
UnicodeMap.__init__(self)
self.attrs = {}
def __repr__(self):
return '<UnicodeMap: %s>' % self.attrs.get('CMapName')
def set_attr(self, k, v):
self.attrs[k] = v
def add_cid2unichr(self, cid, code): def add_cid2unichr(self, cid, code):
assert isinstance(cid, int) assert isinstance(cid, int)
if isinstance(code, str):
# Interpret the contents of the string as bytes, and decode it as if it was bytes
code = code.encode('latin-1')
if isinstance(code, PSLiteral): if isinstance(code, PSLiteral):
# Interpret as an Adobe glyph name. # Interpret as an Adobe glyph name.
self.cid2unichr[cid] = name2unicode(code.name) self.cid2unichr[cid] = name2unicode(code.name)
@ -173,55 +184,58 @@ class FileUnicodeMap(UnicodeMap):
elif isinstance(code, int): elif isinstance(code, int):
self.cid2unichr[cid] = chr(code) self.cid2unichr[cid] = chr(code)
else: else:
raise TypeError(repr(code)) raise TypeError(code)
return
## PyCMap
##
class PyCMap(CMap): class PyCMap(CMap):
def __init__(self, name, module): def __init__(self, name, module):
CMap.__init__(self, module.CODE2CID) CMap.__init__(self, CMapName=name)
self.name = name self.code2cid = module.CODE2CID
self._is_vertical = module.IS_VERTICAL if module.IS_VERTICAL:
self.attrs['WMode'] = 1
return
def __repr__(self):
return '<PyCMap: %s>' % (self.name)
def is_vertical(self):
return self._is_vertical
## PyUnicodeMap
##
class PyUnicodeMap(UnicodeMap): class PyUnicodeMap(UnicodeMap):
def __init__(self, name, module, vertical): def __init__(self, name, module, vertical):
UnicodeMap.__init__(self, CMapName=name)
if vertical: if vertical:
cid2unichr = module.CID2UNICHR_V self.cid2unichr = module.CID2UNICHR_V
self.attrs['WMode'] = 1
else: else:
cid2unichr = module.CID2UNICHR_H self.cid2unichr = module.CID2UNICHR_H
UnicodeMap.__init__(self, cid2unichr) return
self.name = name
def __repr__(self):
return '<PyUnicodeMap: %s>' % (self.name)
## CMapDB
##
class CMapDB: class CMapDB:
_cmap_cache = {} _cmap_cache = {}
_umap_cache = {} _umap_cache = {}
class CMapNotFound(CMapError): pass class CMapNotFound(CMapError):
pass
@classmethod @classmethod
def _load_data(klass, name): def _load_data(klass, name):
filename = '%s.pickle.gz' % name filename = '%s.marshal.gz' % name
logger.debug('loading: %s', name) logging.info('loading: %r' % name)
default_path = os.environ.get('CMAP_PATH', '/usr/share/pdfminer/') cmap_paths = (os.environ.get('CMAP_PATH', '/usr/share/pdfminer/'),
for directory in (os.path.dirname(cmap.__file__), default_path): os.path.join(os.path.dirname(__file__), 'cmap'),)
for directory in cmap_paths:
path = os.path.join(directory, filename) path = os.path.join(directory, filename)
if os.path.exists(path): if os.path.exists(path):
gzfile = gzip.open(path) gzfile = gzip.open(path)
try: try:
return type(name, (), pickle.loads(gzfile.read())) return type(str(name), (), marshal.loads(gzfile.read()))
finally: finally:
gzfile.close() gzfile.close()
else: else:
@ -230,9 +244,9 @@ class CMapDB:
@classmethod @classmethod
def get_cmap(klass, name): def get_cmap(klass, name):
if name == 'Identity-H': if name == 'Identity-H':
return IdentityCMap(False) return IdentityCMap(WMode=0)
elif name == 'Identity-V': elif name == 'Identity-V':
return IdentityCMap(True) return IdentityCMap(WMode=1)
try: try:
return klass._cmap_cache[name] return klass._cmap_cache[name]
except KeyError: except KeyError:
@ -252,42 +266,63 @@ class CMapDB:
return umaps[vertical] return umaps[vertical]
## CMapParser
##
class CMapParser(PSStackParser): class CMapParser(PSStackParser):
def __init__(self, cmap, fp): def __init__(self, cmap, fp):
PSStackParser.__init__(self, fp) PSStackParser.__init__(self, fp)
self.cmap = cmap self.cmap = cmap
self._in_cmap = False # some ToUnicode maps don't have "begincmap" keyword.
self._in_cmap = True
return
def run(self): def run(self):
try: try:
self.nextobject() self.nextobject()
except PSEOF: except PSEOF:
pass pass
return
KEYWORD_BEGINCMAP = KWD(b'begincmap')
KEYWORD_ENDCMAP = KWD(b'endcmap')
KEYWORD_USECMAP = KWD(b'usecmap')
KEYWORD_DEF = KWD(b'def')
KEYWORD_BEGINCODESPACERANGE = KWD(b'begincodespacerange')
KEYWORD_ENDCODESPACERANGE = KWD(b'endcodespacerange')
KEYWORD_BEGINCIDRANGE = KWD(b'begincidrange')
KEYWORD_ENDCIDRANGE = KWD(b'endcidrange')
KEYWORD_BEGINCIDCHAR = KWD(b'begincidchar')
KEYWORD_ENDCIDCHAR = KWD(b'endcidchar')
KEYWORD_BEGINBFRANGE = KWD(b'beginbfrange')
KEYWORD_ENDBFRANGE = KWD(b'endbfrange')
KEYWORD_BEGINBFCHAR = KWD(b'beginbfchar')
KEYWORD_ENDBFCHAR = KWD(b'endbfchar')
KEYWORD_BEGINNOTDEFRANGE = KWD(b'beginnotdefrange')
KEYWORD_ENDNOTDEFRANGE = KWD(b'endnotdefrange')
def do_keyword(self, pos, token): def do_keyword(self, pos, token):
name = token.name if token is self.KEYWORD_BEGINCMAP:
if name == 'begincmap':
self._in_cmap = True self._in_cmap = True
self.popall() self.popall()
return return
elif name == 'endcmap': elif token is self.KEYWORD_ENDCMAP:
self._in_cmap = False self._in_cmap = False
return return
if not self._in_cmap: if not self._in_cmap:
return return
#
if name == 'def': if token is self.KEYWORD_DEF:
try: try:
((_,k),(_,v)) = self.pop(2) ((_, k), (_, v)) = self.pop(2)
self.cmap.set_attr(literal_name(k), v) self.cmap.set_attr(literal_name(k), v)
except PSSyntaxError: except PSSyntaxError:
pass pass
return return
if name == 'usecmap': if token is self.KEYWORD_USECMAP:
try: try:
((_,cmapname),) = self.pop(1) ((_, cmapname),) = self.pop(1)
self.cmap.use_cmap(CMapDB.get_cmap(literal_name(cmapname))) self.cmap.use_cmap(CMapDB.get_cmap(literal_name(cmapname)))
except PSSyntaxError: except PSSyntaxError:
pass pass
@ -295,24 +330,26 @@ class CMapParser(PSStackParser):
pass pass
return return
if name == 'begincodespacerange': if token is self.KEYWORD_BEGINCODESPACERANGE:
self.popall() self.popall()
return return
if name == 'endcodespacerange': if token is self.KEYWORD_ENDCODESPACERANGE:
self.popall() self.popall()
return return
if name == 'begincidrange': if token is self.KEYWORD_BEGINCIDRANGE:
self.popall() self.popall()
return return
if name == 'endcidrange': if token is self.KEYWORD_ENDCIDRANGE:
objs = [ obj for (_,obj) in self.popall() ] objs = [obj for (__, obj) in self.popall()]
for (s,e,cid) in choplist(3, objs): for (s, e, cid) in choplist(3, objs):
if (not isinstance(s, str) or not isinstance(e, str) or if (not isinstance(s, bytes) or not isinstance(e, bytes) or
not isinstance(cid, int) or len(s) != len(e)): continue not isinstance(cid, int) or len(s) != len(e)):
continue
sprefix = s[:-4] sprefix = s[:-4]
eprefix = e[:-4] eprefix = e[:-4]
if sprefix != eprefix: continue if sprefix != eprefix:
continue
svar = s[-4:] svar = s[-4:]
evar = e[-4:] evar = e[-4:]
s1 = nunpack(svar) s1 = nunpack(svar)
@ -320,33 +357,29 @@ class CMapParser(PSStackParser):
vlen = len(svar) vlen = len(svar)
#assert s1 <= e1 #assert s1 <= e1
for i in range(e1-s1+1): for i in range(e1-s1+1):
x = sprefix+struct.pack('>L',s1+i)[-vlen:] x = sprefix+struct.pack('>L', s1+i)[-vlen:]
self.cmap.add_code2cid(x, cid+i) self.cmap.add_code2cid(x, cid+i)
return return
if name == 'begincidchar': if token is self.KEYWORD_BEGINCIDCHAR:
self.popall() self.popall()
return return
if name == 'endcidchar': if token is self.KEYWORD_ENDCIDCHAR:
objs = [ obj for (_,obj) in self.popall() ] objs = [obj for (__, obj) in self.popall()]
for (cid,code) in choplist(2, objs): for (cid, code) in choplist(2, objs):
if isinstance(code, str) and isinstance(cid, str): if isinstance(code, bytes) and isinstance(cid, bytes):
self.cmap.add_code2cid(code, nunpack(cid)) self.cmap.add_code2cid(code, nunpack(cid))
return return
if name == 'beginbfrange': if token is self.KEYWORD_BEGINBFRANGE:
self.popall() self.popall()
return return
if name == 'endbfrange': if token is self.KEYWORD_ENDBFRANGE:
objs = [ obj for (_,obj) in self.popall() ] objs = [obj for (__, obj) in self.popall()]
# These objects were hex numbers and have been parsed into a string. But what we want for (s, e, code) in choplist(3, objs):
# are bytes. Convert them.
# Oh wait, it seems that sometimes we have bytes...
tobytes = lambda o: (o.encode('ascii') if isinstance(o, str) else o)
objs = [tobytes(o) for o in objs]
for (s,e,code) in choplist(3, objs):
if (not isinstance(s, bytes) or not isinstance(e, bytes) or if (not isinstance(s, bytes) or not isinstance(e, bytes) or
len(s) != len(e)): continue len(s) != len(e)):
continue
s1 = nunpack(s) s1 = nunpack(s)
e1 = nunpack(e) e1 = nunpack(e)
#assert s1 <= e1 #assert s1 <= e1
@ -359,39 +392,211 @@ class CMapParser(PSStackParser):
prefix = code[:-4] prefix = code[:-4]
vlen = len(var) vlen = len(var)
for i in range(e1-s1+1): for i in range(e1-s1+1):
x = prefix+struct.pack('>L',base+i)[-vlen:] x = prefix+struct.pack('>L', base+i)[-vlen:]
self.cmap.add_cid2unichr(s1+i, x) self.cmap.add_cid2unichr(s1+i, x)
return return
if name == 'beginbfchar': if token is self.KEYWORD_BEGINBFCHAR:
self.popall() self.popall()
return return
if name == 'endbfchar': if token is self.KEYWORD_ENDBFCHAR:
objs = [ obj for (_,obj) in self.popall() ] objs = [obj for (__, obj) in self.popall()]
for (cid,code) in choplist(2, objs): for (cid, code) in choplist(2, objs):
if isinstance(cid, (str, bytes)) and isinstance(code, (str, bytes)): if isinstance(cid, bytes) and isinstance(code, bytes):
self.cmap.add_cid2unichr(nunpack(cid), code) self.cmap.add_cid2unichr(nunpack(cid), code)
return return
if name == 'beginnotdefrange': if token is self.KEYWORD_BEGINNOTDEFRANGE:
self.popall() self.popall()
return return
if name == 'endnotdefrange': if token is self.KEYWORD_ENDNOTDEFRANGE:
self.popall() self.popall()
return return
self.push((pos, token)) self.push((pos, token))
return
## CMapConverter
##
class CMapConverter:
def __init__(self, enc2codec={}):
self.enc2codec = enc2codec
self.code2cid = {} # {'cmapname': ...}
self.is_vertical = {}
self.cid2unichr_h = {} # {cid: unichr}
self.cid2unichr_v = {} # {cid: unichr}
return
def get_encs(self):
return self.code2cid.keys()
def get_maps(self, enc):
if enc.endswith('-H'):
(hmapenc, vmapenc) = (enc, None)
elif enc == 'H':
(hmapenc, vmapenc) = ('H', 'V')
else:
(hmapenc, vmapenc) = (enc+'-H', enc+'-V')
if hmapenc in self.code2cid:
hmap = self.code2cid[hmapenc]
else:
hmap = {}
self.code2cid[hmapenc] = hmap
vmap = None
if vmapenc:
self.is_vertical[vmapenc] = True
if vmapenc in self.code2cid:
vmap = self.code2cid[vmapenc]
else:
vmap = {}
self.code2cid[vmapenc] = vmap
return (hmap, vmap)
def load(self, fp):
encs = None
for line in fp:
(line,_,_) = line.strip().partition('#')
if not line: continue
values = line.split('\t')
if encs is None:
assert values[0] == 'CID'
encs = values
continue
def put(dmap, code, cid, force=False):
for b in code[:-1]:
if b in dmap:
dmap = dmap[b]
else:
d = {}
dmap[b] = d
dmap = d
b = code[-1]
if force or ((b not in dmap) or dmap[b] == cid):
dmap[b] = cid
return
def add(unimap, enc, code):
try:
codec = self.enc2codec[enc]
c = code.decode(codec, 'strict')
if len(c) == 1:
if c not in unimap:
unimap[c] = 0
unimap[c] += 1
except KeyError:
pass
except UnicodeError:
pass
return
def pick(unimap):
chars = sorted(
unimap.items(),
key=(lambda x:(x[1],-ord(x[0]))), reverse=True)
(c,_) = chars[0]
return c
cid = int(values[0])
unimap_h = {}
unimap_v = {}
for (enc,value) in zip(encs, values):
if enc == 'CID': continue
if value == '*': continue
# hcodes, vcodes: encoded bytes for each writing mode.
hcodes = []
vcodes = []
for code in value.split(','):
vertical = code.endswith('v')
if vertical:
code = code[:-1]
try:
code = codecs.decode(code, 'hex')
except:
code = bytes([int(code, 16)])
if vertical:
vcodes.append(code)
add(unimap_v, enc, code)
else:
hcodes.append(code)
add(unimap_h, enc, code)
# add cid to each map.
(hmap, vmap) = self.get_maps(enc)
if vcodes:
assert vmap is not None
for code in vcodes:
put(vmap, code, cid, True)
for code in hcodes:
put(hmap, code, cid, True)
else:
for code in hcodes:
put(hmap, code, cid)
put(vmap, code, cid)
# Determine the "most popular" candidate.
if unimap_h:
self.cid2unichr_h[cid] = pick(unimap_h)
if unimap_v or unimap_h:
self.cid2unichr_v[cid] = pick(unimap_v or unimap_h)
return
def dump_cmap(self, fp, enc):
data = dict(
IS_VERTICAL=self.is_vertical.get(enc, False),
CODE2CID=self.code2cid.get(enc),
)
fp.write(marshal.dumps(data))
return
def dump_unicodemap(self, fp):
data = dict(
CID2UNICHR_H=self.cid2unichr_h,
CID2UNICHR_V=self.cid2unichr_v,
)
fp.write(marshal.dumps(data))
return
# convert_cmap
def convert_cmap(outdir, regname, enc2codec, paths):
converter = CMapConverter(enc2codec)
for path in paths:
print('reading: %r...' % path)
with open(path) as fp:
converter.load(fp)
files = []
for enc in converter.get_encs():
fname = '%s.marshal.gz' % enc
path = os.path.join(outdir, fname)
print('writing: %r...' % path)
with gzip.open(path, 'wb') as fp:
converter.dump_cmap(fp, enc)
files.append(path)
fname = 'to-unicode-%s.marshal.gz' % regname
path = os.path.join(outdir, fname)
print('writing: %r...' % path)
with gzip.open(path, 'wb') as fp:
converter.dump_unicodemap(fp)
files.append(path)
return files
# test # test
def main(argv): def main(argv):
args = argv[1:] args = argv[1:]
for fname in args: for fname in args:
fp = open(fname, 'rb') with open(fname, 'rb') as fp:
cmap = FileUnicodeMap() cmap = FileUnicodeMap()
#cmap = FileCMap() #cmap = FileCMap()
CMapParser(cmap, fp).run() CMapParser(cmap, fp).run()
fp.close() cmap.dump()
cmap.dump() return
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main(sys.argv)) sys.exit(main(sys.argv))

View File

@ -1,20 +1,29 @@
import os.path #!/usr/bin/env python
import logging import logging
import re
from .pdfdevice import PDFTextDevice from .pdfdevice import PDFTextDevice
from .pdffont import PDFUnicodeNotDefined from .pdffont import PDFUnicodeNotDefined
from .pdftypes import LITERALS_DCT_DECODE from .layout import LTContainer
from .pdfcolor import LITERAL_DEVICE_GRAY, LITERAL_DEVICE_RGB from .layout import LTPage
from .layout import LTContainer, LTPage, LTText, LTLine, LTRect, LTCurve from .layout import LTText
from .layout import LTFigure, LTImage, LTChar, LTTextLine from .layout import LTLine
from .layout import LTTextBox, LTTextBoxVertical, LTTextGroup from .layout import LTRect
from .utils import apply_matrix_pt, mult_matrix from .layout import LTCurve
from .utils import htmlescape, bbox2str, create_bmp from .layout import LTFigure
from .layout import LTImage
from .layout import LTChar
logger = logging.getLogger(__name__) from .layout import LTTextLine
from .layout import LTTextBox
from .layout import LTTextBoxVertical
from .layout import LTTextGroup
from .utils import apply_matrix_pt
from .utils import mult_matrix
from .utils import q
from .utils import bbox2str
## PDFLayoutAnalyzer
##
class PDFLayoutAnalyzer(PDFTextDevice): class PDFLayoutAnalyzer(PDFTextDevice):
def __init__(self, rsrcmgr, pageno=1, laparams=None): def __init__(self, rsrcmgr, pageno=1, laparams=None):
@ -22,13 +31,15 @@ class PDFLayoutAnalyzer(PDFTextDevice):
self.pageno = pageno self.pageno = pageno
self.laparams = laparams self.laparams = laparams
self._stack = [] self._stack = []
return
def begin_page(self, page, ctm): def begin_page(self, page, ctm):
(x0,y0,x1,y1) = page.mediabox (x0, y0, x1, y1) = page.mediabox
(x0,y0) = apply_matrix_pt(ctm, (x0,y0)) (x0, y0) = apply_matrix_pt(ctm, (x0, y0))
(x1,y1) = apply_matrix_pt(ctm, (x1,y1)) (x1, y1) = apply_matrix_pt(ctm, (x1, y1))
mediabox = (0, 0, abs(x0-x1), abs(y0-y1)) mediabox = (0, 0, abs(x0-x1), abs(y0-y1))
self.cur_item = LTPage(self.pageno, mediabox) self.cur_item = LTPage(self.pageno, mediabox)
return
def end_page(self, page): def end_page(self, page):
assert not self._stack assert not self._stack
@ -37,16 +48,19 @@ class PDFLayoutAnalyzer(PDFTextDevice):
self.cur_item.analyze(self.laparams) self.cur_item.analyze(self.laparams)
self.pageno += 1 self.pageno += 1
self.receive_layout(self.cur_item) self.receive_layout(self.cur_item)
return
def begin_figure(self, name, bbox, matrix): def begin_figure(self, name, bbox, matrix):
self._stack.append(self.cur_item) self._stack.append(self.cur_item)
self.cur_item = LTFigure(name, bbox, mult_matrix(matrix, self.ctm)) self.cur_item = LTFigure(name, bbox, mult_matrix(matrix, self.ctm))
return
def end_figure(self, _): def end_figure(self, _):
fig = self.cur_item fig = self.cur_item
assert isinstance(self.cur_item, LTFigure) assert isinstance(self.cur_item, LTFigure)
self.cur_item = self._stack.pop() self.cur_item = self._stack.pop()
self.cur_item.add(fig) self.cur_item.add(fig)
return
def render_image(self, name, stream): def render_image(self, name, stream):
assert isinstance(self.cur_item, LTFigure) assert isinstance(self.cur_item, LTFigure)
@ -54,31 +68,32 @@ class PDFLayoutAnalyzer(PDFTextDevice):
(self.cur_item.x0, self.cur_item.y0, (self.cur_item.x0, self.cur_item.y0,
self.cur_item.x1, self.cur_item.y1)) self.cur_item.x1, self.cur_item.y1))
self.cur_item.add(item) self.cur_item.add(item)
return
def paint_path(self, gstate, stroke, fill, evenodd, path): def paint_path(self, gstate, stroke, fill, evenodd, path):
shape = ''.join(x[0] for x in path) shape = ''.join(x[0] for x in path)
if shape == 'ml': if shape == 'ml':
# horizontal/vertical line # horizontal/vertical line
(_,x0,y0) = path[0] (_, x0, y0) = path[0]
(_,x1,y1) = path[1] (_, x1, y1) = path[1]
(x0,y0) = apply_matrix_pt(self.ctm, (x0,y0)) (x0, y0) = apply_matrix_pt(self.ctm, (x0, y0))
(x1,y1) = apply_matrix_pt(self.ctm, (x1,y1)) (x1, y1) = apply_matrix_pt(self.ctm, (x1, y1))
if x0 == x1 or y0 == y1: if x0 == x1 or y0 == y1:
self.cur_item.add(LTLine(gstate.linewidth, (x0,y0), (x1,y1))) self.cur_item.add(LTLine(gstate.linewidth, (x0, y0), (x1, y1)))
return return
if shape == 'mlllh': if shape == 'mlllh':
# rectangle # rectangle
(_,x0,y0) = path[0] (_, x0, y0) = path[0]
(_,x1,y1) = path[1] (_, x1, y1) = path[1]
(_,x2,y2) = path[2] (_, x2, y2) = path[2]
(_,x3,y3) = path[3] (_, x3, y3) = path[3]
(x0,y0) = apply_matrix_pt(self.ctm, (x0,y0)) (x0, y0) = apply_matrix_pt(self.ctm, (x0, y0))
(x1,y1) = apply_matrix_pt(self.ctm, (x1,y1)) (x1, y1) = apply_matrix_pt(self.ctm, (x1, y1))
(x2,y2) = apply_matrix_pt(self.ctm, (x2,y2)) (x2, y2) = apply_matrix_pt(self.ctm, (x2, y2))
(x3,y3) = apply_matrix_pt(self.ctm, (x3,y3)) (x3, y3) = apply_matrix_pt(self.ctm, (x3, y3))
if ((x0 == x1 and y1 == y2 and x2 == x3 and y3 == y0) or if ((x0 == x1 and y1 == y2 and x2 == x3 and y3 == y0) or
(y0 == y1 and x1 == x2 and y2 == y3 and x3 == x0)): (y0 == y1 and x1 == x2 and y2 == y3 and x3 == x0)):
self.cur_item.add(LTRect(gstate.linewidth, (x0,y0,x2,y2))) self.cur_item.add(LTRect(gstate.linewidth, (x0, y0, x2, y2)))
return return
# other shapes # other shapes
pts = [] pts = []
@ -86,6 +101,7 @@ class PDFLayoutAnalyzer(PDFTextDevice):
for i in range(1, len(p), 2): for i in range(1, len(p), 2):
pts.append(apply_matrix_pt(self.ctm, (p[i], p[i+1]))) pts.append(apply_matrix_pt(self.ctm, (p[i], p[i+1])))
self.cur_item.add(LTCurve(gstate.linewidth, pts)) self.cur_item.add(LTCurve(gstate.linewidth, pts))
return
def render_char(self, matrix, font, fontsize, scaling, rise, cid): def render_char(self, matrix, font, fontsize, scaling, rise, cid):
try: try:
@ -100,21 +116,25 @@ class PDFLayoutAnalyzer(PDFTextDevice):
return item.adv return item.adv
def handle_undefined_char(self, font, cid): def handle_undefined_char(self, font, cid):
logger.warning('undefined: %r, %r', font, cid) logging.info('undefined: %r, %r' % (font, cid))
return '(cid:%d)' % cid return f'(cid:{cid})'
def receive_layout(self, ltpage): def receive_layout(self, ltpage):
pass return
## PDFPageAggregator
##
class PDFPageAggregator(PDFLayoutAnalyzer): class PDFPageAggregator(PDFLayoutAnalyzer):
def __init__(self, rsrcmgr, pageno=1, laparams=None): def __init__(self, rsrcmgr, pageno=1, laparams=None):
PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams) PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
self.result = None self.result = None
return
def receive_layout(self, ltpage): def receive_layout(self, ltpage):
self.result = ltpage self.result = ltpage
return
def get_result(self): def get_result(self):
return self.result return self.result
@ -123,45 +143,27 @@ class PDFPageAggregator(PDFLayoutAnalyzer):
## PDFConverter ## PDFConverter
## ##
class PDFConverter(PDFLayoutAnalyzer): class PDFConverter(PDFLayoutAnalyzer):
# outfp is an fp opened in *text* mode
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None): def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None):
PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams) PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
self.outfp = outfp self.outfp = outfp
return
def write_image(self, image):
stream = image.stream
filters = stream.get_filters()
if len(filters) == 1 and filters[0] in LITERALS_DCT_DECODE:
ext = '.jpg'
data = stream.get_rawdata()
elif image.colorspace is LITERAL_DEVICE_RGB:
ext = '.bmp'
data = create_bmp(stream.get_data(), stream.bits*3, image.width, image.height)
elif image.colorspace is LITERAL_DEVICE_GRAY:
ext = '.bmp'
data = create_bmp(stream.get_data(), stream.bits, image.width, image.height)
else:
ext = '.img'
data = stream.get_data()
name = image.name+ext
path = os.path.join(self.outdir, name)
fp = file(path, 'wb')
fp.write(data)
fp.close()
return name
## TextConverter ## TextConverter
## ##
class TextConverter(PDFConverter): class TextConverter(PDFConverter):
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None, def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None,
showpageno=False): showpageno=False, imagewriter=None):
PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams) PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams)
self.showpageno = showpageno self.showpageno = showpageno
self.imagewriter = imagewriter
return
def write_text(self, text): def write_text(self, text):
self.outfp.write(text) self.outfp.write(text)
return
def receive_layout(self, ltpage): def receive_layout(self, ltpage):
def render(item): def render(item):
@ -172,18 +174,26 @@ class TextConverter(PDFConverter):
self.write_text(item.get_text()) self.write_text(item.get_text())
if isinstance(item, LTTextBox): if isinstance(item, LTTextBox):
self.write_text('\n') self.write_text('\n')
elif isinstance(item, LTImage):
if self.imagewriter is not None:
self.imagewriter.export_image(item)
if self.showpageno: if self.showpageno:
self.write_text('Page %s\n' % ltpage.pageid) self.write_text('Page %s\n' % ltpage.pageid)
render(ltpage) render(ltpage)
self.write_text('\f') self.write_text('\f')
return
# Some dummy functions to save memory/CPU when all that is wanted is text. # Some dummy functions to save memory/CPU when all that is wanted
# This stops all the image and drawing ouput from being recorded and taking # is text. This stops all the image and drawing output from being
# up RAM. # recorded and taking up RAM.
def render_image(self, name, stream): def render_image(self, name, stream):
pass if self.imagewriter is None:
return
PDFConverter.render_image(self, name, stream)
return
def paint_path(self, gstate, stroke, fill, evenodd, path): def paint_path(self, gstate, stroke, fill, evenodd, path):
pass return
## HTMLConverter ## HTMLConverter
@ -198,26 +208,25 @@ class HTMLConverter(PDFConverter):
'textgroup': 'red', 'textgroup': 'red',
'curve': 'black', 'curve': 'black',
'page': 'gray', 'page': 'gray',
} }
TEXT_COLORS = { TEXT_COLORS = {
'textbox': 'blue', 'textbox': 'blue',
'char': 'black', 'char': 'black',
} }
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None, def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None,
scale=1, fontscale=0.7, layoutmode='normal', showpageno=True, scale=1, fontscale=1.0, layoutmode='normal', showpageno=True,
pagemargin=50, outdir=None, pagemargin=50, imagewriter=None, debug=0,
rect_colors={'curve':'black', 'page':'gray'}, rect_colors={'curve': 'black', 'page': 'gray'},
text_colors={'char':'black'}, text_colors={'char': 'black'}):
debug=False):
PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams) PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams)
self.scale = scale self.scale = scale
self.fontscale = fontscale self.fontscale = fontscale
self.layoutmode = layoutmode self.layoutmode = layoutmode
self.showpageno = showpageno self.showpageno = showpageno
self.pagemargin = pagemargin self.pagemargin = pagemargin
self.outdir = outdir self.imagewriter = imagewriter
self.rect_colors = rect_colors self.rect_colors = rect_colors
self.text_colors = text_colors self.text_colors = text_colors
if debug: if debug:
@ -227,22 +236,27 @@ class HTMLConverter(PDFConverter):
self._font = None self._font = None
self._fontstack = [] self._fontstack = []
self.write_header() self.write_header()
return
def write(self, text): def write(self, text):
self.outfp.write(text) self.outfp.write(text)
return
def write_header(self): def write_header(self):
self.write('<html><head>\n') self.write('<html><head>\n')
self.write('<meta http-equiv="Content-Type" content="text/html; charset=%s">\n' % self.outfp.encoding) self.write('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n')
self.write('</head><body>\n') self.write('</head><body>\n')
return
def write_footer(self): def write_footer(self):
self.write('<div style="position:absolute; top:0px;">Page: %s</div>\n' % self.write('<div style="position:absolute; top:0px;">Page: %s</div>\n' %
', '.join('<a href="#%s">%s</a>' % (i,i) for i in range(1,self.pageno))) ', '.join('<a href="#%s">%s</a>' % (i, i) for i in range(1, self.pageno)))
self.write('</body></html>\n') self.write('</body></html>\n')
return
def write_text(self, text): def write_text(self, text):
self.write(htmlescape(text, self.outfp.encoding)) self.write(q(text))
return
def place_rect(self, color, borderwidth, x, y, w, h): def place_rect(self, color, borderwidth, x, y, w, h):
color = self.rect_colors.get(color) color = self.rect_colors.get(color)
@ -252,18 +266,21 @@ class HTMLConverter(PDFConverter):
(color, borderwidth, (color, borderwidth,
x*self.scale, (self._yoffset-y)*self.scale, x*self.scale, (self._yoffset-y)*self.scale,
w*self.scale, h*self.scale)) w*self.scale, h*self.scale))
return
def place_border(self, color, borderwidth, item): def place_border(self, color, borderwidth, item):
self.place_rect(color, borderwidth, item.x0, item.y1, item.width, item.height) self.place_rect(color, borderwidth, item.x0, item.y1, item.width, item.height)
return
def place_image(self, item, borderwidth, x, y, w, h): def place_image(self, item, borderwidth, x, y, w, h):
if self.outdir is not None: if self.imagewriter is not None:
name = self.write_image(item) name = self.imagewriter.export_image(item)
self.write('<img src="%s" border="%d" style="position:absolute; left:%dpx; top:%dpx;" ' self.write('<img src="%s" border="%d" style="position:absolute; left:%dpx; top:%dpx;" '
'width="%d" height="%d" />\n' % 'width="%d" height="%d" />\n' %
(enc(name), borderwidth, (q(name), borderwidth,
x*self.scale, (self._yoffset-y)*self.scale, x*self.scale, (self._yoffset-y)*self.scale,
w*self.scale, h*self.scale)) w*self.scale, h*self.scale))
return
def place_text(self, color, text, x, y, size): def place_text(self, color, text, x, y, size):
color = self.text_colors.get(color) color = self.text_colors.get(color)
@ -272,8 +289,9 @@ class HTMLConverter(PDFConverter):
(color, x*self.scale, (self._yoffset-y)*self.scale, size*self.scale*self.fontscale)) (color, x*self.scale, (self._yoffset-y)*self.scale, size*self.scale*self.fontscale))
self.write_text(text) self.write_text(text)
self.write('</span>\n') self.write('</span>\n')
return
def begin_textbox(self, color, borderwidth, x, y, w, h, writing_mode): def begin_div(self, color, borderwidth, x, y, w, h, writing_mode=False):
self._fontstack.append(self._font) self._fontstack.append(self._font)
self._font = None self._font = None
self.write('<div style="position:absolute; border: %s %dpx solid; writing-mode:%s; ' self.write('<div style="position:absolute; border: %s %dpx solid; writing-mode:%s; '
@ -281,25 +299,29 @@ class HTMLConverter(PDFConverter):
(color, borderwidth, writing_mode, (color, borderwidth, writing_mode,
x*self.scale, (self._yoffset-y)*self.scale, x*self.scale, (self._yoffset-y)*self.scale,
w*self.scale, h*self.scale)) w*self.scale, h*self.scale))
return
def end_div(self, color):
if self._font is not None:
self.write('</span>')
self._font = self._fontstack.pop()
self.write('</div>')
return
def put_text(self, text, fontname, fontsize): def put_text(self, text, fontname, fontsize):
font = (fontname, fontsize) font = (fontname, fontsize)
if font != self._font: if font != self._font:
if self._font is not None: if self._font is not None:
self.write('</span>') self.write('</span>')
self.write('<span style="font-family: %s; font-size:%dpx">' % self.write('<span style="font-family: %s; font-size:%dpx">' %
(fontname, fontsize * self.scale * self.fontscale)) (q(fontname), fontsize * self.scale * self.fontscale))
self._font = font self._font = font
self.write_text(text) self.write_text(text)
return
def put_newline(self): def put_newline(self):
self.write('<br>') self.write('<br>')
return
def end_textbox(self, color):
if self._font is not None:
self.write('</span>')
self._font = self._fontstack.pop()
self.write('</div>')
def receive_layout(self, ltpage): def receive_layout(self, ltpage):
def show_group(item): def show_group(item):
@ -307,7 +329,8 @@ class HTMLConverter(PDFConverter):
self.place_border('textgroup', 1, item) self.place_border('textgroup', 1, item)
for child in item: for child in item:
show_group(child) show_group(child)
return
def render(item): def render(item):
if isinstance(item, LTPage): if isinstance(item, LTPage):
self._yoffset += item.y1 self._yoffset += item.y1
@ -324,9 +347,10 @@ class HTMLConverter(PDFConverter):
elif isinstance(item, LTCurve): elif isinstance(item, LTCurve):
self.place_border('curve', 1, item) self.place_border('curve', 1, item)
elif isinstance(item, LTFigure): elif isinstance(item, LTFigure):
self.place_border('figure', 1, item) self.begin_div('figure', 1, item.x0, item.y1, item.width, item.height)
for child in item: for child in item:
render(child) render(child)
self.end_div('figure')
elif isinstance(item, LTImage): elif isinstance(item, LTImage):
self.place_image(item, 1, item.x0, item.y1, item.width, item.height) self.place_image(item, 1, item.x0, item.y1, item.width, item.height)
else: else:
@ -350,39 +374,53 @@ class HTMLConverter(PDFConverter):
if self.layoutmode != 'loose': if self.layoutmode != 'loose':
self.put_newline() self.put_newline()
elif isinstance(item, LTTextBox): elif isinstance(item, LTTextBox):
self.begin_textbox('textbox', 1, item.x0, item.y1, item.width, item.height, self.begin_div('textbox', 1, item.x0, item.y1, item.width, item.height,
item.get_writing_mode()) item.get_writing_mode())
for child in item: for child in item:
render(child) render(child)
self.end_textbox('textbox') self.end_div('textbox')
elif isinstance(item, LTChar): elif isinstance(item, LTChar):
self.put_text(item.get_text(), item.fontname, item.size) self.put_text(item.get_text(), item.fontname, item.size)
elif isinstance(item, LTText): elif isinstance(item, LTText):
self.write_text(item.get_text()) self.write_text(item.get_text())
return
render(ltpage) render(ltpage)
self._yoffset += self.pagemargin self._yoffset += self.pagemargin
return
def close(self): def close(self):
self.write_footer() self.write_footer()
return
## XMLConverter
##
class XMLConverter(PDFConverter): class XMLConverter(PDFConverter):
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None, outdir=None): CONTROL = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]')
def __init__(self, rsrcmgr, outfp, pageno=1,
laparams=None, imagewriter=None, stripcontrol=False):
PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams) PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams)
self.outdir = outdir self.imagewriter = imagewriter
self.stripcontrol = stripcontrol
self.write_header() self.write_header()
return
def write_header(self): def write_header(self):
self.outfp.write('<?xml version="1.0" encoding="%s" ?>\n' % self.outfp.encoding) self.outfp.write('<?xml version="1.0" encoding="utf-8" ?>\n')
self.outfp.write('<pages>\n') self.outfp.write('<pages>\n')
return
def write_footer(self): def write_footer(self):
self.outfp.write('</pages>\n') self.outfp.write('</pages>\n')
return
def write_text(self, text): def write_text(self, text):
self.outfp.write(htmlescape(text, self.outfp.encoding)) if self.stripcontrol:
text = self.CONTROL.sub(u'', text)
self.outfp.write(q(text))
return
def receive_layout(self, ltpage): def receive_layout(self, ltpage):
def show_group(item): def show_group(item):
@ -394,7 +432,8 @@ class XMLConverter(PDFConverter):
for child in item: for child in item:
show_group(child) show_group(child)
self.outfp.write('</textgroup>\n') self.outfp.write('</textgroup>\n')
return
def render(item): def render(item):
if isinstance(item, LTPage): if isinstance(item, LTPage):
self.outfp.write('<page id="%s" bbox="%s" rotate="%d">\n' % self.outfp.write('<page id="%s" bbox="%s" rotate="%d">\n' %
@ -438,23 +477,25 @@ class XMLConverter(PDFConverter):
self.outfp.write('</textbox>\n') self.outfp.write('</textbox>\n')
elif isinstance(item, LTChar): elif isinstance(item, LTChar):
self.outfp.write('<text font="%s" bbox="%s" size="%.3f">' % self.outfp.write('<text font="%s" bbox="%s" size="%.3f">' %
(htmlescape(item.fontname), bbox2str(item.bbox), item.size)) (q(item.fontname), bbox2str(item.bbox), item.size))
self.write_text(item.get_text()) self.write_text(item.get_text())
self.outfp.write('</text>\n') self.outfp.write('</text>\n')
elif isinstance(item, LTText): elif isinstance(item, LTText):
self.outfp.write('<text>%s</text>\n' % item.get_text()) self.outfp.write('<text>%s</text>\n' % item.get_text())
elif isinstance(item, LTImage): elif isinstance(item, LTImage):
if self.outdir: if self.imagewriter is not None:
name = self.write_image(item) name = self.imagewriter.export_image(item)
self.outfp.write('<image src="%s" width="%d" height="%d" />\n' % self.outfp.write('<image src="%s" width="%d" height="%d" />\n' %
(enc(name), item.width, item.height)) (q(name), item.width, item.height))
else: else:
self.outfp.write('<image width="%d" height="%d" />\n' % self.outfp.write('<image width="%d" height="%d" />\n' %
(item.width, item.height)) (item.width, item.height))
else: else:
assert 0, item assert 0, item
return
render(ltpage) render(ltpage)
return
def close(self): def close(self):
self.write_footer() self.write_footer()
return

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
import re import re
from .psparser import PSLiteral from .psparser import PSLiteral
from .glyphlist import glyphname2unicode from .glyphlist import glyphname2unicode
@ -5,38 +6,49 @@ from .latin_enc import ENCODING
STRIP_NAME = re.compile(r'[0-9]+') STRIP_NAME = re.compile(r'[0-9]+')
## name2unicode
##
def name2unicode(name): def name2unicode(name):
"""Converts Adobe glyph names to Unicode numbers.""" """Converts Adobe glyph names to Unicode numbers."""
if name in glyphname2unicode: if name in glyphname2unicode:
return glyphname2unicode[name] return glyphname2unicode[name]
m = STRIP_NAME.search(name) m = STRIP_NAME.search(name)
if not m: raise KeyError(name) if not m:
raise KeyError(name)
return chr(int(m.group(0))) return chr(int(m.group(0)))
## EncodingDB
##
class EncodingDB: class EncodingDB:
std2unicode = {} std2unicode = {}
mac2unicode = {} mac2unicode = {}
win2unicode = {} win2unicode = {}
pdf2unicode = {} pdf2unicode = {}
for (name,std,mac,win,pdf) in ENCODING: for (name, std, mac, win, pdf) in ENCODING:
c = name2unicode(name) c = name2unicode(name)
if std: std2unicode[std] = c if std:
if mac: mac2unicode[mac] = c std2unicode[std] = c
if win: win2unicode[win] = c if mac:
if pdf: pdf2unicode[pdf] = c mac2unicode[mac] = c
if win:
win2unicode[win] = c
if pdf:
pdf2unicode[pdf] = c
encodings = { encodings = {
'StandardEncoding': std2unicode, 'StandardEncoding': std2unicode,
'MacRomanEncoding': mac2unicode, 'MacRomanEncoding': mac2unicode,
'WinAnsiEncoding': win2unicode, 'WinAnsiEncoding': win2unicode,
'PDFDocEncoding': pdf2unicode, 'PDFDocEncoding': pdf2unicode,
} }
@classmethod @classmethod
def get_encoding(cls, name, diff=None): def get_encoding(klass, name, diff=None):
cid2unicode = cls.encodings.get(name, cls.std2unicode) cid2unicode = klass.encodings.get(name, klass.std2unicode)
if diff: if diff:
cid2unicode = cid2unicode.copy() cid2unicode = cid2unicode.copy()
cid = 0 cid = 0

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
""" Font metrics for the Adobe core 14 fonts. """ Font metrics for the Adobe core 14 fonts.
@ -8,14 +8,13 @@ written with a proportional font.
The following data were extracted from the AFM files: The following data were extracted from the AFM files:
http://www.ctan.org/tex-archive/fonts/adobe/afm/ http://www.ctan.org/tex-archive/fonts/adobe/afm/
"""
"""
### BEGIN Verbatim copy of the license part ### BEGIN Verbatim copy of the license part
# #
# Adobe Core 35 AFM Files with 229 Glyph Entries - ReadMe # Adobe Core 35 AFM Files with 314 Glyph Entries - ReadMe
# #
# This file and the 35 PostScript(R) AFM files it accompanies may be # This file and the 35 PostScript(R) AFM files it accompanies may be
# used, copied, and distributed for any purpose and without charge, # used, copied, and distributed for any purpose and without charge,
@ -30,18 +29,18 @@ The following data were extracted from the AFM files:
### END Verbatim copy of the license part ### END Verbatim copy of the license part
FONT_METRICS = { FONT_METRICS = {
'Courier-Oblique': ({'FontName': 'Courier-Oblique', 'Descent': -194.0, 'FontBBox': (-49.0, -249.0, 749.0, 803.0), 'FontWeight': 'Medium', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': -11.0, 'Ascent': 627.0}, {32: 600, 33: 600, 34: 600, 35: 600, 36: 600, 37: 600, 38: 600, 39: 600, 40: 600, 41: 600, 42: 600, 43: 600, 44: 600, 45: 600, 46: 600, 47: 600, 48: 600, 49: 600, 50: 600, 51: 600, 52: 600, 53: 600, 54: 600, 55: 600, 56: 600, 57: 600, 58: 600, 59: 600, 60: 600, 61: 600, 62: 600, 63: 600, 64: 600, 65: 600, 66: 600, 67: 600, 68: 600, 69: 600, 70: 600, 71: 600, 72: 600, 73: 600, 74: 600, 75: 600, 76: 600, 77: 600, 78: 600, 79: 600, 80: 600, 81: 600, 82: 600, 83: 600, 84: 600, 85: 600, 86: 600, 87: 600, 88: 600, 89: 600, 90: 600, 91: 600, 92: 600, 93: 600, 94: 600, 95: 600, 96: 600, 97: 600, 98: 600, 99: 600, 100: 600, 101: 600, 102: 600, 103: 600, 104: 600, 105: 600, 106: 600, 107: 600, 108: 600, 109: 600, 110: 600, 111: 600, 112: 600, 113: 600, 114: 600, 115: 600, 116: 600, 117: 600, 118: 600, 119: 600, 120: 600, 121: 600, 122: 600, 123: 600, 124: 600, 125: 600, 126: 600, 161: 600, 162: 600, 163: 600, 164: 600, 165: 600, 166: 600, 167: 600, 168: 600, 169: 600, 170: 600, 171: 600, 172: 600, 173: 600, 174: 600, 175: 600, 177: 600, 178: 600, 179: 600, 180: 600, 182: 600, 183: 600, 184: 600, 185: 600, 186: 600, 187: 600, 188: 600, 189: 600, 191: 600, 193: 600, 194: 600, 195: 600, 196: 600, 197: 600, 198: 600, 199: 600, 200: 600, 202: 600, 203: 600, 205: 600, 206: 600, 207: 600, 208: 600, 225: 600, 227: 600, 232: 600, 233: 600, 234: 600, 235: 600, 241: 600, 245: 600, 248: 600, 249: 600, 250: 600, 251: 600}), 'Courier': ({'FontName': 'Courier', 'Descent': -194.0, 'FontBBox': (-6.0, -249.0, 639.0, 803.0), 'FontWeight': 'Medium', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': 0.0, 'Ascent': 627.0}, {u' ': 600, u'!': 600, u'"': 600, u'#': 600, u'$': 600, u'%': 600, u'&': 600, u"'": 600, u'(': 600, u')': 600, u'*': 600, u'+': 600, u',': 600, u'-': 600, u'.': 600, u'/': 600, u'0': 600, u'1': 600, u'2': 600, u'3': 600, u'4': 600, u'5': 600, u'6': 600, u'7': 600, u'8': 600, u'9': 600, u':': 600, u';': 600, u'<': 600, u'=': 600, u'>': 600, u'?': 600, u'@': 600, u'A': 600, u'B': 600, u'C': 600, u'D': 600, u'E': 600, u'F': 600, u'G': 600, u'H': 600, u'I': 600, u'J': 600, u'K': 600, u'L': 600, u'M': 600, u'N': 600, u'O': 600, u'P': 600, u'Q': 600, u'R': 600, u'S': 600, u'T': 600, u'U': 600, u'V': 600, u'W': 600, u'X': 600, u'Y': 600, u'Z': 600, u'[': 600, u'\\': 600, u']': 600, u'^': 600, u'_': 600, u'`': 600, u'a': 600, u'b': 600, u'c': 600, u'd': 600, u'e': 600, u'f': 600, u'g': 600, u'h': 600, u'i': 600, u'j': 600, u'k': 600, u'l': 600, u'm': 600, u'n': 600, u'o': 600, u'p': 600, u'q': 600, u'r': 600, u's': 600, u't': 600, u'u': 600, u'v': 600, u'w': 600, u'x': 600, u'y': 600, u'z': 600, u'{': 600, u'|': 600, u'}': 600, u'~': 600, u'\xa1': 600, u'\xa2': 600, u'\xa3': 600, u'\xa4': 600, u'\xa5': 600, u'\xa6': 600, u'\xa7': 600, u'\xa8': 600, u'\xa9': 600, u'\xaa': 600, u'\xab': 600, u'\xac': 600, u'\xae': 600, u'\xaf': 600, u'\xb0': 600, u'\xb1': 600, u'\xb2': 600, u'\xb3': 600, u'\xb4': 600, u'\xb5': 600, u'\xb6': 600, u'\xb7': 600, u'\xb8': 600, u'\xb9': 600, u'\xba': 600, u'\xbb': 600, u'\xbc': 600, u'\xbd': 600, u'\xbe': 600, u'\xbf': 600, u'\xc0': 600, u'\xc1': 600, u'\xc2': 600, u'\xc3': 600, u'\xc4': 600, u'\xc5': 600, u'\xc6': 600, u'\xc7': 600, u'\xc8': 600, u'\xc9': 600, u'\xca': 600, u'\xcb': 600, u'\xcc': 600, u'\xcd': 600, u'\xce': 600, u'\xcf': 600, u'\xd0': 600, u'\xd1': 600, u'\xd2': 600, u'\xd3': 600, u'\xd4': 600, u'\xd5': 600, u'\xd6': 600, u'\xd7': 600, u'\xd8': 600, u'\xd9': 600, u'\xda': 600, u'\xdb': 600, u'\xdc': 600, u'\xdd': 600, u'\xde': 600, u'\xdf': 600, u'\xe0': 600, u'\xe1': 600, u'\xe2': 600, u'\xe3': 600, u'\xe4': 600, u'\xe5': 600, u'\xe6': 600, u'\xe7': 600, u'\xe8': 600, u'\xe9': 600, u'\xea': 600, u'\xeb': 600, u'\xec': 600, u'\xed': 600, u'\xee': 600, u'\xef': 600, u'\xf0': 600, u'\xf1': 600, u'\xf2': 600, u'\xf3': 600, u'\xf4': 600, u'\xf5': 600, u'\xf6': 600, u'\xf7': 600, u'\xf8': 600, u'\xf9': 600, u'\xfa': 600, u'\xfb': 600, u'\xfc': 600, u'\xfd': 600, u'\xfe': 600, u'\xff': 600, u'\u0100': 600, u'\u0101': 600, u'\u0102': 600, u'\u0103': 600, u'\u0104': 600, u'\u0105': 600, u'\u0106': 600, u'\u0107': 600, u'\u010c': 600, u'\u010d': 600, u'\u010e': 600, u'\u010f': 600, u'\u0110': 600, u'\u0111': 600, u'\u0112': 600, u'\u0113': 600, u'\u0116': 600, u'\u0117': 600, u'\u0118': 600, u'\u0119': 600, u'\u011a': 600, u'\u011b': 600, u'\u011e': 600, u'\u011f': 600, u'\u0122': 600, u'\u0123': 600, u'\u012a': 600, u'\u012b': 600, u'\u012e': 600, u'\u012f': 600, u'\u0130': 600, u'\u0131': 600, u'\u0136': 600, u'\u0137': 600, u'\u0139': 600, u'\u013a': 600, u'\u013b': 600, u'\u013c': 600, u'\u013d': 600, u'\u013e': 600, u'\u0141': 600, u'\u0142': 600, u'\u0143': 600, u'\u0144': 600, u'\u0145': 600, u'\u0146': 600, u'\u0147': 600, u'\u0148': 600, u'\u014c': 600, u'\u014d': 600, u'\u0150': 600, u'\u0151': 600, u'\u0152': 600, u'\u0153': 600, u'\u0154': 600, u'\u0155': 600, u'\u0156': 600, u'\u0157': 600, u'\u0158': 600, u'\u0159': 600, u'\u015a': 600, u'\u015b': 600, u'\u015e': 600, u'\u015f': 600, u'\u0160': 600, u'\u0161': 600, u'\u0162': 600, u'\u0163': 600, u'\u0164': 600, u'\u0165': 600, u'\u016a': 600, u'\u016b': 600, u'\u016e': 600, u'\u016f': 600, u'\u0170': 600, u'\u0171': 600, u'\u0172': 600, u'\u0173': 600, u'\u0178': 600, u'\u0179': 600, u'\u017a': 600, u'\u017b': 600, u'\u017c': 600, u'\u017d': 600, u'\u017e': 600, u'\u0192': 600, u'\u0218': 600, u'\u0219': 600, u'\u02c6': 600, u'\u02c7': 600, u'\u02d8': 600, u'\u02d9': 600, u'\u02da': 600, u'\u02db': 600, u'\u02dc': 600, u'\u02dd': 600, u'\u2013': 600, u'\u2014': 600, u'\u2018': 600, u'\u2019': 600, u'\u201a': 600, u'\u201c': 600, u'\u201d': 600, u'\u201e': 600, u'\u2020': 600, u'\u2021': 600, u'\u2022': 600, u'\u2026': 600, u'\u2030': 600, u'\u2039': 600, u'\u203a': 600, u'\u2044': 600, u'\u2122': 600, u'\u2202': 600, u'\u2206': 600, u'\u2211': 600, u'\u2212': 600, u'\u221a': 600, u'\u2260': 600, u'\u2264': 600, u'\u2265': 600, u'\u25ca': 600, u'\uf6c3': 600, u'\ufb01': 600, u'\ufb02': 600}),
'Times-BoldItalic': ({'FontName': 'Times-BoldItalic', 'Descent': -217.0, 'FontBBox': (-200.0, -218.0, 996.0, 921.0), 'FontWeight': 'Bold', 'CapHeight': 669.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 462.0, 'ItalicAngle': -15.0, 'Ascent': 683.0}, {32: 250, 33: 389, 34: 555, 35: 500, 36: 500, 37: 833, 38: 778, 39: 333, 40: 333, 41: 333, 42: 500, 43: 570, 44: 250, 45: 333, 46: 250, 47: 278, 48: 500, 49: 500, 50: 500, 51: 500, 52: 500, 53: 500, 54: 500, 55: 500, 56: 500, 57: 500, 58: 333, 59: 333, 60: 570, 61: 570, 62: 570, 63: 500, 64: 832, 65: 667, 66: 667, 67: 667, 68: 722, 69: 667, 70: 667, 71: 722, 72: 778, 73: 389, 74: 500, 75: 667, 76: 611, 77: 889, 78: 722, 79: 722, 80: 611, 81: 722, 82: 667, 83: 556, 84: 611, 85: 722, 86: 667, 87: 889, 88: 667, 89: 611, 90: 611, 91: 333, 92: 278, 93: 333, 94: 570, 95: 500, 96: 333, 97: 500, 98: 500, 99: 444, 100: 500, 101: 444, 102: 333, 103: 500, 104: 556, 105: 278, 106: 278, 107: 500, 108: 278, 109: 778, 110: 556, 111: 500, 112: 500, 113: 500, 114: 389, 115: 389, 116: 278, 117: 556, 118: 444, 119: 667, 120: 500, 121: 444, 122: 389, 123: 348, 124: 220, 125: 348, 126: 570, 161: 389, 162: 500, 163: 500, 164: 167, 165: 500, 166: 500, 167: 500, 168: 500, 169: 278, 170: 500, 171: 500, 172: 333, 173: 333, 174: 556, 175: 556, 177: 500, 178: 500, 179: 500, 180: 250, 182: 500, 183: 350, 184: 333, 185: 500, 186: 500, 187: 500, 188: 1000, 189: 1000, 191: 500, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 944, 227: 266, 232: 611, 233: 722, 234: 944, 235: 300, 241: 722, 245: 278, 248: 278, 249: 500, 250: 722, 251: 500}), 'Courier-Bold': ({'FontName': 'Courier-Bold', 'Descent': -194.0, 'FontBBox': (-88.0, -249.0, 697.0, 811.0), 'FontWeight': 'Bold', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': 0.0, 'Ascent': 627.0}, {u' ': 600, u'!': 600, u'"': 600, u'#': 600, u'$': 600, u'%': 600, u'&': 600, u"'": 600, u'(': 600, u')': 600, u'*': 600, u'+': 600, u',': 600, u'-': 600, u'.': 600, u'/': 600, u'0': 600, u'1': 600, u'2': 600, u'3': 600, u'4': 600, u'5': 600, u'6': 600, u'7': 600, u'8': 600, u'9': 600, u':': 600, u';': 600, u'<': 600, u'=': 600, u'>': 600, u'?': 600, u'@': 600, u'A': 600, u'B': 600, u'C': 600, u'D': 600, u'E': 600, u'F': 600, u'G': 600, u'H': 600, u'I': 600, u'J': 600, u'K': 600, u'L': 600, u'M': 600, u'N': 600, u'O': 600, u'P': 600, u'Q': 600, u'R': 600, u'S': 600, u'T': 600, u'U': 600, u'V': 600, u'W': 600, u'X': 600, u'Y': 600, u'Z': 600, u'[': 600, u'\\': 600, u']': 600, u'^': 600, u'_': 600, u'`': 600, u'a': 600, u'b': 600, u'c': 600, u'd': 600, u'e': 600, u'f': 600, u'g': 600, u'h': 600, u'i': 600, u'j': 600, u'k': 600, u'l': 600, u'm': 600, u'n': 600, u'o': 600, u'p': 600, u'q': 600, u'r': 600, u's': 600, u't': 600, u'u': 600, u'v': 600, u'w': 600, u'x': 600, u'y': 600, u'z': 600, u'{': 600, u'|': 600, u'}': 600, u'~': 600, u'\xa1': 600, u'\xa2': 600, u'\xa3': 600, u'\xa4': 600, u'\xa5': 600, u'\xa6': 600, u'\xa7': 600, u'\xa8': 600, u'\xa9': 600, u'\xaa': 600, u'\xab': 600, u'\xac': 600, u'\xae': 600, u'\xaf': 600, u'\xb0': 600, u'\xb1': 600, u'\xb2': 600, u'\xb3': 600, u'\xb4': 600, u'\xb5': 600, u'\xb6': 600, u'\xb7': 600, u'\xb8': 600, u'\xb9': 600, u'\xba': 600, u'\xbb': 600, u'\xbc': 600, u'\xbd': 600, u'\xbe': 600, u'\xbf': 600, u'\xc0': 600, u'\xc1': 600, u'\xc2': 600, u'\xc3': 600, u'\xc4': 600, u'\xc5': 600, u'\xc6': 600, u'\xc7': 600, u'\xc8': 600, u'\xc9': 600, u'\xca': 600, u'\xcb': 600, u'\xcc': 600, u'\xcd': 600, u'\xce': 600, u'\xcf': 600, u'\xd0': 600, u'\xd1': 600, u'\xd2': 600, u'\xd3': 600, u'\xd4': 600, u'\xd5': 600, u'\xd6': 600, u'\xd7': 600, u'\xd8': 600, u'\xd9': 600, u'\xda': 600, u'\xdb': 600, u'\xdc': 600, u'\xdd': 600, u'\xde': 600, u'\xdf': 600, u'\xe0': 600, u'\xe1': 600, u'\xe2': 600, u'\xe3': 600, u'\xe4': 600, u'\xe5': 600, u'\xe6': 600, u'\xe7': 600, u'\xe8': 600, u'\xe9': 600, u'\xea': 600, u'\xeb': 600, u'\xec': 600, u'\xed': 600, u'\xee': 600, u'\xef': 600, u'\xf0': 600, u'\xf1': 600, u'\xf2': 600, u'\xf3': 600, u'\xf4': 600, u'\xf5': 600, u'\xf6': 600, u'\xf7': 600, u'\xf8': 600, u'\xf9': 600, u'\xfa': 600, u'\xfb': 600, u'\xfc': 600, u'\xfd': 600, u'\xfe': 600, u'\xff': 600, u'\u0100': 600, u'\u0101': 600, u'\u0102': 600, u'\u0103': 600, u'\u0104': 600, u'\u0105': 600, u'\u0106': 600, u'\u0107': 600, u'\u010c': 600, u'\u010d': 600, u'\u010e': 600, u'\u010f': 600, u'\u0110': 600, u'\u0111': 600, u'\u0112': 600, u'\u0113': 600, u'\u0116': 600, u'\u0117': 600, u'\u0118': 600, u'\u0119': 600, u'\u011a': 600, u'\u011b': 600, u'\u011e': 600, u'\u011f': 600, u'\u0122': 600, u'\u0123': 600, u'\u012a': 600, u'\u012b': 600, u'\u012e': 600, u'\u012f': 600, u'\u0130': 600, u'\u0131': 600, u'\u0136': 600, u'\u0137': 600, u'\u0139': 600, u'\u013a': 600, u'\u013b': 600, u'\u013c': 600, u'\u013d': 600, u'\u013e': 600, u'\u0141': 600, u'\u0142': 600, u'\u0143': 600, u'\u0144': 600, u'\u0145': 600, u'\u0146': 600, u'\u0147': 600, u'\u0148': 600, u'\u014c': 600, u'\u014d': 600, u'\u0150': 600, u'\u0151': 600, u'\u0152': 600, u'\u0153': 600, u'\u0154': 600, u'\u0155': 600, u'\u0156': 600, u'\u0157': 600, u'\u0158': 600, u'\u0159': 600, u'\u015a': 600, u'\u015b': 600, u'\u015e': 600, u'\u015f': 600, u'\u0160': 600, u'\u0161': 600, u'\u0162': 600, u'\u0163': 600, u'\u0164': 600, u'\u0165': 600, u'\u016a': 600, u'\u016b': 600, u'\u016e': 600, u'\u016f': 600, u'\u0170': 600, u'\u0171': 600, u'\u0172': 600, u'\u0173': 600, u'\u0178': 600, u'\u0179': 600, u'\u017a': 600, u'\u017b': 600, u'\u017c': 600, u'\u017d': 600, u'\u017e': 600, u'\u0192': 600, u'\u0218': 600, u'\u0219': 600, u'\u02c6': 600, u'\u02c7': 600, u'\u02d8': 600, u'\u02d9': 600, u'\u02da': 600, u'\u02db': 600, u'\u02dc': 600, u'\u02dd': 600, u'\u2013': 600, u'\u2014': 600, u'\u2018': 600, u'\u2019': 600, u'\u201a': 600, u'\u201c': 600, u'\u201d': 600, u'\u201e': 600, u'\u2020': 600, u'\u2021': 600, u'\u2022': 600, u'\u2026': 600, u'\u2030': 600, u'\u2039': 600, u'\u203a': 600, u'\u2044': 600, u'\u2122': 600, u'\u2202': 600, u'\u2206': 600, u'\u2211': 600, u'\u2212': 600, u'\u221a': 600, u'\u2260': 600, u'\u2264': 600, u'\u2265': 600, u'\u25ca': 600, u'\uf6c3': 600, u'\ufb01': 600, u'\ufb02': 600}),
'Helvetica-Bold': ({'FontName': 'Helvetica-Bold', 'Descent': -207.0, 'FontBBox': (-170.0, -228.0, 1003.0, 962.0), 'FontWeight': 'Bold', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 532.0, 'ItalicAngle': 0.0, 'Ascent': 718.0}, {32: 278, 33: 333, 34: 474, 35: 556, 36: 556, 37: 889, 38: 722, 39: 278, 40: 333, 41: 333, 42: 389, 43: 584, 44: 278, 45: 333, 46: 278, 47: 278, 48: 556, 49: 556, 50: 556, 51: 556, 52: 556, 53: 556, 54: 556, 55: 556, 56: 556, 57: 556, 58: 333, 59: 333, 60: 584, 61: 584, 62: 584, 63: 611, 64: 975, 65: 722, 66: 722, 67: 722, 68: 722, 69: 667, 70: 611, 71: 778, 72: 722, 73: 278, 74: 556, 75: 722, 76: 611, 77: 833, 78: 722, 79: 778, 80: 667, 81: 778, 82: 722, 83: 667, 84: 611, 85: 722, 86: 667, 87: 944, 88: 667, 89: 667, 90: 611, 91: 333, 92: 278, 93: 333, 94: 584, 95: 556, 96: 278, 97: 556, 98: 611, 99: 556, 100: 611, 101: 556, 102: 333, 103: 611, 104: 611, 105: 278, 106: 278, 107: 556, 108: 278, 109: 889, 110: 611, 111: 611, 112: 611, 113: 611, 114: 389, 115: 556, 116: 333, 117: 611, 118: 556, 119: 778, 120: 556, 121: 556, 122: 500, 123: 389, 124: 280, 125: 389, 126: 584, 161: 333, 162: 556, 163: 556, 164: 167, 165: 556, 166: 556, 167: 556, 168: 556, 169: 238, 170: 500, 171: 556, 172: 333, 173: 333, 174: 611, 175: 611, 177: 556, 178: 556, 179: 556, 180: 278, 182: 556, 183: 350, 184: 278, 185: 500, 186: 500, 187: 556, 188: 1000, 189: 1000, 191: 611, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 1000, 227: 370, 232: 611, 233: 778, 234: 1000, 235: 365, 241: 889, 245: 278, 248: 278, 249: 611, 250: 944, 251: 611}), 'Courier-BoldOblique': ({'FontName': 'Courier-BoldOblique', 'Descent': -194.0, 'FontBBox': (-49.0, -249.0, 758.0, 811.0), 'FontWeight': 'Bold', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': -11.0, 'Ascent': 627.0}, {u' ': 600, u'!': 600, u'"': 600, u'#': 600, u'$': 600, u'%': 600, u'&': 600, u"'": 600, u'(': 600, u')': 600, u'*': 600, u'+': 600, u',': 600, u'-': 600, u'.': 600, u'/': 600, u'0': 600, u'1': 600, u'2': 600, u'3': 600, u'4': 600, u'5': 600, u'6': 600, u'7': 600, u'8': 600, u'9': 600, u':': 600, u';': 600, u'<': 600, u'=': 600, u'>': 600, u'?': 600, u'@': 600, u'A': 600, u'B': 600, u'C': 600, u'D': 600, u'E': 600, u'F': 600, u'G': 600, u'H': 600, u'I': 600, u'J': 600, u'K': 600, u'L': 600, u'M': 600, u'N': 600, u'O': 600, u'P': 600, u'Q': 600, u'R': 600, u'S': 600, u'T': 600, u'U': 600, u'V': 600, u'W': 600, u'X': 600, u'Y': 600, u'Z': 600, u'[': 600, u'\\': 600, u']': 600, u'^': 600, u'_': 600, u'`': 600, u'a': 600, u'b': 600, u'c': 600, u'd': 600, u'e': 600, u'f': 600, u'g': 600, u'h': 600, u'i': 600, u'j': 600, u'k': 600, u'l': 600, u'm': 600, u'n': 600, u'o': 600, u'p': 600, u'q': 600, u'r': 600, u's': 600, u't': 600, u'u': 600, u'v': 600, u'w': 600, u'x': 600, u'y': 600, u'z': 600, u'{': 600, u'|': 600, u'}': 600, u'~': 600, u'\xa1': 600, u'\xa2': 600, u'\xa3': 600, u'\xa4': 600, u'\xa5': 600, u'\xa6': 600, u'\xa7': 600, u'\xa8': 600, u'\xa9': 600, u'\xaa': 600, u'\xab': 600, u'\xac': 600, u'\xae': 600, u'\xaf': 600, u'\xb0': 600, u'\xb1': 600, u'\xb2': 600, u'\xb3': 600, u'\xb4': 600, u'\xb5': 600, u'\xb6': 600, u'\xb7': 600, u'\xb8': 600, u'\xb9': 600, u'\xba': 600, u'\xbb': 600, u'\xbc': 600, u'\xbd': 600, u'\xbe': 600, u'\xbf': 600, u'\xc0': 600, u'\xc1': 600, u'\xc2': 600, u'\xc3': 600, u'\xc4': 600, u'\xc5': 600, u'\xc6': 600, u'\xc7': 600, u'\xc8': 600, u'\xc9': 600, u'\xca': 600, u'\xcb': 600, u'\xcc': 600, u'\xcd': 600, u'\xce': 600, u'\xcf': 600, u'\xd0': 600, u'\xd1': 600, u'\xd2': 600, u'\xd3': 600, u'\xd4': 600, u'\xd5': 600, u'\xd6': 600, u'\xd7': 600, u'\xd8': 600, u'\xd9': 600, u'\xda': 600, u'\xdb': 600, u'\xdc': 600, u'\xdd': 600, u'\xde': 600, u'\xdf': 600, u'\xe0': 600, u'\xe1': 600, u'\xe2': 600, u'\xe3': 600, u'\xe4': 600, u'\xe5': 600, u'\xe6': 600, u'\xe7': 600, u'\xe8': 600, u'\xe9': 600, u'\xea': 600, u'\xeb': 600, u'\xec': 600, u'\xed': 600, u'\xee': 600, u'\xef': 600, u'\xf0': 600, u'\xf1': 600, u'\xf2': 600, u'\xf3': 600, u'\xf4': 600, u'\xf5': 600, u'\xf6': 600, u'\xf7': 600, u'\xf8': 600, u'\xf9': 600, u'\xfa': 600, u'\xfb': 600, u'\xfc': 600, u'\xfd': 600, u'\xfe': 600, u'\xff': 600, u'\u0100': 600, u'\u0101': 600, u'\u0102': 600, u'\u0103': 600, u'\u0104': 600, u'\u0105': 600, u'\u0106': 600, u'\u0107': 600, u'\u010c': 600, u'\u010d': 600, u'\u010e': 600, u'\u010f': 600, u'\u0110': 600, u'\u0111': 600, u'\u0112': 600, u'\u0113': 600, u'\u0116': 600, u'\u0117': 600, u'\u0118': 600, u'\u0119': 600, u'\u011a': 600, u'\u011b': 600, u'\u011e': 600, u'\u011f': 600, u'\u0122': 600, u'\u0123': 600, u'\u012a': 600, u'\u012b': 600, u'\u012e': 600, u'\u012f': 600, u'\u0130': 600, u'\u0131': 600, u'\u0136': 600, u'\u0137': 600, u'\u0139': 600, u'\u013a': 600, u'\u013b': 600, u'\u013c': 600, u'\u013d': 600, u'\u013e': 600, u'\u0141': 600, u'\u0142': 600, u'\u0143': 600, u'\u0144': 600, u'\u0145': 600, u'\u0146': 600, u'\u0147': 600, u'\u0148': 600, u'\u014c': 600, u'\u014d': 600, u'\u0150': 600, u'\u0151': 600, u'\u0152': 600, u'\u0153': 600, u'\u0154': 600, u'\u0155': 600, u'\u0156': 600, u'\u0157': 600, u'\u0158': 600, u'\u0159': 600, u'\u015a': 600, u'\u015b': 600, u'\u015e': 600, u'\u015f': 600, u'\u0160': 600, u'\u0161': 600, u'\u0162': 600, u'\u0163': 600, u'\u0164': 600, u'\u0165': 600, u'\u016a': 600, u'\u016b': 600, u'\u016e': 600, u'\u016f': 600, u'\u0170': 600, u'\u0171': 600, u'\u0172': 600, u'\u0173': 600, u'\u0178': 600, u'\u0179': 600, u'\u017a': 600, u'\u017b': 600, u'\u017c': 600, u'\u017d': 600, u'\u017e': 600, u'\u0192': 600, u'\u0218': 600, u'\u0219': 600, u'\u02c6': 600, u'\u02c7': 600, u'\u02d8': 600, u'\u02d9': 600, u'\u02da': 600, u'\u02db': 600, u'\u02dc': 600, u'\u02dd': 600, u'\u2013': 600, u'\u2014': 600, u'\u2018': 600, u'\u2019': 600, u'\u201a': 600, u'\u201c': 600, u'\u201d': 600, u'\u201e': 600, u'\u2020': 600, u'\u2021': 600, u'\u2022': 600, u'\u2026': 600, u'\u2030': 600, u'\u2039': 600, u'\u203a': 600, u'\u2044': 600, u'\u2122': 600, u'\u2202': 600, u'\u2206': 600, u'\u2211': 600, u'\u2212': 600, u'\u221a': 600, u'\u2260': 600, u'\u2264': 600, u'\u2265': 600, u'\u25ca': 600, u'\uf6c3': 600, u'\ufb01': 600, u'\ufb02': 600}),
'Courier': ({'FontName': 'Courier', 'Descent': -194.0, 'FontBBox': (-6.0, -249.0, 639.0, 803.0), 'FontWeight': 'Medium', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': 0.0, 'Ascent': 627.0}, {32: 600, 33: 600, 34: 600, 35: 600, 36: 600, 37: 600, 38: 600, 39: 600, 40: 600, 41: 600, 42: 600, 43: 600, 44: 600, 45: 600, 46: 600, 47: 600, 48: 600, 49: 600, 50: 600, 51: 600, 52: 600, 53: 600, 54: 600, 55: 600, 56: 600, 57: 600, 58: 600, 59: 600, 60: 600, 61: 600, 62: 600, 63: 600, 64: 600, 65: 600, 66: 600, 67: 600, 68: 600, 69: 600, 70: 600, 71: 600, 72: 600, 73: 600, 74: 600, 75: 600, 76: 600, 77: 600, 78: 600, 79: 600, 80: 600, 81: 600, 82: 600, 83: 600, 84: 600, 85: 600, 86: 600, 87: 600, 88: 600, 89: 600, 90: 600, 91: 600, 92: 600, 93: 600, 94: 600, 95: 600, 96: 600, 97: 600, 98: 600, 99: 600, 100: 600, 101: 600, 102: 600, 103: 600, 104: 600, 105: 600, 106: 600, 107: 600, 108: 600, 109: 600, 110: 600, 111: 600, 112: 600, 113: 600, 114: 600, 115: 600, 116: 600, 117: 600, 118: 600, 119: 600, 120: 600, 121: 600, 122: 600, 123: 600, 124: 600, 125: 600, 126: 600, 161: 600, 162: 600, 163: 600, 164: 600, 165: 600, 166: 600, 167: 600, 168: 600, 169: 600, 170: 600, 171: 600, 172: 600, 173: 600, 174: 600, 175: 600, 177: 600, 178: 600, 179: 600, 180: 600, 182: 600, 183: 600, 184: 600, 185: 600, 186: 600, 187: 600, 188: 600, 189: 600, 191: 600, 193: 600, 194: 600, 195: 600, 196: 600, 197: 600, 198: 600, 199: 600, 200: 600, 202: 600, 203: 600, 205: 600, 206: 600, 207: 600, 208: 600, 225: 600, 227: 600, 232: 600, 233: 600, 234: 600, 235: 600, 241: 600, 245: 600, 248: 600, 249: 600, 250: 600, 251: 600}), 'Courier-Oblique': ({'FontName': 'Courier-Oblique', 'Descent': -194.0, 'FontBBox': (-49.0, -249.0, 749.0, 803.0), 'FontWeight': 'Medium', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': -11.0, 'Ascent': 627.0}, {u' ': 600, u'!': 600, u'"': 600, u'#': 600, u'$': 600, u'%': 600, u'&': 600, u"'": 600, u'(': 600, u')': 600, u'*': 600, u'+': 600, u',': 600, u'-': 600, u'.': 600, u'/': 600, u'0': 600, u'1': 600, u'2': 600, u'3': 600, u'4': 600, u'5': 600, u'6': 600, u'7': 600, u'8': 600, u'9': 600, u':': 600, u';': 600, u'<': 600, u'=': 600, u'>': 600, u'?': 600, u'@': 600, u'A': 600, u'B': 600, u'C': 600, u'D': 600, u'E': 600, u'F': 600, u'G': 600, u'H': 600, u'I': 600, u'J': 600, u'K': 600, u'L': 600, u'M': 600, u'N': 600, u'O': 600, u'P': 600, u'Q': 600, u'R': 600, u'S': 600, u'T': 600, u'U': 600, u'V': 600, u'W': 600, u'X': 600, u'Y': 600, u'Z': 600, u'[': 600, u'\\': 600, u']': 600, u'^': 600, u'_': 600, u'`': 600, u'a': 600, u'b': 600, u'c': 600, u'd': 600, u'e': 600, u'f': 600, u'g': 600, u'h': 600, u'i': 600, u'j': 600, u'k': 600, u'l': 600, u'm': 600, u'n': 600, u'o': 600, u'p': 600, u'q': 600, u'r': 600, u's': 600, u't': 600, u'u': 600, u'v': 600, u'w': 600, u'x': 600, u'y': 600, u'z': 600, u'{': 600, u'|': 600, u'}': 600, u'~': 600, u'\xa1': 600, u'\xa2': 600, u'\xa3': 600, u'\xa4': 600, u'\xa5': 600, u'\xa6': 600, u'\xa7': 600, u'\xa8': 600, u'\xa9': 600, u'\xaa': 600, u'\xab': 600, u'\xac': 600, u'\xae': 600, u'\xaf': 600, u'\xb0': 600, u'\xb1': 600, u'\xb2': 600, u'\xb3': 600, u'\xb4': 600, u'\xb5': 600, u'\xb6': 600, u'\xb7': 600, u'\xb8': 600, u'\xb9': 600, u'\xba': 600, u'\xbb': 600, u'\xbc': 600, u'\xbd': 600, u'\xbe': 600, u'\xbf': 600, u'\xc0': 600, u'\xc1': 600, u'\xc2': 600, u'\xc3': 600, u'\xc4': 600, u'\xc5': 600, u'\xc6': 600, u'\xc7': 600, u'\xc8': 600, u'\xc9': 600, u'\xca': 600, u'\xcb': 600, u'\xcc': 600, u'\xcd': 600, u'\xce': 600, u'\xcf': 600, u'\xd0': 600, u'\xd1': 600, u'\xd2': 600, u'\xd3': 600, u'\xd4': 600, u'\xd5': 600, u'\xd6': 600, u'\xd7': 600, u'\xd8': 600, u'\xd9': 600, u'\xda': 600, u'\xdb': 600, u'\xdc': 600, u'\xdd': 600, u'\xde': 600, u'\xdf': 600, u'\xe0': 600, u'\xe1': 600, u'\xe2': 600, u'\xe3': 600, u'\xe4': 600, u'\xe5': 600, u'\xe6': 600, u'\xe7': 600, u'\xe8': 600, u'\xe9': 600, u'\xea': 600, u'\xeb': 600, u'\xec': 600, u'\xed': 600, u'\xee': 600, u'\xef': 600, u'\xf0': 600, u'\xf1': 600, u'\xf2': 600, u'\xf3': 600, u'\xf4': 600, u'\xf5': 600, u'\xf6': 600, u'\xf7': 600, u'\xf8': 600, u'\xf9': 600, u'\xfa': 600, u'\xfb': 600, u'\xfc': 600, u'\xfd': 600, u'\xfe': 600, u'\xff': 600, u'\u0100': 600, u'\u0101': 600, u'\u0102': 600, u'\u0103': 600, u'\u0104': 600, u'\u0105': 600, u'\u0106': 600, u'\u0107': 600, u'\u010c': 600, u'\u010d': 600, u'\u010e': 600, u'\u010f': 600, u'\u0110': 600, u'\u0111': 600, u'\u0112': 600, u'\u0113': 600, u'\u0116': 600, u'\u0117': 600, u'\u0118': 600, u'\u0119': 600, u'\u011a': 600, u'\u011b': 600, u'\u011e': 600, u'\u011f': 600, u'\u0122': 600, u'\u0123': 600, u'\u012a': 600, u'\u012b': 600, u'\u012e': 600, u'\u012f': 600, u'\u0130': 600, u'\u0131': 600, u'\u0136': 600, u'\u0137': 600, u'\u0139': 600, u'\u013a': 600, u'\u013b': 600, u'\u013c': 600, u'\u013d': 600, u'\u013e': 600, u'\u0141': 600, u'\u0142': 600, u'\u0143': 600, u'\u0144': 600, u'\u0145': 600, u'\u0146': 600, u'\u0147': 600, u'\u0148': 600, u'\u014c': 600, u'\u014d': 600, u'\u0150': 600, u'\u0151': 600, u'\u0152': 600, u'\u0153': 600, u'\u0154': 600, u'\u0155': 600, u'\u0156': 600, u'\u0157': 600, u'\u0158': 600, u'\u0159': 600, u'\u015a': 600, u'\u015b': 600, u'\u015e': 600, u'\u015f': 600, u'\u0160': 600, u'\u0161': 600, u'\u0162': 600, u'\u0163': 600, u'\u0164': 600, u'\u0165': 600, u'\u016a': 600, u'\u016b': 600, u'\u016e': 600, u'\u016f': 600, u'\u0170': 600, u'\u0171': 600, u'\u0172': 600, u'\u0173': 600, u'\u0178': 600, u'\u0179': 600, u'\u017a': 600, u'\u017b': 600, u'\u017c': 600, u'\u017d': 600, u'\u017e': 600, u'\u0192': 600, u'\u0218': 600, u'\u0219': 600, u'\u02c6': 600, u'\u02c7': 600, u'\u02d8': 600, u'\u02d9': 600, u'\u02da': 600, u'\u02db': 600, u'\u02dc': 600, u'\u02dd': 600, u'\u2013': 600, u'\u2014': 600, u'\u2018': 600, u'\u2019': 600, u'\u201a': 600, u'\u201c': 600, u'\u201d': 600, u'\u201e': 600, u'\u2020': 600, u'\u2021': 600, u'\u2022': 600, u'\u2026': 600, u'\u2030': 600, u'\u2039': 600, u'\u203a': 600, u'\u2044': 600, u'\u2122': 600, u'\u2202': 600, u'\u2206': 600, u'\u2211': 600, u'\u2212': 600, u'\u221a': 600, u'\u2260': 600, u'\u2264': 600, u'\u2265': 600, u'\u25ca': 600, u'\uf6c3': 600, u'\ufb01': 600, u'\ufb02': 600}),
'Courier-BoldOblique': ({'FontName': 'Courier-BoldOblique', 'Descent': -194.0, 'FontBBox': (-49.0, -249.0, 758.0, 811.0), 'FontWeight': 'Bold', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': -11.0, 'Ascent': 627.0}, {32: 600, 33: 600, 34: 600, 35: 600, 36: 600, 37: 600, 38: 600, 39: 600, 40: 600, 41: 600, 42: 600, 43: 600, 44: 600, 45: 600, 46: 600, 47: 600, 48: 600, 49: 600, 50: 600, 51: 600, 52: 600, 53: 600, 54: 600, 55: 600, 56: 600, 57: 600, 58: 600, 59: 600, 60: 600, 61: 600, 62: 600, 63: 600, 64: 600, 65: 600, 66: 600, 67: 600, 68: 600, 69: 600, 70: 600, 71: 600, 72: 600, 73: 600, 74: 600, 75: 600, 76: 600, 77: 600, 78: 600, 79: 600, 80: 600, 81: 600, 82: 600, 83: 600, 84: 600, 85: 600, 86: 600, 87: 600, 88: 600, 89: 600, 90: 600, 91: 600, 92: 600, 93: 600, 94: 600, 95: 600, 96: 600, 97: 600, 98: 600, 99: 600, 100: 600, 101: 600, 102: 600, 103: 600, 104: 600, 105: 600, 106: 600, 107: 600, 108: 600, 109: 600, 110: 600, 111: 600, 112: 600, 113: 600, 114: 600, 115: 600, 116: 600, 117: 600, 118: 600, 119: 600, 120: 600, 121: 600, 122: 600, 123: 600, 124: 600, 125: 600, 126: 600, 161: 600, 162: 600, 163: 600, 164: 600, 165: 600, 166: 600, 167: 600, 168: 600, 169: 600, 170: 600, 171: 600, 172: 600, 173: 600, 174: 600, 175: 600, 177: 600, 178: 600, 179: 600, 180: 600, 182: 600, 183: 600, 184: 600, 185: 600, 186: 600, 187: 600, 188: 600, 189: 600, 191: 600, 193: 600, 194: 600, 195: 600, 196: 600, 197: 600, 198: 600, 199: 600, 200: 600, 202: 600, 203: 600, 205: 600, 206: 600, 207: 600, 208: 600, 225: 600, 227: 600, 232: 600, 233: 600, 234: 600, 235: 600, 241: 600, 245: 600, 248: 600, 249: 600, 250: 600, 251: 600}), 'Helvetica': ({'FontName': 'Helvetica', 'Descent': -207.0, 'FontBBox': (-166.0, -225.0, 1000.0, 931.0), 'FontWeight': 'Medium', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 523.0, 'ItalicAngle': 0.0, 'Ascent': 718.0}, {u' ': 278, u'!': 278, u'"': 355, u'#': 556, u'$': 556, u'%': 889, u'&': 667, u"'": 191, u'(': 333, u')': 333, u'*': 389, u'+': 584, u',': 278, u'-': 333, u'.': 278, u'/': 278, u'0': 556, u'1': 556, u'2': 556, u'3': 556, u'4': 556, u'5': 556, u'6': 556, u'7': 556, u'8': 556, u'9': 556, u':': 278, u';': 278, u'<': 584, u'=': 584, u'>': 584, u'?': 556, u'@': 1015, u'A': 667, u'B': 667, u'C': 722, u'D': 722, u'E': 667, u'F': 611, u'G': 778, u'H': 722, u'I': 278, u'J': 500, u'K': 667, u'L': 556, u'M': 833, u'N': 722, u'O': 778, u'P': 667, u'Q': 778, u'R': 722, u'S': 667, u'T': 611, u'U': 722, u'V': 667, u'W': 944, u'X': 667, u'Y': 667, u'Z': 611, u'[': 278, u'\\': 278, u']': 278, u'^': 469, u'_': 556, u'`': 333, u'a': 556, u'b': 556, u'c': 500, u'd': 556, u'e': 556, u'f': 278, u'g': 556, u'h': 556, u'i': 222, u'j': 222, u'k': 500, u'l': 222, u'm': 833, u'n': 556, u'o': 556, u'p': 556, u'q': 556, u'r': 333, u's': 500, u't': 278, u'u': 556, u'v': 500, u'w': 722, u'x': 500, u'y': 500, u'z': 500, u'{': 334, u'|': 260, u'}': 334, u'~': 584, u'\xa1': 333, u'\xa2': 556, u'\xa3': 556, u'\xa4': 556, u'\xa5': 556, u'\xa6': 260, u'\xa7': 556, u'\xa8': 333, u'\xa9': 737, u'\xaa': 370, u'\xab': 556, u'\xac': 584, u'\xae': 737, u'\xaf': 333, u'\xb0': 400, u'\xb1': 584, u'\xb2': 333, u'\xb3': 333, u'\xb4': 333, u'\xb5': 556, u'\xb6': 537, u'\xb7': 278, u'\xb8': 333, u'\xb9': 333, u'\xba': 365, u'\xbb': 556, u'\xbc': 834, u'\xbd': 834, u'\xbe': 834, u'\xbf': 611, u'\xc0': 667, u'\xc1': 667, u'\xc2': 667, u'\xc3': 667, u'\xc4': 667, u'\xc5': 667, u'\xc6': 1000, u'\xc7': 722, u'\xc8': 667, u'\xc9': 667, u'\xca': 667, u'\xcb': 667, u'\xcc': 278, u'\xcd': 278, u'\xce': 278, u'\xcf': 278, u'\xd0': 722, u'\xd1': 722, u'\xd2': 778, u'\xd3': 778, u'\xd4': 778, u'\xd5': 778, u'\xd6': 778, u'\xd7': 584, u'\xd8': 778, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 667, u'\xde': 667, u'\xdf': 611, u'\xe0': 556, u'\xe1': 556, u'\xe2': 556, u'\xe3': 556, u'\xe4': 556, u'\xe5': 556, u'\xe6': 889, u'\xe7': 500, u'\xe8': 556, u'\xe9': 556, u'\xea': 556, u'\xeb': 556, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 556, u'\xf1': 556, u'\xf2': 556, u'\xf3': 556, u'\xf4': 556, u'\xf5': 556, u'\xf6': 556, u'\xf7': 584, u'\xf8': 611, u'\xf9': 556, u'\xfa': 556, u'\xfb': 556, u'\xfc': 556, u'\xfd': 500, u'\xfe': 556, u'\xff': 500, u'\u0100': 667, u'\u0101': 556, u'\u0102': 667, u'\u0103': 556, u'\u0104': 667, u'\u0105': 556, u'\u0106': 722, u'\u0107': 500, u'\u010c': 722, u'\u010d': 500, u'\u010e': 722, u'\u010f': 643, u'\u0110': 722, u'\u0111': 556, u'\u0112': 667, u'\u0113': 556, u'\u0116': 667, u'\u0117': 556, u'\u0118': 667, u'\u0119': 556, u'\u011a': 667, u'\u011b': 556, u'\u011e': 778, u'\u011f': 556, u'\u0122': 778, u'\u0123': 556, u'\u012a': 278, u'\u012b': 278, u'\u012e': 278, u'\u012f': 222, u'\u0130': 278, u'\u0131': 278, u'\u0136': 667, u'\u0137': 500, u'\u0139': 556, u'\u013a': 222, u'\u013b': 556, u'\u013c': 222, u'\u013d': 556, u'\u013e': 299, u'\u0141': 556, u'\u0142': 222, u'\u0143': 722, u'\u0144': 556, u'\u0145': 722, u'\u0146': 556, u'\u0147': 722, u'\u0148': 556, u'\u014c': 778, u'\u014d': 556, u'\u0150': 778, u'\u0151': 556, u'\u0152': 1000, u'\u0153': 944, u'\u0154': 722, u'\u0155': 333, u'\u0156': 722, u'\u0157': 333, u'\u0158': 722, u'\u0159': 333, u'\u015a': 667, u'\u015b': 500, u'\u015e': 667, u'\u015f': 500, u'\u0160': 667, u'\u0161': 500, u'\u0162': 611, u'\u0163': 278, u'\u0164': 611, u'\u0165': 317, u'\u016a': 722, u'\u016b': 556, u'\u016e': 722, u'\u016f': 556, u'\u0170': 722, u'\u0171': 556, u'\u0172': 722, u'\u0173': 556, u'\u0178': 667, u'\u0179': 611, u'\u017a': 500, u'\u017b': 611, u'\u017c': 500, u'\u017d': 611, u'\u017e': 500, u'\u0192': 556, u'\u0218': 667, u'\u0219': 500, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 556, u'\u2014': 1000, u'\u2018': 222, u'\u2019': 222, u'\u201a': 222, u'\u201c': 333, u'\u201d': 333, u'\u201e': 333, u'\u2020': 556, u'\u2021': 556, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 1000, u'\u2202': 476, u'\u2206': 612, u'\u2211': 600, u'\u2212': 584, u'\u221a': 453, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 471, u'\uf6c3': 250, u'\ufb01': 500, u'\ufb02': 500}),
'Times-Bold': ({'FontName': 'Times-Bold', 'Descent': -217.0, 'FontBBox': (-168.0, -218.0, 1000.0, 935.0), 'FontWeight': 'Bold', 'CapHeight': 676.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 461.0, 'ItalicAngle': 0.0, 'Ascent': 683.0}, {32: 250, 33: 333, 34: 555, 35: 500, 36: 500, 37: 1000, 38: 833, 39: 333, 40: 333, 41: 333, 42: 500, 43: 570, 44: 250, 45: 333, 46: 250, 47: 278, 48: 500, 49: 500, 50: 500, 51: 500, 52: 500, 53: 500, 54: 500, 55: 500, 56: 500, 57: 500, 58: 333, 59: 333, 60: 570, 61: 570, 62: 570, 63: 500, 64: 930, 65: 722, 66: 667, 67: 722, 68: 722, 69: 667, 70: 611, 71: 778, 72: 778, 73: 389, 74: 500, 75: 778, 76: 667, 77: 944, 78: 722, 79: 778, 80: 611, 81: 778, 82: 722, 83: 556, 84: 667, 85: 722, 86: 722, 87: 1000, 88: 722, 89: 722, 90: 667, 91: 333, 92: 278, 93: 333, 94: 581, 95: 500, 96: 333, 97: 500, 98: 556, 99: 444, 100: 556, 101: 444, 102: 333, 103: 500, 104: 556, 105: 278, 106: 333, 107: 556, 108: 278, 109: 833, 110: 556, 111: 500, 112: 556, 113: 556, 114: 444, 115: 389, 116: 333, 117: 556, 118: 500, 119: 722, 120: 500, 121: 500, 122: 444, 123: 394, 124: 220, 125: 394, 126: 520, 161: 333, 162: 500, 163: 500, 164: 167, 165: 500, 166: 500, 167: 500, 168: 500, 169: 278, 170: 500, 171: 500, 172: 333, 173: 333, 174: 556, 175: 556, 177: 500, 178: 500, 179: 500, 180: 250, 182: 540, 183: 350, 184: 333, 185: 500, 186: 500, 187: 500, 188: 1000, 189: 1000, 191: 500, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 1000, 227: 300, 232: 667, 233: 778, 234: 1000, 235: 330, 241: 722, 245: 278, 248: 278, 249: 500, 250: 722, 251: 556}), 'Helvetica-Bold': ({'FontName': 'Helvetica-Bold', 'Descent': -207.0, 'FontBBox': (-170.0, -228.0, 1003.0, 962.0), 'FontWeight': 'Bold', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 532.0, 'ItalicAngle': 0.0, 'Ascent': 718.0}, {u' ': 278, u'!': 333, u'"': 474, u'#': 556, u'$': 556, u'%': 889, u'&': 722, u"'": 238, u'(': 333, u')': 333, u'*': 389, u'+': 584, u',': 278, u'-': 333, u'.': 278, u'/': 278, u'0': 556, u'1': 556, u'2': 556, u'3': 556, u'4': 556, u'5': 556, u'6': 556, u'7': 556, u'8': 556, u'9': 556, u':': 333, u';': 333, u'<': 584, u'=': 584, u'>': 584, u'?': 611, u'@': 975, u'A': 722, u'B': 722, u'C': 722, u'D': 722, u'E': 667, u'F': 611, u'G': 778, u'H': 722, u'I': 278, u'J': 556, u'K': 722, u'L': 611, u'M': 833, u'N': 722, u'O': 778, u'P': 667, u'Q': 778, u'R': 722, u'S': 667, u'T': 611, u'U': 722, u'V': 667, u'W': 944, u'X': 667, u'Y': 667, u'Z': 611, u'[': 333, u'\\': 278, u']': 333, u'^': 584, u'_': 556, u'`': 333, u'a': 556, u'b': 611, u'c': 556, u'd': 611, u'e': 556, u'f': 333, u'g': 611, u'h': 611, u'i': 278, u'j': 278, u'k': 556, u'l': 278, u'm': 889, u'n': 611, u'o': 611, u'p': 611, u'q': 611, u'r': 389, u's': 556, u't': 333, u'u': 611, u'v': 556, u'w': 778, u'x': 556, u'y': 556, u'z': 500, u'{': 389, u'|': 280, u'}': 389, u'~': 584, u'\xa1': 333, u'\xa2': 556, u'\xa3': 556, u'\xa4': 556, u'\xa5': 556, u'\xa6': 280, u'\xa7': 556, u'\xa8': 333, u'\xa9': 737, u'\xaa': 370, u'\xab': 556, u'\xac': 584, u'\xae': 737, u'\xaf': 333, u'\xb0': 400, u'\xb1': 584, u'\xb2': 333, u'\xb3': 333, u'\xb4': 333, u'\xb5': 611, u'\xb6': 556, u'\xb7': 278, u'\xb8': 333, u'\xb9': 333, u'\xba': 365, u'\xbb': 556, u'\xbc': 834, u'\xbd': 834, u'\xbe': 834, u'\xbf': 611, u'\xc0': 722, u'\xc1': 722, u'\xc2': 722, u'\xc3': 722, u'\xc4': 722, u'\xc5': 722, u'\xc6': 1000, u'\xc7': 722, u'\xc8': 667, u'\xc9': 667, u'\xca': 667, u'\xcb': 667, u'\xcc': 278, u'\xcd': 278, u'\xce': 278, u'\xcf': 278, u'\xd0': 722, u'\xd1': 722, u'\xd2': 778, u'\xd3': 778, u'\xd4': 778, u'\xd5': 778, u'\xd6': 778, u'\xd7': 584, u'\xd8': 778, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 667, u'\xde': 667, u'\xdf': 611, u'\xe0': 556, u'\xe1': 556, u'\xe2': 556, u'\xe3': 556, u'\xe4': 556, u'\xe5': 556, u'\xe6': 889, u'\xe7': 556, u'\xe8': 556, u'\xe9': 556, u'\xea': 556, u'\xeb': 556, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 611, u'\xf1': 611, u'\xf2': 611, u'\xf3': 611, u'\xf4': 611, u'\xf5': 611, u'\xf6': 611, u'\xf7': 584, u'\xf8': 611, u'\xf9': 611, u'\xfa': 611, u'\xfb': 611, u'\xfc': 611, u'\xfd': 556, u'\xfe': 611, u'\xff': 556, u'\u0100': 722, u'\u0101': 556, u'\u0102': 722, u'\u0103': 556, u'\u0104': 722, u'\u0105': 556, u'\u0106': 722, u'\u0107': 556, u'\u010c': 722, u'\u010d': 556, u'\u010e': 722, u'\u010f': 743, u'\u0110': 722, u'\u0111': 611, u'\u0112': 667, u'\u0113': 556, u'\u0116': 667, u'\u0117': 556, u'\u0118': 667, u'\u0119': 556, u'\u011a': 667, u'\u011b': 556, u'\u011e': 778, u'\u011f': 611, u'\u0122': 778, u'\u0123': 611, u'\u012a': 278, u'\u012b': 278, u'\u012e': 278, u'\u012f': 278, u'\u0130': 278, u'\u0131': 278, u'\u0136': 722, u'\u0137': 556, u'\u0139': 611, u'\u013a': 278, u'\u013b': 611, u'\u013c': 278, u'\u013d': 611, u'\u013e': 400, u'\u0141': 611, u'\u0142': 278, u'\u0143': 722, u'\u0144': 611, u'\u0145': 722, u'\u0146': 611, u'\u0147': 722, u'\u0148': 611, u'\u014c': 778, u'\u014d': 611, u'\u0150': 778, u'\u0151': 611, u'\u0152': 1000, u'\u0153': 944, u'\u0154': 722, u'\u0155': 389, u'\u0156': 722, u'\u0157': 389, u'\u0158': 722, u'\u0159': 389, u'\u015a': 667, u'\u015b': 556, u'\u015e': 667, u'\u015f': 556, u'\u0160': 667, u'\u0161': 556, u'\u0162': 611, u'\u0163': 333, u'\u0164': 611, u'\u0165': 389, u'\u016a': 722, u'\u016b': 611, u'\u016e': 722, u'\u016f': 611, u'\u0170': 722, u'\u0171': 611, u'\u0172': 722, u'\u0173': 611, u'\u0178': 667, u'\u0179': 611, u'\u017a': 500, u'\u017b': 611, u'\u017c': 500, u'\u017d': 611, u'\u017e': 500, u'\u0192': 556, u'\u0218': 667, u'\u0219': 556, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 556, u'\u2014': 1000, u'\u2018': 278, u'\u2019': 278, u'\u201a': 278, u'\u201c': 500, u'\u201d': 500, u'\u201e': 500, u'\u2020': 556, u'\u2021': 556, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 1000, u'\u2202': 494, u'\u2206': 612, u'\u2211': 600, u'\u2212': 584, u'\u221a': 549, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 494, u'\uf6c3': 250, u'\ufb01': 611, u'\ufb02': 611}),
'Symbol': ({'FontName': 'Symbol', 'FontBBox': (-180.0, -293.0, 1090.0, 1010.0), 'FontWeight': 'Medium', 'FontFamily': 'Symbol', 'Flags': 0, 'ItalicAngle': 0.0}, {32: 250, 33: 333, 34: 713, 35: 500, 36: 549, 37: 833, 38: 778, 39: 439, 40: 333, 41: 333, 42: 500, 43: 549, 44: 250, 45: 549, 46: 250, 47: 278, 48: 500, 49: 500, 50: 500, 51: 500, 52: 500, 53: 500, 54: 500, 55: 500, 56: 500, 57: 500, 58: 278, 59: 278, 60: 549, 61: 549, 62: 549, 63: 444, 64: 549, 65: 722, 66: 667, 67: 722, 68: 612, 69: 611, 70: 763, 71: 603, 72: 722, 73: 333, 74: 631, 75: 722, 76: 686, 77: 889, 78: 722, 79: 722, 80: 768, 81: 741, 82: 556, 83: 592, 84: 611, 85: 690, 86: 439, 87: 768, 88: 645, 89: 795, 90: 611, 91: 333, 92: 863, 93: 333, 94: 658, 95: 500, 96: 500, 97: 631, 98: 549, 99: 549, 100: 494, 101: 439, 102: 521, 103: 411, 104: 603, 105: 329, 106: 603, 107: 549, 108: 549, 109: 576, 110: 521, 111: 549, 112: 549, 113: 521, 114: 549, 115: 603, 116: 439, 117: 576, 118: 713, 119: 686, 120: 493, 121: 686, 122: 494, 123: 480, 124: 200, 125: 480, 126: 549, 160: 750, 161: 620, 162: 247, 163: 549, 164: 167, 165: 713, 166: 500, 167: 753, 168: 753, 169: 753, 170: 753, 171: 1042, 172: 987, 173: 603, 174: 987, 175: 603, 176: 400, 177: 549, 178: 411, 179: 549, 180: 549, 181: 713, 182: 494, 183: 460, 184: 549, 185: 549, 186: 549, 187: 549, 188: 1000, 189: 603, 190: 1000, 191: 658, 192: 823, 193: 686, 194: 795, 195: 987, 196: 768, 197: 768, 198: 823, 199: 768, 200: 768, 201: 713, 202: 713, 203: 713, 204: 713, 205: 713, 206: 713, 207: 713, 208: 768, 209: 713, 210: 790, 211: 790, 212: 890, 213: 823, 214: 549, 215: 250, 216: 713, 217: 603, 218: 603, 219: 1042, 220: 987, 221: 603, 222: 987, 223: 603, 224: 494, 225: 329, 226: 790, 227: 790, 228: 786, 229: 713, 230: 384, 231: 384, 232: 384, 233: 384, 234: 384, 235: 384, 236: 494, 237: 494, 238: 494, 239: 494, 241: 329, 242: 274, 243: 686, 244: 686, 245: 686, 246: 384, 247: 384, 248: 384, 249: 384, 250: 384, 251: 384, 252: 494, 253: 494, 254: 494}), 'Helvetica-BoldOblique': ({'FontName': 'Helvetica-BoldOblique', 'Descent': -207.0, 'FontBBox': (-175.0, -228.0, 1114.0, 962.0), 'FontWeight': 'Bold', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 532.0, 'ItalicAngle': -12.0, 'Ascent': 718.0}, {u' ': 278, u'!': 333, u'"': 474, u'#': 556, u'$': 556, u'%': 889, u'&': 722, u"'": 238, u'(': 333, u')': 333, u'*': 389, u'+': 584, u',': 278, u'-': 333, u'.': 278, u'/': 278, u'0': 556, u'1': 556, u'2': 556, u'3': 556, u'4': 556, u'5': 556, u'6': 556, u'7': 556, u'8': 556, u'9': 556, u':': 333, u';': 333, u'<': 584, u'=': 584, u'>': 584, u'?': 611, u'@': 975, u'A': 722, u'B': 722, u'C': 722, u'D': 722, u'E': 667, u'F': 611, u'G': 778, u'H': 722, u'I': 278, u'J': 556, u'K': 722, u'L': 611, u'M': 833, u'N': 722, u'O': 778, u'P': 667, u'Q': 778, u'R': 722, u'S': 667, u'T': 611, u'U': 722, u'V': 667, u'W': 944, u'X': 667, u'Y': 667, u'Z': 611, u'[': 333, u'\\': 278, u']': 333, u'^': 584, u'_': 556, u'`': 333, u'a': 556, u'b': 611, u'c': 556, u'd': 611, u'e': 556, u'f': 333, u'g': 611, u'h': 611, u'i': 278, u'j': 278, u'k': 556, u'l': 278, u'm': 889, u'n': 611, u'o': 611, u'p': 611, u'q': 611, u'r': 389, u's': 556, u't': 333, u'u': 611, u'v': 556, u'w': 778, u'x': 556, u'y': 556, u'z': 500, u'{': 389, u'|': 280, u'}': 389, u'~': 584, u'\xa1': 333, u'\xa2': 556, u'\xa3': 556, u'\xa4': 556, u'\xa5': 556, u'\xa6': 280, u'\xa7': 556, u'\xa8': 333, u'\xa9': 737, u'\xaa': 370, u'\xab': 556, u'\xac': 584, u'\xae': 737, u'\xaf': 333, u'\xb0': 400, u'\xb1': 584, u'\xb2': 333, u'\xb3': 333, u'\xb4': 333, u'\xb5': 611, u'\xb6': 556, u'\xb7': 278, u'\xb8': 333, u'\xb9': 333, u'\xba': 365, u'\xbb': 556, u'\xbc': 834, u'\xbd': 834, u'\xbe': 834, u'\xbf': 611, u'\xc0': 722, u'\xc1': 722, u'\xc2': 722, u'\xc3': 722, u'\xc4': 722, u'\xc5': 722, u'\xc6': 1000, u'\xc7': 722, u'\xc8': 667, u'\xc9': 667, u'\xca': 667, u'\xcb': 667, u'\xcc': 278, u'\xcd': 278, u'\xce': 278, u'\xcf': 278, u'\xd0': 722, u'\xd1': 722, u'\xd2': 778, u'\xd3': 778, u'\xd4': 778, u'\xd5': 778, u'\xd6': 778, u'\xd7': 584, u'\xd8': 778, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 667, u'\xde': 667, u'\xdf': 611, u'\xe0': 556, u'\xe1': 556, u'\xe2': 556, u'\xe3': 556, u'\xe4': 556, u'\xe5': 556, u'\xe6': 889, u'\xe7': 556, u'\xe8': 556, u'\xe9': 556, u'\xea': 556, u'\xeb': 556, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 611, u'\xf1': 611, u'\xf2': 611, u'\xf3': 611, u'\xf4': 611, u'\xf5': 611, u'\xf6': 611, u'\xf7': 584, u'\xf8': 611, u'\xf9': 611, u'\xfa': 611, u'\xfb': 611, u'\xfc': 611, u'\xfd': 556, u'\xfe': 611, u'\xff': 556, u'\u0100': 722, u'\u0101': 556, u'\u0102': 722, u'\u0103': 556, u'\u0104': 722, u'\u0105': 556, u'\u0106': 722, u'\u0107': 556, u'\u010c': 722, u'\u010d': 556, u'\u010e': 722, u'\u010f': 743, u'\u0110': 722, u'\u0111': 611, u'\u0112': 667, u'\u0113': 556, u'\u0116': 667, u'\u0117': 556, u'\u0118': 667, u'\u0119': 556, u'\u011a': 667, u'\u011b': 556, u'\u011e': 778, u'\u011f': 611, u'\u0122': 778, u'\u0123': 611, u'\u012a': 278, u'\u012b': 278, u'\u012e': 278, u'\u012f': 278, u'\u0130': 278, u'\u0131': 278, u'\u0136': 722, u'\u0137': 556, u'\u0139': 611, u'\u013a': 278, u'\u013b': 611, u'\u013c': 278, u'\u013d': 611, u'\u013e': 400, u'\u0141': 611, u'\u0142': 278, u'\u0143': 722, u'\u0144': 611, u'\u0145': 722, u'\u0146': 611, u'\u0147': 722, u'\u0148': 611, u'\u014c': 778, u'\u014d': 611, u'\u0150': 778, u'\u0151': 611, u'\u0152': 1000, u'\u0153': 944, u'\u0154': 722, u'\u0155': 389, u'\u0156': 722, u'\u0157': 389, u'\u0158': 722, u'\u0159': 389, u'\u015a': 667, u'\u015b': 556, u'\u015e': 667, u'\u015f': 556, u'\u0160': 667, u'\u0161': 556, u'\u0162': 611, u'\u0163': 333, u'\u0164': 611, u'\u0165': 389, u'\u016a': 722, u'\u016b': 611, u'\u016e': 722, u'\u016f': 611, u'\u0170': 722, u'\u0171': 611, u'\u0172': 722, u'\u0173': 611, u'\u0178': 667, u'\u0179': 611, u'\u017a': 500, u'\u017b': 611, u'\u017c': 500, u'\u017d': 611, u'\u017e': 500, u'\u0192': 556, u'\u0218': 667, u'\u0219': 556, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 556, u'\u2014': 1000, u'\u2018': 278, u'\u2019': 278, u'\u201a': 278, u'\u201c': 500, u'\u201d': 500, u'\u201e': 500, u'\u2020': 556, u'\u2021': 556, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 1000, u'\u2202': 494, u'\u2206': 612, u'\u2211': 600, u'\u2212': 584, u'\u221a': 549, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 494, u'\uf6c3': 250, u'\ufb01': 611, u'\ufb02': 611}),
'Helvetica': ({'FontName': 'Helvetica', 'Descent': -207.0, 'FontBBox': (-166.0, -225.0, 1000.0, 931.0), 'FontWeight': 'Medium', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 523.0, 'ItalicAngle': 0.0, 'Ascent': 718.0}, {32: 278, 33: 278, 34: 355, 35: 556, 36: 556, 37: 889, 38: 667, 39: 222, 40: 333, 41: 333, 42: 389, 43: 584, 44: 278, 45: 333, 46: 278, 47: 278, 48: 556, 49: 556, 50: 556, 51: 556, 52: 556, 53: 556, 54: 556, 55: 556, 56: 556, 57: 556, 58: 278, 59: 278, 60: 584, 61: 584, 62: 584, 63: 556, 64: 1015, 65: 667, 66: 667, 67: 722, 68: 722, 69: 667, 70: 611, 71: 778, 72: 722, 73: 278, 74: 500, 75: 667, 76: 556, 77: 833, 78: 722, 79: 778, 80: 667, 81: 778, 82: 722, 83: 667, 84: 611, 85: 722, 86: 667, 87: 944, 88: 667, 89: 667, 90: 611, 91: 278, 92: 278, 93: 278, 94: 469, 95: 556, 96: 222, 97: 556, 98: 556, 99: 500, 100: 556, 101: 556, 102: 278, 103: 556, 104: 556, 105: 222, 106: 222, 107: 500, 108: 222, 109: 833, 110: 556, 111: 556, 112: 556, 113: 556, 114: 333, 115: 500, 116: 278, 117: 556, 118: 500, 119: 722, 120: 500, 121: 500, 122: 500, 123: 334, 124: 260, 125: 334, 126: 584, 161: 333, 162: 556, 163: 556, 164: 167, 165: 556, 166: 556, 167: 556, 168: 556, 169: 191, 170: 333, 171: 556, 172: 333, 173: 333, 174: 500, 175: 500, 177: 556, 178: 556, 179: 556, 180: 278, 182: 537, 183: 350, 184: 222, 185: 333, 186: 333, 187: 556, 188: 1000, 189: 1000, 191: 611, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 1000, 227: 370, 232: 556, 233: 778, 234: 1000, 235: 365, 241: 889, 245: 278, 248: 222, 249: 611, 250: 944, 251: 611}), 'Helvetica-Oblique': ({'FontName': 'Helvetica-Oblique', 'Descent': -207.0, 'FontBBox': (-171.0, -225.0, 1116.0, 931.0), 'FontWeight': 'Medium', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 523.0, 'ItalicAngle': -12.0, 'Ascent': 718.0}, {u' ': 278, u'!': 278, u'"': 355, u'#': 556, u'$': 556, u'%': 889, u'&': 667, u"'": 191, u'(': 333, u')': 333, u'*': 389, u'+': 584, u',': 278, u'-': 333, u'.': 278, u'/': 278, u'0': 556, u'1': 556, u'2': 556, u'3': 556, u'4': 556, u'5': 556, u'6': 556, u'7': 556, u'8': 556, u'9': 556, u':': 278, u';': 278, u'<': 584, u'=': 584, u'>': 584, u'?': 556, u'@': 1015, u'A': 667, u'B': 667, u'C': 722, u'D': 722, u'E': 667, u'F': 611, u'G': 778, u'H': 722, u'I': 278, u'J': 500, u'K': 667, u'L': 556, u'M': 833, u'N': 722, u'O': 778, u'P': 667, u'Q': 778, u'R': 722, u'S': 667, u'T': 611, u'U': 722, u'V': 667, u'W': 944, u'X': 667, u'Y': 667, u'Z': 611, u'[': 278, u'\\': 278, u']': 278, u'^': 469, u'_': 556, u'`': 333, u'a': 556, u'b': 556, u'c': 500, u'd': 556, u'e': 556, u'f': 278, u'g': 556, u'h': 556, u'i': 222, u'j': 222, u'k': 500, u'l': 222, u'm': 833, u'n': 556, u'o': 556, u'p': 556, u'q': 556, u'r': 333, u's': 500, u't': 278, u'u': 556, u'v': 500, u'w': 722, u'x': 500, u'y': 500, u'z': 500, u'{': 334, u'|': 260, u'}': 334, u'~': 584, u'\xa1': 333, u'\xa2': 556, u'\xa3': 556, u'\xa4': 556, u'\xa5': 556, u'\xa6': 260, u'\xa7': 556, u'\xa8': 333, u'\xa9': 737, u'\xaa': 370, u'\xab': 556, u'\xac': 584, u'\xae': 737, u'\xaf': 333, u'\xb0': 400, u'\xb1': 584, u'\xb2': 333, u'\xb3': 333, u'\xb4': 333, u'\xb5': 556, u'\xb6': 537, u'\xb7': 278, u'\xb8': 333, u'\xb9': 333, u'\xba': 365, u'\xbb': 556, u'\xbc': 834, u'\xbd': 834, u'\xbe': 834, u'\xbf': 611, u'\xc0': 667, u'\xc1': 667, u'\xc2': 667, u'\xc3': 667, u'\xc4': 667, u'\xc5': 667, u'\xc6': 1000, u'\xc7': 722, u'\xc8': 667, u'\xc9': 667, u'\xca': 667, u'\xcb': 667, u'\xcc': 278, u'\xcd': 278, u'\xce': 278, u'\xcf': 278, u'\xd0': 722, u'\xd1': 722, u'\xd2': 778, u'\xd3': 778, u'\xd4': 778, u'\xd5': 778, u'\xd6': 778, u'\xd7': 584, u'\xd8': 778, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 667, u'\xde': 667, u'\xdf': 611, u'\xe0': 556, u'\xe1': 556, u'\xe2': 556, u'\xe3': 556, u'\xe4': 556, u'\xe5': 556, u'\xe6': 889, u'\xe7': 500, u'\xe8': 556, u'\xe9': 556, u'\xea': 556, u'\xeb': 556, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 556, u'\xf1': 556, u'\xf2': 556, u'\xf3': 556, u'\xf4': 556, u'\xf5': 556, u'\xf6': 556, u'\xf7': 584, u'\xf8': 611, u'\xf9': 556, u'\xfa': 556, u'\xfb': 556, u'\xfc': 556, u'\xfd': 500, u'\xfe': 556, u'\xff': 500, u'\u0100': 667, u'\u0101': 556, u'\u0102': 667, u'\u0103': 556, u'\u0104': 667, u'\u0105': 556, u'\u0106': 722, u'\u0107': 500, u'\u010c': 722, u'\u010d': 500, u'\u010e': 722, u'\u010f': 643, u'\u0110': 722, u'\u0111': 556, u'\u0112': 667, u'\u0113': 556, u'\u0116': 667, u'\u0117': 556, u'\u0118': 667, u'\u0119': 556, u'\u011a': 667, u'\u011b': 556, u'\u011e': 778, u'\u011f': 556, u'\u0122': 778, u'\u0123': 556, u'\u012a': 278, u'\u012b': 278, u'\u012e': 278, u'\u012f': 222, u'\u0130': 278, u'\u0131': 278, u'\u0136': 667, u'\u0137': 500, u'\u0139': 556, u'\u013a': 222, u'\u013b': 556, u'\u013c': 222, u'\u013d': 556, u'\u013e': 299, u'\u0141': 556, u'\u0142': 222, u'\u0143': 722, u'\u0144': 556, u'\u0145': 722, u'\u0146': 556, u'\u0147': 722, u'\u0148': 556, u'\u014c': 778, u'\u014d': 556, u'\u0150': 778, u'\u0151': 556, u'\u0152': 1000, u'\u0153': 944, u'\u0154': 722, u'\u0155': 333, u'\u0156': 722, u'\u0157': 333, u'\u0158': 722, u'\u0159': 333, u'\u015a': 667, u'\u015b': 500, u'\u015e': 667, u'\u015f': 500, u'\u0160': 667, u'\u0161': 500, u'\u0162': 611, u'\u0163': 278, u'\u0164': 611, u'\u0165': 317, u'\u016a': 722, u'\u016b': 556, u'\u016e': 722, u'\u016f': 556, u'\u0170': 722, u'\u0171': 556, u'\u0172': 722, u'\u0173': 556, u'\u0178': 667, u'\u0179': 611, u'\u017a': 500, u'\u017b': 611, u'\u017c': 500, u'\u017d': 611, u'\u017e': 500, u'\u0192': 556, u'\u0218': 667, u'\u0219': 500, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 556, u'\u2014': 1000, u'\u2018': 222, u'\u2019': 222, u'\u201a': 222, u'\u201c': 333, u'\u201d': 333, u'\u201e': 333, u'\u2020': 556, u'\u2021': 556, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 1000, u'\u2202': 476, u'\u2206': 612, u'\u2211': 600, u'\u2212': 584, u'\u221a': 453, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 471, u'\uf6c3': 250, u'\ufb01': 500, u'\ufb02': 500}),
'Helvetica-BoldOblique': ({'FontName': 'Helvetica-BoldOblique', 'Descent': -207.0, 'FontBBox': (-175.0, -228.0, 1114.0, 962.0), 'FontWeight': 'Bold', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 532.0, 'ItalicAngle': -12.0, 'Ascent': 718.0}, {32: 278, 33: 333, 34: 474, 35: 556, 36: 556, 37: 889, 38: 722, 39: 278, 40: 333, 41: 333, 42: 389, 43: 584, 44: 278, 45: 333, 46: 278, 47: 278, 48: 556, 49: 556, 50: 556, 51: 556, 52: 556, 53: 556, 54: 556, 55: 556, 56: 556, 57: 556, 58: 333, 59: 333, 60: 584, 61: 584, 62: 584, 63: 611, 64: 975, 65: 722, 66: 722, 67: 722, 68: 722, 69: 667, 70: 611, 71: 778, 72: 722, 73: 278, 74: 556, 75: 722, 76: 611, 77: 833, 78: 722, 79: 778, 80: 667, 81: 778, 82: 722, 83: 667, 84: 611, 85: 722, 86: 667, 87: 944, 88: 667, 89: 667, 90: 611, 91: 333, 92: 278, 93: 333, 94: 584, 95: 556, 96: 278, 97: 556, 98: 611, 99: 556, 100: 611, 101: 556, 102: 333, 103: 611, 104: 611, 105: 278, 106: 278, 107: 556, 108: 278, 109: 889, 110: 611, 111: 611, 112: 611, 113: 611, 114: 389, 115: 556, 116: 333, 117: 611, 118: 556, 119: 778, 120: 556, 121: 556, 122: 500, 123: 389, 124: 280, 125: 389, 126: 584, 161: 333, 162: 556, 163: 556, 164: 167, 165: 556, 166: 556, 167: 556, 168: 556, 169: 238, 170: 500, 171: 556, 172: 333, 173: 333, 174: 611, 175: 611, 177: 556, 178: 556, 179: 556, 180: 278, 182: 556, 183: 350, 184: 278, 185: 500, 186: 500, 187: 556, 188: 1000, 189: 1000, 191: 611, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 1000, 227: 370, 232: 611, 233: 778, 234: 1000, 235: 365, 241: 889, 245: 278, 248: 278, 249: 611, 250: 944, 251: 611}), 'Symbol': ({'FontName': 'Symbol', 'FontBBox': (-180.0, -293.0, 1090.0, 1010.0), 'FontWeight': 'Medium', 'FontFamily': 'Symbol', 'Flags': 0, 'ItalicAngle': 0.0}, {u' ': 250, u'!': 333, u'#': 500, u'%': 833, u'&': 778, u'(': 333, u')': 333, u'+': 549, u',': 250, u'.': 250, u'/': 278, u'0': 500, u'1': 500, u'2': 500, u'3': 500, u'4': 500, u'5': 500, u'6': 500, u'7': 500, u'8': 500, u'9': 500, u':': 278, u';': 278, u'<': 549, u'=': 549, u'>': 549, u'?': 444, u'[': 333, u']': 333, u'_': 500, u'{': 480, u'|': 200, u'}': 480, u'\xac': 713, u'\xb0': 400, u'\xb1': 549, u'\xb5': 576, u'\xd7': 549, u'\xf7': 549, u'\u0192': 500, u'\u0391': 722, u'\u0392': 667, u'\u0393': 603, u'\u0395': 611, u'\u0396': 611, u'\u0397': 722, u'\u0398': 741, u'\u0399': 333, u'\u039a': 722, u'\u039b': 686, u'\u039c': 889, u'\u039d': 722, u'\u039e': 645, u'\u039f': 722, u'\u03a0': 768, u'\u03a1': 556, u'\u03a3': 592, u'\u03a4': 611, u'\u03a5': 690, u'\u03a6': 763, u'\u03a7': 722, u'\u03a8': 795, u'\u03b1': 631, u'\u03b2': 549, u'\u03b3': 411, u'\u03b4': 494, u'\u03b5': 439, u'\u03b6': 494, u'\u03b7': 603, u'\u03b8': 521, u'\u03b9': 329, u'\u03ba': 549, u'\u03bb': 549, u'\u03bd': 521, u'\u03be': 493, u'\u03bf': 549, u'\u03c0': 549, u'\u03c1': 549, u'\u03c2': 439, u'\u03c3': 603, u'\u03c4': 439, u'\u03c5': 576, u'\u03c6': 521, u'\u03c7': 549, u'\u03c8': 686, u'\u03c9': 686, u'\u03d1': 631, u'\u03d2': 620, u'\u03d5': 603, u'\u03d6': 713, u'\u2022': 460, u'\u2026': 1000, u'\u2032': 247, u'\u2033': 411, u'\u2044': 167, u'\u20ac': 750, u'\u2111': 686, u'\u2118': 987, u'\u211c': 795, u'\u2126': 768, u'\u2135': 823, u'\u2190': 987, u'\u2191': 603, u'\u2192': 987, u'\u2193': 603, u'\u2194': 1042, u'\u21b5': 658, u'\u21d0': 987, u'\u21d1': 603, u'\u21d2': 987, u'\u21d3': 603, u'\u21d4': 1042, u'\u2200': 713, u'\u2202': 494, u'\u2203': 549, u'\u2205': 823, u'\u2206': 612, u'\u2207': 713, u'\u2208': 713, u'\u2209': 713, u'\u220b': 439, u'\u220f': 823, u'\u2211': 713, u'\u2212': 549, u'\u2217': 500, u'\u221a': 549, u'\u221d': 713, u'\u221e': 713, u'\u2220': 768, u'\u2227': 603, u'\u2228': 603, u'\u2229': 768, u'\u222a': 768, u'\u222b': 274, u'\u2234': 863, u'\u223c': 549, u'\u2245': 549, u'\u2248': 549, u'\u2260': 549, u'\u2261': 549, u'\u2264': 549, u'\u2265': 549, u'\u2282': 713, u'\u2283': 713, u'\u2284': 713, u'\u2286': 713, u'\u2287': 713, u'\u2295': 768, u'\u2297': 768, u'\u22a5': 658, u'\u22c5': 250, u'\u2320': 686, u'\u2321': 686, u'\u2329': 329, u'\u232a': 329, u'\u25ca': 494, u'\u2660': 753, u'\u2663': 753, u'\u2665': 753, u'\u2666': 753, u'\uf6d9': 790, u'\uf6da': 790, u'\uf6db': 890, u'\uf8e5': 500, u'\uf8e6': 603, u'\uf8e7': 1000, u'\uf8e8': 790, u'\uf8e9': 790, u'\uf8ea': 786, u'\uf8eb': 384, u'\uf8ec': 384, u'\uf8ed': 384, u'\uf8ee': 384, u'\uf8ef': 384, u'\uf8f0': 384, u'\uf8f1': 494, u'\uf8f2': 494, u'\uf8f3': 494, u'\uf8f4': 494, u'\uf8f5': 686, u'\uf8f6': 384, u'\uf8f7': 384, u'\uf8f8': 384, u'\uf8f9': 384, u'\uf8fa': 384, u'\uf8fb': 384, u'\uf8fc': 494, u'\uf8fd': 494, u'\uf8fe': 494, u'\uf8ff': 790}),
'ZapfDingbats': ({'FontName': 'ZapfDingbats', 'FontBBox': (-1.0, -143.0, 981.0, 820.0), 'FontWeight': 'Medium', 'FontFamily': 'ITC', 'Flags': 0, 'ItalicAngle': 0.0}, {32: 278, 33: 974, 34: 961, 35: 974, 36: 980, 37: 719, 38: 789, 39: 790, 40: 791, 41: 690, 42: 960, 43: 939, 44: 549, 45: 855, 46: 911, 47: 933, 48: 911, 49: 945, 50: 974, 51: 755, 52: 846, 53: 762, 54: 761, 55: 571, 56: 677, 57: 763, 58: 760, 59: 759, 60: 754, 61: 494, 62: 552, 63: 537, 64: 577, 65: 692, 66: 786, 67: 788, 68: 788, 69: 790, 70: 793, 71: 794, 72: 816, 73: 823, 74: 789, 75: 841, 76: 823, 77: 833, 78: 816, 79: 831, 80: 923, 81: 744, 82: 723, 83: 749, 84: 790, 85: 792, 86: 695, 87: 776, 88: 768, 89: 792, 90: 759, 91: 707, 92: 708, 93: 682, 94: 701, 95: 826, 96: 815, 97: 789, 98: 789, 99: 707, 100: 687, 101: 696, 102: 689, 103: 786, 104: 787, 105: 713, 106: 791, 107: 785, 108: 791, 109: 873, 110: 761, 111: 762, 112: 762, 113: 759, 114: 759, 115: 892, 116: 892, 117: 788, 118: 784, 119: 438, 120: 138, 121: 277, 122: 415, 123: 392, 124: 392, 125: 668, 126: 668, 128: 390, 129: 390, 130: 317, 131: 317, 132: 276, 133: 276, 134: 509, 135: 509, 136: 410, 137: 410, 138: 234, 139: 234, 140: 334, 141: 334, 161: 732, 162: 544, 163: 544, 164: 910, 165: 667, 166: 760, 167: 760, 168: 776, 169: 595, 170: 694, 171: 626, 172: 788, 173: 788, 174: 788, 175: 788, 176: 788, 177: 788, 178: 788, 179: 788, 180: 788, 181: 788, 182: 788, 183: 788, 184: 788, 185: 788, 186: 788, 187: 788, 188: 788, 189: 788, 190: 788, 191: 788, 192: 788, 193: 788, 194: 788, 195: 788, 196: 788, 197: 788, 198: 788, 199: 788, 200: 788, 201: 788, 202: 788, 203: 788, 204: 788, 205: 788, 206: 788, 207: 788, 208: 788, 209: 788, 210: 788, 211: 788, 212: 894, 213: 838, 214: 1016, 215: 458, 216: 748, 217: 924, 218: 748, 219: 918, 220: 927, 221: 928, 222: 928, 223: 834, 224: 873, 225: 828, 226: 924, 227: 924, 228: 917, 229: 930, 230: 931, 231: 463, 232: 883, 233: 836, 234: 836, 235: 867, 236: 867, 237: 696, 238: 696, 239: 874, 241: 874, 242: 760, 243: 946, 244: 771, 245: 865, 246: 771, 247: 888, 248: 967, 249: 888, 250: 831, 251: 873, 252: 927, 253: 970, 254: 918}), 'Times-Bold': ({'FontName': 'Times-Bold', 'Descent': -217.0, 'FontBBox': (-168.0, -218.0, 1000.0, 935.0), 'FontWeight': 'Bold', 'CapHeight': 676.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 461.0, 'ItalicAngle': 0.0, 'Ascent': 683.0}, {u' ': 250, u'!': 333, u'"': 555, u'#': 500, u'$': 500, u'%': 1000, u'&': 833, u"'": 278, u'(': 333, u')': 333, u'*': 500, u'+': 570, u',': 250, u'-': 333, u'.': 250, u'/': 278, u'0': 500, u'1': 500, u'2': 500, u'3': 500, u'4': 500, u'5': 500, u'6': 500, u'7': 500, u'8': 500, u'9': 500, u':': 333, u';': 333, u'<': 570, u'=': 570, u'>': 570, u'?': 500, u'@': 930, u'A': 722, u'B': 667, u'C': 722, u'D': 722, u'E': 667, u'F': 611, u'G': 778, u'H': 778, u'I': 389, u'J': 500, u'K': 778, u'L': 667, u'M': 944, u'N': 722, u'O': 778, u'P': 611, u'Q': 778, u'R': 722, u'S': 556, u'T': 667, u'U': 722, u'V': 722, u'W': 1000, u'X': 722, u'Y': 722, u'Z': 667, u'[': 333, u'\\': 278, u']': 333, u'^': 581, u'_': 500, u'`': 333, u'a': 500, u'b': 556, u'c': 444, u'd': 556, u'e': 444, u'f': 333, u'g': 500, u'h': 556, u'i': 278, u'j': 333, u'k': 556, u'l': 278, u'm': 833, u'n': 556, u'o': 500, u'p': 556, u'q': 556, u'r': 444, u's': 389, u't': 333, u'u': 556, u'v': 500, u'w': 722, u'x': 500, u'y': 500, u'z': 444, u'{': 394, u'|': 220, u'}': 394, u'~': 520, u'\xa1': 333, u'\xa2': 500, u'\xa3': 500, u'\xa4': 500, u'\xa5': 500, u'\xa6': 220, u'\xa7': 500, u'\xa8': 333, u'\xa9': 747, u'\xaa': 300, u'\xab': 500, u'\xac': 570, u'\xae': 747, u'\xaf': 333, u'\xb0': 400, u'\xb1': 570, u'\xb2': 300, u'\xb3': 300, u'\xb4': 333, u'\xb5': 556, u'\xb6': 540, u'\xb7': 250, u'\xb8': 333, u'\xb9': 300, u'\xba': 330, u'\xbb': 500, u'\xbc': 750, u'\xbd': 750, u'\xbe': 750, u'\xbf': 500, u'\xc0': 722, u'\xc1': 722, u'\xc2': 722, u'\xc3': 722, u'\xc4': 722, u'\xc5': 722, u'\xc6': 1000, u'\xc7': 722, u'\xc8': 667, u'\xc9': 667, u'\xca': 667, u'\xcb': 667, u'\xcc': 389, u'\xcd': 389, u'\xce': 389, u'\xcf': 389, u'\xd0': 722, u'\xd1': 722, u'\xd2': 778, u'\xd3': 778, u'\xd4': 778, u'\xd5': 778, u'\xd6': 778, u'\xd7': 570, u'\xd8': 778, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 722, u'\xde': 611, u'\xdf': 556, u'\xe0': 500, u'\xe1': 500, u'\xe2': 500, u'\xe3': 500, u'\xe4': 500, u'\xe5': 500, u'\xe6': 722, u'\xe7': 444, u'\xe8': 444, u'\xe9': 444, u'\xea': 444, u'\xeb': 444, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 500, u'\xf1': 556, u'\xf2': 500, u'\xf3': 500, u'\xf4': 500, u'\xf5': 500, u'\xf6': 500, u'\xf7': 570, u'\xf8': 500, u'\xf9': 556, u'\xfa': 556, u'\xfb': 556, u'\xfc': 556, u'\xfd': 500, u'\xfe': 556, u'\xff': 500, u'\u0100': 722, u'\u0101': 500, u'\u0102': 722, u'\u0103': 500, u'\u0104': 722, u'\u0105': 500, u'\u0106': 722, u'\u0107': 444, u'\u010c': 722, u'\u010d': 444, u'\u010e': 722, u'\u010f': 672, u'\u0110': 722, u'\u0111': 556, u'\u0112': 667, u'\u0113': 444, u'\u0116': 667, u'\u0117': 444, u'\u0118': 667, u'\u0119': 444, u'\u011a': 667, u'\u011b': 444, u'\u011e': 778, u'\u011f': 500, u'\u0122': 778, u'\u0123': 500, u'\u012a': 389, u'\u012b': 278, u'\u012e': 389, u'\u012f': 278, u'\u0130': 389, u'\u0131': 278, u'\u0136': 778, u'\u0137': 556, u'\u0139': 667, u'\u013a': 278, u'\u013b': 667, u'\u013c': 278, u'\u013d': 667, u'\u013e': 394, u'\u0141': 667, u'\u0142': 278, u'\u0143': 722, u'\u0144': 556, u'\u0145': 722, u'\u0146': 556, u'\u0147': 722, u'\u0148': 556, u'\u014c': 778, u'\u014d': 500, u'\u0150': 778, u'\u0151': 500, u'\u0152': 1000, u'\u0153': 722, u'\u0154': 722, u'\u0155': 444, u'\u0156': 722, u'\u0157': 444, u'\u0158': 722, u'\u0159': 444, u'\u015a': 556, u'\u015b': 389, u'\u015e': 556, u'\u015f': 389, u'\u0160': 556, u'\u0161': 389, u'\u0162': 667, u'\u0163': 333, u'\u0164': 667, u'\u0165': 416, u'\u016a': 722, u'\u016b': 556, u'\u016e': 722, u'\u016f': 556, u'\u0170': 722, u'\u0171': 556, u'\u0172': 722, u'\u0173': 556, u'\u0178': 722, u'\u0179': 667, u'\u017a': 444, u'\u017b': 667, u'\u017c': 444, u'\u017d': 667, u'\u017e': 444, u'\u0192': 500, u'\u0218': 556, u'\u0219': 389, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 500, u'\u2014': 1000, u'\u2018': 333, u'\u2019': 333, u'\u201a': 333, u'\u201c': 500, u'\u201d': 500, u'\u201e': 500, u'\u2020': 500, u'\u2021': 500, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 1000, u'\u2202': 494, u'\u2206': 612, u'\u2211': 600, u'\u2212': 570, u'\u221a': 549, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 494, u'\uf6c3': 250, u'\ufb01': 556, u'\ufb02': 556}),
'Courier-Bold': ({'FontName': 'Courier-Bold', 'Descent': -194.0, 'FontBBox': (-88.0, -249.0, 697.0, 811.0), 'FontWeight': 'Bold', 'CapHeight': 572.0, 'FontFamily': 'Courier', 'Flags': 64, 'XHeight': 434.0, 'ItalicAngle': 0.0, 'Ascent': 627.0}, {32: 600, 33: 600, 34: 600, 35: 600, 36: 600, 37: 600, 38: 600, 39: 600, 40: 600, 41: 600, 42: 600, 43: 600, 44: 600, 45: 600, 46: 600, 47: 600, 48: 600, 49: 600, 50: 600, 51: 600, 52: 600, 53: 600, 54: 600, 55: 600, 56: 600, 57: 600, 58: 600, 59: 600, 60: 600, 61: 600, 62: 600, 63: 600, 64: 600, 65: 600, 66: 600, 67: 600, 68: 600, 69: 600, 70: 600, 71: 600, 72: 600, 73: 600, 74: 600, 75: 600, 76: 600, 77: 600, 78: 600, 79: 600, 80: 600, 81: 600, 82: 600, 83: 600, 84: 600, 85: 600, 86: 600, 87: 600, 88: 600, 89: 600, 90: 600, 91: 600, 92: 600, 93: 600, 94: 600, 95: 600, 96: 600, 97: 600, 98: 600, 99: 600, 100: 600, 101: 600, 102: 600, 103: 600, 104: 600, 105: 600, 106: 600, 107: 600, 108: 600, 109: 600, 110: 600, 111: 600, 112: 600, 113: 600, 114: 600, 115: 600, 116: 600, 117: 600, 118: 600, 119: 600, 120: 600, 121: 600, 122: 600, 123: 600, 124: 600, 125: 600, 126: 600, 161: 600, 162: 600, 163: 600, 164: 600, 165: 600, 166: 600, 167: 600, 168: 600, 169: 600, 170: 600, 171: 600, 172: 600, 173: 600, 174: 600, 175: 600, 177: 600, 178: 600, 179: 600, 180: 600, 182: 600, 183: 600, 184: 600, 185: 600, 186: 600, 187: 600, 188: 600, 189: 600, 191: 600, 193: 600, 194: 600, 195: 600, 196: 600, 197: 600, 198: 600, 199: 600, 200: 600, 202: 600, 203: 600, 205: 600, 206: 600, 207: 600, 208: 600, 225: 600, 227: 600, 232: 600, 233: 600, 234: 600, 235: 600, 241: 600, 245: 600, 248: 600, 249: 600, 250: 600, 251: 600}), 'Times-BoldItalic': ({'FontName': 'Times-BoldItalic', 'Descent': -217.0, 'FontBBox': (-200.0, -218.0, 996.0, 921.0), 'FontWeight': 'Bold', 'CapHeight': 669.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 462.0, 'ItalicAngle': -15.0, 'Ascent': 683.0}, {u' ': 250, u'!': 389, u'"': 555, u'#': 500, u'$': 500, u'%': 833, u'&': 778, u"'": 278, u'(': 333, u')': 333, u'*': 500, u'+': 570, u',': 250, u'-': 333, u'.': 250, u'/': 278, u'0': 500, u'1': 500, u'2': 500, u'3': 500, u'4': 500, u'5': 500, u'6': 500, u'7': 500, u'8': 500, u'9': 500, u':': 333, u';': 333, u'<': 570, u'=': 570, u'>': 570, u'?': 500, u'@': 832, u'A': 667, u'B': 667, u'C': 667, u'D': 722, u'E': 667, u'F': 667, u'G': 722, u'H': 778, u'I': 389, u'J': 500, u'K': 667, u'L': 611, u'M': 889, u'N': 722, u'O': 722, u'P': 611, u'Q': 722, u'R': 667, u'S': 556, u'T': 611, u'U': 722, u'V': 667, u'W': 889, u'X': 667, u'Y': 611, u'Z': 611, u'[': 333, u'\\': 278, u']': 333, u'^': 570, u'_': 500, u'`': 333, u'a': 500, u'b': 500, u'c': 444, u'd': 500, u'e': 444, u'f': 333, u'g': 500, u'h': 556, u'i': 278, u'j': 278, u'k': 500, u'l': 278, u'm': 778, u'n': 556, u'o': 500, u'p': 500, u'q': 500, u'r': 389, u's': 389, u't': 278, u'u': 556, u'v': 444, u'w': 667, u'x': 500, u'y': 444, u'z': 389, u'{': 348, u'|': 220, u'}': 348, u'~': 570, u'\xa1': 389, u'\xa2': 500, u'\xa3': 500, u'\xa4': 500, u'\xa5': 500, u'\xa6': 220, u'\xa7': 500, u'\xa8': 333, u'\xa9': 747, u'\xaa': 266, u'\xab': 500, u'\xac': 606, u'\xae': 747, u'\xaf': 333, u'\xb0': 400, u'\xb1': 570, u'\xb2': 300, u'\xb3': 300, u'\xb4': 333, u'\xb5': 576, u'\xb6': 500, u'\xb7': 250, u'\xb8': 333, u'\xb9': 300, u'\xba': 300, u'\xbb': 500, u'\xbc': 750, u'\xbd': 750, u'\xbe': 750, u'\xbf': 500, u'\xc0': 667, u'\xc1': 667, u'\xc2': 667, u'\xc3': 667, u'\xc4': 667, u'\xc5': 667, u'\xc6': 944, u'\xc7': 667, u'\xc8': 667, u'\xc9': 667, u'\xca': 667, u'\xcb': 667, u'\xcc': 389, u'\xcd': 389, u'\xce': 389, u'\xcf': 389, u'\xd0': 722, u'\xd1': 722, u'\xd2': 722, u'\xd3': 722, u'\xd4': 722, u'\xd5': 722, u'\xd6': 722, u'\xd7': 570, u'\xd8': 722, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 611, u'\xde': 611, u'\xdf': 500, u'\xe0': 500, u'\xe1': 500, u'\xe2': 500, u'\xe3': 500, u'\xe4': 500, u'\xe5': 500, u'\xe6': 722, u'\xe7': 444, u'\xe8': 444, u'\xe9': 444, u'\xea': 444, u'\xeb': 444, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 500, u'\xf1': 556, u'\xf2': 500, u'\xf3': 500, u'\xf4': 500, u'\xf5': 500, u'\xf6': 500, u'\xf7': 570, u'\xf8': 500, u'\xf9': 556, u'\xfa': 556, u'\xfb': 556, u'\xfc': 556, u'\xfd': 444, u'\xfe': 500, u'\xff': 444, u'\u0100': 667, u'\u0101': 500, u'\u0102': 667, u'\u0103': 500, u'\u0104': 667, u'\u0105': 500, u'\u0106': 667, u'\u0107': 444, u'\u010c': 667, u'\u010d': 444, u'\u010e': 722, u'\u010f': 608, u'\u0110': 722, u'\u0111': 500, u'\u0112': 667, u'\u0113': 444, u'\u0116': 667, u'\u0117': 444, u'\u0118': 667, u'\u0119': 444, u'\u011a': 667, u'\u011b': 444, u'\u011e': 722, u'\u011f': 500, u'\u0122': 722, u'\u0123': 500, u'\u012a': 389, u'\u012b': 278, u'\u012e': 389, u'\u012f': 278, u'\u0130': 389, u'\u0131': 278, u'\u0136': 667, u'\u0137': 500, u'\u0139': 611, u'\u013a': 278, u'\u013b': 611, u'\u013c': 278, u'\u013d': 611, u'\u013e': 382, u'\u0141': 611, u'\u0142': 278, u'\u0143': 722, u'\u0144': 556, u'\u0145': 722, u'\u0146': 556, u'\u0147': 722, u'\u0148': 556, u'\u014c': 722, u'\u014d': 500, u'\u0150': 722, u'\u0151': 500, u'\u0152': 944, u'\u0153': 722, u'\u0154': 667, u'\u0155': 389, u'\u0156': 667, u'\u0157': 389, u'\u0158': 667, u'\u0159': 389, u'\u015a': 556, u'\u015b': 389, u'\u015e': 556, u'\u015f': 389, u'\u0160': 556, u'\u0161': 389, u'\u0162': 611, u'\u0163': 278, u'\u0164': 611, u'\u0165': 366, u'\u016a': 722, u'\u016b': 556, u'\u016e': 722, u'\u016f': 556, u'\u0170': 722, u'\u0171': 556, u'\u0172': 722, u'\u0173': 556, u'\u0178': 611, u'\u0179': 611, u'\u017a': 389, u'\u017b': 611, u'\u017c': 389, u'\u017d': 611, u'\u017e': 389, u'\u0192': 500, u'\u0218': 556, u'\u0219': 389, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 500, u'\u2014': 1000, u'\u2018': 333, u'\u2019': 333, u'\u201a': 333, u'\u201c': 500, u'\u201d': 500, u'\u201e': 500, u'\u2020': 500, u'\u2021': 500, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 1000, u'\u2202': 494, u'\u2206': 612, u'\u2211': 600, u'\u2212': 606, u'\u221a': 549, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 494, u'\uf6c3': 250, u'\ufb01': 556, u'\ufb02': 556}),
'Times-Italic': ({'FontName': 'Times-Italic', 'Descent': -217.0, 'FontBBox': (-169.0, -217.0, 1010.0, 883.0), 'FontWeight': 'Medium', 'CapHeight': 653.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 441.0, 'ItalicAngle': -15.5, 'Ascent': 683.0}, {32: 250, 33: 333, 34: 420, 35: 500, 36: 500, 37: 833, 38: 778, 39: 333, 40: 333, 41: 333, 42: 500, 43: 675, 44: 250, 45: 333, 46: 250, 47: 278, 48: 500, 49: 500, 50: 500, 51: 500, 52: 500, 53: 500, 54: 500, 55: 500, 56: 500, 57: 500, 58: 333, 59: 333, 60: 675, 61: 675, 62: 675, 63: 500, 64: 920, 65: 611, 66: 611, 67: 667, 68: 722, 69: 611, 70: 611, 71: 722, 72: 722, 73: 333, 74: 444, 75: 667, 76: 556, 77: 833, 78: 667, 79: 722, 80: 611, 81: 722, 82: 611, 83: 500, 84: 556, 85: 722, 86: 611, 87: 833, 88: 611, 89: 556, 90: 556, 91: 389, 92: 278, 93: 389, 94: 422, 95: 500, 96: 333, 97: 500, 98: 500, 99: 444, 100: 500, 101: 444, 102: 278, 103: 500, 104: 500, 105: 278, 106: 278, 107: 444, 108: 278, 109: 722, 110: 500, 111: 500, 112: 500, 113: 500, 114: 389, 115: 389, 116: 278, 117: 500, 118: 444, 119: 667, 120: 444, 121: 444, 122: 389, 123: 400, 124: 275, 125: 400, 126: 541, 161: 389, 162: 500, 163: 500, 164: 167, 165: 500, 166: 500, 167: 500, 168: 500, 169: 214, 170: 556, 171: 500, 172: 333, 173: 333, 174: 500, 175: 500, 177: 500, 178: 500, 179: 500, 180: 250, 182: 523, 183: 350, 184: 333, 185: 556, 186: 556, 187: 500, 188: 889, 189: 1000, 191: 500, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 889, 225: 889, 227: 276, 232: 556, 233: 722, 234: 944, 235: 310, 241: 667, 245: 278, 248: 278, 249: 500, 250: 667, 251: 500}), 'Times-Italic': ({'FontName': 'Times-Italic', 'Descent': -217.0, 'FontBBox': (-169.0, -217.0, 1010.0, 883.0), 'FontWeight': 'Medium', 'CapHeight': 653.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 441.0, 'ItalicAngle': -15.5, 'Ascent': 683.0}, {u' ': 250, u'!': 333, u'"': 420, u'#': 500, u'$': 500, u'%': 833, u'&': 778, u"'": 214, u'(': 333, u')': 333, u'*': 500, u'+': 675, u',': 250, u'-': 333, u'.': 250, u'/': 278, u'0': 500, u'1': 500, u'2': 500, u'3': 500, u'4': 500, u'5': 500, u'6': 500, u'7': 500, u'8': 500, u'9': 500, u':': 333, u';': 333, u'<': 675, u'=': 675, u'>': 675, u'?': 500, u'@': 920, u'A': 611, u'B': 611, u'C': 667, u'D': 722, u'E': 611, u'F': 611, u'G': 722, u'H': 722, u'I': 333, u'J': 444, u'K': 667, u'L': 556, u'M': 833, u'N': 667, u'O': 722, u'P': 611, u'Q': 722, u'R': 611, u'S': 500, u'T': 556, u'U': 722, u'V': 611, u'W': 833, u'X': 611, u'Y': 556, u'Z': 556, u'[': 389, u'\\': 278, u']': 389, u'^': 422, u'_': 500, u'`': 333, u'a': 500, u'b': 500, u'c': 444, u'd': 500, u'e': 444, u'f': 278, u'g': 500, u'h': 500, u'i': 278, u'j': 278, u'k': 444, u'l': 278, u'm': 722, u'n': 500, u'o': 500, u'p': 500, u'q': 500, u'r': 389, u's': 389, u't': 278, u'u': 500, u'v': 444, u'w': 667, u'x': 444, u'y': 444, u'z': 389, u'{': 400, u'|': 275, u'}': 400, u'~': 541, u'\xa1': 389, u'\xa2': 500, u'\xa3': 500, u'\xa4': 500, u'\xa5': 500, u'\xa6': 275, u'\xa7': 500, u'\xa8': 333, u'\xa9': 760, u'\xaa': 276, u'\xab': 500, u'\xac': 675, u'\xae': 760, u'\xaf': 333, u'\xb0': 400, u'\xb1': 675, u'\xb2': 300, u'\xb3': 300, u'\xb4': 333, u'\xb5': 500, u'\xb6': 523, u'\xb7': 250, u'\xb8': 333, u'\xb9': 300, u'\xba': 310, u'\xbb': 500, u'\xbc': 750, u'\xbd': 750, u'\xbe': 750, u'\xbf': 500, u'\xc0': 611, u'\xc1': 611, u'\xc2': 611, u'\xc3': 611, u'\xc4': 611, u'\xc5': 611, u'\xc6': 889, u'\xc7': 667, u'\xc8': 611, u'\xc9': 611, u'\xca': 611, u'\xcb': 611, u'\xcc': 333, u'\xcd': 333, u'\xce': 333, u'\xcf': 333, u'\xd0': 722, u'\xd1': 667, u'\xd2': 722, u'\xd3': 722, u'\xd4': 722, u'\xd5': 722, u'\xd6': 722, u'\xd7': 675, u'\xd8': 722, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 556, u'\xde': 611, u'\xdf': 500, u'\xe0': 500, u'\xe1': 500, u'\xe2': 500, u'\xe3': 500, u'\xe4': 500, u'\xe5': 500, u'\xe6': 667, u'\xe7': 444, u'\xe8': 444, u'\xe9': 444, u'\xea': 444, u'\xeb': 444, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 500, u'\xf1': 500, u'\xf2': 500, u'\xf3': 500, u'\xf4': 500, u'\xf5': 500, u'\xf6': 500, u'\xf7': 675, u'\xf8': 500, u'\xf9': 500, u'\xfa': 500, u'\xfb': 500, u'\xfc': 500, u'\xfd': 444, u'\xfe': 500, u'\xff': 444, u'\u0100': 611, u'\u0101': 500, u'\u0102': 611, u'\u0103': 500, u'\u0104': 611, u'\u0105': 500, u'\u0106': 667, u'\u0107': 444, u'\u010c': 667, u'\u010d': 444, u'\u010e': 722, u'\u010f': 544, u'\u0110': 722, u'\u0111': 500, u'\u0112': 611, u'\u0113': 444, u'\u0116': 611, u'\u0117': 444, u'\u0118': 611, u'\u0119': 444, u'\u011a': 611, u'\u011b': 444, u'\u011e': 722, u'\u011f': 500, u'\u0122': 722, u'\u0123': 500, u'\u012a': 333, u'\u012b': 278, u'\u012e': 333, u'\u012f': 278, u'\u0130': 333, u'\u0131': 278, u'\u0136': 667, u'\u0137': 444, u'\u0139': 556, u'\u013a': 278, u'\u013b': 556, u'\u013c': 278, u'\u013d': 611, u'\u013e': 300, u'\u0141': 556, u'\u0142': 278, u'\u0143': 667, u'\u0144': 500, u'\u0145': 667, u'\u0146': 500, u'\u0147': 667, u'\u0148': 500, u'\u014c': 722, u'\u014d': 500, u'\u0150': 722, u'\u0151': 500, u'\u0152': 944, u'\u0153': 667, u'\u0154': 611, u'\u0155': 389, u'\u0156': 611, u'\u0157': 389, u'\u0158': 611, u'\u0159': 389, u'\u015a': 500, u'\u015b': 389, u'\u015e': 500, u'\u015f': 389, u'\u0160': 500, u'\u0161': 389, u'\u0162': 556, u'\u0163': 278, u'\u0164': 556, u'\u0165': 300, u'\u016a': 722, u'\u016b': 500, u'\u016e': 722, u'\u016f': 500, u'\u0170': 722, u'\u0171': 500, u'\u0172': 722, u'\u0173': 500, u'\u0178': 556, u'\u0179': 556, u'\u017a': 389, u'\u017b': 556, u'\u017c': 389, u'\u017d': 556, u'\u017e': 389, u'\u0192': 500, u'\u0218': 500, u'\u0219': 389, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 500, u'\u2014': 889, u'\u2018': 333, u'\u2019': 333, u'\u201a': 333, u'\u201c': 556, u'\u201d': 556, u'\u201e': 556, u'\u2020': 500, u'\u2021': 500, u'\u2022': 350, u'\u2026': 889, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 980, u'\u2202': 476, u'\u2206': 612, u'\u2211': 600, u'\u2212': 675, u'\u221a': 453, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 471, u'\uf6c3': 250, u'\ufb01': 500, u'\ufb02': 500}),
'Times-Roman': ({'FontName': 'Times-Roman', 'Descent': -217.0, 'FontBBox': (-168.0, -218.0, 1000.0, 898.0), 'FontWeight': 'Roman', 'CapHeight': 662.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 450.0, 'ItalicAngle': 0.0, 'Ascent': 683.0}, {32: 250, 33: 333, 34: 408, 35: 500, 36: 500, 37: 833, 38: 778, 39: 333, 40: 333, 41: 333, 42: 500, 43: 564, 44: 250, 45: 333, 46: 250, 47: 278, 48: 500, 49: 500, 50: 500, 51: 500, 52: 500, 53: 500, 54: 500, 55: 500, 56: 500, 57: 500, 58: 278, 59: 278, 60: 564, 61: 564, 62: 564, 63: 444, 64: 921, 65: 722, 66: 667, 67: 667, 68: 722, 69: 611, 70: 556, 71: 722, 72: 722, 73: 333, 74: 389, 75: 722, 76: 611, 77: 889, 78: 722, 79: 722, 80: 556, 81: 722, 82: 667, 83: 556, 84: 611, 85: 722, 86: 722, 87: 944, 88: 722, 89: 722, 90: 611, 91: 333, 92: 278, 93: 333, 94: 469, 95: 500, 96: 333, 97: 444, 98: 500, 99: 444, 100: 500, 101: 444, 102: 333, 103: 500, 104: 500, 105: 278, 106: 278, 107: 500, 108: 278, 109: 778, 110: 500, 111: 500, 112: 500, 113: 500, 114: 333, 115: 389, 116: 278, 117: 500, 118: 500, 119: 722, 120: 500, 121: 500, 122: 444, 123: 480, 124: 200, 125: 480, 126: 541, 161: 333, 162: 500, 163: 500, 164: 167, 165: 500, 166: 500, 167: 500, 168: 500, 169: 180, 170: 444, 171: 500, 172: 333, 173: 333, 174: 556, 175: 556, 177: 500, 178: 500, 179: 500, 180: 250, 182: 453, 183: 350, 184: 333, 185: 444, 186: 444, 187: 500, 188: 1000, 189: 1000, 191: 444, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 889, 227: 276, 232: 611, 233: 722, 234: 889, 235: 310, 241: 667, 245: 278, 248: 278, 249: 500, 250: 722, 251: 500}), 'Times-Roman': ({'FontName': 'Times-Roman', 'Descent': -217.0, 'FontBBox': (-168.0, -218.0, 1000.0, 898.0), 'FontWeight': 'Roman', 'CapHeight': 662.0, 'FontFamily': 'Times', 'Flags': 0, 'XHeight': 450.0, 'ItalicAngle': 0.0, 'Ascent': 683.0}, {u' ': 250, u'!': 333, u'"': 408, u'#': 500, u'$': 500, u'%': 833, u'&': 778, u"'": 180, u'(': 333, u')': 333, u'*': 500, u'+': 564, u',': 250, u'-': 333, u'.': 250, u'/': 278, u'0': 500, u'1': 500, u'2': 500, u'3': 500, u'4': 500, u'5': 500, u'6': 500, u'7': 500, u'8': 500, u'9': 500, u':': 278, u';': 278, u'<': 564, u'=': 564, u'>': 564, u'?': 444, u'@': 921, u'A': 722, u'B': 667, u'C': 667, u'D': 722, u'E': 611, u'F': 556, u'G': 722, u'H': 722, u'I': 333, u'J': 389, u'K': 722, u'L': 611, u'M': 889, u'N': 722, u'O': 722, u'P': 556, u'Q': 722, u'R': 667, u'S': 556, u'T': 611, u'U': 722, u'V': 722, u'W': 944, u'X': 722, u'Y': 722, u'Z': 611, u'[': 333, u'\\': 278, u']': 333, u'^': 469, u'_': 500, u'`': 333, u'a': 444, u'b': 500, u'c': 444, u'd': 500, u'e': 444, u'f': 333, u'g': 500, u'h': 500, u'i': 278, u'j': 278, u'k': 500, u'l': 278, u'm': 778, u'n': 500, u'o': 500, u'p': 500, u'q': 500, u'r': 333, u's': 389, u't': 278, u'u': 500, u'v': 500, u'w': 722, u'x': 500, u'y': 500, u'z': 444, u'{': 480, u'|': 200, u'}': 480, u'~': 541, u'\xa1': 333, u'\xa2': 500, u'\xa3': 500, u'\xa4': 500, u'\xa5': 500, u'\xa6': 200, u'\xa7': 500, u'\xa8': 333, u'\xa9': 760, u'\xaa': 276, u'\xab': 500, u'\xac': 564, u'\xae': 760, u'\xaf': 333, u'\xb0': 400, u'\xb1': 564, u'\xb2': 300, u'\xb3': 300, u'\xb4': 333, u'\xb5': 500, u'\xb6': 453, u'\xb7': 250, u'\xb8': 333, u'\xb9': 300, u'\xba': 310, u'\xbb': 500, u'\xbc': 750, u'\xbd': 750, u'\xbe': 750, u'\xbf': 444, u'\xc0': 722, u'\xc1': 722, u'\xc2': 722, u'\xc3': 722, u'\xc4': 722, u'\xc5': 722, u'\xc6': 889, u'\xc7': 667, u'\xc8': 611, u'\xc9': 611, u'\xca': 611, u'\xcb': 611, u'\xcc': 333, u'\xcd': 333, u'\xce': 333, u'\xcf': 333, u'\xd0': 722, u'\xd1': 722, u'\xd2': 722, u'\xd3': 722, u'\xd4': 722, u'\xd5': 722, u'\xd6': 722, u'\xd7': 564, u'\xd8': 722, u'\xd9': 722, u'\xda': 722, u'\xdb': 722, u'\xdc': 722, u'\xdd': 722, u'\xde': 556, u'\xdf': 500, u'\xe0': 444, u'\xe1': 444, u'\xe2': 444, u'\xe3': 444, u'\xe4': 444, u'\xe5': 444, u'\xe6': 667, u'\xe7': 444, u'\xe8': 444, u'\xe9': 444, u'\xea': 444, u'\xeb': 444, u'\xec': 278, u'\xed': 278, u'\xee': 278, u'\xef': 278, u'\xf0': 500, u'\xf1': 500, u'\xf2': 500, u'\xf3': 500, u'\xf4': 500, u'\xf5': 500, u'\xf6': 500, u'\xf7': 564, u'\xf8': 500, u'\xf9': 500, u'\xfa': 500, u'\xfb': 500, u'\xfc': 500, u'\xfd': 500, u'\xfe': 500, u'\xff': 500, u'\u0100': 722, u'\u0101': 444, u'\u0102': 722, u'\u0103': 444, u'\u0104': 722, u'\u0105': 444, u'\u0106': 667, u'\u0107': 444, u'\u010c': 667, u'\u010d': 444, u'\u010e': 722, u'\u010f': 588, u'\u0110': 722, u'\u0111': 500, u'\u0112': 611, u'\u0113': 444, u'\u0116': 611, u'\u0117': 444, u'\u0118': 611, u'\u0119': 444, u'\u011a': 611, u'\u011b': 444, u'\u011e': 722, u'\u011f': 500, u'\u0122': 722, u'\u0123': 500, u'\u012a': 333, u'\u012b': 278, u'\u012e': 333, u'\u012f': 278, u'\u0130': 333, u'\u0131': 278, u'\u0136': 722, u'\u0137': 500, u'\u0139': 611, u'\u013a': 278, u'\u013b': 611, u'\u013c': 278, u'\u013d': 611, u'\u013e': 344, u'\u0141': 611, u'\u0142': 278, u'\u0143': 722, u'\u0144': 500, u'\u0145': 722, u'\u0146': 500, u'\u0147': 722, u'\u0148': 500, u'\u014c': 722, u'\u014d': 500, u'\u0150': 722, u'\u0151': 500, u'\u0152': 889, u'\u0153': 722, u'\u0154': 667, u'\u0155': 333, u'\u0156': 667, u'\u0157': 333, u'\u0158': 667, u'\u0159': 333, u'\u015a': 556, u'\u015b': 389, u'\u015e': 556, u'\u015f': 389, u'\u0160': 556, u'\u0161': 389, u'\u0162': 611, u'\u0163': 278, u'\u0164': 611, u'\u0165': 326, u'\u016a': 722, u'\u016b': 500, u'\u016e': 722, u'\u016f': 500, u'\u0170': 722, u'\u0171': 500, u'\u0172': 722, u'\u0173': 500, u'\u0178': 722, u'\u0179': 611, u'\u017a': 444, u'\u017b': 611, u'\u017c': 444, u'\u017d': 611, u'\u017e': 444, u'\u0192': 500, u'\u0218': 556, u'\u0219': 389, u'\u02c6': 333, u'\u02c7': 333, u'\u02d8': 333, u'\u02d9': 333, u'\u02da': 333, u'\u02db': 333, u'\u02dc': 333, u'\u02dd': 333, u'\u2013': 500, u'\u2014': 1000, u'\u2018': 333, u'\u2019': 333, u'\u201a': 333, u'\u201c': 444, u'\u201d': 444, u'\u201e': 444, u'\u2020': 500, u'\u2021': 500, u'\u2022': 350, u'\u2026': 1000, u'\u2030': 1000, u'\u2039': 333, u'\u203a': 333, u'\u2044': 167, u'\u2122': 980, u'\u2202': 476, u'\u2206': 612, u'\u2211': 600, u'\u2212': 564, u'\u221a': 453, u'\u2260': 549, u'\u2264': 549, u'\u2265': 549, u'\u25ca': 471, u'\uf6c3': 250, u'\ufb01': 556, u'\ufb02': 556}),
'Helvetica-Oblique': ({'FontName': 'Helvetica-Oblique', 'Descent': -207.0, 'FontBBox': (-171.0, -225.0, 1116.0, 931.0), 'FontWeight': 'Medium', 'CapHeight': 718.0, 'FontFamily': 'Helvetica', 'Flags': 0, 'XHeight': 523.0, 'ItalicAngle': -12.0, 'Ascent': 718.0}, {32: 278, 33: 278, 34: 355, 35: 556, 36: 556, 37: 889, 38: 667, 39: 222, 40: 333, 41: 333, 42: 389, 43: 584, 44: 278, 45: 333, 46: 278, 47: 278, 48: 556, 49: 556, 50: 556, 51: 556, 52: 556, 53: 556, 54: 556, 55: 556, 56: 556, 57: 556, 58: 278, 59: 278, 60: 584, 61: 584, 62: 584, 63: 556, 64: 1015, 65: 667, 66: 667, 67: 722, 68: 722, 69: 667, 70: 611, 71: 778, 72: 722, 73: 278, 74: 500, 75: 667, 76: 556, 77: 833, 78: 722, 79: 778, 80: 667, 81: 778, 82: 722, 83: 667, 84: 611, 85: 722, 86: 667, 87: 944, 88: 667, 89: 667, 90: 611, 91: 278, 92: 278, 93: 278, 94: 469, 95: 556, 96: 222, 97: 556, 98: 556, 99: 500, 100: 556, 101: 556, 102: 278, 103: 556, 104: 556, 105: 222, 106: 222, 107: 500, 108: 222, 109: 833, 110: 556, 111: 556, 112: 556, 113: 556, 114: 333, 115: 500, 116: 278, 117: 556, 118: 500, 119: 722, 120: 500, 121: 500, 122: 500, 123: 334, 124: 260, 125: 334, 126: 584, 161: 333, 162: 556, 163: 556, 164: 167, 165: 556, 166: 556, 167: 556, 168: 556, 169: 191, 170: 333, 171: 556, 172: 333, 173: 333, 174: 500, 175: 500, 177: 556, 178: 556, 179: 556, 180: 278, 182: 537, 183: 350, 184: 222, 185: 333, 186: 333, 187: 556, 188: 1000, 189: 1000, 191: 611, 193: 333, 194: 333, 195: 333, 196: 333, 197: 333, 198: 333, 199: 333, 200: 333, 202: 333, 203: 333, 205: 333, 206: 333, 207: 333, 208: 1000, 225: 1000, 227: 370, 232: 556, 233: 778, 234: 1000, 235: 365, 241: 889, 245: 278, 248: 222, 249: 611, 250: 944, 251: 611}), 'ZapfDingbats': ({'FontName': 'ZapfDingbats', 'FontBBox': (-1.0, -143.0, 981.0, 820.0), 'FontWeight': 'Medium', 'FontFamily': 'ITC', 'Flags': 0, 'ItalicAngle': 0.0}, {u'\x01': 974, u'\x02': 961, u'\x03': 980, u'\x04': 719, u'\x05': 789, u'\x06': 494, u'\x07': 552, u'\x08': 537, u'\t': 577, u'\n': 692, u'\x0b': 960, u'\x0c': 939, u'\r': 549, u'\x0e': 855, u'\x0f': 911, u'\x10': 933, u'\x11': 945, u'\x12': 974, u'\x13': 755, u'\x14': 846, u'\x15': 762, u'\x16': 761, u'\x17': 571, u'\x18': 677, u'\x19': 763, u'\x1a': 760, u'\x1b': 759, u'\x1c': 754, u'\x1d': 786, u'\x1e': 788, u'\x1f': 788, u' ': 790, u'!': 793, u'"': 794, u'#': 816, u'$': 823, u'%': 789, u'&': 841, u"'": 823, u'(': 833, u')': 816, u'*': 831, u'+': 923, u',': 744, u'-': 723, u'.': 749, u'/': 790, u'0': 792, u'1': 695, u'2': 776, u'3': 768, u'4': 792, u'5': 759, u'6': 707, u'7': 708, u'8': 682, u'9': 701, u':': 826, u';': 815, u'<': 789, u'=': 789, u'>': 707, u'?': 687, u'@': 696, u'A': 689, u'B': 786, u'C': 787, u'D': 713, u'E': 791, u'F': 785, u'G': 791, u'H': 873, u'I': 761, u'J': 762, u'K': 759, u'L': 892, u'M': 892, u'N': 788, u'O': 784, u'Q': 438, u'R': 138, u'S': 277, u'T': 415, u'U': 509, u'V': 410, u'W': 234, u'X': 234, u'Y': 390, u'Z': 390, u'[': 276, u'\\': 276, u']': 317, u'^': 317, u'_': 334, u'`': 334, u'a': 392, u'b': 392, u'c': 668, u'd': 668, u'e': 732, u'f': 544, u'g': 544, u'h': 910, u'i': 911, u'j': 667, u'k': 760, u'l': 760, u'm': 626, u'n': 694, u'o': 595, u'p': 776, u'u': 690, u'v': 791, u'w': 790, u'x': 788, u'y': 788, u'z': 788, u'{': 788, u'|': 788, u'}': 788, u'~': 788, u'\x7f': 788, u'\x80': 788, u'\x81': 788, u'\x82': 788, u'\x83': 788, u'\x84': 788, u'\x85': 788, u'\x86': 788, u'\x87': 788, u'\x88': 788, u'\x89': 788, u'\x8a': 788, u'\x8b': 788, u'\x8c': 788, u'\x8d': 788, u'\x8e': 788, u'\x8f': 788, u'\x90': 788, u'\x91': 788, u'\x92': 788, u'\x93': 788, u'\x94': 788, u'\x95': 788, u'\x96': 788, u'\x97': 788, u'\x98': 788, u'\x99': 788, u'\x9a': 788, u'\x9b': 788, u'\x9c': 788, u'\x9d': 788, u'\x9e': 788, u'\x9f': 788, u'\xa0': 894, u'\xa1': 838, u'\xa2': 924, u'\xa3': 1016, u'\xa4': 458, u'\xa5': 924, u'\xa6': 918, u'\xa7': 927, u'\xa8': 928, u'\xa9': 928, u'\xaa': 834, u'\xab': 873, u'\xac': 828, u'\xad': 924, u'\xae': 917, u'\xaf': 930, u'\xb0': 931, u'\xb1': 463, u'\xb2': 883, u'\xb3': 836, u'\xb4': 867, u'\xb5': 696, u'\xb6': 874, u'\xb7': 760, u'\xb8': 946, u'\xb9': 865, u'\xba': 967, u'\xbb': 831, u'\xbc': 873, u'\xbd': 927, u'\xbe': 970, u'\xbf': 918, u'\xc0': 748, u'\xc1': 836, u'\xc2': 771, u'\xc3': 888, u'\xc4': 748, u'\xc5': 771, u'\xc6': 888, u'\xc7': 867, u'\xc8': 696, u'\xc9': 874, u'\xca': 974, u'\xcb': 762, u'\xcc': 759, u'\xcd': 509, u'\xce': 410}),
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
""" Standard encoding tables used in PDF. """ Standard encoding tables used in PDF.
@ -7,7 +7,6 @@ This table is extracted from PDF Reference Manual 1.6, pp.925
""" """
ENCODING = [ ENCODING = [
# (name, std, mac, win, pdf) # (name, std, mac, win, pdf)
('A', 65, 65, 65, 65), ('A', 65, 65, 65, 65),
@ -163,6 +162,7 @@ ENCODING = [
('mu', None, 181, 181, 181), ('mu', None, 181, 181, 181),
('multiply', None, None, 215, 215), ('multiply', None, None, 215, 215),
('n', 110, 110, 110, 110), ('n', 110, 110, 110, 110),
('nbspace', None, 202, 160, None),
('nine', 57, 57, 57, 57), ('nine', 57, 57, 57, 57),
('ntilde', None, 150, 241, 241), ('ntilde', None, 150, 241, 241),
('numbersign', 35, 35, 35, 35), ('numbersign', 35, 35, 35, 35),

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
import io #!/usr/bin/env python
import logging from io import BytesIO
logger = logging.getLogger(__name__)
class CorruptDataError(Exception): class CorruptDataError(Exception):
pass pass
## LZWDecoder
##
class LZWDecoder: class LZWDecoder:
def __init__(self, fp): def __init__(self, fp):
@ -17,6 +17,7 @@ class LZWDecoder:
self.nbits = 9 self.nbits = 9
self.table = None self.table = None
self.prevbuf = None self.prevbuf = None
return
def readbits(self, bits): def readbits(self, bits):
v = 0 v = 0
@ -27,37 +28,34 @@ class LZWDecoder:
# |-----8-bits-----| # |-----8-bits-----|
# |-bpos-|-bits-| | # |-bpos-|-bits-| |
# | |----r----| # | |----r----|
v = (v<<bits) | ((self.buff>>(r-bits)) & ((1<<bits)-1)) v = (v << bits) | ((self.buff >> (r-bits)) & ((1 << bits)-1))
self.bpos += bits self.bpos += bits
break break
else: else:
# |-----8-bits-----| # |-----8-bits-----|
# |-bpos-|---bits----... # |-bpos-|---bits----...
# | |----r----| # | |----r----|
v = (v<<r) | (self.buff & ((1<<r)-1)) v = (v << r) | (self.buff & ((1 << r)-1))
bits -= r bits -= r
x = self.fp.read(1) x = self.fp.read(1)
if not x: raise EOFError if not x:
self.buff = ord(x) raise EOFError
self.buff = x[0]
self.bpos = 0 self.bpos = 0
return v return v
def feed(self, code): def feed(self, code):
x = b'' x = b''
if code == 256: if code == 256:
self.table = [bytes([i]) for i in range(256)] # 0-255 self.table = [bytes([c]) for c in range(256)] # 0-255
self.table.append(None) # 256 self.table.append(None) # 256
self.table.append(None) # 257 self.table.append(None) # 257
self.prevbuf = b'' self.prevbuf = b''
self.nbits = 9 self.nbits = 9
elif code == 257: elif code == 257:
pass pass
elif not self.prevbuf: elif not self.prevbuf:
try: x = self.prevbuf = self.table[code]
x = self.prevbuf = self.table[code]
except (TypeError, IndexError):
# TypeError: table is None
raise CorruptDataError()
else: else:
if code < len(self.table): if code < len(self.table):
x = self.table[code] x = self.table[code]
@ -66,7 +64,7 @@ class LZWDecoder:
self.table.append(self.prevbuf+self.prevbuf[:1]) self.table.append(self.prevbuf+self.prevbuf[:1])
x = self.table[code] x = self.table[code]
else: else:
raise CorruptDataError() raise CorruptDataError
l = len(self.table) l = len(self.table)
if l == 511: if l == 511:
self.nbits = 10 self.nbits = 10
@ -89,9 +87,20 @@ class LZWDecoder:
# just ignore corrupt data and stop yielding there # just ignore corrupt data and stop yielding there
break break
yield x yield x
logger.debug('nbits=%d, code=%d, output=%r, table=%r', self.nbits, code, x, self.table) #logging.debug('nbits=%d, code=%d, output=%r, table=%r' %
# (self.nbits, code, x, self.table[258:]))
return
# lzwdecode # lzwdecode
def lzwdecode(data): def lzwdecode(data):
fp = io.BytesIO(data) """
>>> lzwdecode(bytes.fromhex('800b6050220c0c8501'))
b'-----A---B'
"""
fp = BytesIO(data)
return b''.join(LZWDecoder(fp).run()) return b''.join(LZWDecoder(fp).run())
if __name__ == '__main__':
import doctest
print('pdfminer.lzw', doctest.testmod())

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
from .psparser import LIT from .psparser import LIT
@ -7,25 +8,27 @@ LITERAL_DEVICE_GRAY = LIT('DeviceGray')
LITERAL_DEVICE_RGB = LIT('DeviceRGB') LITERAL_DEVICE_RGB = LIT('DeviceRGB')
LITERAL_DEVICE_CMYK = LIT('DeviceCMYK') LITERAL_DEVICE_CMYK = LIT('DeviceCMYK')
class PDFColorSpace: class PDFColorSpace:
def __init__(self, name, ncomponents): def __init__(self, name, ncomponents):
self.name = name self.name = name
self.ncomponents = ncomponents self.ncomponents = ncomponents
return
def __repr__(self): def __repr__(self):
return '<PDFColorSpace: %s, ncomponents=%d>' % (self.name, self.ncomponents) return '<PDFColorSpace: %s, ncomponents=%d>' % (self.name, self.ncomponents)
PREDEFINED_COLORSPACE = dict( PREDEFINED_COLORSPACE = dict(
(name, PDFColorSpace(name,n)) for (name,n) in { (name, PDFColorSpace(name, n)) for (name, n) in {
'CalRGB': 3, 'CalRGB': 3,
'CalGray': 1, 'CalGray': 1,
'Lab': 3, 'Lab': 3,
'DeviceRGB': 3, 'DeviceRGB': 3,
'DeviceCMYK': 4, 'DeviceCMYK': 4,
'DeviceGray': 1, 'DeviceGray': 1,
'Separation': 1, 'Separation': 1,
'Indexed': 1, 'Indexed': 1,
'Pattern': 1, 'Pattern': 1,
}.items()) }.items())

View File

@ -1,48 +1,64 @@
import sys #!/usr/bin/env python
from .utils import mult_matrix, translate_matrix from .utils import mult_matrix
from .utils import htmlescape, bbox2str from .utils import translate_matrix
from .utils import q
from .utils import bbox2str
from .utils import isnumber
from .pdffont import PDFUnicodeNotDefined from .pdffont import PDFUnicodeNotDefined
## PDFDevice
##
class PDFDevice: class PDFDevice:
def __init__(self, rsrcmgr): def __init__(self, rsrcmgr):
self.rsrcmgr = rsrcmgr self.rsrcmgr = rsrcmgr
self.ctm = None self.ctm = None
return
def __repr__(self): def __repr__(self):
return '<PDFDevice>' return '<PDFDevice>'
def close(self): def close(self):
pass return
def set_ctm(self, ctm): def set_ctm(self, ctm):
self.ctm = ctm self.ctm = ctm
return
def begin_tag(self, tag, props=None): def begin_tag(self, tag, props=None):
pass return
def end_tag(self): def end_tag(self):
pass return
def do_tag(self, tag, props=None): def do_tag(self, tag, props=None):
pass return
def begin_page(self, page, ctm): def begin_page(self, page, ctm):
pass return
def end_page(self, page): def end_page(self, page):
pass return
def begin_figure(self, name, bbox, matrix): def begin_figure(self, name, bbox, matrix):
pass return
def end_figure(self, name): def end_figure(self, name):
pass return
def paint_path(self, graphicstate, stroke, fill, evenodd, path): def paint_path(self, graphicstate, stroke, fill, evenodd, path):
pass return
def render_image(self, name, stream): def render_image(self, name, stream):
pass return
def render_string(self, textstate, seq): def render_string(self, textstate, seq):
pass return
## PDFTextDevice
##
class PDFTextDevice(PDFDevice): class PDFTextDevice(PDFDevice):
def render_string(self, textstate, seq): def render_string(self, textstate, seq):
@ -64,39 +80,40 @@ class PDFTextDevice(PDFDevice):
textstate.linematrix = self.render_string_horizontal( textstate.linematrix = self.render_string_horizontal(
seq, matrix, textstate.linematrix, font, fontsize, seq, matrix, textstate.linematrix, font, fontsize,
scaling, charspace, wordspace, rise, dxscale) scaling, charspace, wordspace, rise, dxscale)
return
def render_string_horizontal(self, seq, matrix, point, font, fontsize, scaling, charspace,
wordspace, rise, dxscale): def render_string_horizontal(self, seq, matrix, pos,
(x,y) = point font, fontsize, scaling, charspace, wordspace, rise, dxscale):
(x, y) = pos
needcharspace = False needcharspace = False
for obj in seq: for obj in seq:
if isinstance(obj, (int, float)): if isnumber(obj):
x -= obj*dxscale x -= obj*dxscale
needcharspace = True needcharspace = True
else: else:
for cid in font.decode(obj): for cid in font.decode(obj):
if needcharspace: if needcharspace:
x += charspace x += charspace
x += self.render_char(translate_matrix(matrix, (x,y)), x += self.render_char(translate_matrix(matrix, (x, y)),
font, fontsize, scaling, rise, cid) font, fontsize, scaling, rise, cid)
if cid == 32 and wordspace: if cid == 32 and wordspace:
x += wordspace x += wordspace
needcharspace = True needcharspace = True
return (x, y) return (x, y)
def render_string_vertical(self, seq, matrix, point, font, fontsize, scaling, charspace, def render_string_vertical(self, seq, matrix, pos,
wordspace, rise, dxscale): font, fontsize, scaling, charspace, wordspace, rise, dxscale):
(x,y) = point (x, y) = pos
needcharspace = False needcharspace = False
for obj in seq: for obj in seq:
if isinstance(obj, (int, float)): if isnumber(obj):
y -= obj*dxscale y -= obj*dxscale
needcharspace = True needcharspace = True
else: else:
for cid in font.decode(obj): for cid in font.decode(obj):
if needcharspace: if needcharspace:
y += charspace y += charspace
y += self.render_char(translate_matrix(matrix, (x,y)), y += self.render_char(translate_matrix(matrix, (x, y)),
font, fontsize, scaling, rise, cid) font, fontsize, scaling, rise, cid)
if cid == 32 and wordspace: if cid == 32 and wordspace:
y += wordspace y += wordspace
@ -107,6 +124,8 @@ class PDFTextDevice(PDFDevice):
return 0 return 0
## TagExtractor
##
class TagExtractor(PDFDevice): class TagExtractor(PDFDevice):
def __init__(self, rsrcmgr, outfp): def __init__(self, rsrcmgr, outfp):
@ -114,12 +133,13 @@ class TagExtractor(PDFDevice):
self.outfp = outfp self.outfp = outfp
self.pageno = 0 self.pageno = 0
self._stack = [] self._stack = []
return
def render_string(self, textstate, seq): def render_string(self, textstate, seq):
font = textstate.font font = textstate.font
text = '' text = ''
for obj in seq: for obj in seq:
if not isinstance(obj, str): if not isinstance(obj, bytes):
continue continue
chars = font.decode(obj) chars = font.decode(obj)
for cid in chars: for cid in chars:
@ -128,29 +148,35 @@ class TagExtractor(PDFDevice):
text += char text += char
except PDFUnicodeNotDefined: except PDFUnicodeNotDefined:
pass pass
self.outfp.write(htmlescape(text, self.outfp.encoding)) self.outfp.write(q(text))
return
def begin_page(self, page, ctm): def begin_page(self, page, ctm):
self.outfp.write('<page id="%s" bbox="%s" rotate="%d">' % self.outfp.write('<page id="%s" bbox="%s" rotate="%d">' %
(self.pageno, bbox2str(page.mediabox), page.rotate)) (self.pageno, bbox2str(page.mediabox), page.rotate))
return
def end_page(self, page): def end_page(self, page):
self.outfp.write('</page>\n') self.outfp.write('</page>\n')
self.pageno += 1 self.pageno += 1
return
def begin_tag(self, tag, props=None): def begin_tag(self, tag, props=None):
s = '' s = ''
if isinstance(props, dict): if isinstance(props, dict):
s = ''.join( ' %s="%s"' % (htmlescape(k), htmlescape(str(v))) for (k,v) s = ''.join(' %s="%s"' % (q(k), q(str(v))) for (k, v)
in sorted(props.items()) ) in sorted(props.items()))
self.outfp.write('<%s%s>' % (htmlescape(tag.name), s)) self.outfp.write('<%s%s>' % (q(tag.name), s))
self._stack.append(tag) self._stack.append(tag)
return
def end_tag(self): def end_tag(self):
assert self._stack assert self._stack
tag = self._stack.pop(-1) tag = self._stack.pop(-1)
self.outfp.write('</%s>' % htmlescape(tag.name)) self.outfp.write('</%s>' % q(tag.name))
return
def do_tag(self, tag, props=None): def do_tag(self, tag, props=None):
self.begin_tag(tag, props) self.begin_tag(tag, props)
self._stack.pop(-1) self._stack.pop(-1)
return

View File

@ -1,18 +1,33 @@
#!/usr/bin/env python3 #!/usr/bin/env python
import sys import sys
import io
import struct import struct
from .cmapdb import CMapDB, CMapParser, FileUnicodeMap, CMap from io import BytesIO
from .encodingdb import EncodingDB, name2unicode from .cmapdb import CMapDB
from .cmapdb import CMapParser
from .cmapdb import FileUnicodeMap
from .cmapdb import CMap
from .encodingdb import EncodingDB
from .encodingdb import name2unicode
from .psparser import PSStackParser from .psparser import PSStackParser
from .psparser import PSEOF from .psparser import PSEOF
from .psparser import LIT, KWD, handle_error from .psparser import LIT
from .psparser import PSLiteral, literal_name from .psparser import KWD
from .pdftypes import (PDFException, resolve1, int_value, num_value, list_value, dict_value, from .psparser import STRICT
stream_value) from .psparser import PSLiteral
from .psparser import literal_name
from .pdftypes import PDFException
from .pdftypes import resolve1
from .pdftypes import int_value
from .pdftypes import num_value
from .pdftypes import bytes_value
from .pdftypes import list_value
from .pdftypes import dict_value
from .pdftypes import stream_value
from .fontmetrics import FONT_METRICS from .fontmetrics import FONT_METRICS
from .utils import apply_matrix_norm, nunpack, choplist from .utils import apply_matrix_norm
from .utils import nunpack
from .utils import choplist
from .utils import isnumber
def get_widths(seq): def get_widths(seq):
@ -22,13 +37,13 @@ def get_widths(seq):
if isinstance(v, list): if isinstance(v, list):
if r: if r:
char1 = r[-1] char1 = r[-1]
for (i,w) in enumerate(v): for (i, w) in enumerate(v):
widths[char1+i] = w widths[char1+i] = w
r = [] r = []
elif isinstance(v, int): elif isnumber(v):
r.append(v) r.append(v)
if len(r) == 3: if len(r) == 3:
(char1,char2,w) = r (char1, char2, w) = r
for i in range(char1, char2+1): for i in range(char1, char2+1):
widths[i] = w widths[i] = w
r = [] r = []
@ -37,6 +52,7 @@ def get_widths(seq):
#assert get_widths([1,2,3]) == {1:3, 2:3} #assert get_widths([1,2,3]) == {1:3, 2:3}
#assert get_widths([1,[2,3],6,[7,8]]) == {1:2,2:3, 6:7,7:8} #assert get_widths([1,[2,3],6,[7,8]]) == {1:2,2:3, 6:7,7:8}
def get_widths2(seq): def get_widths2(seq):
widths = {} widths = {}
r = [] r = []
@ -44,22 +60,24 @@ def get_widths2(seq):
if isinstance(v, list): if isinstance(v, list):
if r: if r:
char1 = r[-1] char1 = r[-1]
for (i,(w,vx,vy)) in enumerate(choplist(3,v)): for (i, (w, vx, vy)) in enumerate(choplist(3, v)):
widths[char1+i] = (w,(vx,vy)) widths[char1+i] = (w, (vx, vy))
r = [] r = []
elif isinstance(v, int): elif isnumber(v):
r.append(v) r.append(v)
if len(r) == 5: if len(r) == 5:
(char1,char2,w,vx,vy) = r (char1, char2, w, vx, vy) = r
for i in range(char1, char2+1): for i in range(char1, char2+1):
widths[i] = (w,(vx,vy)) widths[i] = (w, (vx, vy))
r = [] r = []
return widths return widths
#assert get_widths2([1]) == {} #assert get_widths2([1]) == {}
#assert get_widths2([1,2,3,4,5]) == {1:(3,(4,5)), 2:(3,(4,5))} #assert get_widths2([1,2,3,4,5]) == {1:(3, (4,5)), 2:(3, (4,5))}
#assert get_widths2([1,[2,3,4,5],6,[7,8,9]]) == {1:(2,(3,4)), 6:(7,(8,9))} #assert get_widths2([1,[2,3,4,5],6,[7,8,9]]) == {1:(2, (3,4)), 6:(7, (8,9))}
## FontMetricsDB
##
class FontMetricsDB: class FontMetricsDB:
@classmethod @classmethod
@ -67,26 +85,29 @@ class FontMetricsDB:
return FONT_METRICS[fontname] return FONT_METRICS[fontname]
## Type1FontHeaderParser
##
class Type1FontHeaderParser(PSStackParser): class Type1FontHeaderParser(PSStackParser):
KEYWORD_BEGIN = KWD('begin') KEYWORD_BEGIN = KWD(b'begin')
KEYWORD_END = KWD('end') KEYWORD_END = KWD(b'end')
KEYWORD_DEF = KWD('def') KEYWORD_DEF = KWD(b'def')
KEYWORD_PUT = KWD('put') KEYWORD_PUT = KWD(b'put')
KEYWORD_DICT = KWD('dict') KEYWORD_DICT = KWD(b'dict')
KEYWORD_ARRAY = KWD('array') KEYWORD_ARRAY = KWD(b'array')
KEYWORD_READONLY = KWD('readonly') KEYWORD_READONLY = KWD(b'readonly')
KEYWORD_FOR = KWD('for') KEYWORD_FOR = KWD(b'for')
KEYWORD_FOR = KWD('for') KEYWORD_FOR = KWD(b'for')
def __init__(self, data): def __init__(self, data):
PSStackParser.__init__(self, data) PSStackParser.__init__(self, data)
self._cid2unicode = {} self._cid2unicode = {}
return
def get_encoding(self): def get_encoding(self):
while 1: while 1:
try: try:
(cid,name) = self.nextobject() (cid, name) = self.nextobject()
except PSEOF: except PSEOF:
break break
try: try:
@ -94,27 +115,31 @@ class Type1FontHeaderParser(PSStackParser):
except KeyError: except KeyError:
pass pass
return self._cid2unicode return self._cid2unicode
def do_keyword(self, pos, token): def do_keyword(self, pos, token):
if token is self.KEYWORD_PUT: if token is self.KEYWORD_PUT:
((_,key),(_,value)) = self.pop(2) ((_, key), (_, value)) = self.pop(2)
if (isinstance(key, int) and if (isinstance(key, int) and
isinstance(value, PSLiteral)): isinstance(value, PSLiteral)):
self.add_results((key, literal_name(value))) self.add_results((key, literal_name(value)))
return
NIBBLES = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'e-', None, '-')
## CFFFont ## CFFFont
## (Format specified in Adobe Technical Note: #5176 ## (Format specified in Adobe Technical Note: #5176
## "The Compact Font Format Specification") ## "The Compact Font Format Specification")
## ##
NIBBLES = ('0','1','2','3','4','5','6','7','8','9','.','e','e-',None,'-')
def getdict(data): def getdict(data):
d = {} d = {}
fp = io.BytesIO(data) fp = BytesIO(data)
stack = [] stack = []
while 1: while 1:
c = fp.read(1) c = fp.read(1)
if not c: break if not c:
break
b0 = ord(c) b0 = ord(c)
if b0 <= 21: if b0 <= 21:
d[b0] = stack d[b0] = stack
@ -136,19 +161,21 @@ def getdict(data):
else: else:
b1 = ord(fp.read(1)) b1 = ord(fp.read(1))
if 247 <= b0 and b0 <= 250: if 247 <= b0 and b0 <= 250:
value = ((b0-247)<<8)+b1+108 value = ((b0-247) << 8)+b1+108
elif 251 <= b0 and b0 <= 254: elif 251 <= b0 and b0 <= 254:
value = -((b0-251)<<8)-b1-108 value = -((b0-251) << 8)-b1-108
else: else:
b2 = ord(fp.read(1)) b2 = ord(fp.read(1))
if 128 <= b1: b1 -= 256 if 128 <= b1:
b1 -= 256
if b0 == 28: if b0 == 28:
value = b1<<8 | b2 value = b1 << 8 | b2
else: else:
value = b1<<24 | b2<<16 | struct.unpack('>H', fp.read(2))[0] value = b1 << 24 | b2 << 16 | struct.unpack('>H', fp.read(2))[0]
stack.append(value) stack.append(value)
return d return d
class CFFFont: class CFFFont:
STANDARD_STRINGS = ( STANDARD_STRINGS = (
@ -230,18 +257,19 @@ class CFFFont:
'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000',
'001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book',
'Light', 'Medium', 'Regular', 'Roman', 'Semibold', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold',
) )
class INDEX: class INDEX:
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
self.offsets = [] self.offsets = []
(count, offsize) = struct.unpack(b'>HB', self.fp.read(3)) (count, offsize) = struct.unpack('>HB', self.fp.read(3))
for i in range(count+1): for i in range(count+1):
self.offsets.append(nunpack(self.fp.read(offsize))) self.offsets.append(nunpack(self.fp.read(offsize)))
self.base = self.fp.tell()-1 self.base = self.fp.tell()-1
self.fp.seek(self.base+self.offsets[-1]) self.fp.seek(self.base+self.offsets[-1])
return
def __repr__(self): def __repr__(self):
return '<INDEX: size=%d>' % len(self) return '<INDEX: size=%d>' % len(self)
@ -254,13 +282,13 @@ class CFFFont:
return self.fp.read(self.offsets[i+1]-self.offsets[i]) return self.fp.read(self.offsets[i+1]-self.offsets[i])
def __iter__(self): def __iter__(self):
return iter( self[i] for i in range(len(self)) ) return iter(self[i] for i in range(len(self)))
def __init__(self, name, fp): def __init__(self, name, fp):
self.name = name self.name = name
self.fp = fp self.fp = fp
# Header # Header
(_major,_minor,hdrsize,offsize) = struct.unpack(b'BBBB', self.fp.read(4)) (_major, _minor, hdrsize, offsize) = struct.unpack('BBBB', self.fp.read(4))
self.fp.read(hdrsize-4) self.fp.read(hdrsize-4)
# Name INDEX # Name INDEX
self.name_index = self.INDEX(self.fp) self.name_index = self.INDEX(self.fp)
@ -286,17 +314,17 @@ class CFFFont:
format = self.fp.read(1) format = self.fp.read(1)
if format == b'\x00': if format == b'\x00':
# Format 0 # Format 0
(n,) = struct.unpack(b'B', self.fp.read(1)) (n,) = struct.unpack('B', self.fp.read(1))
for (code,gid) in enumerate(struct.unpack(b'B'*n, self.fp.read(n))): for (code, gid) in enumerate(struct.unpack('B'*n, self.fp.read(n))):
self.code2gid[code] = gid self.code2gid[code] = gid
self.gid2code[gid] = code self.gid2code[gid] = code
elif format == b'\x01': elif format == b'\x01':
# Format 1 # Format 1
(n,) = struct.unpack(b'B', self.fp.read(1)) (n,) = struct.unpack('B', self.fp.read(1))
code = 0 code = 0
for i in range(n): for i in range(n):
(first,nleft) = struct.unpack(b'BB', self.fp.read(2)) (first, nleft) = struct.unpack('BB', self.fp.read(2))
for gid in range(first,first+nleft+1): for gid in range(first, first+nleft+1):
self.code2gid[code] = gid self.code2gid[code] = gid
self.gid2code[gid] = code self.gid2code[gid] = code
code += 1 code += 1
@ -307,33 +335,34 @@ class CFFFont:
self.gid2name = {} self.gid2name = {}
self.fp.seek(charset_pos) self.fp.seek(charset_pos)
format = self.fp.read(1) format = self.fp.read(1)
if format == '\x00': if format == b'\x00':
# Format 0 # Format 0
n = self.nglyphs-1 n = self.nglyphs-1
for (gid,sid) in enumerate(struct.unpack(b'>'+b'H'*n, self.fp.read(2*n))): for (gid, sid) in enumerate(struct.unpack('>'+'H'*n, self.fp.read(2*n))):
gid += 1 gid += 1
name = self.getstr(sid) name = self.getstr(sid)
self.name2gid[name] = gid self.name2gid[name] = gid
self.gid2name[gid] = name self.gid2name[gid] = name
elif format == '\x01': elif format == b'\x01':
# Format 1 # Format 1
(n,) = struct.unpack(b'B', self.fp.read(1)) (n,) = struct.unpack('B', self.fp.read(1))
sid = 0 sid = 0
for i in range(n): for i in range(n):
(first,nleft) = struct.unpack(b'BB', self.fp.read(2)) (first, nleft) = struct.unpack('BB', self.fp.read(2))
for gid in range(first,first+nleft+1): for gid in range(first, first+nleft+1):
name = self.getstr(sid) name = self.getstr(sid)
self.name2gid[name] = gid self.name2gid[name] = gid
self.gid2name[gid] = name self.gid2name[gid] = name
sid += 1 sid += 1
elif format == '\x02': elif format == b'\x02':
# Format 2 # Format 2
assert 0 assert 0
else: else:
raise ValueError('unsupported charset format: %r' % format) raise ValueError('unsupported charset format: %r' % format)
#print self.code2gid #print(self.code2gid)
#print self.name2gid #print(self.name2gid)
#assert 0 #assert 0
return
def getstr(self, sid): def getstr(self, sid):
if sid < len(self.STANDARD_STRINGS): if sid < len(self.STANDARD_STRINGS):
@ -341,19 +370,23 @@ class CFFFont:
return self.string_index[sid-len(self.STANDARD_STRINGS)] return self.string_index[sid-len(self.STANDARD_STRINGS)]
## TrueTypeFont
##
class TrueTypeFont: class TrueTypeFont:
class CMapNotFound(Exception): pass class CMapNotFound(Exception):
pass
def __init__(self, name, fp): def __init__(self, name, fp):
self.name = name self.name = name
self.fp = fp self.fp = fp
self.tables = {} self.tables = {}
self.fonttype = fp.read(4) self.fonttype = fp.read(4)
(ntables, _1, _2, _3) = struct.unpack(b'>HHHH', fp.read(8)) (ntables, _1, _2, _3) = struct.unpack('>HHHH', fp.read(8))
for _ in range(ntables): for _ in range(ntables):
(name, tsum, offset, length) = struct.unpack(b'>4sLLL', fp.read(16)) (name, tsum, offset, length) = struct.unpack('>4sLLL', fp.read(16))
self.tables[name] = (offset, length) self.tables[name] = (offset, length)
return
def create_unicode_map(self): def create_unicode_map(self):
if 'cmap' not in self.tables: if 'cmap' not in self.tables:
@ -361,50 +394,51 @@ class TrueTypeFont:
(base_offset, length) = self.tables['cmap'] (base_offset, length) = self.tables['cmap']
fp = self.fp fp = self.fp
fp.seek(base_offset) fp.seek(base_offset)
(version, nsubtables) = struct.unpack(b'>HH', fp.read(4)) (version, nsubtables) = struct.unpack('>HH', fp.read(4))
subtables = [] subtables = []
for i in range(nsubtables): for i in range(nsubtables):
subtables.append(struct.unpack(b'>HHL', fp.read(8))) subtables.append(struct.unpack('>HHL', fp.read(8)))
char2gid = {} char2gid = {}
# Only supports subtable type 0, 2 and 4. # Only supports subtable type 0, 2 and 4.
for (_1, _2, st_offset) in subtables: for (_1, _2, st_offset) in subtables:
fp.seek(base_offset+st_offset) fp.seek(base_offset+st_offset)
(fmttype, fmtlen, fmtlang) = struct.unpack(b'>HHH', fp.read(6)) (fmttype, fmtlen, fmtlang) = struct.unpack('>HHH', fp.read(6))
if fmttype == 0: if fmttype == 0:
char2gid.update(enumerate(struct.unpack(b'>256B', fp.read(256)))) char2gid.update(enumerate(struct.unpack('>256B', fp.read(256))))
elif fmttype == 2: elif fmttype == 2:
subheaderkeys = struct.unpack(b'>256H', fp.read(512)) subheaderkeys = struct.unpack('>256H', fp.read(512))
firstbytes = [0]*8192 firstbytes = [0]*8192
for (i,k) in enumerate(subheaderkeys): for (i, k) in enumerate(subheaderkeys):
firstbytes[k/8] = i firstbytes[k//8] = i
nhdrs = max(subheaderkeys)/8 + 1 nhdrs = max(subheaderkeys)//8 + 1
hdrs = [] hdrs = []
for i in range(nhdrs): for i in range(nhdrs):
(firstcode,entcount,delta,offset) = struct.unpack(b'>HHhH', fp.read(8)) (firstcode, entcount, delta, offset) = struct.unpack('>HHhH', fp.read(8))
hdrs.append((i,firstcode,entcount,delta,fp.tell()-2+offset)) hdrs.append((i, firstcode, entcount, delta, fp.tell()-2+offset))
for (i,firstcode,entcount,delta,pos) in hdrs: for (i, firstcode, entcount, delta, pos) in hdrs:
if not entcount: continue if not entcount:
continue
first = firstcode + (firstbytes[i] << 8) first = firstcode + (firstbytes[i] << 8)
fp.seek(pos) fp.seek(pos)
for c in range(entcount): for c in range(entcount):
gid = struct.unpack(b'>H', fp.read(2)) gid = struct.unpack('>H', fp.read(2))
if gid: if gid:
gid += delta gid += delta
char2gid[first+c] = gid char2gid[first+c] = gid
elif fmttype == 4: elif fmttype == 4:
(segcount, _1, _2, _3) = struct.unpack(b'>HHHH', fp.read(8)) (segcount, _1, _2, _3) = struct.unpack('>HHHH', fp.read(8))
segcount /= 2 segcount //= 2
ecs = struct.unpack(b'>%dH' % segcount, fp.read(2*segcount)) ecs = struct.unpack('>%dH' % segcount, fp.read(2*segcount))
fp.read(2) fp.read(2)
scs = struct.unpack(b'>%dH' % segcount, fp.read(2*segcount)) scs = struct.unpack('>%dH' % segcount, fp.read(2*segcount))
idds = struct.unpack(b'>%dh' % segcount, fp.read(2*segcount)) idds = struct.unpack('>%dh' % segcount, fp.read(2*segcount))
pos = fp.tell() pos = fp.tell()
idrs = struct.unpack(b'>%dH' % segcount, fp.read(2*segcount)) idrs = struct.unpack('>%dH' % segcount, fp.read(2*segcount))
for (ec,sc,idd,idr) in zip(ecs, scs, idds, idrs): for (ec, sc, idd, idr) in zip(ecs, scs, idds, idrs):
if idr: if idr:
fp.seek(pos+idr) fp.seek(pos+idr)
for c in range(sc, ec+1): for c in range(sc, ec+1):
char2gid[c] = (struct.unpack(b'>H', fp.read(2))[0] + idd) & 0xffff char2gid[c] = (struct.unpack('>H', fp.read(2))[0] + idd) & 0xffff
else: else:
for c in range(sc, ec+1): for c in range(sc, ec+1):
char2gid[c] = (c + idd) & 0xffff char2gid[c] = (c + idd) & 0xffff
@ -412,21 +446,25 @@ class TrueTypeFont:
assert 0 assert 0
# create unicode map # create unicode map
unicode_map = FileUnicodeMap() unicode_map = FileUnicodeMap()
for (char,gid) in char2gid.items(): for (char, gid) in char2gid.items():
unicode_map.add_cid2unichr(gid, char) unicode_map.add_cid2unichr(gid, char)
return unicode_map return unicode_map
## Fonts ## Fonts
## ##
class PDFFontError(PDFException):
pass
class PDFFontError(PDFException): pass
class PDFUnicodeNotDefined(PDFFontError): pass class PDFUnicodeNotDefined(PDFFontError):
pass
LITERAL_STANDARD_ENCODING = LIT('StandardEncoding') LITERAL_STANDARD_ENCODING = LIT('StandardEncoding')
LITERAL_TYPE1C = LIT('Type1C') LITERAL_TYPE1C = LIT('Type1C')
# PDFFont
class PDFFont: class PDFFont:
def __init__(self, descriptor, widths, default_width=None): def __init__(self, descriptor, widths, default_width=None):
@ -441,8 +479,9 @@ class PDFFont:
self.italic_angle = num_value(descriptor.get('ItalicAngle', 0)) self.italic_angle = num_value(descriptor.get('ItalicAngle', 0))
self.default_width = default_width or num_value(descriptor.get('MissingWidth', 0)) self.default_width = default_width or num_value(descriptor.get('MissingWidth', 0))
self.leading = num_value(descriptor.get('Leading', 0)) self.leading = num_value(descriptor.get('Leading', 0))
self.bbox = list_value(descriptor.get('FontBBox', (0,0,0,0))) self.bbox = list_value(descriptor.get('FontBBox', (0, 0, 0, 0)))
self.hscale = self.vscale = .001 self.hscale = self.vscale = .001
return
def __repr__(self): def __repr__(self):
return '<PDFFont>' return '<PDFFont>'
@ -453,14 +492,12 @@ class PDFFont:
def is_multibyte(self): def is_multibyte(self):
return False return False
def decode(self, s): def decode(self, data):
if isinstance(s, str): return list(data)
return list(map(ord, s))
else: # it's already bytes
return s
def get_ascent(self): def get_ascent(self):
return self.ascent * self.vscale return self.ascent * self.vscale
def get_descent(self): def get_descent(self):
return self.descent * self.vscale return self.descent * self.vscale
@ -469,6 +506,7 @@ class PDFFont:
if w == 0: if w == 0:
w = -self.default_width w = -self.default_width
return w * self.hscale return w * self.hscale
def get_height(self): def get_height(self):
h = self.bbox[3]-self.bbox[1] h = self.bbox[3]-self.bbox[1]
if h == 0: if h == 0:
@ -476,15 +514,22 @@ class PDFFont:
return h * self.vscale return h * self.vscale
def char_width(self, cid): def char_width(self, cid):
return self.widths.get(cid, self.default_width) * self.hscale try:
return self.widths[cid] * self.hscale
except KeyError:
try:
return self.widths[self.to_unichr(cid)] * self.hscale
except (KeyError, PDFUnicodeNotDefined):
return self.default_width * self.hscale
def char_disp(self, cid): def char_disp(self, cid):
return 0 return 0
def string_width(self, s): def string_width(self, s):
return sum( self.char_width(cid) for cid in self.decode(s) ) return sum(self.char_width(cid) for cid in self.decode(s))
# PDFSimpleFont
class PDFSimpleFont(PDFFont): class PDFSimpleFont(PDFFont):
def __init__(self, descriptor, widths, spec): def __init__(self, descriptor, widths, spec):
@ -505,8 +550,9 @@ class PDFSimpleFont(PDFFont):
if 'ToUnicode' in spec: if 'ToUnicode' in spec:
strm = stream_value(spec['ToUnicode']) strm = stream_value(spec['ToUnicode'])
self.unicode_map = FileUnicodeMap() self.unicode_map = FileUnicodeMap()
CMapParser(self.unicode_map, io.BytesIO(strm.get_data())).run() CMapParser(self.unicode_map, BytesIO(strm.get_data())).run()
PDFFont.__init__(self, descriptor, widths) PDFFont.__init__(self, descriptor, widths)
return
def to_unichr(self, cid): def to_unichr(self, cid):
if self.unicode_map: if self.unicode_map:
@ -519,97 +565,112 @@ class PDFSimpleFont(PDFFont):
except KeyError: except KeyError:
raise PDFUnicodeNotDefined(None, cid) raise PDFUnicodeNotDefined(None, cid)
# PDFType1Font
class PDFType1Font(PDFSimpleFont): class PDFType1Font(PDFSimpleFont):
def __init__(self, rsrcmgr, spec): def __init__(self, rsrcmgr, spec):
try: try:
self.basefont = literal_name(spec['BaseFont']) self.basefont = literal_name(spec['BaseFont'])
except KeyError: except KeyError:
handle_error(PDFFontError, 'BaseFont is missing') if STRICT:
raise PDFFontError('BaseFont is missing')
self.basefont = 'unknown' self.basefont = 'unknown'
try: try:
(descriptor, widths) = FontMetricsDB.get_metrics(self.basefont) (descriptor, widths) = FontMetricsDB.get_metrics(self.basefont)
except KeyError: except KeyError:
descriptor = dict_value(spec.get('FontDescriptor', {})) descriptor = dict_value(spec.get('FontDescriptor', {}))
firstchar = int_value(spec.get('FirstChar', 0)) firstchar = int_value(spec.get('FirstChar', 0))
lastchar = int_value(spec.get('LastChar', 255)) #lastchar = int_value(spec.get('LastChar', 255))
widths = list_value(spec.get('Widths', [0]*256)) widths = list_value(spec.get('Widths', [0]*256))
widths = dict( (i+firstchar,w) for (i,w) in enumerate(widths) ) widths = dict((i+firstchar, w) for (i, w) in enumerate(widths))
PDFSimpleFont.__init__(self, descriptor, widths, spec) PDFSimpleFont.__init__(self, descriptor, widths, spec)
if 'Encoding' not in spec and 'FontFile' in descriptor: if 'Encoding' not in spec and 'FontFile' in descriptor:
# try to recover the missing encoding info from the font file. # try to recover the missing encoding info from the font file.
self.fontfile = stream_value(descriptor.get('FontFile')) self.fontfile = stream_value(descriptor.get('FontFile'))
length1 = int_value(self.fontfile['Length1']) length1 = int_value(self.fontfile['Length1'])
data = self.fontfile.get_data()[:length1] data = self.fontfile.get_data()[:length1]
parser = Type1FontHeaderParser(io.BytesIO(data)) parser = Type1FontHeaderParser(BytesIO(data))
self.cid2unicode = parser.get_encoding() self.cid2unicode = parser.get_encoding()
return
def __repr__(self): def __repr__(self):
return '<PDFType1Font: basefont=%r>' % self.basefont return '<PDFType1Font: basefont=%r>' % self.basefont
# PDFTrueTypeFont
class PDFTrueTypeFont(PDFType1Font): class PDFTrueTypeFont(PDFType1Font):
def __repr__(self): def __repr__(self):
return '<PDFTrueTypeFont: basefont=%r>' % self.basefont return '<PDFTrueTypeFont: basefont=%r>' % self.basefont
# PDFType3Font
class PDFType3Font(PDFSimpleFont): class PDFType3Font(PDFSimpleFont):
def __init__(self, rsrcmgr, spec): def __init__(self, rsrcmgr, spec):
firstchar = int_value(spec.get('FirstChar', 0)) firstchar = int_value(spec.get('FirstChar', 0))
lastchar = int_value(spec.get('LastChar', 0)) #lastchar = int_value(spec.get('LastChar', 0))
widths = list_value(spec.get('Widths', [0]*256)) widths = list_value(spec.get('Widths', [0]*256))
widths = dict( (i+firstchar,w) for (i,w) in enumerate(widths)) widths = dict((i+firstchar, w) for (i, w) in enumerate(widths))
if 'FontDescriptor' in spec: if 'FontDescriptor' in spec:
descriptor = dict_value(spec['FontDescriptor']) descriptor = dict_value(spec['FontDescriptor'])
else: else:
descriptor = {'Ascent':0, 'Descent':0, descriptor = {'Ascent': 0, 'Descent': 0,
'FontBBox':spec['FontBBox']} 'FontBBox': spec['FontBBox']}
PDFSimpleFont.__init__(self, descriptor, widths, spec) PDFSimpleFont.__init__(self, descriptor, widths, spec)
self.matrix = tuple(list_value(spec.get('FontMatrix'))) self.matrix = tuple(list_value(spec.get('FontMatrix')))
(_,self.descent,_,self.ascent) = self.bbox (_, self.descent, _, self.ascent) = self.bbox
(self.hscale,self.vscale) = apply_matrix_norm(self.matrix, (1,1)) (self.hscale, self.vscale) = apply_matrix_norm(self.matrix, (1, 1))
return
def __repr__(self): def __repr__(self):
return '<PDFType3Font>' return '<PDFType3Font>'
# PDFCIDFont
class PDFCIDFont(PDFFont): class PDFCIDFont(PDFFont):
def __init__(self, rsrcmgr, spec): def __init__(self, rsrcmgr, spec):
try: try:
self.basefont = literal_name(spec['BaseFont']) self.basefont = literal_name(spec['BaseFont'])
except KeyError: except KeyError:
handle_error(PDFFontError, 'BaseFont is missing') if STRICT:
raise PDFFontError('BaseFont is missing')
self.basefont = 'unknown' self.basefont = 'unknown'
self.cidsysteminfo = dict_value(spec.get('CIDSystemInfo', {})) self.cidsysteminfo = dict_value(spec.get('CIDSystemInfo', {}))
self.cidcoding = '%s-%s' % (self.cidsysteminfo.get('Registry', 'unknown'), registry = bytes_value(self.cidsysteminfo.get('Registry', b'unknown'))
self.cidsysteminfo.get('Ordering', 'unknown')) ordering = bytes_value(self.cidsysteminfo.get('Ordering', b'unknown'))
self.cidcoding = (registry + b'-' + ordering).decode('ascii')
try: try:
name = literal_name(spec['Encoding']) name = literal_name(spec['Encoding'])
except KeyError: except KeyError:
handle_error(PDFFontError, 'Encoding is unspecified') if STRICT:
raise PDFFontError('Encoding is unspecified')
name = 'unknown' name = 'unknown'
try: try:
self.cmap = CMapDB.get_cmap(name) self.cmap = CMapDB.get_cmap(name)
except CMapDB.CMapNotFound as e: except CMapDB.CMapNotFound as e:
handle_error(PDFFontError, str(e)) if STRICT:
raise PDFFontError(e)
self.cmap = CMap() self.cmap = CMap()
try: try:
descriptor = dict_value(spec['FontDescriptor']) descriptor = dict_value(spec['FontDescriptor'])
except KeyError: except KeyError:
handle_error(PDFFontError, 'FontDescriptor is missing') if STRICT:
raise PDFFontError('FontDescriptor is missing')
descriptor = {} descriptor = {}
ttf = None ttf = None
if 'FontFile2' in descriptor: if 'FontFile2' in descriptor:
self.fontfile = stream_value(descriptor.get('FontFile2')) self.fontfile = stream_value(descriptor.get('FontFile2'))
ttf = TrueTypeFont(self.basefont, ttf = TrueTypeFont(self.basefont,
io.BytesIO(self.fontfile.get_data())) BytesIO(self.fontfile.get_data()))
self.unicode_map = None self.unicode_map = None
if 'ToUnicode' in spec: if 'ToUnicode' in spec:
strm = stream_value(spec['ToUnicode']) strm = stream_value(spec['ToUnicode'])
self.unicode_map = FileUnicodeMap() self.unicode_map = FileUnicodeMap()
CMapParser(self.unicode_map, io.BytesIO(strm.get_data())).run() CMapParser(self.unicode_map, BytesIO(strm.get_data())).run()
elif self.cidcoding == 'Adobe-Identity': elif self.cidcoding in ('Adobe-Identity', 'Adobe-UCS'):
if ttf: if ttf:
try: try:
self.unicode_map = ttf.create_unicode_map() self.unicode_map = ttf.create_unicode_map()
@ -625,10 +686,10 @@ class PDFCIDFont(PDFFont):
if self.vertical: if self.vertical:
# writing mode: vertical # writing mode: vertical
widths = get_widths2(list_value(spec.get('W2', []))) widths = get_widths2(list_value(spec.get('W2', [])))
self.disps = dict( (cid,(vx,vy)) for (cid,(_,(vx,vy))) in widths.items() ) self.disps = dict((cid, (vx, vy)) for (cid, (_, (vx, vy))) in widths.items())
(vy,w) = spec.get('DW2', [880, -1000]) (vy, w) = spec.get('DW2', [880, -1000])
self.default_disp = (None,vy) self.default_disp = (None, vy)
widths = dict( (cid,w) for (cid,(w,_)) in widths.items() ) widths = dict((cid, w) for (cid, (w, _)) in widths.items())
default_width = w default_width = w
else: else:
# writing mode: horizontal # writing mode: horizontal
@ -637,6 +698,7 @@ class PDFCIDFont(PDFFont):
widths = get_widths(list_value(spec.get('W', []))) widths = get_widths(list_value(spec.get('W', [])))
default_width = spec.get('DW', 1000) default_width = spec.get('DW', 1000)
PDFFont.__init__(self, descriptor, widths, default_width=default_width) PDFFont.__init__(self, descriptor, widths, default_width=default_width)
return
def __repr__(self): def __repr__(self):
return '<PDFCIDFont: basefont=%r, cidcoding=%r>' % (self.basefont, self.cidcoding) return '<PDFCIDFont: basefont=%r, cidcoding=%r>' % (self.basefont, self.cidcoding)
@ -647,8 +709,8 @@ class PDFCIDFont(PDFFont):
def is_multibyte(self): def is_multibyte(self):
return True return True
def decode(self, bytes): def decode(self, data):
return self.cmap.decode(bytes) return self.cmap.decode(data)
def char_disp(self, cid): def char_disp(self, cid):
"Returns an integer for horizontal fonts, a tuple for vertical fonts." "Returns an integer for horizontal fonts, a tuple for vertical fonts."
@ -663,13 +725,14 @@ class PDFCIDFont(PDFFont):
raise PDFUnicodeNotDefined(self.cidcoding, cid) raise PDFUnicodeNotDefined(self.cidcoding, cid)
# main
def main(argv): def main(argv):
for fname in argv[1:]: for fname in argv[1:]:
fp = io.open(fname, 'rb') with open(fname, 'rb') as fp:
#font = TrueTypeFont(fname, fp) #font = TrueTypeFont(fname, fp)
font = CFFFont(fname, fp) font = CFFFont(fname, fp)
print(font) print(font)
fp.close() return
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main(sys.argv)) sys.exit(main(sys.argv))

View File

@ -1,28 +1,44 @@
import io #!/usr/bin/env python
import re import re
import logging import logging
from io import BytesIO
from .cmapdb import CMapDB, CMap from .cmapdb import CMapDB
from .psparser import PSTypeError, PSEOF from .cmapdb import CMap
from .psparser import PSKeyword, literal_name, keyword_name from .psparser import PSTypeError
from .psparser import PSEOF
from .psparser import PSKeyword
from .psparser import literal_name
from .psparser import keyword_name
from .psparser import PSStackParser from .psparser import PSStackParser
from .psparser import LIT, KWD, handle_error from .psparser import LIT
from .pdftypes import (PDFException, PDFStream, PDFObjRef, resolve1, list_value, dict_value, from .psparser import KWD
stream_value) from .psparser import STRICT
from .pdffont import PDFFontError, PDFType1Font, PDFTrueTypeFont, PDFType3Font, PDFCIDFont from .pdftypes import PDFException
from .pdfparser import PDFDocument, PDFParser from .pdftypes import PDFStream
from .pdfcolor import PDFColorSpace, PREDEFINED_COLORSPACE from .pdftypes import PDFObjRef
from .pdftypes import resolve1
from .pdftypes import list_value
from .pdftypes import dict_value
from .pdftypes import stream_value
from .pdffont import PDFFontError
from .pdffont import PDFType1Font
from .pdffont import PDFTrueTypeFont
from .pdffont import PDFType3Font
from .pdffont import PDFCIDFont
from .pdfcolor import PDFColorSpace
from .pdfcolor import PREDEFINED_COLORSPACE
from .utils import choplist from .utils import choplist
from .utils import mult_matrix, MATRIX_IDENTITY from .utils import mult_matrix
from .utils import MATRIX_IDENTITY
logger = logging.getLogger(__name__)
## Exceptions ## Exceptions
## ##
class PDFResourceError(PDFException): pass class PDFResourceError(PDFException):
class PDFInterpreterError(PDFException): pass pass
class PDFInterpreterError(PDFException):
pass
## Constants ## Constants
@ -34,6 +50,8 @@ LITERAL_FORM = LIT('Form')
LITERAL_IMAGE = LIT('Image') LITERAL_IMAGE = LIT('Image')
## PDFTextState
##
class PDFTextState: class PDFTextState:
def __init__(self): def __init__(self):
@ -48,6 +66,7 @@ class PDFTextState:
self.reset() self.reset()
# self.matrix is set # self.matrix is set
# self.linematrix is set # self.linematrix is set
return
def __repr__(self): def __repr__(self):
return ('<PDFTextState: font=%r, fontsize=%r, charspace=%r, wordspace=%r, ' return ('<PDFTextState: font=%r, fontsize=%r, charspace=%r, wordspace=%r, '
@ -74,8 +93,11 @@ class PDFTextState:
def reset(self): def reset(self):
self.matrix = MATRIX_IDENTITY self.matrix = MATRIX_IDENTITY
self.linematrix = (0, 0) self.linematrix = (0, 0)
return
## PDFGraphicState
##
class PDFGraphicState: class PDFGraphicState:
def __init__(self): def __init__(self):
@ -86,6 +108,7 @@ class PDFGraphicState:
self.dash = None self.dash = None
self.intent = None self.intent = None
self.flatness = None self.flatness = None
return
def copy(self): def copy(self):
obj = PDFGraphicState() obj = PDFGraphicState()
@ -104,16 +127,24 @@ class PDFGraphicState:
(self.linewidth, self.linecap, self.linejoin, (self.linewidth, self.linecap, self.linejoin,
self.miterlimit, self.dash, self.intent, self.flatness)) self.miterlimit, self.dash, self.intent, self.flatness))
## Resource Manager
##
class PDFResourceManager: class PDFResourceManager:
"""Repository of shared resources. """Repository of shared resources.
ResourceManager facilitates reuse of shared resources ResourceManager facilitates reuse of shared resources
such as fonts and images so that large objects are not such as fonts and images so that large objects are not
allocated multiple times. allocated multiple times.
""" """
debug = False
def __init__(self, caching=True): def __init__(self, caching=True):
self.caching = caching self.caching = caching
self._cached_fonts = {} self._cached_fonts = {}
return
def get_procset(self, procs): def get_procset(self, procs):
for proc in procs: for proc in procs:
@ -124,26 +155,31 @@ class PDFResourceManager:
else: else:
#raise PDFResourceError('ProcSet %r is not supported.' % proc) #raise PDFResourceError('ProcSet %r is not supported.' % proc)
pass pass
return
def get_cmap(self, cmapname, strict=False): def get_cmap(self, cmapname, strict=False):
try: try:
return CMapDB.get_cmap(cmapname) return CMapDB.get_cmap(cmapname)
except CMapDB.CMapNotFound: except CMapDB.CMapNotFound:
if strict: raise if strict:
raise
return CMap() return CMap()
def get_font(self, objid, spec): def get_font(self, objid, spec):
if objid and objid in self._cached_fonts: if objid and objid in self._cached_fonts:
font = self._cached_fonts[objid] font = self._cached_fonts[objid]
else: else:
# logger.debug('get_font: create: objid=%r, spec=%r', objid, spec) if self.debug:
if spec['Type'] is not LITERAL_FONT: logging.info('get_font: create: objid=%r, spec=%r' % (objid, spec))
handle_error(PDFFontError, 'Type is not /Font') if STRICT:
if spec['Type'] is not LITERAL_FONT:
raise PDFFontError('Type is not /Font')
# Create a Font object. # Create a Font object.
if 'Subtype' in spec: if 'Subtype' in spec:
subtype = literal_name(spec['Subtype']) subtype = literal_name(spec['Subtype'])
else: else:
handle_error(PDFFontError, 'Font Subtype is not specified.') if STRICT:
raise PDFFontError('Font Subtype is not specified.')
subtype = 'Type1' subtype = 'Type1'
if subtype in ('Type1', 'MMType1'): if subtype in ('Type1', 'MMType1'):
# Type1 Font # Type1 Font
@ -167,56 +203,90 @@ class PDFResourceManager:
subspec[k] = resolve1(spec[k]) subspec[k] = resolve1(spec[k])
font = self.get_font(None, subspec) font = self.get_font(None, subspec)
else: else:
handle_error(PDFFontError, 'Invalid Font spec: %r' % spec) if STRICT:
font = PDFType1Font(self, spec) # this is so wrong! raise PDFFontError('Invalid Font spec: %r' % spec)
font = PDFType1Font(self, spec) # this is so wrong!
if objid and self.caching: if objid and self.caching:
self._cached_fonts[objid] = font self._cached_fonts[objid] = font
return font return font
## PDFContentParser
##
class PDFContentParser(PSStackParser): class PDFContentParser(PSStackParser):
def __init__(self, streams): def __init__(self, streams):
fp = io.StringIO() self.streams = streams
for stream in streams: self.istream = 0
stream = stream_value(stream) PSStackParser.__init__(self, None)
data = stream.get_data() return
if isinstance(data, bytes):
data = data.decode('latin-1')
fp.write(data)
fp.seek(0)
PSStackParser.__init__(self, fp)
def get_inline_data(self, pos, target='EI'): def fillfp(self):
currpos = pos if not self.fp:
if self.istream < len(self.streams):
strm = stream_value(self.streams[self.istream])
self.istream += 1
else:
raise PSEOF('Unexpected EOF, file truncated?')
self.fp = BytesIO(strm.get_data())
return
def seek(self, pos):
self.fillfp()
PSStackParser.seek(self, pos)
return
def fillbuf(self):
if self.charpos < len(self.buf):
return
while 1:
self.fillfp()
self.bufpos = self.fp.tell()
self.buf = self.fp.read(self.BUFSIZ)
if self.buf:
break
self.fp = None
self.charpos = 0
return
def get_inline_data(self, pos, target=b'EI'):
self.seek(pos)
i = 0 i = 0
data = '' data = b''
while i <= len(target): while i <= len(target):
self.fillbuf()
if i: if i:
c = self.data[currpos] c = self.buf[self.charpos:self.charpos+1]
data += c data += c
currpos += 1 self.charpos += 1
if len(target) <= i and c.isspace(): if len(target) <= i and c.isspace():
i += 1 i += 1
elif i < len(target) and c == target[i]: elif i < len(target) and c == target[i:i+1]:
i += 1 i += 1
else: else:
i = 0 i = 0
else: else:
j = self.data.index(target[0], currpos) try:
data += self.data[currpos:j+1] j = self.buf.index(target[0], self.charpos)
currpos = j+1 #print('found', (0, self.buf[j:j+10]))
i = 1 data += self.buf[self.charpos:j+1]
data = data[:-(len(target)+1)] # strip the last part self.charpos = j+1
data = re.sub(r'(\x0d\x0a|[\x0d\x0a])$', '', data) i = 1
except ValueError:
data += self.buf[self.charpos:]
self.charpos = len(self.buf)
data = data[:-(len(target)+1)] # strip the last part
data = re.sub(br'(\x0d\x0a|[\x0d\x0a])$', b'', data)
return (pos, data) return (pos, data)
def flush(self): def flush(self):
self.add_results(*self.popall()) self.add_results(*self.popall())
return
KEYWORD_BI = KWD(b'BI')
KEYWORD_ID = KWD(b'ID')
KEYWORD_EI = KWD(b'EI')
KEYWORD_BI = KWD('BI')
KEYWORD_ID = KWD('ID')
KEYWORD_EI = KWD('EI')
def do_keyword(self, pos, token): def do_keyword(self, pos, token):
if token is self.KEYWORD_BI: if token is self.KEYWORD_BI:
# inline image within a content stream # inline image within a content stream
@ -226,25 +296,32 @@ class PDFContentParser(PSStackParser):
(_, objs) = self.end_type('inline') (_, objs) = self.end_type('inline')
if len(objs) % 2 != 0: if len(objs) % 2 != 0:
raise PSTypeError('Invalid dictionary construct: %r' % objs) raise PSTypeError('Invalid dictionary construct: %r' % objs)
d = dict( (literal_name(k), v) for (k,v) in choplist(2, objs) ) d = dict((literal_name(k), v) for (k, v) in choplist(2, objs))
(pos, data) = self.get_inline_data(pos+len('ID ')) (pos, data) = self.get_inline_data(pos+len(b'ID '))
obj = PDFStream(d, data) obj = PDFStream(d, data)
self.push((pos, obj)) self.push((pos, obj))
self.push((pos, self.KEYWORD_EI)) self.push((pos, self.KEYWORD_EI))
except PSTypeError as e: except PSTypeError:
handle_error(type(e), str(e)) if STRICT:
raise
else: else:
self.push((pos, token)) self.push((pos, token))
return
## Interpreter
##
class PDFPageInterpreter: class PDFPageInterpreter:
debug = 0
def __init__(self, rsrcmgr, device): def __init__(self, rsrcmgr, device):
self.rsrcmgr = rsrcmgr self.rsrcmgr = rsrcmgr
self.device = device self.device = device
return
def dup(self): def dup(self):
return PDFPageInterpreter(self.rsrcmgr, self.device) return self.__class__(self.rsrcmgr, self.device)
# init_resources(resources): # init_resources(resources):
# Prepare the fonts and XObjects listed in the Resource attribute. # Prepare the fonts and XObjects listed in the Resource attribute.
@ -255,9 +332,8 @@ class PDFPageInterpreter:
self.csmap = PREDEFINED_COLORSPACE.copy() self.csmap = PREDEFINED_COLORSPACE.copy()
if not resources: if not resources:
return return
def get_colorspace(spec): def get_colorspace(spec):
if spec is None:
return PREDEFINED_COLORSPACE['DeviceRGB']
if isinstance(spec, list): if isinstance(spec, list):
name = literal_name(spec[0]) name = literal_name(spec[0])
else: else:
@ -267,25 +343,26 @@ class PDFPageInterpreter:
elif name == 'DeviceN' and isinstance(spec, list) and 2 <= len(spec): elif name == 'DeviceN' and isinstance(spec, list) and 2 <= len(spec):
return PDFColorSpace(name, len(list_value(spec[1]))) return PDFColorSpace(name, len(list_value(spec[1])))
else: else:
return PREDEFINED_COLORSPACE[name] return PREDEFINED_COLORSPACE.get(name)
for (k,v) in dict_value(resources).items(): for (k, v) in dict_value(resources).items():
# logger.debug('Resource: %r: %r', k,v) if self.debug:
logging.debug('Resource: %r: %r' % (k, v))
if k == 'Font': if k == 'Font':
for (fontid,spec) in dict_value(v).items(): for (fontid, spec) in dict_value(v).items():
objid = None objid = None
if isinstance(spec, PDFObjRef): if isinstance(spec, PDFObjRef):
objid = spec.objid objid = spec.objid
spec = dict_value(spec) spec = dict_value(spec)
if spec: self.fontmap[fontid] = self.rsrcmgr.get_font(objid, spec)
self.fontmap[fontid] = self.rsrcmgr.get_font(objid, spec)
elif k == 'ColorSpace': elif k == 'ColorSpace':
for (csid,spec) in dict_value(v).items(): for (csid, spec) in dict_value(v).items():
self.csmap[csid] = get_colorspace(resolve1(spec)) self.csmap[csid] = get_colorspace(resolve1(spec))
elif k == 'ProcSet': elif k == 'ProcSet':
self.rsrcmgr.get_procset(list_value(v)) self.rsrcmgr.get_procset(list_value(v))
elif k == 'XObject': elif k == 'XObject':
for (xobjid,xobjstrm) in dict_value(v).items(): for (xobjid, xobjstrm) in dict_value(v).items():
self.xobjmap[xobjid] = xobjstrm self.xobjmap[xobjid] = xobjstrm
return
# init_state(ctm) # init_state(ctm)
# Initialize the text and graphic states for rendering a page. # Initialize the text and graphic states for rendering a page.
@ -302,10 +379,14 @@ class PDFPageInterpreter:
# set some global states. # set some global states.
self.scs = self.ncs = None self.scs = self.ncs = None
if self.csmap: if self.csmap:
self.scs = self.ncs = list(self.csmap.values())[0] for v in self.csmap.values():
self.scs = self.ncs = v
break
return
def push(self, obj): def push(self, obj):
self.argstack.append(obj) self.argstack.append(obj)
return
def pop(self, n): def pop(self, n):
if n == 0: if n == 0:
@ -320,283 +401,399 @@ class PDFPageInterpreter:
def set_current_state(self, state): def set_current_state(self, state):
(self.ctm, self.textstate, self.graphicstate) = state (self.ctm, self.textstate, self.graphicstate) = state
self.device.set_ctm(self.ctm) self.device.set_ctm(self.ctm)
return
# gsave # gsave
def do_q(self): def do_q(self):
self.gstack.append(self.get_current_state()) self.gstack.append(self.get_current_state())
return
# grestore # grestore
def do_Q(self): def do_Q(self):
if self.gstack: if self.gstack:
self.set_current_state(self.gstack.pop()) self.set_current_state(self.gstack.pop())
return
# concat-matrix # concat-matrix
def do_cm(self, a1, b1, c1, d1, e1, f1): def do_cm(self, a1, b1, c1, d1, e1, f1):
self.ctm = mult_matrix((a1,b1,c1,d1,e1,f1), self.ctm) self.ctm = mult_matrix((a1, b1, c1, d1, e1, f1), self.ctm)
self.device.set_ctm(self.ctm) self.device.set_ctm(self.ctm)
return
# setlinewidth # setlinewidth
def do_w(self, linewidth): def do_w(self, linewidth):
self.graphicstate.linewidth = linewidth self.graphicstate.linewidth = linewidth
return
# setlinecap # setlinecap
def do_J(self, linecap): def do_J(self, linecap):
self.graphicstate.linecap = linecap self.graphicstate.linecap = linecap
return
# setlinejoin # setlinejoin
def do_j(self, linejoin): def do_j(self, linejoin):
self.graphicstate.linejoin = linejoin self.graphicstate.linejoin = linejoin
return
# setmiterlimit # setmiterlimit
def do_M(self, miterlimit): def do_M(self, miterlimit):
self.graphicstate.miterlimit = miterlimit self.graphicstate.miterlimit = miterlimit
return
# setdash # setdash
def do_d(self, dash, phase): def do_d(self, dash, phase):
self.graphicstate.dash = (dash, phase) self.graphicstate.dash = (dash, phase)
return
# setintent # setintent
def do_ri(self, intent): def do_ri(self, intent):
self.graphicstate.intent = intent self.graphicstate.intent = intent
return
# setflatness # setflatness
def do_i(self, flatness): def do_i(self, flatness):
self.graphicstate.flatness = flatness self.graphicstate.flatness = flatness
return
# load-gstate # load-gstate
def do_gs(self, name): def do_gs(self, name):
#XXX #XXX
pass return
# moveto # moveto
def do_m(self, x, y): def do_m(self, x, y):
self.curpath.append(('m',x,y)) self.curpath.append(('m', x, y))
return
# lineto # lineto
def do_l(self, x, y): def do_l(self, x, y):
self.curpath.append(('l',x,y)) self.curpath.append(('l', x, y))
return
# curveto # curveto
def do_c(self, x1, y1, x2, y2, x3, y3): def do_c(self, x1, y1, x2, y2, x3, y3):
self.curpath.append(('c',x1,y1,x2,y2,x3,y3)) self.curpath.append(('c', x1, y1, x2, y2, x3, y3))
return
# urveto # urveto
def do_v(self, x2, y2, x3, y3): def do_v(self, x2, y2, x3, y3):
self.curpath.append(('v',x2,y2,x3,y3)) self.curpath.append(('v', x2, y2, x3, y3))
return
# rveto # rveto
def do_y(self, x1, y1, x3, y3): def do_y(self, x1, y1, x3, y3):
self.curpath.append(('y',x1,y1,x3,y3)) self.curpath.append(('y', x1, y1, x3, y3))
return
# closepath # closepath
def do_h(self): def do_h(self):
self.curpath.append(('h',)) self.curpath.append(('h',))
return
# rectangle # rectangle
def do_re(self, x, y, w, h): def do_re(self, x, y, w, h):
self.curpath.append(('m',x,y)) self.curpath.append(('m', x, y))
self.curpath.append(('l',x+w,y)) self.curpath.append(('l', x+w, y))
self.curpath.append(('l',x+w,y+h)) self.curpath.append(('l', x+w, y+h))
self.curpath.append(('l',x,y+h)) self.curpath.append(('l', x, y+h))
self.curpath.append(('h',)) self.curpath.append(('h',))
return
# stroke # stroke
def do_S(self): def do_S(self):
self.device.paint_path(self.graphicstate, True, False, False, self.curpath) self.device.paint_path(self.graphicstate, True, False, False, self.curpath)
self.curpath = [] self.curpath = []
return
# close-and-stroke # close-and-stroke
def do_s(self): def do_s(self):
self.do_h() self.do_h()
self.do_S() self.do_S()
return
# fill # fill
def do_f(self): def do_f(self):
self.device.paint_path(self.graphicstate, False, True, False, self.curpath) self.device.paint_path(self.graphicstate, False, True, False, self.curpath)
self.curpath = [] self.curpath = []
return
# fill (obsolete) # fill (obsolete)
do_F = do_f do_F = do_f
# fill-even-odd # fill-even-odd
def do_f_a(self): def do_f_a(self):
self.device.paint_path(self.graphicstate, False, True, True, self.curpath) self.device.paint_path(self.graphicstate, False, True, True, self.curpath)
self.curpath = [] self.curpath = []
return
# fill-and-stroke # fill-and-stroke
def do_B(self): def do_B(self):
self.device.paint_path(self.graphicstate, True, True, False, self.curpath) self.device.paint_path(self.graphicstate, True, True, False, self.curpath)
self.curpath = [] self.curpath = []
return
# fill-and-stroke-even-odd # fill-and-stroke-even-odd
def do_B_a(self): def do_B_a(self):
self.device.paint_path(self.graphicstate, True, True, True, self.curpath) self.device.paint_path(self.graphicstate, True, True, True, self.curpath)
self.curpath = [] self.curpath = []
return
# close-fill-and-stroke # close-fill-and-stroke
def do_b(self): def do_b(self):
self.do_h() self.do_h()
self.do_B() self.do_B()
return
# close-fill-and-stroke-even-odd # close-fill-and-stroke-even-odd
def do_b_a(self): def do_b_a(self):
self.do_h() self.do_h()
self.do_B_a() self.do_B_a()
return
# close-only # close-only
def do_n(self): def do_n(self):
self.curpath = [] self.curpath = []
return
# clip # clip
def do_W(self): def do_W(self):
pass return
# clip-even-odd # clip-even-odd
def do_W_a(self): def do_W_a(self):
pass return
# setcolorspace-stroking # setcolorspace-stroking
def do_CS(self, name): def do_CS(self, name):
self.scs = self.csmap[literal_name(name)] try:
self.scs = self.csmap[literal_name(name)]
except KeyError:
if STRICT:
raise PDFInterpreterError('Undefined ColorSpace: %r' % name)
return
# setcolorspace-non-strokine # setcolorspace-non-strokine
def do_cs(self, name): def do_cs(self, name):
self.ncs = self.csmap[literal_name(name)] try:
self.ncs = self.csmap[literal_name(name)]
except KeyError:
if STRICT:
raise PDFInterpreterError('Undefined ColorSpace: %r' % name)
return
# setgray-stroking # setgray-stroking
def do_G(self, gray): def do_G(self, gray):
#self.do_CS(LITERAL_DEVICE_GRAY) #self.do_CS(LITERAL_DEVICE_GRAY)
pass return
# setgray-non-stroking # setgray-non-stroking
def do_g(self, gray): def do_g(self, gray):
#self.do_cs(LITERAL_DEVICE_GRAY) #self.do_cs(LITERAL_DEVICE_GRAY)
pass return
# setrgb-stroking # setrgb-stroking
def do_RG(self, r, g, b): def do_RG(self, r, g, b):
#self.do_CS(LITERAL_DEVICE_RGB) #self.do_CS(LITERAL_DEVICE_RGB)
pass return
# setrgb-non-stroking # setrgb-non-stroking
def do_rg(self, r, g, b): def do_rg(self, r, g, b):
#self.do_cs(LITERAL_DEVICE_RGB) #self.do_cs(LITERAL_DEVICE_RGB)
pass return
# setcmyk-stroking # setcmyk-stroking
def do_K(self, c, m, y, k): def do_K(self, c, m, y, k):
#self.do_CS(LITERAL_DEVICE_CMYK) #self.do_CS(LITERAL_DEVICE_CMYK)
pass return
# setcmyk-non-stroking # setcmyk-non-stroking
def do_k(self, c, m, y, k): def do_k(self, c, m, y, k):
#self.do_cs(LITERAL_DEVICE_CMYK) #self.do_cs(LITERAL_DEVICE_CMYK)
pass return
# setcolor # setcolor
def do_SCN(self): def do_SCN(self):
if self.scs: if self.scs:
n = self.scs.ncomponents n = self.scs.ncomponents
else: else:
handle_error(PDFInterpreterError, 'No colorspace specified!') if STRICT:
raise PDFInterpreterError('No colorspace specified!')
n = 1 n = 1
self.pop(n) self.pop(n)
return
def do_scn(self): def do_scn(self):
if self.ncs: if self.ncs:
n = self.ncs.ncomponents n = self.ncs.ncomponents
else: else:
handle_error(PDFInterpreterError, 'No colorspace specified!') if STRICT:
raise PDFInterpreterError('No colorspace specified!')
n = 1 n = 1
self.pop(n) self.pop(n)
return
def do_SC(self): def do_SC(self):
self.do_SCN() self.do_SCN()
return
def do_sc(self): def do_sc(self):
self.do_scn() self.do_scn()
return
# sharing-name # sharing-name
def do_sh(self, name): def do_sh(self, name):
pass return
# begin-text # begin-text
def do_BT(self): def do_BT(self):
self.textstate.reset() self.textstate.reset()
return
# end-text # end-text
def do_ET(self): def do_ET(self):
pass return
# begin-compat # begin-compat
def do_BX(self): def do_BX(self):
pass return
# end-compat # end-compat
def do_EX(self): def do_EX(self):
pass return
# marked content operators # marked content operators
def do_MP(self, tag): def do_MP(self, tag):
self.device.do_tag(tag) self.device.do_tag(tag)
return
def do_DP(self, tag, props): def do_DP(self, tag, props):
self.device.do_tag(tag, props) self.device.do_tag(tag, props)
return
def do_BMC(self, tag): def do_BMC(self, tag):
self.device.begin_tag(tag) self.device.begin_tag(tag)
return
def do_BDC(self, tag, props): def do_BDC(self, tag, props):
self.device.begin_tag(tag, props) self.device.begin_tag(tag, props)
return
def do_EMC(self): def do_EMC(self):
self.device.end_tag() self.device.end_tag()
return
# setcharspace # setcharspace
def do_Tc(self, space): def do_Tc(self, space):
self.textstate.charspace = space self.textstate.charspace = space
return
# setwordspace # setwordspace
def do_Tw(self, space): def do_Tw(self, space):
self.textstate.wordspace = space self.textstate.wordspace = space
return
# textscale # textscale
def do_Tz(self, scale): def do_Tz(self, scale):
self.textstate.scaling = scale self.textstate.scaling = scale
return
# setleading # setleading
def do_TL(self, leading): def do_TL(self, leading):
self.textstate.leading = -leading self.textstate.leading = -leading
return
# selectfont # selectfont
def do_Tf(self, fontid, fontsize): def do_Tf(self, fontid, fontsize):
try: try:
self.textstate.font = self.fontmap[literal_name(fontid)] self.textstate.font = self.fontmap[literal_name(fontid)]
except KeyError: except KeyError:
handle_error(PDFInterpreterError, 'Undefined Font id: %r' % fontid) if STRICT:
return raise PDFInterpreterError('Undefined Font id: %r' % fontid)
self.textstate.font = self.rsrcmgr.get_font(None, {})
self.textstate.fontsize = fontsize self.textstate.fontsize = fontsize
return
# setrendering # setrendering
def do_Tr(self, render): def do_Tr(self, render):
self.textstate.render = render self.textstate.render = render
return
# settextrise # settextrise
def do_Ts(self, rise): def do_Ts(self, rise):
self.textstate.rise = rise self.textstate.rise = rise
return
# text-move # text-move
def do_Td(self, tx, ty): def do_Td(self, tx, ty):
(a,b,c,d,e,f) = self.textstate.matrix (a, b, c, d, e, f) = self.textstate.matrix
self.textstate.matrix = (a,b,c,d,tx*a+ty*c+e,tx*b+ty*d+f) self.textstate.matrix = (a, b, c, d, tx*a+ty*c+e, tx*b+ty*d+f)
self.textstate.linematrix = (0, 0) self.textstate.linematrix = (0, 0)
#print >>sys.stderr, 'Td(%r,%r): %r' % (tx,ty,self.textstate) #print('Td(%r,%r): %r' % (tx, ty, self.textstate), file=sys.stderr)
return
# text-move # text-move
def do_TD(self, tx, ty): def do_TD(self, tx, ty):
(a,b,c,d,e,f) = self.textstate.matrix (a, b, c, d, e, f) = self.textstate.matrix
self.textstate.matrix = (a,b,c,d,tx*a+ty*c+e,tx*b+ty*d+f) self.textstate.matrix = (a, b, c, d, tx*a+ty*c+e, tx*b+ty*d+f)
self.textstate.leading = ty self.textstate.leading = ty
self.textstate.linematrix = (0, 0) self.textstate.linematrix = (0, 0)
#print >>sys.stderr, 'TD(%r,%r): %r' % (tx,ty,self.textstate) #print('TD(%r,%r): %r' % (tx, ty, self.textstate), file=sys.stderr)
return
# textmatrix # textmatrix
def do_Tm(self, a,b,c,d,e,f): def do_Tm(self, a, b, c, d, e, f):
self.textstate.matrix = (a,b,c,d,e,f) self.textstate.matrix = (a, b, c, d, e, f)
self.textstate.linematrix = (0, 0) self.textstate.linematrix = (0, 0)
return
# nextline # nextline
def do_T_a(self): def do_T_a(self):
(a,b,c,d,e,f) = self.textstate.matrix (a, b, c, d, e, f) = self.textstate.matrix
self.textstate.matrix = (a,b,c,d,self.textstate.leading*c+e,self.textstate.leading*d+f) self.textstate.matrix = (a, b, c, d, self.textstate.leading*c+e, self.textstate.leading*d+f)
self.textstate.linematrix = (0, 0) self.textstate.linematrix = (0, 0)
return
# show-pos # show-pos
def do_TJ(self, seq): def do_TJ(self, seq):
#print >>sys.stderr, 'TJ(%r): %r' % (seq,self.textstate) #print('TJ(%r): %r' % (seq, self.textstate), file=sys.stderr)
if self.textstate.font is None: if self.textstate.font is None:
handle_error(PDFInterpreterError, 'No font specified!') if STRICT:
raise PDFInterpreterError('No font specified!')
return return
self.device.render_string(self.textstate, seq) self.device.render_string(self.textstate, seq)
return
# show # show
def do_Tj(self, s): def do_Tj(self, s):
self.do_TJ([s]) self.do_TJ([s])
return
# quote # quote
def do__q(self, s): def do__q(self, s):
self.do_T_a() self.do_T_a()
self.do_TJ([s]) self.do_TJ([s])
return
# doublequote # doublequote
def do__w(self, aw, ac, s): def do__w(self, aw, ac, s):
self.do_Tw(aw) self.do_Tw(aw)
self.do_Tc(ac) self.do_Tc(ac)
self.do_TJ([s]) self.do_TJ([s])
return
# inline image # inline image
def do_BI(self): # never called def do_BI(self): # never called
pass return
def do_ID(self): # never called
pass def do_ID(self): # never called
return
def do_EI(self, obj): def do_EI(self, obj):
try: if 'W' in obj and 'H' in obj:
if 'W' in obj and 'H' in obj: iobjid = str(id(obj))
iobjid = str(id(obj)) self.device.begin_figure(iobjid, (0, 0, 1, 1), MATRIX_IDENTITY)
self.device.begin_figure(iobjid, (0,0,1,1), MATRIX_IDENTITY) self.device.render_image(iobjid, obj)
self.device.render_image(iobjid, obj) self.device.end_figure(iobjid)
self.device.end_figure(iobjid) return
except TypeError:
# Sometimes, 'obj' is a PSLiteral. I'm not sure why, but I'm guessing it's because it's
# malformed or something. We can just ignore the thing.
logger.warning("Malformed inline image")
# invoke an XObject # invoke an XObject
def do_Do(self, xobjid): def do_Do(self, xobjid):
@ -604,15 +801,16 @@ class PDFPageInterpreter:
try: try:
xobj = stream_value(self.xobjmap[xobjid]) xobj = stream_value(self.xobjmap[xobjid])
except KeyError: except KeyError:
handle_error(PDFInterpreterError, 'Undefined xobject id: %r' % xobjid) if STRICT:
raise PDFInterpreterError('Undefined xobject id: %r' % xobjid)
return return
logger.debug('Processing xobj: %r', xobj) if self.debug: logging.info('Processing xobj: %r' % xobj)
subtype = xobj.get('Subtype') subtype = xobj.get('Subtype')
if subtype is LITERAL_FORM and 'BBox' in xobj: if subtype is LITERAL_FORM and 'BBox' in xobj:
interpreter = self.dup() interpreter = self.dup()
bbox = list_value(xobj['BBox']) bbox = list_value(xobj['BBox'])
matrix = list_value(xobj.get('Matrix', MATRIX_IDENTITY)) matrix = list_value(xobj.get('Matrix', MATRIX_IDENTITY))
# According to PDF reference 1.7 section 4.9.1, XObjects in # According to PDF reference 1.7 section 4.9.1, XObjects in
# earlier PDFs (prior to v1.2) use the page's Resources entry # earlier PDFs (prior to v1.2) use the page's Resources entry
# instead of having their own Resources entry. # instead of having their own Resources entry.
resources = dict_value(xobj.get('Resources')) or self.resources.copy() resources = dict_value(xobj.get('Resources')) or self.resources.copy()
@ -620,36 +818,41 @@ class PDFPageInterpreter:
interpreter.render_contents(resources, [xobj], ctm=mult_matrix(matrix, self.ctm)) interpreter.render_contents(resources, [xobj], ctm=mult_matrix(matrix, self.ctm))
self.device.end_figure(xobjid) self.device.end_figure(xobjid)
elif subtype is LITERAL_IMAGE and 'Width' in xobj and 'Height' in xobj: elif subtype is LITERAL_IMAGE and 'Width' in xobj and 'Height' in xobj:
self.device.begin_figure(xobjid, (0,0,1,1), MATRIX_IDENTITY) self.device.begin_figure(xobjid, (0, 0, 1, 1), MATRIX_IDENTITY)
self.device.render_image(xobjid, xobj) self.device.render_image(xobjid, xobj)
self.device.end_figure(xobjid) self.device.end_figure(xobjid)
else: else:
# unsupported xobject type. # unsupported xobject type.
pass pass
return
def process_page(self, page): def process_page(self, page):
logger.debug('Processing page: %r', page) if self.debug: logging.info('Processing page: %r' % page)
(x0,y0,x1,y1) = page.mediabox (x0, y0, x1, y1) = page.mediabox
if page.rotate == 90: if page.rotate == 90:
ctm = (0,-1,1,0, -y0,x1) ctm = (0, -1, 1, 0, -y0, x1)
elif page.rotate == 180: elif page.rotate == 180:
ctm = (-1,0,0,-1, x1,y1) ctm = (-1, 0, 0, -1, x1, y1)
elif page.rotate == 270: elif page.rotate == 270:
ctm = (0,1,-1,0, y1,-x0) ctm = (0, 1, -1, 0, y1, -x0)
else: else:
ctm = (1,0,0,1, -x0,-y0) ctm = (1, 0, 0, 1, -x0, -y0)
self.device.begin_page(page, ctm) self.device.begin_page(page, ctm)
self.render_contents(page.resources, page.contents, ctm=ctm) self.render_contents(page.resources, page.contents, ctm=ctm)
self.device.end_page(page) self.device.end_page(page)
return
# render_contents(resources, streams, ctm) # render_contents(resources, streams, ctm)
# Render the content streams. # Render the content streams.
# This method may be called recursively. # This method may be called recursively.
def render_contents(self, resources, streams, ctm=MATRIX_IDENTITY): def render_contents(self, resources, streams, ctm=MATRIX_IDENTITY):
logger.debug('render_contents: resources=%r, streams=%r, ctm=%r', resources, streams, ctm) if self.debug:
logging.info('render_contents: resources=%r, streams=%r, ctm=%r' %
(resources, streams, ctm))
self.init_resources(resources) self.init_resources(resources)
self.init_state(ctm) self.init_state(ctm)
self.execute(list_value(streams)) self.execute(list_value(streams))
return
def execute(self, streams): def execute(self, streams):
try: try:
@ -659,50 +862,28 @@ class PDFPageInterpreter:
return return
while 1: while 1:
try: try:
(_,obj) = parser.nextobject() (_, obj) = parser.nextobject()
except PSEOF: except PSEOF:
break break
if isinstance(obj, PSKeyword): if isinstance(obj, PSKeyword):
name = keyword_name(obj) name = keyword_name(obj).decode('ascii')
method = 'do_%s' % name.replace('*','_a').replace('"','_w').replace("'",'_q') method = 'do_%s' % name.replace('*', '_a').replace('"', '_w').replace("'", '_q')
if hasattr(self, method): if hasattr(self, method):
func = getattr(self, method) func = getattr(self, method)
nargs = func.__code__.co_argcount-1 nargs = func.__code__.co_argcount-1
if nargs: if nargs:
args = self.pop(nargs) args = self.pop(nargs)
# logger.debug('exec: %s %r', name, args) if self.debug:
logging.debug('exec: %s %r' % (name, args))
if len(args) == nargs: if len(args) == nargs:
func(*args) func(*args)
else: else:
# logger.debug('exec: %s', name) if self.debug:
logging.debug('exec: %s' % name)
func() func()
else: else:
handle_error(PDFInterpreterError, 'Unknown operator: %r' % name) if STRICT:
raise PDFInterpreterError('Unknown operator: %r' % name)
else: else:
self.push(obj) self.push(obj)
return
class PDFTextExtractionNotAllowed(PDFInterpreterError): pass
def process_pdf(rsrcmgr, device, fp, pagenos=None, maxpages=0, password='',
caching=True, check_extractable=True):
# Create a PDF parser object associated with the file object.
parser = PDFParser(fp)
# Create a PDF document object that stores the document structure.
doc = PDFDocument(caching=caching)
# Connect the parser and document objects.
parser.set_document(doc)
doc.set_parser(parser)
# Supply the document password for initialization.
# (If no password is set, give an empty string.)
doc.initialize(password)
# Check if the document allows text extraction. If not, abort.
if check_extractable and not doc.is_extractable:
raise PDFTextExtractionNotAllowed('Text extraction is not allowed: %r' % fp)
# Create a PDF interpreter object.
interpreter = PDFPageInterpreter(rsrcmgr, device)
# Process each page contained in the document.
for (pageno,page) in enumerate(doc.get_pages()):
if pagenos and (pageno not in pagenos): continue
interpreter.process_page(page)
if maxpages and maxpages <= pageno+1: break

View File

@ -1,630 +1,26 @@
import io #!/usr/bin/env python
import re
import struct
import hashlib as md5
import logging import logging
from io import BytesIO
from .psparser import PSStackParser, PSSyntaxError, PSEOF, literal_name, LIT, KWD, handle_error from .psparser import PSStackParser
from .pdftypes import (PDFException, PDFTypeError, PDFNotImplementedError, PDFStream, PDFObjRef, from .psparser import PSSyntaxError
resolve1, decipher_all, int_value, str_value, list_value, dict_value, stream_value) from .psparser import PSEOF
from .arcfour import Arcfour from .psparser import KWD
from .utils import choplist, nunpack, decode_text, ObjIdRange from .psparser import STRICT
from .pdftypes import PDFException
from .pdftypes import PDFStream
logger = logging.getLogger(__name__) from .pdftypes import PDFObjRef
from .pdftypes import int_value
from .pdftypes import dict_value
## Exceptions ## Exceptions
## ##
class PDFSyntaxError(PDFException): pass class PDFSyntaxError(PDFException):
class PDFNoValidXRef(PDFSyntaxError): pass pass
class PDFNoOutlines(PDFException): pass
class PDFDestinationNotFound(PDFException): pass
class PDFAlreadyParsed(PDFException): pass
class PDFEncryptionError(PDFException): pass
class PDFPasswordIncorrect(PDFEncryptionError): pass
# some predefined literals and keywords.
LITERAL_OBJSTM = LIT('ObjStm')
LITERAL_XREF = LIT('XRef')
LITERAL_PAGE = LIT('Page')
LITERAL_PAGES = LIT('Pages')
LITERAL_CATALOG = LIT('Catalog')
class PDFBaseXRef:
def get_trailer(self):
raise NotImplementedError
def get_objids(self):
return []
def get_pos(self, objid):
raise KeyError(objid)
class PDFXRef(PDFBaseXRef):
def __init__(self):
self.offsets = {}
self.trailer = {}
def load(self, parser):
while 1:
try:
(pos, line) = parser.nextline()
if not line.strip(): continue
except PSEOF:
raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
if not line:
raise PDFNoValidXRef('Premature eof: %r' % parser)
if line.startswith('trailer'):
parser.setpos(pos)
break
f = line.strip().split(' ')
if len(f) != 2:
raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
try:
(start, nobjs) = list(map(int, f))
except ValueError:
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
for objid in range(start, start+nobjs):
try:
(_, line) = parser.nextline()
except PSEOF:
raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
f = line.strip().split(' ')
if len(f) != 3:
raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
(pos, genno, use) = f
if use != 'n': continue
self.offsets[objid] = (int(genno), int(pos))
logger.debug('xref objects: %r', self.offsets)
self.load_trailer(parser)
KEYWORD_TRAILER = KWD('trailer')
def load_trailer(self, parser):
try:
(_,kwd) = parser.nexttoken()
assert kwd is self.KEYWORD_TRAILER
(_,dic) = parser.nextobject()
except PSEOF:
x = parser.pop(1)
if not x:
raise PDFNoValidXRef('Unexpected EOF - file corrupted')
(_,dic) = x[0]
self.trailer.update(dict_value(dic))
PDFOBJ_CUE = re.compile(r'^(\d+)\s+(\d+)\s+obj\b')
def load_fallback(self, parser, debug=0):
parser.setpos(0)
while 1:
try:
(pos, line) = parser.nextline()
except PSEOF:
break
if line.startswith('trailer'):
parser.setpos(pos)
self.load_trailer(parser)
logger.debug('trailer: %r', self.get_trailer())
break
m = self.PDFOBJ_CUE.match(line)
if not m: continue
(objid, genno) = m.groups()
self.offsets[int(objid)] = (0, pos)
def get_trailer(self):
return self.trailer
def get_objids(self):
return iter(self.offsets.keys())
def get_pos(self, objid):
try:
(genno, pos) = self.offsets[objid]
except KeyError:
raise
return (None, pos)
class PDFXRefStream(PDFBaseXRef):
def __init__(self):
self.data = None
self.entlen = None
self.fl1 = self.fl2 = self.fl3 = None
self.objid_ranges = []
def __repr__(self):
return '<PDFXRefStream: fields=%d,%d,%d>' % (self.fl1, self.fl2, self.fl3)
def load(self, parser):
(_,objid) = parser.nexttoken() # ignored
(_,genno) = parser.nexttoken() # ignored
(_,kwd) = parser.nexttoken()
(_,stream) = parser.nextobject()
if not isinstance(stream, PDFStream) or stream['Type'] is not LITERAL_XREF:
raise PDFNoValidXRef('Invalid PDF stream spec.')
size = stream['Size']
index_array = stream.get('Index', (0,size))
if len(index_array) % 2 != 0:
raise PDFSyntaxError('Invalid index number')
self.objid_ranges.extend( ObjIdRange(start, nobjs)
for (start,nobjs) in choplist(2, index_array) )
(self.fl1, self.fl2, self.fl3) = stream['W']
self.data = stream.get_data()
self.entlen = self.fl1+self.fl2+self.fl3
self.trailer = stream.attrs
if logger.getEffectiveLevel() <= logging.DEBUG:
logger.debug('xref stream: objid=%s, fields=%d,%d,%d',
', '.join(map(repr, self.objid_ranges)), self.fl1, self.fl2, self.fl3)
def get_trailer(self):
return self.trailer
def get_objids(self):
for objid_range in self.objid_ranges:
for x in range(objid_range.get_start_id(), objid_range.get_end_id()+1):
yield x
def get_pos(self, objid):
offset = 0
found = False
for objid_range in self.objid_ranges:
if objid >= objid_range.get_start_id() and objid <= objid_range.get_end_id():
offset += objid - objid_range.get_start_id()
found = True
break
else:
offset += objid_range.get_nobjs()
if not found: raise KeyError(objid)
i = self.entlen * offset
ent = self.data[i:i+self.entlen]
f1 = nunpack(ent[:self.fl1], 1)
if f1 == 1:
pos = nunpack(ent[self.fl1:self.fl1+self.fl2])
genno = nunpack(ent[self.fl1+self.fl2:])
return (None, pos)
elif f1 == 2:
objid = nunpack(ent[self.fl1:self.fl1+self.fl2])
index = nunpack(ent[self.fl1+self.fl2:])
return (objid, index)
# this is a free object
raise KeyError(objid)
class PDFPage:
"""An object that holds the information about a page.
A PDFPage object is merely a convenience class that has a set
of keys and values, which describe the properties of a page
and point to its contents.
Attributes:
doc: a PDFDocument object.
pageid: any Python object that can uniquely identify the page.
attrs: a dictionary of page attributes.
contents: a list of PDFStream objects that represents the page content.
lastmod: the last modified time of the page.
resources: a list of resources used by the page.
mediabox: the physical size of the page.
cropbox: the crop rectangle of the page.
rotate: the page rotation (in degree).
annots: the page annotations.
beads: a chain that represents natural reading order.
"""
def __init__(self, doc, pageid, attrs):
"""Initialize a page object.
doc: a PDFDocument object.
pageid: any Python object that can uniquely identify the page.
attrs: a dictionary of page attributes.
"""
self.doc = doc
self.pageid = pageid
self.attrs = dict_value(attrs)
self.lastmod = resolve1(self.attrs.get('LastModified'))
self.resources = resolve1(self.attrs['Resources'])
self.mediabox = resolve1(self.attrs['MediaBox'])
if 'CropBox' in self.attrs:
self.cropbox = resolve1(self.attrs['CropBox'])
else:
self.cropbox = self.mediabox
self.rotate = (self.attrs.get('Rotate', 0)+360) % 360
self.annots = self.attrs.get('Annots')
self.beads = self.attrs.get('B')
if 'Contents' in self.attrs:
contents = resolve1(self.attrs['Contents'])
else:
contents = []
if not isinstance(contents, list):
contents = [ contents ]
self.contents = contents
def __repr__(self):
return '<PDFPage: Resources=%r, MediaBox=%r>' % (self.resources, self.mediabox)
class PDFDocument:
"""PDFDocument object represents a PDF document.
Since a PDF file can be very big, normally it is not loaded at
once. So PDF document has to cooperate with a PDF parser in order to
dynamically import the data as processing goes.
Typical usage:
doc = PDFDocument()
doc.set_parser(parser)
doc.initialize(password)
obj = doc.getobj(objid)
"""
KEYWORD_OBJ = KWD('obj')
def __init__(self, caching=True):
self.caching = caching
self.xrefs = []
self.info = []
self.catalog = None
self.encryption = None
self.decipher = None
self._parser = None
self._cached_objs = {}
self._parsed_objs = {}
self._parsed_everything = False
def _parse_next_object(self, parser):
# This is a bit awkward and I suspect that it could be a lot more elegant, but it would
# require refactoring the parsing process and I don't want to do that yet.
stack = []
_, token = parser.nexttoken()
while token is not self.KEYWORD_OBJ:
stack.append(token)
_, token = parser.nexttoken()
objid = stack[-2]
genno = stack[-1]
_, obj = parser.nextobject()
return objid, genno, obj
def _parse_objstream(self, stream):
# ObjStm have a special organization. First, the param "N" tells how many objs we have in
# there. Then, they start with a list of (objids, genno) pairs, and then the actual objects
# come in.
parser = PDFStreamParser(stream.get_data())
parser.set_document(self)
objcount = stream['N']
objids = []
for i in range(objcount):
_, objid = parser.nextobject()
_, genno = parser.nextobject()
objids.append(objid)
# Now we should be at the point where we read objects
for objid in objids:
_, obj = parser.nextobject()
self._cached_objs[objid] = obj
def _parse_whole(self, parser):
while True:
try:
objid, genno, obj = self._parse_next_object(parser)
self._cached_objs[objid] = obj
if isinstance(obj, PDFStream) and obj.get('Type') is LITERAL_OBJSTM:
obj.set_objid(objid, genno)
self._parse_objstream(obj)
except PSEOF:
break
def _parse_everything(self):
# Sometimes, we have malformed xref, but we still want to manage to read the PDF. In cases
# like these, the last resort is to read all objects at once so that our object reference
# can finally be resolved. This is slower than the normal method, so ony use this when the
# xref tables are corrupt/wrong/whatever.
if self._parsed_everything:
raise PDFAlreadyParsed()
parser = self._parser
parser.setpos(0)
parser.reset()
self._parse_whole(parser)
self._parsed_everything = True
def _getobj(self, objid):
if not self.xrefs:
raise PDFException('PDFDocument is not initialized')
# logger.debug('getobj: objid=%r', objid)
if objid in self._cached_objs:
genno = 0
obj = self._cached_objs[objid]
else:
strmid, index = self.find_obj_ref(objid)
if index is None:
handle_error(PDFSyntaxError, 'Cannot locate objid=%r' % objid)
# return null for a nonexistent reference.
return None
if strmid:
stream = self.getobj(strmid)
if stream is None:
return None
stream = stream_value(stream)
if stream.get('Type') is not LITERAL_OBJSTM:
handle_error(PDFSyntaxError, 'Not a stream object: %r' % stream)
try:
n = stream['N']
except KeyError:
handle_error(PDFSyntaxError, 'N is not defined: %r' % stream)
n = 0
if strmid in self._parsed_objs:
objs = self._parsed_objs[strmid]
else:
parser = PDFStreamParser(stream.get_data())
parser.set_document(self)
objs = []
try:
while True:
_, obj = parser.nextobject()
objs.append(obj)
except PSEOF:
pass
if self.caching:
self._parsed_objs[strmid] = objs
genno = 0
i = n*2+index
try:
obj = objs[i]
except IndexError:
raise PDFSyntaxError('Invalid object number: objid=%r' % (objid))
if isinstance(obj, PDFStream):
obj.set_objid(objid, 0)
else:
try:
self._parser.setpos(index)
except PSEOF:
handle_error(PSEOF, 'Parser index out of bounds')
return None
(_,objid1) = self._parser.nexttoken() # objid
(_,genno) = self._parser.nexttoken() # genno
(_,kwd) = self._parser.nexttoken()
# #### hack around malformed pdf files
#assert objid1 == objid, (objid, objid1)
if objid1 != objid:
x = []
while kwd is not self.KEYWORD_OBJ:
(_,kwd) = self._parser.nexttoken()
x.append(kwd)
if x:
objid1 = x[-2]
genno = x[-1]
# #### end hack around malformed pdf files
if kwd is not self.KEYWORD_OBJ:
raise PDFSyntaxError('Invalid object spec: offset=%r' % index)
try:
(_,obj) = self._parser.nextobject()
if isinstance(obj, PDFStream):
obj.set_objid(objid, genno)
except PSEOF:
return None
# logger.debug('register: objid=%r: %r', objid, obj)
if self.caching:
self._cached_objs[objid] = obj
if self.decipher:
obj = decipher_all(self.decipher, objid, genno, obj)
return obj
def set_parser(self, parser):
"Set the document to use a given PDFParser object."
if self._parser:
return
self._parser = parser
# Retrieve the information of each header that was appended
# (maybe multiple times) at the end of the document.
self.xrefs = parser.read_xref()
for xref in self.xrefs:
trailer = xref.get_trailer()
if not trailer: continue
# If there's an encryption info, remember it.
if 'Encrypt' in trailer:
#assert not self.encryption
self.encryption = (list_value(trailer['ID']),
dict_value(trailer['Encrypt']))
if 'Info' in trailer:
self.info.append(dict_value(trailer['Info']))
if 'Root' in trailer:
# Every PDF file must have exactly one /Root dictionary.
self.catalog = dict_value(trailer['Root'])
break
else:
raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
if self.catalog.get('Type') is not LITERAL_CATALOG:
handle_error(PDFSyntaxError, 'Catalog not found!')
# initialize(password='')
# Perform the initialization with a given password.
# This step is mandatory even if there's no password associated
# with the document.
PASSWORD_PADDING = b'(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
def initialize(self, password=''):
if not self.encryption:
self.is_printable = self.is_modifiable = self.is_extractable = True
return
(docid, param) = self.encryption
if literal_name(param.get('Filter')) != 'Standard':
raise PDFEncryptionError('Unknown filter: param=%r' % param)
V = int_value(param.get('V', 0))
if not (V == 1 or V == 2):
raise PDFEncryptionError('Unknown algorithm: param=%r' % param)
length = int_value(param.get('Length', 40)) # Key length (bits)
O = str_value(param['O'])
R = int_value(param['R']) # Revision
if 5 <= R:
raise PDFEncryptionError('Unknown revision: %r' % R)
U = str_value(param['U'])
P = int_value(param['P'])
self.is_printable = bool(P & 4)
self.is_modifiable = bool(P & 8)
self.is_extractable = bool(P & 16)
# Algorithm 3.2
# XXX is latin-1 the correct encoding???
password = password.encode('latin-1')
password = (password+self.PASSWORD_PADDING)[:32] # 1
hash = md5.md5(password) # 2
hash.update(O) # 3
hash.update(struct.pack('<l', P)) # 4
hash.update(docid[0]) # 5
if 4 <= R:
# 6
raise PDFNotImplementedError('Revision 4 encryption is currently unsupported')
if 3 <= R:
# 8
for _ in range(50):
hash = md5.md5(hash.digest()[:length//8])
key = hash.digest()[:length//8]
if R == 2:
# Algorithm 3.4
u1 = Arcfour(key).process(self.PASSWORD_PADDING)
elif R == 3:
# Algorithm 3.5
hash = md5.md5(self.PASSWORD_PADDING) # 2
hash.update(docid[0]) # 3
x = Arcfour(key).process(hash.digest()[:16]) # 4
for i in range(1,19+1):
k = bytes( c ^ i for c in key )
x = Arcfour(k).process(x)
u1 = x+x # 32bytes total
if R == 2:
is_authenticated = (u1 == U)
else:
is_authenticated = (u1[:16] == U[:16])
if not is_authenticated:
raise PDFPasswordIncorrect
self.decrypt_key = key
self.decipher = self.decrypt_rc4 # XXX may be AES
def decrypt_rc4(self, objid, genno, data):
key = self.decrypt_key + struct.pack('<L',objid)[:3]+struct.pack('<L',genno)[:2]
hash = md5.md5(key)
key = hash.digest()[:min(len(key),16)]
return Arcfour(key).process(data)
def readobj(self):
"""Read the next object at current position.
The object doesn't have to start exactly where we are. We'll read the first
object that comes to us.
"""
return self._parse_next_object(self._parser)
def find_obj_ref(self, objid):
for xref in self.xrefs:
try:
strmid, index = xref.get_pos(objid)
return strmid, index
except KeyError:
pass
else:
# return null for a nonexistent reference.
return None, None
def getobj(self, objid):
result = self._getobj(objid)
if result is None:
try:
self._parse_everything()
result = self._getobj(objid)
except PDFAlreadyParsed:
result = None
return result
INHERITABLE_ATTRS = {'Resources', 'MediaBox', 'CropBox', 'Rotate'}
def get_pages(self):
if not self.xrefs:
raise PDFException('PDFDocument is not initialized')
def search(obj, parent):
try:
if isinstance(obj, int):
objid = obj
tree = dict_value(self.getobj(objid), strict=True).copy()
else:
objid = obj.objid
tree = dict_value(obj, strict=True).copy()
except PDFTypeError:
return
for (k,v) in parent.items():
if k in self.INHERITABLE_ATTRS and k not in tree:
tree[k] = v
if tree.get('Type') is LITERAL_PAGES and 'Kids' in tree:
logger.debug('Pages: Kids=%r', tree['Kids'])
for c in list_value(tree['Kids']):
for x in search(c, tree):
yield x
elif tree.get('Type') is LITERAL_PAGE:
logger.debug('Page: %r', tree)
yield (objid, tree)
if 'Pages' not in self.catalog:
return
for (pageid,tree) in search(self.catalog['Pages'], self.catalog):
yield PDFPage(self, pageid, tree)
def get_outlines(self):
if 'Outlines' not in self.catalog:
raise PDFNoOutlines
def search(entry, level):
entry = dict_value(entry)
if 'Title' in entry:
if 'A' in entry or 'Dest' in entry:
title = decode_text(str_value(entry['Title']))
dest = entry.get('Dest')
action = entry.get('A')
se = entry.get('SE')
yield (level, title, dest, action, se)
if 'First' in entry and 'Last' in entry:
for x in search(entry['First'], level+1):
yield x
if 'Next' in entry:
for x in search(entry['Next'], level):
yield x
return search(self.catalog['Outlines'], 0)
def lookup_name(self, cat, key):
try:
names = dict_value(self.catalog['Names'])
except (PDFTypeError, KeyError):
raise KeyError((cat,key))
# may raise KeyError
d0 = dict_value(names[cat])
def lookup(d):
if 'Limits' in d:
(k1,k2) = list_value(d['Limits'])
if key < k1 or k2 < key: return None
if 'Names' in d:
objs = list_value(d['Names'])
names = dict(choplist(2, objs))
return names[key]
if 'Kids' in d:
for c in list_value(d['Kids']):
v = lookup(dict_value(c))
if v: return v
raise KeyError((cat,key))
return lookup(d0)
def get_dest(self, name):
try:
# PDF-1.2 or later
obj = self.lookup_name('Dests', name)
except KeyError:
# PDF-1.1 or prior
if 'Dests' not in self.catalog:
raise PDFDestinationNotFound(name)
d0 = dict_value(self.catalog['Dests'])
if name not in d0:
raise PDFDestinationNotFound(name)
obj = d0[name]
return obj
## PDFParser
##
class PDFParser(PSStackParser): class PDFParser(PSStackParser):
""" """
@ -636,33 +32,37 @@ class PDFParser(PSStackParser):
Typical usage: Typical usage:
parser = PDFParser(fp) parser = PDFParser(fp)
parser.read_xref() parser.read_xref()
parser.read_xref(fallback=True) # optional
parser.set_document(doc) parser.set_document(doc)
parser.seek(offset) parser.seek(offset)
parser.nextobject() parser.nextobject()
""" """
def __init__(self, fp): def __init__(self, fp):
PSStackParser.__init__(self, fp) PSStackParser.__init__(self, fp)
self.doc = None self.doc = None
self.fallback = False self.fallback = False
return
def set_document(self, doc): def set_document(self, doc):
"""Associates the parser with a PDFDocument object.""" """Associates the parser with a PDFDocument object."""
self.doc = doc self.doc = doc
return
KEYWORD_R = KWD(b'R')
KEYWORD_NULL = KWD(b'null')
KEYWORD_ENDOBJ = KWD(b'endobj')
KEYWORD_STREAM = KWD(b'stream')
KEYWORD_XREF = KWD(b'xref')
KEYWORD_STARTXREF = KWD(b'startxref')
KEYWORD_R = KWD('R')
KEYWORD_NULL = KWD('null')
KEYWORD_ENDOBJ = KWD('endobj')
KEYWORD_STREAM = KWD('stream')
KEYWORD_XREF = KWD('xref')
KEYWORD_STARTXREF = KWD('startxref')
def do_keyword(self, pos, token): def do_keyword(self, pos, token):
"""Handles PDF-related keywords.""" """Handles PDF-related keywords."""
if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF): if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
self.add_results(*self.pop(1)) self.add_results(*self.pop(1))
elif token is self.KEYWORD_ENDOBJ: elif token is self.KEYWORD_ENDOBJ:
self.add_results(*self.pop(4)) self.add_results(*self.pop(4))
@ -673,7 +73,7 @@ class PDFParser(PSStackParser):
elif token is self.KEYWORD_R: elif token is self.KEYWORD_R:
# reference to indirect object # reference to indirect object
try: try:
((_,objid), (_,genno)) = self.pop(2) ((_, objid), (_, genno)) = self.pop(2)
(objid, genno) = (int(objid), int(genno)) (objid, genno) = (int(objid), int(genno))
obj = PDFObjRef(self.doc, objid, genno) obj = PDFObjRef(self.doc, objid, genno)
self.push((pos, obj)) self.push((pos, obj))
@ -682,102 +82,59 @@ class PDFParser(PSStackParser):
elif token is self.KEYWORD_STREAM: elif token is self.KEYWORD_STREAM:
# stream object # stream object
((_,dic),) = self.pop(1) ((_, dic),) = self.pop(1)
dic = dict_value(dic) dic = dict_value(dic)
try: objlen = 0
objlen = int_value(dic['Length']) if not self.fallback:
except KeyError: try:
handle_error(PDFSyntaxError, '/Length is undefined: %r' % dic) objlen = int_value(dic['Length'])
objlen = 0 except KeyError:
self.setpos(pos) if STRICT:
raise PDFSyntaxError('/Length is undefined: %r' % dic)
self.seek(pos)
try: try:
(_, line) = self.nextline() # 'stream' (_, line) = self.nextline() # 'stream'
except PSEOF: except PSEOF:
handle_error(PDFSyntaxError, 'Unexpected EOF') if STRICT:
raise PDFSyntaxError('Unexpected EOF')
return return
pos += len(line) pos += len(line)
endpos = pos + objlen self.fp.seek(pos)
if 'endstream' not in self.data[endpos:endpos+len('endstream')+2]: data = self.fp.read(objlen)
r = re.compile(r'(\r\n|\r|\n)endstream') self.seek(pos+objlen)
m = r.search(self.data, pos) while 1:
if m is None: try:
raise PDFSyntaxError("stream with no endstream") (linepos, line) = self.nextline()
endpos = m.start() except PSEOF:
data = self.data[pos:endpos].encode('latin-1') if STRICT:
self.setpos(endpos) raise PDFSyntaxError('Unexpected EOF')
self.nexttoken() # consume 'endstream' break
if b'endstream' in line:
i = line.index(b'endstream')
objlen += i
if self.fallback:
data += line[:i]
break
objlen += len(line)
if self.fallback:
data += line
self.seek(pos+objlen)
# XXX limit objlen not to exceed object boundary # XXX limit objlen not to exceed object boundary
# logger.debug('Stream: pos=%d, objlen=%d, dic=%r, data=%r...', pos, objlen, dic, data[:10]) if self.debug:
logging.debug('Stream: pos=%d, objlen=%d, dic=%r, data=%r...' % \
(pos, objlen, dic, data[:10]))
obj = PDFStream(dic, data, self.doc.decipher) obj = PDFStream(dic, data, self.doc.decipher)
self.push((pos, obj)) self.push((pos, obj))
else: else:
# others # others
self.push((pos, token)) self.push((pos, token))
def find_xref(self): return
"""Internal function used to locate the first XRef."""
# the word 'startxref' followed by a newline followed by digits
re_startxref = re.compile(r'startxref\s*[\r\n]+\s*(\d+)', re.MULTILINE)
# try at the end, then try the whole file.
m = re_startxref.findall(self.data, len(self.data)-4096)
if not m:
m = re_startxref.findall(self.data)
if not m:
raise PDFNoValidXRef('Unexpected EOF')
logger.debug('xref found: pos=%r', m[-1])
return int(m[-1])
# read xref table
def read_xref_from(self, start, xrefs):
"""Reads XRefs from the given location."""
self.setpos(start)
self.reset()
try:
(pos, token) = self.nexttoken()
except PSEOF:
raise PDFNoValidXRef('Unexpected EOF')
# logger.debug('read_xref_from: start=%d, token=%r', start, token)
if isinstance(token, int):
# XRefStream: PDF-1.5
self.setpos(pos)
self.reset()
xref = PDFXRefStream()
xref.load(self)
else:
if token is self.KEYWORD_XREF:
self.nextline()
xref = PDFXRef()
xref.load(self)
xrefs.append(xref)
trailer = xref.get_trailer()
logger.debug('trailer: %r', trailer)
if 'XRefStm' in trailer:
pos = int_value(trailer['XRefStm'])
self.read_xref_from(pos, xrefs)
if 'Prev' in trailer:
# find previous xref
pos = int_value(trailer['Prev'])
self.read_xref_from(pos, xrefs)
# read xref tables and trailers
def read_xref(self):
"""Reads all the XRefs in the PDF file and returns them."""
xrefs = []
try:
pos = self.find_xref()
self.read_xref_from(pos, xrefs)
except PDFNoValidXRef:
# fallback
logger.debug('no xref, fallback')
self.fallback = True
xref = PDFXRef()
xref.load_fallback(self)
xrefs.append(xref)
return xrefs
## PDFStreamParser
##
class PDFStreamParser(PDFParser): class PDFStreamParser(PDFParser):
""" """
@ -789,21 +146,31 @@ class PDFStreamParser(PDFParser):
""" """
def __init__(self, data): def __init__(self, data):
PDFParser.__init__(self, io.BytesIO(data)) PDFParser.__init__(self, BytesIO(data))
return
def flush(self): def flush(self):
self.add_results(*self.popall()) self.add_results(*self.popall())
return
KEYWORD_OBJ = KWD(b'obj')
def do_keyword(self, pos, token): def do_keyword(self, pos, token):
if token is self.KEYWORD_R: if token is self.KEYWORD_R:
# reference to indirect object # reference to indirect object
try: try:
((_,objid), (_,genno)) = self.pop(2) ((_, objid), (_, genno)) = self.pop(2)
(objid, genno) = (int(objid), int(genno)) (objid, genno) = (int(objid), int(genno))
obj = PDFObjRef(self.doc, objid, genno) obj = PDFObjRef(self.doc, objid, genno)
self.push((pos, obj)) self.push((pos, obj))
except PSSyntaxError: except PSSyntaxError:
pass pass
return return
elif token in (self.KEYWORD_OBJ, self.KEYWORD_ENDOBJ):
if STRICT:
# See PDF Spec 3.4.6: Only the object values are stored in the
# stream; the obj and endobj keywords are not used.
raise PDFSyntaxError('Keyword endobj found in stream')
return
# others # others
self.push((pos, token)) self.push((pos, token))
return

View File

@ -1,11 +1,17 @@
from functools import partial #!/usr/bin/env python
import zlib import zlib
from .lzw import lzwdecode from .lzw import lzwdecode
from .ascii85 import ascii85decode, asciihexdecode from .ascii85 import ascii85decode
from .ascii85 import asciihexdecode
from .runlength import rldecode from .runlength import rldecode
from .psparser import PSException, PSObject, STRICT from .ccitt import ccittfaxdecode
from .psparser import LIT, handle_error from .psparser import PSException
from .psparser import PSObject
from .psparser import LIT
from .psparser import STRICT
from .utils import apply_png_predictor from .utils import apply_png_predictor
from .utils import isnumber
LITERAL_CRYPT = LIT('Crypt') LITERAL_CRYPT = LIT('Crypt')
@ -21,12 +27,23 @@ LITERALS_DCT_DECODE = (LIT('DCTDecode'), LIT('DCT'))
## PDF Objects ## PDF Objects
## ##
class PDFObject(PSObject): pass class PDFObject(PSObject):
pass
class PDFException(PSException): pass class PDFException(PSException):
class PDFTypeError(PDFException): pass pass
class PDFValueError(PDFException): pass
class PDFNotImplementedError(PSException): pass class PDFTypeError(PDFException):
pass
class PDFValueError(PDFException):
pass
class PDFObjectNotFound(PDFException):
pass
class PDFNotImplementedError(PDFException):
pass
## PDFObjRef ## PDFObjRef
@ -35,82 +52,130 @@ class PDFObjRef(PDFObject):
def __init__(self, doc, objid, _): def __init__(self, doc, objid, _):
if objid == 0: if objid == 0:
handle_error(PDFValueError, 'PDF object id cannot be 0.') if STRICT:
raise PDFValueError('PDF object id cannot be 0.')
self.doc = doc self.doc = doc
self.objid = objid self.objid = objid
#self.genno = genno # Never used. #self.genno = genno # Never used.
return
def __repr__(self): def __repr__(self):
return '<PDFObjRef:%d>' % (self.objid) return '<PDFObjRef:%d>' % (self.objid)
def resolve(self): def resolve(self, default=None):
return self.doc.getobj(self.objid) try:
return self.doc.getobj(self.objid)
except PDFObjectNotFound:
return default
# resolve # resolve
def resolve1(x): def resolve1(x, default=None):
"""Resolves an object. """Resolves an object.
If this is an array or dictionary, it may still contains If this is an array or dictionary, it may still contains
some indirect objects inside. some indirect objects inside.
""" """
while isinstance(x, PDFObjRef): while isinstance(x, PDFObjRef):
x = x.resolve() x = x.resolve(default=default)
return x return x
def resolve_all(x):
def resolve_all(x, default=None):
"""Recursively resolves the given object and all the internals. """Recursively resolves the given object and all the internals.
Make sure there is no indirect reference within the nested object. Make sure there is no indirect reference within the nested object.
This procedure might be slow. This procedure might be slow.
""" """
while isinstance(x, PDFObjRef): while isinstance(x, PDFObjRef):
x = x.resolve() x = x.resolve(default=default)
if isinstance(x, list): if isinstance(x, list):
x = [ resolve_all(v) for v in x ] x = [resolve_all(v, default=default) for v in x]
elif isinstance(x, dict): elif isinstance(x, dict):
for (k,v) in x.items(): for (k, v) in x.items():
x[k] = resolve_all(v) x[k] = resolve_all(v, default=default)
return x return x
def decipher_all(decipher, objid, genno, x): def decipher_all(decipher, objid, genno, x):
"""Recursively deciphers the given object. """Recursively deciphers the given object.
""" """
if isinstance(x, str):
x = x.encode('latin-1')
if isinstance(x, bytes): if isinstance(x, bytes):
return decipher(objid, genno, x) return decipher(objid, genno, x)
if isinstance(x, list): if isinstance(x, list):
x = [ decipher_all(decipher, objid, genno, v) for v in x ] x = [decipher_all(decipher, objid, genno, v) for v in x]
elif isinstance(x, dict): elif isinstance(x, dict):
for (k,v) in x.items(): for (k, v) in x.items():
x[k] = decipher_all(decipher, objid, genno, v) x[k] = decipher_all(decipher, objid, genno, v)
return x return x
# Type cheking
def typecheck_value(x, type, strict=STRICT): # Type checking
def int_value(x):
x = resolve1(x) x = resolve1(x)
if not isinstance(x, type): if not isinstance(x, int):
handle_error(PDFTypeError, 'Wrong type: %r required: %r' % (x, type), strict=strict) if STRICT:
default_type = type[0] if isinstance(type, tuple) else type raise PDFTypeError('Integer required: %r' % x)
return default_type() return 0
return x
def float_value(x):
x = resolve1(x)
if not isinstance(x, float):
if STRICT:
raise PDFTypeError('Float required: %r' % x)
return 0.0
return x
def num_value(x):
x = resolve1(x)
if not isnumber(x):
if STRICT:
raise PDFTypeError('Int or Float required: %r' % x)
return 0
return x
def bytes_value(x):
x = resolve1(x)
if not isinstance(x, bytes):
if STRICT:
raise PDFTypeError('Bytes required: %r' % x)
return b''
return x
def list_value(x):
x = resolve1(x)
if not isinstance(x, (list, tuple)):
if STRICT:
raise PDFTypeError('List required: %r' % x)
return []
return x
def dict_value(x):
x = resolve1(x)
if not isinstance(x, dict):
if STRICT:
raise PDFTypeError('Dict required: %r' % x)
return {}
return x return x
int_value = partial(typecheck_value, type=int)
float_value = partial(typecheck_value, type=float)
num_value = partial(typecheck_value, type=(int, float))
str_value = partial(typecheck_value, type=(str, bytes))
list_value = partial(typecheck_value, type=(list, tuple))
dict_value = partial(typecheck_value, type=dict)
def stream_value(x): def stream_value(x):
x = resolve1(x) x = resolve1(x)
if not isinstance(x, PDFStream): if not isinstance(x, PDFStream):
handle_error(PDFTypeError, 'PDFStream required: %r' % x) if STRICT:
return PDFStream({}, b'') raise PDFTypeError('PDFStream required: %r' % x)
return PDFStream({}, '')
return x return x
## PDFStream type
##
class PDFStream(PDFObject): class PDFStream(PDFObject):
def __init__(self, attrs, rawdata, decipher=None): def __init__(self, attrs, rawdata, decipher=None):
@ -121,10 +186,12 @@ class PDFStream(PDFObject):
self.data = None self.data = None
self.objid = None self.objid = None
self.genno = None self.genno = None
return
def set_objid(self, objid, genno): def set_objid(self, objid, genno):
self.objid = objid self.objid = objid
self.genno = genno self.genno = genno
return
def __repr__(self): def __repr__(self):
if self.data is None: if self.data is None:
@ -136,13 +203,13 @@ class PDFStream(PDFObject):
def __contains__(self, name): def __contains__(self, name):
return name in self.attrs return name in self.attrs
def __getitem__(self, name): def __getitem__(self, name):
return self.attrs[name] return self.attrs[name]
def get(self, name, default=None): def get(self, name, default=None):
return self.attrs.get(name, default) return self.attrs.get(name, default)
def get_any(self, names, default=None): def get_any(self, names, default=None):
for name in names: for name in names:
if name in self.attrs: if name in self.attrs:
@ -151,33 +218,37 @@ class PDFStream(PDFObject):
def get_filters(self): def get_filters(self):
filters = self.get_any(('F', 'Filter')) filters = self.get_any(('F', 'Filter'))
if not filters: return [] params = self.get_any(('DP', 'DecodeParms', 'FDecodeParms'), {})
if isinstance(filters, list): return filters if not filters:
return [ filters ] return []
if not isinstance(filters, list):
filters = [filters]
if not isinstance(params, list):
# Make sure the parameters list is the same as filters.
params = [params]*len(filters)
if STRICT and len(params) != len(filters):
raise PDFException("Parameters len filter mismatch")
return zip(filters, params)
def decode(self): def decode(self):
assert self.data is None and self.rawdata != None assert self.data is None and self.rawdata is not None
data = self.rawdata data = self.rawdata
if self.decipher: if self.decipher:
# Handle encryption # Handle encryption
data = self.decipher(self.objid, self.genno, data) data = self.decipher(self.objid, self.genno, data, self.attrs)
filters = self.get_filters() filters = self.get_filters()
if not filters: if not filters:
self.data = data self.data = data
self.rawdata = None self.rawdata = None
return return
for f in filters: for (f,params) in filters:
# Yeah, we can have references to an object containing a literal.
f = resolve1(f)
if f is None:
# Oops, broken reference. use FlateDecode since it's the most popular.
f = LIT('FlateDecode')
if f in LITERALS_FLATE_DECODE: if f in LITERALS_FLATE_DECODE:
# will get errors if the document is encrypted. # will get errors if the document is encrypted.
try: try:
data = zlib.decompress(data) data = zlib.decompress(data)
except zlib.error as e: except zlib.error as e:
handle_error(PDFException, 'Invalid zlib bytes: %r, %r' % (e, data)) if STRICT:
raise PDFException('Invalid zlib bytes: %r, %r' % (e, data))
data = b'' data = b''
elif f in LITERALS_LZW_DECODE: elif f in LITERALS_LZW_DECODE:
data = lzwdecode(data) data = lzwdecode(data)
@ -187,20 +258,18 @@ class PDFStream(PDFObject):
data = asciihexdecode(data) data = asciihexdecode(data)
elif f in LITERALS_RUNLENGTH_DECODE: elif f in LITERALS_RUNLENGTH_DECODE:
data = rldecode(data) data = rldecode(data)
elif f in LITERALS_DCT_DECODE:
# /DCTDecode is essentially a jpeg image. There's nothing to "decode" per se, simply
# use the data as jpeg data.
pass
elif f in LITERALS_CCITTFAX_DECODE: elif f in LITERALS_CCITTFAX_DECODE:
#data = ccittfaxdecode(data) data = ccittfaxdecode(data, params)
raise PDFNotImplementedError('Unsupported filter: %r' % f) elif f in LITERALS_DCT_DECODE:
# This is probably a JPG stream - it does not need to be decoded twice.
# Just return the stream to the user.
pass
elif f == LITERAL_CRYPT: elif f == LITERAL_CRYPT:
# not yet.. # not yet..
raise PDFNotImplementedError('/Crypt filter is unsupported') raise PDFNotImplementedError('/Crypt filter is unsupported')
else: else:
raise PDFNotImplementedError('Unsupported filter: %r' % f) raise PDFNotImplementedError('Unsupported filter: %r' % f)
# apply predictors # apply predictors
params = self.get_any(('DP', 'DecodeParms', 'FDecodeParms'), {})
if 'Predictor' in params: if 'Predictor' in params:
pred = int_value(params['Predictor']) pred = int_value(params['Predictor'])
if pred == 1: if pred == 1:
@ -211,14 +280,12 @@ class PDFStream(PDFObject):
colors = int_value(params.get('Colors', 1)) colors = int_value(params.get('Colors', 1))
columns = int_value(params.get('Columns', 1)) columns = int_value(params.get('Columns', 1))
bitspercomponent = int_value(params.get('BitsPerComponent', 8)) bitspercomponent = int_value(params.get('BitsPerComponent', 8))
try: data = apply_png_predictor(pred, colors, columns, bitspercomponent, data)
data = apply_png_predictor(pred, colors, columns, bitspercomponent, data)
except ValueError: # predictor not supported
data = b''
else: else:
raise PDFNotImplementedError('Unsupported predictor: %r' % pred) raise PDFNotImplementedError('Unsupported predictor: %r' % pred)
self.data = data self.data = data
self.rawdata = None self.rawdata = None
return
def get_data(self): def get_data(self):
if self.data is None: if self.data is None:

View File

@ -1,38 +1,51 @@
#!/usr/bin/env python
import re import re
import logging import logging
from .utils import choplist from .utils import choplist
from . import pslexer
STRICT = False STRICT = 0
## PS Exceptions ## PS Exceptions
## ##
class PSException(Exception): pass class PSException(Exception):
class PSEOF(PSException): pass pass
class PSSyntaxError(PSException): pass
class PSTypeError(PSException): pass
class PSValueError(PSException): pass class PSEOF(PSException):
pass
class PSSyntaxError(PSException):
pass
class PSTypeError(PSException):
pass
class PSValueError(PSException):
pass
def handle_error(exctype, msg, strict=STRICT):
if strict:
raise exctype(msg)
else:
logging.warning(msg)
## Basic PostScript Types ## Basic PostScript Types
## ##
## PSObject
##
class PSObject: class PSObject:
"""Base class for all PS or PDF-related data types.""" """Base class for all PS or PDF-related data types."""
pass
## PSLiteral
##
class PSLiteral(PSObject): class PSLiteral(PSObject):
"""A class that represents a PostScript literal. """A class that represents a PostScript literal.
Postscript literals are used as identifiers, such as Postscript literals are used as identifiers, such as
variable names, property names and dictionary keys. variable names, property names and dictionary keys.
Literals are case sensitive and denoted by a preceding Literals are case sensitive and denoted by a preceding
@ -44,40 +57,47 @@ class PSLiteral(PSObject):
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
return
def __repr__(self): def __repr__(self):
return '/%s' % self.name return '/%r' % self.name
## PSKeyword
##
class PSKeyword(PSObject): class PSKeyword(PSObject):
"""A class that represents a PostScript keyword. """A class that represents a PostScript keyword.
PostScript keywords are a dozen of predefined words. PostScript keywords are a dozen of predefined words.
Commands and directives in PostScript are expressed by keywords. Commands and directives in PostScript are expressed by keywords.
They are also used to denote the content boundaries. They are also used to denote the content boundaries.
Note: Do not create an instance of PSKeyword directly. Note: Do not create an instance of PSKeyword directly.
Always use PSKeywordTable.intern(). Always use PSKeywordTable.intern().
""" """
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
return
def __repr__(self): def __repr__(self):
return self.name return self.name.decode('ascii')
## PSSymbolTable
##
class PSSymbolTable: class PSSymbolTable:
"""A utility class for storing PSLiteral/PSKeyword objects. """A utility class for storing PSLiteral/PSKeyword objects.
Interned objects can be checked its identity with "is" operator. Interned objects can be checked its identity with "is" operator.
""" """
def __init__(self, klass): def __init__(self, klass):
self.dict = {} self.dict = {}
self.klass = klass self.klass = klass
return
def intern(self, name): def intern(self, name):
if name in self.dict: if name in self.dict:
@ -91,145 +111,461 @@ PSLiteralTable = PSSymbolTable(PSLiteral)
PSKeywordTable = PSSymbolTable(PSKeyword) PSKeywordTable = PSSymbolTable(PSKeyword)
LIT = PSLiteralTable.intern LIT = PSLiteralTable.intern
KWD = PSKeywordTable.intern KWD = PSKeywordTable.intern
KEYWORD_PROC_BEGIN = KWD('{') KEYWORD_PROC_BEGIN = KWD(b'{')
KEYWORD_PROC_END = KWD('}') KEYWORD_PROC_END = KWD(b'}')
KEYWORD_ARRAY_BEGIN = KWD('[') KEYWORD_ARRAY_BEGIN = KWD(b'[')
KEYWORD_ARRAY_END = KWD(']') KEYWORD_ARRAY_END = KWD(b']')
KEYWORD_DICT_BEGIN = KWD('<<') KEYWORD_DICT_BEGIN = KWD(b'<<')
KEYWORD_DICT_END = KWD('>>') KEYWORD_DICT_END = KWD(b'>>')
def literal_name(x): def literal_name(x):
if not isinstance(x, PSLiteral): if not isinstance(x, PSLiteral):
handle_error(PSTypeError, 'Literal required: %r' % x) if STRICT:
return str(x) raise PSTypeError('Literal required: %r' % (x,))
else:
return str(x)
return x.name return x.name
def keyword_name(x): def keyword_name(x):
if not isinstance(x, PSKeyword): if not isinstance(x, PSKeyword):
handle_error(PSTypeError, 'Keyword required: %r' % x) if STRICT:
return str(x) raise PSTypeError('Keyword required: %r' % (x,))
else:
return str(x)
return x.name return x.name
## About PSParser, bytes and strings and all that ## PSBaseParser
## ##
## Most of the contents (well, maybe not in size, but in "parsing effort") of a PDF file is text, EOL = re.compile(br'[\r\n]')
## but in some cases, namely streams, there's binary data involved. What we do is that we read the SPC = re.compile(br'\s')
## data as latin-1. When binary data is encountered, we have to re-encode it as latin-1 as well. NONSPC = re.compile(br'\S')
HEX = re.compile(br'[0-9a-fA-F]')
END_LITERAL = re.compile(br'[#/%\[\]()<>{}\s]')
END_HEX_STRING = re.compile(br'[^\s0-9a-fA-F]')
HEX_PAIR = re.compile(br'[0-9a-fA-F]{2}|.')
END_NUMBER = re.compile(br'[^0-9]')
END_KEYWORD = re.compile(br'[#/%\[\]()<>{}\s]')
END_STRING = re.compile(br'[()\134]')
OCT_STRING = re.compile(br'[0-7]')
ESC_STRING = {
b'b': b'\x08', b't': b'\x09', b'n': b'\x0a', b'f': b'\x0c',
b'r': b'\x0d', b'(': b'(', b')': b')', b'\\': b'\\'
}
## About reading all data at once
## There used to be a buffering mechanism in place, but it made everything rather complicated and
## all this string buffering operations, especially with the ply lexer, ended up being rather slow.
## We read the whole thing in memory now. Sure, some PDFs are rather large, but computers today
## have lots of memory. At first, I wanted to use a mmap, but these are binary and making them work
## with the ply lexer was very complicated. Maybe one day.
EOL = re.compile(r'\r\n|\r|\n', re.MULTILINE)
class PSBaseParser: class PSBaseParser:
"""Most basic PostScript parser that performs only tokenization. """Most basic PostScript parser that performs only tokenization.
""" """
def __init__(self, fp): BUFSIZ = 4096
data = fp.read()
if isinstance(data, bytes): debug = 0
data = data.decode('latin-1')
self.data = data def __init__(self, fp):
self.lex = pslexer.lexer.clone() self.fp = fp
self.lex.input(data) self.seek(0)
return
def __repr__(self):
return '<%s: %r, bufpos=%d>' % (self.__class__.__name__, self.fp, self.bufpos)
def _convert_token(self, token):
# converts `token` which comes from pslexer to a normal token.
if token.type in {'KEYWORD', 'OPERATOR'}:
if token.value == 'true':
return True
elif token.value == 'false':
return False
else:
return KWD(token.value)
elif token.type == 'LITERAL':
return LIT(token.value)
else:
return token.value
def flush(self): def flush(self):
pass return
def close(self): def close(self):
self.flush() self.flush()
del self.lex return
del self.data
def setpos(self, newpos):
if newpos >= self.lex.lexlen:
raise PSEOF()
self.lex.lexpos = newpos
def nextline(self):
m = EOL.search(self.data, pos=self.lex.lexpos)
if m is None:
raise PSEOF()
start = self.lex.lexpos
s = self.data[start:m.end()]
self.lex.lexpos = m.end()
return (start, s)
def nexttoken(self):
token = self.lex.token()
if token is None:
raise PSEOF()
tokenpos = token.lexpos
return (tokenpos, self._convert_token(token))
def tell(self):
return self.bufpos+self.charpos
def poll(self, pos=None, n=80):
pos0 = self.fp.tell()
if not pos:
pos = self.bufpos+self.charpos
self.fp.seek(pos)
logging.info('poll(%d): %r' % (pos, self.fp.read(n)))
self.fp.seek(pos0)
return
def seek(self, pos):
"""Seeks the parser to the given position.
"""
if self.debug:
logging.debug('seek: %r' % pos)
self.fp.seek(pos)
# reset the status for nextline()
self.bufpos = pos
self.buf = b''
self.charpos = 0
# reset the status for nexttoken()
self._parse1 = self._parse_main
self._curtoken = b''
self._curtokenpos = 0
self._tokens = []
return
def fillbuf(self):
if self.charpos < len(self.buf):
return
# fetch next chunk.
self.bufpos = self.fp.tell()
self.buf = self.fp.read(self.BUFSIZ)
if not self.buf:
raise PSEOF('Unexpected EOF')
self.charpos = 0
return
def nextline(self):
"""Fetches a next line that ends either with \\r or \\n.
"""
linebuf = b''
linepos = self.bufpos + self.charpos
eol = False
while 1:
self.fillbuf()
if eol:
c = self.buf[self.charpos:self.charpos+1]
# handle b'\r\n'
if c == b'\n':
linebuf += c
self.charpos += 1
break
m = EOL.search(self.buf, self.charpos)
if m:
linebuf += self.buf[self.charpos:m.end(0)]
self.charpos = m.end(0)
if linebuf[-1:] == b'\r':
eol = True
else:
break
else:
linebuf += self.buf[self.charpos:]
self.charpos = len(self.buf)
if self.debug:
logging.debug('nextline: %r, %r' % (linepos, linebuf))
return (linepos, linebuf)
def revreadlines(self):
"""Fetches a next line backward.
This is used to locate the trailers at the end of a file.
"""
self.fp.seek(0, 2)
pos = self.fp.tell()
buf = b''
while 0 < pos:
prevpos = pos
pos = max(0, pos-self.BUFSIZ)
self.fp.seek(pos)
s = self.fp.read(prevpos-pos)
if not s:
break
while 1:
n = max(s.rfind(b'\r'), s.rfind(b'\n'))
if n == -1:
buf = s + buf
break
yield s[n:]+buf
s = s[:n]
buf = b''
return
def _parse_main(self, s, i):
m = NONSPC.search(s, i)
if not m:
return len(s)
j = m.start(0)
c = s[j:j+1]
self._curtokenpos = self.bufpos+j
if c == b'%':
self._curtoken = b'%'
self._parse1 = self._parse_comment
return j+1
elif c == b'/':
self._curtoken = b''
self._parse1 = self._parse_literal
return j+1
elif c in b'-+' or c.isdigit():
self._curtoken = c
self._parse1 = self._parse_number
return j+1
elif c == b'.':
self._curtoken = c
self._parse1 = self._parse_float
return j+1
elif c.isalpha():
self._curtoken = c
self._parse1 = self._parse_keyword
return j+1
elif c == b'(':
self._curtoken = b''
self.paren = 1
self._parse1 = self._parse_string
return j+1
elif c == b'<':
self._curtoken = b''
self._parse1 = self._parse_wopen
return j+1
elif c == b'>':
self._curtoken = b''
self._parse1 = self._parse_wclose
return j+1
else:
self._add_token(KWD(c))
return j+1
def _add_token(self, obj):
self._tokens.append((self._curtokenpos, obj))
return
def _parse_comment(self, s, i):
m = EOL.search(s, i)
if not m:
self._curtoken += s[i:]
return (self._parse_comment, len(s))
j = m.start(0)
self._curtoken += s[i:j]
self._parse1 = self._parse_main
# We ignore comments.
#self._tokens.append(self._curtoken)
return j
def _parse_literal(self, s, i):
m = END_LITERAL.search(s, i)
if not m:
self._curtoken += s[i:]
return len(s)
j = m.start(0)
self._curtoken += s[i:j]
c = s[j:j+1]
if c == b'#':
self.hex = b''
self._parse1 = self._parse_literal_hex
return j+1
try:
# Try to interpret the token as a utf-8 string
utoken = self._curtoken.decode('utf-8')
except UnicodeDecodeError:
# We failed, there is possibly a corrupt PDF here.
if STRICT: raise
utoken = ""
self._add_token(LIT(utoken))
self._parse1 = self._parse_main
return j
def _parse_literal_hex(self, s, i):
c = s[i:i+1]
if HEX.match(c) and len(self.hex) < 2:
self.hex += c
return i+1
if self.hex:
try:
self._curtoken += bytes([int(self.hex, 16)])
except ValueError:
pass
self._parse1 = self._parse_literal
return i
def _parse_number(self, s, i):
m = END_NUMBER.search(s, i)
if not m:
self._curtoken += s[i:]
return len(s)
j = m.start(0)
self._curtoken += s[i:j]
c = s[j:j+1]
if c == b'.':
self._curtoken += c
self._parse1 = self._parse_float
return j+1
try:
self._add_token(int(self._curtoken))
except ValueError:
pass
self._parse1 = self._parse_main
return j
def _parse_float(self, s, i):
m = END_NUMBER.search(s, i)
if not m:
self._curtoken += s[i:]
return len(s)
j = m.start(0)
self._curtoken += s[i:j]
try:
self._add_token(float(self._curtoken))
except ValueError:
pass
self._parse1 = self._parse_main
return j
def _parse_keyword(self, s, i):
m = END_KEYWORD.search(s, i)
if not m:
self._curtoken += s[i:]
return len(s)
j = m.start(0)
self._curtoken += s[i:j]
if self._curtoken == b'true':
token = True
elif self._curtoken == b'false':
token = False
else:
token = KWD(self._curtoken)
self._add_token(token)
self._parse1 = self._parse_main
return j
def _parse_string(self, s, i):
m = END_STRING.search(s, i)
if not m:
self._curtoken += s[i:]
return len(s)
j = m.start(0)
self._curtoken += s[i:j]
c = s[j:j+1]
if c == b'\\':
self.oct = b''
self._parse1 = self._parse_string_1
return j+1
if c == b'(':
self.paren += 1
self._curtoken += c
return j+1
if c == b')':
self.paren -= 1
if self.paren: # WTF, they said balanced parens need no special treatment.
self._curtoken += c
return j+1
self._add_token(self._curtoken)
self._parse1 = self._parse_main
return j+1
def _parse_string_1(self, s, i):
c = s[i:i+1]
if OCT_STRING.match(c) and len(self.oct) < 3:
self.oct += c
return i+1
if self.oct:
try:
self._curtoken += bytes([int(self.oct, 8)])
except ValueError:
pass
self._parse1 = self._parse_string
return i
if c in ESC_STRING:
self._curtoken += ESC_STRING[c]
self._parse1 = self._parse_string
return i+1
def _parse_wopen(self, s, i):
c = s[i:i+1]
if c == b'<':
self._add_token(KEYWORD_DICT_BEGIN)
self._parse1 = self._parse_main
i += 1
else:
self._parse1 = self._parse_hexstring
return i
def _parse_wclose(self, s, i):
c = s[i:i+1]
if c == b'>':
self._add_token(KEYWORD_DICT_END)
i += 1
self._parse1 = self._parse_main
return i
def _parse_hexstring(self, s, i):
m = END_HEX_STRING.search(s, i)
if not m:
self._curtoken += s[i:]
return len(s)
j = m.start(0)
self._curtoken += s[i:j]
try:
token = HEX_PAIR.sub(lambda m: bytes([int(m.group(0), 16)]),
SPC.sub(b'', self._curtoken))
self._add_token(token)
except ValueError:
pass
self._parse1 = self._parse_main
return j
def nexttoken(self):
while not self._tokens:
self.fillbuf()
self.charpos = self._parse1(self.buf, self.charpos)
token = self._tokens.pop(0)
if self.debug:
logging.debug('nexttoken: %r' % (token,))
return token
## PSStackParser
##
class PSStackParser(PSBaseParser): class PSStackParser(PSBaseParser):
def __init__(self, fp): def __init__(self, fp):
PSBaseParser.__init__(self, fp) PSBaseParser.__init__(self, fp)
self.reset() self.reset()
return
def reset(self): def reset(self):
self.context = [] self.context = []
self.curtype = None self.curtype = None
self.curstack = [] self.curstack = []
self.results = [] self.results = []
return
def setpos(self, newpos): def seek(self, pos):
PSBaseParser.setpos(self, newpos) PSBaseParser.seek(self, pos)
self.reset() self.reset()
return
def push(self, *objs): def push(self, *objs):
self.curstack.extend(objs) self.curstack.extend(objs)
return
def pop(self, n): def pop(self, n):
objs = self.curstack[-n:] objs = self.curstack[-n:]
self.curstack[-n:] = [] self.curstack[-n:] = []
return objs return objs
def popall(self): def popall(self):
objs = self.curstack objs = self.curstack
self.curstack = [] self.curstack = []
return objs return objs
def add_results(self, *objs): def add_results(self, *objs):
# logging.debug('add_results: %r', objs) if self.debug:
logging.debug('add_results: %r' % (objs,))
self.results.extend(objs) self.results.extend(objs)
return
def start_type(self, pos, type): def start_type(self, pos, type):
self.context.append((pos, self.curtype, self.curstack)) self.context.append((pos, self.curtype, self.curstack))
(self.curtype, self.curstack) = (type, []) (self.curtype, self.curstack) = (type, [])
# logging.debug('start_type: pos=%r, type=%r', pos, type) if self.debug:
logging.debug('start_type: pos=%r, type=%r' % (pos, type))
return
def end_type(self, type): def end_type(self, type):
if self.curtype != type: if self.curtype != type:
raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type)) raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type))
objs = [ obj for (_,obj) in self.curstack ] objs = [obj for (_, obj) in self.curstack]
(pos, self.curtype, self.curstack) = self.context.pop() (pos, self.curtype, self.curstack) = self.context.pop()
# logging.debug('end_type: pos=%r, type=%r, objs=%r', pos, type, objs) if self.debug:
logging.debug('end_type: pos=%r, type=%r, objs=%r' % (pos, type, objs))
return (pos, objs) return (pos, objs)
def do_keyword(self, pos, token): def do_keyword(self, pos, token):
pass return
def nextobject(self): def nextobject(self):
"""Yields a list of objects. """Yields a list of objects.
@ -239,8 +575,8 @@ class PSStackParser(PSBaseParser):
""" """
while not self.results: while not self.results:
(pos, token) = self.nexttoken() (pos, token) = self.nexttoken()
#print (pos,token), (self.curtype, self.curstack) #print((pos,token), (self.curtype, self.curstack))
if isinstance(token, (int, float, bool, str, bytes, PSLiteral)): if isinstance(token, (int, float, bool, bytes, PSLiteral)):
# normal token # normal token
self.push((pos, token)) self.push((pos, token))
elif token == KEYWORD_ARRAY_BEGIN: elif token == KEYWORD_ARRAY_BEGIN:
@ -250,8 +586,9 @@ class PSStackParser(PSBaseParser):
# end array # end array
try: try:
self.push(self.end_type('a')) self.push(self.end_type('a'))
except PSTypeError as e: except PSTypeError:
handle_error(type(e), str(e)) if STRICT:
raise
elif token == KEYWORD_DICT_BEGIN: elif token == KEYWORD_DICT_BEGIN:
# begin dictionary # begin dictionary
self.start_type(pos, 'd') self.start_type(pos, 'd')
@ -260,12 +597,13 @@ class PSStackParser(PSBaseParser):
try: try:
(pos, objs) = self.end_type('d') (pos, objs) = self.end_type('d')
if len(objs) % 2 != 0: if len(objs) % 2 != 0:
handle_error(PSSyntaxError, 'Invalid dictionary construct: %r' % objs) raise PSSyntaxError('Invalid dictionary construct: %r' % (objs,))
# construct a Python dictionary. # construct a Python dictionary.
d = dict( (literal_name(k), v) for (k,v) in choplist(2, objs) if v is not None ) d = dict((literal_name(k), v) for (k, v) in choplist(2, objs) if v is not None)
self.push((pos, d)) self.push((pos, d))
except PSTypeError as e: except PSTypeError:
handle_error(type(e), str(e)) if STRICT:
raise
elif token == KEYWORD_PROC_BEGIN: elif token == KEYWORD_PROC_BEGIN:
# begin proc # begin proc
self.start_type(pos, 'p') self.start_type(pos, 'p')
@ -273,15 +611,118 @@ class PSStackParser(PSBaseParser):
# end proc # end proc
try: try:
self.push(self.end_type('p')) self.push(self.end_type('p'))
except PSTypeError as e: except PSTypeError:
handle_error(type(e), str(e)) if STRICT:
raise
else: else:
logging.debug('do_keyword: pos=%r, token=%r, stack=%r', pos, token, self.curstack) if self.debug:
logging.debug('do_keyword: pos=%r, token=%r, stack=%r' % \
(pos, token, self.curstack))
self.do_keyword(pos, token) self.do_keyword(pos, token)
if self.context: if self.context:
continue continue
else: else:
self.flush() self.flush()
obj = self.results.pop(0) obj = self.results.pop(0)
logging.debug('nextobject: %r', obj) if self.debug:
logging.debug('nextobject: %r' % (obj,))
return obj return obj
import unittest
## Simplistic Test cases
##
class TestPSBaseParser(unittest.TestCase):
TESTDATA = br'''%!PS
begin end
" @ #
/a/BCD /Some_Name /foo#5f#xbaa
0 +1 -2 .5 1.234
(abc) () (abc ( def ) ghi)
(def\040\0\0404ghi) (bach\\slask) (foo\nbaa)
(this % is not a comment.)
(foo
baa)
(foo\
baa)
<> <20> < 40 4020 >
<abcd00
12345>
func/a/b{(c)do*}def
[ 1 (z) ! ]
<< /foo (bar) >>
'''
TOKENS = [
(5, KWD(b'begin')), (11, KWD(b'end')), (16, KWD(b'"')), (19, KWD(b'@')),
(21, KWD(b'#')), (23, LIT('a')), (25, LIT('BCD')), (30, LIT('Some_Name')),
(41, LIT('foo_xbaa')), (54, 0), (56, 1), (59, -2), (62, 0.5),
(65, 1.234), (71, b'abc'), (77, b''), (80, b'abc ( def ) ghi'),
(98, b'def \x00 4ghi'), (118, b'bach\\slask'), (132, b'foo\nbaa'),
(143, b'this % is not a comment.'), (170, b'foo\nbaa'), (180, b'foobaa'),
(191, b''), (194, b' '), (199, b'@@ '), (211, b'\xab\xcd\x00\x124\x05'),
(226, KWD(b'func')), (230, LIT('a')), (232, LIT('b')),
(234, KWD(b'{')), (235, b'c'), (238, KWD(b'do*')), (241, KWD(b'}')),
(242, KWD(b'def')), (246, KWD(b'[')), (248, 1), (250, b'z'), (254, KWD(b'!')),
(256, KWD(b']')), (258, KWD(b'<<')), (261, LIT('foo')), (266, b'bar'),
(272, KWD(b'>>'))
]
OBJS = [
(23, LIT('a')), (25, LIT('BCD')), (30, LIT('Some_Name')),
(41, LIT('foo_xbaa')), (54, 0), (56, 1), (59, -2), (62, 0.5),
(65, 1.234), (71, b'abc'), (77, b''), (80, b'abc ( def ) ghi'),
(98, b'def \x00 4ghi'), (118, b'bach\\slask'), (132, b'foo\nbaa'),
(143, b'this % is not a comment.'), (170, b'foo\nbaa'), (180, b'foobaa'),
(191, b''), (194, b' '), (199, b'@@ '), (211, b'\xab\xcd\x00\x124\x05'),
(230, LIT('a')), (232, LIT('b')), (234, [b'c']), (246, [1, b'z']),
(258, {'foo': b'bar'}),
]
def get_tokens(self, s):
from io import BytesIO
class MyParser(PSBaseParser):
def flush(self):
self.add_results(*self.popall())
parser = MyParser(BytesIO(s))
r = []
try:
while 1:
r.append(parser.nexttoken())
except PSEOF:
pass
return r
def get_objects(self, s):
from io import BytesIO
class MyParser(PSStackParser):
def flush(self):
self.add_results(*self.popall())
parser = MyParser(BytesIO(s))
r = []
try:
while 1:
r.append(parser.nextobject())
except PSEOF:
pass
return r
def test_1(self):
tokens = self.get_tokens(self.TESTDATA)
print(tokens)
self.assertEqual(tokens, self.TOKENS)
return
def test_2(self):
objs = self.get_objects(self.TESTDATA)
print(objs)
self.assertEqual(objs, self.OBJS)
return
if __name__ == '__main__':
unittest.main()

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# #
# RunLength decoder (Adobe version) implementation based on PDF Reference # RunLength decoder (Adobe version) implementation based on PDF Reference
# version 1.4 section 3.3.4. # version 1.4 section 3.3.4.
@ -6,10 +6,8 @@
# * public domain * # * public domain *
# #
import sys
def rldecode(data): def rldecode(data):
""" r"""
RunLength decoder (Adobe version) implementation based on PDF Reference RunLength decoder (Adobe version) implementation based on PDF Reference
version 1.4 section 3.3.4: version 1.4 section 3.3.4:
The RunLengthDecode filter decodes data that has been encoded in a The RunLengthDecode filter decodes data that has been encoded in a
@ -21,22 +19,30 @@ def rldecode(data):
129 to 255, the following single byte is to be copied 257 - length 129 to 255, the following single byte is to be copied 257 - length
(2 to 128) times during decompression. A length value of 128 (2 to 128) times during decompression. A length value of 128
denotes EOD. denotes EOD.
>>> s = b'\x05123456\xfa7\x04abcde\x80junk'
>>> rldecode(s)
b'1234567777777abcde'
""" """
decoded = [] decoded = b''
i=0 i = 0
while i < len(data): while i < len(data):
#print "data[%d]=:%d:" % (i,ord(data[i])) #print('data[%d]=:%d:' % (i,ord(data[i])))
length = ord(data[i]) length = data[i]
if length == 128: if length == 128:
break break
if length >= 0 and length < 128: if length >= 0 and length < 128:
run = data[i+1:(i+1)+(length+1)] run = data[i+1:(i+1)+(length+1)]
#print "length=%d, run=%s" % (length+1,run) #print('length=%d, run=%s' % (length+1,run))
decoded.append(run) decoded += run
i = (i+1) + (length+1) i = (i+1) + (length+1)
if length > 128: if length > 128:
run = data[i+1]*(257-length) run = data[i+1:i+2]*(257-length)
#print "length=%d, run=%s" % (257-length,run) #print('length=%d, run=%s' % (257-length,run))
decoded.append(run) decoded += run
i = (i+1) + 1 i = (i+1) + 1
return ''.join(decoded) return decoded
if __name__ == '__main__':
import doctest
print('pdfminer.runlength', doctest.testmod())

View File

@ -1,3 +1,7 @@
#!/usr/bin/env python
"""
Miscellaneous Routines.
"""
import struct import struct
from sys import maxsize as INF from sys import maxsize as INF
@ -7,46 +11,41 @@ from sys import maxsize as INF
def apply_png_predictor(pred, colors, columns, bitspercomponent, data): def apply_png_predictor(pred, colors, columns, bitspercomponent, data):
if bitspercomponent != 8: if bitspercomponent != 8:
# unsupported # unsupported
raise ValueError(bitspercomponent) raise ValueError("Unsupported `bitspercomponent': %d"%bitspercomponent)
nbytes = colors*columns*bitspercomponent//8 nbytes = colors*columns*bitspercomponent//8
i = 0 i = 0
buf = b'' buf = b''
line0 = b'\x00' * columns line0 = b'\x00' * columns
while i < len(data): for i in range(0, len(data), nbytes+1):
pred = data[i] ft = data[i:i+1]
i += 1 i += 1
line1 = data[i:i+nbytes] line1 = data[i:i+nbytes]
i += nbytes line2 = b''
if pred == 0: if ft == b'\x00':
# PNG none # PNG none
buf += line1 line2 += line1
elif pred == 1: elif ft == b'\x01':
# PNG sub (UNTESTED) # PNG sub (UNTESTED)
c = 0 c = 0
bufline = []
for b in line1: for b in line1:
c = (c+b) & 255 c = (c+b) & 255
bufline.append(c) line2 += bytes([c])
buf += bytes(bufline) elif ft == b'\x02':
elif pred == 2:
# PNG up # PNG up
bufline = [] for (a, b) in zip(line0, line1):
for (a,b) in zip(line0,line1):
c = (a+b) & 255 c = (a+b) & 255
bufline.append(c) line2 += bytes([c])
buf += bytes(bufline) elif ft == b'\x03':
elif pred == 3:
# PNG average (UNTESTED) # PNG average (UNTESTED)
c = 0 c = 0
bufline = [] for (a, b) in zip(line0, line1):
for (a,b) in zip(line0,line1):
c = ((c+a+b)//2) & 255 c = ((c+a+b)//2) & 255
bufline.append(c) line2 += bytes([c])
buf += bytes(bufline)
else: else:
# unsupported # unsupported
raise ValueError(pred) raise ValueError("Unsupported predictor value: %d"%ft)
line0 = line1 buf += line2
line0 = line2
return buf return buf
@ -54,44 +53,62 @@ def apply_png_predictor(pred, colors, columns, bitspercomponent, data):
## ##
MATRIX_IDENTITY = (1, 0, 0, 1, 0, 0) MATRIX_IDENTITY = (1, 0, 0, 1, 0, 0)
def mult_matrix(matrix1, matrix2):
def mult_matrix(m1, m0):
(a1, b1, c1, d1, e1, f1) = m1
(a0, b0, c0, d0, e0, f0) = m0
"""Returns the multiplication of two matrices.""" """Returns the multiplication of two matrices."""
(a1,b1,c1,d1,e1,f1) = matrix1
(a0,b0,c0,d0,e0,f0) = matrix2
return (a0*a1+c0*b1, b0*a1+d0*b1, return (a0*a1+c0*b1, b0*a1+d0*b1,
a0*c1+c0*d1, b0*c1+d0*d1, a0*c1+c0*d1, b0*c1+d0*d1,
a0*e1+c0*f1+e0, b0*e1+d0*f1+f0) a0*e1+c0*f1+e0, b0*e1+d0*f1+f0)
def translate_matrix(matrix, point):
"""Translates a matrix by (x,y)."""
(a,b,c,d,e,f) = matrix
(x,y) = point
return (a,b,c,d,x*a+y*c+e,x*b+y*d+f)
def apply_matrix_pt(matrix, point): def translate_matrix(m, v):
"""Translates a matrix by (x, y)."""
(a, b, c, d, e, f) = m
(x, y) = v
return (a, b, c, d, x*a+y*c+e, x*b+y*d+f)
def apply_matrix_pt(m, v):
(a, b, c, d, e, f) = m
(x, y) = v
"""Applies a matrix to a point.""" """Applies a matrix to a point."""
(a,b,c,d,e,f) = matrix
(x,y) = point
return (a*x+c*y+e, b*x+d*y+f) return (a*x+c*y+e, b*x+d*y+f)
def apply_matrix_norm(matrix, norm):
def apply_matrix_norm(m, v):
"""Equivalent to apply_matrix_pt(M, (p,q)) - apply_matrix_pt(M, (0,0))""" """Equivalent to apply_matrix_pt(M, (p,q)) - apply_matrix_pt(M, (0,0))"""
(a,b,c,d,e,f) = matrix (a, b, c, d, e, f) = m
(p,q) = norm (p, q) = v
return (a*p+c*q, b*p+d*q) return (a*p+c*q, b*p+d*q)
## Utility functions ## Utility functions
## ##
# isnumber
def isnumber(x):
return isinstance(x, (int, float))
# uniq # uniq
def uniq(objs): def uniq(objs):
"""Eliminates duplicated elements.""" """Eliminates duplicated elements."""
done = set() done = set()
for obj in objs: for obj in objs:
if obj in done: continue if obj in done:
continue
done.add(obj) done.add(obj)
yield obj yield obj
return
# csort
def csort(objs, key):
"""Order-preserving sorting function."""
idxs = { obj:i for (i, obj) in enumerate(objs) }
return sorted(objs, key=lambda obj: (key(obj), idxs[obj]))
# fsplit # fsplit
def fsplit(pred, objs): def fsplit(pred, objs):
@ -103,7 +120,8 @@ def fsplit(pred, objs):
t.append(obj) t.append(obj)
else: else:
f.append(obj) f.append(obj)
return (t,f) return (t, f)
# drange # drange
def drange(v0, v1, d): def drange(v0, v1, d):
@ -111,16 +129,18 @@ def drange(v0, v1, d):
assert v0 < v1 assert v0 < v1
return range(int(v0)//d, int(v1+d)//d) return range(int(v0)//d, int(v1+d)//d)
# get_bound # get_bound
def get_bound(pts): def get_bound(pts):
"""Compute a minimal rectangle that covers all the points.""" """Compute a minimal rectangle that covers all the points."""
(x0, y0, x1, y1) = (INF, INF, -INF, -INF) (x0, y0, x1, y1) = (INF, INF, -INF, -INF)
for (x,y) in pts: for (x, y) in pts:
x0 = min(x0, x) x0 = min(x0, x)
y0 = min(y0, y) y0 = min(y0, y)
x1 = max(x1, x) x1 = max(x1, x)
y1 = max(y1, y) y1 = max(y1, y)
return (x0,y0,x1,y1) return (x0, y0, x1, y1)
# pick # pick
def pick(seq, func, maxobj=None): def pick(seq, func, maxobj=None):
@ -129,9 +149,10 @@ def pick(seq, func, maxobj=None):
for obj in seq: for obj in seq:
score = func(obj) score = func(obj)
if maxscore is None or maxscore < score: if maxscore is None or maxscore < score:
(maxscore,maxobj) = (score,obj) (maxscore, maxobj) = (score, obj)
return maxobj return maxobj
# choplist # choplist
def choplist(n, seq): def choplist(n, seq):
"""Groups every n elements of the list.""" """Groups every n elements of the list."""
@ -141,129 +162,172 @@ def choplist(n, seq):
if len(r) == n: if len(r) == n:
yield tuple(r) yield tuple(r)
r = [] r = []
return
def trailiter(iterable, skipfirst=False):
"""Yields (prev_element, element), starting with (None, first_element).
If skipfirst is True, there will be no (None, item1) element and we'll start
directly with (item1, item2).
"""
it = iter(iterable)
if skipfirst:
prev = next(it)
else:
prev = None
for item in it:
yield prev, item
prev = item
# nunpack # nunpack
def nunpack(b, default=0): def nunpack(s, default=0):
"""Unpacks 1 to 4 byte integers (big endian).""" """Unpacks 1 to 4 byte integers (big endian)."""
if isinstance(b, str): l = len(s)
b = b.encode('latin-1')
l = len(b)
if not l: if not l:
return default return default
elif l == 1: elif l == 1:
return b[0] return s[0]
elif l == 2: elif l == 2:
return struct.unpack(b'>H', b)[0] return struct.unpack('>H', s)[0]
elif l == 3: elif l == 3:
return struct.unpack(b'>L', b'\x00'+b)[0] return struct.unpack('>L', b'\x00'+s)[0]
elif l == 4: elif l == 4:
return struct.unpack(b'>L', b)[0] return struct.unpack('>L', s)[0]
else: else:
raise TypeError('invalid length: %d' % l) raise TypeError('invalid length: %d' % l)
# decode_text
PDFDocEncoding = ''.join( chr(x) for x in (
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0017, 0x0017,
0x02d8, 0x02c7, 0x02c6, 0x02d9, 0x02dd, 0x02db, 0x02da, 0x02dc,
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000,
0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044,
0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018,
0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x0141, 0x0152, 0x0160,
0x0178, 0x017d, 0x0131, 0x0142, 0x0153, 0x0161, 0x017e, 0x0000,
0x20ac, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x0000, 0x00ae, 0x00af,
0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff,
))
def decode_text(s):
"""Decodes a PDFDocEncoding string to Unicode."""
if s.startswith('\xfe\xff'):
return str(s[2:], 'utf-16be', 'ignore')
else:
return ''.join( PDFDocEncoding[ord(c)] for c in s )
def htmlescape(s, encoding='ascii'): # decode_text
"""Escapes a string for SGML/XML/HTML""" PDFDocEncoding = ''.join(chr(x) for x in (
s = s.replace('&','&amp;').replace('>','&gt;').replace('<','&lt;').replace('"','&quot;') 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
# Additionally to basic replaces, we also make sure that all characters are convertible to our 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
# target encoding. If they're not, they're replaced by XML entities. 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0017, 0x0017,
encoded = s.encode(encoding, errors='xmlcharrefreplace') 0x02d8, 0x02c7, 0x02c6, 0x02d9, 0x02dd, 0x02db, 0x02da, 0x02dc,
return encoded.decode(encoding) 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000,
0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044,
0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018,
0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x0141, 0x0152, 0x0160,
0x0178, 0x017d, 0x0131, 0x0142, 0x0153, 0x0161, 0x017e, 0x0000,
0x20ac, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x0000, 0x00ae, 0x00af,
0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff,
))
def decode_text(s):
"""Decodes a PDFDocEncoding bytes to Unicode."""
if s.startswith(b'\xfe\xff'):
return s[2:].decode('utf-16be', 'ignore')
else:
return ''.join(PDFDocEncoding[c] for c in s)
def q(s):
"""Quotes html string."""
return (s.replace('&','&amp;')
.replace('<','&lt;')
.replace('>','&gt;')
.replace('"','&quot;'))
def bbox2str(bbox): def bbox2str(bbox):
(x0,y0,x1,y1) = bbox (x0, y0, x1, y1) = bbox
return '%.3f,%.3f,%.3f,%.3f' % (x0, y0, x1, y1) return '%.3f,%.3f,%.3f,%.3f' % (x0, y0, x1, y1)
def matrix2str(matrix):
(a,b,c,d,e,f) = matrix
return '[%.2f,%.2f,%.2f,%.2f, (%.2f,%.2f)]' % (a,b,c,d,e,f)
def set_debug_logging(): def matrix2str(m):
import logging, sys (a, b, c, d, e, f) = m
logging.basicConfig(level=logging.DEBUG, stream=sys.stderr) return '[%.2f,%.2f,%.2f,%.2f, (%.2f,%.2f)]' % (a, b, c, d, e, f)
class ObjIdRange:
"A utility class to represent a range of object IDs." ## Plane
##
def __init__(self, start, nobjs): ## A set-like data structure for objects placed on a plane.
self.start = start ## Can efficiently find objects in a certain rectangular area.
self.nobjs = nobjs ## It maintains two parallel lists of objects, each of
## which is sorted by its x or y coordinate.
##
class Plane:
def __init__(self, bbox, gridsize=50):
self._seq = [] # preserve the object order.
self._objs = set()
self._grid = {}
self.gridsize = gridsize
(self.x0, self.y0, self.x1, self.y1) = bbox
return
def __repr__(self): def __repr__(self):
return '<ObjIdRange: %d-%d>' % (self.get_start_id(), self.get_end_id()) return ('<Plane objs=%r>' % list(self))
def get_start_id(self): def __iter__(self):
return self.start return ( obj for obj in self._seq if obj in self._objs )
def get_end_id(self): def __len__(self):
return self.start + self.nobjs - 1 return len(self._objs)
def get_nobjs(self): def __contains__(self, obj):
return self.nobjs return obj in self._objs
def _getrange(self, bbox):
(x0, y0, x1, y1) = bbox
if (x1 <= self.x0 or self.x1 <= x0 or
y1 <= self.y0 or self.y1 <= y0): return
x0 = max(self.x0, x0)
y0 = max(self.y0, y0)
x1 = min(self.x1, x1)
y1 = min(self.y1, y1)
for y in drange(y0, y1, self.gridsize):
for x in drange(x0, x1, self.gridsize):
yield (x, y)
return
# create_bmp # extend(objs)
def create_bmp(data, bits, width, height): def extend(self, objs):
info = struct.pack('<IiiHHIIIIII', 40, width, height, 1, bits, 0, len(data), 0, 0, 0, 0) for obj in objs:
assert len(info) == 40, len(info) self.add(obj)
header = struct.pack('<ccIHHI', 'B', 'M', 14+40+len(data), 0, 0, 14+40) return
assert len(header) == 14, len(header)
# XXX re-rasterize every line # add(obj): place an object.
return header+info+data def add(self, obj):
for k in self._getrange((obj.x0, obj.y0, obj.x1, obj.y1)):
if k not in self._grid:
r = []
self._grid[k] = r
else:
r = self._grid[k]
r.append(obj)
self._seq.append(obj)
self._objs.add(obj)
return
# remove(obj): displace an object.
def remove(self, obj):
for k in self._getrange((obj.x0, obj.y0, obj.x1, obj.y1)):
try:
self._grid[k].remove(obj)
except (KeyError, ValueError):
pass
self._objs.remove(obj)
return
# find(): finds objects that are in a certain area.
def find(self, bbox):
(x0, y0, x1, y1) = bbox
done = set()
for k in self._getrange(bbox):
if k not in self._grid:
continue
for obj in self._grid[k]:
if obj in done:
continue
done.add(obj)
if (obj.x1 <= x0 or x1 <= obj.x0 or
obj.y1 <= y0 or y1 <= obj.y0):
continue
yield obj
return

View File

@ -1,51 +1,60 @@
function global:deactivate ([switch]$NonDestructive) { $script:THIS_PATH = $myinvocation.mycommand.path
# Revert to original values $script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
copy-item function:_OLD_VIRTUAL_PROMPT function:prompt function global:deactivate([switch] $NonDestructive) {
remove-item function:_OLD_VIRTUAL_PROMPT if (Test-Path variable:_OLD_VIRTUAL_PATH) {
$env:PATH = $variable:_OLD_VIRTUAL_PATH
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
} }
if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) { if (Test-Path function:_old_virtual_prompt) {
copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME $function:prompt = $function:_old_virtual_prompt
remove-item env:_OLD_VIRTUAL_PYTHONHOME Remove-Item function:\_old_virtual_prompt
} }
if (Test-Path env:_OLD_VIRTUAL_PATH) { if ($env:VIRTUAL_ENV) {
copy-item env:_OLD_VIRTUAL_PATH env:PATH Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
remove-item env:_OLD_VIRTUAL_PATH
}
if (Test-Path env:VIRTUAL_ENV) {
remove-item env:VIRTUAL_ENV
} }
if (!$NonDestructive) { if (!$NonDestructive) {
# Self destruct! # Self destruct!
remove-item function:deactivate Remove-Item function:deactivate
Remove-Item function:pydoc
} }
} }
function global:pydoc {
python -m pydoc $args
}
# unset irrelevant variables
deactivate -nondestructive deactivate -nondestructive
$env:VIRTUAL_ENV="C:\Users\ChérifBALDE\Desktop\En cours\myclass_api\venv" $VIRTUAL_ENV = $BASE_DIR
$env:VIRTUAL_ENV = $VIRTUAL_ENV
if (! $env:VIRTUAL_ENV_DISABLE_PROMPT) { New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global $env:PATH = "$env:VIRTUAL_ENV/Scripts;" + $env:PATH
function global:_OLD_VIRTUAL_PROMPT {""} if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
copy-item function:prompt function:_OLD_VIRTUAL_PROMPT function global:_old_virtual_prompt {
function global:prompt { ""
Write-Host -NoNewline -ForegroundColor Green '(venv) ' }
_OLD_VIRTUAL_PROMPT $function:_old_virtual_prompt = $function:prompt
if ("" -ne "") {
function global:prompt {
# Add the custom prefix to the existing prompt
$previous_prompt_value = & $function:_old_virtual_prompt
("() " + $previous_prompt_value)
}
}
else {
function global:prompt {
# Add a prefix to the current prompt, but don't discard it.
$previous_prompt_value = & $function:_old_virtual_prompt
$new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) "
($new_prompt_value + $previous_prompt_value)
}
} }
} }
# Clear PYTHONHOME
if (Test-Path env:PYTHONHOME) {
copy-item env:PYTHONHOME env:_OLD_VIRTUAL_PYTHONHOME
remove-item env:PYTHONHOME
}
# Add the venv to the PATH
copy-item env:PATH env:_OLD_VIRTUAL_PATH
$env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH"

View File

@ -1,34 +1,41 @@
# This file must be used with "source bin/activate" *from bash* # This file must be used with "source bin/activate" *from bash*
# you cannot run it directly # you cannot run it directly
if [ "${BASH_SOURCE-}" = "$0" ]; then
echo "You must source this script: \$ source $0" >&2
exit 33
fi
deactivate () { deactivate () {
unset -f pydoc >/dev/null 2>&1 || true
# reset old environment variables # reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then # ! [ -z ${VAR+_} ] returns true if VAR is declared at all
PATH="${_OLD_VIRTUAL_PATH:-}" if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
PATH="$_OLD_VIRTUAL_PATH"
export PATH export PATH
unset _OLD_VIRTUAL_PATH unset _OLD_VIRTUAL_PATH
fi fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
export PYTHONHOME export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME unset _OLD_VIRTUAL_PYTHONHOME
fi fi
# This should detect bash and zsh, which have a hash command that must # The hash command must be called to get it to forget past
# be called to get it to forget past commands. Without forgetting # commands. Without forgetting past commands the $PATH changes
# past commands the $PATH changes we made may not be respected # we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then hash -r 2>/dev/null
hash -r
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}" PS1="$_OLD_VIRTUAL_PS1"
export PS1 export PS1
unset _OLD_VIRTUAL_PS1 unset _OLD_VIRTUAL_PS1
fi fi
unset VIRTUAL_ENV unset VIRTUAL_ENV
if [ ! "$1" = "nondestructive" ] ; then if [ ! "${1-}" = "nondestructive" ] ; then
# Self destruct! # Self destruct!
unset -f deactivate unset -f deactivate
fi fi
@ -37,7 +44,10 @@ deactivate () {
# unset irrelevant variables # unset irrelevant variables
deactivate nondestructive deactivate nondestructive
VIRTUAL_ENV="C:\Users\ChérifBALDE\Desktop\En cours\myclass_api\venv" VIRTUAL_ENV='C:\Users\cheri\Documents\myclass.com\Siteweb\Production\Ela_back\MySy_Back_Office\venv'
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
fi
export VIRTUAL_ENV export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH" _OLD_VIRTUAL_PATH="$PATH"
@ -45,32 +55,29 @@ PATH="$VIRTUAL_ENV/Scripts:$PATH"
export PATH export PATH
# unset PYTHONHOME if set # unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) if ! [ -z "${PYTHONHOME+_}" ] ; then
# could use `if (set -u; : $PYTHONHOME) ;` in bash _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME unset PYTHONHOME
fi fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}" _OLD_VIRTUAL_PS1="${PS1-}"
if [ "x(venv) " != x ] ; then if [ "x" != x ] ; then
PS1="(venv) ${PS1:-}" PS1="() ${PS1-}"
else else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
fi
fi fi
export PS1 export PS1
fi fi
# This should detect bash and zsh, which have a hash command that must # Make sure to unalias pydoc if it's already there
# be called to get it to forget past commands. Without forgetting alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then pydoc () {
hash -r python -m pydoc "$@"
fi }
# The hash command must be called to get it to forget past
# commands. Without forgetting past commands the $PATH changes
# we made may not be respected
hash -r 2>/dev/null

View File

@ -1,45 +1,39 @@
@echo off @echo off
rem This file is UTF-8 encoded, so we need to update the current code page while executing it set "VIRTUAL_ENV=C:\Users\cheri\Documents\myclass.com\Siteweb\Production\Ela_back\MySy_Back_Office\venv"
for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do (
set "_OLD_CODEPAGE=%%a"
)
if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" 65001 > nul
)
set "VIRTUAL_ENV=C:\Users\ChérifBALDE\Desktop\En cours\myclass_api\venv"
if not defined PROMPT (
set "PROMPT=$P$G"
)
if defined _OLD_VIRTUAL_PROMPT ( if defined _OLD_VIRTUAL_PROMPT (
set "PROMPT=%_OLD_VIRTUAL_PROMPT%" set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
)
if defined _OLD_VIRTUAL_PYTHONHOME (
set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
)
set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
set "PROMPT=(venv) %PROMPT%"
if defined PYTHONHOME (
set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
set PYTHONHOME=
)
if defined _OLD_VIRTUAL_PATH (
set "PATH=%_OLD_VIRTUAL_PATH%"
) else ( ) else (
set "_OLD_VIRTUAL_PATH=%PATH%" if not defined PROMPT (
set "PROMPT=$P$G"
)
if not defined VIRTUAL_ENV_DISABLE_PROMPT (
set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
)
) )
if not defined VIRTUAL_ENV_DISABLE_PROMPT (
if "" NEQ "" (
set "PROMPT=() %PROMPT%"
) else (
for %%d in ("%VIRTUAL_ENV%") do set "PROMPT=(%%~nxd) %PROMPT%"
)
)
REM Don't use () to avoid problems with them in %PATH%
if defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME
set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
:ENDIFVHOME
set PYTHONHOME=
REM if defined _OLD_VIRTUAL_PATH (
if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH1
set "PATH=%_OLD_VIRTUAL_PATH%"
:ENDIFVPATH1
REM ) else (
if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2
set "_OLD_VIRTUAL_PATH=%PATH%"
:ENDIFVPATH2
set "PATH=%VIRTUAL_ENV%\Scripts;%PATH%" set "PATH=%VIRTUAL_ENV%\Scripts;%PATH%"
:END
if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
set "_OLD_CODEPAGE="
)

View File

@ -1,21 +1,19 @@
@echo off @echo off
if defined _OLD_VIRTUAL_PROMPT (
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
)
set _OLD_VIRTUAL_PROMPT=
if defined _OLD_VIRTUAL_PYTHONHOME (
set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
set _OLD_VIRTUAL_PYTHONHOME=
)
if defined _OLD_VIRTUAL_PATH (
set "PATH=%_OLD_VIRTUAL_PATH%"
)
set _OLD_VIRTUAL_PATH=
set VIRTUAL_ENV= set VIRTUAL_ENV=
:END REM Don't use () to avoid problems with them in %PATH%
if not defined _OLD_VIRTUAL_PROMPT goto ENDIFVPROMPT
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
set _OLD_VIRTUAL_PROMPT=
:ENDIFVPROMPT
if not defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME
set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
set _OLD_VIRTUAL_PYTHONHOME=
:ENDIFVHOME
if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH
set "PATH=%_OLD_VIRTUAL_PATH%"
set _OLD_VIRTUAL_PATH=
:ENDIFVPATH

View File

@ -1,12 +0,0 @@
#!"C:\Users\ChérifBALDE\Desktop\En cours\myclass_api\venv\Scripts\python.exe" -x
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip'
__requires__ = 'pip==19.0.3'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pip==19.0.3', 'console_scripts', 'pip')()
)

Binary file not shown.

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0"
processorArchitecture="X86"
name="pip"
type="win32"/>
<!-- Identify the application security requirements. -->
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@ -1,12 +0,0 @@
#!"C:\Users\ChérifBALDE\Desktop\En cours\myclass_api\venv\Scripts\python.exe" -x
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==19.0.3','console_scripts','pip3'
__requires__ = 'pip==19.0.3'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('pip==19.0.3', 'console_scripts', 'pip3')()
)

Binary file not shown.

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0"
processorArchitecture="X86"
name="pip3"
type="win32"/>
<!-- Identify the application security requirements. -->
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +1,8 @@
home = C:\Users\ChérifBALDE\AppData\Local\Programs\Python\Python37-32 home = C:\Users\cheri\AppData\Local\Programs\Python\Python311
implementation = CPython
version_info = 3.11.0.final.0
virtualenv = 20.13.0
include-system-site-packages = false include-system-site-packages = false
version = 3.7.4 base-prefix = C:\Users\cheri\AppData\Local\Programs\Python\Python311
base-exec-prefix = C:\Users\cheri\AppData\Local\Programs\Python\Python311
base-executable = C:\Users\cheri\AppData\Local\Programs\Python\Python311\python.exe