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