Repository: fonttools/fonttools

Issue:
Apple kern tables are wrongly compiled
The Apple kern table (with uint32 version) has different subtable format than the OpenType one.  We ignore this when compiling kern subtables.
https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
https://www.microsoft.com/typography/otspec/kern.htm


Discussion:
could this one also be related? https://github.com/fonttools/fonttools/issues/999
Yep.
it looks like in the decompile code we do set an `subtable.apple = True` attribute when kern table version is 1. We just ignore it while compiling the subtables. It shouldn't be difficult to fix. 
Definitely not hard.  Just clumsy.  Note that `tupleIndex` is loaded from Apple tables but not stored in XML...  Also, coverage bits mean different things in OT versus Apple spec.  Moreover, the subtable version field of OpenType is bogus and unused.  
yes, and for the Apple `coverage` field only the low byte is kept (`coverage & 0xFF`), i.e. the one wich defines the subtable format, the high byte is thrown away.
There seems to be a confusion between the subtable version number (only in OT kern) and the kern subtable format.

Also we set the "subtable.apple" attribute only when decompiling from binary, but not when reading kern table version="1.0" from XML.

Resolving patch:
diff --git a/Lib/fontTools/ttLib/tables/_k_e_r_n.py b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
--- a/Lib/fontTools/ttLib/tables/_k_e_r_n.py
+++ b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
@@ -2,7 +2,9 @@
 from fontTools.misc.py23 import *
 from fontTools.ttLib import getSearchRange
 from fontTools.misc.textTools import safeEval, readHex
-from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
+from fontTools.misc.fixedTools import (
+	fixedToFloat as fi2fl,
+	floatToFixed as fl2fi)
 from . import DefaultTable
 import struct
 import sys
@@ -17,7 +19,7 @@ class table__k_e_r_n(DefaultTable.DefaultTable):
 
 	def getkern(self, format):
 		for subtable in self.kernTables:
-			if subtable.version == format:
+			if subtable.format == format:
 				return subtable
 		return None  # not found
 
@@ -33,21 +35,23 @@ def decompile(self, data, ttFont):
 		else:
 			self.version = version
 			data = data[4:]
-		tablesIndex = []
 		self.kernTables = []
 		for i in range(nTables):
 			if self.version == 1.0:
 				# Apple
-				length, coverage, tupleIndex = struct.unpack(">lHH", data[:8])
-				version = coverage & 0xff
+				length, coverage, subtableFormat = struct.unpack(
+					">LBB", data[:6])
 			else:
-				version, length = struct.unpack(">HH", data[:4])
-			length = int(length)
-			if version not in kern_classes:
-				subtable = KernTable_format_unkown(version)
+				# in OpenType spec the "version" field refers to the common
+				# subtable header; the actual subtable format is stored in
+				# the 8-15 mask bits of "coverage" field.
+				# This "version" is always 0 so we ignore it here
+				_, length, subtableFormat, coverage = struct.unpack(
+					">HHBB", data[:6])
+			if subtableFormat not in kern_classes:
+				subtable = KernTable_format_unkown(subtableFormat)
 			else:
-				subtable = kern_classes[version]()
-			subtable.apple = apple
+				subtable = kern_classes[subtableFormat](apple)
 			subtable.decompile(data[:length], ttFont)
 			self.kernTables.append(subtable)
 			data = data[length:]
@@ -59,7 +63,7 @@ def compile(self, ttFont):
 			nTables = 0
 		if self.version == 1.0:
 			# AAT Apple's "new" format.
-			data = struct.pack(">ll", fl2fi(self.version, 16), nTables)
+			data = struct.pack(">LL", fl2fi(self.version, 16), nTables)
 		else:
 			data = struct.pack(">HH", self.version, nTables)
 		if hasattr(self, "kernTables"):
@@ -85,80 +89,142 @@ def fromXML(self, name, attrs, content, ttFont):
 		if format not in kern_classes:
 			subtable = KernTable_format_unkown(format)
 		else:
-			subtable = kern_classes[format]()
+			apple = self.version == 1.0
+			subtable = kern_classes[format](apple)
 		self.kernTables.append(subtable)
 		subtable.fromXML(name, attrs, content, ttFont)
 
 
 class KernTable_format_0(object):
 
