major refactoring, now evaluates everything lazily, so it should be really fast if you only need (say) the glyph order.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@241 4cde692c-a291-49d1-8350-778aa11640f8
This commit is contained in:
parent
357d71b438
commit
4756b3a040
@ -1,7 +1,7 @@
|
|||||||
"""cffLib.py -- read/write tools for Adobe CFF fonts."""
|
"""cffLib.py -- read/write tools for Adobe CFF fonts."""
|
||||||
|
|
||||||
#
|
#
|
||||||
# $Id: cffLib.py,v 1.12 2002-05-15 07:41:30 jvr Exp $
|
# $Id: cffLib.py,v 1.13 2002-05-16 18:17:32 jvr Exp $
|
||||||
#
|
#
|
||||||
|
|
||||||
import struct, sstruct
|
import struct, sstruct
|
||||||
@ -20,36 +20,34 @@ cffHeaderFormat = """
|
|||||||
class CFFFontSet:
|
class CFFFontSet:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.fonts = {}
|
pass
|
||||||
|
|
||||||
def decompile(self, file):
|
def decompile(self, file):
|
||||||
sstruct.unpack(cffHeaderFormat, file.read(4), self)
|
sstruct.unpack(cffHeaderFormat, file.read(4), self)
|
||||||
assert self.major == 1 and self.minor == 0, \
|
assert self.major == 1 and self.minor == 0, \
|
||||||
"unknown CFF format: %d.%d" % (self.major, self.minor)
|
"unknown CFF format: %d.%d" % (self.major, self.minor)
|
||||||
|
|
||||||
self.fontNames = readINDEX(file)
|
self.fontNames = list(Index(file))
|
||||||
topDicts = readINDEX(file)
|
self.topDictIndex = TopDictIndex(file)
|
||||||
strings = IndexedStrings(readINDEX(file))
|
self.strings = IndexedStrings(list(Index(file)))
|
||||||
globalSubrs = readINDEX(file)
|
self.GlobalSubrs = SubrsIndex(file)
|
||||||
|
self.topDictIndex.strings = self.strings
|
||||||
|
|
||||||
file.seek(4)
|
def __len__(self):
|
||||||
xfnames = Index(file)
|
return len(self.fontNames)
|
||||||
xtopds = Index(file)
|
|
||||||
xstrings = Index(file)
|
|
||||||
xgsubrs = Index(file)
|
|
||||||
|
|
||||||
assert xfnames.toList() == self.fontNames
|
def keys(self):
|
||||||
assert xtopds.toList() == topDicts
|
return self.fontNames[:]
|
||||||
assert xstrings.toList() == strings.strings
|
|
||||||
assert xgsubrs.toList() == globalSubrs
|
|
||||||
|
|
||||||
self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
|
def __getitem__(self, name):
|
||||||
|
try:
|
||||||
for i in range(len(topDicts)):
|
index = self.fontNames.index(name)
|
||||||
font = self.fonts[self.fontNames[i]] = CFFFont()
|
except ValueError:
|
||||||
|
raise KeyError, name
|
||||||
|
font = self.topDictIndex[index]
|
||||||
|
if not hasattr(font, "GlobalSubrs"):
|
||||||
font.GlobalSubrs = self.GlobalSubrs
|
font.GlobalSubrs = self.GlobalSubrs
|
||||||
file.seek(0)
|
return font
|
||||||
font.decompile(file, topDicts[i], strings, self)
|
|
||||||
|
|
||||||
def compile(self):
|
def compile(self):
|
||||||
strings = IndexedStrings()
|
strings = IndexedStrings()
|
||||||
@ -60,21 +58,14 @@ class CFFFontSet:
|
|||||||
for fontName in self.fontNames:
|
for fontName in self.fontNames:
|
||||||
xmlWriter.begintag("CFFFont", name=fontName)
|
xmlWriter.begintag("CFFFont", name=fontName)
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
font = self.fonts[fontName]
|
font = self[fontName]
|
||||||
font.toXML(xmlWriter, progress)
|
font.toXML(xmlWriter, progress)
|
||||||
xmlWriter.endtag("CFFFont")
|
xmlWriter.endtag("CFFFont")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
xmlWriter.begintag("GlobalSubrs")
|
xmlWriter.begintag("GlobalSubrs")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
for i in range(len(self.GlobalSubrs)):
|
self.GlobalSubrs.toXML(xmlWriter, progress)
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.begintag("CharString", index=i)
|
|
||||||
xmlWriter.newline()
|
|
||||||
self.GlobalSubrs[i].toXML(xmlWriter)
|
|
||||||
xmlWriter.endtag("CharString")
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.endtag("GlobalSubrs")
|
xmlWriter.endtag("GlobalSubrs")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
@ -83,102 +74,347 @@ class CFFFontSet:
|
|||||||
xxx
|
xxx
|
||||||
|
|
||||||
|
|
||||||
class CFFFont:
|
class Index:
|
||||||
|
|
||||||
def __init__(self):
|
"""This class represents what the CFF spec calls an INDEX."""
|
||||||
pass
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __init__(self, file):
|
||||||
if not topDictDefaults.has_key(attr):
|
self.file = file
|
||||||
raise AttributeError, attr
|
count, = struct.unpack(">H", file.read(2))
|
||||||
return topDictDefaults[attr]
|
self.count = count
|
||||||
|
self.items = [None] * count
|
||||||
|
if count == 0:
|
||||||
|
self.offsets = []
|
||||||
|
return
|
||||||
|
offSize = ord(file.read(1))
|
||||||
|
self.offsets = offsets = []
|
||||||
|
pad = '\0' * (4 - offSize)
|
||||||
|
for index in range(count+1):
|
||||||
|
chunk = file.read(offSize)
|
||||||
|
chunk = pad + chunk
|
||||||
|
offset, = struct.unpack(">L", chunk)
|
||||||
|
offsets.append(int(offset))
|
||||||
|
self.offsetBase = file.tell() - 1
|
||||||
|
file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
|
||||||
|
|
||||||
def fromDict(self, d):
|
def __len__(self):
|
||||||
self.__dict__.update(d)
|
return self.count
|
||||||
|
|
||||||
def decompile(self, file, topDictData, strings, fontSet):
|
def __getitem__(self, index):
|
||||||
top = TopDictDecompiler(strings)
|
item = self.items[index]
|
||||||
top.decompile(topDictData)
|
if item is not None:
|
||||||
self.fromDict(top.getDict())
|
return item
|
||||||
|
offset = self.offsets[index] + self.offsetBase
|
||||||
if hasattr(self, "ROS"):
|
size = self.offsets[index+1] - self.offsets[index]
|
||||||
isCID = 1
|
file = self.file
|
||||||
# XXX CID subFonts
|
|
||||||
offset = self.FDArray
|
|
||||||
file.seek(offset)
|
file.seek(offset)
|
||||||
fontDicts = readINDEX(file)
|
data = file.read(size)
|
||||||
subFonts = self.subFonts = []
|
assert len(data) == size
|
||||||
for topDictData in fontDicts:
|
item = self.produceItem(data, file, offset, size)
|
||||||
subFont = CFFFont()
|
self.items[index] = item
|
||||||
subFonts.append(subFont)
|
return item
|
||||||
subFont.decompile(file, topDictData, strings, None)
|
|
||||||
# XXX
|
|
||||||
else:
|
|
||||||
isCID = 0
|
|
||||||
size, offset = self.Private
|
|
||||||
file.seek(offset)
|
|
||||||
privateData = file.read(size)
|
|
||||||
file.seek(offset)
|
|
||||||
assert len(privateData) == size
|
|
||||||
self.Private = PrivateDict()
|
|
||||||
self.Private.decompile(file, privateData, strings)
|
|
||||||
|
|
||||||
if hasattr(self, "CharStrings"):
|
def produceItem(self, data, file, offset, size):
|
||||||
file.seek(self.CharStrings)
|
return data
|
||||||
rawCharStrings = Index(file)
|
|
||||||
nGlyphs = len(rawCharStrings)
|
|
||||||
|
|
||||||
# get charset (or rather: get glyphNames)
|
|
||||||
if self.charset > 2:
|
class SubrsIndex(Index):
|
||||||
file.seek(self.charset)
|
|
||||||
|
def produceItem(self, data, file, offset, size):
|
||||||
|
return psCharStrings.T2CharString(data)
|
||||||
|
|
||||||
|
def toXML(self, xmlWriter, progress):
|
||||||
|
for i in range(len(self)):
|
||||||
|
xmlWriter.begintag("CharString", index=i)
|
||||||
|
xmlWriter.newline()
|
||||||
|
self[i].toXML(xmlWriter)
|
||||||
|
xmlWriter.endtag("CharString")
|
||||||
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
|
class CharStrings:
|
||||||
|
|
||||||
|
def __init__(self, file, charset):
|
||||||
|
self.charStringsIndex = SubrsIndex(file)
|
||||||
|
self.nameToIndex = nameToIndex = {}
|
||||||
|
for i in range(len(charset)):
|
||||||
|
nameToIndex[charset[i]] = i
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.nameToIndex.keys()
|
||||||
|
|
||||||
|
def has_key(self, name):
|
||||||
|
return self.nameToIndex.has_key(name)
|
||||||
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
index = self.nameToIndex[name]
|
||||||
|
return self.charStringsIndex[index]
|
||||||
|
|
||||||
|
def toXML(self, xmlWriter, progress):
|
||||||
|
names = self.keys()
|
||||||
|
names.sort()
|
||||||
|
for name in names:
|
||||||
|
xmlWriter.begintag("CharString", name=name)
|
||||||
|
xmlWriter.newline()
|
||||||
|
self[name].toXML(xmlWriter)
|
||||||
|
xmlWriter.endtag("CharString")
|
||||||
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
|
class TopDictIndex(Index):
|
||||||
|
def produceItem(self, data, file, offset, size):
|
||||||
|
top = TopDict(self.strings, file, offset)
|
||||||
|
top.decompile(data)
|
||||||
|
return top
|
||||||
|
|
||||||
|
|
||||||
|
def buildOperatorDict(table):
|
||||||
|
d = {}
|
||||||
|
for op, name, arg, default, conv in table:
|
||||||
|
d[op] = (name, arg)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def buildOrder(table):
|
||||||
|
l = []
|
||||||
|
for op, name, arg, default, conv in table:
|
||||||
|
l.append(name)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def buildDefaults(table):
|
||||||
|
d = {}
|
||||||
|
for op, name, arg, default, conv in table:
|
||||||
|
if default is not None:
|
||||||
|
d[name] = default
|
||||||
|
return d
|
||||||
|
|
||||||
|
def buildConverters(table):
|
||||||
|
d = {}
|
||||||
|
for op, name, arg, default, conv in table:
|
||||||
|
d[name] = conv
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateDictConverter:
|
||||||
|
def read(self, parent, value):
|
||||||
|
size, offset = value
|
||||||
|
file = parent.file
|
||||||
|
pr = PrivateDict(parent.strings, file, offset)
|
||||||
|
file.seek(offset)
|
||||||
|
data = file.read(size)
|
||||||
|
len(data) == size
|
||||||
|
pr.decompile(data)
|
||||||
|
return pr
|
||||||
|
def xmlWrite(self, xmlWriter, name, value):
|
||||||
|
xmlWriter.begintag(name)
|
||||||
|
xmlWriter.newline()
|
||||||
|
value.toXML(xmlWriter, None)
|
||||||
|
xmlWriter.endtag(name)
|
||||||
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
class SubrsConverter(PrivateDictConverter):
|
||||||
|
def read(self, parent, value):
|
||||||
|
file = parent.file
|
||||||
|
file.seek(parent.offset + value) # Offset(self)
|
||||||
|
return SubrsIndex(file)
|
||||||
|
|
||||||
|
class CharStringsConverter(PrivateDictConverter):
|
||||||
|
def read(self, parent, value):
|
||||||
|
file = parent.file
|
||||||
|
file.seek(value) # Offset(0)
|
||||||
|
return CharStrings(file, parent.charset)
|
||||||
|
|
||||||
|
class CharsetConverter:
|
||||||
|
def read(self, parent, value):
|
||||||
|
isCID = hasattr(parent, "ROS")
|
||||||
|
if value > 2:
|
||||||
|
numGlyphs = parent.numGlyphs
|
||||||
|
file = parent.file
|
||||||
|
file.seek(value)
|
||||||
format = ord(file.read(1))
|
format = ord(file.read(1))
|
||||||
if format == 0:
|
if format == 0:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
elif format == 1:
|
elif format == 1 or format == 2:
|
||||||
charset = parseCharsetFormat1(nGlyphs, file, strings, isCID)
|
charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
|
||||||
elif format == 2:
|
|
||||||
charset = parseCharsetFormat2(nGlyphs, file, strings, isCID)
|
|
||||||
elif format == 3:
|
elif format == 3:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
self.charset = charset
|
assert len(charset) == numGlyphs
|
||||||
assert len(charset) == nGlyphs
|
|
||||||
else:
|
else:
|
||||||
|
if isCID:
|
||||||
|
assert value == 0
|
||||||
|
charset = None
|
||||||
|
elif value == 0:
|
||||||
|
charset = ISOAdobe
|
||||||
|
elif value == 1:
|
||||||
|
charset = Expert
|
||||||
|
elif value == 2:
|
||||||
|
charset = ExpertSubset
|
||||||
# self.charset:
|
# self.charset:
|
||||||
# 0: ISOAdobe (or CID font!)
|
# 0: ISOAdobe (or CID font!)
|
||||||
# 1: Expert
|
# 1: Expert
|
||||||
# 2: ExpertSubset
|
# 2: ExpertSubset
|
||||||
|
charset = None #
|
||||||
|
return charset
|
||||||
|
def xmlWrite(self, xmlWriter, name, value):
|
||||||
|
# XXX GlyphOrder needs to be stored *somewhere*, but not here...
|
||||||
|
xmlWriter.simpletag("charset", value=value)
|
||||||
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
|
def parseCharset(numGlyphs, file, strings, isCID, format):
|
||||||
|
charset = ['.notdef']
|
||||||
|
count = 1
|
||||||
|
if format == 1:
|
||||||
|
def nLeftFunc(file):
|
||||||
|
return ord(file.read(1))
|
||||||
|
else:
|
||||||
|
def nLeftFunc(file):
|
||||||
|
return struct.unpack(">H", file.read(2))[0]
|
||||||
|
while count < numGlyphs:
|
||||||
|
first, = struct.unpack(">H", file.read(2))
|
||||||
|
nLeft = nLeftFunc(file)
|
||||||
|
if isCID:
|
||||||
|
for CID in range(first, first+nLeft+1):
|
||||||
|
charset.append(CID)
|
||||||
|
else:
|
||||||
|
for SID in range(first, first+nLeft+1):
|
||||||
|
charset.append(strings[SID])
|
||||||
|
count = count + nLeft + 1
|
||||||
|
return charset
|
||||||
|
|
||||||
|
|
||||||
|
topDictOperators = [
|
||||||
|
# opcode name argument type default converter
|
||||||
|
(0, 'version', 'SID', None, None),
|
||||||
|
(1, 'Notice', 'SID', None, None),
|
||||||
|
((12, 0), 'Copyright', 'SID', None, None),
|
||||||
|
(2, 'FullName', 'SID', None, None),
|
||||||
|
(3, 'FamilyName', 'SID', None, None),
|
||||||
|
(4, 'Weight', 'SID', None, None),
|
||||||
|
((12, 1), 'isFixedPitch', 'number', 0, None),
|
||||||
|
((12, 2), 'ItalicAngle', 'number', 0, None),
|
||||||
|
((12, 3), 'UnderlinePosition', 'number', None, None),
|
||||||
|
((12, 4), 'UnderlineThickness', 'number', 50, None),
|
||||||
|
((12, 5), 'PaintType', 'number', 0, None),
|
||||||
|
((12, 6), 'CharstringType', 'number', 2, None),
|
||||||
|
((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None),
|
||||||
|
(13, 'UniqueID', 'number', None, None),
|
||||||
|
(5, 'FontBBox', 'array', [0,0,0,0], None),
|
||||||
|
((12, 8), 'StrokeWidth', 'number', 0, None),
|
||||||
|
(14, 'XUID', 'array', None, None),
|
||||||
|
(15, 'charset', 'number', 0, CharsetConverter()),
|
||||||
|
(16, 'Encoding', 'number', 0, None),
|
||||||
|
(18, 'Private', ('number','number'), None, PrivateDictConverter()),
|
||||||
|
(17, 'CharStrings', 'number', None, CharStringsConverter()), # XXX
|
||||||
|
((12, 20), 'SyntheticBase', 'number', None, None),
|
||||||
|
((12, 21), 'PostScript', 'SID', None, None),
|
||||||
|
((12, 22), 'BaseFontName', 'SID', None, None),
|
||||||
|
((12, 23), 'BaseFontBlend', 'delta', None, None),
|
||||||
|
((12, 30), 'ROS', ('SID','SID','number'), None, None),
|
||||||
|
((12, 31), 'CIDFontVersion', 'number', 0, None),
|
||||||
|
((12, 32), 'CIDFontRevision', 'number', 0, None),
|
||||||
|
((12, 33), 'CIDFontType', 'number', 0, None),
|
||||||
|
((12, 34), 'CIDCount', 'number', 8720, None),
|
||||||
|
((12, 35), 'UIDBase', 'number', None, None),
|
||||||
|
((12, 36), 'FDArray', 'number', None, None),
|
||||||
|
((12, 37), 'FDSelect', 'number', None, None),
|
||||||
|
((12, 38), 'FontName', 'SID', None, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
privateDictOperators = [
|
||||||
|
# opcode name argument type default converter
|
||||||
|
(6, 'BlueValues', 'delta', None, None),
|
||||||
|
(7, 'OtherBlues', 'delta', None, None),
|
||||||
|
(8, 'FamilyBlues', 'delta', None, None),
|
||||||
|
(9, 'FamilyOtherBlues', 'delta', None, None),
|
||||||
|
((12, 9), 'BlueScale', 'number', 0.039625, None),
|
||||||
|
((12, 10), 'BlueShift', 'number', 7, None),
|
||||||
|
((12, 11), 'BlueFuzz', 'number', 1, None),
|
||||||
|
(10, 'StdHW', 'number', None, None),
|
||||||
|
(11, 'StdVW', 'number', None, None),
|
||||||
|
((12, 12), 'StemSnapH', 'delta', None, None),
|
||||||
|
((12, 13), 'StemSnapV', 'delta', None, None),
|
||||||
|
((12, 14), 'ForceBold', 'number', 0, None),
|
||||||
|
((12, 17), 'LanguageGroup', 'number', 0, None),
|
||||||
|
((12, 18), 'ExpansionFactor', 'number', 0.06, None),
|
||||||
|
((12, 19), 'initialRandomSeed', 'number', 0, None),
|
||||||
|
(20, 'defaultWidthX', 'number', 0, None),
|
||||||
|
(21, 'nominalWidthX', 'number', 0, None),
|
||||||
|
(19, 'Subrs', 'number', None, SubrsConverter()),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TopDictDecompiler(psCharStrings.DictDecompiler):
|
||||||
|
operators = buildOperatorDict(topDictOperators)
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateDictDecompiler(psCharStrings.DictDecompiler):
|
||||||
|
operators = buildOperatorDict(privateDictOperators)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDict:
|
||||||
|
|
||||||
|
def __init__(self, strings, file, offset):
|
||||||
|
self.rawDict = {}
|
||||||
|
self.file = file
|
||||||
|
self.offset = offset
|
||||||
|
self.strings = strings
|
||||||
|
|
||||||
|
def decompile(self, data):
|
||||||
|
dec = self.decompiler(self.strings)
|
||||||
|
dec.decompile(data)
|
||||||
|
self.rawDict = dec.getDict()
|
||||||
|
self.postDecompile()
|
||||||
|
|
||||||
|
def postDecompile(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if hasattr(self, "CharStrings"):
|
def __getattr__(self, name):
|
||||||
self.CharStrings = charStrings = {}
|
value = self.rawDict.get(name)
|
||||||
if self.CharstringType == 2:
|
if value is None:
|
||||||
# Type 2 CharStrings
|
value = self.defaults.get(name)
|
||||||
charStringClass = psCharStrings.T2CharString
|
if value is None:
|
||||||
else:
|
raise AttributeError, name
|
||||||
# Type 1 CharStrings
|
conv = self.converters[name]
|
||||||
charStringClass = psCharStrings.T1CharString
|
if conv is not None:
|
||||||
for i in range(nGlyphs):
|
value = conv.read(self, value)
|
||||||
charStrings[charset[i]] = charStringClass(rawCharStrings[i])
|
setattr(self, name, value)
|
||||||
assert len(charStrings) == nGlyphs
|
return value
|
||||||
|
|
||||||
encoding = self.Encoding
|
def toXML(self, xmlWriter, progress):
|
||||||
if encoding > 1:
|
for name in self.order:
|
||||||
# encoding is an offset from the beginning of 'data' to an encoding subtable
|
value = getattr(self, name, None)
|
||||||
raise NotImplementedError
|
if value is None:
|
||||||
self.Encoding = encoding
|
continue
|
||||||
|
conv = self.converters.get(name)
|
||||||
|
if conv is not None:
|
||||||
|
conv.xmlWrite(xmlWriter, name, value)
|
||||||
else:
|
else:
|
||||||
# self.Encoding:
|
if isinstance(value, types.ListType):
|
||||||
# 0 Standard Encoding
|
value = " ".join(map(str, value))
|
||||||
# 1 Expert Encoding
|
xmlWriter.simpletag(name, value=value)
|
||||||
pass
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
|
class TopDict(BaseDict):
|
||||||
|
|
||||||
|
defaults = buildDefaults(topDictOperators)
|
||||||
|
converters = buildConverters(topDictOperators)
|
||||||
|
order = buildOrder(topDictOperators)
|
||||||
|
decompiler = TopDictDecompiler
|
||||||
|
|
||||||
def getGlyphOrder(self):
|
def getGlyphOrder(self):
|
||||||
return self.charset
|
return self.charset
|
||||||
|
|
||||||
def setGlyphOrder(self, glyphOrder):
|
def postDecompile(self):
|
||||||
self.charset = glyphOrder
|
offset = self.rawDict.get("CharStrings")
|
||||||
|
if offset is None:
|
||||||
|
return
|
||||||
|
# get the number of glyphs beforehand.
|
||||||
|
self.file.seek(offset)
|
||||||
|
self.numGlyphs, = struct.unpack(">H", self.file.read(2))
|
||||||
|
|
||||||
def decompileAllCharStrings(self):
|
def decompileAllCharStrings(self):
|
||||||
if self.CharstringType == 2:
|
if self.CharstringType == 2:
|
||||||
@ -193,300 +429,16 @@ class CFFFont:
|
|||||||
for charString in self.CharStrings.values():
|
for charString in self.CharStrings.values():
|
||||||
charString.decompile()
|
charString.decompile()
|
||||||
|
|
||||||
def toXML(self, xmlWriter, progress=None):
|
|
||||||
xmlWriter.newline()
|
class PrivateDict(BaseDict):
|
||||||
genericToXML(self, topDictOrder, {'CharStrings': 'CharString'}, xmlWriter)
|
defaults = buildDefaults(privateDictOperators)
|
||||||
|
converters = buildConverters(privateDictOperators)
|
||||||
|
order = buildOrder(privateDictOperators)
|
||||||
|
decompiler = PrivateDictDecompiler
|
||||||
|
|
||||||
|
|
||||||
class PrivateDict:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def decompile(self, file, privateData, strings):
|
|
||||||
p = PrivateDictDecompiler(strings)
|
|
||||||
p.decompile(privateData)
|
|
||||||
self.fromDict(p.getDict())
|
|
||||||
|
|
||||||
# get local subrs
|
|
||||||
if hasattr(self, "Subrs"):
|
|
||||||
file.seek(self.Subrs, 1)
|
|
||||||
localSubrs = Index(file)
|
|
||||||
self.Subrs = map(psCharStrings.T2CharString, localSubrs)
|
|
||||||
else:
|
|
||||||
self.Subrs = []
|
|
||||||
|
|
||||||
def toXML(self, xmlWriter):
|
|
||||||
xmlWriter.newline()
|
|
||||||
genericToXML(self, privateDictOrder, {'Subrs': 'CharString'}, xmlWriter)
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if not privateDictDefaults.has_key(attr):
|
|
||||||
raise AttributeError, attr
|
|
||||||
return privateDictDefaults[attr]
|
|
||||||
|
|
||||||
def fromDict(self, d):
|
|
||||||
self.__dict__.update(d)
|
|
||||||
|
|
||||||
|
|
||||||
def readINDEX(file):
|
|
||||||
count, = struct.unpack(">H", file.read(2))
|
|
||||||
if count == 0:
|
|
||||||
return []
|
|
||||||
offSize = ord(file.read(1))
|
|
||||||
offsets = []
|
|
||||||
for index in range(count+1):
|
|
||||||
chunk = file.read(offSize)
|
|
||||||
chunk = '\0' * (4 - offSize) + chunk
|
|
||||||
offset, = struct.unpack(">L", chunk)
|
|
||||||
offset = int(offset)
|
|
||||||
offsets.append(offset)
|
|
||||||
offsetBase = file.tell() - 1
|
|
||||||
prev = offsets[0]
|
|
||||||
stuff = []
|
|
||||||
for next in offsets[1:]:
|
|
||||||
assert offsetBase + prev == file.tell()
|
|
||||||
chunk = file.read(next - prev)
|
|
||||||
assert len(chunk) == next - prev
|
|
||||||
stuff.append(chunk)
|
|
||||||
prev = next
|
|
||||||
return stuff
|
|
||||||
|
|
||||||
|
|
||||||
class Index:
|
|
||||||
|
|
||||||
def __init__(self, file):
|
|
||||||
self.file = file
|
|
||||||
count, = struct.unpack(">H", file.read(2))
|
|
||||||
self.count = count
|
|
||||||
if count == 0:
|
|
||||||
self.offsets = []
|
|
||||||
return
|
|
||||||
offSize = ord(file.read(1))
|
|
||||||
self.offsets = offsets = []
|
|
||||||
pad = '\0' * (4 - offSize)
|
|
||||||
for index in range(count+1):
|
|
||||||
chunk = file.read(offSize)
|
|
||||||
chunk = pad + chunk
|
|
||||||
offset, = struct.unpack(">L", chunk)
|
|
||||||
offsets.append(int(offset))
|
|
||||||
self.offsetBase = file.tell() - 1
|
|
||||||
file.seek(self.offsetBase + offsets[-1])
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self.count
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
|
||||||
offset = self.offsets[index] + self.offsetBase
|
|
||||||
size = self.offsets[index+1] - self.offsets[index]
|
|
||||||
return FileString(self.file, offset, size)
|
|
||||||
|
|
||||||
def toList(self):
|
|
||||||
l = []
|
|
||||||
for item in self:
|
|
||||||
l.append(item[:])
|
|
||||||
return l
|
|
||||||
|
|
||||||
|
|
||||||
class FileString:
|
|
||||||
|
|
||||||
def __init__(self, file, offset, size):
|
|
||||||
self.file = file
|
|
||||||
self.offset = offset
|
|
||||||
self.size = size
|
|
||||||
self.string = None
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self.size
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
|
||||||
return self.get()[index]
|
|
||||||
|
|
||||||
def __getslice__(self, i, j):
|
|
||||||
return self.get()[i:j]
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
if self.string is None:
|
|
||||||
self.file.seek(self.offset)
|
|
||||||
self.string = self.file.read(self.size)
|
|
||||||
return self.string
|
|
||||||
|
|
||||||
|
|
||||||
def getItems(o):
|
|
||||||
if hasattr(o, "items"):
|
|
||||||
items = o.items()
|
|
||||||
items.sort()
|
|
||||||
return "name", items
|
|
||||||
else:
|
|
||||||
return "index", map(None, range(len(o)), o)
|
|
||||||
|
|
||||||
|
|
||||||
def genericToXML(obj, order, arrayTypes, xmlWriter):
|
|
||||||
for name in order:
|
|
||||||
value = getattr(obj, name, None)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
if hasattr(value, "toXML"):
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.begintag(name)
|
|
||||||
xmlWriter.newline()
|
|
||||||
value.toXML(xmlWriter)
|
|
||||||
xmlWriter.endtag(name)
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.newline()
|
|
||||||
elif arrayTypes.has_key(name):
|
|
||||||
typeName = arrayTypes[name]
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.begintag(name)
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.newline()
|
|
||||||
label, items = getItems(value)
|
|
||||||
for k, v in items:
|
|
||||||
xmlWriter.begintag(typeName, [(label, k)])
|
|
||||||
xmlWriter.newline()
|
|
||||||
v.toXML(xmlWriter)
|
|
||||||
xmlWriter.endtag(typeName)
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.endtag(name)
|
|
||||||
xmlWriter.newline()
|
|
||||||
xmlWriter.newline()
|
|
||||||
else:
|
|
||||||
if isinstance(value, types.ListType):
|
|
||||||
value = " ".join(map(str, value))
|
|
||||||
xmlWriter.simpletag(name, value=value)
|
|
||||||
xmlWriter.newline()
|
|
||||||
|
|
||||||
|
|
||||||
def parseCharsetFormat1(nGlyphs, file, strings, isCID):
|
|
||||||
charset = ['.notdef']
|
|
||||||
count = 1
|
|
||||||
while count < nGlyphs:
|
|
||||||
first, = struct.unpack(">H", file.read(2))
|
|
||||||
nLeft = ord(file.read(1))
|
|
||||||
if isCID:
|
|
||||||
for CID in range(first, first+nLeft+1):
|
|
||||||
charset.append(CID)
|
|
||||||
else:
|
|
||||||
for SID in range(first, first+nLeft+1):
|
|
||||||
charset.append(strings[SID])
|
|
||||||
count = count + nLeft + 1
|
|
||||||
return charset
|
|
||||||
|
|
||||||
|
|
||||||
def parseCharsetFormat2(nGlyphs, file, strings, isCID):
|
|
||||||
charset = ['.notdef']
|
|
||||||
count = 1
|
|
||||||
while count < nGlyphs:
|
|
||||||
first, = struct.unpack(">H", file.read(2))
|
|
||||||
nLeft, = struct.unpack(">H", file.read(2))
|
|
||||||
if isCID:
|
|
||||||
for CID in range(first, first+nLeft+1):
|
|
||||||
charset.append(CID)
|
|
||||||
else:
|
|
||||||
for SID in range(first, first+nLeft+1):
|
|
||||||
charset.append(strings[SID])
|
|
||||||
count = count + nLeft + 1
|
|
||||||
return charset
|
|
||||||
|
|
||||||
|
|
||||||
def buildOperatorDict(table):
|
|
||||||
d = {}
|
|
||||||
for row in table:
|
|
||||||
d[row[0]] = row[1:3]
|
|
||||||
return d
|
|
||||||
|
|
||||||
def buildOrder(table):
|
|
||||||
l = []
|
|
||||||
for row in table:
|
|
||||||
l.append(row[1])
|
|
||||||
return l
|
|
||||||
|
|
||||||
def buildDefaults(table):
|
|
||||||
d = {}
|
|
||||||
for row in table:
|
|
||||||
if row[3] is not None:
|
|
||||||
d[row[1]] = row[3]
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
topDictOperators = [
|
|
||||||
# opcode name argument type default
|
|
||||||
(0, 'version', 'SID', None),
|
|
||||||
(1, 'Notice', 'SID', None),
|
|
||||||
((12, 0), 'Copyright', 'SID', None),
|
|
||||||
(2, 'FullName', 'SID', None),
|
|
||||||
(3, 'FamilyName', 'SID', None),
|
|
||||||
(4, 'Weight', 'SID', None),
|
|
||||||
((12, 1), 'isFixedPitch', 'number', 0),
|
|
||||||
((12, 2), 'ItalicAngle', 'number', 0),
|
|
||||||
((12, 3), 'UnderlinePosition', 'number', None),
|
|
||||||
((12, 4), 'UnderlineThickness', 'number', 50),
|
|
||||||
((12, 5), 'PaintType', 'number', 0),
|
|
||||||
((12, 6), 'CharstringType', 'number', 2),
|
|
||||||
((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0]),
|
|
||||||
(13, 'UniqueID', 'number', None),
|
|
||||||
(5, 'FontBBox', 'array', [0,0,0,0]),
|
|
||||||
((12, 8), 'StrokeWidth', 'number', 0),
|
|
||||||
(14, 'XUID', 'array', None),
|
|
||||||
(15, 'charset', 'number', 0),
|
|
||||||
(16, 'Encoding', 'number', 0),
|
|
||||||
(18, 'Private', ('number','number'), None),
|
|
||||||
(17, 'CharStrings', 'number', None),
|
|
||||||
((12, 20), 'SyntheticBase', 'number', None),
|
|
||||||
((12, 21), 'PostScript', 'SID', None),
|
|
||||||
((12, 22), 'BaseFontName', 'SID', None),
|
|
||||||
((12, 23), 'BaseFontBlend', 'delta', None),
|
|
||||||
((12, 30), 'ROS', ('SID','SID','number'), None),
|
|
||||||
((12, 31), 'CIDFontVersion', 'number', 0),
|
|
||||||
((12, 32), 'CIDFontRevision', 'number', 0),
|
|
||||||
((12, 33), 'CIDFontType', 'number', 0),
|
|
||||||
((12, 34), 'CIDCount', 'number', 8720),
|
|
||||||
((12, 35), 'UIDBase', 'number', None),
|
|
||||||
((12, 36), 'FDArray', 'number', None),
|
|
||||||
((12, 37), 'FDSelect', 'number', None),
|
|
||||||
((12, 38), 'FontName', 'SID', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
topDictDefaults = buildDefaults(topDictOperators)
|
|
||||||
topDictOrder = buildOrder(topDictOperators)
|
|
||||||
|
|
||||||
class TopDictDecompiler(psCharStrings.DictDecompiler):
|
|
||||||
|
|
||||||
operators = buildOperatorDict(topDictOperators)
|
|
||||||
dictDefaults = topDictDefaults
|
|
||||||
|
|
||||||
|
|
||||||
privateDictOperators = [
|
|
||||||
# opcode name argument type default
|
|
||||||
(6, 'BlueValues', 'delta', None),
|
|
||||||
(7, 'OtherBlues', 'delta', None),
|
|
||||||
(8, 'FamilyBlues', 'delta', None),
|
|
||||||
(9, 'FamilyOtherBlues', 'delta', None),
|
|
||||||
((12, 9), 'BlueScale', 'number', 0.039625),
|
|
||||||
((12, 10), 'BlueShift', 'number', 7),
|
|
||||||
((12, 11), 'BlueFuzz', 'number', 1),
|
|
||||||
(10, 'StdHW', 'number', None),
|
|
||||||
(11, 'StdVW', 'number', None),
|
|
||||||
((12, 12), 'StemSnapH', 'delta', None),
|
|
||||||
((12, 13), 'StemSnapV', 'delta', None),
|
|
||||||
((12, 14), 'ForceBold', 'number', 0),
|
|
||||||
((12, 17), 'LanguageGroup', 'number', 0),
|
|
||||||
((12, 18), 'ExpansionFactor', 'number', 0.06),
|
|
||||||
((12, 19), 'initialRandomSeed', 'number', 0),
|
|
||||||
(20, 'defaultWidthX', 'number', 0),
|
|
||||||
(21, 'nominalWidthX', 'number', 0),
|
|
||||||
(19, 'Subrs', 'number', None),
|
|
||||||
]
|
|
||||||
|
|
||||||
privateDictDefaults = buildDefaults(privateDictOperators)
|
|
||||||
privateDictOrder = buildOrder(privateDictOperators)
|
|
||||||
|
|
||||||
class PrivateDictDecompiler(psCharStrings.DictDecompiler):
|
|
||||||
|
|
||||||
operators = buildOperatorDict(privateDictOperators)
|
|
||||||
dictDefaults = privateDictDefaults
|
|
||||||
|
|
||||||
|
# SID
|
||||||
|
|
||||||
class IndexedStrings:
|
class IndexedStrings:
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user