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