• 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 . import DefaultTable
5from fontTools.misc.textTools import safeEval
6from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
7import struct
8import itertools
9from collections import deque
10
11eblcHeaderFormat = """
12	> # big endian
13	version:  16.16F
14	numSizes: I
15"""
16# The table format string is split to handle sbitLineMetrics simply.
17bitmapSizeTableFormatPart1 = """
18	> # big endian
19	indexSubTableArrayOffset: I
20	indexTablesSize:          I
21	numberOfIndexSubTables:   I
22	colorRef:                 I
23"""
24# The compound type for hori and vert.
25sbitLineMetricsFormat = """
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.
41bitmapSizeTableFormatPart2 = """
42	> # big endian
43	startGlyphIndex: H
44	endGlyphIndex:   H
45	ppemX:           B
46	ppemY:           B
47	bitDepth:        B
48	flags:           b
49"""
50
51indexSubTableArrayFormat = ">HHL"
52indexSubTableArraySize = struct.calcsize(indexSubTableArrayFormat)
53
54indexSubHeaderFormat = ">HHL"
55indexSubHeaderSize = struct.calcsize(indexSubHeaderFormat)
56
57codeOffsetPairFormat = ">HH"
58codeOffsetPairSize = struct.calcsize(codeOffsetPairFormat)
59
60class 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
225class 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
258class 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
299class 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
322class 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.
421def _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.
488class 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
515class eblc_index_sub_table_1(_createOffsetArrayIndexSubTableMixin('L'), EblcIndexSubTable):
516	pass
517
518class 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
540class eblc_index_sub_table_3(_createOffsetArrayIndexSubTableMixin('H'), EblcIndexSubTable):
541	pass
542
543class 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
583class 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.
611eblc_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