Merge pull request #1638 from madig/varLib-merge-kerning-correctly
varLib: Fix merging of class kerning tables
diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py
index b4b1705..d92eb7f 100644
--- a/Lib/fontTools/varLib/merger.py
+++ b/Lib/fontTools/varLib/merger.py
@@ -2,6 +2,7 @@
Merge OpenType Layout tables (GDEF / GPOS / GSUB).
"""
from __future__ import print_function, division, absolute_import
+import copy
from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
from fontTools.misc import classifyTools
@@ -455,7 +456,7 @@
exemplarGlyph = next(iter(classSet))
klass = classDef2.get(exemplarGlyph, 0)
rec2 = oldClass2Records[klass]
- class2Records.append(rec2)
+ class2Records.append(copy.deepcopy(rec2))
class1Records.append(rec1new)
new_matrices.append(class1Records)
matrices = new_matrices
diff --git a/NEWS.rst b/NEWS.rst
index c862424..ae278fd 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,3 +1,7 @@
+- [varLib] Fixed merging of class-based kerning. Before, the process could introduce
+ rogue kerning values and variations for random classes against class zero (everything
+ not otherwise classed).
+
3.42.0 (released 2019-05-28)
----------------------------
diff --git a/Tests/varLib/data/KerningMerging.designspace b/Tests/varLib/data/KerningMerging.designspace
new file mode 100644
index 0000000..f7ce7ef
--- /dev/null
+++ b/Tests/varLib/data/KerningMerging.designspace
@@ -0,0 +1,23 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400"/>
+ </axes>
+ <sources>
+ <source filename="master_kerning_merging/0.ttf">
+ <location>
+ <dimension name="Weight" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="master_kerning_merging/1.ttf">
+ <location>
+ <dimension name="Weight" xvalue="400"/>
+ </location>
+ </source>
+ <source filename="master_kerning_merging/2.ttf">
+ <location>
+ <dimension name="Weight" xvalue="900"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
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 0000000..858f927
--- /dev/null
+++ b/Tests/varLib/data/master_kerning_merging/0.ttx
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.41">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0x341296b2"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 12 15:33:01 2019"/>
+ <modified value="Wed Jun 12 15:33:01 2019"/>
+ <xMin value="50"/>
+ <yMin value="-200"/>
+ <xMax value="450"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="500"/>
+ <minLeftSideBearing value="50"/>
+ <minRightSideBearing value="50"/>
+ <xMaxExtent value="450"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="500"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="68"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="0"/>
+ <mtx name="B" width="500" lsb="0"/>
+ <mtx name="C" width="500" lsb="0"/>
+ <mtx name="D" width="500" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A"/><!-- contains no outline data -->
+
+ <TTGlyph name="B"/><!-- contains no outline data -->
+
+ <TTGlyph name="C"/><!-- contains no outline data -->
+
+ <TTGlyph name="D"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Thin
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;Test-Thin
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Thin
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-Thin
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Thin
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-150"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="2">
+ <Coverage Format="1">
+ <Glyph value="A"/>
+ <Glyph value="B"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <ClassDef1 Format="1">
+ <ClassDef glyph="A" class="1"/>
+ </ClassDef1>
+ <ClassDef2 Format="1">
+ <ClassDef glyph="C" class="1"/>
+ </ClassDef2>
+ <!-- Class1Count=2 -->
+ <!-- Class2Count=2 -->
+ <Class1Record index="0">
+ <Class2Record index="0">
+ <Value1 XAdvance="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XAdvance="10"/>
+ </Class2Record>
+ </Class1Record>
+ <Class1Record index="1">
+ <Class2Record index="0">
+ <Value1 XAdvance="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XAdvance="10"/>
+ </Class2Record>
+ </Class1Record>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
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 0000000..d60a708
--- /dev/null
+++ b/Tests/varLib/data/master_kerning_merging/1.ttx
@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.41">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0x340ca516"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 12 15:33:01 2019"/>
+ <modified value="Wed Jun 12 15:33:01 2019"/>
+ <xMin value="50"/>
+ <yMin value="-200"/>
+ <xMax value="450"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="500"/>
+ <minLeftSideBearing value="50"/>
+ <minRightSideBearing value="50"/>
+ <xMaxExtent value="450"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="500"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="68"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="0"/>
+ <mtx name="B" width="500" lsb="0"/>
+ <mtx name="C" width="500" lsb="0"/>
+ <mtx name="D" width="500" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A"/><!-- contains no outline data -->
+
+ <TTGlyph name="B"/><!-- contains no outline data -->
+
+ <TTGlyph name="C"/><!-- contains no outline data -->
+
+ <TTGlyph name="D"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;Test-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-150"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="2">
+ <Coverage Format="1">
+ <Glyph value="A"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <ClassDef1 Format="2">
+ </ClassDef1>
+ <ClassDef2 Format="1">
+ <ClassDef glyph="B" class="2"/>
+ <ClassDef glyph="D" class="1"/>
+ </ClassDef2>
+ <!-- Class1Count=1 -->
+ <!-- Class2Count=3 -->
+ <Class1Record index="0">
+ <Class2Record index="0">
+ <Value1 XAdvance="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XAdvance="-20"/>
+ </Class2Record>
+ <Class2Record index="2">
+ <Value1 XAdvance="-20"/>
+ </Class2Record>
+ </Class1Record>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
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 0000000..e01a7b1
--- /dev/null
+++ b/Tests/varLib/data/master_kerning_merging/2.ttx
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.41">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="A"/>
+ <GlyphID id="2" name="B"/>
+ <GlyphID id="3" name="C"/>
+ <GlyphID id="4" name="D"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0x330c9492"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Jun 12 15:33:01 2019"/>
+ <modified value="Wed Jun 12 15:33:01 2019"/>
+ <xMin value="50"/>
+ <yMin value="-200"/>
+ <xMax value="450"/>
+ <yMax value="800"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="500"/>
+ <minLeftSideBearing value="50"/>
+ <minRightSideBearing value="50"/>
+ <xMaxExtent value="450"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ <numberOfHMetrics value="1"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="8"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ <maxComponentDepth value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="500"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="0"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="NONE"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="68"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="2"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="500" lsb="0"/>
+ <mtx name="B" width="500" lsb="0"/>
+ <mtx name="C" width="500" lsb="0"/>
+ <mtx name="D" width="500" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
+ <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->
+ <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->
+ <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->
+ </cmap_format_4>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+
+ <!-- The xMin, yMin, xMax and yMax values
+ will be recalculated by the compiler. -->
+
+ <TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
+ <contour>
+ <pt x="50" y="-200" on="1"/>
+ <pt x="450" y="-200" on="1"/>
+ <pt x="450" y="800" on="1"/>
+ <pt x="50" y="800" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-150" on="1"/>
+ <pt x="100" y="750" on="1"/>
+ <pt x="400" y="750" on="1"/>
+ <pt x="400" y="-150" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A"/><!-- contains no outline data -->
+
+ <TTGlyph name="B"/><!-- contains no outline data -->
+
+ <TTGlyph name="C"/><!-- contains no outline data -->
+
+ <TTGlyph name="D"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Black
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;Test-Black
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Black
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-Black
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Black
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-150"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ <psNames>
+ <!-- This file uses unique glyph names based on the information
+ found in the 'post' table. Since these names might not be unique,
+ we have to invent artificial names in case of clashes. In order to
+ be able to retain the original information, we need a name to
+ ps name mapping for those cases where they differ. That's what
+ you see below.
+ -->
+ </psNames>
+ <extraNames>
+ <!-- following are the name that are not taken from the standard Mac glyph order -->
+ </extraNames>
+ </post>
+
+ <GPOS>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <!-- ScriptCount=1 -->
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=1 -->
+ <FeatureIndex index="0" value="0"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=1 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="kern"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="2"/>
+ <LookupFlag value="8"/>
+ <!-- SubTableCount=1 -->
+ <PairPos index="0" Format="2">
+ <Coverage Format="1">
+ <Glyph value="A"/>
+ <Glyph value="B"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <ClassDef1 Format="1">
+ <ClassDef glyph="A" class="1"/>
+ </ClassDef1>
+ <ClassDef2 Format="1">
+ <ClassDef glyph="D" class="1"/>
+ </ClassDef2>
+ <!-- Class1Count=2 -->
+ <!-- Class2Count=2 -->
+ <Class1Record index="0">
+ <Class2Record index="0">
+ <Value1 XAdvance="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XAdvance="40"/>
+ </Class2Record>
+ </Class1Record>
+ <Class1Record index="1">
+ <Class2Record index="0">
+ <Value1 XAdvance="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XAdvance="40"/>
+ </Class2Record>
+ </Class1Record>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index f303733..207a4bc 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -2,6 +2,7 @@
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 @@
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 @@
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())