Implement FeatureParams
All types of FeatureParams are correctly handled now.
The only thing not handled is broken fonts generated with the
old AFDKO that have their FeatureParams offset computed wrong.
I don't currently plan on handling those.
Fixes https://github.com/behdad/fonttools/issues/38
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
index 788f6da..168553d 100644
--- a/Lib/fontTools/ttLib/tables/otBase.py
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -135,6 +135,14 @@
self.pos = newpos
return value
+ def readUInt24(self):
+ pos = self.pos
+ newpos = pos + 3
+ value = (ord(self.data[pos]) << 16) | (ord(self.data[pos+1]) << 8) | ord(self.data[pos+2])
+ value, = struct.unpack(">H", self.data[pos:newpos])
+ self.pos = newpos
+ return value
+
def readULong(self):
pos = self.pos
newpos = pos + 4
@@ -397,6 +405,10 @@
def writeShort(self, value):
self.items.append(struct.pack(">h", value))
+
+ def writeUInt24(self, value):
+ assert 0 <= value < 0x1000000
+ self.items.append(''.join(chr(v) for v in (value>>16, (value>>8)&0xFF, value&0xff)))
def writeLong(self, value):
self.items.append(struct.pack(">l", value))
@@ -533,6 +545,8 @@
if conv.name == "ExtSubTable":
conv = conv.getConverter(reader.globalState.tableType,
table["ExtensionLookupType"])
+ if conv.name == "FeatureParams":
+ conv = conv.getConverter(reader["FeatureTag"])
if conv.repeat:
l = []
if conv.repeat in table:
@@ -547,7 +561,7 @@
if conv.aux and not eval(conv.aux, None, table):
continue
table[conv.name] = conv.read(reader, font, table)
- if conv.isPropagatedCount:
+ if conv.isPropagated:
reader[conv.name] = table[conv.name]
self.postRead(table, font)
@@ -591,7 +605,7 @@
# We add a reference: by the time the data is assembled
# the Count value will be filled in.
ref = writer.writeCountReference(table, conv.name)
- if conv.isPropagatedCount:
+ if conv.isPropagated:
table[conv.name] = None
writer[conv.name] = ref
else:
@@ -600,6 +614,8 @@
if conv.aux and not eval(conv.aux, None, table):
continue
conv.write(writer, font, table, value)
+ if conv.isPropagated:
+ writer[conv.name] = value
def readFormat(self, reader):
pass
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index 37505d3..065659d 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -20,6 +20,8 @@
converterClass = SubTable
elif name == "ExtSubTable":
converterClass = ExtSubTable
+ elif name == "FeatureParams":
+ converterClass = FeatureParams
else:
converterClass = converterMapping[tp]
tableClass = tableNamespace.get(name)
@@ -30,6 +32,11 @@
for t in conv.lookupTypes.values():
for cls in t.values():
convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
+ if name == "FeatureParams":
+ conv.featureParamTypes = tableNamespace['featureParamTypes']
+ conv.defaultFeatureParams = tableNamespace['FeatureParams']
+ for cls in conv.featureParamTypes.values():
+ convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
converters.append(conv)
assert not convertersByName.has_key(name)
convertersByName[name] = conv
@@ -47,7 +54,7 @@
self.aux = aux
self.tableClass = tableClass
self.isCount = name.endswith("Count")
- self.isPropagatedCount = name in ["ClassCount", "Class2Count"]
+ self.isPropagated = name in ["ClassCount", "Class2Count", "FeatureTag"]
def read(self, reader, font, tableDict):
"""Read a value from the reader."""
@@ -121,6 +128,12 @@
def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeUShort(value)
+class UInt24(IntValue):
+ def read(self, reader, font, tableDict):
+ return reader.readUInt24()
+ def write(self, writer, font, tableDict, value, repeatIndex=None):
+ writer.writeUInt24(value)
+
class Count(Short):
def xmlWrite(self, xmlWriter, font, value, name, attrs):
xmlWriter.comment("%s=%s" % (name, value))
@@ -244,6 +257,11 @@
writer.Extension = 1 # actually, mere presence of the field flags it as an Ext Subtable writer.
Table.write(self, writer, font, tableDict, value, repeatIndex)
+class FeatureParams(Table):
+ def getConverter(self, featureTag):
+ tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
+ return self.__class__(self.name, self.repeat, self.aux, tableClass)
+
class ValueFormat(IntValue):
def __init__(self, name, repeat, aux, tableClass):
@@ -333,6 +351,7 @@
# type class
"int16": Short,
"uint16": UShort,
+ "uint24": UInt24,
"Version": Version,
"Tag": Tag,
"GlyphID": GlyphID,
diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py
index 1b8cf3f..40e6ba9 100644
--- a/Lib/fontTools/ttLib/tables/otData.py
+++ b/Lib/fontTools/ttLib/tables/otData.py
@@ -48,6 +48,33 @@
('uint16', 'LookupListIndex', 'LookupCount', 0, 'Array of LookupList indices for this feature -zero-based (first lookup is LookupListIndex = 0)'),
]),
+ ('FeatureParams', [
+ ]),
+
+ ('FeatureParamsSize', [
+ ('uint16', 'DesignSize', None, None, 'The design size in 720/inch units (decipoints).'),
+ ('uint16', 'SubfamilyID', None, None, 'Serves as an identifier that associates fonts in a subfamily.'),
+ ('uint16', 'SubfamilyNameID', None, None, 'Subfamily NameID.'),
+ ('uint16', 'RangeStart', None, None, 'Small end of recommended usage range (exclusive) in 720/inch units.'),
+ ('uint16', 'RangeEnd', None, None, 'Large end of recommended usage range (inclusive) in 720/inch units.'),
+ ]),
+
+ ('FeatureParamsStylisticSet', [
+ ('uint16', 'Version', None, None, 'Set to 0.'),
+ ('uint16', 'UINameID', None, None, 'UI NameID.'),
+ ]),
+
+ ('FeatureParamsCharacterVariants', [
+ ('uint16', 'Format', None, None, 'Set to 0.'),
+ ('uint16', 'FeatUILabelNameID', None, None, 'Feature UI label NameID.'),
+ ('uint16', 'FeatUITooltipTextNameID', None, None, 'Feature UI tooltip text NameID.'),
+ ('uint16', 'SampleTextNameID', None, None, 'Sample text NameID.'),
+ ('uint16', 'NumNamedParameters', None, None, 'Number of named parameters.'),
+ ('uint16', 'FirstParamUILabelNameID', None, None, 'First NameID of UI feature parameters.'),
+ ('uint16', 'CharCount', None, None, 'Count of characters this feature provides glyph variants for.'),
+ ('uint24', 'Character', 'CharCount', 0, 'Unicode characters for which this feature provides glyph variants.'),
+ ]),
+
('LookupList', [
('uint16', 'LookupCount', None, None, 'Number of lookups in this table'),
('Offset', 'Lookup', 'LookupCount', 0, 'Array of offsets to Lookup tables-from beginning of LookupList -zero based (first lookup is Lookup index = 0)'),
diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index 0cabd75..584d0c5 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -13,16 +13,20 @@
class LookupOrder(BaseTable):
"""Dummy class; this table isn't defined, but is used, and is always NULL."""
-
class FeatureParams(BaseTable):
- """This class has been used by Adobe, but but this one implementation was done wrong.
- No other use has been made, becuase there is no way to know how to interpret
- the data at the offset.. For now, if we see one, just skip the data on
- decompiling and dumping to XML. """
- # XXX The above is no longer true; the 'size' feature uses FeatureParams now.
- def __init__(self):
- BaseTable.__init__(self)
- self.converters = []
+
+ def compile(self, writer, font):
+ assert featureParamTypes.get(writer['FeatureTag'], None) == self.__class__, "Wrong FeatureParams type for feature '%s': %s" % (writer['FeatureTag'], self.__class__.__name__)
+ BaseTable.compile(self, writer, font)
+
+class FeatureParamsSize(FeatureParams):
+ pass
+
+class FeatureParamsStylisticSet(FeatureParams):
+ pass
+
+class FeatureParamsCharacterVariants(FeatureParams):
+ pass
class Coverage(FormatSwitchingBaseTable):
@@ -696,6 +700,15 @@
for lookupEnum in lookupTypes.values():
for enum, cls in lookupEnum.items():
cls.LookupType = enum
+
+ global featureParamTypes
+ featureParamTypes = {
+ 'size': FeatureParamsSize,
+ }
+ for i in range(1, 20+1):
+ featureParamTypes['ss%02d' % i] = FeatureParamsStylisticSet
+ for i in range(1, 99+1):
+ featureParamTypes['cv%02d' % i] = FeatureParamsCharacterVariants
# add converters to classes
from otConverters import buildConverters