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