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