Repository: fonttools/fonttools

Issue:
ignore length of the last 'kern' subtable"
I noticed this warning with ttx'ing Open Sans Bold; the other styles in Open Sans don't have this issue:

``` sh
$ git clone https://github.com/google/fonts.git ; 
$ ttx fonts/apache/opensans/OpenSans-Bold.ttf ;
Dumping "OpenSans-Bold.ttf" to "OpenSans-Bold.ttx"...
Dumping 'GlyphOrder' table...
Dumping 'head' table...
Dumping 'hhea' table...
Dumping 'maxp' table...
Dumping 'OS/2' table...
Dumping 'hmtx' table...
Dumping 'cmap' table...
Dumping 'fpgm' table...
Dumping 'prep' table...
Dumping 'cvt ' table...
Dumping 'loca' table...
Dumping 'glyf' table...
/Library/Python/2.7/site-packages/fonttools-2.5-py2.7.egg/fontTools/ttLib/tables/_k_e_r_n.py:121: UserWarning: excess data in 'kern' subtable: 46628 bytes
Dumping 'kern' table...
Dumping 'name' table...
Dumping 'post' table...
Dumping 'gasp' table...
Dumping 'GDEF' table...
Dumping 'GPOS' table...
Dumping 'GSUB' table...
Dumping 'DSIG' table...
$ ttx OpenSans-Bold.ttx ;
$ ls -l OpenSans-Bold.ttf OpenSans-Bold#1.ttf ;
-rw-r--r--  1 user  group   158,720 Jul  1 20:53 OpenSans-Bold#1.ttf
-rw-r--r--  1 user  group   224,592 Jan 15 16:28 OpenSans-Bold.ttf
```

Since the data doesn't round trip, I guess its a bug in Open Sans, but before reporting this to the developer, I thought I'd file an issue to better understand this error :)



Discussion:
Either bug in font, or in our tools. :)  Please report to developer.

Ruh roh:

```
$ ot-sanitise OpenSans-Bold.ttf 
ERROR: kern: Too large subtable., table discarded
```

Is it conceivable that a canny fontTools script could heal this? :)

If the OTS error is to be trusted, then the suntable is larger than what it takes to store 65536 kerning pairs (the largest possible number), which probably indicates a faulty translation of `GPOS` pair positioning to old style `kern` table.

Hmm. It seems like ttx already heals this. When I compare the ttx of the original and the ttx-round-tripped files, their TTX data is the same; the `kern` tables have the same size. I even made a 2nd round trip to be sure... 

