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

515 statements  

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

1from fontTools.misc import sstruct 

2from fontTools.misc.textTools import ( 

3 bytechr, 

4 byteord, 

5 bytesjoin, 

6 strjoin, 

7 safeEval, 

8 readHex, 

9 hexStr, 

10 deHexStr, 

11) 

12from .BitmapGlyphMetrics import ( 

13 BigGlyphMetrics, 

14 bigGlyphMetricsFormat, 

15 SmallGlyphMetrics, 

16 smallGlyphMetricsFormat, 

17) 

18from . import DefaultTable 

19import itertools 

20import os 

21import struct 

22import logging 

23 

24 

25log = logging.getLogger(__name__) 

26 

27ebdtTableVersionFormat = """ 

28 > # big endian 

29 version: 16.16F 

30""" 

31 

32ebdtComponentFormat = """ 

33 > # big endian 

34 glyphCode: H 

35 xOffset: b 

36 yOffset: b 

37""" 

38 

39 

40class table_E_B_D_T_(DefaultTable.DefaultTable): 

41 # Keep a reference to the name of the data locator table. 

42 locatorName = "EBLC" 

43 

44 # This method can be overridden in subclasses to support new formats 

45 # without changing the other implementation. Also can be used as a 

46 # convenience method for coverting a font file to an alternative format. 

47 def getImageFormatClass(self, imageFormat): 

48 return ebdt_bitmap_classes[imageFormat] 

49 

50 def decompile(self, data, ttFont): 

51 # Get the version but don't advance the slice. 

52 # Most of the lookup for this table is done relative 

53 # to the begining so slice by the offsets provided 

54 # in the EBLC table. 

55 sstruct.unpack2(ebdtTableVersionFormat, data, self) 

56 

57 # Keep a dict of glyphs that have been seen so they aren't remade. 

58 # This dict maps intervals of data to the BitmapGlyph. 

59 glyphDict = {} 

60 

61 # Pull out the EBLC table and loop through glyphs. 

62 # A strike is a concept that spans both tables. 

63 # The actual bitmap data is stored in the EBDT. 

64 locator = ttFont[self.__class__.locatorName] 

65 self.strikeData = [] 

66 for curStrike in locator.strikes: 

67 bitmapGlyphDict = {} 

68 self.strikeData.append(bitmapGlyphDict) 

69 for indexSubTable in curStrike.indexSubTables: 

70 dataIter = zip(indexSubTable.names, indexSubTable.locations) 

71 for curName, curLoc in dataIter: 

72 # Don't create duplicate data entries for the same glyphs. 

73 # Instead just use the structures that already exist if they exist. 

74 if curLoc in glyphDict: 

75 curGlyph = glyphDict[curLoc] 

76 else: 

77 curGlyphData = data[slice(*curLoc)] 

78 imageFormatClass = self.getImageFormatClass( 

79 indexSubTable.imageFormat 

80 ) 

81 curGlyph = imageFormatClass(curGlyphData, ttFont) 

82 glyphDict[curLoc] = curGlyph 

83 bitmapGlyphDict[curName] = curGlyph 

84 

85 def compile(self, ttFont): 

86 dataList = [] 

87 dataList.append(sstruct.pack(ebdtTableVersionFormat, self)) 

88 dataSize = len(dataList[0]) 

89 

90 # Keep a dict of glyphs that have been seen so they aren't remade. 

91 # This dict maps the id of the BitmapGlyph to the interval 

92 # in the data. 

93 glyphDict = {} 

94 

95 # Go through the bitmap glyph data. Just in case the data for a glyph 

96 # changed the size metrics should be recalculated. There are a variety 

97 # of formats and they get stored in the EBLC table. That is why 

98 # recalculation is defered to the EblcIndexSubTable class and just 

99 # pass what is known about bitmap glyphs from this particular table. 

100 locator = ttFont[self.__class__.locatorName] 

101 for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData): 

102 for curIndexSubTable in curStrike.indexSubTables: 

103 dataLocations = [] 

104 for curName in curIndexSubTable.names: 

