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