[varLib] Rename fields in fvar, to accommodate for postscriptNameID

This commit is contained in:
Behdad Esfahbod 2016-09-02 18:12:14 -07:00
parent 8e675db59b
commit ae93928275
7 changed files with 76 additions and 53 deletions

View File

@ -2368,8 +2368,10 @@ def prune_pre_subset(self, font, options):
nameIDs = set(options.name_IDs) nameIDs = set(options.name_IDs)
fvar = font.get('fvar') fvar = font.get('fvar')
if fvar: if fvar:
nameIDs.update([inst.nameID for inst in fvar.instances]) nameIDs.update([axis.axisNameID for axis in fvar.axes])
nameIDs.update([axis.nameID for axis in fvar.axes]) nameIDs.update([inst.subfamilyNameID for inst in fvar.instances])
nameIDs.update([inst.postscriptNameID for inst in fvar.instances
if inst.postscriptNameID != 0xFFFF])
if '*' not in options.name_IDs: if '*' not in options.name_IDs:
self.names = [n for n in self.names if n.nameID in nameIDs] self.names = [n for n in self.names if n.nameID in nameIDs]
if not options.name_legacy: if not options.name_legacy:

View File

@ -394,31 +394,31 @@
<MinValue>100.0</MinValue> <MinValue>100.0</MinValue>
<DefaultValue>400.0</DefaultValue> <DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue> <MaxValue>900.0</MaxValue>
<NameID>257</NameID> <AxisNameID>257</AxisNameID>
</Axis> </Axis>
<!-- Thin --> <!-- Thin -->
<NamedInstance nameID="258"> <NamedInstance subfamilyNameID="258">
<coord axis="wght" value="100.0"/> <coord axis="wght" value="100.0"/>
</NamedInstance> </NamedInstance>
<!-- Light --> <!-- Light -->
<NamedInstance nameID="259"> <NamedInstance subfamilyNameID="259">
<coord axis="wght" value="300.0"/> <coord axis="wght" value="300.0"/>
</NamedInstance> </NamedInstance>
<!-- Regular --> <!-- Regular -->
<NamedInstance nameID="260"> <NamedInstance subfamilyNameID="260">
<coord axis="wght" value="400.0"/> <coord axis="wght" value="400.0"/>
</NamedInstance> </NamedInstance>
<!-- Bold --> <!-- Bold -->
<NamedInstance nameID="261"> <NamedInstance subfamilyNameID="261">
<coord axis="wght" value="700.0"/> <coord axis="wght" value="700.0"/>
</NamedInstance> </NamedInstance>
<!-- Black --> <!-- Black -->
<NamedInstance nameID="262"> <NamedInstance subfamilyNameID="262">
<coord axis="wght" value="900.0"/> <coord axis="wght" value="900.0"/>
</NamedInstance> </NamedInstance>
</fvar> </fvar>

View File

@ -25,31 +25,31 @@
<MinValue>100.0</MinValue> <MinValue>100.0</MinValue>
<DefaultValue>400.0</DefaultValue> <DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue> <MaxValue>900.0</MaxValue>
<NameID>257</NameID> <AxisNameID>257</AxisNameID>
</Axis> </Axis>
<!-- Thin --> <!-- Thin -->
<NamedInstance nameID="258"> <NamedInstance subfamilyNameID="258">
<coord axis="wght" value="100.0"/> <coord axis="wght" value="100.0"/>
</NamedInstance> </NamedInstance>
<!-- Light --> <!-- Light -->
<NamedInstance nameID="259"> <NamedInstance subfamilyNameID="259">
<coord axis="wght" value="300.0"/> <coord axis="wght" value="300.0"/>
</NamedInstance> </NamedInstance>
<!-- Regular --> <!-- Regular -->
<NamedInstance nameID="260"> <NamedInstance subfamilyNameID="260">
<coord axis="wght" value="400.0"/> <coord axis="wght" value="400.0"/>
</NamedInstance> </NamedInstance>
<!-- Bold --> <!-- Bold -->
<NamedInstance nameID="261"> <NamedInstance subfamilyNameID="261">
<coord axis="wght" value="700.0"/> <coord axis="wght" value="700.0"/>
</NamedInstance> </NamedInstance>
<!-- Black --> <!-- Black -->
<NamedInstance nameID="262"> <NamedInstance subfamilyNameID="262">
<coord axis="wght" value="900.0"/> <coord axis="wght" value="900.0"/>
</NamedInstance> </NamedInstance>
</fvar> </fvar>

