Coverage for /usr/lib/python3/dist-packages/fontTools/ttLib/tables/_k_e_r_n.py: 17%

192 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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[ 

151 (ttFont.getGlyphName(left), ttFont.getGlyphName(right)) 

152 ] = value 

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}