105 # Handle the data placement based on seeing the glyph or not. 

106 # Just save a reference to the location if the glyph has already 

107 # been saved in compile. This code assumes that glyphs will only 

108 # be referenced multiple times from indexFormat5. By luck the 

109 # code may still work when referencing poorly ordered fonts with 

110 # duplicate references. If there is a font that is unlucky the 

111 # respective compile methods for the indexSubTables will fail 

112 # their assertions. All fonts seem to follow this assumption. 

113 # More complicated packing may be needed if a counter-font exists. 

114 glyph = curGlyphDict[curName] 

115 objectId = id(glyph) 

116 if objectId not in glyphDict: 

117 data = glyph.compile(ttFont) 

118 data = curIndexSubTable.padBitmapData(data) 

119 startByte = dataSize 

120 dataSize += len(data) 

121 endByte = dataSize 

122 dataList.append(data) 

123 dataLoc = (startByte, endByte) 

124 glyphDict[objectId] = dataLoc 

125 else: 

126 dataLoc = glyphDict[objectId] 

127 dataLocations.append(dataLoc) 

128 # Just use the new data locations in the indexSubTable. 

129 # The respective compile implementations will take care 

130 # of any of the problems in the convertion that may arise. 

131 curIndexSubTable.locations = dataLocations 

132 

133 return bytesjoin(dataList) 

134 

135 def toXML(self, writer, ttFont): 

136 # When exporting to XML if one of the data export formats 

137 # requires metrics then those metrics may be in the locator. 

138 # In this case populate the bitmaps with "export metrics". 

139 if ttFont.bitmapGlyphDataFormat in ("row", "bitwise"): 

140 locator = ttFont[self.__class__.locatorName] 

141 for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData): 

142 for curIndexSubTable in curStrike.indexSubTables: 

143 for curName in curIndexSubTable.names: 

144 glyph = curGlyphDict[curName] 

145 # I'm not sure which metrics have priority here. 

146 # For now if both metrics exist go with glyph metrics. 

147 if hasattr(glyph, "metrics"): 

148 glyph.exportMetrics = glyph.metrics 

149 else: 

150 glyph.exportMetrics = curIndexSubTable.metrics 

151 glyph.exportBitDepth = curStrike.bitmapSizeTable.bitDepth 

152 

153 writer.simpletag("header", [("version", self.version)]) 

154 writer.newline() 

155 locator = ttFont[self.__class__.locatorName] 

156 for strikeIndex, bitmapGlyphDict in enumerate(self.strikeData): 

157 writer.begintag("strikedata", [("index", strikeIndex)]) 

158 writer.newline() 

159 for curName, curBitmap in bitmapGlyphDict.items(): 

160 curBitmap.toXML(strikeIndex, curName, writer, ttFont) 

161 writer.endtag("strikedata") 

162 writer.newline() 

163 

164 def fromXML(self, name, attrs, content, ttFont): 

165 if name == "header": 

166 self.version = safeEval(attrs["version"]) 

167 elif name == "strikedata": 

168 if not hasattr(self, "strikeData"): 

169 self.strikeData = [] 

170 strikeIndex = safeEval(attrs["index"]) 

171 

172 bitmapGlyphDict = {} 

173 for element in content: 

174 if not isinstance(element, tuple): 

175 continue 

176 name, attrs, content = element 

177 if name[4:].startswith(_bitmapGlyphSubclassPrefix[4:]): 

178 imageFormat = safeEval(name[len(_bitmapGlyphSubclassPrefix) :]) 

179 glyphName = attrs["name"] 

180 imageFormatClass = self.getImageFormatClass(imageFormat) 

181 curGlyph = imageFormatClass(None, None) 

182 curGlyph.fromXML(name, attrs, content, ttFont) 

183 assert glyphName not in bitmapGlyphDict, ( 

184 "Duplicate glyphs with the same name '%s' in the same strike." 

185 % glyphName 

186 ) 

187 bitmapGlyphDict[glyphName] = curGlyph 

188 else: 

