• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc import sstruct
4from fontTools.misc.fixedTools import fixedToFloat, floatToFixed
5from fontTools.misc.textTools import safeEval, num2binary, binary2num
6from fontTools.ttLib import TTLibError
7from . import DefaultTable
8import struct
9
10
11# Apple's documentation of 'fvar':
12# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html
13
14FVAR_HEADER_FORMAT = """
15    > # big endian
16    version:        L
17    offsetToData:   H
18    countSizePairs: H
19    axisCount:      H
20    axisSize:       H
21    instanceCount:  H
22    instanceSize:   H
23"""
24
25FVAR_AXIS_FORMAT = """
26    > # big endian
27    axisTag:        4s
28    minValue:       16.16F
29    defaultValue:   16.16F
30    maxValue:       16.16F
31    flags:          H
32    axisNameID:         H
33"""
34
35FVAR_INSTANCE_FORMAT = """
36    > # big endian
37    subfamilyNameID:     H
38    flags:      H
39"""
40
41class table__f_v_a_r(DefaultTable.DefaultTable):
42    dependencies = ["name"]
43
44    def __init__(self, tag=None):
45        DefaultTable.DefaultTable.__init__(self, tag)
46        self.axes = []
47        self.instances = []
48
49    def compile(self, ttFont):
50        instanceSize = sstruct.calcsize(FVAR_INSTANCE_FORMAT) + (len(self.axes) * 4)
51        includePostScriptNames = any(instance.postscriptNameID != 0xFFFF
52                                     for instance in self.instances)
53        if includePostScriptNames:
54            instanceSize += 2
55        header = {
56            "version": 0x00010000,
57            "offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT),
58            "countSizePairs": 2,
59            "axisCount": len(self.axes),
60            "axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT),
61            "instanceCount": len(self.instances),
62            "instanceSize": instanceSize,
63        }
64        result = [sstruct.pack(FVAR_HEADER_FORMAT, header)]
65        result.extend([axis.compile() for axis in self.axes])
66        axisTags = [axis.axisTag for axis in self.axes]
67        for instance in self.instances:
68            result.append(instance.compile(axisTags, includePostScriptNames))
69        return bytesjoin(result)
70
71    def decompile(self, data, ttFont):
72        header = {}
73        headerSize = sstruct.calcsize(FVAR_HEADER_FORMAT)
74        header = sstruct.unpack(FVAR_HEADER_FORMAT, data[0:headerSize])
75        if header["version"] != 0x00010000:
76            raise TTLibError("unsupported 'fvar' version %04x" % header["version"])
77        pos = header["offsetToData"]
78        axisSize = header["axisSize"]
79        for _ in range(header["axisCount"]):
80            axis = Axis()
81            axis.decompile(data[pos:pos+axisSize])
82            self.axes.append(axis)
83            pos += axisSize
84        instanceSize = header["instanceSize"]
85        axisTags = [axis.axisTag for axis in self.axes]
86        for _ in range(header["instanceCount"]):
87            instance = NamedInstance()
88            instance.decompile(data[pos:pos+instanceSize], axisTags)
89            self.instances.append(instance)
90            pos += instanceSize
91
92    def toXML(self, writer, ttFont):
93        for axis in self.axes:
94            axis.toXML(writer, ttFont)
95        for instance in self.instances:
96            instance.toXML(writer, ttFont)
97
98    def fromXML(self, name, attrs, content, ttFont):
99        if name == "Axis":
100            axis = Axis()
101            axis.fromXML(name, attrs, content, ttFont)
102            self.axes.append(axis)
103        elif name == "NamedInstance":
104            instance = NamedInstance()
105            instance.fromXML(name, attrs, content, ttFont)
106            self.instances.append(instance)
107
108class Axis(object):
109    def __init__(self):
110        self.axisTag = None
111        self.axisNameID = 0
112        self.flags = 0
113        self.minValue = -1.0
114        self.defaultValue = 0.0
115        self.maxValue = 1.0
116
117    def compile(self):
118        return sstruct.pack(FVAR_AXIS_FORMAT, self)
119
120    def decompile(self, data):
121        sstruct.unpack2(FVAR_AXIS_FORMAT, data, self)
122
123    def toXML(self, writer, ttFont):
124        name = ttFont["name"].getDebugName(self.axisNameID)
125        if name is not None:
126            writer.newline()
127            writer.comment(name)
128            writer.newline()
129        writer.begintag("Axis")
130        writer.newline()
131        for tag, value in [("AxisTag", self.axisTag),
132                           ("Flags", "0x%X" % self.flags),
133                           ("MinValue", str(self.minValue)),
134                           ("DefaultValue", str(self.defaultValue)),
135                           ("MaxValue", str(self.maxValue)),
136                           ("AxisNameID", str(self.axisNameID))]:
137            writer.begintag(tag)
138            writer.write(value)
139            writer.endtag(tag)
140            writer.newline()
141        writer.endtag("Axis")
142        writer.newline()
143
144    def fromXML(self, name, _attrs, content, ttFont):
145        assert(name == "Axis")
146        for tag, _, value in filter(lambda t: type(t) is tuple, content):
147            value = ''.join(value)
148            if tag == "AxisTag":
149                self.axisTag = Tag(value)
150            elif tag in {"Flags", "MinValue", "DefaultValue", "MaxValue",
151                         "AxisNameID"}:
152                setattr(self, tag[0].lower() + tag[1:], safeEval(value))
153
154
155class NamedInstance(object):
156    def __init__(self):
157        self.subfamilyNameID = 0
158        self.postscriptNameID = 0xFFFF
159        self.flags = 0
160        self.coordinates = {}
161
162    def compile(self, axisTags, includePostScriptName):
163        result = [sstruct.pack(FVAR_INSTANCE_FORMAT, self)]
164        for axis in axisTags:
165            fixedCoord = floatToFixed(self.coordinates[axis], 16)
166            result.append(struct.pack(">l", fixedCoord))
167        if includePostScriptName:
168            result.append(struct.pack(">H", self.postscriptNameID))
169        return bytesjoin(result)
170
171    def decompile(self, data, axisTags):
172        sstruct.unpack2(FVAR_INSTANCE_FORMAT, data, self)
173        pos = sstruct.calcsize(FVAR_INSTANCE_FORMAT)
174        for axis in axisTags:
175            value = struct.unpack(">l", data[pos : pos + 4])[0]
176            self.coordinates[axis] = fixedToFloat(value, 16)
177            pos += 4
178        if pos + 2 <= len(data):
179          self.postscriptNameID = struct.unpack(">H", data[pos : pos + 2])[0]
180        else:
181          self.postscriptNameID = 0xFFFF
182
183    def toXML(self, writer, ttFont):
184        name = ttFont["name"].getDebugName(self.subfamilyNameID)
185        if name is not None:
186            writer.newline()
187            writer.comment(name)
188            writer.newline()
189        psname = ttFont["name"].getDebugName(self.postscriptNameID)
190        if psname is not None:
191            writer.comment(u"PostScript: " + psname)
192            writer.newline()
193        if self.postscriptNameID  == 0xFFFF:
194           writer.begintag("NamedInstance", flags=("0x%X" % self.flags),
195                           subfamilyNameID=self.subfamilyNameID)
196        else:
197            writer.begintag("NamedInstance", flags=("0x%X" % self.flags),
198                            subfamilyNameID=self.subfamilyNameID,
199                            postscriptNameID=self.postscriptNameID, )
200        writer.newline()
201        for axis in ttFont["fvar"].axes:
202            writer.simpletag("coord", axis=axis.axisTag,
203                             value=self.coordinates[axis.axisTag])
204            writer.newline()
205        writer.endtag("NamedInstance")
206        writer.newline()
207
208    def fromXML(self, name, attrs, content, ttFont):
209        assert(name == "NamedInstance")
210        self.subfamilyNameID = safeEval(attrs["subfamilyNameID"])
211        self.flags = safeEval(attrs.get("flags", "0"))
212        if "postscriptNameID" in attrs:
213            self.postscriptNameID = safeEval(attrs["postscriptNameID"])
214        else:
215            self.postscriptNameID = 0xFFFF
216
217        for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content):
218            if tag == "coord":
219                self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"])
220