View File

@ -24,31 +24,31 @@
<MinValue>100.0</MinValue> <MinValue>100.0</MinValue>
<DefaultValue>400.0</DefaultValue> <DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue> <MaxValue>900.0</MaxValue>
<NameID>257</NameID> <AxisNameID>257</AxisNameID>
</Axis> </Axis>
<!-- Thin --> <!-- Thin -->
<NamedInstance nameID="258"> <NamedInstance subfamilyNameID="258">
<coord axis="wght" value="100.0"/> <coord axis="wght" value="100.0"/>
</NamedInstance> </NamedInstance>
<!-- Light --> <!-- Light -->
<NamedInstance nameID="259"> <NamedInstance subfamilyNameID="259">
<coord axis="wght" value="300.0"/> <coord axis="wght" value="300.0"/>
</NamedInstance> </NamedInstance>
<!-- Regular --> <!-- Regular -->
<NamedInstance nameID="260"> <NamedInstance subfamilyNameID="260">
<coord axis="wght" value="400.0"/> <coord axis="wght" value="400.0"/>
</NamedInstance> </NamedInstance>
<!-- Bold --> <!-- Bold -->
<NamedInstance nameID="261"> <NamedInstance subfamilyNameID="261">
<coord axis="wght" value="700.0"/> <coord axis="wght" value="700.0"/>
</NamedInstance> </NamedInstance>
<!-- Black --> <!-- Black -->
<NamedInstance nameID="262"> <NamedInstance subfamilyNameID="262">
<coord axis="wght" value="900.0"/> <coord axis="wght" value="900.0"/>
</NamedInstance> </NamedInstance>
</fvar> </fvar>

View File

