diff --git a/Lib/fontTools/ttLib/tables/_m_o_r_x.py b/Lib/fontTools/ttLib/tables/_m_o_r_x.py new file mode 100644 index 000000000..6fbce0201 --- /dev/null +++ b/Lib/fontTools/ttLib/tables/_m_o_r_x.py @@ -0,0 +1,7 @@ +from __future__ import print_function, division, absolute_import +from fontTools.misc.py23 import * +from .otBase import BaseTTXConverter + + +class table__m_o_r_x(BaseTTXConverter): + pass diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py index ed0c93534..52d5b59f6 100644 --- a/Lib/fontTools/ttLib/tables/otBase.py +++ b/Lib/fontTools/ttLib/tables/otBase.py @@ -604,6 +604,9 @@ class BaseTable(object): table["ExtensionLookupType"]) if conv.name == "FeatureParams": conv = conv.getConverter(reader["FeatureTag"]) + if conv.name == "SubStruct": + conv = conv.getConverter(reader.globalState.tableType, + table["MorphType"]) if conv.repeat: if conv.repeat in table: countValue = table[conv.repeat] diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py index 860f1086d..f22bcb20e 100644 --- a/Lib/fontTools/ttLib/tables/otConverters.py +++ b/Lib/fontTools/ttLib/tables/otConverters.py @@ -23,13 +23,15 @@ def buildConverters(tableSpec, tableNamespace): if name.startswith("ValueFormat"): assert tp == "uint16" converterClass = ValueFormat - elif name.endswith("Count"): + elif name.endswith("Count") or name == "MorphType": assert tp in ("uint16", "uint32") converterClass = ComputedUShort if tp == 'uint16' else ComputedULong elif name == "SubTable": converterClass = SubTable elif name == "ExtSubTable": converterClass = ExtSubTable + elif name == "SubStruct": + converterClass = SubStruct elif name == "FeatureParams": converterClass = FeatureParams else: @@ -43,7 +45,7 @@ def buildConverters(tableSpec, tableNamespace): conv = converterClass(name, repeat, aux, tableClass=tableClass) else: conv = converterClass(name, repeat, aux) - if name in ["SubTable", "ExtSubTable"]: + if name in ["SubTable", "ExtSubTable", "SubStruct"]: conv.lookupTypes = tableNamespace['lookupTypes'] # also create reverse mapping for t in conv.lookupTypes.values(): @@ -170,6 +172,11 @@ class ULong(IntValue): def write(self, writer, font, tableDict, value, repeatIndex=None): writer.writeULong(value) +class Flags32(ULong): + def xmlWrite(self, xmlWriter, font, value, name, attrs): + xmlWriter.simpletag(name, attrs + [("value", "0x%08X" % value)]) + xmlWriter.newline() + class Short(IntValue): staticSize = 2 def read(self, reader, font, tableDict): @@ -409,6 +416,25 @@ class Struct(BaseConverter): return "Struct of " + repr(self.tableClass) +class StructWithLength(Struct): + def read(self, reader, font, tableDict): + pos = reader.pos + table = self.tableClass() + table.decompile(reader, font) + reader.seek(pos + table.StructLength) + return table + + def write(self, writer, font, tableDict, value, repeatIndex=None): + value.compile(writer, font) + assert 0, "Fix length" + + +class SubStruct(Struct): + def getConverter(self, tableType, lookupType): + tableClass = self.lookupTypes[tableType][lookupType] + return self.__class__(self.name, self.repeat, self.aux, tableClass) + + class Table(Struct): longOffset = False @@ -511,6 +537,88 @@ class ValueRecord(ValueFormat): return value +class AATLookup(BaseConverter): + def read(self, reader, font, tableDict): + format = reader.readUShort() + if format == 0: + glyphs = font.getGlyphOrder() + mapping = self.readFormat0(reader, len(glyphs)) + elif format == 2: + mapping = self.readFormat2(reader) + elif format == 4: + mapping = self.readFormat4(reader) + elif format == 6: + mapping = self.readFormat6(reader) + elif format == 8: + mapping = self.readFormat8(reader) + else: + assert False, "unsupported lookup format: %d" % format + return {font.getGlyphName(k):font.getGlyphName(v) + for k, v in mapping.items() if k != v} + + def readFormat0(self, reader, numGlyphs): + data = reader.readUShortArray(numGlyphs) + return {k:v for (k,v) in enumerate(data)} + + def readFormat2(self, reader): + mapping = {} + pos = reader.pos - 2 # start of table is at UShort for format + size = reader.readUShort() + assert size == 6, size + for i in range(reader.readUShort()): + reader.seek(pos + i * size + 12) + last = reader.readUShort() + first = reader.readUShort() + value = reader.readUShort() + if last != 0xFFFF: + for k in range(first, last + 1): + mapping[k] = value + return mapping + + def readFormat4(self, reader): + mapping = {} + pos = reader.pos - 2 # start of table is at UShort for format + size = reader.readUShort() + assert size == 6, size + for i in range(reader.readUShort()): + reader.seek(pos + i * size + 12) + last = reader.readUShort() + first = reader.readUShort() + offset = reader.readUShort() + if last != 0xFFFF: + dataReader = reader.getSubReader(pos + offset) + data = dataReader.readUShortArray(last - first + 1) + for k, v in enumerate(data): + mapping[first + k] = v + return mapping + + def readFormat6(self, reader): + mapping = {} + pos = reader.pos - 2 # start of table is at UShort for format + size = reader.readUShort() + assert size == 4, size + for i in range(reader.readUShort()): + reader.seek(pos + i * size + 12) + glyph = reader.readUShort() + value = reader.readUShort() + if glyph != 0xFFFF: + mapping[glyph] = value + return mapping + + def readFormat8(self, reader): + first = reader.readUShort() + count = reader.readUShort() + data = reader.readUShortArray(count) + return {(first + k):v for (k, v) in enumerate(data)} + + def xmlWrite(self, xmlWriter, font, value, name, attrs): + items = sorted(value.items()) + for inGlyph, outGlyph in items: + xmlWriter.simpletag("Substitution", + [("in", inGlyph), ("out", outGlyph)]) + xmlWriter.newline() + + class DeltaValue(BaseConverter): def read(self, reader, font, tableDict): @@ -660,6 +768,7 @@ converterMapping = { "uint24": UInt24, "uint32": ULong, "char64": Char64, + "Flags32": Flags32, "Version": Version, "Tag": Tag, "GlyphID": GlyphID, @@ -674,6 +783,10 @@ converterMapping = { "DeltaValue": DeltaValue, "VarIdxMapValue": VarIdxMapValue, "VarDataValue": VarDataValue, + # AAT + "AATLookup": AATLookup, + "MorphChain": StructWithLength, + "MorphSubtable":StructWithLength, # "Template" types "OffsetTo": lambda C: partial(Table, tableClass=C), "LOffsetTo": lambda C: partial(LTable, tableClass=C), diff --git a/Tests/ttLib/tables/otConverters_test.py b/Tests/ttLib/tables/otConverters_test.py index 3b7796d8c..4af593cae 100644 --- a/Tests/ttLib/tables/otConverters_test.py +++ b/Tests/ttLib/tables/otConverters_test.py @@ -174,6 +174,83 @@ class UInt8Test(unittest.TestCase): self.assertEqual(xml, '') +class AATLookupTest(unittest.TestCase): + font = FakeFont(".notdef A B C D E F G H".split()) + converter = otConverters.AATLookup("AATLookup", 0, None, None) + + def __init__(self, methodName): + unittest.TestCase.__init__(self, methodName) + # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, + # and fires deprecation warnings if a program uses the old name. + if not hasattr(self, "assertRaisesRegex"): + self.assertRaisesRegex = self.assertRaisesRegexp + + def test_readFormat0(self): + reader = OTTableReader(deHexStr("0000 0000 0001 0002 0000 7D00 0001")) + self.assertEqual(self.converter.read(reader, self.font, None), { + "C": ".notdef", + "D": "glyph32000", + "E": "A" + }) + + def test_readFormat2(self): + reader = OTTableReader(deHexStr( + "0002 0006 0003 000C 0001 0006 " + "0002 0001 0003 " # glyph A..B: map to C + "0007 0005 0008 " # glyph E..G: map to H + "FFFF FFFF FFFF")) # end of search table + self.assertEqual(self.converter.read(reader, self.font, None), { + "A": "C", + "B": "C", + "E": "H", + "F": "H", + "G": "H", + }) + + def test_readFormat4(self): + reader = OTTableReader(deHexStr( + "0004 0006 0003 000C 0001 0006 " + "0002 0001 001E " # glyph 1..2: mapping at offset 0x1E + "0005 0004 001E " # glyph 4..5: mapping at offset 0x1E + "FFFF FFFF FFFF " # end of search table + "0007 0008")) # offset 0x18: glyphs [7, 8] = [G, H] + self.assertEqual(self.converter.read(reader, self.font, None), { + "A": "G", + "B": "H", + "D": "G", + "E": "H", + }) + + def test_readFormat6(self): + reader = OTTableReader(deHexStr( + "0006 0004 0003 0008 0001 0004 " + "0003 0001 " # C --> A + "0005 0002 " # E --> B + "FFFF FFFF")) # end of search table + self.assertEqual(self.converter.read(reader, self.font, None), { + "C": "A", + "E": "B", + }) + + def test_readFormat8(self): + reader = OTTableReader(deHexStr( + "0008 " + "0003 0003 " # first: C, count: 3 + "0007 0001 0002")) # [G, A, B] + self.assertEqual(self.converter.read(reader, self.font, None), { + "C": "G", + "D": "A", + "E": "B", + }) + + def test_readUnknownFormat(self): + reader = OTTableReader(deHexStr("0009")) + self.assertRaisesRegex( + AssertionError, + "unsupported lookup format: 9", + self.converter.read, reader, self.font, None) + + if __name__ == "__main__": import sys sys.exit(unittest.main())