+	# 'version' is kept for backward compatibility
+	version = format = 0
+
+	def __init__(self, apple=False):
+		self.apple = apple
+
 	def decompile(self, data, ttFont):
-		version, length, coverage = (0,0,0)
 		if not self.apple:
-			version, length, coverage = struct.unpack(">HHH", data[:6])
+			version, length, subtableFormat, coverage = struct.unpack(
+				">HHBB", data[:6])
+			if version != 0:
+				from fontTools.ttLib import TTLibError
+				raise TTLibError(
+					"unsupported kern subtable version: %d" % version)
+			tupleIndex = None
+			# Should we also assert length == len(data)?
 			data = data[6:]
 		else:
-			version, length, coverage = struct.unpack(">LHH", data[:8])
+			length, coverage, subtableFormat, tupleIndex = struct.unpack(
+				">LBBH", data[:8])
 			data = data[8:]
-		self.version, self.coverage = int(version), int(coverage)
+		assert self.format == subtableFormat, "unsupported format"
+		self.coverage = coverage
+		self.tupleIndex = tupleIndex
 
 		self.kernTable = kernTable = {}
 
-		nPairs, searchRange, entrySelector, rangeShift = struct.unpack(">HHHH", data[:8])
+		nPairs, searchRange, entrySelector, rangeShift = struct.unpack(
+			">HHHH", data[:8])
 		data = data[8:]
 
 		nPairs = min(nPairs, len(data) // 6)
 		datas = array.array("H", data[:6 * nPairs])
-		if sys.byteorder != "big":
+		if sys.byteorder != "big":  # pragma: no cover
 			datas.byteswap()
 		it = iter(datas)
 		glyphOrder = ttFont.getGlyphOrder()
 		for k in range(nPairs):
 			left, right, value = next(it), next(it), next(it)
-			if value >= 32768: value -= 65536
+			if value >= 32768:
+				value -= 65536
 			try:
 				kernTable[(glyphOrder[left], glyphOrder[right])] = value
 			except IndexError:
-				# Slower, but will not throw an IndexError on an invalid glyph id.
-				kernTable[(ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value
-		if len(data) > 6 * nPairs + 4: # Ignore up to 4 bytes excess
-			log.warning("excess data in 'kern' subtable: %d bytes", len(data) - 6 * nPairs)
+				# Slower, but will not throw an IndexError on an invalid
+				# glyph id.
+				kernTable[(
+					ttFont.getGlyphName(left),
+					ttFont.getGlyphName(right))] = value
+		if len(data) > 6 * nPairs + 4:  # Ignore up to 4 bytes excess
+			log.warning(
+				"excess data in 'kern' subtable: %d bytes",
+				len(data) - 6 * nPairs)
 
 	def compile(self, ttFont):
 		nPairs = len(self.kernTable)
 		searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6)
-		data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift)
+		data = struct.pack(
+			">HHHH", nPairs, searchRange, entrySelector, rangeShift)
 
 		# yeehee! (I mean, turn names into indices)
 		try:
 			reverseOrder = ttFont.getReverseGlyphMap()
-			kernTable = sorted((reverseOrder[left], reverseOrder[right], value) for ((left,right),value) in self.kernTable.items())
+			kernTable = sorted(
+				(reverseOrder[left], reverseOrder[right], value)
+				for ((left, right), value) in self.kernTable.items())
 		except KeyError:
 			# Slower, but will not throw KeyError on invalid glyph id.
 			getGlyphID = ttFont.getGlyphID
-			kernTable = sorted((getGlyphID(left), getGlyphID(right), value) for ((left,right),value) in self.kernTable.items())
+			kernTable = sorted(
+				(getGlyphID(left), getGlyphID(right), value)
+				for ((left, right), value) in self.kernTable.items())
 
 		for left, right, value in kernTable:
 			data = data + struct.pack(">HHh", left, right, value)
-		return struct.pack(">HHH", self.version, len(data) + 6, self.coverage) + data
+
+		if not self.apple:
+			version = 0
+			length = len(data) + 6
+			header = struct.pack(
+				">HHBB", version, length, self.format, self.coverage)
+		else:
+			if self.tupleIndex is None:
+				# sensible default when compiling a TTX from an old fonttools
+				# or when inserting a Windows-style format 0 subtable into an
+				# Apple version=1.0 kern table
+				log.warning("'tupleIndex' is None; default to 0")
+				self.tupleIndex = 0
+			length = len(data) + 8
+			header = struct.pack(
+				">LBBH", length, self.coverage, self.format, self.tupleIndex)
+		return header + data
 
 	def toXML(self, writer, ttFont):
-		writer.begintag("kernsubtable", coverage=self.coverage, format=0)
+		attrs = dict(coverage=self.coverage, format=self.format)
+		if self.apple:
+			if self.tupleIndex is None:
+				log.warning("'tupleIndex' is None; default to 0")
+				attrs["tupleIndex"] = 0
+			else:
+				attrs["tupleIndex"] = self.tupleIndex
+		writer.begintag("kernsubtable", **attrs)
 		writer.newline()
 		items = sorted(self.kernTable.items())
 		for (left, right), value in items:
 			writer.simpletag("pair", [
-					("l", left),
-					("r", right),
-					("v", value)
-					])
+				("l", left),
+				("r", right),
+				("v", value)
+			])
 			writer.newline()
 		writer.endtag("kernsubtable")
 		writer.newline()
 
 	def fromXML(self, name, attrs, content, ttFont):
 		self.coverage = safeEval(attrs["coverage"])
