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 = min(len(self.kernTable), 0xFFFF) 165 searchRange, entrySelector, rangeShift = getSearchRange(nPairs, 6) 166 searchRange &= 0xFFFF 167 entrySelector = min(entrySelector, 0xFFFF) 168 rangeShift = min(rangeShift, 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