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