189 log.warning("%s being ignored by %s", name, self.__class__.__name__) 

190 

191 # Grow the strike data array to the appropriate size. The XML 

192 # format allows the strike index value to be out of order. 

193 if strikeIndex >= len(self.strikeData): 

194 self.strikeData += [None] * (strikeIndex + 1 - len(self.strikeData)) 

195 assert ( 

196 self.strikeData[strikeIndex] is None 

197 ), "Duplicate strike EBDT indices." 

198 self.strikeData[strikeIndex] = bitmapGlyphDict 

199 

200 

201class EbdtComponent(object): 

202 def toXML(self, writer, ttFont): 

203 writer.begintag("ebdtComponent", [("name", self.name)]) 

204 writer.newline() 

205 for componentName in sstruct.getformat(ebdtComponentFormat)[1][1:]: 

206 writer.simpletag(componentName, value=getattr(self, componentName)) 

207 writer.newline() 

208 writer.endtag("ebdtComponent") 

209 writer.newline() 

210 

211 def fromXML(self, name, attrs, content, ttFont): 

212 self.name = attrs["name"] 

213 componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:]) 

214 for element in content: 

215 if not isinstance(element, tuple): 

216 continue 

217 name, attrs, content = element 

218 if name in componentNames: 

219 vars(self)[name] = safeEval(attrs["value"]) 

220 else: 

221 log.warning("unknown name '%s' being ignored by EbdtComponent.", name) 

222 

223 

224# Helper functions for dealing with binary. 

225 

226 

227def _data2binary(data, numBits): 

228 binaryList = [] 

229 for curByte in data: 

230 value = byteord(curByte) 

231 numBitsCut = min(8, numBits) 

232 for i in range(numBitsCut): 

233 if value & 0x1: 

234 binaryList.append("1") 

235 else: 

236 binaryList.append("0") 

237 value = value >> 1 

238 numBits -= numBitsCut 

239 return strjoin(binaryList) 

240 

241 

242def _binary2data(binary): 

243 byteList = [] 

244 for bitLoc in range(0, len(binary), 8): 

245 byteString = binary[bitLoc : bitLoc + 8] 

246 curByte = 0 

247 for curBit in reversed(byteString): 

248 curByte = curByte << 1 

249 if curBit == "1": 

250 curByte |= 1 

251 byteList.append(bytechr(curByte)) 

252 return bytesjoin(byteList) 

253 

254 

255def _memoize(f): 

256 class memodict(dict): 

257 def __missing__(self, key): 

258 ret = f(key) 

259 if isinstance(key, int) or len(key) == 1: 

260 self[key] = ret 

261 return ret 

262 

263 return memodict().__getitem__ 

264 

265 

266# 00100111 -> 11100100 per byte, not to be confused with little/big endian. 

267# Bitmap data per byte is in the order that binary is written on the page 

268# with the least significant bit as far right as possible. This is the 

269# opposite of what makes sense algorithmically and hence this function. 

270@_memoize 

271def _reverseBytes(data): 

272 r""" 

273 >>> bin(ord(_reverseBytes(0b00100111))) 

274 '0b11100100' 

275 >>> _reverseBytes(b'\x00\xf0') 

276 b'\x00\x0f' 

277 """ 

278 if isinstance(data, bytes) and len(data) != 1: 

279 return bytesjoin(map(_reverseBytes, data)) 

280 byte = byteord(data) 

281 result = 0 

282 for i in range(8): 

283 result = result << 1 

284 result |= byte & 1 

285 byte = byte >> 1 

286 return bytechr(result) 

287 

288 

289# This section of code is for reading and writing image data to/from XML. 

290 

291 

292def _writeRawImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 

293 writer.begintag("rawimagedata") 

294 writer.newline() 

295 writer.dumphex(bitmapObject.imageData) 

296 writer.endtag("rawimagedata") 

297 writer.newline() 

298 

299 

300def _readRawImageData(bitmapObject, name, attrs, content, ttFont): 

301 bitmapObject.imageData = readHex(content) 

302 

303 

304def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 

