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:
jvr 2002-05-11 10:21:36 +00:00
parent 3f776b83cb
commit 64b5c80e80
3 changed files with 117 additions and 69 deletions

View File

@ -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

View File

@ -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()

View File

@ -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()