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