```
$ mkdir Original; cp OpenSans-Bold.ttf Original/ ;
$ mkdir RoundTrip; mv OpenSans-Bold#1.ttf RoundTrip/OpenSans-Bold.ttf ;
$ ttx -s */OpenSans-Bold.ttf ;
$ diff Original/ RoundTrip/ ;
diff Original/OpenSans-Bold._h_e_a_d.ttx RoundTrip/OpenSans-Bold._h_e_a_d.ttx
8c8
<     <checkSumAdjustment value="0x3aa939b3"/>
---
>     <checkSumAdjustment value="0x370a73eb"/>
10c10
<     <flags value="00000000 00001001"/>
---
>     <flags value="00000000 00001011"/>
13c13
<     <modified value="Thu May  5 12:37:53 2011"/>
---
>     <modified value="Thu Jul  2 00:51:54 2015"/>
Binary files Original/OpenSans-Bold.ttf and RoundTrip/OpenSans-Bold.ttf differ
$

$ mkdir SecondRoundTrip; ttx -o SecondRoundTrip/OpenSans-Bold.ttf RoundTrip/OpenSans-Bold.ttx ;
$ ttx -s SecondRoundTrip/OpenSans-Bold.ttf ;
$ diff RoundTrip SecondRoundTrip
diff RoundTrip/OpenSans-Bold._h_e_a_d.ttx SecondRoundTrip/OpenSans-Bold._h_e_a_d.ttx
8c8
<     <checkSumAdjustment value="0x370a5b31"/>
---
>     <checkSumAdjustment value="0x370a572b"/>
13c13
<     <modified value="Thu Jul  2 01:44:39 2015"/>
---
>     <modified value="Thu Jul  2 01:53:14 2015"/>
Binary files RoundTrip/OpenSans-Bold.ttf and SecondRoundTrip/OpenSans-Bold.ttf differ
$

$ ls -alFR Original/ RoundTrip/ SecondRoundTrip/ | grep k_e_r_n
-rw-r--r--   1 dcrossland  nonconf   353655 Jul  1 21:53 OpenSans-Bold._k_e_r_n.ttx
-rw-r--r--   1 dcrossland  nonconf   353655 Jul  1 21:53 OpenSans-Bold._k_e_r_n.ttx
-rw-r--r--   1 dcrossland  nonconf   353655 Jul  1 21:54 OpenSans-Bold._k_e_r_n.ttx
$ ls -alFR Original/ RoundTrip/ SecondRoundTrip/
Original/:
total 4536
drwxr-xr-x  24 dcrossland  nonconf      816 Jul  1 21:53 ./
drwxr-xr-x  28 dcrossland  nonconf      952 Jul  1 21:52 ../
-rw-r--r--   1 dcrossland  nonconf     7712 Jul  1 21:53 OpenSans-Bold.D_S_I_G_.ttx
-rw-r--r--   1 dcrossland  nonconf    41900 Jul  1 21:53 OpenSans-Bold.G_D_E_F_.ttx
-rw-r--r--   1 dcrossland  nonconf     1076 Jul  1 21:53 OpenSans-Bold.G_P_O_S_.ttx
-rw-r--r--   1 dcrossland  nonconf    15219 Jul  1 21:53 OpenSans-Bold.G_S_U_B_.ttx
-rw-r--r--   1 dcrossland  nonconf    37018 Jul  1 21:53 OpenSans-Bold.GlyphOrder.ttx
-rw-r--r--   1 dcrossland  nonconf     1968 Jul  1 21:53 OpenSans-Bold.O_S_2f_2.ttx
-rw-r--r--   1 dcrossland  nonconf    70439 Jul  1 21:53 OpenSans-Bold._c_m_a_p.ttx
-rw-r--r--   1 dcrossland  nonconf     2808 Jul  1 21:53 OpenSans-Bold._c_v_t.ttx
-rw-r--r--   1 dcrossland  nonconf    53706 Jul  1 21:53 OpenSans-Bold._f_p_g_m.ttx
-rw-r--r--   1 dcrossland  nonconf      216 Jul  1 21:53 OpenSans-Bold._g_a_s_p.ttx
-rw-r--r--   1 dcrossland  nonconf  1371014 Jul  1 21:53 OpenSans-Bold._g_l_y_f.ttx
-rw-r--r--   1 dcrossland  nonconf      757 Jul  1 21:53 OpenSans-Bold._h_e_a_d.ttx
-rw-r--r--   1 dcrossland  nonconf      627 Jul  1 21:53 OpenSans-Bold._h_h_e_a.ttx
-rw-r--r--   1 dcrossland  nonconf    45572 Jul  1 21:53 OpenSans-Bold._h_m_t_x.ttx
-rw-r--r--   1 dcrossland  nonconf   353655 Jul  1 21:53 OpenSans-Bold._k_e_r_n.ttx
-rw-r--r--   1 dcrossland  nonconf      163 Jul  1 21:53 OpenSans-Bold._l_o_c_a.ttx
-rw-r--r--   1 dcrossland  nonconf      677 Jul  1 21:53 OpenSans-Bold._m_a_x_p.ttx
-rw-r--r--   1 dcrossland  nonconf     3615 Jul  1 21:53 OpenSans-Bold._n_a_m_e.ttx
-rw-r--r--   1 dcrossland  nonconf    30387 Jul  1 21:53 OpenSans-Bold._p_o_s_t.ttx
-rw-r--r--   1 dcrossland  nonconf     3580 Jul  1 21:53 OpenSans-Bold._p_r_e_p.ttx
-rw-r--r--   1 dcrossland  nonconf   224592 Jan 15 16:28 OpenSans-Bold.ttf
-rw-r--r--   1 dcrossland  nonconf      973 Jul  1 21:53 OpenSans-Bold.ttx

RoundTrip/:
total 4408
drwxr-xr-x  24 dcrossland  nonconf      816 Jul  1 21:53 ./
drwxr-xr-x  28 dcrossland  nonconf      952 Jul  1 21:52 ../
-rw-r--r--   1 dcrossland  nonconf     7712 Jul  1 21:53 OpenSans-Bold.D_S_I_G_.ttx
-rw-r--r--   1 dcrossland  nonconf    41900 Jul  1 21:53 OpenSans-Bold.G_D_E_F_.ttx
-rw-r--r--   1 dcrossland  nonconf     1076 Jul  1 21:53 OpenSans-Bold.G_P_O_S_.ttx
-rw-r--r--   1 dcrossland  nonconf    15219 Jul  1 21:53 OpenSans-Bold.G_S_U_B_.ttx
-rw-r--r--   1 dcrossland  nonconf    37018 Jul  1 21:53 OpenSans-Bold.GlyphOrder.ttx
-rw-r--r--   1 dcrossland  nonconf     1968 Jul  1 21:53 OpenSans-Bold.O_S_2f_2.ttx
-rw-r--r--   1 dcrossland  nonconf    70439 Jul  1 21:53 OpenSans-Bold._c_m_a_p.ttx
-rw-r--r--   1 dcrossland  nonconf     2808 Jul  1 21:53 OpenSans-Bold._c_v_t.ttx
-rw-r--r--   1 dcrossland  nonconf    53706 Jul  1 21:53 OpenSans-Bold._f_p_g_m.ttx
-rw-r--r--   1 dcrossland  nonconf      216 Jul  1 21:53 OpenSans-Bold._g_a_s_p.ttx
-rw-r--r--   1 dcrossland  nonconf  1371014 Jul  1 21:53 OpenSans-Bold._g_l_y_f.ttx
-rw-r--r--   1 dcrossland  nonconf      757 Jul  1 21:53 OpenSans-Bold._h_e_a_d.ttx
-rw-r--r--   1 dcrossland  nonconf      627 Jul  1 21:53 OpenSans-Bold._h_h_e_a.ttx
-rw-r--r--   1 dcrossland  nonconf    45572 Jul  1 21:53 OpenSans-Bold._h_m_t_x.ttx
-rw-r--r--   1 dcrossland  nonconf   353655 Jul  1 21:53 OpenSans-Bold._k_e_r_n.ttx
-rw-r--r--   1 dcrossland  nonconf      163 Jul  1 21:53 OpenSans-Bold._l_o_c_a.ttx
-rw-r--r--   1 dcrossland  nonconf      677 Jul  1 21:53 OpenSans-Bold._m_a_x_p.ttx
-rw-r--r--   1 dcrossland  nonconf     3615 Jul  1 21:53 OpenSans-Bold._n_a_m_e.ttx
-rw-r--r--   1 dcrossland  nonconf    30387 Jul  1 21:53 OpenSans-Bold._p_o_s_t.ttx
-rw-r--r--   1 dcrossland  nonconf     3580 Jul  1 21:53 OpenSans-Bold._p_r_e_p.ttx
-rw-r--r--   1 dcrossland  nonconf   158720 Jul  1 21:51 OpenSans-Bold.ttf
-rw-r--r--   1 dcrossland  nonconf      973 Jul  1 21:53 OpenSans-Bold.ttx

SecondRoundTrip/:
total 4408
drwxr-xr-x  24 dcrossland  nonconf      816 Jul  1 21:54 ./
drwxr-xr-x  28 dcrossland  nonconf      952 Jul  1 21:52 ../
-rw-r--r--   1 dcrossland  nonconf     7712 Jul  1 21:54 OpenSans-Bold.D_S_I_G_.ttx
-rw-r--r--   1 dcrossland  nonconf    41900 Jul  1 21:54 OpenSans-Bold.G_D_E_F_.ttx
-rw-r--r--   1 dcrossland  nonconf     1076 Jul  1 21:54 OpenSans-Bold.G_P_O_S_.ttx
-rw-r--r--   1 dcrossland  nonconf    15219 Jul  1 21:54 OpenSans-Bold.G_S_U_B_.ttx
-rw-r--r--   1 dcrossland  nonconf    37018 Jul  1 21:54 OpenSans-Bold.GlyphOrder.ttx
-rw-r--r--   1 dcrossland  nonconf     1968 Jul  1 21:54 OpenSans-Bold.O_S_2f_2.ttx
-rw-r--r--   1 dcrossland  nonconf    70439 Jul  1 21:54 OpenSans-Bold._c_m_a_p.ttx
-rw-r--r--   1 dcrossland  nonconf     2808 Jul  1 21:54 OpenSans-Bold._c_v_t.ttx
-rw-r--r--   1 dcrossland  nonconf    53706 Jul  1 21:54 OpenSans-Bold._f_p_g_m.ttx
-rw-r--r--   1 dcrossland  nonconf      216 Jul  1 21:54 OpenSans-Bold._g_a_s_p.ttx
-rw-r--r--   1 dcrossland  nonconf  1371014 Jul  1 21:54 OpenSans-Bold._g_l_y_f.ttx
-rw-r--r--   1 dcrossland  nonconf      757 Jul  1 21:54 OpenSans-Bold._h_e_a_d.ttx
-rw-r--r--   1 dcrossland  nonconf      627 Jul  1 21:54 OpenSans-Bold._h_h_e_a.ttx
-rw-r--r--   1 dcrossland  nonconf    45572 Jul  1 21:54 OpenSans-Bold._h_m_t_x.ttx
-rw-r--r--   1 dcrossland  nonconf   353655 Jul  1 21:54 OpenSans-Bold._k_e_r_n.ttx
-rw-r--r--   1 dcrossland  nonconf      163 Jul  1 21:54 OpenSans-Bold._l_o_c_a.ttx
-rw-r--r--   1 dcrossland  nonconf      677 Jul  1 21:54 OpenSans-Bold._m_a_x_p.ttx
-rw-r--r--   1 dcrossland  nonconf     3615 Jul  1 21:54 OpenSans-Bold._n_a_m_e.ttx
-rw-r--r--   1 dcrossland  nonconf    30387 Jul  1 21:54 OpenSans-Bold._p_o_s_t.ttx
-rw-r--r--   1 dcrossland  nonconf     3580 Jul  1 21:54 OpenSans-Bold._p_r_e_p.ttx
-rw-r--r--   1 dcrossland  nonconf   158720 Jul  1 21:54 OpenSans-Bold.ttf
-rw-r--r--   1 dcrossland  nonconf      973 Jul  1 21:54 OpenSans-Bold.ttx
```

