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