blob: 3bc46e2455ebf7aa6c6ae4db48084f54ef2dcc11 [file] [log] [blame]
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from fontTools.ttLib import TTLibError
from . import DefaultTable
import struct
# Apple's documentation of 'fvar':
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html
FVAR_HEADER_FORMAT = """
> # big endian
version: L
offsetToData: H
countSizePairs: H
axisCount: H
axisSize: H
instanceCount: H
instanceSize: H
"""
FVAR_AXIS_FORMAT = """
> # big endian
axisTag: 4s
minValue: 16.16F
defaultValue: 16.16F
maxValue: 16.16F
flags: H
axisNameID: H
"""
FVAR_INSTANCE_FORMAT = """
> # big endian
subfamilyNameID: H
flags: H
"""
class table__f_v_a_r(DefaultTable.DefaultTable):
dependencies = ["name"]
def __init__(self, tag=None):
DefaultTable.DefaultTable.__init__(self, tag)
self.axes = []
self.instances = []
def compile(self, ttFont):
instanceSize = sstruct.calcsize(FVAR_INSTANCE_FORMAT) + (len(self.axes) * 4)
includePostScriptNames = any(instance.postscriptNameID != 0xFFFF
for instance in self.instances)
if includePostScriptNames:
instanceSize += 2
header = {
"version": 0x00010000,
"offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT),
"countSizePairs": 2,
"axisCount": len(self.axes),
"axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT),
"instanceCount": len(self.instances),
"instanceSize": instanceSize,
}
result = [sstruct.pack(FVAR_HEADER_FORMAT, header)]
result.extend([axis.compile() for axis in self.axes])
axisTags = [axis.axisTag for axis in self.axes]
for instance in self.instances:
result.append(instance.compile(axisTags, includePostScriptNames))
return bytesjoin(result)
def decompile(self, data, ttFont):
header = {}
headerSize = sstruct.calcsize(FVAR_HEADER_FORMAT)
header = sstruct.unpack(FVAR_HEADER_FORMAT, data[0:headerSize])
if header["version"] != 0x00010000:
raise TTLibError("unsupported 'fvar' version %04x" % header["version"])
pos = header["offsetToData"]
axisSize = header["axisSize"]
for _ in range(header["axisCount"]):
axis = Axis()
axis.decompile(data[pos:pos+axisSize])
self.axes.append(axis)
pos += axisSize
instanceSize = header["instanceSize"]
axisTags = [axis.axisTag for axis in self.axes]
for _ in range(header["instanceCount"]):
instance = NamedInstance()
instance.decompile(data[pos:pos+instanceSize], axisTags)
self.instances.append(instance)
pos += instanceSize
def toXML(self, writer, ttFont):
for axis in self.axes:
axis.toXML(writer, ttFont)
for instance in self.instances:
instance.toXML(writer, ttFont)
def fromXML(self, name, attrs, content, ttFont):
if name == "Axis":
axis = Axis()
axis.fromXML(name, attrs, content, ttFont)
self.axes.append(axis)
elif name == "NamedInstance":
instance = NamedInstance()
instance.fromXML(name, attrs, content, ttFont)
self.instances.append(instance)
class Axis(object):
def __init__(self):
self.axisTag = None
self.axisNameID = 0
self.flags = 0
self.minValue = -1.0
self.defaultValue = 0.0
self.maxValue = 1.0
def compile(self):
return sstruct.pack(FVAR_AXIS_FORMAT, self)
def decompile(self, data):
sstruct.unpack2(FVAR_AXIS_FORMAT, data, self)
def toXML(self, writer, ttFont):
name = ttFont["name"].getDebugName(self.axisNameID)
if name is not None:
writer.newline()
writer.comment(name)
writer.newline()
writer.begintag("Axis")
writer.newline()
for tag, value in [("AxisTag", self.axisTag),
("Flags", "0x%X" % self.flags),
("MinValue", str(self.minValue)),
("DefaultValue", str(self.defaultValue)),
("MaxValue", str(self.maxValue)),
("AxisNameID", str(self.axisNameID))]:
writer.begintag(tag)
writer.write(value)
writer.endtag(tag)
writer.newline()
writer.endtag("Axis")
writer.newline()
def fromXML(self, name, _attrs, content, ttFont):
assert(name == "Axis")
for tag, _, value in filter(lambda t: type(t) is tuple, content):
value = ''.join(value)
if tag == "AxisTag":
self.axisTag = Tag(value)
elif tag in {"Flags", "MinValue", "DefaultValue", "MaxValue",
"AxisNameID"}:
setattr(self, tag[0].lower() + tag[1:], safeEval(value))
class NamedInstance(object):
def __init__(self):
self.subfamilyNameID = 0
self.postscriptNameID = 0xFFFF
self.flags = 0
self.coordinates = {}
def compile(self, axisTags, includePostScriptName):
result = [sstruct.pack(FVAR_INSTANCE_FORMAT, self)]
for axis in axisTags:
fixedCoord = floatToFixed(self.coordinates[axis], 16)
result.append(struct.pack(">l", fixedCoord))
if includePostScriptName:
result.append(struct.pack(">H", self.postscriptNameID))
return bytesjoin(result)
def decompile(self, data, axisTags):
sstruct.unpack2(FVAR_INSTANCE_FORMAT, data, self)
pos = sstruct.calcsize(FVAR_INSTANCE_FORMAT)
for axis in axisTags:
value = struct.unpack(">l", data[pos : pos + 4])[0]
self.coordinates[axis] = fixedToFloat(value, 16)
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):
name = ttFont["name"].getDebugName(self.subfamilyNameID)
if name is not None:
writer.newline()
writer.comment(name)
writer.newline()
psname = ttFont["name"].getDebugName(self.postscriptNameID)
if psname is not None:
writer.comment(u"PostScript: " + psname)
writer.newline()
if self.postscriptNameID == 0xFFFF:
writer.begintag("NamedInstance", flags=("0x%X" % self.flags),
subfamilyNameID=self.subfamilyNameID)
else:
writer.begintag("NamedInstance", flags=("0x%X" % self.flags),
subfamilyNameID=self.subfamilyNameID,
postscriptNameID=self.postscriptNameID, )
writer.newline()
for axis in ttFont["fvar"].axes:
writer.simpletag("coord", axis=axis.axisTag,
value=self.coordinates[axis.axisTag])
writer.newline()
writer.endtag("NamedInstance")
writer.newline()
def fromXML(self, name, attrs, content, ttFont):
assert(name == "NamedInstance")
self.subfamilyNameID = safeEval(attrs["subfamilyNameID"])
self.flags = safeEval(attrs.get("flags", "0"))
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):
if tag == "coord":
self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"])