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