1from __future__ import print_function, division, absolute_import 2from fontTools.misc.py23 import * 3from fontTools import ttLib 4from fontTools.misc import sstruct 5from fontTools.misc.textTools import safeEval 6from fontTools.ttLib import TTLibError 7from . import DefaultTable 8import array 9import itertools 10import logging 11import struct 12import sys 13import fontTools.ttLib.tables.TupleVariation as tv 14 15 16log = logging.getLogger(__name__) 17TupleVariation = tv.TupleVariation 18 19 20# https://www.microsoft.com/typography/otspec/gvar.htm 21# https://www.microsoft.com/typography/otspec/otvarcommonformats.htm 22# 23# Apple's documentation of 'gvar': 24# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html 25# 26# FreeType2 source code for parsing 'gvar': 27# http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/src/truetype/ttgxvar.c 28 29GVAR_HEADER_FORMAT = """ 30 > # big endian 31 version: H 32 reserved: H 33 axisCount: H 34 sharedTupleCount: H 35 offsetToSharedTuples: I 36 glyphCount: H 37 flags: H 38 offsetToGlyphVariationData: I 39""" 40 41GVAR_HEADER_SIZE = sstruct.calcsize(GVAR_HEADER_FORMAT) 42 43 44class table__g_v_a_r(DefaultTable.DefaultTable): 45 dependencies = ["fvar", "glyf"] 46 47 def __init__(self, tag=None): 48 DefaultTable.DefaultTable.__init__(self, tag) 49 self.version, self.reserved = 1, 0 50 self.variations = {} 51 52 def compile(self, ttFont): 53 axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] 54 sharedTuples = tv.compileSharedTuples( 55 axisTags, itertools.chain(*self.variations.values())) 56 sharedTupleIndices = {coord:i for i, coord in enumerate(sharedTuples)} 57 sharedTupleSize = sum([len(c) for c in sharedTuples]) 58 compiledGlyphs = self.compileGlyphs_( 59 ttFont, axisTags, sharedTupleIndices) 60 offset = 0 61 offsets = [] 62 for glyph in compiledGlyphs: 63 offsets.append(offset) 64 offset += len(glyph) 65 offsets.append(offset) 66 compiledOffsets, tableFormat = self.compileOffsets_(offsets) 67 68 header = {} 69 header["version"] = self.version 70 header["reserved"] = self.reserved 71 header["axisCount"] = len(axisTags) 72 header["sharedTupleCount"] = len(sharedTuples) 73 header["offsetToSharedTuples"] = GVAR_HEADER_SIZE + len(compiledOffsets) 74 header["glyphCount"] = len(compiledGlyphs) 75 header["flags"] = tableFormat 76 header["offsetToGlyphVariationData"] = header["offsetToSharedTuples"] + sharedTupleSize 77 compiledHeader = sstruct.pack(GVAR_HEADER_FORMAT, header) 78 79 result = [compiledHeader, compiledOffsets] 80 result.extend(sharedTuples) 81 result.extend(compiledGlyphs) 82 return bytesjoin(result) 83 84 def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices): 85 result = [] 86 for glyphName in ttFont.getGlyphOrder(): 87 glyph = ttFont["glyf"][glyphName] 88 pointCount = self.getNumPoints_(glyph) 89 variations = self.variations.get(glyphName, []) 90 result.append(compileGlyph_(variations, pointCount, 91 axisTags, sharedCoordIndices)) 92 return result 93 94 def decompile(self, data, ttFont): 95 axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] 96 glyphs = ttFont.getGlyphOrder() 97 sstruct.unpack(GVAR_HEADER_FORMAT, data[0:GVAR_HEADER_SIZE], self) 98 assert len(glyphs) == self.glyphCount 99 assert len(axisTags) == self.axisCount 100 offsets = self.decompileOffsets_(data[GVAR_HEADER_SIZE:], tableFormat=(self.flags & 1), glyphCount=self.glyphCount) 101 sharedCoords = tv.decompileSharedTuples( 102 axisTags, self.sharedTupleCount, data, self.offsetToSharedTuples) 103 self.variations = {} 104 offsetToData = self.offsetToGlyphVariationData 105 for i in range(self.glyphCount): 106 glyphName = glyphs[i] 107 glyph = ttFont["glyf"][glyphName] 108 numPointsInGlyph = self.getNumPoints_(glyph) 109 gvarData = data[offsetToData + offsets[i] : offsetToData + offsets[i + 1]] 110 self.variations[glyphName] = decompileGlyph_( 111 numPointsInGlyph, sharedCoords, axisTags, gvarData) 112 113 @staticmethod 114 def decompileOffsets_(data, tableFormat, glyphCount): 115 if tableFormat == 0: 116 # Short format: array of UInt16 117 offsets = array.array("H") 118 offsetsSize = (glyphCount + 1) * 2 119 else: 120 # Long format: array of UInt32 121 offsets = array.array("I") 122 offsetsSize = (glyphCount + 1) * 4 123 offsets.fromstring(data[0 : offsetsSize]) 124 if sys.byteorder != "big": offsets.byteswap() 125 126 # In the short format, offsets need to be multiplied by 2. 127 # This is not documented in Apple's TrueType specification, 128 # but can be inferred from the FreeType implementation, and 129 # we could verify it with two sample GX fonts. 130 if tableFormat == 0: 131 offsets = [off * 2 for off in offsets] 132 133 return offsets 134 135 @staticmethod 136 def compileOffsets_(offsets): 137 """Packs a list of offsets into a 'gvar' offset table. 138 139 Returns a pair (bytestring, tableFormat). Bytestring is the 140 packed offset table. Format indicates whether the table 141 uses short (tableFormat=0) or long (tableFormat=1) integers. 142 The returned tableFormat should get packed into the flags field 143 of the 'gvar' header. 144 """ 145 assert len(offsets) >= 2 146 for i in range(1, len(offsets)): 147 assert offsets[i - 1] <= offsets[i] 148 if max(offsets) <= 0xffff * 2: 149 packed = array.array("H", [n >> 1 for n in offsets]) 150 tableFormat = 0 151 else: 152 packed = array.array("I", offsets) 153 tableFormat = 1 154 if sys.byteorder != "big": packed.byteswap() 155 return (packed.tostring(), tableFormat) 156 157 def toXML(self, writer, ttFont): 158 writer.simpletag("version", value=self.version) 159 writer.newline() 160 writer.simpletag("reserved", value=self.reserved) 161 writer.newline() 162 axisTags = [axis.axisTag for axis in ttFont["fvar"].axes] 163 for glyphName in ttFont.getGlyphOrder(): 164 variations = self.variations.get(glyphName) 165 if not variations: 166 continue 167 writer.begintag("glyphVariations", glyph=glyphName) 168 writer.newline() 169 for gvar in variations: 170 gvar.toXML(writer, axisTags) 171 writer.endtag("glyphVariations") 172 writer.newline() 173 174 def fromXML(self, name, attrs, content, ttFont): 175 if name == "version": 176 self.version = safeEval(attrs["value"]) 177 elif name == "reserved": 178 self.reserved = safeEval(attrs["value"]) 179 elif name == "glyphVariations": 180 if not hasattr(self, "variations"): 181 self.variations = {} 182 glyphName = attrs["glyph"] 183 glyph = ttFont["glyf"][glyphName] 184 numPointsInGlyph = self.getNumPoints_(glyph) 185 glyphVariations = [] 186 for element in content: 187 if isinstance(element, tuple): 188 name, attrs, content = element 189 if name == "tuple": 190 gvar = TupleVariation({}, [None] * numPointsInGlyph) 191 glyphVariations.append(gvar) 192 for tupleElement in content: 193 if isinstance(tupleElement, tuple): 194 tupleName, tupleAttrs, tupleContent = tupleElement 195 gvar.fromXML(tupleName, tupleAttrs, tupleContent) 196 self.variations[glyphName] = glyphVariations 197 198 @staticmethod 199 def getNumPoints_(glyph): 200 NUM_PHANTOM_POINTS = 4 201 if glyph.isComposite(): 202 return len(glyph.components) + NUM_PHANTOM_POINTS 203 else: 204 # Empty glyphs (eg. space, nonmarkingreturn) have no "coordinates" attribute. 205 return len(getattr(glyph, "coordinates", [])) + NUM_PHANTOM_POINTS 206 207 208def compileGlyph_(variations, pointCount, axisTags, sharedCoordIndices): 209 tupleVariationCount, tuples, data = tv.compileTupleVariationStore( 210 variations, pointCount, axisTags, sharedCoordIndices) 211 if tupleVariationCount == 0: 212 return b"" 213 result = (struct.pack(">HH", tupleVariationCount, 4 + len(tuples)) + 214 tuples + data) 215 if len(result) % 2 != 0: 216 result = result + b"\0" # padding 217 return result 218 219 220def decompileGlyph_(pointCount, sharedTuples, axisTags, data): 221 if len(data) < 4: 222 return [] 223 tupleVariationCount, offsetToData = struct.unpack(">HH", data[:4]) 224 dataPos = offsetToData 225 return tv.decompileTupleVariationStore("gvar", axisTags, 226 tupleVariationCount, pointCount, 227 sharedTuples, data, 4, offsetToData) 228