results of morning-after code review, added some doc strings, etc.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@210 4cde692c-a291-49d1-8350-778aa11640f8
This commit is contained in:
parent
3f776b83cb
commit
64b5c80e80
@ -6,6 +6,10 @@ from types import TupleType
|
|||||||
|
|
||||||
class BaseTTXConverter(DefaultTable):
|
class BaseTTXConverter(DefaultTable):
|
||||||
|
|
||||||
|
"""Generic base class for TTX table converters. Functions as an adapter
|
||||||
|
between the TTX (ttLib actually) table model and the model we use for
|
||||||
|
OpenType tables, which is neccesarily subtly different."""
|
||||||
|
|
||||||
def decompile(self, data, font):
|
def decompile(self, data, font):
|
||||||
import otTables
|
import otTables
|
||||||
reader = OTTableReader(data, self.tableTag)
|
reader = OTTableReader(data, self.tableTag)
|
||||||
@ -31,6 +35,8 @@ class BaseTTXConverter(DefaultTable):
|
|||||||
|
|
||||||
class OTTableReader:
|
class OTTableReader:
|
||||||
|
|
||||||
|
"""Helper class to retrieve data from an OpenType table."""
|
||||||
|
|
||||||
def __init__(self, data, tableType, offset=0, valueFormat=None, cachingStats=None):
|
def __init__(self, data, tableType, offset=0, valueFormat=None, cachingStats=None):
|
||||||
self.data = data
|
self.data = data
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
@ -102,6 +108,8 @@ class OTTableReader:
|
|||||||
|
|
||||||
class OTTableWriter:
|
class OTTableWriter:
|
||||||
|
|
||||||
|
"""Helper class to gather and assemble data for OpenType tables."""
|
||||||
|
|
||||||
def __init__(self, tableType, valueFormat=None):
|
def __init__(self, tableType, valueFormat=None):
|
||||||
self.items = []
|
self.items = []
|
||||||
self.tableType = tableType
|
self.tableType = tableType
|
||||||
@ -169,6 +177,7 @@ class OTTableWriter:
|
|||||||
|
|
||||||
|
|
||||||
class CountReference:
|
class CountReference:
|
||||||
|
"""A reference to a Count value, not a count of a reference."""
|
||||||
def __init__(self, table, name):
|
def __init__(self, table, name):
|
||||||
self.table = table
|
self.table = table
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -176,14 +185,42 @@ class CountReference:
|
|||||||
return packUShort(self.table[self.name])
|
return packUShort(self.table[self.name])
|
||||||
|
|
||||||
|
|
||||||
def packUShort(offset):
|
def packUShort(value):
|
||||||
assert 0 <= offset < 0x10000
|
assert 0 <= value < 0x10000
|
||||||
return struct.pack(">H", offset)
|
return struct.pack(">H", value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TableStack:
|
||||||
|
"""A stack of table dicts, working as a stack of namespaces so we can
|
||||||
|
retrieve values from (and store values to) tables higher up the stack."""
|
||||||
|
def __init__(self):
|
||||||
|
self.stack = []
|
||||||
|
def push(self, table):
|
||||||
|
self.stack.insert(0, table)
|
||||||
|
def pop(self):
|
||||||
|
self.stack.pop(0)
|
||||||
|
def getTop(self):
|
||||||
|
return self.stack[0]
|
||||||
|
def getValue(self, name):
|
||||||
|
return self.__findTable(name)[name]
|
||||||
|
def storeValue(self, name, value):
|
||||||
|
table = self.__findTable(name)
|
||||||
|
if table[name] is None:
|
||||||
|
table[name] = value
|
||||||
|
else:
|
||||||
|
assert table[name] == value, (table[name], value)
|
||||||
|
def __findTable(self, name):
|
||||||
|
for table in self.stack:
|
||||||
|
if table.has_key(name):
|
||||||
|
return table
|
||||||
|
raise KeyError, name
|
||||||
|
|
||||||
|
|
||||||
class BaseTable:
|
class BaseTable:
|
||||||
|
|
||||||
|
"""Generic base class for all OpenType (sub)tables."""
|
||||||
|
|
||||||
def getConverters(self):
|
def getConverters(self):
|
||||||
return self.converters
|
return self.converters
|
||||||
|
|
||||||
@ -220,7 +257,7 @@ class BaseTable:
|
|||||||
value = table.get(conv.name)
|
value = table.get(conv.name)
|
||||||
if conv.repeat:
|
if conv.repeat:
|
||||||
if value is None:
|
if value is None:
|
||||||
value = [] # XXXXXX
|
value = []
|
||||||
tableStack.storeValue(conv.repeat, len(value) - conv.repeatOffset)
|
tableStack.storeValue(conv.repeat, len(value) - conv.repeatOffset)
|
||||||
for item in value:
|
for item in value:
|
||||||
conv.write(writer, font, tableStack, item)
|
conv.write(writer, font, tableStack, item)
|
||||||
@ -249,7 +286,7 @@ class BaseTable:
|
|||||||
if attrs is None:
|
if attrs is None:
|
||||||
attrs = []
|
attrs = []
|
||||||
if hasattr(self, "Format"):
|
if hasattr(self, "Format"):
|
||||||
attrs = attrs + [("Format", str(self.Format))]
|
attrs = attrs + [("Format", self.Format)]
|
||||||
xmlWriter.begintag(tableName, attrs)
|
xmlWriter.begintag(tableName, attrs)
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
self.toXML2(xmlWriter, font)
|
self.toXML2(xmlWriter, font)
|
||||||
@ -262,30 +299,30 @@ class BaseTable:
|
|||||||
# do it ourselves. I think I'm getting schizophrenic...
|
# do it ourselves. I think I'm getting schizophrenic...
|
||||||
for conv in self.getConverters():
|
for conv in self.getConverters():
|
||||||
value = getattr(self, conv.name)
|
value = getattr(self, conv.name)
|
||||||
if not conv.repeat:
|
if conv.repeat:
|
||||||
conv.xmlWrite(xmlWriter, font, value, conv.name, [])
|
|
||||||
else:
|
|
||||||
for i in range(len(value)):
|
for i in range(len(value)):
|
||||||
item = value[i]
|
item = value[i]
|
||||||
conv.xmlWrite(xmlWriter, font, item, conv.name, [("index", i)])
|
conv.xmlWrite(xmlWriter, font, item, conv.name,
|
||||||
|
[("index", i)])
|
||||||
|
else:
|
||||||
|
conv.xmlWrite(xmlWriter, font, value, conv.name, [])
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), font):
|
def fromXML(self, (name, attrs, content), font):
|
||||||
try:
|
try:
|
||||||
conv = self.getConverterByName(name)
|
conv = self.getConverterByName(name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print self, name, attrs, content
|
## print self, name, attrs, content
|
||||||
raise # XXX on KeyError, raise nice error
|
raise # XXX on KeyError, raise nice error
|
||||||
value = conv.xmlRead(attrs, content, font)
|
value = conv.xmlRead(attrs, content, font)
|
||||||
name = conv.name
|
|
||||||
if conv.repeat:
|
if conv.repeat:
|
||||||
try:
|
try:
|
||||||
seq = getattr(self, name)
|
seq = getattr(self, conv.name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
seq = []
|
seq = []
|
||||||
setattr(self, name, seq)
|
setattr(self, conv.name, seq)
|
||||||
seq.append(value)
|
seq.append(value)
|
||||||
else:
|
else:
|
||||||
setattr(self, name, value)
|
setattr(self, conv.name, value)
|
||||||
|
|
||||||
def __cmp__(self, other):
|
def __cmp__(self, other):
|
||||||
# this is only for debugging, so it's ok to barf
|
# this is only for debugging, so it's ok to barf
|
||||||
@ -300,6 +337,9 @@ class BaseTable:
|
|||||||
|
|
||||||
class FormatSwitchingBaseTable(BaseTable):
|
class FormatSwitchingBaseTable(BaseTable):
|
||||||
|
|
||||||
|
"""Small specialization of BaseTable, for tables that have multiple
|
||||||
|
formats, eg. CoverageFormat1 vs. CoverageFormat2."""
|
||||||
|
|
||||||
def getConverters(self):
|
def getConverters(self):
|
||||||
return self.converters[self.Format]
|
return self.converters[self.Format]
|
||||||
|
|
||||||
@ -316,6 +356,14 @@ class FormatSwitchingBaseTable(BaseTable):
|
|||||||
BaseTable.compile(self, writer, font, tableStack)
|
BaseTable.compile(self, writer, font, tableStack)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Support for ValueRecords
|
||||||
|
#
|
||||||
|
# This data type is so different from all other OpenType data types that
|
||||||
|
# it requires quite a bit of code for itself. It even has special support
|
||||||
|
# in OTTableReader and OTTableWriter...
|
||||||
|
#
|
||||||
|
|
||||||
valueRecordFormat = [
|
valueRecordFormat = [
|
||||||
# Mask Name isDevice signed
|
# Mask Name isDevice signed
|
||||||
(0x0001, "XPlacement", 0, 1),
|
(0x0001, "XPlacement", 0, 1),
|
||||||
@ -348,6 +396,8 @@ valueRecordFormatDict = _buildDict()
|
|||||||
|
|
||||||
class ValueRecordFactory:
|
class ValueRecordFactory:
|
||||||
|
|
||||||
|
"""Given a format code, this object convert ValueRecords."""
|
||||||
|
|
||||||
def setFormat(self, valueFormat):
|
def setFormat(self, valueFormat):
|
||||||
format = []
|
format = []
|
||||||
for mask, name, isDevice, signed in valueRecordFormat:
|
for mask, name, isDevice, signed in valueRecordFormat:
|
||||||
@ -453,27 +503,3 @@ class ValueRecord:
|
|||||||
else:
|
else:
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
class TableStack:
|
|
||||||
def __init__(self):
|
|
||||||
self.stack = []
|
|
||||||
def push(self, table):
|
|
||||||
self.stack.insert(0, table)
|
|
||||||
def pop(self):
|
|
||||||
self.stack.pop(0)
|
|
||||||
def getTop(self):
|
|
||||||
return self.stack[0]
|
|
||||||
def getValue(self, name):
|
|
||||||
return self.__findTable(name)[name]
|
|
||||||
def storeValue(self, name, value):
|
|
||||||
table = self.__findTable(name)
|
|
||||||
if table[name] is None:
|
|
||||||
table[name] = value
|
|
||||||
else:
|
|
||||||
assert table[name] == value, (table[name], value)
|
|
||||||
def __findTable(self, name):
|
|
||||||
for table in self.stack:
|
|
||||||
if table.has_key(name):
|
|
||||||
return table
|
|
||||||
raise KeyError, name
|
|
||||||
|
|
||||||
|
@ -2,7 +2,10 @@ from types import TupleType
|
|||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
|
|
||||||
|
|
||||||
def buildConverterList(tableSpec, tableNamespace):
|
def buildConverters(tableSpec, tableNamespace):
|
||||||
|
"""Given a table spec from otData.py, build a converter object for each
|
||||||
|
field of the table. This is called for each table in otData.py, and
|
||||||
|
the results are assigned to the corresponding class in otTables.py."""
|
||||||
converters = []
|
converters = []
|
||||||
convertersByName = {}
|
convertersByName = {}
|
||||||
for tp, name, repeat, repeatOffset, descr in tableSpec:
|
for tp, name, repeat, repeatOffset, descr in tableSpec:
|
||||||
@ -35,6 +38,9 @@ def buildConverterList(tableSpec, tableNamespace):
|
|||||||
|
|
||||||
class BaseConverter:
|
class BaseConverter:
|
||||||
|
|
||||||
|
"""Base class for converter objects. Apart from the constructor, this
|
||||||
|
is an abstract class."""
|
||||||
|
|
||||||
def __init__(self, name, repeat, repeatOffset, tableClass):
|
def __init__(self, name, repeat, repeatOffset, tableClass):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.repeat = repeat
|
self.repeat = repeat
|
||||||
@ -43,15 +49,19 @@ class BaseConverter:
|
|||||||
self.isCount = name.endswith("Count")
|
self.isCount = name.endswith("Count")
|
||||||
|
|
||||||
def read(self, reader, font, tableStack):
|
def read(self, reader, font, tableStack):
|
||||||
|
"""Read a value from the reader."""
|
||||||
raise NotImplementedError, self
|
raise NotImplementedError, self
|
||||||
|
|
||||||
def write(self, writer, font, tableStack, value):
|
def write(self, writer, font, tableStack, value):
|
||||||
raise NotImplementedError, self
|
"""Write a value to the writer."""
|
||||||
|
|
||||||
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
||||||
raise NotImplementedError, self
|
raise NotImplementedError, self
|
||||||
|
|
||||||
def xmlRead(self, attrs, content, font):
|
def xmlRead(self, attrs, content, font):
|
||||||
|
"""Read a value from XML."""
|
||||||
|
raise NotImplementedError, self
|
||||||
|
|
||||||
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
||||||
|
"""Write a value to XML."""
|
||||||
raise NotImplementedError, self
|
raise NotImplementedError, self
|
||||||
|
|
||||||
|
|
||||||
@ -240,6 +250,7 @@ class DeltaValue(BaseConverter):
|
|||||||
writer.writeUShort(tmp)
|
writer.writeUShort(tmp)
|
||||||
|
|
||||||
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
||||||
|
# XXX this could do with a nicer format
|
||||||
xmlWriter.simpletag(name, attrs + [("value", value)])
|
xmlWriter.simpletag(name, attrs + [("value", value)])
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
|
"""fontTools.ttLib.tables.otTables -- A collection of classes representing the various
|
||||||
OpenType subtables.
|
OpenType subtables.
|
||||||
|
|
||||||
Most are constructed upon import from data in otData.py. Most smartness is contained
|
Most are constructed upon import from data in otData.py, all are populated with
|
||||||
in otBase.BaseTable.
|
converter objects from otConverters.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from otBase import BaseTable, FormatSwitchingBaseTable
|
from otBase import BaseTable, FormatSwitchingBaseTable
|
||||||
@ -15,28 +15,35 @@ class FeatureParams(BaseTable):
|
|||||||
"""Dummy class; this table isn't defined, but is used, and is always NULL."""
|
"""Dummy class; this table isn't defined, but is used, and is always NULL."""
|
||||||
|
|
||||||
|
|
||||||
_equivalents = [
|
#
|
||||||
('MarkArray', ("Mark1Array",)),
|
# For each subtable format there is a class. However, we don't really distinguish
|
||||||
('LangSys', ('DefaultLangSys',)),
|
# between "field name" and "format name": often these are the same. Yet there's
|
||||||
('Coverage', ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
|
# a whole bunch of fields with different names. The following dict is a mapping
|
||||||
|
# from "format name" to "field name". _buildClasses() uses this to create a
|
||||||
|
# subclass for each alternate field name.
|
||||||
|
#
|
||||||
|
_equivalents = {
|
||||||
|
'MarkArray': ("Mark1Array",),
|
||||||
|
'LangSys': ('DefaultLangSys',),
|
||||||
|
'Coverage': ('MarkCoverage', 'BaseCoverage', 'LigatureCoverage', 'Mark1Coverage',
|
||||||
'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
|
'Mark2Coverage', 'BacktrackCoverage', 'InputCoverage',
|
||||||
'LookaheadCoverage')),
|
'LookaheadCoverage'),
|
||||||
('ClassDef', ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
|
'ClassDef': ('ClassDef1', 'ClassDef2', 'BacktrackClassDef', 'InputClassDef',
|
||||||
'LookaheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef')),
|
'LookaheadClassDef', 'GlyphClassDef', 'MarkAttachClassDef'),
|
||||||
('Anchor', ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
|
'Anchor': ('EntryAnchor', 'ExitAnchor', 'BaseAnchor', 'LigatureAnchor',
|
||||||
'Mark2Anchor', 'MarkAnchor')),
|
'Mark2Anchor', 'MarkAnchor'),
|
||||||
('Device', ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
|
'Device': ('XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice',
|
||||||
'XDeviceTable', 'YDeviceTable', 'DeviceTable')),
|
'XDeviceTable', 'YDeviceTable', 'DeviceTable'),
|
||||||
('Axis', ('HorizAxis', 'VertAxis',)),
|
'Axis': ('HorizAxis', 'VertAxis',),
|
||||||
('MinMax', ('DefaultMinMax',)),
|
'MinMax': ('DefaultMinMax',),
|
||||||
('BaseCoord', ('MinCoord', 'MaxCoord',)),
|
'BaseCoord': ('MinCoord', 'MaxCoord',),
|
||||||
('JstfLangSys', ('DefJstfLangSys',)),
|
'JstfLangSys': ('DefJstfLangSys',),
|
||||||
('JstfGSUBModList', ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
|
'JstfGSUBModList': ('ShrinkageEnableGSUB', 'ShrinkageDisableGSUB', 'ExtensionEnableGSUB',
|
||||||
'ExtensionDisableGSUB',)),
|
'ExtensionDisableGSUB',),
|
||||||
('JstfGPOSModList', ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
|
'JstfGPOSModList': ('ShrinkageEnableGPOS', 'ShrinkageDisableGPOS', 'ExtensionEnableGPOS',
|
||||||
'ExtensionDisableGPOS',)),
|
'ExtensionDisableGPOS',),
|
||||||
('JstfMax', ('ShrinkageJstfMax', 'ExtensionJstfMax',)),
|
'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
|
||||||
]
|
}
|
||||||
|
|
||||||
|
|
||||||
def _buildClasses():
|
def _buildClasses():
|
||||||
@ -55,10 +62,11 @@ def _buildClasses():
|
|||||||
name = m.group(1)
|
name = m.group(1)
|
||||||
baseClass = FormatSwitchingBaseTable
|
baseClass = FormatSwitchingBaseTable
|
||||||
if not namespace.has_key(name):
|
if not namespace.has_key(name):
|
||||||
|
# the class doesn't exist yet, so the base implementation is used.
|
||||||
cls = new.classobj(name, (baseClass,), {})
|
cls = new.classobj(name, (baseClass,), {})
|
||||||
namespace[name] = cls
|
namespace[name] = cls
|
||||||
|
|
||||||
for base, alts in _equivalents:
|
for base, alts in _equivalents.items():
|
||||||
base = namespace[base]
|
base = namespace[base]
|
||||||
for alt in alts:
|
for alt in alts:
|
||||||
namespace[alt] = new.classobj(alt, (base,), {})
|
namespace[alt] = new.classobj(alt, (base,), {})
|
||||||
@ -87,9 +95,12 @@ def _buildClasses():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS
|
lookupTypes['JSTF'] = lookupTypes['GPOS'] # JSTF contains GPOS
|
||||||
|
for lookupEnum in lookupTypes.values():
|
||||||
|
for enum, cls in lookupEnum.items():
|
||||||
|
cls.LookupType = enum
|
||||||
|
|
||||||
# add converters to classes
|
# add converters to classes
|
||||||
from otConverters import buildConverterList
|
from otConverters import buildConverters
|
||||||
for name, table in otData:
|
for name, table in otData:
|
||||||
m = formatPat.match(name)
|
m = formatPat.match(name)
|
||||||
if m:
|
if m:
|
||||||
@ -100,12 +111,12 @@ def _buildClasses():
|
|||||||
if not hasattr(cls, "converters"):
|
if not hasattr(cls, "converters"):
|
||||||
cls.converters = {}
|
cls.converters = {}
|
||||||
cls.convertersByName = {}
|
cls.convertersByName = {}
|
||||||
converters, convertersByName = buildConverterList(table[1:], namespace)
|
converters, convertersByName = buildConverters(table[1:], namespace)
|
||||||
cls.converters[format] = converters
|
cls.converters[format] = converters
|
||||||
cls.convertersByName[format] = convertersByName
|
cls.convertersByName[format] = convertersByName
|
||||||
else:
|
else:
|
||||||
cls = namespace[name]
|
cls = namespace[name]
|
||||||
cls.converters, cls.convertersByName = buildConverterList(table, namespace)
|
cls.converters, cls.convertersByName = buildConverters(table, namespace)
|
||||||
|
|
||||||
|
|
||||||
_buildClasses()
|
_buildClasses()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user