Coverage for /usr/lib/python3/dist-packages/fontTools/ttLib/tables/E_B_D_T_.py: 18%
515 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
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
25log = logging.getLogger(__name__)
27ebdtTableVersionFormat = """
28 > # big endian
29 version: 16.16F
30"""
32ebdtComponentFormat = """
33 > # big endian
34 glyphCode: H
35 xOffset: b
36 yOffset: b
37"""
40class table_E_B_D_T_(DefaultTable.DefaultTable):
41 # Keep a reference to the name of the data locator table.
42 locatorName = "EBLC"
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]
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)
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 = {}
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
85 def compile(self, ttFont):
86 dataList = []
87 dataList.append(sstruct.pack(ebdtTableVersionFormat, self))
88 dataSize = len(dataList[0])
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 = {}
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
133 return bytesjoin(dataList)
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
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()
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"])
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__)
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
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()
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)
224# Helper functions for dealing with binary.
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)
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)
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
263 return memodict().__getitem__
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)
289# This section of code is for reading and writing image data to/from XML.
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()
300def _readRawImageData(bitmapObject, name, attrs, content, ttFont):
301 bitmapObject.imageData = readHex(content)
304def _writeRowImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
305 metrics = bitmapObject.exportMetrics
306 del bitmapObject.exportMetrics
307 bitDepth = bitmapObject.exportBitDepth
308 del bitmapObject.exportBitDepth
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()
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"])
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)
339def _writeBitwiseImageData(strikeIndex, glyphName, bitmapObject, writer, ttFont):
340 metrics = bitmapObject.exportMetrics
341 del bitmapObject.exportMetrics
342 bitDepth = bitmapObject.exportBitDepth
343 del bitmapObject.exportBitDepth
345 # A dict for mapping binary to more readable/artistic ASCII characters.
346 binaryConv = {"0": ".", "1": "@"}
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()
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"])
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"}
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))
388 bitmapObject.setRows(
389 dataRows, bitDepth=bitDepth, metrics=metrics, reverseBytes=True
390 )
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)
407 fullPath = os.path.join(folder, filename)
408 writer.simpletag("extfileimagedata", value=fullPath)
409 writer.newline()
411 with open(fullPath, "wb") as file:
412 file.write(bitmapObject.imageData)
415def _readExtFileImageData(bitmapObject, name, attrs, content, ttFont):
416 fullPath = attrs["value"]
417 with open(fullPath, "rb") as file:
418 bitmapObject.imageData = file.read()
421# End of XML writing code.
423# Important information about the naming scheme. Used for identifying formats
424# in XML.
425_bitmapGlyphSubclassPrefix = "ebdt_bitmap_format_"
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"
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 }
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
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)
460 def ensureDecompiled(self, recurse=False):
461 if hasattr(self, "data"):
462 self.decompile()
463 del self.data
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) :])
469 def toXML(self, strikeIndex, glyphName, writer, ttFont):
470 writer.begintag(self.__class__.__name__, [("name", glyphName)])
471 writer.newline()
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)
477 writer.endtag(self.__class__.__name__)
478 writer.newline()
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)
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
498 # The opposite of write metrics.
499 def readMetrics(self, name, attrs, content, ttFont):
500 pass
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)
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)
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]
529 class BitmapPlusMetricsMixin(object):
530 def writeMetrics(self, writer, ttFont):
531 self.metrics.toXML(writer, ttFont)
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 )
548 return BitmapPlusMetricsMixin
551# Since there are only two types of mixin's just create them here.
552BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics)
553BitmapPlusSmallMetricsMixin = _createBitmapPlusMetricsMixin(SmallGlyphMetrics)
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)
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"
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))
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
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))
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
643 # Save the image data with the bits going the correct way.
644 self.imageData = _reverseBytes(bytesjoin(map(bytechr, ordDataList)))
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)
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
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)
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
679 def compile(self, ttFont):
680 data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
681 return data + self.imageData
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
692 def compile(self, ttFont):
693 data = sstruct.pack(smallGlyphMetricsFormat, self.metrics)
694 return data + self.imageData
697class ebdt_bitmap_format_5(BitAlignedBitmapMixin, BitmapGlyph):
698 def decompile(self):
699 self.imageData = self.data
701 def compile(self, ttFont):
702 return self.imageData
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
713 def compile(self, ttFont):
714 data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
715 return data + self.imageData
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
726 def compile(self, ttFont):
727 data = sstruct.pack(bigGlyphMetricsFormat, self.metrics)
728 return data + self.imageData
731class ComponentBitmapGlyph(BitmapGlyph):
732 def toXML(self, strikeIndex, glyphName, writer, ttFont):
733 writer.begintag(self.__class__.__name__, [("name", glyphName)])
734 writer.newline()
736 self.writeMetrics(writer, ttFont)
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()
745 writer.endtag(self.__class__.__name__)
746 writer.newline()
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)
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:]
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)
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)
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)
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)
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}