sss - init new pc
parent
69c75e9325
commit
7107d326c5
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</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" />
|
||||
</component>
|
||||
</module>
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
__version__ = '1.3.1'
|
||||
#!/usr/bin/env python
|
||||
__version__ = '20191125'
|
||||
|
||||
if __name__ == '__main__': print(__version__)
|
||||
if __name__ == '__main__':
|
||||
print(__version__)
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
""" Python implementation of Arcfour encryption algorithm.
|
||||
|
||||
This code is in the public domain.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
## 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):
|
||||
s = list(range(256))
|
||||
j = 0
|
||||
|
@ -14,6 +29,7 @@ class Arcfour:
|
|||
(s[i], s[j]) = (s[j], s[i])
|
||||
self.s = s
|
||||
(self.i, self.j) = (0, 0)
|
||||
return
|
||||
|
||||
def process(self, data):
|
||||
(i, j) = (self.i, self.j)
|
||||
|
@ -27,3 +43,12 @@ class Arcfour:
|
|||
r.append(c ^ k)
|
||||
(self.i, self.j) = (i, j)
|
||||
return bytes(r)
|
||||
|
||||
encrypt = decrypt = process
|
||||
|
||||
new = Arcfour
|
||||
|
||||
# test
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
print('pdfminer.arcfour:', doctest.testmod())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
""" Python implementation of ASCII85/ASCIIHex decoder (Adobe version).
|
||||
|
||||
|
@ -9,43 +9,51 @@ This code is in the public domain.
|
|||
import re
|
||||
import struct
|
||||
|
||||
|
||||
# ascii85decode(data)
|
||||
def ascii85decode(data):
|
||||
"""
|
||||
In ASCII85 encoding, every four bytes are encoded with five ASCII
|
||||
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
|
||||
rule is used for round up.
|
||||
|
||||
|
||||
The Adobe's ASCII85 implementation is slightly different from
|
||||
its original in handling the last characters.
|
||||
|
||||
|
||||
The sample string is taken from:
|
||||
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
|
||||
out = bytearray()
|
||||
out = b''
|
||||
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
|
||||
b = b*85+(c-33)
|
||||
if n == 5:
|
||||
out += struct.pack(b'>L',b)
|
||||
out += struct.pack('>L', b)
|
||||
n = b = 0
|
||||
elif c == ord('z'):
|
||||
elif c == 122: # b'z'
|
||||
assert n == 0
|
||||
out += b'\0\0\0\0'
|
||||
elif c == ord('~'):
|
||||
elif c == 126: # b'~'
|
||||
if n:
|
||||
for _ in range(5-n):
|
||||
b = b*85+84
|
||||
out += struct.pack(b'>L',b)[:n-1]
|
||||
out += struct.pack('>L', b)[:n-1]
|
||||
break
|
||||
return bytes(out)
|
||||
return out
|
||||
|
||||
# asciihexdecode(data)
|
||||
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)
|
||||
|
||||
|
||||
def asciihexdecode(data):
|
||||
"""
|
||||
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
|
||||
the EOD marker after reading an odd number of hexadecimal digits, it
|
||||
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)))
|
||||
out = list(map(decode, hex_re.findall(data)))
|
||||
data = data.decode('latin1')
|
||||
out = [ int(hx,16) for hx in hex_re.findall(data) ]
|
||||
m = trail_re.search(data)
|
||||
if m:
|
||||
out.append(decode("%c0" % m.group(1)))
|
||||
return ''.join(out)
|
||||
out.append(int(m.group(1),16) << 4)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
print('pdfminer.ascii85', doctest.testmod())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
""" Adobe character mapping (CMap) support.
|
||||
|
||||
|
@ -15,37 +15,69 @@ import sys
|
|||
import os
|
||||
import os.path
|
||||
import gzip
|
||||
import pickle as pickle
|
||||
import codecs
|
||||
import marshal
|
||||
import struct
|
||||
import logging
|
||||
|
||||
from . import cmap
|
||||
from .psparser import PSStackParser
|
||||
from .psparser import PSSyntaxError, PSEOF
|
||||
from .psparser import PSSyntaxError
|
||||
from .psparser import PSEOF
|
||||
from .psparser import PSLiteral
|
||||
from .psparser import literal_name
|
||||
from .psparser import KWD
|
||||
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, code2cid=None):
|
||||
self.code2cid = code2cid or {}
|
||||
def __init__(self, **kwargs):
|
||||
self.attrs = kwargs.copy()
|
||||
return
|
||||
|
||||
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):
|
||||
assert isinstance(cmap, CMap)
|
||||
|
||||
def copy(dst, src):
|
||||
for (k,v) in src.items():
|
||||
for (k, v) in src.items():
|
||||
if isinstance(v, dict):
|
||||
d = {}
|
||||
dst[k] = d
|
||||
|
@ -53,11 +85,11 @@ class CMap:
|
|||
else:
|
||||
dst[k] = v
|
||||
copy(self.code2cid, cmap.code2cid)
|
||||
return
|
||||
|
||||
def decode(self, code):
|
||||
logger.debug('decode: %r, %r', self, code)
|
||||
if isinstance(code, str):
|
||||
code = code.encode('latin-1')
|
||||
if self.debug:
|
||||
logging.debug('decode: %r, %r' % (self, code))
|
||||
d = self.code2cid
|
||||
for c in code:
|
||||
if c in d:
|
||||
|
@ -67,73 +99,62 @@ class CMap:
|
|||
d = self.code2cid
|
||||
else:
|
||||
d = self.code2cid
|
||||
return
|
||||
|
||||
def dump(self, out=sys.stdout, code2cid=None, code=None):
|
||||
if code2cid is None:
|
||||
code2cid = self.code2cid
|
||||
code = ()
|
||||
for (k,v) in sorted(code2cid.items()):
|
||||
for (k, v) in sorted(code2cid.items()):
|
||||
c = code+(k,)
|
||||
if isinstance(v, int):
|
||||
out.write('code %r = cid %d\n' % (c,v))
|
||||
out.write('code %r = cid %d\n' % (c, v))
|
||||
else:
|
||||
self.dump(out=out, code2cid=v, code=c)
|
||||
|
||||
return
|
||||
|
||||
class IdentityCMap:
|
||||
|
||||
def __init__(self, vertical):
|
||||
self.vertical = vertical
|
||||
|
||||
def is_vertical(self):
|
||||
return self.vertical
|
||||
## IdentityCMap
|
||||
##
|
||||
class IdentityCMap(CMapBase):
|
||||
|
||||
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
|
||||
if n:
|
||||
return struct.unpack('>%dH' % n, code)
|
||||
else:
|
||||
return ()
|
||||
|
||||
|
||||
|
||||
class UnicodeMap:
|
||||
|
||||
def __init__(self, cid2unichr=None):
|
||||
self.cid2unichr = cid2unichr or {}
|
||||
|
||||
## UnicodeMap
|
||||
##
|
||||
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):
|
||||
logger.debug('get_unichr: %r, %r', self, cid)
|
||||
if self.debug:
|
||||
logging.debug('get_unichr: %r, %r' % (self, cid))
|
||||
return self.cid2unichr[cid]
|
||||
|
||||
def dump(self, out=sys.stdout):
|
||||
for (k,v) in sorted(self.cid2unichr.items()):
|
||||
out.write('cid %d = unicode %r\n' % (k,v))
|
||||
for (k, v) in sorted(self.cid2unichr.items()):
|
||||
out.write('cid %d = unicode %r\n' % (k, v))
|
||||
return
|
||||
|
||||
|
||||
## FileCMap
|
||||
##
|
||||
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):
|
||||
assert isinstance(code, str) and isinstance(cid, int)
|
||||
assert isinstance(code, bytes) and isinstance(cid, int)
|
||||
d = self.code2cid
|
||||
for c in code[:-1]:
|
||||
c = ord(c)
|
||||
|
@ -142,28 +163,18 @@ class FileCMap(CMap):
|
|||
else:
|
||||
t = {}
|
||||
d[c] = t
|
||||
d =t
|
||||
d = t
|
||||
c = ord(code[-1])
|
||||
d[c] = cid
|
||||
return
|
||||
|
||||
|
||||
## FileUnicodeMap
|
||||
##
|
||||
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):
|
||||
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):
|
||||
# Interpret as an Adobe glyph name.
|
||||
self.cid2unichr[cid] = name2unicode(code.name)
|
||||
|
@ -173,55 +184,58 @@ class FileUnicodeMap(UnicodeMap):
|
|||
elif isinstance(code, int):
|
||||
self.cid2unichr[cid] = chr(code)
|
||||
else:
|
||||
raise TypeError(repr(code))
|
||||
raise TypeError(code)
|
||||
return
|
||||
|
||||
|
||||
## PyCMap
|
||||
##
|
||||
class PyCMap(CMap):
|
||||
|
||||
def __init__(self, name, module):
|
||||
CMap.__init__(self, module.CODE2CID)
|
||||
self.name = name
|
||||
self._is_vertical = module.IS_VERTICAL
|
||||
CMap.__init__(self, CMapName=name)
|
||||
self.code2cid = module.CODE2CID
|
||||
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):
|
||||
|
||||
|
||||
def __init__(self, name, module, vertical):
|
||||
UnicodeMap.__init__(self, CMapName=name)
|
||||
if vertical:
|
||||
cid2unichr = module.CID2UNICHR_V
|
||||
self.cid2unichr = module.CID2UNICHR_V
|
||||
self.attrs['WMode'] = 1
|
||||
else:
|
||||
cid2unichr = module.CID2UNICHR_H
|
||||
UnicodeMap.__init__(self, cid2unichr)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<PyUnicodeMap: %s>' % (self.name)
|
||||
self.cid2unichr = module.CID2UNICHR_H
|
||||
return
|
||||
|
||||
|
||||
## CMapDB
|
||||
##
|
||||
class CMapDB:
|
||||
|
||||
_cmap_cache = {}
|
||||
_umap_cache = {}
|
||||
|
||||
class CMapNotFound(CMapError): pass
|
||||
|
||||
class CMapNotFound(CMapError):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _load_data(klass, name):
|
||||
filename = '%s.pickle.gz' % name
|
||||
logger.debug('loading: %s', name)
|
||||
default_path = os.environ.get('CMAP_PATH', '/usr/share/pdfminer/')
|
||||
for directory in (os.path.dirname(cmap.__file__), default_path):
|
||||
filename = '%s.marshal.gz' % name
|
||||
logging.info('loading: %r' % name)
|
||||
cmap_paths = (os.environ.get('CMAP_PATH', '/usr/share/pdfminer/'),
|
||||
os.path.join(os.path.dirname(__file__), 'cmap'),)
|
||||
for directory in cmap_paths:
|
||||
path = os.path.join(directory, filename)
|
||||
if os.path.exists(path):
|
||||
gzfile = gzip.open(path)
|
||||
try:
|
||||
return type(name, (), pickle.loads(gzfile.read()))
|
||||
return type(str(name), (), marshal.loads(gzfile.read()))
|
||||
finally:
|
||||
gzfile.close()
|
||||
else:
|
||||
|
@ -230,9 +244,9 @@ class CMapDB:
|
|||
@classmethod
|
||||
def get_cmap(klass, name):
|
||||
if name == 'Identity-H':
|
||||
return IdentityCMap(False)
|
||||
return IdentityCMap(WMode=0)
|
||||
elif name == 'Identity-V':
|
||||
return IdentityCMap(True)
|
||||
return IdentityCMap(WMode=1)
|
||||
try:
|
||||
return klass._cmap_cache[name]
|
||||
except KeyError:
|
||||
|
@ -252,42 +266,63 @@ class CMapDB:
|
|||
return umaps[vertical]
|
||||
|
||||
|
||||
## CMapParser
|
||||
##
|
||||
class CMapParser(PSStackParser):
|
||||
|
||||
def __init__(self, cmap, fp):
|
||||
PSStackParser.__init__(self, fp)
|
||||
self.cmap = cmap
|
||||
self._in_cmap = False
|
||||
# some ToUnicode maps don't have "begincmap" keyword.
|
||||
self._in_cmap = True
|
||||
return
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.nextobject()
|
||||
except PSEOF:
|
||||
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):
|
||||
name = token.name
|
||||
if name == 'begincmap':
|
||||
if token is self.KEYWORD_BEGINCMAP:
|
||||
self._in_cmap = True
|
||||
self.popall()
|
||||
return
|
||||
elif name == 'endcmap':
|
||||
elif token is self.KEYWORD_ENDCMAP:
|
||||
self._in_cmap = False
|
||||
return
|
||||
if not self._in_cmap:
|
||||
return
|
||||
|
||||
if name == 'def':
|
||||
#
|
||||
if token is self.KEYWORD_DEF:
|
||||
try:
|
||||
((_,k),(_,v)) = self.pop(2)
|
||||
((_, k), (_, v)) = self.pop(2)
|
||||
self.cmap.set_attr(literal_name(k), v)
|
||||
except PSSyntaxError:
|
||||
pass
|
||||
return
|
||||
|
||||
if name == 'usecmap':
|
||||
if token is self.KEYWORD_USECMAP:
|
||||
try:
|
||||
((_,cmapname),) = self.pop(1)
|
||||
((_, cmapname),) = self.pop(1)
|
||||
self.cmap.use_cmap(CMapDB.get_cmap(literal_name(cmapname)))
|
||||
except PSSyntaxError:
|
||||
pass
|
||||
|
@ -295,24 +330,26 @@ class CMapParser(PSStackParser):
|
|||
pass
|
||||
return
|
||||
|
||||
if name == 'begincodespacerange':
|
||||
if token is self.KEYWORD_BEGINCODESPACERANGE:
|
||||
self.popall()
|
||||
return
|
||||
if name == 'endcodespacerange':
|
||||
if token is self.KEYWORD_ENDCODESPACERANGE:
|
||||
self.popall()
|
||||
return
|
||||
|
||||
if name == 'begincidrange':
|
||||
if token is self.KEYWORD_BEGINCIDRANGE:
|
||||
self.popall()
|
||||
return
|
||||
if name == 'endcidrange':
|
||||
objs = [ obj for (_,obj) in self.popall() ]
|
||||
for (s,e,cid) in choplist(3, objs):
|
||||
if (not isinstance(s, str) or not isinstance(e, str) or
|
||||
not isinstance(cid, int) or len(s) != len(e)): continue
|
||||
if token is self.KEYWORD_ENDCIDRANGE:
|
||||
objs = [obj for (__, obj) in self.popall()]
|
||||
for (s, e, cid) in choplist(3, objs):
|
||||
if (not isinstance(s, bytes) or not isinstance(e, bytes) or
|
||||
not isinstance(cid, int) or len(s) != len(e)):
|
||||
continue
|
||||
sprefix = s[:-4]
|
||||
eprefix = e[:-4]
|
||||
if sprefix != eprefix: continue
|
||||
if sprefix != eprefix:
|
||||
continue
|
||||
svar = s[-4:]
|
||||
evar = e[-4:]
|
||||
s1 = nunpack(svar)
|
||||
|
@ -320,33 +357,29 @@ class CMapParser(PSStackParser):
|
|||
vlen = len(svar)
|
||||
#assert s1 <= e1
|
||||
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)
|
||||
return
|
||||
|
||||
if name == 'begincidchar':
|
||||
if token is self.KEYWORD_BEGINCIDCHAR:
|
||||
self.popall()
|
||||
return
|
||||
if name == 'endcidchar':
|
||||
objs = [ obj for (_,obj) in self.popall() ]
|
||||
for (cid,code) in choplist(2, objs):
|
||||
if isinstance(code, str) and isinstance(cid, str):
|
||||
if token is self.KEYWORD_ENDCIDCHAR:
|
||||
objs = [obj for (__, obj) in self.popall()]
|
||||
for (cid, code) in choplist(2, objs):
|
||||
if isinstance(code, bytes) and isinstance(cid, bytes):
|
||||
self.cmap.add_code2cid(code, nunpack(cid))
|
||||
return
|
||||
|
||||
if name == 'beginbfrange':
|
||||
if token is self.KEYWORD_BEGINBFRANGE:
|
||||
self.popall()
|
||||
return
|
||||
if name == 'endbfrange':
|
||||
objs = [ obj for (_,obj) in self.popall() ]
|
||||
# These objects were hex numbers and have been parsed into a string. But what we want
|
||||
# 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 token is self.KEYWORD_ENDBFRANGE:
|
||||
objs = [obj for (__, obj) in self.popall()]
|
||||
for (s, e, code) in choplist(3, objs):
|
||||
if (not isinstance(s, bytes) or not isinstance(e, bytes) or
|
||||
len(s) != len(e)): continue
|
||||
len(s) != len(e)):
|
||||
continue
|
||||
s1 = nunpack(s)
|
||||
e1 = nunpack(e)
|
||||
#assert s1 <= e1
|
||||
|
@ -359,39 +392,211 @@ class CMapParser(PSStackParser):
|
|||
prefix = code[:-4]
|
||||
vlen = len(var)
|
||||
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)
|
||||
return
|
||||
|
||||
if name == 'beginbfchar':
|
||||
if token is self.KEYWORD_BEGINBFCHAR:
|
||||
self.popall()
|
||||
return
|
||||
if name == 'endbfchar':
|
||||
objs = [ obj for (_,obj) in self.popall() ]
|
||||
for (cid,code) in choplist(2, objs):
|
||||
if isinstance(cid, (str, bytes)) and isinstance(code, (str, bytes)):
|
||||
if token is self.KEYWORD_ENDBFCHAR:
|
||||
objs = [obj for (__, obj) in self.popall()]
|
||||
for (cid, code) in choplist(2, objs):
|
||||
if isinstance(cid, bytes) and isinstance(code, bytes):
|
||||
self.cmap.add_cid2unichr(nunpack(cid), code)
|
||||
return
|
||||
|
||||
if name == 'beginnotdefrange':
|
||||
if token is self.KEYWORD_BEGINNOTDEFRANGE:
|
||||
self.popall()
|
||||
return
|
||||
if name == 'endnotdefrange':
|
||||
if token is self.KEYWORD_ENDNOTDEFRANGE:
|
||||
self.popall()
|
||||
return
|
||||
|
||||
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
|
||||
def main(argv):
|
||||
args = argv[1:]
|
||||
for fname in args:
|
||||
fp = open(fname, 'rb')
|
||||
cmap = FileUnicodeMap()
|
||||
#cmap = FileCMap()
|
||||
CMapParser(cmap, fp).run()
|
||||
fp.close()
|
||||
cmap.dump()
|
||||
with open(fname, 'rb') as fp:
|
||||
cmap = FileUnicodeMap()
|
||||
#cmap = FileCMap()
|
||||
CMapParser(cmap, fp).run()
|
||||
cmap.dump()
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
import os.path
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
|
||||
import re
|
||||
from .pdfdevice import PDFTextDevice
|
||||
from .pdffont import PDFUnicodeNotDefined
|
||||
from .pdftypes import LITERALS_DCT_DECODE
|
||||
from .pdfcolor import LITERAL_DEVICE_GRAY, LITERAL_DEVICE_RGB
|
||||
from .layout import LTContainer, LTPage, LTText, LTLine, LTRect, LTCurve
|
||||
from .layout import LTFigure, LTImage, LTChar, LTTextLine
|
||||
from .layout import LTTextBox, LTTextBoxVertical, LTTextGroup
|
||||
from .utils import apply_matrix_pt, mult_matrix
|
||||
from .utils import htmlescape, bbox2str, create_bmp
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from .layout import LTContainer
|
||||
from .layout import LTPage
|
||||
from .layout import LTText
|
||||
from .layout import LTLine
|
||||
from .layout import LTRect
|
||||
from .layout import LTCurve
|
||||
from .layout import LTFigure
|
||||
from .layout import LTImage
|
||||
from .layout import LTChar
|
||||
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):
|
||||
|
||||
def __init__(self, rsrcmgr, pageno=1, laparams=None):
|
||||
|
@ -22,13 +31,15 @@ class PDFLayoutAnalyzer(PDFTextDevice):
|
|||
self.pageno = pageno
|
||||
self.laparams = laparams
|
||||
self._stack = []
|
||||
return
|
||||
|
||||
def begin_page(self, page, ctm):
|
||||
(x0,y0,x1,y1) = page.mediabox
|
||||
(x0,y0) = apply_matrix_pt(ctm, (x0,y0))
|
||||
(x1,y1) = apply_matrix_pt(ctm, (x1,y1))
|
||||
(x0, y0, x1, y1) = page.mediabox
|
||||
(x0, y0) = apply_matrix_pt(ctm, (x0, y0))
|
||||
(x1, y1) = apply_matrix_pt(ctm, (x1, y1))
|
||||
mediabox = (0, 0, abs(x0-x1), abs(y0-y1))
|
||||
self.cur_item = LTPage(self.pageno, mediabox)
|
||||
return
|
||||
|
||||
def end_page(self, page):
|
||||
assert not self._stack
|
||||
|
@ -37,16 +48,19 @@ class PDFLayoutAnalyzer(PDFTextDevice):
|
|||
self.cur_item.analyze(self.laparams)
|
||||
self.pageno += 1
|
||||
self.receive_layout(self.cur_item)
|
||||
return
|
||||
|
||||
def begin_figure(self, name, bbox, matrix):
|
||||
self._stack.append(self.cur_item)
|
||||
self.cur_item = LTFigure(name, bbox, mult_matrix(matrix, self.ctm))
|
||||
return
|
||||
|
||||
def end_figure(self, _):
|
||||
fig = self.cur_item
|
||||
assert isinstance(self.cur_item, LTFigure)
|
||||
self.cur_item = self._stack.pop()
|
||||
self.cur_item.add(fig)
|
||||
return
|
||||
|
||||
def render_image(self, name, stream):
|
||||
assert isinstance(self.cur_item, LTFigure)
|
||||
|
@ -54,31 +68,32 @@ class PDFLayoutAnalyzer(PDFTextDevice):
|
|||
(self.cur_item.x0, self.cur_item.y0,
|
||||
self.cur_item.x1, self.cur_item.y1))
|
||||
self.cur_item.add(item)
|
||||
return
|
||||
|
||||
def paint_path(self, gstate, stroke, fill, evenodd, path):
|
||||
shape = ''.join(x[0] for x in path)
|
||||
if shape == 'ml':
|
||||
# horizontal/vertical line
|
||||
(_,x0,y0) = path[0]
|
||||
(_,x1,y1) = path[1]
|
||||
(x0,y0) = apply_matrix_pt(self.ctm, (x0,y0))
|
||||
(x1,y1) = apply_matrix_pt(self.ctm, (x1,y1))
|
||||
(_, x0, y0) = path[0]
|
||||
(_, x1, y1) = path[1]
|
||||
(x0, y0) = apply_matrix_pt(self.ctm, (x0, y0))
|
||||
(x1, y1) = apply_matrix_pt(self.ctm, (x1, 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
|
||||
if shape == 'mlllh':
|
||||
# rectangle
|
||||
(_,x0,y0) = path[0]
|
||||
(_,x1,y1) = path[1]
|
||||
(_,x2,y2) = path[2]
|
||||
(_,x3,y3) = path[3]
|
||||
(x0,y0) = apply_matrix_pt(self.ctm, (x0,y0))
|
||||
(x1,y1) = apply_matrix_pt(self.ctm, (x1,y1))
|
||||
(x2,y2) = apply_matrix_pt(self.ctm, (x2,y2))
|
||||
(x3,y3) = apply_matrix_pt(self.ctm, (x3,y3))
|
||||
(_, x0, y0) = path[0]
|
||||
(_, x1, y1) = path[1]
|
||||
(_, x2, y2) = path[2]
|
||||
(_, x3, y3) = path[3]
|
||||
(x0, y0) = apply_matrix_pt(self.ctm, (x0, y0))
|
||||
(x1, y1) = apply_matrix_pt(self.ctm, (x1, y1))
|
||||
(x2, y2) = apply_matrix_pt(self.ctm, (x2, y2))
|
||||
(x3, y3) = apply_matrix_pt(self.ctm, (x3, y3))
|
||||
if ((x0 == x1 and y1 == y2 and x2 == x3 and y3 == y0) or
|
||||
(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
|
||||
# other shapes
|
||||
pts = []
|
||||
|
@ -86,6 +101,7 @@ class PDFLayoutAnalyzer(PDFTextDevice):
|
|||
for i in range(1, len(p), 2):
|
||||
pts.append(apply_matrix_pt(self.ctm, (p[i], p[i+1])))
|
||||
self.cur_item.add(LTCurve(gstate.linewidth, pts))
|
||||
return
|
||||
|
||||
def render_char(self, matrix, font, fontsize, scaling, rise, cid):
|
||||
try:
|
||||
|
@ -100,21 +116,25 @@ class PDFLayoutAnalyzer(PDFTextDevice):
|
|||
return item.adv
|
||||
|
||||
def handle_undefined_char(self, font, cid):
|
||||
logger.warning('undefined: %r, %r', font, cid)
|
||||
return '(cid:%d)' % cid
|
||||
logging.info('undefined: %r, %r' % (font, cid))
|
||||
return f'(cid:{cid})'
|
||||
|
||||
def receive_layout(self, ltpage):
|
||||
pass
|
||||
return
|
||||
|
||||
|
||||
## PDFPageAggregator
|
||||
##
|
||||
class PDFPageAggregator(PDFLayoutAnalyzer):
|
||||
|
||||
def __init__(self, rsrcmgr, pageno=1, laparams=None):
|
||||
PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
|
||||
self.result = None
|
||||
|
||||
return
|
||||
|
||||
def receive_layout(self, ltpage):
|
||||
self.result = ltpage
|
||||
return
|
||||
|
||||
def get_result(self):
|
||||
return self.result
|
||||
|
@ -123,45 +143,27 @@ class PDFPageAggregator(PDFLayoutAnalyzer):
|
|||
## PDFConverter
|
||||
##
|
||||
class PDFConverter(PDFLayoutAnalyzer):
|
||||
# outfp is an fp opened in *text* mode
|
||||
|
||||
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None):
|
||||
PDFLayoutAnalyzer.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
|
||||
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
|
||||
##
|
||||
class TextConverter(PDFConverter):
|
||||
|
||||
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None,
|
||||
showpageno=False):
|
||||
showpageno=False, imagewriter=None):
|
||||
PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams)
|
||||
self.showpageno = showpageno
|
||||
self.imagewriter = imagewriter
|
||||
return
|
||||
|
||||
def write_text(self, text):
|
||||
self.outfp.write(text)
|
||||
return
|
||||
|
||||
def receive_layout(self, ltpage):
|
||||
def render(item):
|
||||
|
@ -172,18 +174,26 @@ class TextConverter(PDFConverter):
|
|||
self.write_text(item.get_text())
|
||||
if isinstance(item, LTTextBox):
|
||||
self.write_text('\n')
|
||||
elif isinstance(item, LTImage):
|
||||
if self.imagewriter is not None:
|
||||
self.imagewriter.export_image(item)
|
||||
if self.showpageno:
|
||||
self.write_text('Page %s\n' % ltpage.pageid)
|
||||
render(ltpage)
|
||||
self.write_text('\f')
|
||||
return
|
||||
|
||||
# Some dummy functions to save memory/CPU when all that is wanted is text.
|
||||
# This stops all the image and drawing ouput from being recorded and taking
|
||||
# up RAM.
|
||||
# Some dummy functions to save memory/CPU when all that is wanted
|
||||
# is text. This stops all the image and drawing output from being
|
||||
# recorded and taking up RAM.
|
||||
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):
|
||||
pass
|
||||
return
|
||||
|
||||
|
||||
## HTMLConverter
|
||||
|
@ -198,26 +208,25 @@ class HTMLConverter(PDFConverter):
|
|||
'textgroup': 'red',
|
||||
'curve': 'black',
|
||||
'page': 'gray',
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEXT_COLORS = {
|
||||
'textbox': 'blue',
|
||||
'char': 'black',
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None,
|
||||
scale=1, fontscale=0.7, layoutmode='normal', showpageno=True,
|
||||
pagemargin=50, outdir=None,
|
||||
rect_colors={'curve':'black', 'page':'gray'},
|
||||
text_colors={'char':'black'},
|
||||
debug=False):
|
||||
def __init__(self, rsrcmgr, outfp, pageno=1, laparams=None,
|
||||
scale=1, fontscale=1.0, layoutmode='normal', showpageno=True,
|
||||
pagemargin=50, imagewriter=None, debug=0,
|
||||
rect_colors={'curve': 'black', 'page': 'gray'},
|
||||
text_colors={'char': 'black'}):
|
||||
PDFConverter.__init__(self, rsrcmgr, outfp, pageno=pageno, laparams=laparams)
|
||||
self.scale = scale
|
||||
self.fontscale = fontscale
|
||||
self.layoutmode = layoutmode
|
||||
self.showpageno = showpageno
|
||||
self.pagemargin = pagemargin
|
||||
self.outdir = outdir
|
||||
self.imagewriter = imagewriter
|
||||
self.rect_colors = rect_colors
|
||||
self.text_colors = text_colors
|
||||
if debug:
|
||||
|
@ -227,22 +236,27 @@ class HTMLConverter(PDFConverter):
|
|||
self._font = None
|
||||
self._fontstack = []
|
||||
self.write_header()
|
||||
return
|
||||
|
||||
def write(self, text):
|
||||
self.outfp.write(text)
|
||||
return
|
||||
|
||||
def write_header(self):
|
||||
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')
|
||||
return
|
||||
|
||||
def write_footer(self):
|
||||
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')
|
||||
return
|
||||
|
||||
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):
|
||||
color = self.rect_colors.get(color)
|
||||
|
@ -252,18 +266,21 @@ class HTMLConverter(PDFConverter):
|
|||
(color, borderwidth,
|
||||
x*self.scale, (self._yoffset-y)*self.scale,
|
||||
w*self.scale, h*self.scale))
|
||||
return
|
||||
|
||||
def place_border(self, color, borderwidth, item):
|
||||
self.place_rect(color, borderwidth, item.x0, item.y1, item.width, item.height)
|
||||
return
|
||||
|
||||
def place_image(self, item, borderwidth, x, y, w, h):
|
||||
if self.outdir is not None:
|
||||
name = self.write_image(item)
|
||||
if self.imagewriter is not None:
|
||||
name = self.imagewriter.export_image(item)
|
||||
self.write('<img src="%s" border="%d" style="position:absolute; left:%dpx; top:%dpx;" '
|
||||
'width="%d" height="%d" />\n' %
|
||||
(enc(name), borderwidth,
|
||||
(q(name), borderwidth,
|
||||
x*self.scale, (self._yoffset-y)*self.scale,
|
||||
w*self.scale, h*self.scale))
|
||||
return
|
||||
|
||||
def place_text(self, color, text, x, y, size):
|
||||
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))
|
||||
self.write_text(text)
|
||||
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._font = None
|
||||
self.write('<div style="position:absolute; border: %s %dpx solid; writing-mode:%s; '
|
||||
|
@ -281,25 +299,29 @@ class HTMLConverter(PDFConverter):
|
|||
(color, borderwidth, writing_mode,
|
||||
x*self.scale, (self._yoffset-y)*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):
|
||||
font = (fontname, fontsize)
|
||||
if font != self._font:
|
||||
if self._font is not None:
|
||||
self.write('</span>')
|
||||
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.write_text(text)
|
||||
return
|
||||
|
||||
def put_newline(self):
|
||||
self.write('<br>')
|
||||
|
||||
def end_textbox(self, color):
|
||||
if self._font is not None:
|
||||
self.write('</span>')
|
||||
self._font = self._fontstack.pop()
|
||||
self.write('</div>')
|
||||
return
|
||||
|
||||
def receive_layout(self, ltpage):
|
||||
def show_group(item):
|
||||
|
@ -307,7 +329,8 @@ class HTMLConverter(PDFConverter):
|
|||
self.place_border('textgroup', 1, item)
|
||||
for child in item:
|
||||
show_group(child)
|
||||
|
||||
return
|
||||
|
||||
def render(item):
|
||||
if isinstance(item, LTPage):
|
||||
self._yoffset += item.y1
|
||||
|
@ -324,9 +347,10 @@ class HTMLConverter(PDFConverter):
|
|||
elif isinstance(item, LTCurve):
|
||||
self.place_border('curve', 1, item)
|
||||
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:
|
||||
render(child)
|
||||
self.end_div('figure')
|
||||
elif isinstance(item, LTImage):
|
||||
self.place_image(item, 1, item.x0, item.y1, item.width, item.height)
|
||||
else:
|
||||
|
@ -350,39 +374,53 @@ class HTMLConverter(PDFConverter):
|
|||
if self.layoutmode != 'loose':
|
||||
self.put_newline()
|
||||
elif isinstance(item, LTTextBox):
|
||||
self.begin_textbox('textbox', 1, item.x0, item.y1, item.width, item.height,
|
||||
item.get_writing_mode())
|
||||
self.begin_div('textbox', 1, item.x0, item.y1, item.width, item.height,
|
||||
item.get_writing_mode())
|
||||
for child in item:
|
||||
render(child)
|
||||
self.end_textbox('textbox')
|
||||
self.end_div('textbox')
|
||||
elif isinstance(item, LTChar):
|
||||
self.put_text(item.get_text(), item.fontname, item.size)
|
||||
elif isinstance(item, LTText):
|
||||
self.write_text(item.get_text())
|
||||
|
||||
return
|
||||
render(ltpage)
|
||||
self._yoffset += self.pagemargin
|
||||
return
|
||||
|
||||
def close(self):
|
||||
self.write_footer()
|
||||
return
|
||||
|
||||
|
||||
## XMLConverter
|
||||
##
|
||||
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)
|
||||
self.outdir = outdir
|
||||
self.imagewriter = imagewriter
|
||||
self.stripcontrol = stripcontrol
|
||||
self.write_header()
|
||||
return
|
||||
|
||||
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')
|
||||
return
|
||||
|
||||
def write_footer(self):
|
||||
self.outfp.write('</pages>\n')
|
||||
|
||||
return
|
||||
|
||||
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 show_group(item):
|
||||
|
@ -394,7 +432,8 @@ class XMLConverter(PDFConverter):
|
|||
for child in item:
|
||||
show_group(child)
|
||||
self.outfp.write('</textgroup>\n')
|
||||
|
||||
return
|
||||
|
||||
def render(item):
|
||||
if isinstance(item, LTPage):
|
||||
self.outfp.write('<page id="%s" bbox="%s" rotate="%d">\n' %
|
||||
|
@ -438,23 +477,25 @@ class XMLConverter(PDFConverter):
|
|||
self.outfp.write('</textbox>\n')
|
||||
elif isinstance(item, LTChar):
|
||||
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.outfp.write('</text>\n')
|
||||
elif isinstance(item, LTText):
|
||||
self.outfp.write('<text>%s</text>\n' % item.get_text())
|
||||
elif isinstance(item, LTImage):
|
||||
if self.outdir:
|
||||
name = self.write_image(item)
|
||||
if self.imagewriter is not None:
|
||||
name = self.imagewriter.export_image(item)
|
||||
self.outfp.write('<image src="%s" width="%d" height="%d" />\n' %
|
||||
(enc(name), item.width, item.height))
|
||||
(q(name), item.width, item.height))
|
||||
else:
|
||||
self.outfp.write('<image width="%d" height="%d" />\n' %
|
||||
(item.width, item.height))
|
||||
else:
|
||||
assert 0, item
|
||||
|
||||
return
|
||||
render(ltpage)
|
||||
return
|
||||
|
||||
def close(self):
|
||||
self.write_footer()
|
||||
return
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
import re
|
||||
from .psparser import PSLiteral
|
||||
from .glyphlist import glyphname2unicode
|
||||
|
@ -5,38 +6,49 @@ from .latin_enc import ENCODING
|
|||
|
||||
|
||||
STRIP_NAME = re.compile(r'[0-9]+')
|
||||
|
||||
|
||||
## name2unicode
|
||||
##
|
||||
def name2unicode(name):
|
||||
"""Converts Adobe glyph names to Unicode numbers."""
|
||||
if name in glyphname2unicode:
|
||||
return glyphname2unicode[name]
|
||||
m = STRIP_NAME.search(name)
|
||||
if not m: raise KeyError(name)
|
||||
if not m:
|
||||
raise KeyError(name)
|
||||
return chr(int(m.group(0)))
|
||||
|
||||
|
||||
## EncodingDB
|
||||
##
|
||||
class EncodingDB:
|
||||
|
||||
std2unicode = {}
|
||||
mac2unicode = {}
|
||||
win2unicode = {}
|
||||
pdf2unicode = {}
|
||||
for (name,std,mac,win,pdf) in ENCODING:
|
||||
for (name, std, mac, win, pdf) in ENCODING:
|
||||
c = name2unicode(name)
|
||||
if std: std2unicode[std] = c
|
||||
if mac: mac2unicode[mac] = c
|
||||
if win: win2unicode[win] = c
|
||||
if pdf: pdf2unicode[pdf] = c
|
||||
if std:
|
||||
std2unicode[std] = c
|
||||
if mac:
|
||||
mac2unicode[mac] = c
|
||||
if win:
|
||||
win2unicode[win] = c
|
||||
if pdf:
|
||||
pdf2unicode[pdf] = c
|
||||
|
||||
encodings = {
|
||||
'StandardEncoding': std2unicode,
|
||||
'MacRomanEncoding': mac2unicode,
|
||||
'WinAnsiEncoding': win2unicode,
|
||||
'PDFDocEncoding': pdf2unicode,
|
||||
}
|
||||
'StandardEncoding': std2unicode,
|
||||
'MacRomanEncoding': mac2unicode,
|
||||
'WinAnsiEncoding': win2unicode,
|
||||
'PDFDocEncoding': pdf2unicode,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_encoding(cls, name, diff=None):
|
||||
cid2unicode = cls.encodings.get(name, cls.std2unicode)
|
||||
def get_encoding(klass, name, diff=None):
|
||||
cid2unicode = klass.encodings.get(name, klass.std2unicode)
|
||||
if diff:
|
||||
cid2unicode = cid2unicode.copy()
|
||||
cid = 0
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
""" 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:
|
||||
|
||||
http://www.ctan.org/tex-archive/fonts/adobe/afm/
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
### 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
|
||||
# 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
|
||||
|
||||
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}),
|
||||
'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}),
|
||||
'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': ({'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-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}),
|
||||
'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}),
|
||||
'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': ({'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-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}),
|
||||
'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}),
|
||||
'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-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-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}),
|
||||
'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}),
|
||||
'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}),
|
||||
'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}),
|
||||
'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-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}),
|
||||
'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}),
|
||||
'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}),
|
||||
'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-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}),
|
||||
'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}),
|
||||
'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}),
|
||||
'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}, {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}, {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}),
|
||||
'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
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
""" Standard encoding tables used in PDF.
|
||||
|
||||
|
@ -7,7 +7,6 @@ This table is extracted from PDF Reference Manual 1.6, pp.925
|
|||
|
||||
"""
|
||||
|
||||
|
||||
ENCODING = [
|
||||
# (name, std, mac, win, pdf)
|
||||
('A', 65, 65, 65, 65),
|
||||
|
@ -163,6 +162,7 @@ ENCODING = [
|
|||
('mu', None, 181, 181, 181),
|
||||
('multiply', None, None, 215, 215),
|
||||
('n', 110, 110, 110, 110),
|
||||
('nbspace', None, 202, 160, None),
|
||||
('nine', 57, 57, 57, 57),
|
||||
('ntilde', None, 150, 241, 241),
|
||||
('numbersign', 35, 35, 35, 35),
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,13 @@
|
|||
import io
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
#!/usr/bin/env python
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class CorruptDataError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
## LZWDecoder
|
||||
##
|
||||
class LZWDecoder:
|
||||
|
||||
def __init__(self, fp):
|
||||
|
@ -17,6 +17,7 @@ class LZWDecoder:
|
|||
self.nbits = 9
|
||||
self.table = None
|
||||
self.prevbuf = None
|
||||
return
|
||||
|
||||
def readbits(self, bits):
|
||||
v = 0
|
||||
|
@ -27,37 +28,34 @@ class LZWDecoder:
|
|||
# |-----8-bits-----|
|
||||
# |-bpos-|-bits-| |
|
||||
# | |----r----|
|
||||
v = (v<<bits) | ((self.buff>>(r-bits)) & ((1<<bits)-1))
|
||||
v = (v << bits) | ((self.buff >> (r-bits)) & ((1 << bits)-1))
|
||||
self.bpos += bits
|
||||
break
|
||||
else:
|
||||
# |-----8-bits-----|
|
||||
# |-bpos-|---bits----...
|
||||
# | |----r----|
|
||||
v = (v<<r) | (self.buff & ((1<<r)-1))
|
||||
v = (v << r) | (self.buff & ((1 << r)-1))
|
||||
bits -= r
|
||||
x = self.fp.read(1)
|
||||
if not x: raise EOFError
|
||||
self.buff = ord(x)
|
||||
if not x:
|
||||
raise EOFError
|
||||
self.buff = x[0]
|
||||
self.bpos = 0
|
||||
return v
|
||||
|
||||
def feed(self, code):
|
||||
x = b''
|
||||
if code == 256:
|
||||
self.table = [bytes([i]) for i in range(256)] # 0-255
|
||||
self.table.append(None) # 256
|
||||
self.table.append(None) # 257
|
||||
self.table = [bytes([c]) for c in range(256)] # 0-255
|
||||
self.table.append(None) # 256
|
||||
self.table.append(None) # 257
|
||||
self.prevbuf = b''
|
||||
self.nbits = 9
|
||||
elif code == 257:
|
||||
pass
|
||||
elif not self.prevbuf:
|
||||
try:
|
||||
x = self.prevbuf = self.table[code]
|
||||
except (TypeError, IndexError):
|
||||
# TypeError: table is None
|
||||
raise CorruptDataError()
|
||||
x = self.prevbuf = self.table[code]
|
||||
else:
|
||||
if code < len(self.table):
|
||||
x = self.table[code]
|
||||
|
@ -66,7 +64,7 @@ class LZWDecoder:
|
|||
self.table.append(self.prevbuf+self.prevbuf[:1])
|
||||
x = self.table[code]
|
||||
else:
|
||||
raise CorruptDataError()
|
||||
raise CorruptDataError
|
||||
l = len(self.table)
|
||||
if l == 511:
|
||||
self.nbits = 10
|
||||
|
@ -89,9 +87,20 @@ class LZWDecoder:
|
|||
# just ignore corrupt data and stop yielding there
|
||||
break
|
||||
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
|
||||
def lzwdecode(data):
|
||||
fp = io.BytesIO(data)
|
||||
"""
|
||||
>>> lzwdecode(bytes.fromhex('800b6050220c0c8501'))
|
||||
b'-----A---B'
|
||||
"""
|
||||
fp = BytesIO(data)
|
||||
return b''.join(LZWDecoder(fp).run())
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
print('pdfminer.lzw', doctest.testmod())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
from .psparser import LIT
|
||||
|
||||
|
||||
|
@ -7,25 +8,27 @@ LITERAL_DEVICE_GRAY = LIT('DeviceGray')
|
|||
LITERAL_DEVICE_RGB = LIT('DeviceRGB')
|
||||
LITERAL_DEVICE_CMYK = LIT('DeviceCMYK')
|
||||
|
||||
|
||||
class PDFColorSpace:
|
||||
|
||||
def __init__(self, name, ncomponents):
|
||||
self.name = name
|
||||
self.ncomponents = ncomponents
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFColorSpace: %s, ncomponents=%d>' % (self.name, self.ncomponents)
|
||||
|
||||
|
||||
PREDEFINED_COLORSPACE = dict(
|
||||
(name, PDFColorSpace(name,n)) for (name,n) in {
|
||||
'CalRGB': 3,
|
||||
'CalGray': 1,
|
||||
'Lab': 3,
|
||||
'DeviceRGB': 3,
|
||||
'DeviceCMYK': 4,
|
||||
'DeviceGray': 1,
|
||||
'Separation': 1,
|
||||
'Indexed': 1,
|
||||
'Pattern': 1,
|
||||
}.items())
|
||||
(name, PDFColorSpace(name, n)) for (name, n) in {
|
||||
'CalRGB': 3,
|
||||
'CalGray': 1,
|
||||
'Lab': 3,
|
||||
'DeviceRGB': 3,
|
||||
'DeviceCMYK': 4,
|
||||
'DeviceGray': 1,
|
||||
'Separation': 1,
|
||||
'Indexed': 1,
|
||||
'Pattern': 1,
|
||||
}.items())
|
||||
|
|
|
@ -1,48 +1,64 @@
|
|||
import sys
|
||||
from .utils import mult_matrix, translate_matrix
|
||||
from .utils import htmlescape, bbox2str
|
||||
#!/usr/bin/env python
|
||||
from .utils import mult_matrix
|
||||
from .utils import translate_matrix
|
||||
from .utils import q
|
||||
from .utils import bbox2str
|
||||
from .utils import isnumber
|
||||
from .pdffont import PDFUnicodeNotDefined
|
||||
|
||||
|
||||
## PDFDevice
|
||||
##
|
||||
class PDFDevice:
|
||||
|
||||
def __init__(self, rsrcmgr):
|
||||
self.rsrcmgr = rsrcmgr
|
||||
self.ctm = None
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFDevice>'
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
return
|
||||
|
||||
def set_ctm(self, ctm):
|
||||
self.ctm = ctm
|
||||
return
|
||||
|
||||
def begin_tag(self, tag, props=None):
|
||||
pass
|
||||
return
|
||||
|
||||
def end_tag(self):
|
||||
pass
|
||||
return
|
||||
|
||||
def do_tag(self, tag, props=None):
|
||||
pass
|
||||
return
|
||||
|
||||
def begin_page(self, page, ctm):
|
||||
pass
|
||||
return
|
||||
|
||||
def end_page(self, page):
|
||||
pass
|
||||
return
|
||||
|
||||
def begin_figure(self, name, bbox, matrix):
|
||||
pass
|
||||
return
|
||||
|
||||
def end_figure(self, name):
|
||||
pass
|
||||
return
|
||||
|
||||
def paint_path(self, graphicstate, stroke, fill, evenodd, path):
|
||||
pass
|
||||
return
|
||||
|
||||
def render_image(self, name, stream):
|
||||
pass
|
||||
return
|
||||
|
||||
def render_string(self, textstate, seq):
|
||||
pass
|
||||
return
|
||||
|
||||
|
||||
## PDFTextDevice
|
||||
##
|
||||
class PDFTextDevice(PDFDevice):
|
||||
|
||||
def render_string(self, textstate, seq):
|
||||
|
@ -64,39 +80,40 @@ class PDFTextDevice(PDFDevice):
|
|||
textstate.linematrix = self.render_string_horizontal(
|
||||
seq, matrix, textstate.linematrix, font, fontsize,
|
||||
scaling, charspace, wordspace, rise, dxscale)
|
||||
|
||||
def render_string_horizontal(self, seq, matrix, point, font, fontsize, scaling, charspace,
|
||||
wordspace, rise, dxscale):
|
||||
(x,y) = point
|
||||
return
|
||||
|
||||
def render_string_horizontal(self, seq, matrix, pos,
|
||||
font, fontsize, scaling, charspace, wordspace, rise, dxscale):
|
||||
(x, y) = pos
|
||||
needcharspace = False
|
||||
for obj in seq:
|
||||
if isinstance(obj, (int, float)):
|
||||
if isnumber(obj):
|
||||
x -= obj*dxscale
|
||||
needcharspace = True
|
||||
else:
|
||||
for cid in font.decode(obj):
|
||||
if needcharspace:
|
||||
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)
|
||||
if cid == 32 and wordspace:
|
||||
x += wordspace
|
||||
needcharspace = True
|
||||
return (x, y)
|
||||
|
||||
def render_string_vertical(self, seq, matrix, point, font, fontsize, scaling, charspace,
|
||||
wordspace, rise, dxscale):
|
||||
(x,y) = point
|
||||
def render_string_vertical(self, seq, matrix, pos,
|
||||
font, fontsize, scaling, charspace, wordspace, rise, dxscale):
|
||||
(x, y) = pos
|
||||
needcharspace = False
|
||||
for obj in seq:
|
||||
if isinstance(obj, (int, float)):
|
||||
if isnumber(obj):
|
||||
y -= obj*dxscale
|
||||
needcharspace = True
|
||||
else:
|
||||
for cid in font.decode(obj):
|
||||
if needcharspace:
|
||||
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)
|
||||
if cid == 32 and wordspace:
|
||||
y += wordspace
|
||||
|
@ -107,6 +124,8 @@ class PDFTextDevice(PDFDevice):
|
|||
return 0
|
||||
|
||||
|
||||
## TagExtractor
|
||||
##
|
||||
class TagExtractor(PDFDevice):
|
||||
|
||||
def __init__(self, rsrcmgr, outfp):
|
||||
|
@ -114,12 +133,13 @@ class TagExtractor(PDFDevice):
|
|||
self.outfp = outfp
|
||||
self.pageno = 0
|
||||
self._stack = []
|
||||
return
|
||||
|
||||
def render_string(self, textstate, seq):
|
||||
font = textstate.font
|
||||
text = ''
|
||||
for obj in seq:
|
||||
if not isinstance(obj, str):
|
||||
if not isinstance(obj, bytes):
|
||||
continue
|
||||
chars = font.decode(obj)
|
||||
for cid in chars:
|
||||
|
@ -128,29 +148,35 @@ class TagExtractor(PDFDevice):
|
|||
text += char
|
||||
except PDFUnicodeNotDefined:
|
||||
pass
|
||||
self.outfp.write(htmlescape(text, self.outfp.encoding))
|
||||
self.outfp.write(q(text))
|
||||
return
|
||||
|
||||
def begin_page(self, page, ctm):
|
||||
self.outfp.write('<page id="%s" bbox="%s" rotate="%d">' %
|
||||
(self.pageno, bbox2str(page.mediabox), page.rotate))
|
||||
return
|
||||
|
||||
def end_page(self, page):
|
||||
self.outfp.write('</page>\n')
|
||||
self.pageno += 1
|
||||
return
|
||||
|
||||
def begin_tag(self, tag, props=None):
|
||||
s = ''
|
||||
if isinstance(props, dict):
|
||||
s = ''.join( ' %s="%s"' % (htmlescape(k), htmlescape(str(v))) for (k,v)
|
||||
in sorted(props.items()) )
|
||||
self.outfp.write('<%s%s>' % (htmlescape(tag.name), s))
|
||||
s = ''.join(' %s="%s"' % (q(k), q(str(v))) for (k, v)
|
||||
in sorted(props.items()))
|
||||
self.outfp.write('<%s%s>' % (q(tag.name), s))
|
||||
self._stack.append(tag)
|
||||
return
|
||||
|
||||
def end_tag(self):
|
||||
assert self._stack
|
||||
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):
|
||||
self.begin_tag(tag, props)
|
||||
self._stack.pop(-1)
|
||||
return
|
||||
|
|
|
@ -1,18 +1,33 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import io
|
||||
import struct
|
||||
from .cmapdb import CMapDB, CMapParser, FileUnicodeMap, CMap
|
||||
from .encodingdb import EncodingDB, name2unicode
|
||||
from io import BytesIO
|
||||
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 PSEOF
|
||||
from .psparser import LIT, KWD, handle_error
|
||||
from .psparser import PSLiteral, literal_name
|
||||
from .pdftypes import (PDFException, resolve1, int_value, num_value, list_value, dict_value,
|
||||
stream_value)
|
||||
from .psparser import LIT
|
||||
from .psparser import KWD
|
||||
from .psparser import STRICT
|
||||
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 .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):
|
||||
|
@ -22,13 +37,13 @@ def get_widths(seq):
|
|||
if isinstance(v, list):
|
||||
if r:
|
||||
char1 = r[-1]
|
||||
for (i,w) in enumerate(v):
|
||||
for (i, w) in enumerate(v):
|
||||
widths[char1+i] = w
|
||||
r = []
|
||||
elif isinstance(v, int):
|
||||
elif isnumber(v):
|
||||
r.append(v)
|
||||
if len(r) == 3:
|
||||
(char1,char2,w) = r
|
||||
(char1, char2, w) = r
|
||||
for i in range(char1, char2+1):
|
||||
widths[i] = w
|
||||
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],6,[7,8]]) == {1:2,2:3, 6:7,7:8}
|
||||
|
||||
|
||||
def get_widths2(seq):
|
||||
widths = {}
|
||||
r = []
|
||||
|
@ -44,22 +60,24 @@ def get_widths2(seq):
|
|||
if isinstance(v, list):
|
||||
if r:
|
||||
char1 = r[-1]
|
||||
for (i,(w,vx,vy)) in enumerate(choplist(3,v)):
|
||||
widths[char1+i] = (w,(vx,vy))
|
||||
for (i, (w, vx, vy)) in enumerate(choplist(3, v)):
|
||||
widths[char1+i] = (w, (vx, vy))
|
||||
r = []
|
||||
elif isinstance(v, int):
|
||||
elif isnumber(v):
|
||||
r.append(v)
|
||||
if len(r) == 5:
|
||||
(char1,char2,w,vx,vy) = r
|
||||
(char1, char2, w, vx, vy) = r
|
||||
for i in range(char1, char2+1):
|
||||
widths[i] = (w,(vx,vy))
|
||||
widths[i] = (w, (vx, vy))
|
||||
r = []
|
||||
return widths
|
||||
#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],6,[7,8,9]]) == {1:(2,(3,4)), 6:(7,(8,9))}
|
||||
#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))}
|
||||
|
||||
|
||||
## FontMetricsDB
|
||||
##
|
||||
class FontMetricsDB:
|
||||
|
||||
@classmethod
|
||||
|
@ -67,26 +85,29 @@ class FontMetricsDB:
|
|||
return FONT_METRICS[fontname]
|
||||
|
||||
|
||||
## Type1FontHeaderParser
|
||||
##
|
||||
class Type1FontHeaderParser(PSStackParser):
|
||||
|
||||
KEYWORD_BEGIN = KWD('begin')
|
||||
KEYWORD_END = KWD('end')
|
||||
KEYWORD_DEF = KWD('def')
|
||||
KEYWORD_PUT = KWD('put')
|
||||
KEYWORD_DICT = KWD('dict')
|
||||
KEYWORD_ARRAY = KWD('array')
|
||||
KEYWORD_READONLY = KWD('readonly')
|
||||
KEYWORD_FOR = KWD('for')
|
||||
KEYWORD_FOR = KWD('for')
|
||||
KEYWORD_BEGIN = KWD(b'begin')
|
||||
KEYWORD_END = KWD(b'end')
|
||||
KEYWORD_DEF = KWD(b'def')
|
||||
KEYWORD_PUT = KWD(b'put')
|
||||
KEYWORD_DICT = KWD(b'dict')
|
||||
KEYWORD_ARRAY = KWD(b'array')
|
||||
KEYWORD_READONLY = KWD(b'readonly')
|
||||
KEYWORD_FOR = KWD(b'for')
|
||||
KEYWORD_FOR = KWD(b'for')
|
||||
|
||||
def __init__(self, data):
|
||||
PSStackParser.__init__(self, data)
|
||||
self._cid2unicode = {}
|
||||
return
|
||||
|
||||
def get_encoding(self):
|
||||
while 1:
|
||||
try:
|
||||
(cid,name) = self.nextobject()
|
||||
(cid, name) = self.nextobject()
|
||||
except PSEOF:
|
||||
break
|
||||
try:
|
||||
|
@ -94,27 +115,31 @@ class Type1FontHeaderParser(PSStackParser):
|
|||
except KeyError:
|
||||
pass
|
||||
return self._cid2unicode
|
||||
|
||||
|
||||
def do_keyword(self, pos, token):
|
||||
if token is self.KEYWORD_PUT:
|
||||
((_,key),(_,value)) = self.pop(2)
|
||||
((_, key), (_, value)) = self.pop(2)
|
||||
if (isinstance(key, int) and
|
||||
isinstance(value, PSLiteral)):
|
||||
self.add_results((key, literal_name(value)))
|
||||
return
|
||||
|
||||
|
||||
NIBBLES = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'e-', None, '-')
|
||||
|
||||
|
||||
|
||||
## CFFFont
|
||||
## (Format specified in Adobe Technical Note: #5176
|
||||
## "The Compact Font Format Specification")
|
||||
##
|
||||
NIBBLES = ('0','1','2','3','4','5','6','7','8','9','.','e','e-',None,'-')
|
||||
def getdict(data):
|
||||
d = {}
|
||||
fp = io.BytesIO(data)
|
||||
fp = BytesIO(data)
|
||||
stack = []
|
||||
while 1:
|
||||
c = fp.read(1)
|
||||
if not c: break
|
||||
if not c:
|
||||
break
|
||||
b0 = ord(c)
|
||||
if b0 <= 21:
|
||||
d[b0] = stack
|
||||
|
@ -136,19 +161,21 @@ def getdict(data):
|
|||
else:
|
||||
b1 = ord(fp.read(1))
|
||||
if 247 <= b0 and b0 <= 250:
|
||||
value = ((b0-247)<<8)+b1+108
|
||||
value = ((b0-247) << 8)+b1+108
|
||||
elif 251 <= b0 and b0 <= 254:
|
||||
value = -((b0-251)<<8)-b1-108
|
||||
value = -((b0-251) << 8)-b1-108
|
||||
else:
|
||||
b2 = ord(fp.read(1))
|
||||
if 128 <= b1: b1 -= 256
|
||||
if 128 <= b1:
|
||||
b1 -= 256
|
||||
if b0 == 28:
|
||||
value = b1<<8 | b2
|
||||
value = b1 << 8 | b2
|
||||
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)
|
||||
return d
|
||||
|
||||
|
||||
class CFFFont:
|
||||
|
||||
STANDARD_STRINGS = (
|
||||
|
@ -230,18 +257,19 @@ class CFFFont:
|
|||
'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000',
|
||||
'001.001', '001.002', '001.003', 'Black', 'Bold', 'Book',
|
||||
'Light', 'Medium', 'Regular', 'Roman', 'Semibold',
|
||||
)
|
||||
)
|
||||
|
||||
class INDEX:
|
||||
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
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):
|
||||
self.offsets.append(nunpack(self.fp.read(offsize)))
|
||||
self.base = self.fp.tell()-1
|
||||
self.fp.seek(self.base+self.offsets[-1])
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<INDEX: size=%d>' % len(self)
|
||||
|
@ -254,13 +282,13 @@ class CFFFont:
|
|||
return self.fp.read(self.offsets[i+1]-self.offsets[i])
|
||||
|
||||
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):
|
||||
self.name = name
|
||||
self.fp = fp
|
||||
# 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)
|
||||
# Name INDEX
|
||||
self.name_index = self.INDEX(self.fp)
|
||||
|
@ -286,17 +314,17 @@ class CFFFont:
|
|||
format = self.fp.read(1)
|
||||
if format == b'\x00':
|
||||
# Format 0
|
||||
(n,) = struct.unpack(b'B', self.fp.read(1))
|
||||
for (code,gid) in enumerate(struct.unpack(b'B'*n, self.fp.read(n))):
|
||||
(n,) = struct.unpack('B', self.fp.read(1))
|
||||
for (code, gid) in enumerate(struct.unpack('B'*n, self.fp.read(n))):
|
||||
self.code2gid[code] = gid
|
||||
self.gid2code[gid] = code
|
||||
elif format == b'\x01':
|
||||
# Format 1
|
||||
(n,) = struct.unpack(b'B', self.fp.read(1))
|
||||
(n,) = struct.unpack('B', self.fp.read(1))
|
||||
code = 0
|
||||
for i in range(n):
|
||||
(first,nleft) = struct.unpack(b'BB', self.fp.read(2))
|
||||
for gid in range(first,first+nleft+1):
|
||||
(first, nleft) = struct.unpack('BB', self.fp.read(2))
|
||||
for gid in range(first, first+nleft+1):
|
||||
self.code2gid[code] = gid
|
||||
self.gid2code[gid] = code
|
||||
code += 1
|
||||
|
@ -307,33 +335,34 @@ class CFFFont:
|
|||
self.gid2name = {}
|
||||
self.fp.seek(charset_pos)
|
||||
format = self.fp.read(1)
|
||||
if format == '\x00':
|
||||
if format == b'\x00':
|
||||
# Format 0
|
||||
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
|
||||
name = self.getstr(sid)
|
||||
self.name2gid[name] = gid
|
||||
self.gid2name[gid] = name
|
||||
elif format == '\x01':
|
||||
elif format == b'\x01':
|
||||
# Format 1
|
||||
(n,) = struct.unpack(b'B', self.fp.read(1))
|
||||
(n,) = struct.unpack('B', self.fp.read(1))
|
||||
sid = 0
|
||||
for i in range(n):
|
||||
(first,nleft) = struct.unpack(b'BB', self.fp.read(2))
|
||||
for gid in range(first,first+nleft+1):
|
||||
(first, nleft) = struct.unpack('BB', self.fp.read(2))
|
||||
for gid in range(first, first+nleft+1):
|
||||
name = self.getstr(sid)
|
||||
self.name2gid[name] = gid
|
||||
self.gid2name[gid] = name
|
||||
sid += 1
|
||||
elif format == '\x02':
|
||||
elif format == b'\x02':
|
||||
# Format 2
|
||||
assert 0
|
||||
else:
|
||||
raise ValueError('unsupported charset format: %r' % format)
|
||||
#print self.code2gid
|
||||
#print self.name2gid
|
||||
#print(self.code2gid)
|
||||
#print(self.name2gid)
|
||||
#assert 0
|
||||
return
|
||||
|
||||
def getstr(self, sid):
|
||||
if sid < len(self.STANDARD_STRINGS):
|
||||
|
@ -341,19 +370,23 @@ class CFFFont:
|
|||
return self.string_index[sid-len(self.STANDARD_STRINGS)]
|
||||
|
||||
|
||||
## TrueTypeFont
|
||||
##
|
||||
class TrueTypeFont:
|
||||
|
||||
class CMapNotFound(Exception): pass
|
||||
class CMapNotFound(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, name, fp):
|
||||
self.name = name
|
||||
self.fp = fp
|
||||
self.tables = {}
|
||||
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):
|
||||
(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)
|
||||
return
|
||||
|
||||
def create_unicode_map(self):
|
||||
if 'cmap' not in self.tables:
|
||||
|
@ -361,50 +394,51 @@ class TrueTypeFont:
|
|||
(base_offset, length) = self.tables['cmap']
|
||||
fp = self.fp
|
||||
fp.seek(base_offset)
|
||||
(version, nsubtables) = struct.unpack(b'>HH', fp.read(4))
|
||||
(version, nsubtables) = struct.unpack('>HH', fp.read(4))
|
||||
subtables = []
|
||||
for i in range(nsubtables):
|
||||
subtables.append(struct.unpack(b'>HHL', fp.read(8)))
|
||||
subtables.append(struct.unpack('>HHL', fp.read(8)))
|
||||
char2gid = {}
|
||||
# Only supports subtable type 0, 2 and 4.
|
||||
for (_1, _2, st_offset) in subtables:
|
||||
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:
|
||||
char2gid.update(enumerate(struct.unpack(b'>256B', fp.read(256))))
|
||||
char2gid.update(enumerate(struct.unpack('>256B', fp.read(256))))
|
||||
elif fmttype == 2:
|
||||
subheaderkeys = struct.unpack(b'>256H', fp.read(512))
|
||||
subheaderkeys = struct.unpack('>256H', fp.read(512))
|
||||
firstbytes = [0]*8192
|
||||
for (i,k) in enumerate(subheaderkeys):
|
||||
firstbytes[k/8] = i
|
||||
nhdrs = max(subheaderkeys)/8 + 1
|
||||
for (i, k) in enumerate(subheaderkeys):
|
||||
firstbytes[k//8] = i
|
||||
nhdrs = max(subheaderkeys)//8 + 1
|
||||
hdrs = []
|
||||
for i in range(nhdrs):
|
||||
(firstcode,entcount,delta,offset) = struct.unpack(b'>HHhH', fp.read(8))
|
||||
hdrs.append((i,firstcode,entcount,delta,fp.tell()-2+offset))
|
||||
for (i,firstcode,entcount,delta,pos) in hdrs:
|
||||
if not entcount: continue
|
||||
(firstcode, entcount, delta, offset) = struct.unpack('>HHhH', fp.read(8))
|
||||
hdrs.append((i, firstcode, entcount, delta, fp.tell()-2+offset))
|
||||
for (i, firstcode, entcount, delta, pos) in hdrs:
|
||||
if not entcount:
|
||||
continue
|
||||
first = firstcode + (firstbytes[i] << 8)
|
||||
fp.seek(pos)
|
||||
for c in range(entcount):
|
||||
gid = struct.unpack(b'>H', fp.read(2))
|
||||
gid = struct.unpack('>H', fp.read(2))
|
||||
if gid:
|
||||
gid += delta
|
||||
char2gid[first+c] = gid
|
||||
elif fmttype == 4:
|
||||
(segcount, _1, _2, _3) = struct.unpack(b'>HHHH', fp.read(8))
|
||||
segcount /= 2
|
||||
ecs = struct.unpack(b'>%dH' % segcount, fp.read(2*segcount))
|
||||
(segcount, _1, _2, _3) = struct.unpack('>HHHH', fp.read(8))
|
||||
segcount //= 2
|
||||
ecs = struct.unpack('>%dH' % segcount, fp.read(2*segcount))
|
||||
fp.read(2)
|
||||
scs = struct.unpack(b'>%dH' % segcount, fp.read(2*segcount))
|
||||
idds = struct.unpack(b'>%dh' % segcount, fp.read(2*segcount))
|
||||
scs = struct.unpack('>%dH' % segcount, fp.read(2*segcount))
|
||||
idds = struct.unpack('>%dh' % segcount, fp.read(2*segcount))
|
||||
pos = fp.tell()
|
||||
idrs = struct.unpack(b'>%dH' % segcount, fp.read(2*segcount))
|
||||
for (ec,sc,idd,idr) in zip(ecs, scs, idds, idrs):
|
||||
idrs = struct.unpack('>%dH' % segcount, fp.read(2*segcount))
|
||||
for (ec, sc, idd, idr) in zip(ecs, scs, idds, idrs):
|
||||
if idr:
|
||||
fp.seek(pos+idr)
|
||||
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:
|
||||
for c in range(sc, ec+1):
|
||||
char2gid[c] = (c + idd) & 0xffff
|
||||
|
@ -412,21 +446,25 @@ class TrueTypeFont:
|
|||
assert 0
|
||||
# create unicode map
|
||||
unicode_map = FileUnicodeMap()
|
||||
for (char,gid) in char2gid.items():
|
||||
for (char, gid) in char2gid.items():
|
||||
unicode_map.add_cid2unichr(gid, char)
|
||||
return unicode_map
|
||||
|
||||
|
||||
## Fonts
|
||||
##
|
||||
class PDFFontError(PDFException):
|
||||
pass
|
||||
|
||||
class PDFFontError(PDFException): pass
|
||||
class PDFUnicodeNotDefined(PDFFontError): pass
|
||||
|
||||
class PDFUnicodeNotDefined(PDFFontError):
|
||||
pass
|
||||
|
||||
LITERAL_STANDARD_ENCODING = LIT('StandardEncoding')
|
||||
LITERAL_TYPE1C = LIT('Type1C')
|
||||
|
||||
|
||||
# PDFFont
|
||||
class PDFFont:
|
||||
|
||||
def __init__(self, descriptor, widths, default_width=None):
|
||||
|
@ -441,8 +479,9 @@ class PDFFont:
|
|||
self.italic_angle = num_value(descriptor.get('ItalicAngle', 0))
|
||||
self.default_width = default_width or num_value(descriptor.get('MissingWidth', 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
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFFont>'
|
||||
|
@ -453,14 +492,12 @@ class PDFFont:
|
|||
def is_multibyte(self):
|
||||
return False
|
||||
|
||||
def decode(self, s):
|
||||
if isinstance(s, str):
|
||||
return list(map(ord, s))
|
||||
else: # it's already bytes
|
||||
return s
|
||||
def decode(self, data):
|
||||
return list(data)
|
||||
|
||||
def get_ascent(self):
|
||||
return self.ascent * self.vscale
|
||||
|
||||
def get_descent(self):
|
||||
return self.descent * self.vscale
|
||||
|
||||
|
@ -469,6 +506,7 @@ class PDFFont:
|
|||
if w == 0:
|
||||
w = -self.default_width
|
||||
return w * self.hscale
|
||||
|
||||
def get_height(self):
|
||||
h = self.bbox[3]-self.bbox[1]
|
||||
if h == 0:
|
||||
|
@ -476,15 +514,22 @@ class PDFFont:
|
|||
return h * self.vscale
|
||||
|
||||
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):
|
||||
return 0
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, descriptor, widths, spec):
|
||||
|
@ -505,8 +550,9 @@ class PDFSimpleFont(PDFFont):
|
|||
if 'ToUnicode' in spec:
|
||||
strm = stream_value(spec['ToUnicode'])
|
||||
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)
|
||||
return
|
||||
|
||||
def to_unichr(self, cid):
|
||||
if self.unicode_map:
|
||||
|
@ -519,97 +565,112 @@ class PDFSimpleFont(PDFFont):
|
|||
except KeyError:
|
||||
raise PDFUnicodeNotDefined(None, cid)
|
||||
|
||||
|
||||
# PDFType1Font
|
||||
class PDFType1Font(PDFSimpleFont):
|
||||
|
||||
def __init__(self, rsrcmgr, spec):
|
||||
try:
|
||||
self.basefont = literal_name(spec['BaseFont'])
|
||||
except KeyError:
|
||||
handle_error(PDFFontError, 'BaseFont is missing')
|
||||
if STRICT:
|
||||
raise PDFFontError('BaseFont is missing')
|
||||
self.basefont = 'unknown'
|
||||
try:
|
||||
(descriptor, widths) = FontMetricsDB.get_metrics(self.basefont)
|
||||
except KeyError:
|
||||
descriptor = dict_value(spec.get('FontDescriptor', {}))
|
||||
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 = 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)
|
||||
if 'Encoding' not in spec and 'FontFile' in descriptor:
|
||||
# try to recover the missing encoding info from the font file.
|
||||
self.fontfile = stream_value(descriptor.get('FontFile'))
|
||||
length1 = int_value(self.fontfile['Length1'])
|
||||
data = self.fontfile.get_data()[:length1]
|
||||
parser = Type1FontHeaderParser(io.BytesIO(data))
|
||||
parser = Type1FontHeaderParser(BytesIO(data))
|
||||
self.cid2unicode = parser.get_encoding()
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFType1Font: basefont=%r>' % self.basefont
|
||||
|
||||
|
||||
# PDFTrueTypeFont
|
||||
class PDFTrueTypeFont(PDFType1Font):
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFTrueTypeFont: basefont=%r>' % self.basefont
|
||||
|
||||
|
||||
# PDFType3Font
|
||||
class PDFType3Font(PDFSimpleFont):
|
||||
|
||||
def __init__(self, rsrcmgr, spec):
|
||||
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 = 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:
|
||||
descriptor = dict_value(spec['FontDescriptor'])
|
||||
else:
|
||||
descriptor = {'Ascent':0, 'Descent':0,
|
||||
'FontBBox':spec['FontBBox']}
|
||||
descriptor = {'Ascent': 0, 'Descent': 0,
|
||||
'FontBBox': spec['FontBBox']}
|
||||
PDFSimpleFont.__init__(self, descriptor, widths, spec)
|
||||
self.matrix = tuple(list_value(spec.get('FontMatrix')))
|
||||
(_,self.descent,_,self.ascent) = self.bbox
|
||||
(self.hscale,self.vscale) = apply_matrix_norm(self.matrix, (1,1))
|
||||
(_, self.descent, _, self.ascent) = self.bbox
|
||||
(self.hscale, self.vscale) = apply_matrix_norm(self.matrix, (1, 1))
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFType3Font>'
|
||||
|
||||
|
||||
# PDFCIDFont
|
||||
class PDFCIDFont(PDFFont):
|
||||
|
||||
def __init__(self, rsrcmgr, spec):
|
||||
try:
|
||||
self.basefont = literal_name(spec['BaseFont'])
|
||||
except KeyError:
|
||||
handle_error(PDFFontError, 'BaseFont is missing')
|
||||
if STRICT:
|
||||
raise PDFFontError('BaseFont is missing')
|
||||
self.basefont = 'unknown'
|
||||
self.cidsysteminfo = dict_value(spec.get('CIDSystemInfo', {}))
|
||||
self.cidcoding = '%s-%s' % (self.cidsysteminfo.get('Registry', 'unknown'),
|
||||
self.cidsysteminfo.get('Ordering', 'unknown'))
|
||||
registry = bytes_value(self.cidsysteminfo.get('Registry', b'unknown'))
|
||||
ordering = bytes_value(self.cidsysteminfo.get('Ordering', b'unknown'))
|
||||
self.cidcoding = (registry + b'-' + ordering).decode('ascii')
|
||||
try:
|
||||
name = literal_name(spec['Encoding'])
|
||||
except KeyError:
|
||||
handle_error(PDFFontError, 'Encoding is unspecified')
|
||||
if STRICT:
|
||||
raise PDFFontError('Encoding is unspecified')
|
||||
name = 'unknown'
|
||||
try:
|
||||
self.cmap = CMapDB.get_cmap(name)
|
||||
except CMapDB.CMapNotFound as e:
|
||||
handle_error(PDFFontError, str(e))
|
||||
if STRICT:
|
||||
raise PDFFontError(e)
|
||||
self.cmap = CMap()
|
||||
try:
|
||||
descriptor = dict_value(spec['FontDescriptor'])
|
||||
except KeyError:
|
||||
handle_error(PDFFontError, 'FontDescriptor is missing')
|
||||
if STRICT:
|
||||
raise PDFFontError('FontDescriptor is missing')
|
||||
descriptor = {}
|
||||
ttf = None
|
||||
if 'FontFile2' in descriptor:
|
||||
self.fontfile = stream_value(descriptor.get('FontFile2'))
|
||||
ttf = TrueTypeFont(self.basefont,
|
||||
io.BytesIO(self.fontfile.get_data()))
|
||||
BytesIO(self.fontfile.get_data()))
|
||||
self.unicode_map = None
|
||||
if 'ToUnicode' in spec:
|
||||
strm = stream_value(spec['ToUnicode'])
|
||||
self.unicode_map = FileUnicodeMap()
|
||||
CMapParser(self.unicode_map, io.BytesIO(strm.get_data())).run()
|
||||
elif self.cidcoding == 'Adobe-Identity':
|
||||
CMapParser(self.unicode_map, BytesIO(strm.get_data())).run()
|
||||
elif self.cidcoding in ('Adobe-Identity', 'Adobe-UCS'):
|
||||
if ttf:
|
||||
try:
|
||||
self.unicode_map = ttf.create_unicode_map()
|
||||
|
@ -625,10 +686,10 @@ class PDFCIDFont(PDFFont):
|
|||
if self.vertical:
|
||||
# writing mode: vertical
|
||||
widths = get_widths2(list_value(spec.get('W2', [])))
|
||||
self.disps = dict( (cid,(vx,vy)) for (cid,(_,(vx,vy))) in widths.items() )
|
||||
(vy,w) = spec.get('DW2', [880, -1000])
|
||||
self.default_disp = (None,vy)
|
||||
widths = dict( (cid,w) for (cid,(w,_)) in widths.items() )
|
||||
self.disps = dict((cid, (vx, vy)) for (cid, (_, (vx, vy))) in widths.items())
|
||||
(vy, w) = spec.get('DW2', [880, -1000])
|
||||
self.default_disp = (None, vy)
|
||||
widths = dict((cid, w) for (cid, (w, _)) in widths.items())
|
||||
default_width = w
|
||||
else:
|
||||
# writing mode: horizontal
|
||||
|
@ -637,6 +698,7 @@ class PDFCIDFont(PDFFont):
|
|||
widths = get_widths(list_value(spec.get('W', [])))
|
||||
default_width = spec.get('DW', 1000)
|
||||
PDFFont.__init__(self, descriptor, widths, default_width=default_width)
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFCIDFont: basefont=%r, cidcoding=%r>' % (self.basefont, self.cidcoding)
|
||||
|
@ -647,8 +709,8 @@ class PDFCIDFont(PDFFont):
|
|||
def is_multibyte(self):
|
||||
return True
|
||||
|
||||
def decode(self, bytes):
|
||||
return self.cmap.decode(bytes)
|
||||
def decode(self, data):
|
||||
return self.cmap.decode(data)
|
||||
|
||||
def char_disp(self, cid):
|
||||
"Returns an integer for horizontal fonts, a tuple for vertical fonts."
|
||||
|
@ -663,13 +725,14 @@ class PDFCIDFont(PDFFont):
|
|||
raise PDFUnicodeNotDefined(self.cidcoding, cid)
|
||||
|
||||
|
||||
# main
|
||||
def main(argv):
|
||||
for fname in argv[1:]:
|
||||
fp = io.open(fname, 'rb')
|
||||
#font = TrueTypeFont(fname, fp)
|
||||
font = CFFFont(fname, fp)
|
||||
print(font)
|
||||
fp.close()
|
||||
with open(fname, 'rb') as fp:
|
||||
#font = TrueTypeFont(fname, fp)
|
||||
font = CFFFont(fname, fp)
|
||||
print(font)
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
|
|
|
@ -1,28 +1,44 @@
|
|||
import io
|
||||
#!/usr/bin/env python
|
||||
import re
|
||||
import logging
|
||||
|
||||
from .cmapdb import CMapDB, CMap
|
||||
from .psparser import PSTypeError, PSEOF
|
||||
from .psparser import PSKeyword, literal_name, keyword_name
|
||||
from io import BytesIO
|
||||
from .cmapdb import CMapDB
|
||||
from .cmapdb import CMap
|
||||
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 LIT, KWD, handle_error
|
||||
from .pdftypes import (PDFException, PDFStream, PDFObjRef, resolve1, list_value, dict_value,
|
||||
stream_value)
|
||||
from .pdffont import PDFFontError, PDFType1Font, PDFTrueTypeFont, PDFType3Font, PDFCIDFont
|
||||
from .pdfparser import PDFDocument, PDFParser
|
||||
from .pdfcolor import PDFColorSpace, PREDEFINED_COLORSPACE
|
||||
from .psparser import LIT
|
||||
from .psparser import KWD
|
||||
from .psparser import STRICT
|
||||
from .pdftypes import PDFException
|
||||
from .pdftypes import PDFStream
|
||||
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 mult_matrix, MATRIX_IDENTITY
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from .utils import mult_matrix
|
||||
from .utils import MATRIX_IDENTITY
|
||||
|
||||
|
||||
## Exceptions
|
||||
##
|
||||
class PDFResourceError(PDFException): pass
|
||||
class PDFInterpreterError(PDFException): pass
|
||||
class PDFResourceError(PDFException):
|
||||
pass
|
||||
|
||||
class PDFInterpreterError(PDFException):
|
||||
pass
|
||||
|
||||
|
||||
## Constants
|
||||
|
@ -34,6 +50,8 @@ LITERAL_FORM = LIT('Form')
|
|||
LITERAL_IMAGE = LIT('Image')
|
||||
|
||||
|
||||
## PDFTextState
|
||||
##
|
||||
class PDFTextState:
|
||||
|
||||
def __init__(self):
|
||||
|
@ -48,6 +66,7 @@ class PDFTextState:
|
|||
self.reset()
|
||||
# self.matrix is set
|
||||
# self.linematrix is set
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return ('<PDFTextState: font=%r, fontsize=%r, charspace=%r, wordspace=%r, '
|
||||
|
@ -74,8 +93,11 @@ class PDFTextState:
|
|||
def reset(self):
|
||||
self.matrix = MATRIX_IDENTITY
|
||||
self.linematrix = (0, 0)
|
||||
return
|
||||
|
||||
|
||||
## PDFGraphicState
|
||||
##
|
||||
class PDFGraphicState:
|
||||
|
||||
def __init__(self):
|
||||
|
@ -86,6 +108,7 @@ class PDFGraphicState:
|
|||
self.dash = None
|
||||
self.intent = None
|
||||
self.flatness = None
|
||||
return
|
||||
|
||||
def copy(self):
|
||||
obj = PDFGraphicState()
|
||||
|
@ -104,16 +127,24 @@ class PDFGraphicState:
|
|||
(self.linewidth, self.linecap, self.linejoin,
|
||||
self.miterlimit, self.dash, self.intent, self.flatness))
|
||||
|
||||
|
||||
## Resource Manager
|
||||
##
|
||||
class PDFResourceManager:
|
||||
|
||||
"""Repository of shared resources.
|
||||
|
||||
|
||||
ResourceManager facilitates reuse of shared resources
|
||||
such as fonts and images so that large objects are not
|
||||
allocated multiple times.
|
||||
"""
|
||||
|
||||
debug = False
|
||||
|
||||
def __init__(self, caching=True):
|
||||
self.caching = caching
|
||||
self._cached_fonts = {}
|
||||
return
|
||||
|
||||
def get_procset(self, procs):
|
||||
for proc in procs:
|
||||
|
@ -124,26 +155,31 @@ class PDFResourceManager:
|
|||
else:
|
||||
#raise PDFResourceError('ProcSet %r is not supported.' % proc)
|
||||
pass
|
||||
return
|
||||
|
||||
def get_cmap(self, cmapname, strict=False):
|
||||
try:
|
||||
return CMapDB.get_cmap(cmapname)
|
||||
except CMapDB.CMapNotFound:
|
||||
if strict: raise
|
||||
if strict:
|
||||
raise
|
||||
return CMap()
|
||||
|
||||
def get_font(self, objid, spec):
|
||||
if objid and objid in self._cached_fonts:
|
||||
font = self._cached_fonts[objid]
|
||||
else:
|
||||
# logger.debug('get_font: create: objid=%r, spec=%r', objid, spec)
|
||||
if spec['Type'] is not LITERAL_FONT:
|
||||
handle_error(PDFFontError, 'Type is not /Font')
|
||||
if self.debug:
|
||||
logging.info('get_font: create: objid=%r, spec=%r' % (objid, spec))
|
||||
if STRICT:
|
||||
if spec['Type'] is not LITERAL_FONT:
|
||||
raise PDFFontError('Type is not /Font')
|
||||
# Create a Font object.
|
||||
if 'Subtype' in spec:
|
||||
subtype = literal_name(spec['Subtype'])
|
||||
else:
|
||||
handle_error(PDFFontError, 'Font Subtype is not specified.')
|
||||
if STRICT:
|
||||
raise PDFFontError('Font Subtype is not specified.')
|
||||
subtype = 'Type1'
|
||||
if subtype in ('Type1', 'MMType1'):
|
||||
# Type1 Font
|
||||
|
@ -167,56 +203,90 @@ class PDFResourceManager:
|
|||
subspec[k] = resolve1(spec[k])
|
||||
font = self.get_font(None, subspec)
|
||||
else:
|
||||
handle_error(PDFFontError, 'Invalid Font spec: %r' % spec)
|
||||
font = PDFType1Font(self, spec) # this is so wrong!
|
||||
if STRICT:
|
||||
raise PDFFontError('Invalid Font spec: %r' % spec)
|
||||
font = PDFType1Font(self, spec) # this is so wrong!
|
||||
if objid and self.caching:
|
||||
self._cached_fonts[objid] = font
|
||||
return font
|
||||
|
||||
|
||||
## PDFContentParser
|
||||
##
|
||||
class PDFContentParser(PSStackParser):
|
||||
|
||||
def __init__(self, streams):
|
||||
fp = io.StringIO()
|
||||
for stream in streams:
|
||||
stream = stream_value(stream)
|
||||
data = stream.get_data()
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode('latin-1')
|
||||
fp.write(data)
|
||||
fp.seek(0)
|
||||
PSStackParser.__init__(self, fp)
|
||||
self.streams = streams
|
||||
self.istream = 0
|
||||
PSStackParser.__init__(self, None)
|
||||
return
|
||||
|
||||
def get_inline_data(self, pos, target='EI'):
|
||||
currpos = pos
|
||||
def fillfp(self):
|
||||
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
|
||||
data = ''
|
||||
data = b''
|
||||
while i <= len(target):
|
||||
self.fillbuf()
|
||||
if i:
|
||||
c = self.data[currpos]
|
||||
c = self.buf[self.charpos:self.charpos+1]
|
||||
data += c
|
||||
currpos += 1
|
||||
self.charpos += 1
|
||||
if len(target) <= i and c.isspace():
|
||||
i += 1
|
||||
elif i < len(target) and c == target[i]:
|
||||
elif i < len(target) and c == target[i:i+1]:
|
||||
i += 1
|
||||
else:
|
||||
i = 0
|
||||
else:
|
||||
j = self.data.index(target[0], currpos)
|
||||
data += self.data[currpos:j+1]
|
||||
currpos = j+1
|
||||
i = 1
|
||||
data = data[:-(len(target)+1)] # strip the last part
|
||||
data = re.sub(r'(\x0d\x0a|[\x0d\x0a])$', '', data)
|
||||
try:
|
||||
j = self.buf.index(target[0], self.charpos)
|
||||
#print('found', (0, self.buf[j:j+10]))
|
||||
data += self.buf[self.charpos:j+1]
|
||||
self.charpos = j+1
|
||||
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)
|
||||
|
||||
def flush(self):
|
||||
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):
|
||||
if token is self.KEYWORD_BI:
|
||||
# inline image within a content stream
|
||||
|
@ -226,25 +296,32 @@ class PDFContentParser(PSStackParser):
|
|||
(_, objs) = self.end_type('inline')
|
||||
if len(objs) % 2 != 0:
|
||||
raise PSTypeError('Invalid dictionary construct: %r' % objs)
|
||||
d = dict( (literal_name(k), v) for (k,v) in choplist(2, objs) )
|
||||
(pos, data) = self.get_inline_data(pos+len('ID '))
|
||||
d = dict((literal_name(k), v) for (k, v) in choplist(2, objs))
|
||||
(pos, data) = self.get_inline_data(pos+len(b'ID '))
|
||||
obj = PDFStream(d, data)
|
||||
self.push((pos, obj))
|
||||
self.push((pos, self.KEYWORD_EI))
|
||||
except PSTypeError as e:
|
||||
handle_error(type(e), str(e))
|
||||
except PSTypeError:
|
||||
if STRICT:
|
||||
raise
|
||||
else:
|
||||
self.push((pos, token))
|
||||
return
|
||||
|
||||
|
||||
## Interpreter
|
||||
##
|
||||
class PDFPageInterpreter:
|
||||
|
||||
debug = 0
|
||||
|
||||
def __init__(self, rsrcmgr, device):
|
||||
self.rsrcmgr = rsrcmgr
|
||||
self.device = device
|
||||
return
|
||||
|
||||
def dup(self):
|
||||
return PDFPageInterpreter(self.rsrcmgr, self.device)
|
||||
return self.__class__(self.rsrcmgr, self.device)
|
||||
|
||||
# init_resources(resources):
|
||||
# Prepare the fonts and XObjects listed in the Resource attribute.
|
||||
|
@ -255,9 +332,8 @@ class PDFPageInterpreter:
|
|||
self.csmap = PREDEFINED_COLORSPACE.copy()
|
||||
if not resources:
|
||||
return
|
||||
|
||||
def get_colorspace(spec):
|
||||
if spec is None:
|
||||
return PREDEFINED_COLORSPACE['DeviceRGB']
|
||||
if isinstance(spec, list):
|
||||
name = literal_name(spec[0])
|
||||
else:
|
||||
|
@ -267,25 +343,26 @@ class PDFPageInterpreter:
|
|||
elif name == 'DeviceN' and isinstance(spec, list) and 2 <= len(spec):
|
||||
return PDFColorSpace(name, len(list_value(spec[1])))
|
||||
else:
|
||||
return PREDEFINED_COLORSPACE[name]
|
||||
for (k,v) in dict_value(resources).items():
|
||||
# logger.debug('Resource: %r: %r', k,v)
|
||||
return PREDEFINED_COLORSPACE.get(name)
|
||||
for (k, v) in dict_value(resources).items():
|
||||
if self.debug:
|
||||
logging.debug('Resource: %r: %r' % (k, v))
|
||||
if k == 'Font':
|
||||
for (fontid,spec) in dict_value(v).items():
|
||||
for (fontid, spec) in dict_value(v).items():
|
||||
objid = None
|
||||
if isinstance(spec, PDFObjRef):
|
||||
objid = spec.objid
|
||||
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':
|
||||
for (csid,spec) in dict_value(v).items():
|
||||
for (csid, spec) in dict_value(v).items():
|
||||
self.csmap[csid] = get_colorspace(resolve1(spec))
|
||||
elif k == 'ProcSet':
|
||||
self.rsrcmgr.get_procset(list_value(v))
|
||||
elif k == 'XObject':
|
||||
for (xobjid,xobjstrm) in dict_value(v).items():
|
||||
for (xobjid, xobjstrm) in dict_value(v).items():
|
||||
self.xobjmap[xobjid] = xobjstrm
|
||||
return
|
||||
|
||||
# init_state(ctm)
|
||||
# Initialize the text and graphic states for rendering a page.
|
||||
|
@ -302,10 +379,14 @@ class PDFPageInterpreter:
|
|||
# set some global states.
|
||||
self.scs = self.ncs = None
|
||||
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):
|
||||
self.argstack.append(obj)
|
||||
return
|
||||
|
||||
def pop(self, n):
|
||||
if n == 0:
|
||||
|
@ -320,283 +401,399 @@ class PDFPageInterpreter:
|
|||
def set_current_state(self, state):
|
||||
(self.ctm, self.textstate, self.graphicstate) = state
|
||||
self.device.set_ctm(self.ctm)
|
||||
return
|
||||
|
||||
# gsave
|
||||
def do_q(self):
|
||||
self.gstack.append(self.get_current_state())
|
||||
return
|
||||
|
||||
# grestore
|
||||
def do_Q(self):
|
||||
if self.gstack:
|
||||
self.set_current_state(self.gstack.pop())
|
||||
return
|
||||
|
||||
# concat-matrix
|
||||
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)
|
||||
return
|
||||
|
||||
# setlinewidth
|
||||
def do_w(self, linewidth):
|
||||
self.graphicstate.linewidth = linewidth
|
||||
return
|
||||
|
||||
# setlinecap
|
||||
def do_J(self, linecap):
|
||||
self.graphicstate.linecap = linecap
|
||||
return
|
||||
|
||||
# setlinejoin
|
||||
def do_j(self, linejoin):
|
||||
self.graphicstate.linejoin = linejoin
|
||||
return
|
||||
|
||||
# setmiterlimit
|
||||
def do_M(self, miterlimit):
|
||||
self.graphicstate.miterlimit = miterlimit
|
||||
return
|
||||
|
||||
# setdash
|
||||
def do_d(self, dash, phase):
|
||||
self.graphicstate.dash = (dash, phase)
|
||||
return
|
||||
|
||||
# setintent
|
||||
def do_ri(self, intent):
|
||||
self.graphicstate.intent = intent
|
||||
return
|
||||
|
||||
# setflatness
|
||||
def do_i(self, flatness):
|
||||
self.graphicstate.flatness = flatness
|
||||
return
|
||||
|
||||
# load-gstate
|
||||
def do_gs(self, name):
|
||||
#XXX
|
||||
pass
|
||||
return
|
||||
|
||||
# moveto
|
||||
def do_m(self, x, y):
|
||||
self.curpath.append(('m',x,y))
|
||||
self.curpath.append(('m', x, y))
|
||||
return
|
||||
|
||||
# lineto
|
||||
def do_l(self, x, y):
|
||||
self.curpath.append(('l',x,y))
|
||||
self.curpath.append(('l', x, y))
|
||||
return
|
||||
|
||||
# curveto
|
||||
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
|
||||
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
|
||||
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
|
||||
def do_h(self):
|
||||
self.curpath.append(('h',))
|
||||
return
|
||||
|
||||
# rectangle
|
||||
def do_re(self, x, y, w, h):
|
||||
self.curpath.append(('m',x,y))
|
||||
self.curpath.append(('l',x+w,y))
|
||||
self.curpath.append(('l',x+w,y+h))
|
||||
self.curpath.append(('l',x,y+h))
|
||||
self.curpath.append(('m', x, y))
|
||||
self.curpath.append(('l', x+w, y))
|
||||
self.curpath.append(('l', x+w, y+h))
|
||||
self.curpath.append(('l', x, y+h))
|
||||
self.curpath.append(('h',))
|
||||
return
|
||||
|
||||
# stroke
|
||||
def do_S(self):
|
||||
self.device.paint_path(self.graphicstate, True, False, False, self.curpath)
|
||||
self.curpath = []
|
||||
return
|
||||
|
||||
# close-and-stroke
|
||||
def do_s(self):
|
||||
self.do_h()
|
||||
self.do_S()
|
||||
return
|
||||
|
||||
# fill
|
||||
def do_f(self):
|
||||
self.device.paint_path(self.graphicstate, False, True, False, self.curpath)
|
||||
self.curpath = []
|
||||
return
|
||||
# fill (obsolete)
|
||||
do_F = do_f
|
||||
|
||||
# fill-even-odd
|
||||
def do_f_a(self):
|
||||
self.device.paint_path(self.graphicstate, False, True, True, self.curpath)
|
||||
self.curpath = []
|
||||
return
|
||||
|
||||
# fill-and-stroke
|
||||
def do_B(self):
|
||||
self.device.paint_path(self.graphicstate, True, True, False, self.curpath)
|
||||
self.curpath = []
|
||||
return
|
||||
|
||||
# fill-and-stroke-even-odd
|
||||
def do_B_a(self):
|
||||
self.device.paint_path(self.graphicstate, True, True, True, self.curpath)
|
||||
self.curpath = []
|
||||
return
|
||||
|
||||
# close-fill-and-stroke
|
||||
def do_b(self):
|
||||
self.do_h()
|
||||
self.do_B()
|
||||
return
|
||||
|
||||
# close-fill-and-stroke-even-odd
|
||||
def do_b_a(self):
|
||||
self.do_h()
|
||||
self.do_B_a()
|
||||
return
|
||||
|
||||
# close-only
|
||||
def do_n(self):
|
||||
self.curpath = []
|
||||
return
|
||||
|
||||
# clip
|
||||
def do_W(self):
|
||||
pass
|
||||
return
|
||||
|
||||
# clip-even-odd
|
||||
def do_W_a(self):
|
||||
pass
|
||||
return
|
||||
|
||||
# setcolorspace-stroking
|
||||
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
|
||||
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
|
||||
def do_G(self, gray):
|
||||
#self.do_CS(LITERAL_DEVICE_GRAY)
|
||||
pass
|
||||
return
|
||||
|
||||
# setgray-non-stroking
|
||||
def do_g(self, gray):
|
||||
#self.do_cs(LITERAL_DEVICE_GRAY)
|
||||
pass
|
||||
return
|
||||
|
||||
# setrgb-stroking
|
||||
def do_RG(self, r, g, b):
|
||||
#self.do_CS(LITERAL_DEVICE_RGB)
|
||||
pass
|
||||
return
|
||||
|
||||
# setrgb-non-stroking
|
||||
def do_rg(self, r, g, b):
|
||||
#self.do_cs(LITERAL_DEVICE_RGB)
|
||||
pass
|
||||
return
|
||||
|
||||
# setcmyk-stroking
|
||||
def do_K(self, c, m, y, k):
|
||||
#self.do_CS(LITERAL_DEVICE_CMYK)
|
||||
pass
|
||||
return
|
||||
|
||||
# setcmyk-non-stroking
|
||||
def do_k(self, c, m, y, k):
|
||||
#self.do_cs(LITERAL_DEVICE_CMYK)
|
||||
pass
|
||||
return
|
||||
|
||||
# setcolor
|
||||
def do_SCN(self):
|
||||
if self.scs:
|
||||
n = self.scs.ncomponents
|
||||
else:
|
||||
handle_error(PDFInterpreterError, 'No colorspace specified!')
|
||||
if STRICT:
|
||||
raise PDFInterpreterError('No colorspace specified!')
|
||||
n = 1
|
||||
self.pop(n)
|
||||
return
|
||||
|
||||
def do_scn(self):
|
||||
if self.ncs:
|
||||
n = self.ncs.ncomponents
|
||||
else:
|
||||
handle_error(PDFInterpreterError, 'No colorspace specified!')
|
||||
if STRICT:
|
||||
raise PDFInterpreterError('No colorspace specified!')
|
||||
n = 1
|
||||
self.pop(n)
|
||||
return
|
||||
|
||||
def do_SC(self):
|
||||
self.do_SCN()
|
||||
return
|
||||
|
||||
def do_sc(self):
|
||||
self.do_scn()
|
||||
return
|
||||
|
||||
# sharing-name
|
||||
def do_sh(self, name):
|
||||
pass
|
||||
return
|
||||
|
||||
# begin-text
|
||||
def do_BT(self):
|
||||
self.textstate.reset()
|
||||
return
|
||||
|
||||
# end-text
|
||||
def do_ET(self):
|
||||
pass
|
||||
return
|
||||
|
||||
# begin-compat
|
||||
def do_BX(self):
|
||||
pass
|
||||
return
|
||||
|
||||
# end-compat
|
||||
def do_EX(self):
|
||||
pass
|
||||
return
|
||||
|
||||
# marked content operators
|
||||
def do_MP(self, tag):
|
||||
self.device.do_tag(tag)
|
||||
return
|
||||
|
||||
def do_DP(self, tag, props):
|
||||
self.device.do_tag(tag, props)
|
||||
return
|
||||
|
||||
def do_BMC(self, tag):
|
||||
self.device.begin_tag(tag)
|
||||
return
|
||||
|
||||
def do_BDC(self, tag, props):
|
||||
self.device.begin_tag(tag, props)
|
||||
return
|
||||
|
||||
def do_EMC(self):
|
||||
self.device.end_tag()
|
||||
return
|
||||
|
||||
# setcharspace
|
||||
def do_Tc(self, space):
|
||||
self.textstate.charspace = space
|
||||
return
|
||||
|
||||
# setwordspace
|
||||
def do_Tw(self, space):
|
||||
self.textstate.wordspace = space
|
||||
return
|
||||
|
||||
# textscale
|
||||
def do_Tz(self, scale):
|
||||
self.textstate.scaling = scale
|
||||
return
|
||||
|
||||
# setleading
|
||||
def do_TL(self, leading):
|
||||
self.textstate.leading = -leading
|
||||
return
|
||||
|
||||
# selectfont
|
||||
def do_Tf(self, fontid, fontsize):
|
||||
try:
|
||||
self.textstate.font = self.fontmap[literal_name(fontid)]
|
||||
except KeyError:
|
||||
handle_error(PDFInterpreterError, 'Undefined Font id: %r' % fontid)
|
||||
return
|
||||
if STRICT:
|
||||
raise PDFInterpreterError('Undefined Font id: %r' % fontid)
|
||||
self.textstate.font = self.rsrcmgr.get_font(None, {})
|
||||
self.textstate.fontsize = fontsize
|
||||
return
|
||||
|
||||
# setrendering
|
||||
def do_Tr(self, render):
|
||||
self.textstate.render = render
|
||||
return
|
||||
|
||||
# settextrise
|
||||
def do_Ts(self, rise):
|
||||
self.textstate.rise = rise
|
||||
return
|
||||
|
||||
# text-move
|
||||
def do_Td(self, tx, ty):
|
||||
(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)
|
||||
(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.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
|
||||
def do_TD(self, tx, ty):
|
||||
(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)
|
||||
(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.leading = ty
|
||||
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
|
||||
def do_Tm(self, a,b,c,d,e,f):
|
||||
self.textstate.matrix = (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.linematrix = (0, 0)
|
||||
return
|
||||
|
||||
# nextline
|
||||
def do_T_a(self):
|
||||
(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)
|
||||
(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.linematrix = (0, 0)
|
||||
return
|
||||
|
||||
# show-pos
|
||||
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:
|
||||
handle_error(PDFInterpreterError, 'No font specified!')
|
||||
if STRICT:
|
||||
raise PDFInterpreterError('No font specified!')
|
||||
return
|
||||
self.device.render_string(self.textstate, seq)
|
||||
return
|
||||
|
||||
# show
|
||||
def do_Tj(self, s):
|
||||
self.do_TJ([s])
|
||||
return
|
||||
|
||||
# quote
|
||||
def do__q(self, s):
|
||||
self.do_T_a()
|
||||
self.do_TJ([s])
|
||||
return
|
||||
|
||||
# doublequote
|
||||
def do__w(self, aw, ac, s):
|
||||
self.do_Tw(aw)
|
||||
self.do_Tc(ac)
|
||||
self.do_TJ([s])
|
||||
return
|
||||
|
||||
# inline image
|
||||
def do_BI(self): # never called
|
||||
pass
|
||||
def do_ID(self): # never called
|
||||
pass
|
||||
def do_BI(self): # never called
|
||||
return
|
||||
|
||||
def do_ID(self): # never called
|
||||
return
|
||||
|
||||
def do_EI(self, obj):
|
||||
try:
|
||||
if 'W' in obj and 'H' in obj:
|
||||
iobjid = str(id(obj))
|
||||
self.device.begin_figure(iobjid, (0,0,1,1), MATRIX_IDENTITY)
|
||||
self.device.render_image(iobjid, obj)
|
||||
self.device.end_figure(iobjid)
|
||||
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")
|
||||
if 'W' in obj and 'H' in obj:
|
||||
iobjid = str(id(obj))
|
||||
self.device.begin_figure(iobjid, (0, 0, 1, 1), MATRIX_IDENTITY)
|
||||
self.device.render_image(iobjid, obj)
|
||||
self.device.end_figure(iobjid)
|
||||
return
|
||||
|
||||
# invoke an XObject
|
||||
def do_Do(self, xobjid):
|
||||
|
@ -604,15 +801,16 @@ class PDFPageInterpreter:
|
|||
try:
|
||||
xobj = stream_value(self.xobjmap[xobjid])
|
||||
except KeyError:
|
||||
handle_error(PDFInterpreterError, 'Undefined xobject id: %r' % xobjid)
|
||||
if STRICT:
|
||||
raise PDFInterpreterError('Undefined xobject id: %r' % xobjid)
|
||||
return
|
||||
logger.debug('Processing xobj: %r', xobj)
|
||||
if self.debug: logging.info('Processing xobj: %r' % xobj)
|
||||
subtype = xobj.get('Subtype')
|
||||
if subtype is LITERAL_FORM and 'BBox' in xobj:
|
||||
interpreter = self.dup()
|
||||
bbox = list_value(xobj['BBox'])
|
||||
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
|
||||
# instead of having their own Resources entry.
|
||||
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))
|
||||
self.device.end_figure(xobjid)
|
||||
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.end_figure(xobjid)
|
||||
else:
|
||||
# unsupported xobject type.
|
||||
pass
|
||||
return
|
||||
|
||||
def process_page(self, page):
|
||||
logger.debug('Processing page: %r', page)
|
||||
(x0,y0,x1,y1) = page.mediabox
|
||||
if self.debug: logging.info('Processing page: %r' % page)
|
||||
(x0, y0, x1, y1) = page.mediabox
|
||||
if page.rotate == 90:
|
||||
ctm = (0,-1,1,0, -y0,x1)
|
||||
ctm = (0, -1, 1, 0, -y0, x1)
|
||||
elif page.rotate == 180:
|
||||
ctm = (-1,0,0,-1, x1,y1)
|
||||
ctm = (-1, 0, 0, -1, x1, y1)
|
||||
elif page.rotate == 270:
|
||||
ctm = (0,1,-1,0, y1,-x0)
|
||||
ctm = (0, 1, -1, 0, y1, -x0)
|
||||
else:
|
||||
ctm = (1,0,0,1, -x0,-y0)
|
||||
ctm = (1, 0, 0, 1, -x0, -y0)
|
||||
self.device.begin_page(page, ctm)
|
||||
self.render_contents(page.resources, page.contents, ctm=ctm)
|
||||
self.device.end_page(page)
|
||||
return
|
||||
|
||||
# render_contents(resources, streams, ctm)
|
||||
# Render the content streams.
|
||||
# This method may be called recursively.
|
||||
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_state(ctm)
|
||||
self.execute(list_value(streams))
|
||||
return
|
||||
|
||||
def execute(self, streams):
|
||||
try:
|
||||
|
@ -659,50 +862,28 @@ class PDFPageInterpreter:
|
|||
return
|
||||
while 1:
|
||||
try:
|
||||
(_,obj) = parser.nextobject()
|
||||
(_, obj) = parser.nextobject()
|
||||
except PSEOF:
|
||||
break
|
||||
if isinstance(obj, PSKeyword):
|
||||
name = keyword_name(obj)
|
||||
method = 'do_%s' % name.replace('*','_a').replace('"','_w').replace("'",'_q')
|
||||
name = keyword_name(obj).decode('ascii')
|
||||
method = 'do_%s' % name.replace('*', '_a').replace('"', '_w').replace("'", '_q')
|
||||
if hasattr(self, method):
|
||||
func = getattr(self, method)
|
||||
nargs = func.__code__.co_argcount-1
|
||||
if 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:
|
||||
func(*args)
|
||||
else:
|
||||
# logger.debug('exec: %s', name)
|
||||
if self.debug:
|
||||
logging.debug('exec: %s' % name)
|
||||
func()
|
||||
else:
|
||||
handle_error(PDFInterpreterError, 'Unknown operator: %r' % name)
|
||||
if STRICT:
|
||||
raise PDFInterpreterError('Unknown operator: %r' % name)
|
||||
else:
|
||||
self.push(obj)
|
||||
|
||||
|
||||
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
|
||||
return
|
||||
|
|
|
@ -1,630 +1,26 @@
|
|||
import io
|
||||
import re
|
||||
import struct
|
||||
import hashlib as md5
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
|
||||
from .psparser import PSStackParser, PSSyntaxError, PSEOF, literal_name, LIT, KWD, handle_error
|
||||
from .pdftypes import (PDFException, PDFTypeError, PDFNotImplementedError, PDFStream, PDFObjRef,
|
||||
resolve1, decipher_all, int_value, str_value, list_value, dict_value, stream_value)
|
||||
from .arcfour import Arcfour
|
||||
from .utils import choplist, nunpack, decode_text, ObjIdRange
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from io import BytesIO
|
||||
from .psparser import PSStackParser
|
||||
from .psparser import PSSyntaxError
|
||||
from .psparser import PSEOF
|
||||
from .psparser import KWD
|
||||
from .psparser import STRICT
|
||||
from .pdftypes import PDFException
|
||||
from .pdftypes import PDFStream
|
||||
from .pdftypes import PDFObjRef
|
||||
from .pdftypes import int_value
|
||||
from .pdftypes import dict_value
|
||||
|
||||
|
||||
## Exceptions
|
||||
##
|
||||
class PDFSyntaxError(PDFException): pass
|
||||
class PDFNoValidXRef(PDFSyntaxError): 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
|
||||
class PDFSyntaxError(PDFException):
|
||||
pass
|
||||
|
||||
|
||||
## PDFParser
|
||||
##
|
||||
class PDFParser(PSStackParser):
|
||||
|
||||
"""
|
||||
|
@ -636,33 +32,37 @@ class PDFParser(PSStackParser):
|
|||
Typical usage:
|
||||
parser = PDFParser(fp)
|
||||
parser.read_xref()
|
||||
parser.read_xref(fallback=True) # optional
|
||||
parser.set_document(doc)
|
||||
parser.seek(offset)
|
||||
parser.nextobject()
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, fp):
|
||||
PSStackParser.__init__(self, fp)
|
||||
self.doc = None
|
||||
self.fallback = False
|
||||
return
|
||||
|
||||
def set_document(self, doc):
|
||||
"""Associates the parser with a PDFDocument object."""
|
||||
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):
|
||||
"""Handles PDF-related keywords."""
|
||||
|
||||
|
||||
if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
|
||||
self.add_results(*self.pop(1))
|
||||
|
||||
|
||||
elif token is self.KEYWORD_ENDOBJ:
|
||||
self.add_results(*self.pop(4))
|
||||
|
||||
|
@ -673,7 +73,7 @@ class PDFParser(PSStackParser):
|
|||
elif token is self.KEYWORD_R:
|
||||
# reference to indirect object
|
||||
try:
|
||||
((_,objid), (_,genno)) = self.pop(2)
|
||||
((_, objid), (_, genno)) = self.pop(2)
|
||||
(objid, genno) = (int(objid), int(genno))
|
||||
obj = PDFObjRef(self.doc, objid, genno)
|
||||
self.push((pos, obj))
|
||||
|
@ -682,102 +82,59 @@ class PDFParser(PSStackParser):
|
|||
|
||||
elif token is self.KEYWORD_STREAM:
|
||||
# stream object
|
||||
((_,dic),) = self.pop(1)
|
||||
((_, dic),) = self.pop(1)
|
||||
dic = dict_value(dic)
|
||||
try:
|
||||
objlen = int_value(dic['Length'])
|
||||
except KeyError:
|
||||
handle_error(PDFSyntaxError, '/Length is undefined: %r' % dic)
|
||||
objlen = 0
|
||||
self.setpos(pos)
|
||||
objlen = 0
|
||||
if not self.fallback:
|
||||
try:
|
||||
objlen = int_value(dic['Length'])
|
||||
except KeyError:
|
||||
if STRICT:
|
||||
raise PDFSyntaxError('/Length is undefined: %r' % dic)
|
||||
self.seek(pos)
|
||||
try:
|
||||
(_, line) = self.nextline() # 'stream'
|
||||
except PSEOF:
|
||||
handle_error(PDFSyntaxError, 'Unexpected EOF')
|
||||
if STRICT:
|
||||
raise PDFSyntaxError('Unexpected EOF')
|
||||
return
|
||||
pos += len(line)
|
||||
endpos = pos + objlen
|
||||
if 'endstream' not in self.data[endpos:endpos+len('endstream')+2]:
|
||||
r = re.compile(r'(\r\n|\r|\n)endstream')
|
||||
m = r.search(self.data, pos)
|
||||
if m is None:
|
||||
raise PDFSyntaxError("stream with no endstream")
|
||||
endpos = m.start()
|
||||
data = self.data[pos:endpos].encode('latin-1')
|
||||
self.setpos(endpos)
|
||||
self.nexttoken() # consume 'endstream'
|
||||
self.fp.seek(pos)
|
||||
data = self.fp.read(objlen)
|
||||
self.seek(pos+objlen)
|
||||
while 1:
|
||||
try:
|
||||
(linepos, line) = self.nextline()
|
||||
except PSEOF:
|
||||
if STRICT:
|
||||
raise PDFSyntaxError('Unexpected EOF')
|
||||
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
|
||||
# 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)
|
||||
self.push((pos, obj))
|
||||
|
||||
else:
|
||||
# others
|
||||
self.push((pos, token))
|
||||
|
||||
|
||||
def find_xref(self):
|
||||
"""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
|
||||
return
|
||||
|
||||
|
||||
## PDFStreamParser
|
||||
##
|
||||
class PDFStreamParser(PDFParser):
|
||||
|
||||
"""
|
||||
|
@ -789,21 +146,31 @@ class PDFStreamParser(PDFParser):
|
|||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
PDFParser.__init__(self, io.BytesIO(data))
|
||||
PDFParser.__init__(self, BytesIO(data))
|
||||
return
|
||||
|
||||
def flush(self):
|
||||
self.add_results(*self.popall())
|
||||
return
|
||||
|
||||
KEYWORD_OBJ = KWD(b'obj')
|
||||
def do_keyword(self, pos, token):
|
||||
if token is self.KEYWORD_R:
|
||||
# reference to indirect object
|
||||
try:
|
||||
((_,objid), (_,genno)) = self.pop(2)
|
||||
((_, objid), (_, genno)) = self.pop(2)
|
||||
(objid, genno) = (int(objid), int(genno))
|
||||
obj = PDFObjRef(self.doc, objid, genno)
|
||||
self.push((pos, obj))
|
||||
except PSSyntaxError:
|
||||
pass
|
||||
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
|
||||
self.push((pos, token))
|
||||
return
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
from functools import partial
|
||||
#!/usr/bin/env python
|
||||
import zlib
|
||||
from .lzw import lzwdecode
|
||||
from .ascii85 import ascii85decode, asciihexdecode
|
||||
from .ascii85 import ascii85decode
|
||||
from .ascii85 import asciihexdecode
|
||||
from .runlength import rldecode
|
||||
from .psparser import PSException, PSObject, STRICT
|
||||
from .psparser import LIT, handle_error
|
||||
from .ccitt import ccittfaxdecode
|
||||
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 isnumber
|
||||
|
||||
|
||||
LITERAL_CRYPT = LIT('Crypt')
|
||||
|
||||
|
@ -21,12 +27,23 @@ LITERALS_DCT_DECODE = (LIT('DCTDecode'), LIT('DCT'))
|
|||
|
||||
## PDF Objects
|
||||
##
|
||||
class PDFObject(PSObject): pass
|
||||
class PDFObject(PSObject):
|
||||
pass
|
||||
|
||||
class PDFException(PSException): pass
|
||||
class PDFTypeError(PDFException): pass
|
||||
class PDFValueError(PDFException): pass
|
||||
class PDFNotImplementedError(PSException): pass
|
||||
class PDFException(PSException):
|
||||
pass
|
||||
|
||||
class PDFTypeError(PDFException):
|
||||
pass
|
||||
|
||||
class PDFValueError(PDFException):
|
||||
pass
|
||||
|
||||
class PDFObjectNotFound(PDFException):
|
||||
pass
|
||||
|
||||
class PDFNotImplementedError(PDFException):
|
||||
pass
|
||||
|
||||
|
||||
## PDFObjRef
|
||||
|
@ -35,82 +52,130 @@ class PDFObjRef(PDFObject):
|
|||
|
||||
def __init__(self, doc, objid, _):
|
||||
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.objid = objid
|
||||
#self.genno = genno # Never used.
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '<PDFObjRef:%d>' % (self.objid)
|
||||
|
||||
def resolve(self):
|
||||
return self.doc.getobj(self.objid)
|
||||
def resolve(self, default=None):
|
||||
try:
|
||||
return self.doc.getobj(self.objid)
|
||||
except PDFObjectNotFound:
|
||||
return default
|
||||
|
||||
|
||||
# resolve
|
||||
def resolve1(x):
|
||||
def resolve1(x, default=None):
|
||||
"""Resolves an object.
|
||||
|
||||
If this is an array or dictionary, it may still contains
|
||||
some indirect objects inside.
|
||||
"""
|
||||
while isinstance(x, PDFObjRef):
|
||||
x = x.resolve()
|
||||
x = x.resolve(default=default)
|
||||
return x
|
||||
|
||||
def resolve_all(x):
|
||||
|
||||
def resolve_all(x, default=None):
|
||||
"""Recursively resolves the given object and all the internals.
|
||||
|
||||
|
||||
Make sure there is no indirect reference within the nested object.
|
||||
This procedure might be slow.
|
||||
"""
|
||||
while isinstance(x, PDFObjRef):
|
||||
x = x.resolve()
|
||||
x = x.resolve(default=default)
|
||||
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):
|
||||
for (k,v) in x.items():
|
||||
x[k] = resolve_all(v)
|
||||
for (k, v) in x.items():
|
||||
x[k] = resolve_all(v, default=default)
|
||||
return x
|
||||
|
||||
|
||||
def decipher_all(decipher, objid, genno, x):
|
||||
"""Recursively deciphers the given object.
|
||||
"""
|
||||
if isinstance(x, str):
|
||||
x = x.encode('latin-1')
|
||||
if isinstance(x, bytes):
|
||||
return decipher(objid, genno, x)
|
||||
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):
|
||||
for (k,v) in x.items():
|
||||
for (k, v) in x.items():
|
||||
x[k] = decipher_all(decipher, objid, genno, v)
|
||||
return x
|
||||
|
||||
# Type cheking
|
||||
def typecheck_value(x, type, strict=STRICT):
|
||||
|
||||
# Type checking
|
||||
def int_value(x):
|
||||
x = resolve1(x)
|
||||
if not isinstance(x, type):
|
||||
handle_error(PDFTypeError, 'Wrong type: %r required: %r' % (x, type), strict=strict)
|
||||
default_type = type[0] if isinstance(type, tuple) else type
|
||||
return default_type()
|
||||
if not isinstance(x, int):
|
||||
if STRICT:
|
||||
raise PDFTypeError('Integer required: %r' % x)
|
||||
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
|
||||
|
||||
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):
|
||||
x = resolve1(x)
|
||||
if not isinstance(x, PDFStream):
|
||||
handle_error(PDFTypeError, 'PDFStream required: %r' % x)
|
||||
return PDFStream({}, b'')
|
||||
if STRICT:
|
||||
raise PDFTypeError('PDFStream required: %r' % x)
|
||||
return PDFStream({}, '')
|
||||
return x
|
||||
|
||||
|
||||
## PDFStream type
|
||||
##
|
||||
class PDFStream(PDFObject):
|
||||
|
||||
def __init__(self, attrs, rawdata, decipher=None):
|
||||
|
@ -121,10 +186,12 @@ class PDFStream(PDFObject):
|
|||
self.data = None
|
||||
self.objid = None
|
||||
self.genno = None
|
||||
return
|
||||
|
||||
def set_objid(self, objid, genno):
|
||||
self.objid = objid
|
||||
self.genno = genno
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
if self.data is None:
|
||||
|
@ -136,13 +203,13 @@ class PDFStream(PDFObject):
|
|||
|
||||
def __contains__(self, name):
|
||||
return name in self.attrs
|
||||
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.attrs[name]
|
||||
|
||||
|
||||
def get(self, name, default=None):
|
||||
return self.attrs.get(name, default)
|
||||
|
||||
|
||||
def get_any(self, names, default=None):
|
||||
for name in names:
|
||||
if name in self.attrs:
|
||||
|
@ -151,33 +218,37 @@ class PDFStream(PDFObject):
|
|||
|
||||
def get_filters(self):
|
||||
filters = self.get_any(('F', 'Filter'))
|
||||
if not filters: return []
|
||||
if isinstance(filters, list): return filters
|
||||
return [ filters ]
|
||||
params = self.get_any(('DP', 'DecodeParms', 'FDecodeParms'), {})
|
||||
if not 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):
|
||||
assert self.data is None and self.rawdata != None
|
||||
assert self.data is None and self.rawdata is not None
|
||||
data = self.rawdata
|
||||
if self.decipher:
|
||||
# Handle encryption
|
||||
data = self.decipher(self.objid, self.genno, data)
|
||||
data = self.decipher(self.objid, self.genno, data, self.attrs)
|
||||
filters = self.get_filters()
|
||||
if not filters:
|
||||
self.data = data
|
||||
self.rawdata = None
|
||||
return
|
||||
for f 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')
|
||||
for (f,params) in filters:
|
||||
if f in LITERALS_FLATE_DECODE:
|
||||
# will get errors if the document is encrypted.
|
||||
try:
|
||||
data = zlib.decompress(data)
|
||||
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''
|
||||
elif f in LITERALS_LZW_DECODE:
|
||||
data = lzwdecode(data)
|
||||
|
@ -187,20 +258,18 @@ class PDFStream(PDFObject):
|
|||
data = asciihexdecode(data)
|
||||
elif f in LITERALS_RUNLENGTH_DECODE:
|
||||
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:
|
||||
#data = ccittfaxdecode(data)
|
||||
raise PDFNotImplementedError('Unsupported filter: %r' % f)
|
||||
data = ccittfaxdecode(data, params)
|
||||
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:
|
||||
# not yet..
|
||||
raise PDFNotImplementedError('/Crypt filter is unsupported')
|
||||
else:
|
||||
raise PDFNotImplementedError('Unsupported filter: %r' % f)
|
||||
# apply predictors
|
||||
params = self.get_any(('DP', 'DecodeParms', 'FDecodeParms'), {})
|
||||
if 'Predictor' in params:
|
||||
pred = int_value(params['Predictor'])
|
||||
if pred == 1:
|
||||
|
@ -211,14 +280,12 @@ class PDFStream(PDFObject):
|
|||
colors = int_value(params.get('Colors', 1))
|
||||
columns = int_value(params.get('Columns', 1))
|
||||
bitspercomponent = int_value(params.get('BitsPerComponent', 8))
|
||||
try:
|
||||
data = apply_png_predictor(pred, colors, columns, bitspercomponent, data)
|
||||
except ValueError: # predictor not supported
|
||||
data = b''
|
||||
data = apply_png_predictor(pred, colors, columns, bitspercomponent, data)
|
||||
else:
|
||||
raise PDFNotImplementedError('Unsupported predictor: %r' % pred)
|
||||
self.data = data
|
||||
self.rawdata = None
|
||||
return
|
||||
|
||||
def get_data(self):
|
||||
if self.data is None:
|
||||
|
|
|
@ -1,38 +1,51 @@
|
|||
#!/usr/bin/env python
|
||||
import re
|
||||
import logging
|
||||
|
||||
from .utils import choplist
|
||||
from . import pslexer
|
||||
|
||||
STRICT = False
|
||||
STRICT = 0
|
||||
|
||||
|
||||
## PS Exceptions
|
||||
##
|
||||
class PSException(Exception): pass
|
||||
class PSEOF(PSException): pass
|
||||
class PSSyntaxError(PSException): pass
|
||||
class PSTypeError(PSException): pass
|
||||
class PSValueError(PSException): pass
|
||||
class PSException(Exception):
|
||||
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
|
||||
##
|
||||
|
||||
## PSObject
|
||||
##
|
||||
class PSObject:
|
||||
|
||||
"""Base class for all PS or PDF-related data types."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
## PSLiteral
|
||||
##
|
||||
class PSLiteral(PSObject):
|
||||
|
||||
"""A class that represents a PostScript literal.
|
||||
|
||||
|
||||
Postscript literals are used as identifiers, such as
|
||||
variable names, property names and dictionary keys.
|
||||
Literals are case sensitive and denoted by a preceding
|
||||
|
@ -44,40 +57,47 @@ class PSLiteral(PSObject):
|
|||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return '/%s' % self.name
|
||||
return '/%r' % self.name
|
||||
|
||||
|
||||
## PSKeyword
|
||||
##
|
||||
class PSKeyword(PSObject):
|
||||
|
||||
"""A class that represents a PostScript keyword.
|
||||
|
||||
|
||||
PostScript keywords are a dozen of predefined words.
|
||||
Commands and directives in PostScript are expressed by keywords.
|
||||
They are also used to denote the content boundaries.
|
||||
|
||||
|
||||
Note: Do not create an instance of PSKeyword directly.
|
||||
Always use PSKeywordTable.intern().
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
return
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
return self.name.decode('ascii')
|
||||
|
||||
|
||||
## PSSymbolTable
|
||||
##
|
||||
class PSSymbolTable:
|
||||
|
||||
"""A utility class for storing PSLiteral/PSKeyword objects.
|
||||
|
||||
Interned objects can be checked its identity with "is" operator.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, klass):
|
||||
self.dict = {}
|
||||
self.klass = klass
|
||||
return
|
||||
|
||||
def intern(self, name):
|
||||
if name in self.dict:
|
||||
|
@ -91,145 +111,461 @@ PSLiteralTable = PSSymbolTable(PSLiteral)
|
|||
PSKeywordTable = PSSymbolTable(PSKeyword)
|
||||
LIT = PSLiteralTable.intern
|
||||
KWD = PSKeywordTable.intern
|
||||
KEYWORD_PROC_BEGIN = KWD('{')
|
||||
KEYWORD_PROC_END = KWD('}')
|
||||
KEYWORD_ARRAY_BEGIN = KWD('[')
|
||||
KEYWORD_ARRAY_END = KWD(']')
|
||||
KEYWORD_DICT_BEGIN = KWD('<<')
|
||||
KEYWORD_DICT_END = KWD('>>')
|
||||
KEYWORD_PROC_BEGIN = KWD(b'{')
|
||||
KEYWORD_PROC_END = KWD(b'}')
|
||||
KEYWORD_ARRAY_BEGIN = KWD(b'[')
|
||||
KEYWORD_ARRAY_END = KWD(b']')
|
||||
KEYWORD_DICT_BEGIN = KWD(b'<<')
|
||||
KEYWORD_DICT_END = KWD(b'>>')
|
||||
|
||||
|
||||
def literal_name(x):
|
||||
if not isinstance(x, PSLiteral):
|
||||
handle_error(PSTypeError, 'Literal required: %r' % x)
|
||||
return str(x)
|
||||
if STRICT:
|
||||
raise PSTypeError('Literal required: %r' % (x,))
|
||||
else:
|
||||
return str(x)
|
||||
return x.name
|
||||
|
||||
|
||||
def keyword_name(x):
|
||||
if not isinstance(x, PSKeyword):
|
||||
handle_error(PSTypeError, 'Keyword required: %r' % x)
|
||||
return str(x)
|
||||
if STRICT:
|
||||
raise PSTypeError('Keyword required: %r' % (x,))
|
||||
else:
|
||||
return str(x)
|
||||
return x.name
|
||||
|
||||
|
||||
## About PSParser, bytes and strings and all that
|
||||
##
|
||||
## Most of the contents (well, maybe not in size, but in "parsing effort") of a PDF file is text,
|
||||
## but in some cases, namely streams, there's binary data involved. What we do is that we read the
|
||||
## data as latin-1. When binary data is encountered, we have to re-encode it as latin-1 as well.
|
||||
## PSBaseParser
|
||||
##
|
||||
EOL = re.compile(br'[\r\n]')
|
||||
SPC = re.compile(br'\s')
|
||||
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:
|
||||
|
||||
"""Most basic PostScript parser that performs only tokenization.
|
||||
"""
|
||||
def __init__(self, fp):
|
||||
data = fp.read()
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode('latin-1')
|
||||
self.data = data
|
||||
self.lex = pslexer.lexer.clone()
|
||||
self.lex.input(data)
|
||||
BUFSIZ = 4096
|
||||
|
||||
debug = 0
|
||||
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
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):
|
||||
pass
|
||||
return
|
||||
|
||||
def close(self):
|
||||
self.flush()
|
||||
del self.lex
|
||||
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))
|
||||
|
||||
return
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, fp):
|
||||
PSBaseParser.__init__(self, fp)
|
||||
self.reset()
|
||||
return
|
||||
|
||||
def reset(self):
|
||||
self.context = []
|
||||
self.curtype = None
|
||||
self.curstack = []
|
||||
self.results = []
|
||||
return
|
||||
|
||||
def setpos(self, newpos):
|
||||
PSBaseParser.setpos(self, newpos)
|
||||
def seek(self, pos):
|
||||
PSBaseParser.seek(self, pos)
|
||||
self.reset()
|
||||
return
|
||||
|
||||
def push(self, *objs):
|
||||
self.curstack.extend(objs)
|
||||
|
||||
return
|
||||
|
||||
def pop(self, n):
|
||||
objs = self.curstack[-n:]
|
||||
self.curstack[-n:] = []
|
||||
return objs
|
||||
|
||||
|
||||
def popall(self):
|
||||
objs = self.curstack
|
||||
self.curstack = []
|
||||
return 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)
|
||||
return
|
||||
|
||||
def start_type(self, pos, type):
|
||||
self.context.append((pos, self.curtype, self.curstack))
|
||||
(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):
|
||||
if 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()
|
||||
# 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)
|
||||
|
||||
def do_keyword(self, pos, token):
|
||||
pass
|
||||
return
|
||||
|
||||
def nextobject(self):
|
||||
"""Yields a list of objects.
|
||||
|
@ -239,8 +575,8 @@ class PSStackParser(PSBaseParser):
|
|||
"""
|
||||
while not self.results:
|
||||
(pos, token) = self.nexttoken()
|
||||
#print (pos,token), (self.curtype, self.curstack)
|
||||
if isinstance(token, (int, float, bool, str, bytes, PSLiteral)):
|
||||
#print((pos,token), (self.curtype, self.curstack))
|
||||
if isinstance(token, (int, float, bool, bytes, PSLiteral)):
|
||||
# normal token
|
||||
self.push((pos, token))
|
||||
elif token == KEYWORD_ARRAY_BEGIN:
|
||||
|
@ -250,8 +586,9 @@ class PSStackParser(PSBaseParser):
|
|||
# end array
|
||||
try:
|
||||
self.push(self.end_type('a'))
|
||||
except PSTypeError as e:
|
||||
handle_error(type(e), str(e))
|
||||
except PSTypeError:
|
||||
if STRICT:
|
||||
raise
|
||||
elif token == KEYWORD_DICT_BEGIN:
|
||||
# begin dictionary
|
||||
self.start_type(pos, 'd')
|
||||
|
@ -260,12 +597,13 @@ class PSStackParser(PSBaseParser):
|
|||
try:
|
||||
(pos, objs) = self.end_type('d')
|
||||
if len(objs) % 2 != 0:
|
||||
handle_error(PSSyntaxError, 'Invalid dictionary construct: %r' % objs)
|
||||
raise PSSyntaxError('Invalid dictionary construct: %r' % (objs,))
|
||||
# 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))
|
||||
except PSTypeError as e:
|
||||
handle_error(type(e), str(e))
|
||||
except PSTypeError:
|
||||
if STRICT:
|
||||
raise
|
||||
elif token == KEYWORD_PROC_BEGIN:
|
||||
# begin proc
|
||||
self.start_type(pos, 'p')
|
||||
|
@ -273,15 +611,118 @@ class PSStackParser(PSBaseParser):
|
|||
# end proc
|
||||
try:
|
||||
self.push(self.end_type('p'))
|
||||
except PSTypeError as e:
|
||||
handle_error(type(e), str(e))
|
||||
except PSTypeError:
|
||||
if STRICT:
|
||||
raise
|
||||
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)
|
||||
if self.context:
|
||||
continue
|
||||
else:
|
||||
self.flush()
|
||||
obj = self.results.pop(0)
|
||||
logging.debug('nextobject: %r', obj)
|
||||
if self.debug:
|
||||
logging.debug('nextobject: %r' % (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
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# RunLength decoder (Adobe version) implementation based on PDF Reference
|
||||
# version 1.4 section 3.3.4.
|
||||
|
@ -6,10 +6,8 @@
|
|||
# * public domain *
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
def rldecode(data):
|
||||
"""
|
||||
r"""
|
||||
RunLength decoder (Adobe version) implementation based on PDF Reference
|
||||
version 1.4 section 3.3.4:
|
||||
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
|
||||
(2 to 128) times during decompression. A length value of 128
|
||||
denotes EOD.
|
||||
>>> s = b'\x05123456\xfa7\x04abcde\x80junk'
|
||||
>>> rldecode(s)
|
||||
b'1234567777777abcde'
|
||||
"""
|
||||
decoded = []
|
||||
i=0
|
||||
decoded = b''
|
||||
i = 0
|
||||
while i < len(data):
|
||||
#print "data[%d]=:%d:" % (i,ord(data[i]))
|
||||
length = ord(data[i])
|
||||
#print('data[%d]=:%d:' % (i,ord(data[i])))
|
||||
length = data[i]
|
||||
if length == 128:
|
||||
break
|
||||
if length >= 0 and length < 128:
|
||||
run = data[i+1:(i+1)+(length+1)]
|
||||
#print "length=%d, run=%s" % (length+1,run)
|
||||
decoded.append(run)
|
||||
#print('length=%d, run=%s' % (length+1,run))
|
||||
decoded += run
|
||||
i = (i+1) + (length+1)
|
||||
if length > 128:
|
||||
run = data[i+1]*(257-length)
|
||||
#print "length=%d, run=%s" % (257-length,run)
|
||||
decoded.append(run)
|
||||
run = data[i+1:i+2]*(257-length)
|
||||
#print('length=%d, run=%s' % (257-length,run))
|
||||
decoded += run
|
||||
i = (i+1) + 1
|
||||
return ''.join(decoded)
|
||||
return decoded
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
print('pdfminer.runlength', doctest.testmod())
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Miscellaneous Routines.
|
||||
"""
|
||||
import struct
|
||||
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):
|
||||
if bitspercomponent != 8:
|
||||
# unsupported
|
||||
raise ValueError(bitspercomponent)
|
||||
raise ValueError("Unsupported `bitspercomponent': %d"%bitspercomponent)
|
||||
nbytes = colors*columns*bitspercomponent//8
|
||||
i = 0
|
||||
buf = b''
|
||||
line0 = b'\x00' * columns
|
||||
while i < len(data):
|
||||
pred = data[i]
|
||||
for i in range(0, len(data), nbytes+1):
|
||||
ft = data[i:i+1]
|
||||
i += 1
|
||||
line1 = data[i:i+nbytes]
|
||||
i += nbytes
|
||||
if pred == 0:
|
||||
line2 = b''
|
||||
if ft == b'\x00':
|
||||
# PNG none
|
||||
buf += line1
|
||||
elif pred == 1:
|
||||
line2 += line1
|
||||
elif ft == b'\x01':
|
||||
# PNG sub (UNTESTED)
|
||||
c = 0
|
||||
bufline = []
|
||||
for b in line1:
|
||||
c = (c+b) & 255
|
||||
bufline.append(c)
|
||||
buf += bytes(bufline)
|
||||
elif pred == 2:
|
||||
line2 += bytes([c])
|
||||
elif ft == b'\x02':
|
||||
# PNG up
|
||||
bufline = []
|
||||
for (a,b) in zip(line0,line1):
|
||||
for (a, b) in zip(line0, line1):
|
||||
c = (a+b) & 255
|
||||
bufline.append(c)
|
||||
buf += bytes(bufline)
|
||||
elif pred == 3:
|
||||
line2 += bytes([c])
|
||||
elif ft == b'\x03':
|
||||
# PNG average (UNTESTED)
|
||||
c = 0
|
||||
bufline = []
|
||||
for (a,b) in zip(line0,line1):
|
||||
for (a, b) in zip(line0, line1):
|
||||
c = ((c+a+b)//2) & 255
|
||||
bufline.append(c)
|
||||
buf += bytes(bufline)
|
||||
line2 += bytes([c])
|
||||
else:
|
||||
# unsupported
|
||||
raise ValueError(pred)
|
||||
line0 = line1
|
||||
raise ValueError("Unsupported predictor value: %d"%ft)
|
||||
buf += line2
|
||||
line0 = line2
|
||||
return buf
|
||||
|
||||
|
||||
|
@ -54,44 +53,62 @@ def apply_png_predictor(pred, colors, columns, bitspercomponent, data):
|
|||
##
|
||||
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."""
|
||||
(a1,b1,c1,d1,e1,f1) = matrix1
|
||||
(a0,b0,c0,d0,e0,f0) = matrix2
|
||||
return (a0*a1+c0*b1, b0*a1+d0*b1,
|
||||
a0*c1+c0*d1, b0*c1+d0*d1,
|
||||
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."""
|
||||
(a,b,c,d,e,f) = matrix
|
||||
(x,y) = point
|
||||
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))"""
|
||||
(a,b,c,d,e,f) = matrix
|
||||
(p,q) = norm
|
||||
(a, b, c, d, e, f) = m
|
||||
(p, q) = v
|
||||
return (a*p+c*q, b*p+d*q)
|
||||
|
||||
|
||||
## Utility functions
|
||||
##
|
||||
|
||||
# isnumber
|
||||
def isnumber(x):
|
||||
return isinstance(x, (int, float))
|
||||
|
||||
# uniq
|
||||
def uniq(objs):
|
||||
"""Eliminates duplicated elements."""
|
||||
done = set()
|
||||
for obj in objs:
|
||||
if obj in done: continue
|
||||
if obj in done:
|
||||
continue
|
||||
done.add(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
|
||||
def fsplit(pred, objs):
|
||||
|
@ -103,7 +120,8 @@ def fsplit(pred, objs):
|
|||
t.append(obj)
|
||||
else:
|
||||
f.append(obj)
|
||||
return (t,f)
|
||||
return (t, f)
|
||||
|
||||
|
||||
# drange
|
||||
def drange(v0, v1, d):
|
||||
|
@ -111,16 +129,18 @@ def drange(v0, v1, d):
|
|||
assert v0 < v1
|
||||
return range(int(v0)//d, int(v1+d)//d)
|
||||
|
||||
|
||||
# get_bound
|
||||
def get_bound(pts):
|
||||
"""Compute a minimal rectangle that covers all the points."""
|
||||
(x0, y0, x1, y1) = (INF, INF, -INF, -INF)
|
||||
for (x,y) in pts:
|
||||
for (x, y) in pts:
|
||||
x0 = min(x0, x)
|
||||
y0 = min(y0, y)
|
||||
x1 = max(x1, x)
|
||||
y1 = max(y1, y)
|
||||
return (x0,y0,x1,y1)
|
||||
return (x0, y0, x1, y1)
|
||||
|
||||
|
||||
# pick
|
||||
def pick(seq, func, maxobj=None):
|
||||
|
@ -129,9 +149,10 @@ def pick(seq, func, maxobj=None):
|
|||
for obj in seq:
|
||||
score = func(obj)
|
||||
if maxscore is None or maxscore < score:
|
||||
(maxscore,maxobj) = (score,obj)
|
||||
(maxscore, maxobj) = (score, obj)
|
||||
return maxobj
|
||||
|
||||
|
||||
# choplist
|
||||
def choplist(n, seq):
|
||||
"""Groups every n elements of the list."""
|
||||
|
@ -141,129 +162,172 @@ def choplist(n, seq):
|
|||
if len(r) == n:
|
||||
yield tuple(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
|
||||
def nunpack(b, default=0):
|
||||
def nunpack(s, default=0):
|
||||
"""Unpacks 1 to 4 byte integers (big endian)."""
|
||||
if isinstance(b, str):
|
||||
b = b.encode('latin-1')
|
||||
l = len(b)
|
||||
l = len(s)
|
||||
if not l:
|
||||
return default
|
||||
elif l == 1:
|
||||
return b[0]
|
||||
return s[0]
|
||||
elif l == 2:
|
||||
return struct.unpack(b'>H', b)[0]
|
||||
return struct.unpack('>H', s)[0]
|
||||
elif l == 3:
|
||||
return struct.unpack(b'>L', b'\x00'+b)[0]
|
||||
return struct.unpack('>L', b'\x00'+s)[0]
|
||||
elif l == 4:
|
||||
return struct.unpack(b'>L', b)[0]
|
||||
return struct.unpack('>L', s)[0]
|
||||
else:
|
||||
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'):
|
||||
"""Escapes a string for SGML/XML/HTML"""
|
||||
s = s.replace('&','&').replace('>','>').replace('<','<').replace('"','"')
|
||||
# Additionally to basic replaces, we also make sure that all characters are convertible to our
|
||||
# target encoding. If they're not, they're replaced by XML entities.
|
||||
encoded = s.encode(encoding, errors='xmlcharrefreplace')
|
||||
return encoded.decode(encoding)
|
||||
# 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 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('&','&')
|
||||
.replace('<','<')
|
||||
.replace('>','>')
|
||||
.replace('"','"'))
|
||||
|
||||
def bbox2str(bbox):
|
||||
(x0,y0,x1,y1) = bbox
|
||||
(x0, y0, x1, y1) = bbox
|
||||
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():
|
||||
import logging, sys
|
||||
logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
|
||||
def matrix2str(m):
|
||||
(a, b, c, d, e, f) = m
|
||||
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."
|
||||
|
||||
def __init__(self, start, nobjs):
|
||||
self.start = start
|
||||
self.nobjs = nobjs
|
||||
## Plane
|
||||
##
|
||||
## A set-like data structure for objects placed on a plane.
|
||||
## Can efficiently find objects in a certain rectangular area.
|
||||
## 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):
|
||||
return '<ObjIdRange: %d-%d>' % (self.get_start_id(), self.get_end_id())
|
||||
return ('<Plane objs=%r>' % list(self))
|
||||
|
||||
def get_start_id(self):
|
||||
return self.start
|
||||
def __iter__(self):
|
||||
return ( obj for obj in self._seq if obj in self._objs )
|
||||
|
||||
def get_end_id(self):
|
||||
return self.start + self.nobjs - 1
|
||||
def __len__(self):
|
||||
return len(self._objs)
|
||||
|
||||
def get_nobjs(self):
|
||||
return self.nobjs
|
||||
def __contains__(self, obj):
|
||||
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
|
||||
def create_bmp(data, bits, width, height):
|
||||
info = struct.pack('<IiiHHIIIIII', 40, width, height, 1, bits, 0, len(data), 0, 0, 0, 0)
|
||||
assert len(info) == 40, len(info)
|
||||
header = struct.pack('<ccIHHI', 'B', 'M', 14+40+len(data), 0, 0, 14+40)
|
||||
assert len(header) == 14, len(header)
|
||||
# XXX re-rasterize every line
|
||||
return header+info+data
|
||||
# extend(objs)
|
||||
def extend(self, objs):
|
||||
for obj in objs:
|
||||
self.add(obj)
|
||||
return
|
||||
|
||||
# add(obj): place an object.
|
||||
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
|
||||
|
|
|
@ -1,51 +1,60 @@
|
|||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
|
||||
copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
|
||||
remove-item function:_OLD_VIRTUAL_PROMPT
|
||||
$script:THIS_PATH = $myinvocation.mycommand.path
|
||||
$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
|
||||
|
||||
function global:deactivate([switch] $NonDestructive) {
|
||||
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) {
|
||||
copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
|
||||
remove-item env:_OLD_VIRTUAL_PYTHONHOME
|
||||
if (Test-Path function:_old_virtual_prompt) {
|
||||
$function:prompt = $function:_old_virtual_prompt
|
||||
Remove-Item function:\_old_virtual_prompt
|
||||
}
|
||||
|
||||
if (Test-Path env:_OLD_VIRTUAL_PATH) {
|
||||
copy-item env:_OLD_VIRTUAL_PATH env:PATH
|
||||
remove-item env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
if (Test-Path env:VIRTUAL_ENV) {
|
||||
remove-item env:VIRTUAL_ENV
|
||||
if ($env:VIRTUAL_ENV) {
|
||||
Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (!$NonDestructive) {
|
||||
# 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
|
||||
|
||||
$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) {
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT {""}
|
||||
copy-item function:prompt function:_OLD_VIRTUAL_PROMPT
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green '(venv) '
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
|
||||
|
||||
$env:PATH = "$env:VIRTUAL_ENV/Scripts;" + $env:PATH
|
||||
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
function global:_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"
|
||||
|
|
|
@ -1,34 +1,41 @@
|
|||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
|
||||
if [ "${BASH_SOURCE-}" = "$0" ]; then
|
||||
echo "You must source this script: \$ source $0" >&2
|
||||
exit 33
|
||||
fi
|
||||
|
||||
deactivate () {
|
||||
unset -f pydoc >/dev/null 2>&1 || true
|
||||
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
|
||||
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
|
||||
PATH="$_OLD_VIRTUAL_PATH"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
|
||||
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
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
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
|
||||
PS1="$_OLD_VIRTUAL_PS1"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
if [ ! "${1-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
|
@ -37,7 +44,10 @@ deactivate () {
|
|||
# unset irrelevant variables
|
||||
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
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
|
@ -45,32 +55,29 @@ PATH="$VIRTUAL_ENV/Scripts:$PATH"
|
|||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
if ! [ -z "${PYTHONHOME+_}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
if [ "x(venv) " != x ] ; then
|
||||
PS1="(venv) ${PS1:-}"
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1-}"
|
||||
if [ "x" != x ] ; then
|
||||
PS1="() ${PS1-}"
|
||||
else
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# 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
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
|
||||
fi
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
# Make sure to unalias pydoc if it's already there
|
||||
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
|
||||
|
||||
pydoc () {
|
||||
python -m pydoc "$@"
|
||||
}
|
||||
|
||||
# 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
|
||||
|
|
|
@ -1,45 +1,39 @@
|
|||
@echo off
|
||||
|
||||
rem This file is UTF-8 encoded, so we need to update the current code page while executing it
|
||||
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"
|
||||
)
|
||||
set "VIRTUAL_ENV=C:\Users\cheri\Documents\myclass.com\Siteweb\Production\Ela_back\MySy_Back_Office\venv"
|
||||
|
||||
if defined _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 (
|
||||
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%"
|
||||
|
||||
:END
|
||||
if defined _OLD_CODEPAGE (
|
||||
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
|
||||
set "_OLD_CODEPAGE="
|
||||
)
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
@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=
|
||||
|
||||
: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
|
||||
|
|
|
@ -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.
|
@ -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>
|
|
@ -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.
|
@ -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.
|
@ -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
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue