• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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