No no that is silly, the original has a lot more data, in the kern table.

```
$ ls -alFR Original/ RoundTrip/ SecondRoundTrip/ | grep ttf
-rw-r--r--   1 dcrossland  nonconf   224,592 Jan 15 16:28 OpenSans-Bold.ttf
-rw-r--r--   1 dcrossland  nonconf   158,720 Jul  1 21:51 OpenSans-Bold.ttf
-rw-r--r--   1 dcrossland  nonconf   158,720 Jul  1 21:54 OpenSans-Bold.ttf
$ ftxdiff Original/OpenSans-Bold.ttf RoundTrip/OpenSans-Bold.ttf | less 
...
##### Table 'kern' ( 46644 bytes in the modified font)
                   (112182 bytes in the original font)
...
```

No one knows what the junk in the table was supposed to mean...

The **`kern`** subtable header, according to the OpenType spec, uses a `length` field of type `USHORT`. The `length` field describes the subtables’s bytesize, so the maximum subtable size can be 65,535 (0x**FFFF**) bytes. Since the format 0 `kern` subtable uses 6 bytes pro kerning pair, and there is a 14-bytes long header, one `kern` subtable can include no more than (65,535 – 14) / 6 = **10,920** kerning pairs. 

OpenSans-Bold.ttf has one `kern` subtable that is 112,182 (0x**1B636**) bytes long and has **18,964** kerning pairs. This font subtable’s length field gives the value 46,642 (0x**B636**) — note that the last 4 bytes of the length field are identical with the real length. 

