Coverage for /usr/lib/python3/dist-packages/fontTools/ttLib/tables/E_B_L_C_.py: 19%

384 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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}