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