We could play this trick forever, i.e. have a subtable with 1,000,000 kerning pairs where the `length` field would only report the last 4 bytes of the actual bytesize — but that’s not how it works :) 

The font’s offset table uses `ULONG` for `offset` and `length` (so it’s free from the 64K limit of course). Some tools just read the offset and the length of the entire `kern` table (112,182) and then in the `kern` table header only check for the `nTables` field (the number of the subtables), and when it’s =1, they just read all kerning pairs until the end of the table. They seem to ignore the the `length` field of the subtable. Other tools take the subtable `length` seriously, and then there is a mismatch. 

So this is a font bug and should be reported to the font developers. Since `kern` with format 0 is a legacy TrueType layout table only used in systems that don’t support OpenType Layout, the way to make valid `kern` tables that work cross-platform is: 
1. Take your `GPOS` kerning and expand it to a flat list. 
2. Eliminate all kerning pairs that involve at least one unencoded glyphs (it only makes sense to put `kern` entries for glyphs that have real Unicode value because it doesn’t work with OTL anyway).
3. Sort the list descendingly by the absolute kern value, or (if your tool has such stats) by a combination of the absolute kern value and the frequency of the occurrence of given kerning pairs in real-world texts. 
4. Trim that list at 10,920 kerning pairs. In other words, limit the `kern` kerning to the 10,920 “most relevant” kerning pairs, whatever that means. 
5. Write that kerning into the `kern` table. 

