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