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