1from fontTools.misc.testTools import FakeFont, getXML, parseXML 2from fontTools.misc.textTools import deHexStr, hexStr 3from fontTools.ttLib import TTLibError, getTableClass, getTableModule, newTable 4import unittest 5from fontTools.ttLib.tables.TupleVariation import TupleVariation 6 7 8gvarClass = getTableClass("gvar") 9 10 11GVAR_DATA = deHexStr( 12 "0001 0000 " # 0: majorVersion=1 minorVersion=0 13 "0002 0000 " # 4: axisCount=2 sharedTupleCount=0 14 "0000001C " # 8: offsetToSharedTuples=28 15 "0003 0000 " # 12: glyphCount=3 flags=0 16 "0000001C " # 16: offsetToGlyphVariationData=28 17 "0000 0000 000C 002F " # 20: offsets=[0,0,12,47], times 2: [0,0,24,94], 18 # # +offsetToGlyphVariationData: [28,28,52,122] 19 # 20 # 28: Glyph variation data for glyph #0, ".notdef" 21 # ------------------------------------------------ 22 # (no variation data for this glyph) 23 # 24 # 28: Glyph variation data for glyph #1, "space" 25 # ---------------------------------------------- 26 "8001 000C " # 28: tupleVariationCount=1|TUPLES_SHARE_POINT_NUMBERS, offsetToData=12(+28=40) 27 "000A " # 32: tvHeader[0].variationDataSize=10 28 "8000 " # 34: tvHeader[0].tupleIndex=EMBEDDED_PEAK 29 "0000 2CCD " # 36: tvHeader[0].peakTuple={wght:0.0, wdth:0.7} 30 "00 " # 40: all points 31 "03 01 02 03 04 " # 41: deltaX=[1, 2, 3, 4] 32 "03 0b 16 21 2C " # 46: deltaY=[11, 22, 33, 44] 33 "00 " # 51: padding 34 # 35 # 52: Glyph variation data for glyph #2, "I" 36 # ------------------------------------------ 37 "8002 001c " # 52: tupleVariationCount=2|TUPLES_SHARE_POINT_NUMBERS, offsetToData=28(+52=80) 38 "0012 " # 56: tvHeader[0].variationDataSize=18 39 "C000 " # 58: tvHeader[0].tupleIndex=EMBEDDED_PEAK|INTERMEDIATE_REGION 40 "2000 0000 " # 60: tvHeader[0].peakTuple={wght:0.5, wdth:0.0} 41 "0000 0000 " # 64: tvHeader[0].intermediateStart={wght:0.0, wdth:0.0} 42 "4000 0000 " # 68: tvHeader[0].intermediateEnd={wght:1.0, wdth:0.0} 43 "0016 " # 72: tvHeader[1].variationDataSize=22 44 "A000 " # 74: tvHeader[1].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINTS 45 "C000 3333 " # 76: tvHeader[1].peakTuple={wght:-1.0, wdth:0.8} 46 "00 " # 80: all points 47 "07 03 01 04 01 " # 81: deltaX.len=7, deltaX=[3, 1, 4, 1, 48 "05 09 02 06 " # 86: 5, 9, 2, 6] 49 "07 03 01 04 01 " # 90: deltaY.len=7, deltaY=[3, 1, 4, 1, 50 "05 09 02 06 " # 95: 5, 9, 2, 6] 51 "06 " # 99: 6 points 52 "05 00 01 03 01 " # 100: runLen=5(+1=6); delta-encoded run=[0, 1, 4, 5, 53 "01 01 " # 105: 6, 7] 54 "05 f8 07 fc 03 fe 01 " # 107: deltaX.len=5, deltaX=[-8,7,-4,3,-2,1] 55 "05 a8 4d 2c 21 ea 0b " # 114: deltaY.len=5, deltaY=[-88,77,44,33,-22,11] 56 "00" # 121: padding 57) # 122: <end> 58assert len(GVAR_DATA) == 122 59 60 61GVAR_VARIATIONS = { 62 ".notdef": [], 63 "space": [ 64 TupleVariation( 65 {"wdth": (0.0, 0.7000122, 0.7000122)}, [(1, 11), (2, 22), (3, 33), (4, 44)] 66 ), 67 ], 68 "I": [ 69 TupleVariation( 70 {"wght": (0.0, 0.5, 1.0)}, 71 [(3, 3), (1, 1), (4, 4), (1, 1), (5, 5), (9, 9), (2, 2), (6, 6)], 72 ), 73 TupleVariation( 74 {"wght": (-1.0, -1.0, 0.0), "wdth": (0.0, 0.7999878, 0.7999878)}, 75 [(-8, -88), (7, 77), None, None, (-4, 44), (3, 33), (-2, -22), (1, 11)], 76 ), 77 ], 78} 79 80 81GVAR_XML = [ 82 '<version value="1"/>', 83 '<reserved value="0"/>', 84 '<glyphVariations glyph="I">', 85 " <tuple>", 86 ' <coord axis="wght" min="0.0" value="0.5" max="1.0"/>', 87 ' <delta pt="0" x="3" y="3"/>', 88 ' <delta pt="1" x="1" y="1"/>', 89 ' <delta pt="2" x="4" y="4"/>', 90 ' <delta pt="3" x="1" y="1"/>', 91 ' <delta pt="4" x="5" y="5"/>', 92 ' <delta pt="5" x="9" y="9"/>', 93 ' <delta pt="6" x="2" y="2"/>', 94 ' <delta pt="7" x="6" y="6"/>', 95 " </tuple>", 96 " <tuple>", 97 ' <coord axis="wght" value="-1.0"/>', 98 ' <coord axis="wdth" value="0.8"/>', 99 ' <delta pt="0" x="-8" y="-88"/>', 100 ' <delta pt="1" x="7" y="77"/>', 101 ' <delta pt="4" x="-4" y="44"/>', 102 ' <delta pt="5" x="3" y="33"/>', 103 ' <delta pt="6" x="-2" y="-22"/>', 104 ' <delta pt="7" x="1" y="11"/>', 105 " </tuple>", 106 "</glyphVariations>", 107 '<glyphVariations glyph="space">', 108 " <tuple>", 109 ' <coord axis="wdth" value="0.7"/>', 110 ' <delta pt="0" x="1" y="11"/>', 111 ' <delta pt="1" x="2" y="22"/>', 112 ' <delta pt="2" x="3" y="33"/>', 113 ' <delta pt="3" x="4" y="44"/>', 114 " </tuple>", 115 "</glyphVariations>", 116] 117 118 119GVAR_DATA_EMPTY_VARIATIONS = deHexStr( 120 "0001 0000 " # 0: majorVersion=1 minorVersion=0 121 "0002 0000 " # 4: axisCount=2 sharedTupleCount=0 122 "0000001c " # 8: offsetToSharedTuples=28 123 "0003 0000 " # 12: glyphCount=3 flags=0 124 "0000001c " # 16: offsetToGlyphVariationData=28 125 "0000 0000 0000 0000" # 20: offsets=[0, 0, 0, 0] 126) # 28: <end> 127 128 129def hexencode(s): 130 h = hexStr(s).upper() 131 return " ".join([h[i : i + 2] for i in range(0, len(h), 2)]) 132 133 134class GVARTableTest(unittest.TestCase): 135 def assertVariationsAlmostEqual(self, vars1, vars2): 136 self.assertSetEqual(set(vars1.keys()), set(vars2.keys())) 137 for glyphName, variations1 in vars1.items(): 138 variations2 = vars2[glyphName] 139 self.assertEqual(len(variations1), len(variations2)) 140 for v1, v2 in zip(variations1, variations2): 141 self.assertSetEqual(set(v1.axes), set(v2.axes)) 142 for axisTag, support1 in v1.axes.items(): 143 support2 = v2.axes[axisTag] 144 self.assertEqual(len(support1), len(support2)) 145 for s1, s2 in zip(support1, support2): 146 self.assertAlmostEqual(s1, s2) 147 self.assertEqual(v1.coordinates, v2.coordinates) 148 149 def makeFont(self, variations): 150 glyphs = [".notdef", "space", "I"] 151 Axis = getTableModule("fvar").Axis 152 Glyph = getTableModule("glyf").Glyph 153 glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar") 154 font = FakeFont(glyphs) 155 font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar} 156 glyf.glyphs = {glyph: Glyph() for glyph in glyphs} 157 glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)] 158 fvar.axes = [Axis(), Axis()] 159 fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth" 160 gvar.variations = variations 161 return font, gvar 162 163 def test_compile(self): 164 font, gvar = self.makeFont(GVAR_VARIATIONS) 165 self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA)) 166 167 def test_compile_noVariations(self): 168 font, gvar = self.makeFont({}) 169 self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA_EMPTY_VARIATIONS)) 170 171 def test_compile_emptyVariations(self): 172 font, gvar = self.makeFont({".notdef": [], "space": [], "I": []}) 173 self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA_EMPTY_VARIATIONS)) 174 175 def test_decompile(self): 176 for lazy in (True, False, None): 177 with self.subTest(lazy=lazy): 178 font, gvar = self.makeFont({}) 179 font.lazy = lazy 180 gvar.decompile(GVAR_DATA, font) 181 182 self.assertEqual( 183 all(callable(v) for v in gvar.variations.data.values()), 184 lazy is not False, 185 ) 186 187 self.assertVariationsAlmostEqual(gvar.variations, GVAR_VARIATIONS) 188 189 def test_decompile_noVariations(self): 190 font, gvar = self.makeFont({}) 191 gvar.decompile(GVAR_DATA_EMPTY_VARIATIONS, font) 192 self.assertEqual(gvar.variations, {".notdef": [], "space": [], "I": []}) 193 194 def test_fromXML(self): 195 font, gvar = self.makeFont({}) 196 for name, attrs, content in parseXML(GVAR_XML): 197 gvar.fromXML(name, attrs, content, ttFont=font) 198 self.assertVariationsAlmostEqual( 199 gvar.variations, {g: v for g, v in GVAR_VARIATIONS.items() if v} 200 ) 201 202 def test_toXML(self): 203 font, gvar = self.makeFont(GVAR_VARIATIONS) 204 self.assertEqual(getXML(gvar.toXML, font), GVAR_XML) 205 206 def test_compileOffsets_shortFormat(self): 207 self.assertEqual( 208 (deHexStr("00 00 00 02 FF C0"), 0), 209 gvarClass.compileOffsets_([0, 4, 0x1FF80]), 210 ) 211 212 def test_compileOffsets_longFormat(self): 213 self.assertEqual( 214 (deHexStr("00 00 00 00 00 00 00 04 CA FE BE EF"), 1), 215 gvarClass.compileOffsets_([0, 4, 0xCAFEBEEF]), 216 ) 217 218 def test_decompileOffsets_shortFormat(self): 219 decompileOffsets = gvarClass.decompileOffsets_ 220 data = deHexStr("00 11 22 33 44 55 66 77 88 99 aa bb") 221 self.assertEqual( 222 [2 * 0x0011, 2 * 0x2233, 2 * 0x4455, 2 * 0x6677, 2 * 0x8899, 2 * 0xAABB], 223 list(decompileOffsets(data, tableFormat=0, glyphCount=5)), 224 ) 225 226 def test_decompileOffsets_longFormat(self): 227 decompileOffsets = gvarClass.decompileOffsets_ 228 data = deHexStr("00 11 22 33 44 55 66 77 88 99 aa bb") 229 self.assertEqual( 230 [0x00112233, 0x44556677, 0x8899AABB], 231 list(decompileOffsets(data, tableFormat=1, glyphCount=2)), 232 ) 233 234 235if __name__ == "__main__": 236 import sys 237 238 sys.exit(unittest.main()) 239