Only fonts with one `kern` subtable work reliably across systems. If a `kern` table has multiple subtables, older Windows apps will completely ignore the entire kerning. So you have to limit the `kern` kerning to one subtable only. 

Ps. fontTools’ “fixing” isn’t really good (although it’s the only sensible one done fully automatically) because it’ll read the subtable’s `length` (46,642) and “believe” it, therefore it’ll only read the first 7,773 pairs out of all 18,964 pairs, and there will be no “relevance” filtering whatsoever. But there could be 10,920 pairs (instead of 18,964) in this font, and a “relevance” sort could be performed. 

BTW, I recommend the app Synalyze It! Pro ( https://www.synalysis.net/ ). It comes with a nifty “opentype.grammar” definition file (needs to be installed from https://www.synalysis.net/formats.xml ) which allows me to look at the contents of any SFNT fonts this way: 

![synalyze3](https://cloud.githubusercontent.com/assets/519108/8484841/242dd05e-20fa-11e5-8e22-0b09a0ea66a9.png)

so I really know what’s going on there. :) It’s even more low-level than fontTools or OTMaster (though OTMaster does display this data as well). 

> the way to make valid kern tables that work cross-platform is

Could fontTools have an argument to ignore the the length field of the subtable, thus making it possible to make a script to extract the 18,964 kerning pairs, and perform a “relevance” filtering?

FWIW, MS ships TTFs that have `excess data in 'kern' subtable`. Calibri being the example I'm encountering now.

@twardoch Can you test and report again?  Previous code was reporting wrong number of excess bytes.

> OpenSans-Bold.ttf has one kern subtable that is 112,182 (0x1B636) bytes long and has 18,964 kerning pairs. This font subtable’s length field gives the value 46,642 (0xB636) — note that the last 4 bytes of the length field are identical with the real length.

Ok, I see.  Guess we can detect this and possibly split into multiple subtables.  Reopening to address this case as well.

https://www.microsoft.com/typography/otspec/recom.htm says 

> 'kern' table
> Should contain a single kerning pair subtable (format 0). Windows will not support format 2 (two-dimensional array of kern values by class); nor multiple tables (only the first format 0 table found will be used) nor coverage bits 0 through 4 (i.e. assumes horizontal data, kerning values, no cross stream, and override).

@jmsole did some tests in Windows 10 with Chrome, Word and PowerPoint with a font that only has a `kern` table (format 0 with multiple subtables) and no GPOS kerning. Having multiple subtables still breaks kerning on Windows, in our tests even the first subtable wasn’t used. Some other applications like InDesign or Firefox apply the kerning but not those mentioned before.

So, splitting the `kern` table subtable in multiple subtables is the same as dropping the kern table in some applications on Windows.

> OpenSans-Bold.ttf has one kern subtable that is 112,182 (0x1B636) bytes long and has 18,964 kerning pairs. This font subtable’s length field gives the value 46,642 (0xB636) — note that the last 4 bytes of the length field are identical with the real length.
> 
> Ok, I see. Guess we can detect this and possibly split into multiple subtables. Reopening to address this case as well.

Any volunteers?

In HB, I implemented this by ignoring the subtable length. We should do the same here, for the last subtable.
@moyogo recently found out that Word for Mac 365 does not use OTL kerning and instead relies on a `kern` table being present, where previous versions used OTL kerning. Gives a new meaning to legacy support.
OMG, this doesn't make any sense... :man_facepalming: 
Note that in OSX 10.8, a flat kerning table over 16k of pairs makes the system declare that the font is corrupt and it won’t install.



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
@@ -48,6 +48,19 @@ def decompile(self, data, ttFont):
 				# 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 @@ def decompile(self, data, ttFont):
 			">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 @@ def decompile(self, data, ttFont):
 	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 @@ def compile(self, ttFont):
 		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:


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
@@ -5,6 +5,7 @@
     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 @@
     '</kernsubtable>',
 ]
 
+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 @@ def test_decompileBadGlyphId(self, font):
         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