Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc import sstruct
      4 from . import DefaultTable
      5 from fontTools.misc.textTools import safeEval
      6 from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
      7 import struct
      8 import itertools
      9 from collections import deque
     10 
     11 eblcHeaderFormat = """
     12 	> # big endian
     13 	version:  16.16F
     14 	numSizes: I
     15 """
     16 # The table format string is split to handle sbitLineMetrics simply.
     17 bitmapSizeTableFormatPart1 = """
     18 	> # big endian
     19 	indexSubTableArrayOffset: I
     20 	indexTablesSize:          I
     21 	numberOfIndexSubTables:   I
     22 	colorRef:                 I
     23 """
     24 # The compound type for hori and vert.
     25 sbitLineMetricsFormat = """
     26 	> # big endian
     27 	ascender:              b
     28 	descender:             b
     29 	widthMax:              B
     30 	caretSlopeNumerator:   b
     31 	caretSlopeDenominator: b
     32 	caretOffset:           b
     33 	minOriginSB:           b
     34 	minAdvanceSB:          b
     35 	maxBeforeBL:           b
     36 	minAfterBL:            b
     37 	pad1:                  b
     38 	pad2:                  b
     39 """
     40 # hori and vert go between the two parts.
     41 bitmapSizeTableFormatPart2 = """
     42 	> # big endian
     43 	startGlyphIndex: H
     44 	endGlyphIndex:   H
     45 	ppemX:           B
     46 	ppemY:           B
     47 	bitDepth:        B
     48 	flags:           b
     49 """
     50 
     51 indexSubTableArrayFormat = ">HHL"
     52 indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat)
     53 
     54 indexSubHeaderFormat = ">HHL"
     55 indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat)
     56 
     57 codeOffsetPairFormat = ">HH"
     58 codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat)
     59 
     60 class table_E_B_L_C_(DefaultTable.DefaultTable):
     61 
     62 	dependencies = ['EBDT']
     63 
     64 	# This method can be overridden in subclasses to support new formats
     65 	# without changing the other implementation. Also can be used as a
     66 	# convenience method for coverting a font file to an alternative format.
     67 	def getIndexFormatClass(self, indexFormat):
     68 		return eblc_sub_table_classes[indexFormat]
     69 
     70 	def decompile(self, data, ttFont):
     71 
     72 		# Save the original data because offsets are from the start of the table.
     73 		origData = data
     74 
     75 		dummy, data = sstruct.unpack2(eblcHeaderFormat, data, self)
     76 
     77 		self.strikes = []
     78 		for curStrikeIndex in range(self.numSizes):
     79 			curStrike = Strike()
     80 			self.strikes.append(curStrike)
     81 			curTable = curStrike.bitmapSizeTable
     82 			dummy, data = sstruct.unpack2(bitmapSizeTableFormatPart1, data, curTable)
     83 			for metric in ('hori', 'vert'):
     84 				metricObj = SbitLineMetrics()
     85 				vars(curTable)[metric] = metricObj
     86 				dummy, data = sstruct.unpack2(sbitLineMetricsFormat, data, metricObj)
     87 			dummy, data = sstruct.unpack2(bitmapSizeTableFormatPart2, data, curTable)
     88 
     89 		for curStrike in self.strikes:
     90 			curTable = curStrike.bitmapSizeTable
     91 			for subtableIndex in range(curTable.numberOfIndexSubTables):
     92 				lowerBound = curTable.indexSubTableArrayOffset + subtableIndex * indexSubTableArraySize
     93 				upperBound = lowerBound + indexSubTableArraySize
     94 				data = origData[lowerBound:upperBound]
     95 
     96 				tup = struct.unpack(indexSubTableArrayFormat, data)
     97 				(firstGlyphIndex, lastGlyphIndex, additionalOffsetToIndexSubtable) = tup
     98 				offsetToIndexSubTable = curTable.indexSubTableArrayOffset + additionalOffsetToIndexSubtable
     99 				data = origData[offsetToIndexSubTable:]
    100 
    101 				tup = struct.unpack(indexSubHeaderFormat, data[:indexSubHeaderSize])
    102 				(indexFormat, imageFormat, imageDataOffset) = tup
    103 
    104 				indexFormatClass = self.getIndexFormatClass(indexFormat)
    105 				indexSubTable = indexFormatClass(data[indexSubHeaderSize:], ttFont)
    106 				indexSubTable.firstGlyphIndex = firstGlyphIndex
    107 				indexSubTable.lastGlyphIndex = lastGlyphIndex
    108 				indexSubTable.additionalOffsetToIndexSubtable = additionalOffsetToIndexSubtable
    109 				indexSubTable.indexFormat = indexFormat
    110 				indexSubTable.imageFormat = imageFormat
    111 				indexSubTable.imageDataOffset = imageDataOffset
    112 				curStrike.indexSubTables.append(indexSubTable)
    113 
    114 	def compile(self, ttFont):
    115 
    116 		dataList = []
    117 		self.numSizes = len(self.strikes)
    118 		dataList.append(sstruct.pack(eblcHeaderFormat, self))
    119 
    120 		# Data size of the header + bitmapSizeTable needs to be calculated
    121 		# in order to form offsets. This value will hold the size of the data
    122 		# in dataList after all the data is consolidated in dataList.
    123 		dataSize = len(dataList[0])
    124 
    125 		# The table will be structured in the following order:
    126 		# (0) header
    127 		# (1) Each bitmapSizeTable [1 ... self.numSizes]
    128 		# (2) Alternate between indexSubTableArray and indexSubTable
    129 		#     for each bitmapSizeTable present.
    130 		#
    131 		# The issue is maintaining the proper offsets when table information
    132 		# gets moved around. All offsets and size information must be recalculated
    133 		# when building the table to allow editing within ttLib and also allow easy
    134 		# import/export to and from XML. All of this offset information is lost
    135 		# when exporting to XML so everything must be calculated fresh so importing
    136 		# from XML will work cleanly. Only byte offset and size information is
    137 		# calculated fresh. Count information like numberOfIndexSubTables is
    138 		# checked through assertions. If the information in this table was not
    139 		# touched or was changed properly then these types of values should match.
    140 		#
    141 		# The table will be rebuilt the following way:
    142 		# (0) Precompute the size of all the bitmapSizeTables. This is needed to
    143 		#     compute the offsets properly.
    144 		# (1) For each bitmapSizeTable compute the indexSubTable and
    145 		#    	indexSubTableArray pair. The indexSubTable must be computed first
    146 		#     so that the offset information in indexSubTableArray can be
    147 		#     calculated. Update the data size after each pairing.
    148 		# (2) Build each bitmapSizeTable.
    149 		# (3) Consolidate all the data into the main dataList in the correct order.
    150 
    151 		for curStrike in self.strikes:
    152 			dataSize += sstruct.calcsize(bitmapSizeTableFormatPart1)
    153 			dataSize += len(('hori', 'vert')) * sstruct.calcsize(sbitLineMetricsFormat)
    154 			dataSize += sstruct.calcsize(bitmapSizeTableFormatPart2)
    155 
    156 		indexSubTablePairDataList = []
    157 		for curStrike in self.strikes:
    158 			curTable = curStrike.bitmapSizeTable
    159 			curTable.numberOfIndexSubTables = len(curStrike.indexSubTables)
    160 			curTable.indexSubTableArrayOffset = dataSize
    161 
    162 			# Precompute the size of the indexSubTableArray. This information
    163 			# is important for correctly calculating the new value for
    164 			# additionalOffsetToIndexSubtable.
    165 			sizeOfSubTableArray = curTable.numberOfIndexSubTables * indexSubTableArraySize
    166 			lowerBound = dataSize
    167 			dataSize += sizeOfSubTableArray
    168 			upperBound = dataSize
    169 
    170 			indexSubTableDataList = []
    171 			for indexSubTable in curStrike.indexSubTables:
    172 				indexSubTable.additionalOffsetToIndexSubtable = dataSize - curTable.indexSubTableArrayOffset
    173 				glyphIds = list(map(ttFont.getGlyphID, indexSubTable.names))
    174 				indexSubTable.firstGlyphIndex = min(glyphIds)
    175 				indexSubTable.lastGlyphIndex = max(glyphIds)
    176 				data = indexSubTable.compile(ttFont)
    177 				indexSubTableDataList.append(data)
    178 				dataSize += len(data)
    179 			curTable.startGlyphIndex = min(ist.firstGlyphIndex for ist in curStrike.indexSubTables)
    180 			curTable.endGlyphIndex = max(ist.lastGlyphIndex for ist in curStrike.indexSubTables)
    181 
    182 			for i in curStrike.indexSubTables:
    183 				data = struct.pack(indexSubHeaderFormat, i.firstGlyphIndex, i.lastGlyphIndex, i.additionalOffsetToIndexSubtable)
    184 				indexSubTablePairDataList.append(data)
    185 			indexSubTablePairDataList.extend(indexSubTableDataList)
    186 			curTable.indexTablesSize = dataSize - curTable.indexSubTableArrayOffset
    187 
    188 		for curStrike in self.strikes:
    189 			curTable = curStrike.bitmapSizeTable
    190 			data = sstruct.pack(bitmapSizeTableFormatPart1, curTable)
    191 			dataList.append(data)
    192 			for metric in ('hori', 'vert'):
    193 				metricObj = vars(curTable)[metric]
    194 				data = sstruct.pack(sbitLineMetricsFormat, metricObj)
    195 				dataList.append(data)
    196 			data = sstruct.pack(bitmapSizeTableFormatPart2, curTable)
    197 			dataList.append(data)
    198 		dataList.extend(indexSubTablePairDataList)
    199 
    200 		return bytesjoin(dataList)
    201 
    202 	def toXML(self, writer, ttFont):
    203 		writer.simpletag('header', [('version', self.version)])
    204 		writer.newline()
    205 		for curIndex, curStrike in enumerate(self.strikes):
    206 			curStrike.toXML(curIndex, writer, ttFont)
    207 
    208 	def fromXML(self, name, attrs, content, ttFont):
    209 		if name == 'header':
    210 			self.version = safeEval(attrs['version'])
    211 		elif name == 'strike':
    212 			if not hasattr(self, 'strikes'):
    213 				self.strikes = []
    214 			strikeIndex = safeEval(attrs['index'])
    215 			curStrike = Strike()
    216 			curStrike.fromXML(name, attrs, content, ttFont, self)
    217 
    218 			# Grow the strike array to the appropriate size. The XML format
    219 			# allows for the strike index value to be out of order.
    220 			if strikeIndex >= len(self.strikes):
    221 				self.strikes += [None] * (strikeIndex + 1 - len(self.strikes))
    222 			assert self.strikes[strikeIndex] is None, "Duplicate strike EBLC indices."
    223 			self.strikes[strikeIndex] = curStrike
    224 
    225 class Strike(object):
    226 
    227 	def __init__(self):
    228 		self.bitmapSizeTable = BitmapSizeTable()
    229 		self.indexSubTables = []
    230 
    231 	def toXML(self, strikeIndex, writer, ttFont):
    232 		writer.begintag('strike', [('index', strikeIndex)])
    233 		writer.newline()
    234 		self.bitmapSizeTable.toXML(writer, ttFont)
    235 		writer.comment('GlyphIds are written but not read. The firstGlyphIndex and\nlastGlyphIndex values will be recalculated by the compiler.')
    236 		writer.newline()
    237 		for indexSubTable in self.indexSubTables:
    238 			indexSubTable.toXML(writer, ttFont)
    239 		writer.endtag('strike')
    240 		writer.newline()
    241 
    242 	def fromXML(self, name, attrs, content, ttFont, locator):
    243 		for element in content:
    244 			if not isinstance(element, tuple):
    245 				continue
    246 			name, attrs, content = element
    247 			if name == 'bitmapSizeTable':
    248 				self.bitmapSizeTable.fromXML(name, attrs, content, ttFont)
    249 			elif name.startswith(_indexSubTableSubclassPrefix):
    250 				indexFormat = safeEval(name[len(_indexSubTableSubclassPrefix):])
    251 				indexFormatClass = locator.getIndexFormatClass(indexFormat)
    252 				indexSubTable = indexFormatClass(None, None)
    253 				indexSubTable.indexFormat = indexFormat
    254 				indexSubTable.fromXML(name, attrs, content, ttFont)
    255 				self.indexSubTables.append(indexSubTable)
    256 
    257 
    258 class BitmapSizeTable(object):
    259 
    260 	# Returns all the simple metric names that bitmap size table
    261 	# cares about in terms of XML creation.
    262 	def _getXMLMetricNames(self):
    263 		dataNames = sstruct.getformat(bitmapSizeTableFormatPart1)[1]
    264 		dataNames = dataNames + sstruct.getformat(bitmapSizeTableFormatPart2)[1]
    265 		# Skip the first 3 data names because they are byte offsets and counts.
    266 		return dataNames[3:]
    267 
    268 	def toXML(self, writer, ttFont):
    269 		writer.begintag('bitmapSizeTable')
    270 		writer.newline()
    271 		for metric in ('hori', 'vert'):
    272 			getattr(self, metric).toXML(metric, writer, ttFont)
    273 		for metricName in self._getXMLMetricNames():
    274 			writer.simpletag(metricName, value=getattr(self, metricName))
    275 			writer.newline()
    276 		writer.endtag('bitmapSizeTable')
    277 		writer.newline()
    278 
    279 	def fromXML(self, name, attrs, content, ttFont):
    280 		# Create a lookup for all the simple names that make sense to
    281 		# bitmap size table. Only read the information from these names.
    282 		dataNames = set(self._getXMLMetricNames())
    283 		for element in content:
    284 			if not isinstance(element, tuple):
    285 				continue
    286 			name, attrs, content = element
    287 			if name == 'sbitLineMetrics':
    288 				direction = attrs['direction']
    289 				assert direction in ('hori', 'vert'), "SbitLineMetrics direction specified invalid."
    290 				metricObj = SbitLineMetrics()
    291 				metricObj.fromXML(name, attrs, content, ttFont)
    292 				vars(self)[direction] = metricObj
    293 			elif name in dataNames:
    294 				vars(self)[name] = safeEval(attrs['value'])
    295 			else:
    296 				print("Warning: unknown name '%s' being ignored in BitmapSizeTable." % name)
    297 
    298 
    299 class SbitLineMetrics(object):
    300 
    301 	def toXML(self, name, writer, ttFont):
    302 		writer.begintag('sbitLineMetrics', [('direction', name)])
    303 		writer.newline()
    304 		for metricName in sstruct.getformat(sbitLineMetricsFormat)[1]:
    305 			writer.simpletag(metricName, value=getattr(self, metricName))
    306 			writer.newline()
    307 		writer.endtag('sbitLineMetrics')
    308 		writer.newline()
    309 
    310 	def fromXML(self, name, attrs, content, ttFont):
    311 		metricNames = set(sstruct.getformat(sbitLineMetricsFormat)[1])
    312 		for element in content:
    313 			if not isinstance(element, tuple):
    314 				continue
    315 			name, attrs, content = element
    316 			if name in metricNames:
    317 				vars(self)[name] = safeEval(attrs['value'])
    318 
    319 # Important information about the naming scheme. Used for identifying subtables.
    320 _indexSubTableSubclassPrefix = 'eblc_index_sub_table_'
    321 
    322 class EblcIndexSubTable(object):
    323 
    324 	def __init__(self, data, ttFont):
    325 		self.data = data
    326 		self.ttFont = ttFont
    327 		# TODO Currently non-lazy decompiling doesn't work for this class...
    328 		#if not ttFont.lazy:
    329 		#	self.decompile()
    330 		#	del self.data, self.ttFont
    331 
    332 	def __getattr__(self, attr):
    333 		# Allow lazy decompile.
    334 		if attr[:2] == '__':
    335 			raise AttributeError(attr)
    336 		if not hasattr(self, "data"):
    337 			raise AttributeError(attr)
    338 		self.decompile()
    339 		del self.data, self.ttFont
    340 		return getattr(self, attr)
    341 
    342 	# This method just takes care of the indexSubHeader. Implementing subclasses
    343 	# should call it to compile the indexSubHeader and then continue compiling
    344 	# the remainder of their unique format.
    345 	def compile(self, ttFont):
    346 		return struct.pack(indexSubHeaderFormat, self.indexFormat, self.imageFormat, self.imageDataOffset)
    347 
    348 	# Creates the XML for bitmap glyphs. Each index sub table basically makes
    349 	# the same XML except for specific metric information that is written
    350 	# out via a method call that a subclass implements optionally.
    351 	def toXML(self, writer, ttFont):
    352 		writer.begintag(self.__class__.__name__, [
    353 				('imageFormat', self.imageFormat),
    354 				('firstGlyphIndex', self.firstGlyphIndex),
    355 				('lastGlyphIndex', self.lastGlyphIndex),
    356 				])
    357 		writer.newline()
    358 		self.writeMetrics(writer, ttFont)
    359 		# Write out the names as thats all thats needed to rebuild etc.
    360 		# For font debugging of consecutive formats the ids are also written.
    361 		# The ids are not read when moving from the XML format.
    362 		glyphIds = map(ttFont.getGlyphID, self.names)
    363 		for glyphName, glyphId in zip(self.names, glyphIds):
    364 			writer.simpletag('glyphLoc', name=glyphName, id=glyphId)
    365 			writer.newline()
    366 		writer.endtag(self.__class__.__name__)
    367 		writer.newline()
    368 
    369 	def fromXML(self, name, attrs, content, ttFont):
    370 		# Read all the attributes. Even though the glyph indices are
    371 		# recalculated, they are still read in case there needs to
    372 		# be an immediate export of the data.
    373 		self.imageFormat = safeEval(attrs['imageFormat'])
    374 		self.firstGlyphIndex = safeEval(attrs['firstGlyphIndex'])
    375 		self.lastGlyphIndex = safeEval(attrs['lastGlyphIndex'])
    376 
    377 		self.readMetrics(name, attrs, content, ttFont)
    378 
    379 		self.names = []
    380 		for element in content:
    381 			if not isinstance(element, tuple):
    382 				continue
    383 			name, attrs, content = element
    384 			if name == 'glyphLoc':
    385 				self.names.append(attrs['name'])
    386 
    387 	# A helper method that writes the metrics for the index sub table. It also
    388 	# is responsible for writing the image size for fixed size data since fixed
    389 	# size is not recalculated on compile. Default behavior is to do nothing.
    390 	def writeMetrics(self, writer, ttFont):
    391 		pass
    392 
    393 	# A helper method that is the inverse of writeMetrics.
    394 	def readMetrics(self, name, attrs, content, ttFont):
    395 		pass
    396 
    397 	# This method is for fixed glyph data sizes. There are formats where
    398 	# the glyph data is fixed but are actually composite glyphs. To handle
    399 	# this the font spec in indexSubTable makes the data the size of the
    400 	# fixed size by padding the component arrays. This function abstracts
    401 	# out this padding process. Input is data unpadded. Output is data
    402 	# padded only in fixed formats. Default behavior is to return the data.
    403 	def padBitmapData(self, data):
    404 		return data
    405 
    406 	# Remove any of the glyph locations and names that are flagged as skipped.
    407 	# This only occurs in formats {1,3}.
    408 	def removeSkipGlyphs(self):
    409 		# Determines if a name, location pair is a valid data location.
    410 		# Skip glyphs are marked when the size is equal to zero.
    411 		def isValidLocation(args):
    412 			(name, (startByte, endByte)) = args
    413 			return startByte < endByte
    414 		# Remove all skip glyphs.
    415 		dataPairs = list(filter(isValidLocation, zip(self.names, self.locations)))
    416 		self.names, self.locations = list(map(list, zip(*dataPairs)))
    417 
    418 # A closure for creating a custom mixin. This is done because formats 1 and 3
    419 # are very similar. The only difference between them is the size per offset
    420 # value. Code put in here should handle both cases generally.
    421 def _createOffsetArrayIndexSubTableMixin(formatStringForDataType):
    422 
    423 	# Prep the data size for the offset array data format.
    424 	dataFormat = '>'+formatStringForDataType
    425 	offsetDataSize = struct.calcsize(dataFormat)
    426 
    427 	class OffsetArrayIndexSubTableMixin(object):
    428 
    429 		def decompile(self):
    430 
    431 			numGlyphs = self.lastGlyphIndex - self.firstGlyphIndex + 1
    432 			indexingOffsets = [glyphIndex * offsetDataSize for glyphIndex in range(numGlyphs+2)]
    433 			indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
    434 			offsetArray = [struct.unpack(dataFormat, self.data[slice(*loc)])[0] for loc in indexingLocations]
    435 
    436 			glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex+1))
    437 			modifiedOffsets = [offset + self.imageDataOffset for offset in offsetArray]
    438 			self.locations = list(zip(modifiedOffsets, modifiedOffsets[1:]))
    439 
    440 			self.names = list(map(self.ttFont.getGlyphName, glyphIds))
    441 			self.removeSkipGlyphs()
    442 
    443 		def compile(self, ttFont):
    444 			# First make sure that all the data lines up properly. Formats 1 and 3
    445 			# must have all its data lined up consecutively. If not this will fail.
    446 			for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
    447 				assert curLoc[1] == nxtLoc[0], "Data must be consecutive in indexSubTable offset formats"
    448 
    449 			glyphIds = list(map(ttFont.getGlyphID, self.names))
    450 			# Make sure that all ids are sorted strictly increasing.
    451 			assert all(glyphIds[i] < glyphIds[i+1] for i in range(len(glyphIds)-1))
    452 
    453 			# Run a simple algorithm to add skip glyphs to the data locations at
    454 			# the places where an id is not present.
    455 			idQueue = deque(glyphIds)
    456 			locQueue = deque(self.locations)
    457 			allGlyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex+1))
    458 			allLocations = []
    459 			for curId in allGlyphIds:
    460 				if curId != idQueue[0]:
    461 					allLocations.append((locQueue[0][0], locQueue[0][0]))
    462 				else:
    463 					idQueue.popleft()
    464 					allLocations.append(locQueue.popleft())
    465 
    466 			# Now that all the locations are collected, pack them appropriately into
    467 			# offsets. This is the form where offset[i] is the location and
    468 			# offset[i+1]-offset[i] is the size of the data location.
    469 			offsets = list(allLocations[0]) + [loc[1] for loc in allLocations[1:]]
    470 			# Image data offset must be less than or equal to the minimum of locations.
    471 			# This offset may change the value for round tripping but is safer and
    472 			# allows imageDataOffset to not be required to be in the XML version.
    473 			self.imageDataOffset = min(offsets)
    474 			offsetArray = [offset - self.imageDataOffset for offset in offsets]
    475 
    476 			dataList = [EblcIndexSubTable.compile(self, ttFont)]
    477 			dataList += [struct.pack(dataFormat, offsetValue) for offsetValue in offsetArray]
    478 			# Take care of any padding issues. Only occurs in format 3.
    479 			if offsetDataSize * len(dataList) % 4 != 0:
    480 				dataList.append(struct.pack(dataFormat, 0))
    481 			return bytesjoin(dataList)
    482 
    483 	return OffsetArrayIndexSubTableMixin
    484 
    485 # A Mixin for functionality shared between the different kinds
    486 # of fixed sized data handling. Both kinds have big metrics so
    487 # that kind of special processing is also handled in this mixin.
    488 class FixedSizeIndexSubTableMixin(object):
    489 
    490 	def writeMetrics(self, writer, ttFont):
    491 		writer.simpletag('imageSize', value=self.imageSize)
    492 		writer.newline()
    493 		self.metrics.toXML(writer, ttFont)
    494 
    495 	def readMetrics(self, name, attrs, content, ttFont):
    496 		for element in content:
    497 			if not isinstance(element, tuple):
    498 				continue
    499 			name, attrs, content = element
    500 			if name == 'imageSize':
    501 				self.imageSize = safeEval(attrs['value'])
    502 			elif name == BigGlyphMetrics.__name__:
    503 				self.metrics = BigGlyphMetrics()
    504 				self.metrics.fromXML(name, attrs, content, ttFont)
    505 			elif name == SmallGlyphMetrics.__name__:
    506 				print("Warning: SmallGlyphMetrics being ignored in format %d." % self.indexFormat)
    507 
    508 	def padBitmapData(self, data):
    509 		# Make sure that the data isn't bigger than the fixed size.
    510 		assert len(data) <= self.imageSize, "Data in indexSubTable format %d must be less than the fixed size." % self.indexFormat
    511 		# Pad the data so that it matches the fixed size.
    512 		pad = (self.imageSize - len(data)) * b'\0'
    513 		return data + pad
    514 
    515 class eblc_index_sub_table_1(_createOffsetArrayIndexSubTableMixin('L'), EblcIndexSubTable):
    516 	pass
    517 
    518 class eblc_index_sub_table_2(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
    519 
    520 	def decompile(self):
    521 		(self.imageSize,) = struct.unpack(">L", self.data[:4])
    522 		self.metrics = BigGlyphMetrics()
    523 		sstruct.unpack2(bigGlyphMetricsFormat, self.data[4:], self.metrics)
    524 		glyphIds = list(range(self.firstGlyphIndex, self.lastGlyphIndex+1))
    525 		offsets = [self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds)+1)]
    526 		self.locations = list(zip(offsets, offsets[1:]))
    527 		self.names = list(map(self.ttFont.getGlyphName, glyphIds))
    528 
    529 	def compile(self, ttFont):
    530 		glyphIds = list(map(ttFont.getGlyphID, self.names))
    531 		# Make sure all the ids are consecutive. This is required by Format 2.
    532 		assert glyphIds == list(range(self.firstGlyphIndex, self.lastGlyphIndex+1)), "Format 2 ids must be consecutive."
    533 		self.imageDataOffset = min(zip(*self.locations)[0])
    534 
    535 		dataList = [EblcIndexSubTable.compile(self, ttFont)]
    536 		dataList.append(struct.pack(">L", self.imageSize))
    537 		dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
    538 		return bytesjoin(dataList)
    539 
    540 class eblc_index_sub_table_3(_createOffsetArrayIndexSubTableMixin('H'), EblcIndexSubTable):
    541 	pass
    542 
    543 class eblc_index_sub_table_4(EblcIndexSubTable):
    544 
    545 	def decompile(self):
    546 
    547 		(numGlyphs,) = struct.unpack(">L", self.data[:4])
    548 		data = self.data[4:]
    549 		indexingOffsets = [glyphIndex * codeOffsetPairSize for glyphIndex in range(numGlyphs+2)]
    550 		indexingLocations = zip(indexingOffsets, indexingOffsets[1:])
    551 		glyphArray = [struct.unpack(codeOffsetPairFormat, data[slice(*loc)]) for loc in indexingLocations]
    552 		glyphIds, offsets = list(map(list, zip(*glyphArray)))
    553 		# There are one too many glyph ids. Get rid of the last one.
    554 		glyphIds.pop()
    555 
    556 		offsets = [offset + self.imageDataOffset for offset in offsets]
    557 		self.locations = list(zip(offsets, offsets[1:]))
    558 		self.names = list(map(self.ttFont.getGlyphName, glyphIds))
    559 
    560 	def compile(self, ttFont):
    561 		# First make sure that all the data lines up properly. Format 4
    562 		# must have all its data lined up consecutively. If not this will fail.
    563 		for curLoc, nxtLoc in zip(self.locations, self.locations[1:]):
    564 			assert curLoc[1] == nxtLoc[0], "Data must be consecutive in indexSubTable format 4"
    565 
    566 		offsets = list(self.locations[0]) + [loc[1] for loc in self.locations[1:]]
    567 		# Image data offset must be less than or equal to the minimum of locations.
    568 		# Resetting this offset may change the value for round tripping but is safer
    569 		# and allows imageDataOffset to not be required to be in the XML version.
    570 		self.imageDataOffset = min(offsets)
    571 		offsets = [offset - self.imageDataOffset for offset in offsets]
    572 		glyphIds = list(map(ttFont.getGlyphID, self.names))
    573 		# Create an iterator over the ids plus a padding value.
    574 		idsPlusPad = list(itertools.chain(glyphIds, [0]))
    575 
    576 		dataList = [EblcIndexSubTable.compile(self, ttFont)]
    577 		dataList.append(struct.pack(">L", len(glyphIds)))
    578 		tmp = [struct.pack(codeOffsetPairFormat, *cop) for cop in zip(idsPlusPad, offsets)]
    579 		dataList += tmp
    580 		data = bytesjoin(dataList)
    581 		return data
    582 
    583 class eblc_index_sub_table_5(FixedSizeIndexSubTableMixin, EblcIndexSubTable):
    584 
    585 	def decompile(self):
    586 		self.origDataLen = 0
    587 		(self.imageSize,) = struct.unpack(">L", self.data[:4])
    588 		data = self.data[4:]
    589 		self.metrics, data = sstruct.unpack2(bigGlyphMetricsFormat, data, BigGlyphMetrics())
    590 		(numGlyphs,) = struct.unpack(">L", data[:4])
    591 		data = data[4:]
    592 		glyphIds = [struct.unpack(">H", data[2*i:2*(i+1)])[0] for i in range(numGlyphs)]
    593 
    594 		offsets = [self.imageSize * i + self.imageDataOffset for i in range(len(glyphIds)+1)]
    595 		self.locations = list(zip(offsets, offsets[1:]))
    596 		self.names = list(map(self.ttFont.getGlyphName, glyphIds))
    597 
    598 	def compile(self, ttFont):
    599 		self.imageDataOffset = min(zip(*self.locations)[0])
    600 		dataList = [EblcIndexSubTable.compile(self, ttFont)]
    601 		dataList.append(struct.pack(">L", self.imageSize))
    602 		dataList.append(sstruct.pack(bigGlyphMetricsFormat, self.metrics))
    603 		glyphIds = list(map(ttFont.getGlyphID, self.names))
    604 		dataList.append(struct.pack(">L", len(glyphIds)))
    605 		dataList += [struct.pack(">H", curId) for curId in glyphIds]
    606 		if len(glyphIds) % 2 == 1:
    607 			dataList.append(struct.pack(">H", 0))
    608 		return bytesjoin(dataList)
    609 
    610 # Dictionary of indexFormat to the class representing that format.
    611 eblc_sub_table_classes = {
    612 		1: eblc_index_sub_table_1,
    613 		2: eblc_index_sub_table_2,
    614 		3: eblc_index_sub_table_3,
    615 		4: eblc_index_sub_table_4,
    616 		5: eblc_index_sub_table_5,
    617 	}
    618