Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.ttLib import getSearchRange
      4 from fontTools.misc.textTools import safeEval, readHex
      5 from fontTools.misc.fixedTools import (
      6 	fixedToFloat as fi2fl,
      7 	floatToFixed as fl2fi)
      8 from . import DefaultTable
      9 import struct
     10 import sys
     11 import array
     12 import logging
     13 
     14 
     15 log = logging.getLogger(__name__)
     16 
     17 
     18 class 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 
    111 class 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 
    262 class 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 
    286 kern_classes = {0: KernTable_format_0}
    287