-		self.version = safeEval(attrs["format"])
+		subtableFormat = safeEval(attrs["format"])
+		if self.apple:
+			if "tupleIndex" in attrs:
+				self.tupleIndex = safeEval(attrs["tupleIndex"])
+			else:
+				# previous fontTools versions didn't export tupleIndex
+				log.warning(
+					"Apple kern subtable is missing 'tupleIndex' attribute")
+				self.tupleIndex = None
+		else:
+			self.tupleIndex = None
+		assert subtableFormat == self.format, "unsupported format"
 		if not hasattr(self, "kernTable"):
 			self.kernTable = {}
 		for element in content:


Test patch:
diff --git a/Tests/ttLib/tables/_k_e_r_n_test.py b/Tests/ttLib/tables/_k_e_r_n_test.py
--- a/Tests/ttLib/tables/_k_e_r_n_test.py
+++ b/Tests/ttLib/tables/_k_e_r_n_test.py
@@ -1,30 +1,370 @@
 from __future__ import print_function, absolute_import
 from fontTools.misc.py23 import *
-from fontTools import ttLib
-import unittest
-from fontTools.ttLib.tables._k_e_r_n import KernTable_format_0
+from fontTools.ttLib import newTable
+from fontTools.ttLib.tables._k_e_r_n import (
+    KernTable_format_0, KernTable_format_unkown)
+from fontTools.misc.textTools import deHexStr
+from fontTools.misc.testTools import FakeFont, getXML, parseXML
+import pytest
 
-class MockFont(object):
 
-        def getGlyphOrder(self):
-                return ["glyph00000", "glyph00001", "glyph00002", "glyph00003"]
+KERN_VER_0_FMT_0_DATA = deHexStr(
+    '0000 '            #  0: version=0
+    '0001 '            #  2: nTables=1
+    '0000 '            #  4: version=0 (bogus field, unused)
+    '0020 '            #  6: length=32
+    '00 '              #  8: format=0
+    '01 '              #  9: coverage=1
+    '0003 '            # 10: nPairs=3
+    '000C '            # 12: searchRange=12
+    '0001 '            # 14: entrySelector=1
+    '0006 '            # 16: rangeShift=6
+    '0004 000C FFD8 '  # 18: l=4, r=12, v=-40
+    '0004 001C 0028 '  # 24: l=4, r=28, v=40
+    '0005 0028 FFCE '  # 30: l=5, r=40, v=-50
+)
+assert len(KERN_VER_0_FMT_0_DATA) == 36
 
-        def getGlyphName(self, glyphID):
-                return "glyph%.5d" % glyphID
+KERN_VER_0_FMT_0_XML = [
+    '<version value="0"/>',
+    '<kernsubtable coverage="1" format="0">',
+    '  <pair l="E" r="M" v="-40"/>',
+    '  <pair l="E" r="c" v="40"/>',
+    '  <pair l="F" r="o" v="-50"/>',
+    '</kernsubtable>',
+]
 