305 metrics = bitmapObject.exportMetrics 

306 del bitmapObject.exportMetrics 

307 bitDepth = bitmapObject.exportBitDepth 

308 del bitmapObject.exportBitDepth 

309 

310 writer.begintag( 

311 "rowimagedata", bitDepth=bitDepth, width=metrics.width, height=metrics.height 

312 ) 

313 writer.newline() 

314 for curRow in range(metrics.height): 

315 rowData = bitmapObject.getRow(curRow, bitDepth=bitDepth, metrics=metrics) 

316 writer.simpletag("row", value=hexStr(rowData)) 

317 writer.newline() 

318 writer.endtag("rowimagedata") 

319 writer.newline() 

320 

321 

322def _readRowImageData(bitmapObject, name, attrs, content, ttFont): 

323 bitDepth = safeEval(attrs["bitDepth"]) 

324 metrics = SmallGlyphMetrics() 

325 metrics.width = safeEval(attrs["width"]) 

326 metrics.height = safeEval(attrs["height"]) 

327 

328 dataRows = [] 

329 for element in content: 

330 if not isinstance(element, tuple): 

331 continue 

332 name, attr, content = element 

333 # Chop off 'imagedata' from the tag to get just the option. 

334 if name == "row": 

335 dataRows.append(deHexStr(attr["value"])) 

336 bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics) 

337 

338 

339def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 

340 metrics = bitmapObject.exportMetrics 

341 del bitmapObject.exportMetrics 

342 bitDepth = bitmapObject.exportBitDepth 

343 del bitmapObject.exportBitDepth 

344 

345 # A dict for mapping binary to more readable/artistic ASCII characters. 

346 binaryConv = {"0": ".", "1": "@"} 

347 

348 writer.begintag( 

349 "bitwiseimagedata", 

350 bitDepth=bitDepth, 

351 width=metrics.width, 

352 height=metrics.height, 

353 ) 

354 writer.newline() 

355 for curRow in range(metrics.height): 

356 rowData = bitmapObject.getRow( 

357 curRow, bitDepth=1, metrics=metrics, reverseBytes=True 

358 ) 

359 rowData = _data2binary(rowData, metrics.width) 

360 # Make the output a readable ASCII art form. 

361 rowData = strjoin(map(binaryConv.get, rowData)) 

362 writer.simpletag("row", value=rowData) 

363 writer.newline() 

364 writer.endtag("bitwiseimagedata") 

365 writer.newline() 

366 

367 

368def _readBitwiseImageData(bitmapObject, name, attrs, content, ttFont): 

369 bitDepth = safeEval(attrs["bitDepth"]) 

370 metrics = SmallGlyphMetrics() 

371 metrics.width = safeEval(attrs["width"]) 

372 metrics.height = safeEval(attrs["height"]) 

373 

374 # A dict for mapping from ASCII to binary. All characters are considered 

375 # a '1' except space, period and '0' which maps to '0'. 

376 binaryConv = {" ": "0", ".": "0", "0": "0"} 

377 

378 dataRows = [] 

379 for element in content: 

380 if not isinstance(element, tuple): 

381 continue 

382 name, attr, content = element 

383 if name == "row": 

384 mapParams = zip(attr["value"], itertools.repeat("1")) 

385 rowData = strjoin(itertools.starmap(binaryConv.get, mapParams)) 

386 dataRows.append(_binary2data(rowData)) 

387 

388 bitmapObject.setRows( 

389 dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True 

390 ) 

391 

392 

393def _writeExtFileImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 

394 try: 

395 folder = os.path.dirname(writer.file.name) 

396 except AttributeError: 

397 # fall back to current directory if output file's directory isn't found 

398 folder = "." 

399 folder = os.path.join(folder, "bitmaps") 

400 filename = glyphName + bitmapObject.fileExtension 

401 if not os.path.isdir(folder): 

402 os.makedirs(folder) 

403 folder = os.path.join(folder, "strike%d" % strikeIndex) 

404 if not os.path.isdir(folder): 

405 os.makedirs(folder) 

