diff --git a/Lib/fontTools/ttLib/tables/_k_e_r_n.py b/Lib/fontTools/ttLib/tables/_k_e_r_n.py index 98eb70923..1e5140bc8 100644 --- a/Lib/fontTools/ttLib/tables/_k_e_r_n.py +++ b/Lib/fontTools/ttLib/tables/_k_e_r_n.py @@ -48,6 +48,19 @@ class table__k_e_r_n(DefaultTable.DefaultTable): # This "version" is always 0 so we ignore it here _, length, subtableFormat, coverage = struct.unpack( ">HHBB", data[:6]) + if nTables == 1 and subtableFormat == 0: + # The "length" value is ignored since some fonts + # (like OpenSans and Calibri) have a subtable larger than + # its value. + nPairs, = struct.unpack(">H", data[6:8]) + calculated_length = (nPairs * 6) + 14 + if length != calculated_length: + log.warning( + "'kern' subtable longer than defined: " + "%d bytes instead of %d bytes" % + (calculated_length, length) + ) + length = calculated_length if subtableFormat not in kern_classes: subtable = KernTable_format_unkown(subtableFormat) else: @@ -128,7 +141,6 @@ class KernTable_format_0(object): ">HHHH", data[:8]) data = data[8:] - nPairs = min(nPairs, len(data) // 6) datas = array.array("H", data[:6 * nPairs]) if sys.byteorder != "big": datas.byteswap() it = iter(datas) @@ -153,6 +165,7 @@ class KernTable_format_0(object): def compile(self, ttFont): nPairs = len(self.kernTable) searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6) + searchRange &= 0xFFFF data = struct.pack( ">HHHH", nPairs, searchRange, entrySelector, rangeShift) @@ -175,6 +188,10 @@ class KernTable_format_0(object): if not self.apple: version = 0 length = len(data) + 6 + if length >= 0x10000: + log.warning('"kern" subtable overflow, ' + 'truncating length value while preserving pairs.') + length &= 0xFFFF header = struct.pack( ">HHBB", version, length, self.format, self.coverage) else: diff --git a/Tests/ttLib/tables/_k_e_r_n_test.py b/Tests/ttLib/tables/_k_e_r_n_test.py index 8ab1b1ccf..b37748ad3 100644 --- a/Tests/ttLib/tables/_k_e_r_n_test.py +++ b/Tests/ttLib/tables/_k_e_r_n_test.py @@ -5,6 +5,7 @@ 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 itertools import pytest @@ -120,12 +121,32 @@ KERN_VER_1_FMT_UNKNOWN_XML = [ '', ] +KERN_VER_0_FMT_0_OVERFLOWING_DATA = deHexStr( + '0000 ' # 0: version=0 + '0001 ' # 2: nTables=1 + '0000 ' # 4: version=0 (bogus field, unused) + '0274 ' # 6: length=628 (bogus value for 66164 % 0x10000) + '00 ' # 8: format=0 + '01 ' # 9: coverage=1 + '2B11 ' # 10: nPairs=11025 + 'C000 ' # 12: searchRange=49152 + '000D ' # 14: entrySelector=13 + '4266 ' # 16: rangeShift=16998 +) + deHexStr(' '.join( + '%04X %04X %04X' % (a, b, 0) + for (a, b) in itertools.product(range(105), repeat=2) +)) + @pytest.fixture def font(): return FakeFont(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz")) +@pytest.fixture +def overflowing_font(): + return FakeFont(["glyph%i" % i for i in range(105)]) + class KernTableTest(object): @@ -364,6 +385,36 @@ class KernTable_format_0_Test(object): assert subtable[("B", "D")] == 1 assert subtable[("B", "glyph65535")] == 2 + def test_compileOverflowingSubtable(self, overflowing_font): + font = overflowing_font + kern = newTable("kern") + kern.version = 0 + st = KernTable_format_0(0) + kern.kernTables = [st] + st.coverage = 1 + st.tupleIndex = None + st.kernTable = { + (a, b): 0 + for (a, b) in itertools.product( + font.getGlyphOrder(), repeat=2) + } + assert len(st.kernTable) == 11025 + data = kern.compile(font) + assert data == KERN_VER_0_FMT_0_OVERFLOWING_DATA + + def test_decompileOverflowingSubtable(self, overflowing_font): + font = overflowing_font + data = KERN_VER_0_FMT_0_OVERFLOWING_DATA + kern = newTable("kern") + kern.decompile(data, font) + + st = kern.kernTables[0] + assert st.kernTable == { + (a, b): 0 + for (a, b) in itertools.product( + font.getGlyphOrder(), repeat=2) + } + if __name__ == "__main__": import sys