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