406 

407 fullPath = os.path.join(folder, filename) 

408 writer.simpletag("extfileimagedata", value=fullPath) 

409 writer.newline() 

410 

411 with open(fullPath, "wb") as file: 

412 file.write(bitmapObject.imageData) 

413 

414 

415def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont): 

416 fullPath = attrs["value"] 

417 with open(fullPath, "rb") as file: 

418 bitmapObject.imageData = file.read() 

419 

420 

421# End of XML writing code. 

422 

423# Important information about the naming scheme. Used for identifying formats 

424# in XML. 

425_bitmapGlyphSubclassPrefix = "ebdt_bitmap_format_" 

426 

427 

428class BitmapGlyph(object): 

429 # For the external file format. This can be changed in subclasses. This way 

430 # when the extfile option is turned on files have the form: glyphName.ext 

431 # The default is just a flat binary file with no meaning. 

432 fileExtension = ".bin" 

433 

434 # Keep track of reading and writing of various forms. 

435 xmlDataFunctions = { 

436 "raw": (_writeRawImageData, _readRawImageData), 

437 "row": (_writeRowImageData, _readRowImageData), 

438 "bitwise": (_writeBitwiseImageData, _readBitwiseImageData), 

439 "extfile": (_writeExtFileImageData, _readExtFileImageData), 

440 } 

441 

442 def __init__(self, data, ttFont): 

443 self.data = data 

444 self.ttFont = ttFont 

445 # TODO Currently non-lazy decompilation is untested here... 

446 # if not ttFont.lazy: 

447 # self.decompile() 

448 # del self.data 

449 

450 def __getattr__(self, attr): 

451 # Allow lazy decompile. 

452 if attr[:2] == "__": 

453 raise AttributeError(attr) 

454 if attr == "data": 

455 raise AttributeError(attr) 

456 self.decompile() 

457 del self.data 

458 return getattr(self, attr) 

459 

460 def ensureDecompiled(self, recurse=False): 

461 if hasattr(self, "data"): 

462 self.decompile() 

463 del self.data 

464 

465 # Not a fan of this but it is needed for safer safety checking. 

466 def getFormat(self): 

467 return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix) :]) 

468 

469 def toXML(self, strikeIndex, glyphName, writer, ttFont): 

470 writer.begintag(self.__class__.__name__, [("name", glyphName)]) 

471 writer.newline() 

472 

473 self.writeMetrics(writer, ttFont) 

474 # Use the internal write method to write using the correct output format. 

475 self.writeData(strikeIndex, glyphName, writer, ttFont) 

476 

477 writer.endtag(self.__class__.__name__) 

478 writer.newline() 

479 

480 def fromXML(self, name, attrs, content, ttFont): 

481 self.readMetrics(name, attrs, content, ttFont) 

482 for element in content: 

483 if not isinstance(element, tuple): 

484 continue 

485 name, attr, content = element 

486 if not name.endswith("imagedata"): 

487 continue 

488 # Chop off 'imagedata' from the tag to get just the option. 

489 option = name[: -len("imagedata")] 

490 assert option in self.__class__.xmlDataFunctions 

491 self.readData(name, attr, content, ttFont) 

492 

493 # Some of the glyphs have the metrics. This allows for metrics to be 

494 # added if the glyph format has them. Default behavior is to do nothing. 

495 def writeMetrics(self, writer, ttFont): 

496 pass 

497 

498 # The opposite of write metrics. 

499 def readMetrics(self, name, attrs, content, ttFont): 

500 pass 

501 

502 def writeData(self, strikeIndex, glyphName, writer, ttFont): 

503 try: 

504 writeFunc, readFunc = self.__class__.xmlDataFunctions[ 

505 ttFont.bitmapGlyphDataFormat 

506 ] 

507 except KeyError: 

508 writeFunc = _writeRawImageData 

509 writeFunc(strikeIndex, glyphName, self, writer, ttFont) 

510 

511 def readData(self, name, attrs, content, ttFont): 

512 # Chop off 'imagedata' from the tag to get just the option. 

513 option = name[: -len("imagedata")] 

514 writeFunc, readFunc = self.__class__.xmlDataFunctions[option] 

515 readFunc(self, name, attrs, content, ttFont) 

516 

517 

518# A closure for creating a mixin for the two types of metrics handling. 

519# Most of the code is very similar so its easier to deal with here. 

520# Everything works just by passing the class that the mixin is for. 

521def _createBitmapPlusMetricsMixin(metricsClass): 

522 # Both metrics names are listed here to make meaningful error messages. 

523 metricStrings = [BigGlyphMetrics.__name__, SmallGlyphMetrics.__name__] 

524 curMetricsName = metricsClass.__name__ 

525 # Find which metrics this is for and determine the opposite name. 

526 metricsId = metricStrings.index(curMetricsName) 

527 oppositeMetricsName = metricStrings[1 - metricsId] 

528 

529 class BitmapPlusMetricsMixin(object): 

530 def writeMetrics(self, writer, ttFont): 

531 self.metrics.toXML(writer, ttFont) 

532 

533 def readMetrics(self, name, attrs, content, ttFont): 

534 for element in content: 

535 if not isinstance(element, tuple): 

536 continue 

537 name, attrs, content = element 

538 if name == curMetricsName: 

539 self.metrics = metricsClass() 

540 self.metrics.fromXML(name, attrs, content, ttFont) 

541 elif name == oppositeMetricsName: 

542 log.warning( 

543 "Warning: %s being ignored in format %d.", 

544 oppositeMetricsName, 

545 self.getFormat(), 

546 ) 

547 

548 return BitmapPlusMetricsMixin 

549 

550 

551# Since there are only two types of mixin's just create them here. 

552BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics) 

553BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics) 

554 

555 

556# Data that is bit aligned can be tricky to deal with. These classes implement 

557# helper functionality for dealing with the data and getting a particular row 

558# of bitwise data. Also helps implement fancy data export/import in XML. 

559class BitAlignedBitmapMixin(object): 

560 def _getBitRange(self, row, bitDepth, metrics): 

561 rowBits = bitDepth * metrics.width 

562 bitOffset = row * rowBits 

563 return (bitOffset, bitOffset + rowBits) 

564 

565 def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): 

566 if metrics is None: 

567 metrics = self.metrics 

568 assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" 

569 

570 # Loop through each byte. This can cover two bytes in the original data or 

571 # a single byte if things happen to be aligned. The very last entry might 

572 # not be aligned so take care to trim the binary data to size and pad with 

573 # zeros in the row data. Bit aligned data is somewhat tricky. 

574 # 

575 # Example of data cut. Data cut represented in x's. 

576 # '|' represents byte boundary. 

577 # data = ...0XX|XXXXXX00|000... => XXXXXXXX 

578 # or 

579 # data = ...0XX|XXXX0000|000... => XXXXXX00 

580 # or 

581 # data = ...000|XXXXXXXX|000... => XXXXXXXX 

582 # or 

583 # data = ...000|00XXXX00|000... => XXXX0000 

584 # 

585 dataList = [] 

586 bitRange = self._getBitRange(row, bitDepth, metrics) 

587 stepRange = bitRange + (8,) 

588 for curBit in range(*stepRange): 

589 endBit = min(curBit + 8, bitRange[1]) 

590 numBits = endBit - curBit 

591 cutPoint = curBit % 8 

592 firstByteLoc = curBit // 8 

593 secondByteLoc = endBit // 8 

594 if firstByteLoc < secondByteLoc: 

595 numBitsCut = 8 - cutPoint 

596 else: 

597 numBitsCut = endBit - curBit 

598 curByte = _reverseBytes(self.imageData[firstByteLoc]) 

599 firstHalf = byteord(curByte) >> cutPoint 

600 firstHalf = ((1 << numBitsCut) - 1) & firstHalf 

601 newByte = firstHalf 

602 if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData): 

603 curByte = _reverseBytes(self.imageData[secondByteLoc]) 

604 secondHalf = byteord(curByte) << numBitsCut 

605 newByte = (firstHalf | secondHalf) & ((1 << numBits) - 1) 

606 dataList.append(bytechr(newByte)) 

607 

608 # The way the data is kept is opposite the algorithm used. 

609 data = bytesjoin(dataList) 

610 if not reverseBytes: 

611 data = _reverseBytes(data) 

612 return data 

613 

614 def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False): 

615 if metrics is None: 

616 metrics = self.metrics 

617 if not reverseBytes: 

618 dataRows = list(map(_reverseBytes, dataRows)) 

619 

620 # Keep track of a list of ordinal values as they are easier to modify 

621 # than a list of strings. Map to actual strings later. 

622 numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) // 8 

623 ordDataList = [0] * numBytes 

624 for row, data in enumerate(dataRows): 

625 bitRange = self._getBitRange(row, bitDepth, metrics) 

626 stepRange = bitRange + (8,) 

627 for curBit, curByte in zip(range(*stepRange), data): 

628 endBit = min(curBit + 8, bitRange[1]) 

629 cutPoint = curBit % 8 

630 firstByteLoc = curBit // 8 

631 secondByteLoc = endBit // 8 

632 if firstByteLoc < secondByteLoc: 

633 numBitsCut = 8 - cutPoint 

634 else: 

635 numBitsCut = endBit - curBit 

636 curByte = byteord(curByte) 

637 firstByte = curByte & ((1 << numBitsCut) - 1) 

638 ordDataList[firstByteLoc] |= firstByte << cutPoint 

639 if firstByteLoc < secondByteLoc and secondByteLoc < numBytes: 

640 secondByte = (curByte >> numBitsCut) & ((1 << 8 - numBitsCut) - 1) 

641 ordDataList[secondByteLoc] |= secondByte 

642 

643 # Save the image data with the bits going the correct way. 

644 self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList))) 

645 

646 

647class ByteAlignedBitmapMixin(object): 

648 def _getByteRange(self, row, bitDepth, metrics): 

649 rowBytes = (bitDepth * metrics.width + 7) // 8 

650 byteOffset = row * rowBytes 

651 return (byteOffset, byteOffset + rowBytes) 

652 

653 def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): 

654 if metrics is None: 

655 metrics = self.metrics 

656 assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" 

657 byteRange = self._getByteRange(row, bitDepth, metrics) 

658 data = self.imageData[slice(*byteRange)] 

659 if reverseBytes: 

660 data = _reverseBytes(data) 

661 return data 

662 

663 def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False): 

664 if metrics is None: 

665 metrics = self.metrics 

666 if reverseBytes: 

667 dataRows = map(_reverseBytes, dataRows) 

668 self.imageData = bytesjoin(dataRows) 

669 

670 

671class ebdt_bitmap_format_1( 

672 ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph 

673): 

674 def decompile(self): 

675 self.metrics = SmallGlyphMetrics() 

676 dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) 

677 self.imageData = data 

678 

679 def compile(self, ttFont): 

680 data = sstruct.pack(smallGlyphMetricsFormat, self.metrics) 

681 return data + self.imageData 

682 

683 

684class ebdt_bitmap_format_2( 

685 BitAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph 

686): 

687 def decompile(self): 

688 self.metrics = SmallGlyphMetrics() 

689 dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) 

690 self.imageData = data 

691 

692 def compile(self, ttFont): 

693 data = sstruct.pack(smallGlyphMetricsFormat, self.metrics) 

694 return data + self.imageData 

695 

696 

697class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph): 

698 def decompile(self): 

699 self.imageData = self.data 

700 

701 def compile(self, ttFont): 

702 return self.imageData 

703 

704 

705class ebdt_bitmap_format_6( 

706 ByteAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph 

707): 

708 def decompile(self): 

709 self.metrics = BigGlyphMetrics() 

710 dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) 

711 self.imageData = data 

712 

713 def compile(self, ttFont): 

714 data = sstruct.pack(bigGlyphMetricsFormat, self.metrics) 

715 return data + self.imageData 

716 

717 

718class ebdt_bitmap_format_7( 

719 BitAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph 

720): 

721 def decompile(self): 

722 self.metrics = BigGlyphMetrics() 

723 dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) 

724 self.imageData = data 

725 

726 def compile(self, ttFont): 

727 data = sstruct.pack(bigGlyphMetricsFormat, self.metrics) 

728 return data + self.imageData 

729 

730 

731class ComponentBitmapGlyph(BitmapGlyph): 

732 def toXML(self, strikeIndex, glyphName, writer, ttFont): 

733 writer.begintag(self.__class__.__name__, [("name", glyphName)]) 

734 writer.newline() 

735 

736 self.writeMetrics(writer, ttFont) 

737 

738 writer.begintag("components") 

739 writer.newline() 

740 for curComponent in self.componentArray: 

741 curComponent.toXML(writer, ttFont) 

742 writer.endtag("components") 

743 writer.newline() 

744 

745 writer.endtag(self.__class__.__name__) 

746 writer.newline() 

747 

748 def fromXML(self, name, attrs, content, ttFont): 

749 self.readMetrics(name, attrs, content, ttFont) 

750 for element in content: 

751 if not isinstance(element, tuple): 

752 continue 

753 name, attr, content = element 

754 if name == "components": 

755 self.componentArray = [] 

756 for compElement in content: 

757 if not isinstance(compElement, tuple): 

758 continue 

759 name, attrs, content = compElement 

760 if name == "ebdtComponent": 

761 curComponent = EbdtComponent() 

762 curComponent.fromXML(name, attrs, content, ttFont) 

763 self.componentArray.append(curComponent) 

764 else: 

765 log.warning("'%s' being ignored in component array.", name) 

766 

767 

768class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph): 

769 def decompile(self): 

770 self.metrics = SmallGlyphMetrics() 

771 dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) 

772 data = data[1:] 

773 

774 (numComponents,) = struct.unpack(">H", data[:2]) 

775 data = data[2:] 

776 self.componentArray = [] 

777 for i in range(numComponents): 

778 curComponent = EbdtComponent() 

779 dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent) 

780 curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode) 

781 self.componentArray.append(curComponent) 

782 

783 def compile(self, ttFont): 

784 dataList = [] 

785 dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics)) 

786 dataList.append(b"\0") 

787 dataList.append(struct.pack(">H", len(self.componentArray))) 

788 for curComponent in self.componentArray: 

789 curComponent.glyphCode = ttFont.getGlyphID(curComponent.name) 

790 dataList.append(sstruct.pack(ebdtComponentFormat, curComponent)) 

791 return bytesjoin(dataList) 

792 

793 

794class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph): 

795 def decompile(self): 

796 self.metrics = BigGlyphMetrics() 

797 dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) 

798 (numComponents,) = struct.unpack(">H", data[:2]) 

799 data = data[2:] 

800 self.componentArray = [] 

801 for i in range(numComponents): 

802 curComponent = EbdtComponent() 

803 dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent) 

804 curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode) 

805 self.componentArray.append(curComponent) 

806 

807 def compile(self, ttFont): 

808 dataList = [] 

809 dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) 

810 dataList.append(struct.pack(">H", len(self.componentArray))) 

811 for curComponent in self.componentArray: 

812 curComponent.glyphCode = ttFont.getGlyphID(curComponent.name) 

813 dataList.append(sstruct.pack(ebdtComponentFormat, curComponent)) 

814 return bytesjoin(dataList) 

815 

816 

817# Dictionary of bitmap formats to the class representing that format 

818# currently only the ones listed in this map are the ones supported. 

819ebdt_bitmap_classes = { 

820 1: ebdt_bitmap_format_1, 

821 2: ebdt_bitmap_format_2, 

822 5: ebdt_bitmap_format_5, 

823 6: ebdt_bitmap_format_6, 

824 7: ebdt_bitmap_format_7, 

825 8: ebdt_bitmap_format_8, 

826 9: ebdt_bitmap_format_9, 

827}