pdfminer.six/pdfminer/cmapdb.py

416 lines
11 KiB
Python
Raw Normal View History

2013-10-17 14:05:27 +00:00
#!/usr/bin/env python
""" Adobe character mapping (CMap) support.
CMaps provide the mapping between character codes and Unicode
code-points to character ids (CIDs).
More information is available on the Adobe website:
http://opensource.adobe.com/wiki/display/cmap/CMap+Resources
"""
import sys
import os
import os.path
import gzip
import cPickle as pickle
2011-03-02 14:43:03 +00:00
import struct
from psparser import PSStackParser
from psparser import PSSyntaxError, PSEOF
from psparser import PSLiteral
from psparser import literal_name
from encodingdb import name2unicode
from utils import choplist, nunpack
2013-11-07 08:35:04 +00:00
class CMapError(Exception):
pass
## CMapBase
##
class CMapBase(object):
debug = 0
def __init__(self, **kwargs):
self.attrs = kwargs.copy()
return
def is_vertical(self):
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)
2013-11-07 08:35:04 +00:00
def copy(dst, src):
2013-11-07 08:35:04 +00:00
for (k, v) in src.iteritems():
if isinstance(v, dict):
d = {}
dst[k] = d
copy(d, v)
else:
dst[k] = v
copy(self.code2cid, cmap.code2cid)
return
def decode(self, code):
if self.debug:
print >>sys.stderr, 'decode: %r, %r' % (self, code)
d = self.code2cid
for c in code:
c = ord(c)
if c in d:
d = d[c]
if isinstance(d, int):
yield d
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 = ()
2013-11-07 08:35:04 +00:00
for (k, v) in sorted(code2cid.iteritems()):
c = code+(k,)
if isinstance(v, int):
2013-11-07 08:35:04 +00:00
out.write('code %r = cid %d\n' % (c, v))
else:
self.dump(out=out, code2cid=v, code=c)
return
2013-11-07 07:14:53 +00:00
## IdentityCMap
##
class IdentityCMap(CMapBase):
def decode(self, code):
2013-11-26 12:35:16 +00:00
n = len(code)//2
if n:
2011-03-02 14:43:03 +00:00
return struct.unpack('>%dH' % n, code)
else:
return ()
2013-11-07 07:14:53 +00:00
## 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):
if self.debug:
print >>sys.stderr, 'get_unichr: %r, %r' % (self, cid)
return self.cid2unichr[cid]
def dump(self, out=sys.stdout):
2013-11-07 08:35:04 +00:00
for (k, v) in sorted(self.cid2unichr.iteritems()):
out.write('cid %d = unicode %r\n' % (k, v))
return
## FileCMap
##
class FileCMap(CMap):
def add_code2cid(self, code, cid):
assert isinstance(code, str) and isinstance(cid, int)
d = self.code2cid
for c in code[:-1]:
c = ord(c)
if c in d:
d = d[c]
else:
t = {}
d[c] = t
2013-11-07 08:35:04 +00:00
d = t
c = ord(code[-1])
d[c] = cid
return
## FileUnicodeMap
##
class FileUnicodeMap(UnicodeMap):
2013-11-07 07:14:53 +00:00
def add_cid2unichr(self, cid, code):
assert isinstance(cid, int)
if isinstance(code, PSLiteral):
# Interpret as an Adobe glyph name.
self.cid2unichr[cid] = name2unicode(code.name)
elif isinstance(code, str):
# Interpret as UTF-16BE.
self.cid2unichr[cid] = unicode(code, 'UTF-16BE', 'ignore')
elif isinstance(code, int):
self.cid2unichr[cid] = unichr(code)
else:
raise TypeError(code)
return
## PyCMap
##
class PyCMap(CMap):
def __init__(self, name, module):
CMap.__init__(self, CMapName=name)
self.code2cid = module.CODE2CID
if module.IS_VERTICAL:
self.attrs['WMode'] = 1
return
## PyUnicodeMap
##
class PyUnicodeMap(UnicodeMap):
2013-11-07 07:14:53 +00:00
def __init__(self, name, module, vertical):
UnicodeMap.__init__(self, CMapName=name)
if vertical:
self.cid2unichr = module.CID2UNICHR_V
self.attrs['WMode'] = 1
else:
self.cid2unichr = module.CID2UNICHR_H
return
## CMapDB
##
class CMapDB(object):
debug = 0
_cmap_cache = {}
_umap_cache = {}
2013-11-07 07:14:53 +00:00
2013-11-07 08:35:04 +00:00
class CMapNotFound(CMapError):
pass
@classmethod
def _load_data(klass, name):
filename = '%s.pickle.gz' % name
if klass.debug:
print >>sys.stderr, 'loading:', name
2013-11-07 08:35:04 +00:00
cmap_paths = (os.environ.get('CMAP_PATH', '/usr/share/pdfminer/'),
os.path.join(os.path.dirname(__file__), 'cmap'),)
2011-07-31 08:05:07 +00:00
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()))
finally:
gzfile.close()
else:
raise CMapDB.CMapNotFound(name)
@classmethod
def get_cmap(klass, name):
if name == 'Identity-H':
return IdentityCMap(WMode=0)
elif name == 'Identity-V':
return IdentityCMap(WMode=1)
try:
return klass._cmap_cache[name]
except KeyError:
pass
data = klass._load_data(name)
klass._cmap_cache[name] = cmap = PyCMap(name, data)
return cmap
@classmethod
def get_unicode_map(klass, name, vertical=False):
try:
return klass._umap_cache[name][vertical]
except KeyError:
pass
data = klass._load_data('to-unicode-%s' % name)
klass._umap_cache[name] = umaps = [PyUnicodeMap(name, data, v) for v in (False, True)]
return umaps[vertical]
## CMapParser
##
class CMapParser(PSStackParser):
def __init__(self, cmap, fp):
PSStackParser.__init__(self, fp)
self.cmap = cmap
2013-10-17 12:40:43 +00:00
# some ToUnicode maps don't have "begincmap" keyword.
self._in_cmap = True
return
def run(self):
try:
self.nextobject()
except PSEOF:
pass
return
def do_keyword(self, pos, token):
name = token.name
if name == 'begincmap':
self._in_cmap = True
self.popall()
return
elif name == 'endcmap':
self._in_cmap = False
return
2013-11-07 08:35:04 +00:00
if not self._in_cmap:
return
#
if name == 'def':
try:
2013-11-07 08:35:04 +00:00
((_, k), (_, v)) = self.pop(2)
self.cmap.set_attr(literal_name(k), v)
except PSSyntaxError:
pass
return
if name == 'usecmap':
try:
2013-11-07 08:35:04 +00:00
((_, cmapname),) = self.pop(1)
self.cmap.use_cmap(CMapDB.get_cmap(literal_name(cmapname)))
except PSSyntaxError:
pass
except CMapDB.CMapNotFound:
pass
return
if name == 'begincodespacerange':
self.popall()
return
if name == 'endcodespacerange':
self.popall()
return
if name == 'begincidrange':
self.popall()
return
if name == 'endcidrange':
2013-11-07 08:35:04 +00:00
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
2013-11-07 08:35:04 +00:00
not isinstance(cid, int) or len(s) != len(e)):
continue
sprefix = s[:-4]
eprefix = e[:-4]
2013-11-07 08:35:04 +00:00
if sprefix != eprefix:
continue
svar = s[-4:]
evar = e[-4:]
s1 = nunpack(svar)
e1 = nunpack(evar)
vlen = len(svar)
#assert s1 <= e1
for i in xrange(e1-s1+1):
2013-11-07 08:35:04 +00:00
x = sprefix+struct.pack('>L', s1+i)[-vlen:]
self.cmap.add_code2cid(x, cid+i)
return
if name == 'begincidchar':
self.popall()
return
if name == 'endcidchar':
2013-11-07 08:35:04 +00:00
objs = [obj for (__, obj) in self.popall()]
for (cid, code) in choplist(2, objs):
if isinstance(code, str) and isinstance(cid, str):
self.cmap.add_code2cid(code, nunpack(cid))
return
if name == 'beginbfrange':
self.popall()
return
if name == 'endbfrange':
2013-11-07 08:35:04 +00:00
objs = [obj for (__, obj) in self.popall()]
for (s, e, code) in choplist(3, objs):
if (not isinstance(s, str) or not isinstance(e, str) or
2013-11-07 08:35:04 +00:00
len(s) != len(e)):
continue
s1 = nunpack(s)
e1 = nunpack(e)
#assert s1 <= e1
if isinstance(code, list):
for i in xrange(e1-s1+1):
self.cmap.add_cid2unichr(s1+i, code[i])
else:
var = code[-4:]
base = nunpack(var)
prefix = code[:-4]
vlen = len(var)
for i in xrange(e1-s1+1):
2013-11-07 08:35:04 +00:00
x = prefix+struct.pack('>L', base+i)[-vlen:]
self.cmap.add_cid2unichr(s1+i, x)
return
if name == 'beginbfchar':
self.popall()
return
if name == 'endbfchar':
2013-11-07 08:35:04 +00:00
objs = [obj for (__, obj) in self.popall()]
for (cid, code) in choplist(2, objs):
if isinstance(cid, str) and isinstance(code, str):
self.cmap.add_cid2unichr(nunpack(cid), code)
return
if name == 'beginnotdefrange':
self.popall()
return
if name == 'endnotdefrange':
self.popall()
return
self.push((pos, token))
return
2013-11-07 08:35:04 +00:00
# test
def main(argv):
args = argv[1:]
for fname in args:
fp = file(fname, 'rb')
cmap = FileUnicodeMap()
#cmap = FileCMap()
CMapParser(cmap, fp).run()
fp.close()
cmap.dump()
return
2013-11-07 08:35:04 +00:00
if __name__ == '__main__':
sys.exit(main(sys.argv))