Coverage for /usr/lib/python3/dist-packages/fontTools/ttLib/tables/E_B_L_C_.py: 19%
384 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.misc import sstruct
2from . import DefaultTable
3from fontTools.misc.textTools import bytesjoin, safeEval
4from .BitmapGlyphMetrics import (
5 BigGlyphMetrics,
6 bigGlyphMetricsFormat,
7 SmallGlyphMetrics,
8 smallGlyphMetricsFormat,
9)
10import struct
11import itertools
12from collections import deque
13import logging
16log = logging.getLogger(__name__)
18eblcHeaderFormat = """
19 > # big endian
20 version: 16.16F
21 numSizes: I
22"""
23# The table format string is split to handle sbitLineMetrics simply.
24bitmapSizeTableFormatPart1 = """
25 > # big endian
26 indexSubTableArrayOffset: I
27 indexTablesSize: I
28 numberOfIndexSubTables: I
29 colorRef: I
30"""
31# The compound type for hori and vert.
32sbitLineMetricsFormat = """
33 > # big endian
34 ascender: b
35 descender: b
36 widthMax: B
37 caretSlopeNumerator: b
38 caretSlopeDenominator: b
39 caretOffset: b
40 minOriginSB: b
41 minAdvanceSB: b
42 maxBeforeBL: b
43 minAfterBL: b
44 pad1: b
45 pad2: b
46"""
47# hori and vert go between the two parts.
48bitmapSizeTableFormatPart2 = """
49 > # big endian
50 startGlyphIndex: H
51 endGlyphIndex: H
52 ppemX: B
53 ppemY: B
54 bitDepth: B
55 flags: b
56"""
58indexSubTableArrayFormat = ">HHL"
59indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat)
61indexSubHeaderFormat = ">HHL"
62indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat)
64codeOffsetPairFormat = ">HH"
65codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat)
68class table_E_B_L_C_(DefaultTable.DefaultTable):
69 dependencies = ["EBDT"]
71 # This method can be overridden in subclasses to support new formats
72 # without changing the other implementation. Also can be used as a
73 # convenience method for coverting a font file to an alternative format.
74 def getIndexFormatClass(self, indexFormat):
75 return eblc_sub_table_classes[indexFormat]
77 def decompile(self, data, ttFont):
78 # Save the original data because offsets are from the start of the table.
79 origData = data
80 i = 0
82 dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self)
83 i += 8
85 self.strikes = []
86 for curStrikeIndex in range(self.numSizes):
87 curStrike = Strike()
88 self.strikes.append(curStrike)
89 curTable = curStrike.bitmapSizeTable
90 dummy = sstruct.unpack2(
91 bitmapSizeTableFormatPart1, data[i : i + 16], curTable
92 )
93 i += 16
94 for metric in ("hori", "vert"):
95 metricObj = SbitLineMetrics()
96 vars(curTable)[metric] = metricObj
97 dummy = sstruct.unpack2(
98 sbitLineMetricsFormat, data[i : i + 12], metricObj
99 )
100 i += 12
101 dummy = sstruct.unpack(
102 bitmapSizeTableFormatPart2, data[i : i + 8], curTable
103 )
104 i += 8
106 for curStrike in self.strikes:
107 curTable = curStrike.bitmapSizeTable
108 for subtableIndex in range(curTable.numberOfIndexSubTables):
109 i = (
110 curTable.indexSubTableArrayOffset
111 + subtableIndex * indexSubTableArraySize
112 )
114 tup = struct.unpack(
115 indexSubTableArrayFormat, data[i : i + indexSubTableArraySize]
116 )
117 (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup
118 i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable
120 tup = struct.unpack(
121 indexSubHeaderFormat, data[i : i + indexSubHeaderSize]
122 )
123 (indexFormat, imageFormat, imageDataOffset) = tup
125 indexFormatClass = self.getIndexFormatClass(indexFormat)
126 indexSubTable = indexFormatClass(data[i + indexSubHeaderSize :], ttFont)
127 indexSubTable.firstGlyphIndex = firstGlyphIndex
128 indexSubTable.lastGlyphIndex = lastGlyphIndex
129 indexSubTable.additionalOffsetToIndexSubtable = (
130 additionalOffsetToIndexSubtable
131 )
132 indexSubTable.indexFormat = indexFormat
133 indexSubTable.imageFormat = imageFormat
134 indexSubTable.imageDataOffset = imageDataOffset
135 indexSubTable.decompile() # https://github.com/fonttools/fonttools/issues/317
136 curStrike.indexSubTables.append(indexSubTable)
138 def compile(self, ttFont):
139 dataList = []
140 self.numSizes = len(self.strikes)
141 dataList.append(sstruct.pack(eblcHeaderFormat, self))
143 # Data size of the header + bitmapSizeTable needs to be calculated
144 # in order to form offsets. This value will hold the size of the data
145 # in dataList after all the data is consolidated in dataList.
146 dataSize = len(dataList[0])
148 # The table will be structured in the following order:
149 # (0) header
150 # (1) Each bitmapSizeTable [1 ... self.numSizes]
151 # (2) Alternate between indexSubTableArray and indexSubTable
152 # for each bitmapSizeTable present.
153 #
154 # The issue is maintaining the proper offsets when table information
155 # gets moved around. All offsets and size information must be recalculated
156 # when building the table to allow editing within ttLib and also allow easy
157 # import/export to and from XML. All of this offset information is lost
158 # when exporting to XML so everything must be calculated fresh so importing
159 # from XML will work cleanly. Only byte offset and size information is
160 # calculated fresh. Count information like numberOfIndexSubTables is
161 # checked through assertions. If the information in this table was not
162 # touched or was changed properly then these types of values should match.
163 #
164 # The table will be rebuilt the following way:
165 # (0) Precompute the size of all the bitmapSizeTables. This is needed to
166 # compute the offsets properly.
167 # (1) For each bitmapSizeTable compute the indexSubTable and
168 # indexSubTableArray pair. The indexSubTable must be computed first
169 # so that the offset information in indexSubTableArray can be
170 # calculated. Update the data size after each pairing.
171 # (2) Build each bitmapSizeTable.
172 # (3) Consolidate all the data into the main dataList in the correct order.
174 for _ in self.strikes:
175 dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1)
176 dataSize += len(("hori", "vert")) * sstruct.calcsize(sbitLineMetricsFormat)
177 dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2)
179 indexSubTablePairDataList = []
180 for curStrike in self.strikes:
181 curTable = curStrike.bitmapSizeTable
182 curTable.numberOfIndexSubTables = len(curStrike.indexSubTables)
183 curTable.indexSubTableArrayOffset = dataSize
185 # Precompute the size of the indexSubTableArray. This information
186 # is important for correctly calculating the new value for
187 # additionalOffsetToIndexSubtable.
188 sizeOfSubTableArray = (
189 curTable.numberOfIndexSubTables * indexSubTableArraySize
190 )
191 lowerBound = dataSize
192 dataSize += sizeOfSubTableArray
193 upperBound = dataSize
195 indexSubTableDataList = []
196 for indexSubTable in curStrike.indexSubTables:
197 indexSubTable.additionalOffsetToIndexSubtable = (
198 dataSize - curTable.indexSubTableArrayOffset
199 )
200 glyphIds = list(map(ttFont.getGlyphID, indexSubTable.names))
201 indexSubTable.firstGlyphIndex = min(glyphIds)
202 indexSubTable.lastGlyphIndex = max(glyphIds)
203 data = indexSubTable.compile(ttFont)
204 indexSubTableDataList.append(data)
205 dataSize += len(data)
206 curTable.startGlyphIndex = min(
207 ist.firstGlyphIndex for ist in curStrike.indexSubTables
208 )
209 curTable.endGlyphIndex = max(
210 ist.lastGlyphIndex for ist in curStrike.indexSubTables
211 )
213 for i in curStrike.indexSubTables:
214 data = struct.pack(
215 indexSubHeaderFormat,
216 i.firstGlyphIndex,
217 i.lastGlyphIndex,
218 i.additionalOffsetToIndexSubtable,
219 )
220 indexSubTablePairDataList.append(data)
221 indexSubTablePairDataList.extend(indexSubTableDataList)
222 curTable.indexTablesSize = dataSize - curTable.indexSubTableArrayOffset
224 for curStrike in self.strikes:
225 curTable = curStrike.bitmapSizeTable
226 data = sstruct.pack(bitmapSizeTableFormatPart1, curTable)
227 dataList.append(data)
228 for metric in ("hori", "vert"):
229 metricObj = vars(curTable)[metric]
230 data = sstruct.pack(sbitLineMetricsFormat, metricObj)
231 dataList.append(data)
232 data = sstruct.pack(bitmapSizeTableFormatPart2, curTable)
233 dataList.append(data)
234 dataList.extend(indexSubTablePairDataList)
236 return bytesjoin(dataList)
238 def toXML(self, writer, ttFont):
239 writer.simpletag("header", [("version", self.version)])
240 writer.newline()
241 for curIndex, curStrike in enumerate(self.strikes):
242 curStrike.toXML(curIndex, writer, ttFont)
244 def fromXML(self, name, attrs, content, ttFont):
245 if name == "header":
246 self.version = safeEval(attrs["version"])
247 elif name == "strike":
248 if not hasattr(self, "strikes"):
249 self.strikes = []
250 strikeIndex = safeEval(attrs["index"])
251 curStrike = Strike()
252 curStrike.fromXML(name, attrs, content, ttFont, self)
254 # Grow the strike array to the appropriate size. The XML format
255 # allows for the strike index value to be out of order.
256 if strikeIndex >= len(self.strikes):
257 self.strikes += [None] * (strikeIndex + 1 - len(self.strikes))
258 assert self.strikes[strikeIndex] is None, "Duplicate strike EBLC indices."
259 self.strikes[strikeIndex] = curStrike
262class Strike(object):
263 def __init__(self):
264 self.bitmapSizeTable = BitmapSizeTable()
265 self.indexSubTables = []
267 def toXML(self, strikeIndex, writer, ttFont):
268 writer.begintag("strike", [("index", strikeIndex)])
269 writer.newline()
270 self.bitmapSizeTable.toXML(writer, ttFont)
271 writer.comment(
272 "GlyphIds are written but not read. The firstGlyphIndex and\nlastGlyphIndex values will be recalculated by the compiler."
273 )
274 writer.newline()
275 for indexSubTable in self.indexSubTables:
276 indexSubTable.toXML(writer, ttFont)
277 writer.endtag("strike")
278 writer.newline()
280 def fromXML(self, name, attrs, content, ttFont, locator):
281 for element in content:
282 if not isinstance(element, tuple):
283 continue
284 name, attrs, content = element
285 if name == "bitmapSizeTable":
286 self.bitmapSizeTable.fromXML(name, attrs, content, ttFont)
287 elif name.startswith(_indexSubTableSubclassPrefix):
288 indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix) :])
289 indexFormatClass = locator.getIndexFormatClass(indexFormat)
290 indexSubTable = indexFormatClass(None, None)
291 indexSubTable.indexFormat = indexFormat
292 indexSubTable.fromXML(name, attrs, content, ttFont)
293 self.indexSubTables.append(indexSubTable)
296class BitmapSizeTable(object):
297 # Returns all the simple metric names that bitmap size table
298 # cares about in terms of XML creation.
299 def _getXMLMetricNames(self):
300 dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1]
301 dataNames = dataNames + sstruct.getformat(bitmapSizeTableFormatPart2)[1]
302 # Skip the first 3 data names because they are byte offsets and counts.
303 return dataNames[3:]
305 def toXML(self, writer, ttFont):
306 writer.begintag("bitmapSizeTable")
307 writer.newline()
308 for metric in ("hori", "vert"):
309 getattr(self, metric).toXML(metric, writer, ttFont)
310 for metricName in self._getXMLMetricNames():
311 writer.simpletag(metricName, value=getattr(self, metricName))
312 writer.newline()
313 writer.endtag("bitmapSizeTable")
314 writer.newline()
316 def fromXML(self, name, attrs, content, ttFont):
317 # Create a lookup for all the simple names that make sense to
318 # bitmap size table. Only read the information from these names.
319 dataNames = set(self._getXMLMetricNames())
320 for element in content:
321 if not isinstance(element, tuple):
322 continue
323 name, attrs, content = element
324 if name == "sbitLineMetrics":
325 direction = attrs["direction"]
326 assert direction in (
327 "hori",
328 "vert",
329 ), "SbitLineMetrics direction specified invalid."
330 metricObj = SbitLineMetrics()
331 metricObj.fromXML(name, attrs, content, ttFont)
332 vars(self)[direction] = metricObj
333 elif name in dataNames:
334 vars(self)[name] = safeEval(attrs["value"])
335 else:
336 log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name)
339class SbitLineMetrics(object):
340 def toXML(self, name, writer, ttFont):
341 writer.begintag("sbitLineMetrics", [("direction", name)])
342 writer.newline()
343 for metricName in sstruct.getformat(sbitLineMetricsFormat)[1]:
344 writer.simpletag(metricName, value=getattr(self, metricName))
345 writer.newline()
346 writer.endtag("sbitLineMetrics")
347 writer.newline()
349 def fromXML(self, name, attrs, content, ttFont):
350 metricNames = set(sstruct.getformat(sbitLineMetricsFormat)[1])
351 for element in content:
352 if not isinstance(element, tuple):
353 continue
354 name, attrs, content = element
355 if name in metricNames:
356 vars(self)[name] = safeEval(attrs["value"])
359# Important information about the naming scheme. Used for identifying subtables.
360_indexSubTableSubclassPrefix = "eblc_index_sub_table_"
363class EblcIndexSubTable(object):
364 def __init__(self, data, ttFont):
365 self.data = data
366 self.ttFont = ttFont
367 # TODO Currently non-lazy decompiling doesn't work for this class...
368 # if not ttFont.lazy:
369 # self.decompile()
370 # del self.data, self.ttFont
372 def __getattr__(self, attr):
373 # Allow lazy decompile.
374 if attr[:2] == "__":
375 raise AttributeError(attr)
376 if attr == "data":
377 raise AttributeError(attr)
378 self.decompile()
379 return getattr(self, attr)
381 def ensureDecompiled(self, recurse=False):
382 if hasattr(self, "data"):
383 self.decompile()
385 # This method just takes care of the indexSubHeader. Implementing subclasses
386 # should call it to compile the indexSubHeader and then continue compiling
387 # the remainder of their unique format.
388 def compile(self, ttFont):
389 return struct.pack(
390 indexSubHeaderFormat,
391 self.indexFormat,
392 self.imageFormat,
393 self.imageDataOffset,
394 )
396 # Creates the XML for bitmap glyphs. Each index sub table basically makes
397 # the same XML except for specific metric information that is written
398 # out via a method call that a subclass implements optionally.
399 def toXML(self, writer, ttFont):
400 writer.begintag(
401 self.__class__.__name__,
402 [
403 ("imageFormat", self.imageFormat),
404 ("firstGlyphIndex", self.firstGlyphIndex),
405 ("lastGlyphIndex", self.lastGlyphIndex),
406 ],
407 )
408 writer.newline()
409 self.writeMetrics(writer, ttFont)
410 # Write out the names as thats all thats needed to rebuild etc.
411 # For font debugging of consecutive formats the ids are also written.
412 # The ids are not read when moving from the XML format.
413 glyphIds = map(ttFont.getGlyphID, self.names)
414 for glyphName, glyphId in zip(self.names, glyphIds):
415 writer.simpletag("glyphLoc", name=glyphName, id=glyphId)
416 writer.newline()
417 writer.endtag(self.__class__.__name__)
418 writer.newline()
420 def fromXML(self, name, attrs, content, ttFont):
421 # Read all the attributes. Even though the glyph indices are
422 # recalculated, they are still read in case there needs to
423 # be an immediate export of the data.
424 self.imageFormat = safeEval(attrs["imageFormat"])
425 self.firstGlyphIndex = safeEval(attrs["firstGlyphIndex"])
426 self.lastGlyphIndex = safeEval(attrs["lastGlyphIndex"])
428 self.readMetrics(name, attrs, content, ttFont)
430 self.names = []
431 for element in content:
432 if not isinstance(element, tuple):
433 continue
434 name, attrs, content = element
435 if name == "glyphLoc":
436 self.names.append(attrs["name"])
438 # A helper method that writes the metrics for the index sub table. It also
439 # is responsible for writing the image size for fixed size data since fixed
440 # size is not recalculated on compile. Default behavior is to do nothing.
441 def writeMetrics(self, writer, ttFont):
442 pass
444 # A helper method that is the inverse of writeMetrics.
445 def readMetrics(self, name, attrs, content, ttFont):
446 pass
448 # This method is for fixed glyph data sizes. There are formats where
449 # the glyph data is fixed but are actually composite glyphs. To handle
450 # this the font spec in indexSubTable makes the data the size of the
451 # fixed size by padding the component arrays. This function abstracts
452 # out this padding process. Input is data unpadded. Output is data
453 # padded only in fixed formats. Default behavior is to return the data.
454 def padBitmapData(self, data):
455 return data
457 # Remove any of the glyph locations and names that are flagged as skipped.
458 # This only occurs in formats {1,3}.
459 def removeSkipGlyphs(self):
460 # Determines if a name, location pair is a valid data location.
461 # Skip glyphs are marked when the size is equal to zero.
462 def isValidLocation(args):
463 (name, (startByte, endByte)) = args
464 return startByte < endByte
466 # Remove all skip glyphs.
467 dataPairs = list(filter(isValidLocation, zip(self.names, self.locations)))
468 self.names, self.locations = list(map(list, zip(*dataPairs)))
471# A closure for creating a custom mixin. This is done because formats 1 and 3
472# are very similar. The only difference between them is the size per offset
473# value. Code put in here should handle both cases generally.
474def _createOffsetArrayIndexSubTableMixin(formatStringForDataType):
475 # Prep the data size for the offset array data format.
476 dataFormat = ">" + formatStringForDataType
477 offsetDataSize = struct.calcsize(dataFormat)
479 class OffsetArrayIndexSubTableMixin(object):
480 def decompile(self):
481 numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1
482 indexingOffsets = [
483 glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs + 2)
484 ]
485 indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
486 offsetArray = [
487 struct.unpack(dataFormat, self.data[slice(*loc)])[0]
488 for loc in indexingLocations
489 ]
491 glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
492 modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray]
493 self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:]))
495 self.names = list(map(self.ttFont.getGlyphName, glyphIds))
496 self.removeSkipGlyphs()
497 del self.data, self.ttFont
499 def compile(self, ttFont):
500 # First make sure that all the data lines up properly. Formats 1 and 3
501 # must have all its data lined up consecutively. If not this will fail.
502 for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
503 assert (
504 curLoc[1] == nxtLoc[0]
505 ), "Data must be consecutive in indexSubTable offset formats"
507 glyphIds = list(map(ttFont.getGlyphID, self.names))
508 # Make sure that all ids are sorted strictly increasing.
509 assert all(glyphIds[i] < glyphIds[i + 1] for i in range(len(glyphIds) - 1))
511 # Run a simple algorithm to add skip glyphs to the data locations at
512 # the places where an id is not present.
513 idQueue = deque(glyphIds)
514 locQueue = deque(self.locations)
515 allGlyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
516 allLocations = []
517 for curId in allGlyphIds:
518 if curId != idQueue[0]:
519 allLocations.append((locQueue[0][0], locQueue[0][0]))
520 else:
521 idQueue.popleft()
522 allLocations.append(locQueue.popleft())
524 # Now that all the locations are collected, pack them appropriately into
525 # offsets. This is the form where offset[i] is the location and
526 # offset[i+1]-offset[i] is the size of the data location.
527 offsets = list(allLocations[0]) + [loc[1] for loc in allLocations[1:]]
528 # Image data offset must be less than or equal to the minimum of locations.
529 # This offset may change the value for round tripping but is safer and
530 # allows imageDataOffset to not be required to be in the XML version.
531 self.imageDataOffset = min(offsets)
532 offsetArray = [offset - self.imageDataOffset for offset in offsets]
534 dataList = [EblcIndexSubTable.compile(self, ttFont)]
535 dataList += [
536 struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray
537 ]
538 # Take care of any padding issues. Only occurs in format 3.
539 if offsetDataSize * len(offsetArray) % 4 != 0:
540 dataList.append(struct.pack(dataFormat, 0))
541 return bytesjoin(dataList)
543 return OffsetArrayIndexSubTableMixin
546# A Mixin for functionality shared between the different kinds
547# of fixed sized data handling. Both kinds have big metrics so
548# that kind of special processing is also handled in this mixin.
549class FixedSizeIndexSubTableMixin(object):
550 def writeMetrics(self, writer, ttFont):
551 writer.simpletag("imageSize", value=self.imageSize)
552 writer.newline()
553 self.metrics.toXML(writer, ttFont)
555 def readMetrics(self, name, attrs, content, ttFont):
556 for element in content:
557 if not isinstance(element, tuple):
558 continue
559 name, attrs, content = element
560 if name == "imageSize":
561 self.imageSize = safeEval(attrs["value"])
562 elif name == BigGlyphMetrics.__name__:
563 self.metrics = BigGlyphMetrics()
564 self.metrics.fromXML(name, attrs, content, ttFont)
565 elif name == SmallGlyphMetrics.__name__:
566 log.warning(
567 "SmallGlyphMetrics being ignored in format %d.", self.indexFormat
568 )
570 def padBitmapData(self, data):
571 # Make sure that the data isn't bigger than the fixed size.
572 assert len(data) <= self.imageSize, (
573 "Data in indexSubTable format %d must be less than the fixed size."
574 % self.indexFormat
575 )
576 # Pad the data so that it matches the fixed size.
577 pad = (self.imageSize - len(data)) * b"\0"
578 return data + pad
581class eblc_index_sub_table_1(
582 _createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable
583):
584 pass
587class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
588 def decompile(self):
589 (self.imageSize,) = struct.unpack(">L", self.data[:4])
590 self.metrics = BigGlyphMetrics()
591 sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics)
592 glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex + 1))
593 offsets = [
594 self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
595 ]
596 self.locations = list(zip(offsets, offsets[1:]))
597 self.names = list(map(self.ttFont.getGlyphName, glyphIds))
598 del self.data, self.ttFont
600 def compile(self, ttFont):
601 glyphIds = list(map(ttFont.getGlyphID, self.names))
602 # Make sure all the ids are consecutive. This is required by Format 2.
603 assert glyphIds == list(
604 range(self.firstGlyphIndex, self.lastGlyphIndex + 1)
605 ), "Format 2 ids must be consecutive."
606 self.imageDataOffset = min(next(iter(zip(*self.locations))))
608 dataList = [EblcIndexSubTable.compile(self, ttFont)]
609 dataList.append(struct.pack(">L", self.imageSize))
610 dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
611 return bytesjoin(dataList)
614class eblc_index_sub_table_3(
615 _createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable
616):
617 pass
620class eblc_index_sub_table_4(EblcIndexSubTable):
621 def decompile(self):
622 (numGlyphs,) = struct.unpack(">L", self.data[:4])
623 data = self.data[4:]
624 indexingOffsets = [
625 glyphIndex * codeOffsetPairSize for glyphIndex in range(numGlyphs + 2)
626 ]
627 indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
628 glyphArray = [
629 struct.unpack(codeOffsetPairFormat, data[slice(*loc)])
630 for loc in indexingLocations
631 ]
632 glyphIds, offsets = list(map(list, zip(*glyphArray)))
633 # There are one too many glyph ids. Get rid of the last one.
634 glyphIds.pop()
636 offsets = [offset + self.imageDataOffset for offset in offsets]
637 self.locations = list(zip(offsets, offsets[1:]))
638 self.names = list(map(self.ttFont.getGlyphName, glyphIds))
639 del self.data, self.ttFont
641 def compile(self, ttFont):
642 # First make sure that all the data lines up properly. Format 4
643 # must have all its data lined up consecutively. If not this will fail.
644 for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
645 assert (
646 curLoc[1] == nxtLoc[0]
647 ), "Data must be consecutive in indexSubTable format 4"
649 offsets = list(self.locations[0]) + [loc[1] for loc in self.locations[1:]]
650 # Image data offset must be less than or equal to the minimum of locations.
651 # Resetting this offset may change the value for round tripping but is safer
652 # and allows imageDataOffset to not be required to be in the XML version.
653 self.imageDataOffset = min(offsets)
654 offsets = [offset - self.imageDataOffset for offset in offsets]
655 glyphIds = list(map(ttFont.getGlyphID, self.names))
656 # Create an iterator over the ids plus a padding value.
657 idsPlusPad = list(itertools.chain(glyphIds, [0]))
659 dataList = [EblcIndexSubTable.compile(self, ttFont)]
660 dataList.append(struct.pack(">L", len(glyphIds)))
661 tmp = [
662 struct.pack(codeOffsetPairFormat, *cop) for cop in zip(idsPlusPad, offsets)
663 ]
664 dataList += tmp
665 data = bytesjoin(dataList)
666 return data
669class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
670 def decompile(self):
671 self.origDataLen = 0
672 (self.imageSize,) = struct.unpack(">L", self.data[:4])
673 data = self.data[4:]
674 self.metrics, data = sstruct.unpack2(
675 bigGlyphMetricsFormat, data, BigGlyphMetrics()
676 )
677 (numGlyphs,) = struct.unpack(">L", data[:4])
678 data = data[4:]
679 glyphIds = [
680 struct.unpack(">H", data[2 * i : 2 * (i + 1)])[0] for i in range(numGlyphs)
681 ]
683 offsets = [
684 self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds) + 1)
685 ]
686 self.locations = list(zip(offsets, offsets[1:]))
687 self.names = list(map(self.ttFont.getGlyphName, glyphIds))
688 del self.data, self.ttFont
690 def compile(self, ttFont):
691 self.imageDataOffset = min(next(iter(zip(*self.locations))))
692 dataList = [EblcIndexSubTable.compile(self, ttFont)]
693 dataList.append(struct.pack(">L", self.imageSize))
694 dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
695 glyphIds = list(map(ttFont.getGlyphID, self.names))
696 dataList.append(struct.pack(">L", len(glyphIds)))
697 dataList += [struct.pack(">H", curId) for curId in glyphIds]
698 if len(glyphIds) % 2 == 1:
699 dataList.append(struct.pack(">H", 0))
700 return bytesjoin(dataList)
703# Dictionary of indexFormat to the class representing that format.
704eblc_sub_table_classes = {
705 1: eblc_index_sub_table_1,
706 2: eblc_index_sub_table_2,
707 3: eblc_index_sub_table_3,
708 4: eblc_index_sub_table_4,
709 5: eblc_index_sub_table_5,
710}