Add test
This commit is contained in:
parent
1fc1d2f529
commit
bcb0761202
23
Tests/varLib/data/KerningMerging.designspace
Normal file
23
Tests/varLib/data/KerningMerging.designspace
Normal file
@ -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>
|
304
Tests/varLib/data/master_kerning_merging/0.ttx
Normal file
304
Tests/varLib/data/master_kerning_merging/0.ttx
Normal file
@ -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>
|
292
Tests/varLib/data/master_kerning_merging/1.ttx
Normal file
292
Tests/varLib/data/master_kerning_merging/1.ttx
Normal file
@ -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>
|
304
Tests/varLib/data/master_kerning_merging/2.ttx
Normal file
304
Tests/varLib/data/master_kerning_merging/2.ttx
Normal file
@ -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>
|
@ -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())
|
||||
|
Loading…
x
Reference in New Issue
Block a user