diff --git a/Tests/varLib/data/KerningMerging.designspace b/Tests/varLib/data/KerningMerging.designspace
new file mode 100644
index 000000000..f7ce7ef8c
--- /dev/null
+++ b/Tests/varLib/data/KerningMerging.designspace
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/data/master_kerning_merging/0.ttx b/Tests/varLib/data/master_kerning_merging/0.ttx
new file mode 100644
index 000000000..858f9275d
--- /dev/null
+++ b/Tests/varLib/data/master_kerning_merging/0.ttx
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Thin
+
+
+ Regular
+
+
+ 0.000;NONE;Test-Thin
+
+
+ Test Thin
+
+
+ Version 0.000
+
+
+ Test-Thin
+
+
+ Test
+
+
+ Thin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/data/master_kerning_merging/1.ttx b/Tests/varLib/data/master_kerning_merging/1.ttx
new file mode 100644
index 000000000..d60a70845
--- /dev/null
+++ b/Tests/varLib/data/master_kerning_merging/1.ttx
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test
+
+
+ Regular
+
+
+ 0.000;NONE;Test-Regular
+
+
+ Test Regular
+
+
+ Version 0.000
+
+
+ Test-Regular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/data/master_kerning_merging/2.ttx b/Tests/varLib/data/master_kerning_merging/2.ttx
new file mode 100644
index 000000000..e01a7b1f6
--- /dev/null
+++ b/Tests/varLib/data/master_kerning_merging/2.ttx
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Black
+
+
+ Regular
+
+
+ 0.000;NONE;Test-Black
+
+
+ Test Black
+
+
+ Version 0.000
+
+
+ Test-Black
+
+
+ Test
+
+
+ Black
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index f3037339e..207a4bc8e 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -2,6 +2,7 @@ from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont, newTable
from fontTools.varLib import build
+from fontTools.varLib.mutator import instantiateVariableFont
from fontTools.varLib import main as varLib_main, load_masters
from fontTools.designspaceLib import (
DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor,
@@ -496,6 +497,98 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
+ def test_kerning_merging(self):
+ """Test the correct merging of class-based pair kerning.
+
+ Problem description at https://github.com/fonttools/fonttools/pull/1638.
+ Test font and Designspace generated by
+ https://gist.github.com/madig/183d0440c9f7d05f04bd1280b9664bd1.
+ """
+ ds_path = self.get_test_input("KerningMerging.designspace")
+ ttx_dir = self.get_test_input("master_kerning_merging")
+
+ ds = DesignSpaceDocument.fromfile(ds_path)
+ for source in ds.sources:
+ ttx_dump = TTFont()
+ ttx_dump.importXML(
+ os.path.join(
+ ttx_dir, os.path.basename(source.filename).replace(".ttf", ".ttx")
+ )
+ )
+ source.font = reload_font(ttx_dump)
+
+ varfont, _, _ = build(ds)
+ varfont = reload_font(varfont)
+
+ class_kerning_tables = [
+ t
+ for l in varfont["GPOS"].table.LookupList.Lookup
+ for t in l.SubTable
+ if t.Format == 2
+ ]
+ assert len(class_kerning_tables) == 1
+ class_kerning_table = class_kerning_tables[0]
+
+ # Test that no class kerned against class zero (containing all glyphs not
+ # classed) has a `XAdvDevice` table attached, which in the variable font
+ # context is a "VariationIndex" table and points to kerning deltas in the GDEF
+ # table. Variation deltas of any kerning class against class zero should
+ # probably never exist.
+ for class1_record in class_kerning_table.Class1Record:
+ class2_zero = class1_record.Class2Record[0]
+ assert getattr(class2_zero.Value1, "XAdvDevice", None) is None
+
+ # Assert the variable font's kerning table (without deltas) is equal to the
+ # default font's kerning table. The bug fixed in
+ # https://github.com/fonttools/fonttools/pull/1638 caused rogue kerning
+ # values to be written to the variable font.
+ assert _extract_flat_kerning(varfont, class_kerning_table) == {
+ ("A", ".notdef"): 0,
+ ("A", "A"): 0,
+ ("A", "B"): -20,
+ ("A", "C"): 0,
+ ("A", "D"): -20,
+ ("B", ".notdef"): 0,
+ ("B", "A"): 0,
+ ("B", "B"): 0,
+ ("B", "C"): 0,
+ ("B", "D"): 0,
+ }
+
+ instance_thin = instantiateVariableFont(varfont, {"wght": 100})
+ instance_thin_kerning_table = (
+ instance_thin["GPOS"].table.LookupList.Lookup[0].SubTable[0]
+ )
+ assert _extract_flat_kerning(instance_thin, instance_thin_kerning_table) == {
+ ("A", ".notdef"): 0,
+ ("A", "A"): 0,
+ ("A", "B"): 0,
+ ("A", "C"): 10,
+ ("A", "D"): 0,
+ ("B", ".notdef"): 0,
+ ("B", "A"): 0,
+ ("B", "B"): 0,
+ ("B", "C"): 10,
+ ("B", "D"): 0,
+ }
+
+ instance_black = instantiateVariableFont(varfont, {"wght": 900})
+ instance_black_kerning_table = (
+ instance_black["GPOS"].table.LookupList.Lookup[0].SubTable[0]
+ )
+ assert _extract_flat_kerning(instance_black, instance_black_kerning_table) == {
+ ("A", ".notdef"): 0,
+ ("A", "A"): 0,
+ ("A", "B"): 0,
+ ("A", "C"): 0,
+ ("A", "D"): 40,
+ ("B", ".notdef"): 0,
+ ("B", "A"): 0,
+ ("B", "B"): 0,
+ ("B", "C"): 0,
+ ("B", "D"): 40,
+ }
+
def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument()
@@ -511,5 +604,20 @@ def test_load_masters_layerName_without_required_font():
load_masters(ds)
+def _extract_flat_kerning(font, pairpos_table):
+ extracted_kerning = {}
+ for glyph_name_1 in pairpos_table.Coverage.glyphs:
+ class_def_1 = pairpos_table.ClassDef1.classDefs.get(glyph_name_1, 0)
+ for glyph_name_2 in font.getGlyphOrder():
+ class_def_2 = pairpos_table.ClassDef2.classDefs.get(glyph_name_2, 0)
+ kern_value = (
+ pairpos_table.Class1Record[class_def_1]
+ .Class2Record[class_def_2]
+ .Value1.XAdvance
+ )
+ extracted_kerning[(glyph_name_1, glyph_name_2)] = kern_value
+ return extracted_kerning
+
+
if __name__ == "__main__":
sys.exit(unittest.main())