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