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