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