• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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