1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.ttLib import getSearchRange
4from fontTools.misc.textTools import safeEval, readHex
5from fontTools.misc.fixedTools import (
6	fixedToFloat as fi2fl,
7	floatToFixed as fl2fi)
8from . import DefaultTable
9import struct
10import sys
11import array
12import logging
13
14
15log = logging.getLogger(__name__)
16
17
18class table__k_e_r_n(DefaultTable.DefaultTable):
19
20	def getkern(self, format):
21		for subtable in self.kernTables:
22			if subtable.format == format:
23				return subtable
24		return None  # not found
25
26	def decompile(self, data, ttFont):
27		version, nTables = struct.unpack(">HH", data[:4])
28		apple = False
29		if (len(data) >= 8) and (version == 1):
30			# AAT Apple's "new" format. Hm.
31			version, nTables = struct.unpack(">LL", data[:8])
32			self.version = fi2fl(version, 16)
33			data = data[8:]
34			apple = True
35		else:
36			self.version = version
37			data = data[4:]
38		self.kernTables = []
39		for i in range(nTables):
40			if self.version == 1.0:
41				# Apple
42				length, coverage, subtableFormat = struct.unpack(
43					">LBB", data[:6])
44			else:
45				# in OpenType spec the "version" field refers to the common
46				# subtable header; the actual subtable format is stored in
47				# the 8-15 mask bits of "coverage" field.
48				# This "version" is always 0 so we ignore it here
49				_, length, subtableFormat, coverage = struct.unpack(
50					">HHBB", data[:6])
51				if nTables == 1 and subtableFormat == 0:
52					# The "length" value is ignored since some fonts
53					# (like OpenSans and Calibri) have a subtable larger than
54					# its value.
55					nPairs, = struct.unpack(">H", data[6:8])
56					calculated_length = (nPairs * 6) + 14
57					if length != calculated_length:
58						log.warning(
59							"'kern' subtable longer than defined: "
60							"%d bytes instead of %d bytes" %
61							(calculated_length, length)
62						)
63					length = calculated_length
64			if subtableFormat not in kern_classes:
65				subtable = KernTable_format_unkown(subtableFormat)
66			else:
67				subtable = kern_classes[subtableFormat](apple)
68			subtable.decompile(data[:length], ttFont)
69			self.kernTables.append(subtable)
70			data = data[length:]
71
72	def compile(self, ttFont):
73		if hasattr(self, "kernTables"):
74			nTables = len(self.kernTables)
75		else:
76			nTables = 0
77		if self.version == 1.0:
78			# AAT Apple's "new" format.
79			data = struct.pack(">LL", fl2fi(self.version, 16), nTables)
80		else:
81			data = struct.pack(">HH", self.version, nTables)
82		if hasattr(self, "kernTables"):
83			for subtable in self.kernTables:
84				data = data + subtable.compile(ttFont)
85		return data
86
87	def toXML(self, writer, ttFont):
88		writer.simpletag("version", value=self.version)
89		writer.newline()
90		for subtable in self.kernTables:
91			subtable.toXML(writer, ttFont)
92
93	def fromXML(self, name, attrs, content, ttFont):
94		if name == "version":
95			self.version = safeEval(attrs["value"])
96			return
97		if name != "kernsubtable":
98			return
99		if not hasattr(self, "kernTables"):
100			self.kernTables = []
101		format = safeEval(attrs["format"])
102		if format not in kern_classes:
103			subtable = KernTable_format_unkown(format)
104		else:
105			apple = self.version == 1.0
106			subtable = kern_classes[format](apple)
107		self.kernTables.append(subtable)
108		subtable.fromXML(name, attrs, content, ttFont)
109
110
111class KernTable_format_0(object):
112
113	# 'version' is kept for backward compatibility
114	version = format = 0
115
116	def __init__(self, apple=False):
117		self.apple = apple
118
119	def decompile(self, data, ttFont):
120		if not self.apple:
121			version, length, subtableFormat, coverage = struct.unpack(
122				">HHBB", data[:6])
123			if version != 0:
124				from fontTools.ttLib import TTLibError
125				raise TTLibError(
126					"unsupported kern subtable version: %d" % version)
127			tupleIndex = None
128			# Should we also assert length == len(data)?
129			data = data[6:]
130		else:
131			length, coverage, subtableFormat, tupleIndex = struct.unpack(
132				">LBBH", data[:8])
133			data = data[8:]
134		assert self.format == subtableFormat, "unsupported format"
135		self.coverage = coverage
136		self.tupleIndex = tupleIndex
137
138		self.kernTable = kernTable = {}
139
140		nPairs, searchRange, entrySelector, rangeShift = struct.unpack(
141			">HHHH", data[:8])
142		data = data[8:]
143
144		datas = array.array("H", data[:6 * nPairs])
145		if sys.byteorder != "big": datas.byteswap()
146		it = iter(datas)
147		glyphOrder = ttFont.getGlyphOrder()
148		for k in range(nPairs):
149			left, right, value = next(it), next(it), next(it)
150			if value >= 32768:
151				value -= 65536
152			try:
153				kernTable[(glyphOrder[left], glyphOrder[right])] = value
154			except IndexError:
155				# Slower, but will not throw an IndexError on an invalid
156				# glyph id.
157				kernTable[(
158					ttFont.getGlyphName(left),
159					ttFont.getGlyphName(right))] = value
160		if len(data) > 6 * nPairs + 4:  # Ignore up to 4 bytes excess
161			log.warning(
162				"excess data in 'kern' subtable: %d bytes",
163				len(data) - 6 * nPairs)
164
165	def compile(self, ttFont):
166		nPairs = len(self.kernTable)
167		searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6)
168		searchRange &= 0xFFFF
169		data = struct.pack(
170			">HHHH", nPairs, searchRange, entrySelector, rangeShift)
171
172		# yeehee! (I mean, turn names into indices)
173		try:
174			reverseOrder = ttFont.getReverseGlyphMap()
175			kernTable = sorted(
176				(reverseOrder[left], reverseOrder[right], value)
177				for ((left, right), value) in self.kernTable.items())
178		except KeyError:
179			# Slower, but will not throw KeyError on invalid glyph id.
180			getGlyphID = ttFont.getGlyphID
181			kernTable = sorted(
182				(getGlyphID(left), getGlyphID(right), value)
183				for ((left, right), value) in self.kernTable.items())
184
185		for left, right, value in kernTable:
186			data = data + struct.pack(">HHh", left, right, value)
187
188		if not self.apple:
189			version = 0
190			length = len(data) + 6
191			if length >= 0x10000:
192				log.warning('"kern" subtable overflow, '
193							'truncating length value while preserving pairs.')
194				length &= 0xFFFF
195			header = struct.pack(
196				">HHBB", version, length, self.format, self.coverage)
197		else:
198			if self.tupleIndex is None:
199				# sensible default when compiling a TTX from an old fonttools
200				# or when inserting a Windows-style format 0 subtable into an
201				# Apple version=1.0 kern table
202				log.warning("'tupleIndex' is None; default to 0")
203				self.tupleIndex = 0
204			length = len(data) + 8
205			header = struct.pack(
206				">LBBH", length, self.coverage, self.format, self.tupleIndex)
207		return header + data
208
209	def toXML(self, writer, ttFont):
210		attrs = dict(coverage=self.coverage, format=self.format)
211		if self.apple:
212			if self.tupleIndex is None:
213				log.warning("'tupleIndex' is None; default to 0")
214				attrs["tupleIndex"] = 0
215			else:
216				attrs["tupleIndex"] = self.tupleIndex
217		writer.begintag("kernsubtable", **attrs)
218		writer.newline()
219		items = sorted(self.kernTable.items())
220		for (left, right), value in items:
221			writer.simpletag("pair", [
222				("l", left),
223				("r", right),
224				("v", value)
225			])
226			writer.newline()
227		writer.endtag("kernsubtable")
228		writer.newline()
229
230	def fromXML(self, name, attrs, content, ttFont):
231		self.coverage = safeEval(attrs["coverage"])
232		subtableFormat = safeEval(attrs["format"])
233		if self.apple:
234			if "tupleIndex" in attrs:
235				self.tupleIndex = safeEval(attrs["tupleIndex"])
236			else:
237				# previous fontTools versions didn't export tupleIndex
238				log.warning(
239					"Apple kern subtable is missing 'tupleIndex' attribute")
240				self.tupleIndex = None
241		else:
242			self.tupleIndex = None
243		assert subtableFormat == self.format, "unsupported format"
244		if not hasattr(self, "kernTable"):
245			self.kernTable = {}
246		for element in content:
247			if not isinstance(element, tuple):
248				continue
249			name, attrs, content = element
250			self.kernTable[(attrs["l"], attrs["r"])] = safeEval(attrs["v"])
251
252	def __getitem__(self, pair):
253		return self.kernTable[pair]
254
255	def __setitem__(self, pair, value):
256		self.kernTable[pair] = value
257
258	def __delitem__(self, pair):
259		del self.kernTable[pair]
260
261
262class KernTable_format_unkown(object):
263
264	def __init__(self, format):
265		self.format = format
266
267	def decompile(self, data, ttFont):
268		self.data = data
269
270	def compile(self, ttFont):
271		return self.data
272
273	def toXML(self, writer, ttFont):
274		writer.begintag("kernsubtable", format=self.format)
275		writer.newline()
276		writer.comment("unknown 'kern' subtable format")
277		writer.newline()
278		writer.dumphex(self.data)
279		writer.endtag("kernsubtable")
280		writer.newline()
281
282	def fromXML(self, name, attrs, content, ttFont):
283		self.decompile(readHex(content), ttFont)
284
285
286kern_classes = {0: KernTable_format_0}
287