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:
jvr 2002-05-16 18:17:32 +00:00
parent 357d71b438
commit 4756b3a040

View File

@ -1,7 +1,7 @@
"""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
@ -20,36 +20,34 @@ cffHeaderFormat = """
class CFFFontSet:
def __init__(self):
self.fonts = {}
pass
def decompile(self, file):
sstruct.unpack(cffHeaderFormat, file.read(4), self)
assert self.major == 1 and self.minor == 0, \
"unknown CFF format: %d.%d" % (self.major, self.minor)
self.fontNames = readINDEX(file)
topDicts = readINDEX(file)
strings = IndexedStrings(readINDEX(file))
globalSubrs = readINDEX(file)
file.seek(4)
xfnames = Index(file)
xtopds = Index(file)
xstrings = Index(file)
xgsubrs = Index(file)
assert xfnames.toList() == self.fontNames
assert xtopds.toList() == topDicts
assert xstrings.toList() == strings.strings
assert xgsubrs.toList() == globalSubrs
self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
for i in range(len(topDicts)):
font = self.fonts[self.fontNames[i]] = CFFFont()
self.fontNames = list(Index(file))
self.topDictIndex = TopDictIndex(file)
self.strings = IndexedStrings(list(Index(file)))
self.GlobalSubrs = SubrsIndex(file)
self.topDictIndex.strings = self.strings
def __len__(self):
return len(self.fontNames)
def keys(self):
return self.fontNames[:]
def __getitem__(self, name):
try:
index = self.fontNames.index(name)
except ValueError:
raise KeyError, name
font = self.topDictIndex[index]
if not hasattr(font, "GlobalSubrs"):
font.GlobalSubrs = self.GlobalSubrs
file.seek(0)
font.decompile(file, topDicts[i], strings, self)
return font
def compile(self):
strings = IndexedStrings()
@ -60,21 +58,14 @@ class CFFFontSet:
for fontName in self.fontNames:
xmlWriter.begintag("CFFFont", name=fontName)
xmlWriter.newline()
font = self.fonts[fontName]
font = self[fontName]
font.toXML(xmlWriter, progress)
xmlWriter.endtag("CFFFont")
xmlWriter.newline()
xmlWriter.newline()
xmlWriter.begintag("GlobalSubrs")
xmlWriter.newline()
for i in range(len(self.GlobalSubrs)):
xmlWriter.newline()
xmlWriter.begintag("CharString", index=i)
xmlWriter.newline()
self.GlobalSubrs[i].toXML(xmlWriter)
xmlWriter.endtag("CharString")
xmlWriter.newline()
xmlWriter.newline()
self.GlobalSubrs.toXML(xmlWriter, progress)
xmlWriter.endtag("GlobalSubrs")
xmlWriter.newline()
xmlWriter.newline()
@ -83,102 +74,347 @@ class CFFFontSet:
xxx
class CFFFont:
class Index:
def __init__(self):
pass
"""This class represents what the CFF spec calls an INDEX."""
def __getattr__(self, attr):
if not topDictDefaults.has_key(attr):
raise AttributeError, attr
return topDictDefaults[attr]
def __init__(self, file):
self.file = file
count, = struct.unpack(">H", file.read(2))
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):
self.__dict__.update(d)
def __len__(self):
return self.count
def decompile(self, file, topDictData, strings, fontSet):
top = TopDictDecompiler(strings)
top.decompile(topDictData)
self.fromDict(top.getDict())
if hasattr(self, "ROS"):
isCID = 1
# XXX CID subFonts
offset = self.FDArray
file.seek(offset)
fontDicts = readINDEX(file)
subFonts = self.subFonts = []
for topDictData in fontDicts:
subFont = CFFFont()
subFonts.append(subFont)
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"):
file.seek(self.CharStrings)
rawCharStrings = Index(file)
nGlyphs = len(rawCharStrings)
# get charset (or rather: get glyphNames)
if self.charset > 2:
file.seek(self.charset)
def __getitem__(self, index):
item = self.items[index]
if item is not None:
return item
offset = self.offsets[index] + self.offsetBase
size = self.offsets[index+1] - self.offsets[index]
file = self.file
file.seek(offset)
data = file.read(size)
assert len(data) == size
item = self.produceItem(data, file, offset, size)
self.items[index] = item
return item
def produceItem(self, data, file, offset, size):
return data
class SubrsIndex(Index):
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))
if format == 0:
raise NotImplementedError
elif format == 1:
charset = parseCharsetFormat1(nGlyphs, file, strings, isCID)
elif format == 2:
charset = parseCharsetFormat2(nGlyphs, file, strings, isCID)
elif format == 1 or format == 2:
charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
elif format == 3:
raise NotImplementedError
else:
raise NotImplementedError
self.charset = charset
assert len(charset) == nGlyphs
assert len(charset) == numGlyphs
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:
# 0: ISOAdobe (or CID font!)
# 1: Expert
# 2: ExpertSubset
pass
if hasattr(self, "CharStrings"):
self.CharStrings = charStrings = {}
if self.CharstringType == 2:
# Type 2 CharStrings
charStringClass = psCharStrings.T2CharString
else:
# Type 1 CharStrings
charStringClass = psCharStrings.T1CharString
for i in range(nGlyphs):
charStrings[charset[i]] = charStringClass(rawCharStrings[i])
assert len(charStrings) == nGlyphs
encoding = self.Encoding
if encoding > 1:
# encoding is an offset from the beginning of 'data' to an encoding subtable
raise NotImplementedError
self.Encoding = encoding
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:
# self.Encoding:
# 0 Standard Encoding
# 1 Expert Encoding
pass
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
def __getattr__(self, name):
value = self.rawDict.get(name)
if value is None:
value = self.defaults.get(name)
if value is None:
raise AttributeError, name
conv = self.converters[name]
if conv is not None:
value = conv.read(self, value)
setattr(self, name, value)
return value
def toXML(self, xmlWriter, progress):
for name in self.order:
value = getattr(self, name, None)
if value is None:
continue
conv = self.converters.get(name)
if conv is not None:
conv.xmlWrite(xmlWriter, name, value)
else:
if isinstance(value, types.ListType):
value = " ".join(map(str, value))
xmlWriter.simpletag(name, value=value)
xmlWriter.newline()
class TopDict(BaseDict):
defaults = buildDefaults(topDictOperators)
converters = buildConverters(topDictOperators)
order = buildOrder(topDictOperators)
decompiler = TopDictDecompiler
def getGlyphOrder(self):
return self.charset
def setGlyphOrder(self, glyphOrder):
self.charset = glyphOrder
def postDecompile(self):
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):
if self.CharstringType == 2:
@ -192,301 +428,17 @@ class CFFFont:
# Type 1 CharStrings
for charString in self.CharStrings.values():
charString.decompile()
def toXML(self, xmlWriter, progress=None):
xmlWriter.newline()
genericToXML(self, topDictOrder, {'CharStrings': 'CharString'}, xmlWriter)
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)
class PrivateDict(BaseDict):
defaults = buildDefaults(privateDictOperators)
converters = buildConverters(privateDictOperators)
order = buildOrder(privateDictOperators)
decompiler = PrivateDictDecompiler
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: