1from __future__ import print_function, division, absolute_import 2from fontTools.misc.py23 import * 3from fontTools.misc import sstruct 4from fontTools.misc.textTools import safeEval, readHex, hexStr, deHexStr 5from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat 6from . import DefaultTable 7import itertools 8import os 9import struct 10 11ebdtTableVersionFormat = """ 12 > # big endian 13 version: 16.16F 14""" 15 16ebdtComponentFormat = """ 17 > # big endian 18 glyphCode: H 19 xOffset: b 20 yOffset: b 21""" 22 23class table_E_B_D_T_(DefaultTable.DefaultTable): 24 25 # Keep a reference to the name of the data locator table. 26 locatorName = 'EBLC' 27 28 # This method can be overridden in subclasses to support new formats 29 # without changing the other implementation. Also can be used as a 30 # convenience method for coverting a font file to an alternative format. 31 def getImageFormatClass(self, imageFormat): 32 return ebdt_bitmap_classes[imageFormat] 33 34 def decompile(self, data, ttFont): 35 # Get the version but don't advance the slice. 36 # Most of the lookup for this table is done relative 37 # to the begining so slice by the offsets provided 38 # in the EBLC table. 39 sstruct.unpack2(ebdtTableVersionFormat, data, self) 40 41 # Keep a dict of glyphs that have been seen so they aren't remade. 42 # This dict maps intervals of data to the BitmapGlyph. 43 glyphDict = {} 44 45 # Pull out the EBLC table and loop through glyphs. 46 # A strike is a concept that spans both tables. 47 # The actual bitmap data is stored in the EBDT. 48 locator = ttFont[self.__class__.locatorName] 49 self.strikeData = [] 50 for curStrike in locator.strikes: 51 bitmapGlyphDict = {} 52 self.strikeData.append(bitmapGlyphDict) 53 for indexSubTable in curStrike.indexSubTables: 54 dataIter = zip(indexSubTable.names, indexSubTable.locations) 55 for curName, curLoc in dataIter: 56 # Don't create duplicate data entries for the same glyphs. 57 # Instead just use the structures that already exist if they exist. 58 if curLoc in glyphDict: 59 curGlyph = glyphDict[curLoc] 60 else: 61 curGlyphData = data[slice(*curLoc)] 62 imageFormatClass = self.getImageFormatClass(indexSubTable.imageFormat) 63 curGlyph = imageFormatClass(curGlyphData, ttFont) 64 glyphDict[curLoc] = curGlyph 65 bitmapGlyphDict[curName] = curGlyph 66 67 def compile(self, ttFont): 68 69 dataList = [] 70 dataList.append(sstruct.pack(ebdtTableVersionFormat, self)) 71 dataSize = len(dataList[0]) 72 73 # Keep a dict of glyphs that have been seen so they aren't remade. 74 # This dict maps the id of the BitmapGlyph to the interval 75 # in the data. 76 glyphDict = {} 77 78 # Go through the bitmap glyph data. Just in case the data for a glyph 79 # changed the size metrics should be recalculated. There are a variety 80 # of formats and they get stored in the EBLC table. That is why 81 # recalculation is defered to the EblcIndexSubTable class and just 82 # pass what is known about bitmap glyphs from this particular table. 83 locator = ttFont[self.__class__.locatorName] 84 for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData): 85 for curIndexSubTable in curStrike.indexSubTables: 86 dataLocations = [] 87 for curName in curIndexSubTable.names: 88 # Handle the data placement based on seeing the glyph or not. 89 # Just save a reference to the location if the glyph has already 90 # been saved in compile. This code assumes that glyphs will only 91 # be referenced multiple times from indexFormat5. By luck the 92 # code may still work when referencing poorly ordered fonts with 93 # duplicate references. If there is a font that is unlucky the 94 # respective compile methods for the indexSubTables will fail 95 # their assertions. All fonts seem to follow this assumption. 96 # More complicated packing may be needed if a counter-font exists. 97 glyph = curGlyphDict[curName] 98 objectId = id(glyph) 99 if objectId not in glyphDict: 100 data = glyph.compile(ttFont) 101 data = curIndexSubTable.padBitmapData(data) 102 startByte = dataSize 103 dataSize += len(data) 104 endByte = dataSize 105 dataList.append(data) 106 dataLoc = (startByte, endByte) 107 glyphDict[objectId] = dataLoc 108 else: 109 dataLoc = glyphDict[objectId] 110 dataLocations.append(dataLoc) 111 # Just use the new data locations in the indexSubTable. 112 # The respective compile implementations will take care 113 # of any of the problems in the convertion that may arise. 114 curIndexSubTable.locations = dataLocations 115 116 return bytesjoin(dataList) 117 118 def toXML(self, writer, ttFont): 119 # When exporting to XML if one of the data export formats 120 # requires metrics then those metrics may be in the locator. 121 # In this case populate the bitmaps with "export metrics". 122 if ttFont.bitmapGlyphDataFormat in ('row', 'bitwise'): 123 locator = ttFont[self.__class__.locatorName] 124 for curStrike, curGlyphDict in zip(locator.strikes, self.strikeData): 125 for curIndexSubTable in curStrike.indexSubTables: 126 for curName in curIndexSubTable.names: 127 glyph = curGlyphDict[curName] 128 # I'm not sure which metrics have priority here. 129 # For now if both metrics exist go with glyph metrics. 130 if hasattr(glyph, 'metrics'): 131 glyph.exportMetrics = glyph.metrics 132 else: 133 glyph.exportMetrics = curIndexSubTable.metrics 134 glyph.exportBitDepth = curStrike.bitmapSizeTable.bitDepth 135 136 writer.simpletag("header", [('version', self.version)]) 137 writer.newline() 138 locator = ttFont[self.__class__.locatorName] 139 for strikeIndex, bitmapGlyphDict in enumerate(self.strikeData): 140 writer.begintag('strikedata', [('index', strikeIndex)]) 141 writer.newline() 142 for curName, curBitmap in bitmapGlyphDict.items(): 143 curBitmap.toXML(strikeIndex, curName, writer, ttFont) 144 writer.endtag('strikedata') 145 writer.newline() 146 147 def fromXML(self, name, attrs, content, ttFont): 148 if name == 'header': 149 self.version = safeEval(attrs['version']) 150 elif name == 'strikedata': 151 if not hasattr(self, 'strikeData'): 152 self.strikeData = [] 153 strikeIndex = safeEval(attrs['index']) 154 155 bitmapGlyphDict = {} 156 for element in content: 157 if not isinstance(element, tuple): 158 continue 159 name, attrs, content = element 160 if name[4:].startswith(_bitmapGlyphSubclassPrefix[4:]): 161 imageFormat = safeEval(name[len(_bitmapGlyphSubclassPrefix):]) 162 glyphName = attrs['name'] 163 imageFormatClass = self.getImageFormatClass(imageFormat) 164 curGlyph = imageFormatClass(None, None) 165 curGlyph.fromXML(name, attrs, content, ttFont) 166 assert glyphName not in bitmapGlyphDict, "Duplicate glyphs with the same name '%s' in the same strike." % glyphName 167 bitmapGlyphDict[glyphName] = curGlyph 168 else: 169 print("Warning: %s being ignored by %s", name, self.__class__.__name__) 170 171 # Grow the strike data array to the appropriate size. The XML 172 # format allows the strike index value to be out of order. 173 if strikeIndex >= len(self.strikeData): 174 self.strikeData += [None] * (strikeIndex + 1 - len(self.strikeData)) 175 assert self.strikeData[strikeIndex] is None, "Duplicate strike EBDT indices." 176 self.strikeData[strikeIndex] = bitmapGlyphDict 177 178class EbdtComponent(object): 179 180 def toXML(self, writer, ttFont): 181 writer.begintag('ebdtComponent', [('name', self.name)]) 182 writer.newline() 183 for componentName in sstruct.getformat(ebdtComponentFormat)[1][1:]: 184 writer.simpletag(componentName, value=getattr(self, componentName)) 185 writer.newline() 186 writer.endtag('ebdtComponent') 187 writer.newline() 188 189 def fromXML(self, name, attrs, content, ttFont): 190 self.name = attrs['name'] 191 componentNames = set(sstruct.getformat(ebdtComponentFormat)[1][1:]) 192 for element in content: 193 if not isinstance(element, tuple): 194 continue 195 name, attrs, content = element 196 if name in componentNames: 197 vars(self)[name] = safeEval(attrs['value']) 198 else: 199 print("Warning: unknown name '%s' being ignored by EbdtComponent." % name) 200 201# Helper functions for dealing with binary. 202 203def _data2binary(data, numBits): 204 binaryList = [] 205 for curByte in data: 206 value = byteord(curByte) 207 numBitsCut = min(8, numBits) 208 for i in range(numBitsCut): 209 if value & 0x1: 210 binaryList.append('1') 211 else: 212 binaryList.append('0') 213 value = value >> 1 214 numBits -= numBitsCut 215 return strjoin(binaryList) 216 217def _binary2data(binary): 218 byteList = [] 219 for bitLoc in range(0, len(binary), 8): 220 byteString = binary[bitLoc:bitLoc+8] 221 curByte = 0 222 for curBit in reversed(byteString): 223 curByte = curByte << 1 224 if curBit == '1': 225 curByte |= 1 226 byteList.append(bytechr(curByte)) 227 return bytesjoin(byteList) 228 229def _memoize(f): 230 class memodict(dict): 231 def __missing__(self, key): 232 ret = f(key) 233 if len(key) == 1: 234 self[key] = ret 235 return ret 236 return memodict().__getitem__ 237 238# 00100111 -> 11100100 per byte, not to be confused with little/big endian. 239# Bitmap data per byte is in the order that binary is written on the page 240# with the least significant bit as far right as possible. This is the 241# opposite of what makes sense algorithmically and hence this function. 242@_memoize 243def _reverseBytes(data): 244 if len(data) != 1: 245 return bytesjoin(map(_reverseBytes, data)) 246 byte = byteord(data) 247 result = 0 248 for i in range(8): 249 result = result << 1 250 result |= byte & 1 251 byte = byte >> 1 252 return bytechr(result) 253 254# This section of code is for reading and writing image data to/from XML. 255 256def _writeRawImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 257 writer.begintag('rawimagedata') 258 writer.newline() 259 writer.dumphex(bitmapObject.imageData) 260 writer.endtag('rawimagedata') 261 writer.newline() 262 263def _readRawImageData(bitmapObject, name, attrs, content, ttFont): 264 bitmapObject.imageData = readHex(content) 265 266def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 267 metrics = bitmapObject.exportMetrics 268 del bitmapObject.exportMetrics 269 bitDepth = bitmapObject.exportBitDepth 270 del bitmapObject.exportBitDepth 271 272 writer.begintag('rowimagedata', bitDepth=bitDepth, width=metrics.width, height=metrics.height) 273 writer.newline() 274 for curRow in range(metrics.height): 275 rowData = bitmapObject.getRow(curRow, bitDepth=bitDepth, metrics=metrics) 276 writer.simpletag('row', value=hexStr(rowData)) 277 writer.newline() 278 writer.endtag('rowimagedata') 279 writer.newline() 280 281def _readRowImageData(bitmapObject, name, attrs, content, ttFont): 282 bitDepth = safeEval(attrs['bitDepth']) 283 metrics = SmallGlyphMetrics() 284 metrics.width = safeEval(attrs['width']) 285 metrics.height = safeEval(attrs['height']) 286 287 dataRows = [] 288 for element in content: 289 if not isinstance(element, tuple): 290 continue 291 name, attr, content = element 292 # Chop off 'imagedata' from the tag to get just the option. 293 if name == 'row': 294 dataRows.append(deHexStr(attr['value'])) 295 bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics) 296 297def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 298 metrics = bitmapObject.exportMetrics 299 del bitmapObject.exportMetrics 300 bitDepth = bitmapObject.exportBitDepth 301 del bitmapObject.exportBitDepth 302 303 # A dict for mapping binary to more readable/artistic ASCII characters. 304 binaryConv = {'0':'.', '1':'@'} 305 306 writer.begintag('bitwiseimagedata', bitDepth=bitDepth, width=metrics.width, height=metrics.height) 307 writer.newline() 308 for curRow in range(metrics.height): 309 rowData = bitmapObject.getRow(curRow, bitDepth=1, metrics=metrics, reverseBytes=True) 310 rowData = _data2binary(rowData, metrics.width) 311 # Make the output a readable ASCII art form. 312 rowData = strjoin(map(binaryConv.get, rowData)) 313 writer.simpletag('row', value=rowData) 314 writer.newline() 315 writer.endtag('bitwiseimagedata') 316 writer.newline() 317 318def _readBitwiseImageData(bitmapObject, name, attrs, content, ttFont): 319 bitDepth = safeEval(attrs['bitDepth']) 320 metrics = SmallGlyphMetrics() 321 metrics.width = safeEval(attrs['width']) 322 metrics.height = safeEval(attrs['height']) 323 324 # A dict for mapping from ASCII to binary. All characters are considered 325 # a '1' except space, period and '0' which maps to '0'. 326 binaryConv = {' ':'0', '.':'0', '0':'0'} 327 328 dataRows = [] 329 for element in content: 330 if not isinstance(element, tuple): 331 continue 332 name, attr, content = element 333 if name == 'row': 334 mapParams = zip(attr['value'], itertools.repeat('1')) 335 rowData = strjoin(itertools.starmap(binaryConv.get, mapParams)) 336 dataRows.append(_binary2data(rowData)) 337 338 bitmapObject.setRows(dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True) 339 340def _writeExtFileImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont): 341 folder = 'bitmaps/' 342 filename = glyphName + bitmapObject.fileExtension 343 if not os.path.isdir(folder): 344 os.makedirs(folder) 345 folder += 'strike%d/' % strikeIndex 346 if not os.path.isdir(folder): 347 os.makedirs(folder) 348 349 fullPath = folder + filename 350 writer.simpletag('extfileimagedata', value=fullPath) 351 writer.newline() 352 353 with open(fullPath, "wb") as file: 354 file.write(bitmapObject.imageData) 355 356def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont): 357 fullPath = attrs['value'] 358 with open(fullPath, "rb") as file: 359 bitmapObject.imageData = file.read() 360 361# End of XML writing code. 362 363# Important information about the naming scheme. Used for identifying formats 364# in XML. 365_bitmapGlyphSubclassPrefix = 'ebdt_bitmap_format_' 366 367class BitmapGlyph(object): 368 369 # For the external file format. This can be changed in subclasses. This way 370 # when the extfile option is turned on files have the form: glyphName.ext 371 # The default is just a flat binary file with no meaning. 372 fileExtension = '.bin' 373 374 # Keep track of reading and writing of various forms. 375 xmlDataFunctions = { 376 'raw': (_writeRawImageData, _readRawImageData), 377 'row': (_writeRowImageData, _readRowImageData), 378 'bitwise': (_writeBitwiseImageData, _readBitwiseImageData), 379 'extfile': (_writeExtFileImageData, _readExtFileImageData), 380 } 381 382 def __init__(self, data, ttFont): 383 self.data = data 384 self.ttFont = ttFont 385 # TODO Currently non-lazy decompilation is untested here... 386 #if not ttFont.lazy: 387 # self.decompile() 388 # del self.data 389 390 def __getattr__(self, attr): 391 # Allow lazy decompile. 392 if attr[:2] == '__': 393 raise AttributeError(attr) 394 if not hasattr(self, "data"): 395 raise AttributeError(attr) 396 self.decompile() 397 del self.data 398 return getattr(self, attr) 399 400 # Not a fan of this but it is needed for safer safety checking. 401 def getFormat(self): 402 return safeEval(self.__class__.__name__[len(_bitmapGlyphSubclassPrefix):]) 403 404 def toXML(self, strikeIndex, glyphName, writer, ttFont): 405 writer.begintag(self.__class__.__name__, [('name', glyphName)]) 406 writer.newline() 407 408 self.writeMetrics(writer, ttFont) 409 # Use the internal write method to write using the correct output format. 410 self.writeData(strikeIndex, glyphName, writer, ttFont) 411 412 writer.endtag(self.__class__.__name__) 413 writer.newline() 414 415 def fromXML(self, name, attrs, content, ttFont): 416 self.readMetrics(name, attrs, content, ttFont) 417 for element in content: 418 if not isinstance(element, tuple): 419 continue 420 name, attr, content = element 421 if not name.endswith('imagedata'): 422 continue 423 # Chop off 'imagedata' from the tag to get just the option. 424 option = name[:-len('imagedata')] 425 assert option in self.__class__.xmlDataFunctions 426 self.readData(name, attrs, content, ttFont) 427 428 # Some of the glyphs have the metrics. This allows for metrics to be 429 # added if the glyph format has them. Default behavior is to do nothing. 430 def writeMetrics(self, writer, ttFont): 431 pass 432 433 # The opposite of write metrics. 434 def readMetrics(self, name, attrs, content, ttFont): 435 pass 436 437 def writeData(self, strikeIndex, glyphName, writer, ttFont): 438 try: 439 writeFunc, readFunc = self.__class__.xmlDataFunctions[ttFont.bitmapGlyphDataFormat] 440 except KeyError: 441 writeFunc = _writeRawImageData 442 writeFunc(strikeIndex, glyphName, self, writer, ttFont) 443 444 def readData(self, name, attrs, content, ttFont): 445 # Chop off 'imagedata' from the tag to get just the option. 446 option = name[:-len('imagedata')] 447 writeFunc, readFunc = self.__class__.xmlDataFunctions[option] 448 readFunc(self, name, attrs, content, ttFont) 449 450 451# A closure for creating a mixin for the two types of metrics handling. 452# Most of the code is very similar so its easier to deal with here. 453# Everything works just by passing the class that the mixin is for. 454def _createBitmapPlusMetricsMixin(metricsClass): 455 # Both metrics names are listed here to make meaningful error messages. 456 metricStrings = [BigGlyphMetrics.__name__, SmallGlyphMetrics.__name__] 457 curMetricsName = metricsClass.__name__ 458 # Find which metrics this is for and determine the opposite name. 459 metricsId = metricStrings.index(curMetricsName) 460 oppositeMetricsName = metricStrings[1-metricsId] 461 462 class BitmapPlusMetricsMixin(object): 463 464 def writeMetrics(self, writer, ttFont): 465 self.metrics.toXML(writer, ttFont) 466 467 def readMetrics(self, name, attrs, content, ttFont): 468 for element in content: 469 if not isinstance(element, tuple): 470 continue 471 name, attrs, content = element 472 if name == curMetricsName: 473 self.metrics = metricsClass() 474 self.metrics.fromXML(name, attrs, content, ttFont) 475 elif name == oppositeMetricsName: 476 print("Warning: %s being ignored in format %d." % oppositeMetricsName, self.getFormat()) 477 478 return BitmapPlusMetricsMixin 479 480# Since there are only two types of mixin's just create them here. 481BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics) 482BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics) 483 484# Data that is bit aligned can be tricky to deal with. These classes implement 485# helper functionality for dealing with the data and getting a particular row 486# of bitwise data. Also helps implement fancy data export/import in XML. 487class BitAlignedBitmapMixin(object): 488 489 def _getBitRange(self, row, bitDepth, metrics): 490 rowBits = (bitDepth * metrics.width) 491 bitOffset = row * rowBits 492 return (bitOffset, bitOffset+rowBits) 493 494 def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): 495 if metrics is None: 496 metrics = self.metrics 497 assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" 498 499 # Loop through each byte. This can cover two bytes in the original data or 500 # a single byte if things happen to be aligned. The very last entry might 501 # not be aligned so take care to trim the binary data to size and pad with 502 # zeros in the row data. Bit aligned data is somewhat tricky. 503 # 504 # Example of data cut. Data cut represented in x's. 505 # '|' represents byte boundary. 506 # data = ...0XX|XXXXXX00|000... => XXXXXXXX 507 # or 508 # data = ...0XX|XXXX0000|000... => XXXXXX00 509 # or 510 # data = ...000|XXXXXXXX|000... => XXXXXXXX 511 # or 512 # data = ...000|00XXXX00|000... => XXXX0000 513 # 514 dataList = [] 515 bitRange = self._getBitRange(row, bitDepth, metrics) 516 stepRange = bitRange + (8,) 517 for curBit in range(*stepRange): 518 endBit = min(curBit+8, bitRange[1]) 519 numBits = endBit - curBit 520 cutPoint = curBit % 8 521 firstByteLoc = curBit // 8 522 secondByteLoc = endBit // 8 523 if firstByteLoc < secondByteLoc: 524 numBitsCut = 8 - cutPoint 525 else: 526 numBitsCut = endBit - curBit 527 curByte = _reverseBytes(self.imageData[firstByteLoc]) 528 firstHalf = byteord(curByte) >> cutPoint 529 firstHalf = ((1<<numBitsCut)-1) & firstHalf 530 newByte = firstHalf 531 if firstByteLoc < secondByteLoc and secondByteLoc < len(self.imageData): 532 curByte = _reverseBytes(self.imageData[secondByteLoc]) 533 secondHalf = byteord(curByte) << numBitsCut 534 newByte = (firstHalf | secondHalf) & ((1<<numBits)-1) 535 dataList.append(bytechr(newByte)) 536 537 # The way the data is kept is opposite the algorithm used. 538 data = bytesjoin(dataList) 539 if not reverseBytes: 540 data = _reverseBytes(data) 541 return data 542 543 def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False): 544 if metrics is None: 545 metrics = self.metrics 546 if not reverseBytes: 547 dataRows = list(map(_reverseBytes, dataRows)) 548 549 # Keep track of a list of ordinal values as they are easier to modify 550 # than a list of strings. Map to actual strings later. 551 numBytes = (self._getBitRange(len(dataRows), bitDepth, metrics)[0] + 7) // 8 552 ordDataList = [0] * numBytes 553 for row, data in enumerate(dataRows): 554 bitRange = self._getBitRange(row, bitDepth, metrics) 555 stepRange = bitRange + (8,) 556 for curBit, curByte in zip(range(*stepRange), data): 557 endBit = min(curBit+8, bitRange[1]) 558 cutPoint = curBit % 8 559 firstByteLoc = curBit // 8 560 secondByteLoc = endBit // 8 561 if firstByteLoc < secondByteLoc: 562 numBitsCut = 8 - cutPoint 563 else: 564 numBitsCut = endBit - curBit 565 curByte = byteord(curByte) 566 firstByte = curByte & ((1<<numBitsCut)-1) 567 ordDataList[firstByteLoc] |= (firstByte << cutPoint) 568 if firstByteLoc < secondByteLoc and secondByteLoc < numBytes: 569 secondByte = (curByte >> numBitsCut) & ((1<<8-numBitsCut)-1) 570 ordDataList[secondByteLoc] |= secondByte 571 572 # Save the image data with the bits going the correct way. 573 self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList))) 574 575class ByteAlignedBitmapMixin(object): 576 577 def _getByteRange(self, row, bitDepth, metrics): 578 rowBytes = (bitDepth * metrics.width + 7) // 8 579 byteOffset = row * rowBytes 580 return (byteOffset, byteOffset+rowBytes) 581 582 def getRow(self, row, bitDepth=1, metrics=None, reverseBytes=False): 583 if metrics is None: 584 metrics = self.metrics 585 assert 0 <= row and row < metrics.height, "Illegal row access in bitmap" 586 byteRange = self._getByteRange(row, bitDepth, metrics) 587 data = self.imageData[slice(*byteRange)] 588 if reverseBytes: 589 data = _reverseBytes(data) 590 return data 591 592 def setRows(self, dataRows, bitDepth=1, metrics=None, reverseBytes=False): 593 if metrics is None: 594 metrics = self.metrics 595 if reverseBytes: 596 dataRows = map(_reverseBytes, dataRows) 597 self.imageData = bytesjoin(dataRows) 598 599class ebdt_bitmap_format_1(ByteAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph): 600 601 def decompile(self): 602 self.metrics = SmallGlyphMetrics() 603 dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) 604 self.imageData = data 605 606 def compile(self, ttFont): 607 data = sstruct.pack(smallGlyphMetricsFormat, self.metrics) 608 return data + self.imageData 609 610 611class ebdt_bitmap_format_2(BitAlignedBitmapMixin, BitmapPlusSmallMetricsMixin, BitmapGlyph): 612 613 def decompile(self): 614 self.metrics = SmallGlyphMetrics() 615 dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) 616 self.imageData = data 617 618 def compile(self, ttFont): 619 data = sstruct.pack(smallGlyphMetricsFormat, self.metrics) 620 return data + self.imageData 621 622 623class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph): 624 625 def decompile(self): 626 self.imageData = self.data 627 628 def compile(self, ttFont): 629 return self.imageData 630 631class ebdt_bitmap_format_6(ByteAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph): 632 633 def decompile(self): 634 self.metrics = BigGlyphMetrics() 635 dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) 636 self.imageData = data 637 638 def compile(self, ttFont): 639 data = sstruct.pack(bigGlyphMetricsFormat, self.metrics) 640 return data + self.imageData 641 642 643class ebdt_bitmap_format_7(BitAlignedBitmapMixin, BitmapPlusBigMetricsMixin, BitmapGlyph): 644 645 def decompile(self): 646 self.metrics = BigGlyphMetrics() 647 dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) 648 self.imageData = data 649 650 def compile(self, ttFont): 651 data = sstruct.pack(bigGlyphMetricsFormat, self.metrics) 652 return data + self.imageData 653 654 655class ComponentBitmapGlyph(BitmapGlyph): 656 657 def toXML(self, strikeIndex, glyphName, writer, ttFont): 658 writer.begintag(self.__class__.__name__, [('name', glyphName)]) 659 writer.newline() 660 661 self.writeMetrics(writer, ttFont) 662 663 writer.begintag('components') 664 writer.newline() 665 for curComponent in self.componentArray: 666 curComponent.toXML(writer, ttFont) 667 writer.endtag('components') 668 writer.newline() 669 670 writer.endtag(self.__class__.__name__) 671 writer.newline() 672 673 def fromXML(self, name, attrs, content, ttFont): 674 self.readMetrics(name, attrs, content, ttFont) 675 for element in content: 676 if not isinstance(element, tuple): 677 continue 678 name, attr, content = element 679 if name == 'components': 680 self.componentArray = [] 681 for compElement in content: 682 if not isinstance(compElement, tuple): 683 continue 684 name, attrs, content = compElement 685 if name == 'ebdtComponent': 686 curComponent = EbdtComponent() 687 curComponent.fromXML(name, attrs, content, ttFont) 688 self.componentArray.append(curComponent) 689 else: 690 print("Warning: '%s' being ignored in component array." % name) 691 692 693class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph): 694 695 def decompile(self): 696 self.metrics = SmallGlyphMetrics() 697 dummy, data = sstruct.unpack2(smallGlyphMetricsFormat, self.data, self.metrics) 698 data = data[1:] 699 700 (numComponents,) = struct.unpack(">H", data[:2]) 701 data = data[2:] 702 self.componentArray = [] 703 for i in range(numComponents): 704 curComponent = EbdtComponent() 705 dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent) 706 curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode) 707 self.componentArray.append(curComponent) 708 709 def compile(self, ttFont): 710 dataList = [] 711 dataList.append(sstruct.pack(smallGlyphMetricsFormat, self.metrics)) 712 dataList.append(b'\0') 713 dataList.append(struct.pack(">H", len(self.componentArray))) 714 for curComponent in self.componentArray: 715 curComponent.glyphCode = ttFont.getGlyphID(curComponent.name) 716 dataList.append(sstruct.pack(ebdtComponentFormat, curComponent)) 717 return bytesjoin(dataList) 718 719 720class ebdt_bitmap_format_9(BitmapPlusBigMetricsMixin, ComponentBitmapGlyph): 721 722 def decompile(self): 723 self.metrics = BigGlyphMetrics() 724 dummy, data = sstruct.unpack2(bigGlyphMetricsFormat, self.data, self.metrics) 725 (numComponents,) = struct.unpack(">H", data[:2]) 726 data = data[2:] 727 self.componentArray = [] 728 for i in range(numComponents): 729 curComponent = EbdtComponent() 730 dummy, data = sstruct.unpack2(ebdtComponentFormat, data, curComponent) 731 curComponent.name = self.ttFont.getGlyphName(curComponent.glyphCode) 732 self.componentArray.append(curComponent) 733 734 def compile(self, ttFont): 735 dataList = [] 736 dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics)) 737 dataList.append(struct.pack(">H", len(self.componentArray))) 738 for curComponent in self.componentArray: 739 curComponent.glyphCode = ttFont.getGlyphID(curComponent.name) 740 dataList.append(sstruct.pack(ebdtComponentFormat, curComponent)) 741 return bytesjoin(dataList) 742 743 744# Dictionary of bitmap formats to the class representing that format 745# currently only the ones listed in this map are the ones supported. 746ebdt_bitmap_classes = { 747 1: ebdt_bitmap_format_1, 748 2: ebdt_bitmap_format_2, 749 5: ebdt_bitmap_format_5, 750 6: ebdt_bitmap_format_6, 751 7: ebdt_bitmap_format_7, 752 8: ebdt_bitmap_format_8, 753 9: ebdt_bitmap_format_9, 754 } 755