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 14 15 16log = logging.getLogger(__name__) 17 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""" 57 58indexSubTableArrayFormat = ">HHL" 59indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat) 60 61indexSubHeaderFormat = ">HHL" 62indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat) 63 64codeOffsetPairFormat = ">HH" 65codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat) 66 67 68class table_E_B_L_C_(DefaultTable.DefaultTable): 69 dependencies = ["EBDT"] 70 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] 76 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 81 82 dummy = sstruct.unpack(eblcHeaderFormat, data[:8], self) 83 i += 8 84 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 105 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 ) 113 114 tup = struct.unpack( 115 indexSubTableArrayFormat, data[i : i + indexSubTableArraySize] 116 ) 117 (firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup 118 i = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable 119 120 tup = struct.unpack( 121 indexSubHeaderFormat, data[i : i + indexSubHeaderSize] 122 ) 123 (indexFormat, imageFormat, imageDataOffset) = tup 124 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) 137 138 def compile(self, ttFont): 139 dataList = [] 140 self.numSizes = len(self.strikes) 141 dataList.append(sstruct.pack(eblcHeaderFormat, self)) 142 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]) 147 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. 173 174 for _ in self.strikes: 175 dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1) 176 dataSize += len(("hori", "vert")) * sstruct.calcsize(sbitLineMetricsFormat) 177 dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2) 178 179 indexSubTablePairDataList = [] 180 for curStrike in self.strikes: 181 curTable = curStrike.bitmapSizeTable 182 curTable.numberOfIndexSubTables = len(curStrike.indexSubTables) 183 curTable.indexSubTableArrayOffset = dataSize 184 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 194 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 ) 212 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 223 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) 235 236 return bytesjoin(dataList) 237 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) 243 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) 253 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 260 261 262class Strike(object): 263 def __init__(self): 264 self.bitmapSizeTable = BitmapSizeTable() 265 self.indexSubTables = [] 266 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() 279 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) 294 295 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:] 304 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() 315 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) 337 338 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() 348 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"]) 357 358 359# Important information about the naming scheme. Used for identifying subtables. 360_indexSubTableSubclassPrefix = "eblc_index_sub_table_" 361 362 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 371 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) 380 381 def ensureDecompiled(self, recurse=False): 382 if hasattr(self, "data"): 383 self.decompile() 384 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 ) 395 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() 419 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"]) 427 428 self.readMetrics(name, attrs, content, ttFont) 429 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"]) 437 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 443 444 # A helper method that is the inverse of writeMetrics. 445 def readMetrics(self, name, attrs, content, ttFont): 446 pass 447 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 456 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 465 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))) 469 470 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) 478 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 ] 490 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:])) 494 495 self.names = list(map(self.ttFont.getGlyphName, glyphIds)) 496 self.removeSkipGlyphs() 497 del self.data, self.ttFont 498 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" 506 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)) 510 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()) 523 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] 533 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) 542 543 return OffsetArrayIndexSubTableMixin 544 545 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) 554 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 ) 569 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 579 580 581class eblc_index_sub_table_1( 582 _createOffsetArrayIndexSubTableMixin("L"), EblcIndexSubTable 583): 584 pass 585 586 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 599 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)))) 607 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) 612 613 614class eblc_index_sub_table_3( 615 _createOffsetArrayIndexSubTableMixin("H"), EblcIndexSubTable 616): 617 pass 618 619 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() 635 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 640 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" 648 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])) 658 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 667 668 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 ] 682 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 689 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) 701 702 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} 711