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