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