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
« 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
11log = logging.getLogger(__name__)
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
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:]
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
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)
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)
103class KernTable_format_0(object):
104 # 'version' is kept for backward compatibility
105 version = format = 0
107 def __init__(self, apple=False):
108 self.apple = apple
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
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
129 self.kernTable = kernTable = {}
131 nPairs, searchRange, entrySelector, rangeShift = struct.unpack(
132 ">HHHH", data[:8]
133 )
134 data = data[8:]
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 )
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)
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 )
181 for left, right, value in kernTable:
182 data = data + struct.pack(">HHh", left, right, value)
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
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()
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"])
245 def __getitem__(self, pair):
246 return self.kernTable[pair]
248 def __setitem__(self, pair, value):
249 self.kernTable[pair] = value
251 def __delitem__(self, pair):
252 del self.kernTable[pair]
255class KernTable_format_unkown(object):
256 def __init__(self, format):
257 self.format = format
259 def decompile(self, data, ttFont):
260 self.data = data
262 def compile(self, ttFont):
263 return self.data
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()
274 def fromXML(self, name, attrs, content, ttFont):
275 self.decompile(readHex(content), ttFont)
278kern_classes = {0: KernTable_format_0}