• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import print_function, division, absolute_import
2from fontTools.misc.py23 import *
3from fontTools.misc import sstruct
4from fontTools.misc.textTools import safeEval, readHex, hexStr, deHexStr
5from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
6from . import DefaultTable
7import itertools
8import os
9import struct
10
11ebdtTableVersionFormat = """
12	> # big endian
13	version: 16.16F
14"""
15
16ebdtComponentFormat = """
17	> # big endian
18	glyphCode: H
19	xOffset:   b
20	yOffset:   b
21"""
22
23class 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
178class 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
203def _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
217def _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
229def _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
243def _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
256def _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
263def _readRawImageData(bitmapObject, name, attrs, content, ttFont):
264	bitmapObject.imageData = readHex(content)
265
266def _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
281def _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
297def _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
318def _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
340def _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
356def _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
367class 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.
454def _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.
481BitmapPlusBigMetricsMixin = _createBitmapPlusMetricsMixin(BigGlyphMetrics)
482BitmapPlusSmallMetricsMixin = _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.
487class 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
575class 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
599class 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
611class 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
623class 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
631class 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
643class 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
655class 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
693class 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
720class 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.
746ebdt_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