2015-09-11 17:11:25 +02:00
|
|
|
from fontTools.misc.testTools import parseXML
|
2015-06-23 22:04:41 +02:00
|
|
|
from fontTools.misc.textTools import deHexStr
|
|
|
|
from fontTools.misc.xmlWriter import XMLWriter
|
|
|
|
from fontTools.ttLib import TTLibError
|
|
|
|
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
|
|
|
|
from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e, NameRecord
|
2021-03-29 11:45:58 +02:00
|
|
|
from io import BytesIO
|
2015-06-23 22:04:41 +02:00
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
|
|
FVAR_DATA = deHexStr(
|
|
|
|
"00 01 00 00 00 10 00 02 00 02 00 14 00 02 00 0C "
|
2015-06-27 02:07:37 +02:00
|
|
|
"77 67 68 74 00 64 00 00 01 90 00 00 03 84 00 00 00 00 01 01 "
|
|
|
|
"77 64 74 68 00 32 00 00 00 64 00 00 00 c8 00 00 00 00 01 02 "
|
|
|
|
"01 03 00 00 01 2c 00 00 00 64 00 00 "
|
|
|
|
"01 04 00 00 01 2c 00 00 00 4b 00 00"
|
|
|
|
)
|
2015-06-23 22:04:41 +02:00
|
|
|
|
2015-07-01 09:10:32 +02:00
|
|
|
FVAR_AXIS_DATA = deHexStr("6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")
|
2015-06-23 22:04:41 +02:00
|
|
|
|
2016-10-12 10:58:41 +02:00
|
|
|
FVAR_INSTANCE_DATA_WITHOUT_PSNAME = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00")
|
|
|
|
|
|
|
|
FVAR_INSTANCE_DATA_WITH_PSNAME = FVAR_INSTANCE_DATA_WITHOUT_PSNAME + deHexStr("02 34")
|
2015-06-23 22:04:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
def xml_lines(writer):
|
|
|
|
content = writer.file.getvalue().decode("utf-8")
|
|
|
|
return [line.strip() for line in content.splitlines()][1:]
|
|
|
|
|
|
|
|
|
|
|
|
def AddName(font, name):
|
|
|
|
nameTable = font.get("name")
|
|
|
|
if nameTable is None:
|
|
|
|
nameTable = font["name"] = table__n_a_m_e()
|
|
|
|
nameTable.names = []
|
|
|
|
namerec = NameRecord()
|
|
|
|
namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
|
|
|
|
namerec.string = name.encode("mac_roman")
|
|
|
|
namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
|
|
|
|
nameTable.names.append(namerec)
|
|
|
|
return namerec
|
|
|
|
|
|
|
|
|
|
|
|
def MakeFont():
|
2015-06-27 02:07:37 +02:00
|
|
|
axes = [("wght", "Weight", 100, 400, 900), ("wdth", "Width", 50, 100, 200)]
|
|
|
|
instances = [("Light", 300, 100), ("Light Condensed", 300, 75)]
|
2015-06-23 22:04:41 +02:00
|
|
|
fvarTable = table__f_v_a_r()
|
|
|
|
font = {"fvar": fvarTable}
|
2015-06-27 02:07:37 +02:00
|
|
|
for tag, name, minValue, defaultValue, maxValue in axes:
|
2015-06-23 22:04:41 +02:00
|
|
|
axis = Axis()
|
|
|
|
axis.axisTag = tag
|
2015-06-27 02:07:37 +02:00
|
|
|
axis.defaultValue = defaultValue
|
|
|
|
axis.minValue, axis.maxValue = minValue, maxValue
|
2016-09-02 18:12:14 -07:00
|
|
|
axis.axisNameID = AddName(font, name).nameID
|
2015-06-23 22:04:41 +02:00
|
|
|
fvarTable.axes.append(axis)
|
|
|
|
for name, weight, width in instances:
|
|
|
|
inst = NamedInstance()
|
2016-09-02 18:12:14 -07:00
|
|
|
inst.subfamilyNameID = AddName(font, name).nameID
|
2015-06-23 22:04:41 +02:00
|
|
|
inst.coordinates = {"wght": weight, "wdth": width}
|
|
|
|
fvarTable.instances.append(inst)
|
|
|
|
return font
|
|
|
|
|
|
|
|
|
|
|
|
class FontVariationTableTest(unittest.TestCase):
|
|
|
|
def test_compile(self):
|
|
|
|
font = MakeFont()
|
|
|
|
h = font["fvar"].compile(font)
|
|
|
|
self.assertEqual(FVAR_DATA, font["fvar"].compile(font))
|
|
|
|
|
|
|
|
def test_decompile(self):
|
|
|
|
fvar = table__f_v_a_r()
|
|
|
|
fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar})
|
|
|
|
self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes])
|
2016-09-02 18:12:14 -07:00
|
|
|
self.assertEqual([259, 260], [i.subfamilyNameID for i in fvar.instances])
|
2015-06-23 22:04:41 +02:00
|
|
|
|
|
|
|
def test_toXML(self):
|
|
|
|
font = MakeFont()
|
2015-08-07 16:16:49 +01:00
|
|
|
writer = XMLWriter(BytesIO())
|
2015-06-23 22:04:41 +02:00
|
|
|
font["fvar"].toXML(writer, font)
|
|
|
|
xml = writer.file.getvalue().decode("utf-8")
|
|
|
|
self.assertEqual(2, xml.count("<Axis>"))
|
|
|
|
self.assertTrue("<AxisTag>wght</AxisTag>" in xml)
|
|
|
|
self.assertTrue("<AxisTag>wdth</AxisTag>" in xml)
|
|
|
|
self.assertEqual(2, xml.count("<NamedInstance "))
|
|
|
|
self.assertTrue("<!-- Light -->" in xml)
|
|
|
|
self.assertTrue("<!-- Light Condensed -->" in xml)
|
|
|
|
|
|
|
|
def test_fromXML(self):
|
|
|
|
fvar = table__f_v_a_r()
|
2015-09-11 17:11:25 +02:00
|
|
|
for name, attrs, content in parseXML(
|
|
|
|
"<Axis>"
|
|
|
|
" <AxisTag>opsz</AxisTag>"
|
|
|
|
"</Axis>"
|
|
|
|
"<Axis>"
|
|
|
|
" <AxisTag>slnt</AxisTag>"
|
2017-05-01 17:13:02 +02:00
|
|
|
" <Flags>0x123</Flags>"
|
2015-09-11 17:11:25 +02:00
|
|
|
"</Axis>"
|
2016-09-02 18:12:14 -07:00
|
|
|
'<NamedInstance subfamilyNameID="765"/>'
|
|
|
|
'<NamedInstance subfamilyNameID="234"/>'
|
|
|
|
):
|
2015-09-11 17:11:25 +02:00
|
|
|
fvar.fromXML(name, attrs, content, ttFont=None)
|
2015-06-23 22:04:41 +02:00
|
|
|
self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes])
|
2017-05-01 17:13:02 +02:00
|
|
|
self.assertEqual([0, 0x123], [a.flags for a in fvar.axes])
|
2016-09-02 18:12:14 -07:00
|
|
|
self.assertEqual([765, 234], [i.subfamilyNameID for i in fvar.instances])
|
2015-06-23 22:04:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
class AxisTest(unittest.TestCase):
|
|
|
|
def test_compile(self):
|
|
|
|
axis = Axis()
|
2016-09-02 18:12:14 -07:00
|
|
|
axis.axisTag, axis.axisNameID = ("opsz", 345)
|
2015-06-23 22:04:41 +02:00
|
|
|
axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5)
|
|
|
|
self.assertEqual(FVAR_AXIS_DATA, axis.compile())
|
|
|
|
|
|
|
|
def test_decompile(self):
|
|
|
|
axis = Axis()
|
|
|
|
axis.decompile(FVAR_AXIS_DATA)
|
|
|
|
self.assertEqual("opsz", axis.axisTag)
|
2016-09-02 18:12:14 -07:00
|
|
|
self.assertEqual(345, axis.axisNameID)
|
2015-06-23 22:04:41 +02:00
|
|
|
self.assertEqual(-0.5, axis.minValue)
|
2019-10-07 15:19:24 +01:00
|
|
|
self.assertAlmostEqual(1.3000031, axis.defaultValue)
|
2015-06-23 22:04:41 +02:00
|
|
|
self.assertEqual(1.5, axis.maxValue)
|
|
|
|
|
|
|
|
def test_toXML(self):
|
|
|
|
font = MakeFont()
|
|
|
|
axis = Axis()
|
|
|
|
axis.decompile(FVAR_AXIS_DATA)
|
|
|
|
AddName(font, "Optical Size").nameID = 256
|
2016-09-02 18:12:14 -07:00
|
|
|
axis.axisNameID = 256
|
2017-05-01 17:13:02 +02:00
|
|
|
axis.flags = 0xABC
|
2015-08-07 16:16:49 +01:00
|
|
|
writer = XMLWriter(BytesIO())
|
2015-06-23 22:04:41 +02:00
|
|
|
axis.toXML(writer, font)
|
|
|
|
self.assertEqual(
|
|
|
|
[
|
|
|
|
"",
|
|
|
|
"<!-- Optical Size -->",
|
|
|
|
"<Axis>",
|
|
|
|
"<AxisTag>opsz</AxisTag>",
|
2017-05-01 17:13:02 +02:00
|
|
|
"<Flags>0xABC</Flags>",
|
2015-06-23 22:04:41 +02:00
|
|
|
"<MinValue>-0.5</MinValue>",
|
|
|
|
"<DefaultValue>1.3</DefaultValue>",
|
|
|
|
"<MaxValue>1.5</MaxValue>",
|
2016-09-02 18:12:14 -07:00
|
|
|
"<AxisNameID>256</AxisNameID>",
|
2015-06-23 22:04:41 +02:00
|
|
|
"</Axis>",
|
|
|
|
],
|
|
|
|
xml_lines(writer),
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_fromXML(self):
|
|
|
|
axis = Axis()
|
2015-09-11 17:11:25 +02:00
|
|
|
for name, attrs, content in parseXML(
|
|
|
|
"<Axis>"
|
|
|
|
" <AxisTag>wght</AxisTag>"
|
2017-05-01 17:13:02 +02:00
|
|
|
" <Flags>0x123ABC</Flags>"
|
2015-09-11 17:11:25 +02:00
|
|
|
" <MinValue>100</MinValue>"
|
|
|
|
" <DefaultValue>400</DefaultValue>"
|
|
|
|
" <MaxValue>900</MaxValue>"
|
2016-09-02 18:12:14 -07:00
|
|
|
" <AxisNameID>256</AxisNameID>"
|
2015-09-11 17:11:25 +02:00
|
|
|
"</Axis>"
|
|
|
|
):
|
|
|
|
axis.fromXML(name, attrs, content, ttFont=None)
|
2015-06-27 02:07:37 +02:00
|
|
|
self.assertEqual("wght", axis.axisTag)
|
2017-05-01 17:13:02 +02:00
|
|
|
self.assertEqual(0x123ABC, axis.flags)
|
2015-06-27 02:07:37 +02:00
|
|
|
self.assertEqual(100, axis.minValue)
|
|
|
|
self.assertEqual(400, axis.defaultValue)
|
|
|
|
self.assertEqual(900, axis.maxValue)
|
2016-09-02 18:12:14 -07:00
|
|
|
self.assertEqual(256, axis.axisNameID)
|
2015-06-23 22:04:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
class NamedInstanceTest(unittest.TestCase):
|
2019-10-07 15:19:24 +01:00
|
|
|
def assertDictAlmostEqual(self, dict1, dict2):
|
|
|
|
self.assertEqual(set(dict1.keys()), set(dict2.keys()))
|
|
|
|
for key in dict1:
|
|
|
|
self.assertAlmostEqual(dict1[key], dict2[key])
|
|
|
|
|
2016-10-12 10:58:41 +02:00
|
|
|
def test_compile_withPostScriptName(self):
|
2015-06-23 22:04:41 +02:00
|
|
|
inst = NamedInstance()
|
2016-09-02 18:12:14 -07:00
|
|
|
inst.subfamilyNameID = 345
|
2016-10-12 10:58:41 +02:00
|
|
|
inst.postscriptNameID = 564
|
2015-06-23 22:04:41 +02:00
|
|
|
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
|
2016-10-12 10:58:41 +02:00
|
|
|
self.assertEqual(
|
2016-10-25 00:09:31 +02:00
|
|
|
FVAR_INSTANCE_DATA_WITH_PSNAME, inst.compile(["wght", "wdth"], True)
|
2022-12-13 11:26:36 +00:00
|
|
|
)
|
2015-06-23 22:04:41 +02:00
|
|
|
|
2016-10-12 10:58:41 +02:00
|
|
|
def test_compile_withoutPostScriptName(self):
|
2015-06-23 22:04:41 +02:00
|
|
|
inst = NamedInstance()
|
2016-10-12 10:58:41 +02:00
|
|
|
inst.subfamilyNameID = 345
|
2016-10-25 00:09:31 +02:00
|
|
|
inst.postscriptNameID = 564
|
2016-10-12 10:58:41 +02:00
|
|
|
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
|
|
|
|
self.assertEqual(
|
2016-10-25 00:09:31 +02:00
|
|
|
FVAR_INSTANCE_DATA_WITHOUT_PSNAME, inst.compile(["wght", "wdth"], False)
|
2022-12-13 11:26:36 +00:00
|
|
|
)
|
2016-10-12 10:58:41 +02:00
|
|
|
|
|
|
|
def test_decompile_withPostScriptName(self):
|
|
|
|
inst = NamedInstance()
|
|
|
|
inst.decompile(FVAR_INSTANCE_DATA_WITH_PSNAME, ["wght", "wdth"])
|
|
|
|
self.assertEqual(564, inst.postscriptNameID)
|
2016-09-02 18:12:14 -07:00
|
|
|
self.assertEqual(345, inst.subfamilyNameID)
|
2019-10-07 15:19:24 +01:00
|
|
|
self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
|
2015-06-23 22:04:41 +02:00
|
|
|
|
2016-10-12 10:58:41 +02:00
|
|
|
def test_decompile_withoutPostScriptName(self):
|
|
|
|
inst = NamedInstance()
|
|
|
|
inst.decompile(FVAR_INSTANCE_DATA_WITHOUT_PSNAME, ["wght", "wdth"])
|
|
|
|
self.assertEqual(0xFFFF, inst.postscriptNameID)
|
|
|
|
self.assertEqual(345, inst.subfamilyNameID)
|
2019-10-07 15:19:24 +01:00
|
|
|
self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
|
2016-10-12 10:58:41 +02:00
|
|
|
|
|
|
|
def test_toXML_withPostScriptName(self):
|
|
|
|
font = MakeFont()
|
|
|
|
inst = NamedInstance()
|
2017-05-01 17:13:02 +02:00
|
|
|
inst.flags = 0xE9
|
2016-10-12 10:58:41 +02:00
|
|
|
inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
|
|
|
|
inst.postscriptNameID = AddName(font, "Test-LightCondensed").nameID
|
|
|
|
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
|
|
|
|
writer = XMLWriter(BytesIO())
|
|
|
|
inst.toXML(writer, font)
|
|
|
|
self.assertEqual(
|
|
|
|
[
|
|
|
|
"",
|
|
|
|
"<!-- Light Condensed -->",
|
|
|
|
"<!-- PostScript: Test-LightCondensed -->",
|
2017-05-01 17:13:02 +02:00
|
|
|
'<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">'
|
2016-10-12 10:58:41 +02:00
|
|
|
% (inst.postscriptNameID, inst.subfamilyNameID),
|
|
|
|
'<coord axis="wght" value="0.7"/>',
|
|
|
|
'<coord axis="wdth" value="0.5"/>',
|
|
|
|
"</NamedInstance>",
|
|
|
|
],
|
|
|
|
xml_lines(writer),
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_toXML_withoutPostScriptName(self):
|
2015-06-23 22:04:41 +02:00
|
|
|
font = MakeFont()
|
|
|
|
inst = NamedInstance()
|
2017-05-01 17:13:02 +02:00
|
|
|
inst.flags = 0xABC
|
2016-09-02 18:12:14 -07:00
|
|
|
inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
|
2015-06-23 22:04:41 +02:00
|
|
|
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
|
2015-08-07 16:16:49 +01:00
|
|
|
writer = XMLWriter(BytesIO())
|
2015-06-23 22:04:41 +02:00
|
|
|
inst.toXML(writer, font)
|
|
|
|
self.assertEqual(
|
|
|
|
[
|
|
|
|
"",
|
|
|
|
"<!-- Light Condensed -->",
|
2017-05-01 17:13:02 +02:00
|
|
|
'<NamedInstance flags="0xABC" subfamilyNameID="%s">'
|
|
|
|
% inst.subfamilyNameID,
|
2015-06-23 22:04:41 +02:00
|
|
|
'<coord axis="wght" value="0.7"/>',
|
|
|
|
'<coord axis="wdth" value="0.5"/>',
|
|
|
|
"</NamedInstance>",
|
|
|
|
],
|
|
|
|
xml_lines(writer),
|
|
|
|
)
|
|
|
|
|
2016-10-12 10:58:41 +02:00
|
|
|
def test_fromXML_withPostScriptName(self):
|
|
|
|
inst = NamedInstance()
|
|
|
|
for name, attrs, content in parseXML(
|
2017-05-01 17:13:02 +02:00
|
|
|
'<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">'
|
2016-10-12 10:58:41 +02:00
|
|
|
' <coord axis="wght" value="0.7"/>'
|
|
|
|
' <coord axis="wdth" value="0.5"/>'
|
|
|
|
"</NamedInstance>"
|
|
|
|
):
|
|
|
|
inst.fromXML(name, attrs, content, ttFont=MakeFont())
|
|
|
|
self.assertEqual(257, inst.postscriptNameID)
|
|
|
|
self.assertEqual(345, inst.subfamilyNameID)
|
2019-10-07 15:19:24 +01:00
|
|
|
self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
|
2016-10-12 10:58:41 +02:00
|
|
|
|
|
|
|
def test_fromXML_withoutPostScriptName(self):
|
2015-06-23 22:04:41 +02:00
|
|
|
inst = NamedInstance()
|
2015-09-11 17:11:25 +02:00
|
|
|
for name, attrs, content in parseXML(
|
2017-05-01 17:13:02 +02:00
|
|
|
'<NamedInstance flags="0x123ABC" subfamilyNameID="345">'
|
2015-09-11 17:11:25 +02:00
|
|
|
' <coord axis="wght" value="0.7"/>'
|
|
|
|
' <coord axis="wdth" value="0.5"/>'
|
|
|
|
"</NamedInstance>"
|
|
|
|
):
|
|
|
|
inst.fromXML(name, attrs, content, ttFont=MakeFont())
|
2017-05-01 17:13:02 +02:00
|
|
|
self.assertEqual(0x123ABC, inst.flags)
|
2016-09-02 18:12:14 -07:00
|
|
|
self.assertEqual(345, inst.subfamilyNameID)
|
2019-10-07 15:19:24 +01:00
|
|
|
self.assertDictAlmostEqual({"wght": 0.6999969, "wdth": 0.5}, inst.coordinates)
|
2015-06-23 22:04:41 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2017-01-11 13:05:35 +00:00
|
|
|
import sys
|
2022-12-13 11:26:36 +00:00
|
|
|
|
2017-01-11 13:05:35 +00:00
|
|
|
sys.exit(unittest.main())
|