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