1from __future__ import print_function, division, absolute_import, unicode_literals 2from fontTools.misc.py23 import * 3from fontTools.misc.testTools import parseXML 4from fontTools.misc.textTools import deHexStr 5from fontTools.misc.xmlWriter import XMLWriter 6from fontTools.ttLib import TTLibError 7from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance 8from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord 9import unittest 10 11 12 13FVAR_DATA = deHexStr( 14 "00 01 00 00 00 10 00 02 00 02 00 14 00 02 00 0C " 15 "77 67 68 74 00 64 00 00 01 90 00 00 03 84 00 00 00 00 01 01 " 16 "77 64 74 68 00 32 00 00 00 64 00 00 00 c8 00 00 00 00 01 02 " 17 "01 03 00 00 01 2c 00 00 00 64 00 00 " 18 "01 04 00 00 01 2c 00 00 00 4b 00 00") 19 20FVAR_AXIS_DATA = deHexStr( 21 "6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59") 22 23FVAR_INSTANCE_DATA_WITHOUT_PSNAME = deHexStr( 24 "01 59 00 00 00 00 b3 33 00 00 80 00") 25 26FVAR_INSTANCE_DATA_WITH_PSNAME = ( 27 FVAR_INSTANCE_DATA_WITHOUT_PSNAME + deHexStr("02 34")) 28 29 30def xml_lines(writer): 31 content = writer.file.getvalue().decode("utf-8") 32 return [line.strip() for line in content.splitlines()][1:] 33 34 35def AddName(font, name): 36 nameTable = font.get("name") 37 if nameTable is None: 38 nameTable = font["name"] = table__n_a_m_e() 39 nameTable.names = [] 40 namerec = NameRecord() 41 namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256]) 42 namerec.string = name.encode('mac_roman') 43 namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0) 44 nameTable.names.append(namerec) 45 return namerec 46 47 48def MakeFont(): 49 axes = [("wght", "Weight", 100, 400, 900), ("wdth", "Width", 50, 100, 200)] 50 instances = [("Light", 300, 100), ("Light Condensed", 300, 75)] 51 fvarTable = table__f_v_a_r() 52 font = {"fvar": fvarTable} 53 for tag, name, minValue, defaultValue, maxValue in axes: 54 axis = Axis() 55 axis.axisTag = tag 56 axis.defaultValue = defaultValue 57 axis.minValue, axis.maxValue = minValue, maxValue 58 axis.axisNameID = AddName(font, name).nameID 59 fvarTable.axes.append(axis) 60 for name, weight, width in instances: 61 inst = NamedInstance() 62 inst.subfamilyNameID = AddName(font, name).nameID 63 inst.coordinates = {"wght": weight, "wdth": width} 64 fvarTable.instances.append(inst) 65 return font 66 67 68class FontVariationTableTest(unittest.TestCase): 69 def test_compile(self): 70 font = MakeFont() 71 h = font["fvar"].compile(font) 72 self.assertEqual(FVAR_DATA, font["fvar"].compile(font)) 73 74 def test_decompile(self): 75 fvar = table__f_v_a_r() 76 fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar}) 77 self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes]) 78 self.assertEqual([259, 260], [i.subfamilyNameID for i in fvar.instances]) 79 80 def test_toXML(self): 81 font = MakeFont() 82 writer = XMLWriter(BytesIO()) 83 font["fvar"].toXML(writer, font) 84 xml = writer.file.getvalue().decode("utf-8") 85 self.assertEqual(2, xml.count("<Axis>")) 86 self.assertTrue("<AxisTag>wght</AxisTag>" in xml) 87 self.assertTrue("<AxisTag>wdth</AxisTag>" in xml) 88 self.assertEqual(2, xml.count("<NamedInstance ")) 89 self.assertTrue("<!-- Light -->" in xml) 90 self.assertTrue("<!-- Light Condensed -->" in xml) 91 92 def test_fromXML(self): 93 fvar = table__f_v_a_r() 94 for name, attrs, content in parseXML( 95 '<Axis>' 96 ' <AxisTag>opsz</AxisTag>' 97 '</Axis>' 98 '<Axis>' 99 ' <AxisTag>slnt</AxisTag>' 100 ' <Flags>0x123</Flags>' 101 '</Axis>' 102 '<NamedInstance subfamilyNameID="765"/>' 103 '<NamedInstance subfamilyNameID="234"/>'): 104 fvar.fromXML(name, attrs, content, ttFont=None) 105 self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes]) 106 self.assertEqual([0, 0x123], [a.flags for a in fvar.axes]) 107 self.assertEqual([765, 234], [i.subfamilyNameID for i in fvar.instances]) 108 109 110class AxisTest(unittest.TestCase): 111 def test_compile(self): 112 axis = Axis() 113 axis.axisTag, axis.axisNameID = ('opsz', 345) 114 axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5) 115 self.assertEqual(FVAR_AXIS_DATA, axis.compile()) 116 117 def test_decompile(self): 118 axis = Axis() 119 axis.decompile(FVAR_AXIS_DATA) 120 self.assertEqual("opsz", axis.axisTag) 121 self.assertEqual(345, axis.axisNameID) 122 self.assertEqual(-0.5, axis.minValue) 123 self.assertEqual(1.3, axis.defaultValue) 124 self.assertEqual(1.5, axis.maxValue) 125 126 def test_toXML(self): 127 font = MakeFont() 128 axis = Axis() 129 axis.decompile(FVAR_AXIS_DATA) 130 AddName(font, "Optical Size").nameID = 256 131 axis.axisNameID = 256 132 axis.flags = 0xABC 133 writer = XMLWriter(BytesIO()) 134 axis.toXML(writer, font) 135 self.assertEqual([ 136 '', 137 '<!-- Optical Size -->', 138 '<Axis>', 139 '<AxisTag>opsz</AxisTag>', 140 '<Flags>0xABC</Flags>', 141 '<MinValue>-0.5</MinValue>', 142 '<DefaultValue>1.3</DefaultValue>', 143 '<MaxValue>1.5</MaxValue>', 144 '<AxisNameID>256</AxisNameID>', 145 '</Axis>' 146 ], xml_lines(writer)) 147 148 def test_fromXML(self): 149 axis = Axis() 150 for name, attrs, content in parseXML( 151 '<Axis>' 152 ' <AxisTag>wght</AxisTag>' 153 ' <Flags>0x123ABC</Flags>' 154 ' <MinValue>100</MinValue>' 155 ' <DefaultValue>400</DefaultValue>' 156 ' <MaxValue>900</MaxValue>' 157 ' <AxisNameID>256</AxisNameID>' 158 '</Axis>'): 159 axis.fromXML(name, attrs, content, ttFont=None) 160 self.assertEqual("wght", axis.axisTag) 161 self.assertEqual(0x123ABC, axis.flags) 162 self.assertEqual(100, axis.minValue) 163 self.assertEqual(400, axis.defaultValue) 164 self.assertEqual(900, axis.maxValue) 165 self.assertEqual(256, axis.axisNameID) 166 167 168class NamedInstanceTest(unittest.TestCase): 169 def test_compile_withPostScriptName(self): 170 inst = NamedInstance() 171 inst.subfamilyNameID = 345 172 inst.postscriptNameID = 564 173 inst.coordinates = {"wght": 0.7, "wdth": 0.5} 174 self.assertEqual(FVAR_INSTANCE_DATA_WITH_PSNAME, 175 inst.compile(["wght", "wdth"], True)) 176 177 def test_compile_withoutPostScriptName(self): 178 inst = NamedInstance() 179 inst.subfamilyNameID = 345 180 inst.postscriptNameID = 564 181 inst.coordinates = {"wght": 0.7, "wdth": 0.5} 182 self.assertEqual(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, 183 inst.compile(["wght", "wdth"], False)) 184 185 def test_decompile_withPostScriptName(self): 186 inst = NamedInstance() 187 inst.decompile(FVAR_INSTANCE_DATA_WITH_PSNAME, ["wght", "wdth"]) 188 self.assertEqual(564, inst.postscriptNameID) 189 self.assertEqual(345, inst.subfamilyNameID) 190 self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) 191 192 def test_decompile_withoutPostScriptName(self): 193 inst = NamedInstance() 194 inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"]) 195 self.assertEqual(0xFFFF, inst.postscriptNameID) 196 self.assertEqual(345, inst.subfamilyNameID) 197 self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) 198 199 def test_toXML_withPostScriptName(self): 200 font = MakeFont() 201 inst = NamedInstance() 202 inst.flags = 0xE9 203 inst.subfamilyNameID = AddName(font, "Light Condensed").nameID 204 inst.postscriptNameID = AddName(font, "Test-LightCondensed").nameID 205 inst.coordinates = {"wght": 0.7, "wdth": 0.5} 206 writer = XMLWriter(BytesIO()) 207 inst.toXML(writer, font) 208 self.assertEqual([ 209 '', 210 '<!-- Light Condensed -->', 211 '<!-- PostScript: Test-LightCondensed -->', 212 '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">' % ( 213 inst.postscriptNameID, inst.subfamilyNameID), 214 '<coord axis="wght" value="0.7"/>', 215 '<coord axis="wdth" value="0.5"/>', 216 '</NamedInstance>' 217 ], xml_lines(writer)) 218 219 def test_toXML_withoutPostScriptName(self): 220 font = MakeFont() 221 inst = NamedInstance() 222 inst.flags = 0xABC 223 inst.subfamilyNameID = AddName(font, "Light Condensed").nameID 224 inst.coordinates = {"wght": 0.7, "wdth": 0.5} 225 writer = XMLWriter(BytesIO()) 226 inst.toXML(writer, font) 227 self.assertEqual([ 228 '', 229 '<!-- Light Condensed -->', 230 '<NamedInstance flags="0xABC" subfamilyNameID="%s">' % 231 inst.subfamilyNameID, 232 '<coord axis="wght" value="0.7"/>', 233 '<coord axis="wdth" value="0.5"/>', 234 '</NamedInstance>' 235 ], xml_lines(writer)) 236 237 def test_fromXML_withPostScriptName(self): 238 inst = NamedInstance() 239 for name, attrs, content in parseXML( 240 '<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">' 241 ' <coord axis="wght" value="0.7"/>' 242 ' <coord axis="wdth" value="0.5"/>' 243 '</NamedInstance>'): 244 inst.fromXML(name, attrs, content, ttFont=MakeFont()) 245 self.assertEqual(257, inst.postscriptNameID) 246 self.assertEqual(345, inst.subfamilyNameID) 247 self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) 248 249 def test_fromXML_withoutPostScriptName(self): 250 inst = NamedInstance() 251 for name, attrs, content in parseXML( 252 '<NamedInstance flags="0x123ABC" subfamilyNameID="345">' 253 ' <coord axis="wght" value="0.7"/>' 254 ' <coord axis="wdth" value="0.5"/>' 255 '</NamedInstance>'): 256 inst.fromXML(name, attrs, content, ttFont=MakeFont()) 257 self.assertEqual(0x123ABC, inst.flags) 258 self.assertEqual(345, inst.subfamilyNameID) 259 self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) 260 261 262if __name__ == "__main__": 263 import sys 264 sys.exit(unittest.main()) 265