-class KernTable_format_0_Test(unittest.TestCase):
+KERN_VER_1_FMT_0_DATA = deHexStr(
+    '0001 0000 '       #  0: version=1
+    '0000 0001 '       #  4: nTables=1
+    '0000 0022 '       #  8: length=34
+    '00 '              # 12: coverage=0
+    '00 '              # 13: format=0
+    '0000 '            # 14: tupleIndex=0
+    '0003 '            # 16: nPairs=3
+    '000C '            # 18: searchRange=12
+    '0001 '            # 20: entrySelector=1
+    '0006 '            # 22: rangeShift=6
+    '0004 000C FFD8 '  # 24: l=4, r=12, v=-40
+    '0004 001C 0028 '  # 30: l=4, r=28, v=40
+    '0005 0028 FFCE '  # 36: l=5, r=40, v=-50
+)
+assert len(KERN_VER_1_FMT_0_DATA) == 42
+
+KERN_VER_1_FMT_0_XML = [
+    '<version value="1.0"/>',
+    '<kernsubtable coverage="0" format="0" tupleIndex="0">',
+    '  <pair l="E" r="M" v="-40"/>',
+    '  <pair l="E" r="c" v="40"/>',
+    '  <pair l="F" r="o" v="-50"/>',
+    '</kernsubtable>',
+]
+
+KERN_VER_0_FMT_UNKNOWN_DATA = deHexStr(
+    '0000 '            #  0: version=0
+    '0002 '            #  2: nTables=2
+    '0000 '            #  4: version=0
+    '000A '            #  6: length=10
+    '04 '              #  8: format=4  (format 4 doesn't exist)
+    '01 '              #  9: coverage=1
+    '1234 5678 '       # 10: garbage...
+    '0000 '            # 14: version=0
+    '000A '            # 16: length=10
+    '05 '              # 18: format=5  (format 5 doesn't exist)
+    '01 '              # 19: coverage=1
+    '9ABC DEF0 '       # 20: garbage...
+)
+assert len(KERN_VER_0_FMT_UNKNOWN_DATA) == 24
+
+KERN_VER_0_FMT_UNKNOWN_XML = [
+    '<version value="0"/>',
+    '<kernsubtable format="4">',
+    "  <!-- unknown 'kern' subtable format -->",
+    '  0000000A 04011234',
+    '  5678             ',
+    '</kernsubtable>',
+    '<kernsubtable format="5">',
+    "<!-- unknown 'kern' subtable format -->",
+    '  0000000A 05019ABC',
+    '  DEF0             ',
+    '</kernsubtable>',
+]
+
+KERN_VER_1_FMT_UNKNOWN_DATA = deHexStr(
+    '0001 0000 '       #  0: version=1
+    '0000 0002 '       #  4: nTables=2
+    '0000 000C '       #  8: length=12
+    '00 '              # 12: coverage=0
+    '04 '              # 13: format=4  (format 4 doesn't exist)
+    '0000 '            # 14: tupleIndex=0
+    '1234 5678'        # 16: garbage...
+    '0000 000C '       # 20: length=12
+    '00 '              # 24: coverage=0
+    '05 '              # 25: format=5  (format 5 doesn't exist)
+    '0000 '            # 26: tupleIndex=0
+    '9ABC DEF0 '       # 28: garbage...
+)
+assert len(KERN_VER_1_FMT_UNKNOWN_DATA) == 32
+
+KERN_VER_1_FMT_UNKNOWN_XML = [
+    '<version value="1"/>',
+    '<kernsubtable format="4">',
+    "  <!-- unknown 'kern' subtable format -->",
+    '  0000000C 00040000',
+    '  12345678         ',
+    '</kernsubtable>',
+    '<kernsubtable format="5">',
+    "  <!-- unknown 'kern' subtable format -->",
+    '  0000000C 00050000',
+    '  9ABCDEF0         ',
+    '</kernsubtable>',
+]
+
+
+@pytest.fixture
+def font():
+    return FakeFont(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                         "abcdefghijklmnopqrstuvwxyz"))
+
+
+class KernTableTest(object):
+
+    @pytest.mark.parametrize(
+        "data, version",
+        [
+            (KERN_VER_0_FMT_0_DATA, 0),
+            (KERN_VER_1_FMT_0_DATA, 1.0),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_decompile_single_format_0(self, data, font, version):
+        kern = newTable("kern")
+        kern.decompile(data, font)
+
+        assert kern.version == version
+        assert len(kern.kernTables) == 1
+
+        st = kern.kernTables[0]
+        assert st.apple is (version == 1.0)
+        assert st.format == 0
+        # horizontal kerning in OT kern is coverage 0x01, while in
+        # AAT kern it's the default (0)
+        assert st.coverage == (0 if st.apple else 1)
+        assert st.tupleIndex == (0 if st.apple else None)
+        assert len(st.kernTable) == 3
+        assert st.kernTable == {
+            ('E', 'M'): -40,
+            ('E', 'c'): 40,
+            ('F', 'o'): -50
+        }
+
+    @pytest.mark.parametrize(
+        "version, expected",
+        [
+            (0, KERN_VER_0_FMT_0_DATA),
+            (1.0, KERN_VER_1_FMT_0_DATA),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_compile_single_format_0(self, font, version, expected):
+        kern = newTable("kern")
+        kern.version = version
+        apple = version == 1.0
+        st = KernTable_format_0(apple)
+        kern.kernTables = [st]
+        st.coverage = (0 if apple else 1)
+        st.tupleIndex = 0 if apple else None
+        st.kernTable = {
+            ('E', 'M'): -40,
+            ('E', 'c'): 40,
+            ('F', 'o'): -50
+        }
+        data = kern.compile(font)
+        assert data == expected
+
+    @pytest.mark.parametrize(
+        "xml, version",
+        [
+            (KERN_VER_0_FMT_0_XML, 0),
+            (KERN_VER_1_FMT_0_XML, 1.0),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_fromXML_single_format_0(self, xml, font, version):
+        kern = newTable("kern")
+        for name, attrs, content in parseXML(xml):
+            kern.fromXML(name, attrs, content, ttFont=font)
+
+        assert kern.version == version
+        assert len(kern.kernTables) == 1
+
+        st = kern.kernTables[0]
+        assert st.apple is (version == 1.0)
+        assert st.format == 0
+        assert st.coverage == (0 if st.apple else 1)
+        assert st.tupleIndex == (0 if st.apple else None)
+        assert len(st.kernTable) == 3
+        assert st.kernTable == {
+            ('E', 'M'): -40,
+            ('E', 'c'): 40,
+            ('F', 'o'): -50
+        }
+
+    @pytest.mark.parametrize(
+        "version, expected",
+        [
+            (0, KERN_VER_0_FMT_0_XML),
+            (1.0, KERN_VER_1_FMT_0_XML),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_toXML_single_format_0(self, font, version, expected):
+        kern = newTable("kern")
+        kern.version = version
+        apple = version == 1.0
+        st = KernTable_format_0(apple)
+        kern.kernTables = [st]
+        st.coverage = 0 if apple else 1
+        st.tupleIndex = 0 if apple else None
+        st.kernTable = {
+            ('E', 'M'): -40,
+            ('E', 'c'): 40,
+            ('F', 'o'): -50
+        }
+        xml = getXML(kern.toXML, font)
+        assert xml == expected
+
+    @pytest.mark.parametrize(
+        "data, version, header_length, st_length",
+        [
+            (KERN_VER_0_FMT_UNKNOWN_DATA, 0, 4, 10),
+            (KERN_VER_1_FMT_UNKNOWN_DATA, 1.0, 8, 12),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_decompile_format_unknown(
+            self, data, font, version, header_length, st_length):
+        kern = newTable("kern")
+        kern.decompile(data, font)
+
+        assert kern.version == version
+        assert len(kern.kernTables) == 2
+
+        st_data = data[header_length:]
+        st0 = kern.kernTables[0]
+        assert st0.format == 4
+        assert st0.data == st_data[:st_length]
+        st_data = st_data[st_length:]
+
+        st1 = kern.kernTables[1]
+        assert st1.format == 5
+        assert st1.data == st_data[:st_length]
+
+    @pytest.mark.parametrize(
+        "version, st_length, expected",
+        [
+            (0, 10, KERN_VER_0_FMT_UNKNOWN_DATA),
+            (1.0, 12, KERN_VER_1_FMT_UNKNOWN_DATA),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_compile_format_unknown(self, version, st_length, expected):
+        kern = newTable("kern")
+        kern.version = version
+        kern.kernTables = []
+
+        for unknown_fmt, kern_data in zip((4, 5), ("1234 5678", "9ABC DEF0")):
+            if version > 0:
+                coverage = 0
+                header_fmt = deHexStr(
+                    "%08X %02X %02X %04X" % (
+                        st_length, coverage, unknown_fmt, 0))
+            else:
+                coverage = 1
+                header_fmt = deHexStr(
+                    "%04X %04X %02X %02X" % (
+                        0, st_length, unknown_fmt, coverage))
+            st = KernTable_format_unkown(unknown_fmt)
+            st.data = header_fmt + deHexStr(kern_data)
+            kern.kernTables.append(st)
+
+        data = kern.compile(font)
+        assert data == expected
+
+    @pytest.mark.parametrize(
+        "xml, version, st_length",
+        [
+            (KERN_VER_0_FMT_UNKNOWN_XML, 0, 10),
+            (KERN_VER_1_FMT_UNKNOWN_XML, 1.0, 12),
+        ],
+        ids=["version_0", "version_1"]
+    )
+    def test_fromXML_format_unknown(self, xml, font, version, st_length):
+        kern = newTable("kern")
+        for name, attrs, content in parseXML(xml):
+            kern.fromXML(name, attrs, content, ttFont=font)
+
+        assert kern.version == version
+        assert len(kern.kernTables) == 2
+
+        st0 = kern.kernTables[0]
+        assert st0.format == 4
+        assert len(st0.data) == st_length
+
+        st1 = kern.kernTables[1]
+        assert st1.format == 5
+        assert len(st1.data) == st_length
+
+    @pytest.mark.parametrize(
+        "version", [0, 1.0], ids=["version_0", "version_1"])
+    def test_toXML_format_unknown(self, font, version):
+        kern = newTable("kern")
+        kern.version = version
+        st = KernTable_format_unkown(4)
+        st.data = b"ABCD"
+        kern.kernTables = [st]
+
+        xml = getXML(kern.toXML, font)
+
+        assert xml == [
+            '<version value="%s"/>' % version,
+            '<kernsubtable format="4">',
+            '  <!-- unknown \'kern\' subtable format -->',
+            '  41424344   ',
+            '</kernsubtable>',
+        ]
+
+    def test_getkern(self):
+        table = newTable("kern")
+        table.version = 0
+        table.kernTables = []
+
+        assert table.getkern(0) is None
+
+        st0 = KernTable_format_0()
+        table.kernTables.append(st0)
+
+        assert table.getkern(0) is st0
+        assert table.getkern(4) is None
+
+        st1 = KernTable_format_unkown(4)
+        table.kernTables.append(st1)
+
+
+class KernTable_format_0_Test(object):
+
+    def test_decompileBadGlyphId(self, font):
+        subtable = KernTable_format_0()
+        subtable.decompile(
+            b'\x00' + b'\x00' + b'\x00' + b'\x1a' + b'\x00' + b'\x00' +
+            b'\x00' + b'\x02' + b'\x00' * 6 +
+            b'\x00' + b'\x01' + b'\x00' + b'\x03' + b'\x00' + b'\x01' +
+            b'\x00' + b'\x01' + b'\xFF' + b'\xFF' + b'\x00' + b'\x02',
+            font)
+        assert subtable[("B", "D")] == 1
+        assert subtable[("B", "glyph65535")] == 2
 
-        def test_decompileBadGlyphId(self):
-                subtable = KernTable_format_0()
-                subtable.apple = False
-                subtable.decompile(  b'\x00' * 6
-                                   + b'\x00' + b'\x02' + b'\x00' * 6
-                                   + b'\x00' + b'\x01' + b'\x00' + b'\x03' + b'\x00' + b'\x01'
-                                   + b'\x00' + b'\x01' + b'\xFF' + b'\xFF' + b'\x00' + b'\x02',
-                                   MockFont())
-                self.assertEqual(subtable[("glyph00001", "glyph00003")], 1)
-                self.assertEqual(subtable[("glyph00001", "glyph65535")], 2)
 
 if __name__ == "__main__":
-        import sys
-        sys.exit(unittest.main())
+    import sys
+    sys.exit(pytest.main(sys.argv))