@ -29,12 +29,12 @@ FVAR_AXIS_FORMAT = """
defaultValue: 16.16F defaultValue: 16.16F
maxValue: 16.16F maxValue: 16.16F
flags: H flags: H
nameID: H axisNameID: H
""" """
FVAR_INSTANCE_FORMAT = """ FVAR_INSTANCE_FORMAT = """
> # big endian > # big endian
nameID: H subfamilyNameID: H
flags: H flags: H
""" """
@ -47,6 +47,9 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
self.instances = [] self.instances = []
def compile(self, ttFont): def compile(self, ttFont):
instanceSize = sstruct.calcsize(FVAR_INSTANCE_FORMAT) + (len(self.axes) * 4)
if any(instance.postscriptNameID != 0xFFFF for instance in self.instances):
instanceSize += 2
header = { header = {
"version": 0x00010000, "version": 0x00010000,
"offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT), "offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT),
@ -54,12 +57,12 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
"axisCount": len(self.axes), "axisCount": len(self.axes),
"axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT), "axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT),
"instanceCount": len(self.instances), "instanceCount": len(self.instances),
"instanceSize": sstruct.calcsize(FVAR_INSTANCE_FORMAT) + len(self.axes) * 4 "instanceSize": instanceSize,
} }
result = [sstruct.pack(FVAR_HEADER_FORMAT, header)] result = [sstruct.pack(FVAR_HEADER_FORMAT, header)]
result.extend([axis.compile() for axis in self.axes]) result.extend([axis.compile() for axis in self.axes])
axisTags = [axis.axisTag for axis in self.axes] axisTags = [axis.axisTag for axis in self.axes]
result.extend([instance.compile(axisTags) for instance in self.instances]) result.extend([instance.compile(axisTags)[:instanceSize] for instance in self.instances])
return bytesjoin(result) return bytesjoin(result)
def decompile(self, data, ttFont): def decompile(self, data, ttFont):
@ -102,7 +105,7 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
class Axis(object): class Axis(object):
def __init__(self): def __init__(self):
self.axisTag = None self.axisTag = None
self.nameID = 0 self.axisNameID = 0
self.flags = 0 # not exposed in XML because spec defines no values self.flags = 0 # not exposed in XML because spec defines no values
self.minValue = -1.0 self.minValue = -1.0
self.defaultValue = 0.0 self.defaultValue = 0.0
@ -115,7 +118,7 @@ class Axis(object):
sstruct.unpack2(FVAR_AXIS_FORMAT, data, self) sstruct.unpack2(FVAR_AXIS_FORMAT, data, self)
def toXML(self, writer, ttFont): def toXML(self, writer, ttFont):
name = ttFont["name"].getDebugName(self.nameID) name = ttFont["name"].getDebugName(self.axisNameID)
if name is not None: if name is not None:
writer.newline() writer.newline()
writer.comment(name) writer.comment(name)
@ -126,7 +129,7 @@ class Axis(object):
("MinValue", str(self.minValue)), ("MinValue", str(self.minValue)),
("DefaultValue", str(self.defaultValue)), ("DefaultValue", str(self.defaultValue)),
("MaxValue", str(self.maxValue)), ("MaxValue", str(self.maxValue)),
("NameID", str(self.nameID))]: ("AxisNameID", str(self.axisNameID))]:
writer.begintag(tag) writer.begintag(tag)
writer.write(value) writer.write(value)
writer.endtag(tag) writer.endtag(tag)
@ -140,12 +143,13 @@ class Axis(object):
value = ''.join(value) value = ''.join(value)
if tag == "AxisTag": if tag == "AxisTag":
self.axisTag = Tag(value) self.axisTag = Tag(value)
elif tag in ["MinValue", "DefaultValue", "MaxValue", "NameID"]: elif tag in ["MinValue", "DefaultValue", "MaxValue", "AxisNameID"]:
setattr(self, tag[0].lower() + tag[1:], safeEval(value)) setattr(self, tag[0].lower() + tag[1:], safeEval(value))
class NamedInstance(object): class NamedInstance(object):
def __init__(self): def __init__(self):
self.nameID = 0 self.subfamilyNameID = 0
self.postscriptNameID = 0xFFFF
self.flags = 0 # not exposed in XML because spec defines no values self.flags = 0 # not exposed in XML because spec defines no values
self.coordinates = {} self.coordinates = {}
@ -154,6 +158,7 @@ class NamedInstance(object):
for axis in axisTags: for axis in axisTags:
fixedCoord = floatToFixed(self.coordinates[axis], 16) fixedCoord = floatToFixed(self.coordinates[axis], 16)
result.append(struct.pack(">l", fixedCoord)) result.append(struct.pack(">l", fixedCoord))
result.append(struct.pack(">H", self.postscriptNameID))
return bytesjoin(result) return bytesjoin(result)
def decompile(self, data, axisTags): def decompile(self, data, axisTags):
@ -163,14 +168,22 @@ class NamedInstance(object):
value = struct.unpack(">l", data[pos : pos + 4])[0] value = struct.unpack(">l", data[pos : pos + 4])[0]
self.coordinates[axis] = fixedToFloat(value, 16) self.coordinates[axis] = fixedToFloat(value, 16)
pos += 4 pos += 4
if pos + 2 <= len(data):
self.postscriptNameID = struct.unpack(">H", data[pos : pos + 2])[0]
else:
self.postscriptNameID = 0xFFFF
def toXML(self, writer, ttFont): def toXML(self, writer, ttFont):
name = ttFont["name"].getDebugName(self.nameID) name = ttFont["name"].getDebugName(self.subfamilyNameID)
if name is not None: if name is not None:
writer.newline() writer.newline()
writer.comment(name) writer.comment(name)
writer.newline() writer.newline()
writer.begintag("NamedInstance", nameID=self.nameID) if self.postscriptNameID == 0xFFFF:
writer.begintag("NamedInstance", subfamilyNameID=self.subfamilyNameID)
else:
writer.begintag("NamedInstance", subfamilyNameID=self.subfamilyNameID,
postscriptNameID=self.postscriptNameID, )
writer.newline() writer.newline()
for axis in ttFont["fvar"].axes: for axis in ttFont["fvar"].axes:
writer.simpletag("coord", axis=axis.axisTag, writer.simpletag("coord", axis=axis.axisTag,
@ -181,7 +194,12 @@ class NamedInstance(object):
def fromXML(self, name, attrs, content, ttFont): def fromXML(self, name, attrs, content, ttFont):
assert(name == "NamedInstance") assert(name == "NamedInstance")
self.nameID = safeEval(attrs["nameID"]) self.subfamilyNameID = safeEval(attrs["subfamilyNameID"])
if "postscriptNameID" in attrs:
self.postscriptNameID = safeEval(attrs["postscriptNameID"])
else:
self.postscriptNameID = 0xFFFF
for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content): for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content):
if tag == "coord": if tag == "coord":
self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"]) self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"])

View File

@ -20,7 +20,7 @@ FVAR_DATA = deHexStr(
FVAR_AXIS_DATA = deHexStr( FVAR_AXIS_DATA = deHexStr(
"6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59") "6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")
FVAR_INSTANCE_DATA = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00") FVAR_INSTANCE_DATA = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00 ff ff")
def xml_lines(writer): def xml_lines(writer):
@ -51,11 +51,11 @@ def MakeFont():
axis.axisTag = tag axis.axisTag = tag
axis.defaultValue = defaultValue axis.defaultValue = defaultValue
axis.minValue, axis.maxValue = minValue, maxValue axis.minValue, axis.maxValue = minValue, maxValue
axis.nameID = AddName(font, name).nameID axis.axisNameID = AddName(font, name).nameID
fvarTable.axes.append(axis) fvarTable.axes.append(axis)
for name, weight, width in instances: for name, weight, width in instances:
inst = NamedInstance() inst = NamedInstance()
inst.nameID = AddName(font, name).nameID inst.subfamilyNameID = AddName(font, name).nameID
inst.coordinates = {"wght": weight, "wdth": width} inst.coordinates = {"wght": weight, "wdth": width}
fvarTable.instances.append(inst) fvarTable.instances.append(inst)
return font return font
@ -71,7 +71,7 @@ class FontVariationTableTest(unittest.TestCase):
fvar = table__f_v_a_r() fvar = table__f_v_a_r()
fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar}) fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar})
self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes]) self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes])
self.assertEqual([259, 260], [i.nameID for i in fvar.instances]) self.assertEqual([259, 260], [i.subfamilyNameID for i in fvar.instances])
def test_toXML(self): def test_toXML(self):
font = MakeFont() font = MakeFont()
@ -94,17 +94,17 @@ class FontVariationTableTest(unittest.TestCase):
'<Axis>' '<Axis>'
' <AxisTag>slnt</AxisTag>' ' <AxisTag>slnt</AxisTag>'
'</Axis>' '</Axis>'
'<NamedInstance nameID="765"/>' '<NamedInstance subfamilyNameID="765"/>'
'<NamedInstance nameID="234"/>'): '<NamedInstance subfamilyNameID="234"/>'):
fvar.fromXML(name, attrs, content, ttFont=None) fvar.fromXML(name, attrs, content, ttFont=None)
self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes]) self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes])
self.assertEqual([765, 234], [i.nameID for i in fvar.instances]) self.assertEqual([765, 234], [i.subfamilyNameID for i in fvar.instances])
class AxisTest(unittest.TestCase): class AxisTest(unittest.TestCase):
def test_compile(self): def test_compile(self):
axis = Axis() axis = Axis()
axis.axisTag, axis.nameID = ('opsz', 345) axis.axisTag, axis.axisNameID = ('opsz', 345)
axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5) axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5)
self.assertEqual(FVAR_AXIS_DATA, axis.compile()) self.assertEqual(FVAR_AXIS_DATA, axis.compile())
@ -112,7 +112,7 @@ class AxisTest(unittest.TestCase):
axis = Axis() axis = Axis()
axis.decompile(FVAR_AXIS_DATA) axis.decompile(FVAR_AXIS_DATA)
self.assertEqual("opsz", axis.axisTag) self.assertEqual("opsz", axis.axisTag)
self.assertEqual(345, axis.nameID) self.assertEqual(345, axis.axisNameID)
self.assertEqual(-0.5, axis.minValue) self.assertEqual(-0.5, axis.minValue)
self.assertEqual(1.3, axis.defaultValue) self.assertEqual(1.3, axis.defaultValue)
self.assertEqual(1.5, axis.maxValue) self.assertEqual(1.5, axis.maxValue)
@ -122,7 +122,7 @@ class AxisTest(unittest.TestCase):
axis = Axis() axis = Axis()
axis.decompile(FVAR_AXIS_DATA) axis.decompile(FVAR_AXIS_DATA)
AddName(font, "Optical Size").nameID = 256 AddName(font, "Optical Size").nameID = 256
axis.nameID = 256 axis.axisNameID = 256
writer = XMLWriter(BytesIO()) writer = XMLWriter(BytesIO())
axis.toXML(writer, font) axis.toXML(writer, font)
self.assertEqual([ self.assertEqual([
@ -133,7 +133,7 @@ class AxisTest(unittest.TestCase):
'<MinValue>-0.5</MinValue>', '<MinValue>-0.5</MinValue>',
'<DefaultValue>1.3</DefaultValue>', '<DefaultValue>1.3</DefaultValue>',
'<MaxValue>1.5</MaxValue>', '<MaxValue>1.5</MaxValue>',
'<NameID>256</NameID>', '<AxisNameID>256</AxisNameID>',
'</Axis>' '</Axis>'
], xml_lines(writer)) ], xml_lines(writer))
@ -145,40 +145,40 @@ class AxisTest(unittest.TestCase):
' <MinValue>100</MinValue>' ' <MinValue>100</MinValue>'
' <DefaultValue>400</DefaultValue>' ' <DefaultValue>400</DefaultValue>'
' <MaxValue>900</MaxValue>' ' <MaxValue>900</MaxValue>'
' <NameID>256</NameID>' ' <AxisNameID>256</AxisNameID>'
'</Axis>'): '</Axis>'):
axis.fromXML(name, attrs, content, ttFont=None) axis.fromXML(name, attrs, content, ttFont=None)
self.assertEqual("wght", axis.axisTag) self.assertEqual("wght", axis.axisTag)
self.assertEqual(100, axis.minValue) self.assertEqual(100, axis.minValue)
self.assertEqual(400, axis.defaultValue) self.assertEqual(400, axis.defaultValue)
self.assertEqual(900, axis.maxValue) self.assertEqual(900, axis.maxValue)
self.assertEqual(256, axis.nameID) self.assertEqual(256, axis.axisNameID)
class NamedInstanceTest(unittest.TestCase): class NamedInstanceTest(unittest.TestCase):
def test_compile(self): def test_compile(self):
inst = NamedInstance() inst = NamedInstance()
inst.nameID = 345 inst.subfamilyNameID = 345
inst.coordinates = {"wght": 0.7, "wdth": 0.5} inst.coordinates = {"wght": 0.7, "wdth": 0.5}
self.assertEqual(FVAR_INSTANCE_DATA, inst.compile(["wght", "wdth"])) self.assertEqual(FVAR_INSTANCE_DATA, inst.compile(["wght", "wdth"]))
def test_decompile(self): def test_decompile(self):
inst = NamedInstance() inst = NamedInstance()
inst.decompile(FVAR_INSTANCE_DATA, ["wght", "wdth"]) inst.decompile(FVAR_INSTANCE_DATA, ["wght", "wdth"])
self.assertEqual(345, inst.nameID) self.assertEqual(345, inst.subfamilyNameID)
self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def test_toXML(self): def test_toXML(self):
font = MakeFont() font = MakeFont()
inst = NamedInstance() inst = NamedInstance()
inst.nameID = AddName(font, "Light Condensed").nameID inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
inst.coordinates = {"wght": 0.7, "wdth": 0.5} inst.coordinates = {"wght": 0.7, "wdth": 0.5}
writer = XMLWriter(BytesIO()) writer = XMLWriter(BytesIO())
inst.toXML(writer, font) inst.toXML(writer, font)
self.assertEqual([ self.assertEqual([
'', '',
'<!-- Light Condensed -->', '<!-- Light Condensed -->',
'<NamedInstance nameID="%s">' % inst.nameID, '<NamedInstance subfamilyNameID="%s">' % inst.subfamilyNameID,
'<coord axis="wght" value="0.7"/>', '<coord axis="wght" value="0.7"/>',
'<coord axis="wdth" value="0.5"/>', '<coord axis="wdth" value="0.5"/>',
'</NamedInstance>' '</NamedInstance>'
@ -187,12 +187,12 @@ class NamedInstanceTest(unittest.TestCase):
def test_fromXML(self): def test_fromXML(self):
inst = NamedInstance() inst = NamedInstance()
for name, attrs, content in parseXML( for name, attrs, content in parseXML(
'<NamedInstance nameID="345">' '<NamedInstance subfamilyNameID="345">'
' <coord axis="wght" value="0.7"/>' ' <coord axis="wght" value="0.7"/>'
' <coord axis="wdth" value="0.5"/>' ' <coord axis="wdth" value="0.5"/>'
'</NamedInstance>'): '</NamedInstance>'):
inst.fromXML(name, attrs, content, ttFont=MakeFont()) inst.fromXML(name, attrs, content, ttFont=MakeFont())
self.assertEqual(345, inst.nameID) self.assertEqual(345, inst.subfamilyNameID)
self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)

View File

@ -68,15 +68,18 @@ def _add_fvar(font, axes, instances, axis_map):
axis = Axis() axis = Axis()
axis.axisTag = Tag(axis_map[iden][0]) axis.axisTag = Tag(axis_map[iden][0])
axis.minValue, axis.defaultValue, axis.maxValue = axes[iden] axis.minValue, axis.defaultValue, axis.maxValue = axes[iden]
axis.nameID = _AddName(font, axis_map[iden][1]).nameID axis.axisNameID = _AddName(font, axis_map[iden][1]).nameID
fvar.axes.append(axis) fvar.axes.append(axis)
for instance in instances: for instance in instances:
coordinates = instance['location'] coordinates = instance['location']
name = instance['stylename'] name = instance['stylename']
psname = instance.get('postscriptfontname')
inst = NamedInstance() inst = NamedInstance()
inst.nameID = _AddName(font, name).nameID inst.subfamilyNameID = _AddName(font, name).nameID
if psname:
inst.postscriptNamedID = _AddName(font, psname).nameID
inst.coordinates = {axis_map[k][0]:v for k,v in coordinates.items()} inst.coordinates = {axis_map[k][0]:v for k,v in coordinates.items()}
fvar.instances.append(inst) fvar.instances.append(inst)