# Source code for fontTools.ttLib.tables._t_r_a_k

    from fontTools.misc import sstruct
    from fontTools.misc.fixedTools import (
    	fixedToFloat as fi2fl,
    	floatToFixed as fl2fi,
    	floatToFixedToStr as fl2str,
    	strToFixedToFloat as str2fl,
    )
    from fontTools.misc.textTools import bytesjoin, safeEval
    from fontTools.ttLib import TTLibError
    from . import DefaultTable
    import struct
    from collections.abc import MutableMapping

    # Apple's documentation of 'trak':
    # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html

    TRAK_HEADER_FORMAT = """
    	> # big endian
    	version:     16.16F
    	format:      H
    	horizOffset: H
    	vertOffset:  H
    	reserved:    H
    """

    TRAK_HEADER_FORMAT_SIZE = sstruct.calcsize(TRAK_HEADER_FORMAT)

    TRACK_DATA_FORMAT = """
    	> # big endian
    	nTracks:         H
    	nSizes:          H
    	sizeTableOffset: L
    """

    TRACK_DATA_FORMAT_SIZE = sstruct.calcsize(TRACK_DATA_FORMAT)

    TRACK_TABLE_ENTRY_FORMAT = """
    	> # big endian
    	track:      16.16F
    	nameIndex:       H
    	offset:          H
    """

    TRACK_TABLE_ENTRY_FORMAT_SIZE = sstruct.calcsize(TRACK_TABLE_ENTRY_FORMAT)

    # size values are actually '16.16F' fixed-point values, but here I do the
    # fixedToFloat conversion manually instead of relying on sstruct
    SIZE_VALUE_FORMAT = ">l"
    SIZE_VALUE_FORMAT_SIZE = struct.calcsize(SIZE_VALUE_FORMAT)

    # per-Size values are in 'FUnits', i.e. 16-bit signed integers
    PER_SIZE_VALUE_FORMAT = ">h"
    PER_SIZE_VALUE_FORMAT_SIZE = struct.calcsize(PER_SIZE_VALUE_FORMAT)

    class table__t_r_a_k(DefaultTable.DefaultTable):
    	dependencies = ['name']

    	def compile(self, ttFont):
    		dataList = []
    		offset = TRAK_HEADER_FORMAT_SIZE
    		for direction in ('horiz', 'vert'):
    			trackData = getattr(self, direction + 'Data', TrackData())
    			offsetName = direction + 'Offset'
    			# set offset to 0 if None or empty
    			if not trackData:
    				setattr(self, offsetName, 0)
    				continue
    			# TrackData table format must be longword aligned
    			alignedOffset = (offset + 3) & ~3
    			padding, offset = b"\x00"*(alignedOffset - offset), alignedOffset
    			setattr(self, offsetName, offset)

    			data = trackData.compile(offset)
    			offset += len(data)
    			dataList.append(padding + data)

    		self.reserved = 0
    		tableData = bytesjoin([sstruct.pack(TRAK_HEADER_FORMAT, self)] + dataList)
    		return tableData

    	def decompile(self, data, ttFont):
    		sstruct.unpack(TRAK_HEADER_FORMAT, data[:TRAK_HEADER_FORMAT_SIZE], self)
    		for direction in ('horiz', 'vert'):
    			trackData = TrackData()
    			offset = getattr(self, direction + 'Offset')
    			if offset != 0:
    				trackData.decompile(data, offset)
    			setattr(self, direction + 'Data', trackData)

    	def toXML(self, writer, ttFont):
    		writer.simpletag('version', value=self.version)
    		writer.newline()
    		writer.simpletag('format', value=self.format)
    		writer.newline()
    		for direction in ('horiz', 'vert'):
    			dataName = direction + 'Data'
    			writer.begintag(dataName)
    			writer.newline()
    			trackData = getattr(self, dataName, TrackData())
    			trackData.toXML(writer, ttFont)
    			writer.endtag(dataName)
    			writer.newline()

    	def fromXML(self, name, attrs, content, ttFont):
    		if name == 'version':
    			self.version = safeEval(attrs['value'])
    		elif name == 'format':
    			self.format = safeEval(attrs['value'])
    		elif name in ('horizData', 'vertData'):
    			trackData = TrackData()
    			setattr(self, name, trackData)
    			for element in content:
    				if not isinstance(element, tuple):
    					continue
    				name, attrs, content_ = element
    				trackData.fromXML(name, attrs, content_, ttFont)

    class TrackData(MutableMapping):

    	def __init__(self, initialdata={}):
    		self._map = dict(initialdata)

    	def compile(self, offset):
    		nTracks = len(self)
    		sizes = self.sizes()
    		nSizes = len(sizes)

    		# offset to the start of the size subtable
    		offset += TRACK_DATA_FORMAT_SIZE + TRACK_TABLE_ENTRY_FORMAT_SIZE*nTracks
    		trackDataHeader = sstruct.pack(
    			TRACK_DATA_FORMAT,
    			{'nTracks': nTracks, 'nSizes': nSizes, 'sizeTableOffset': offset})

    		entryDataList = []
    		perSizeDataList = []
    		# offset to per-size tracking values
    		offset += SIZE_VALUE_FORMAT_SIZE*nSizes
    		# sort track table entries by track value
    		for track, entry in sorted(self.items()):
    			assert entry.nameIndex is not None
    			entry.track = track
    			entry.offset = offset
    			entryDataList += [sstruct.pack(TRACK_TABLE_ENTRY_FORMAT, entry)]
    			# sort per-size values by size
    			for size, value in sorted(entry.items()):
    				perSizeDataList += [struct.pack(PER_SIZE_VALUE_FORMAT, value)]
    			offset += PER_SIZE_VALUE_FORMAT_SIZE*nSizes
    		# sort size values
    		sizeDataList = [struct.pack(SIZE_VALUE_FORMAT, fl2fi(sv, 16)) for sv in sorted(sizes)]

    		data = bytesjoin([trackDataHeader] + entryDataList + sizeDataList + perSizeDataList)
    		return data

    	def decompile(self, data, offset):
    		# initial offset is from the start of trak table to the current TrackData
    		trackDataHeader = data[offset:offset+TRACK_DATA_FORMAT_SIZE]
    		if len(trackDataHeader) != TRACK_DATA_FORMAT_SIZE:
    			raise TTLibError('not enough data to decompile TrackData header')
    		sstruct.unpack(TRACK_DATA_FORMAT, trackDataHeader, self)
    		offset += TRACK_DATA_FORMAT_SIZE

    		nSizes = self.nSizes
    		sizeTableOffset = self.sizeTableOffset
    		sizeTable = []
    		for i in range(nSizes):
    			sizeValueData = data[sizeTableOffset:sizeTableOffset+SIZE_VALUE_FORMAT_SIZE]
    			if len(sizeValueData) < SIZE_VALUE_FORMAT_SIZE:
    				raise TTLibError('not enough data to decompile TrackData size subtable')
    			sizeValue, = struct.unpack(SIZE_VALUE_FORMAT, sizeValueData)
    			sizeTable.append(fi2fl(sizeValue, 16))
    			sizeTableOffset += SIZE_VALUE_FORMAT_SIZE

    		for i in range(self.nTracks):
    			entry = TrackTableEntry()
    			entryData = data[offset:offset+TRACK_TABLE_ENTRY_FORMAT_SIZE]
    			if len(entryData) < TRACK_TABLE_ENTRY_FORMAT_SIZE:
    				raise TTLibError('not enough data to decompile TrackTableEntry record')
    			sstruct.unpack(TRACK_TABLE_ENTRY_FORMAT, entryData, entry)
    			perSizeOffset = entry.offset
    			for j in range(nSizes):
    				size = sizeTable[j]
    				perSizeValueData = data[perSizeOffset:perSizeOffset+PER_SIZE_VALUE_FORMAT_SIZE]
    				if len(perSizeValueData) < PER_SIZE_VALUE_FORMAT_SIZE:
    					raise TTLibError('not enough data to decompile per-size track values')
    				perSizeValue, = struct.unpack(PER_SIZE_VALUE_FORMAT, perSizeValueData)
    				entry[size] = perSizeValue
    				perSizeOffset += PER_SIZE_VALUE_FORMAT_SIZE
    			self[entry.track] = entry
    			offset += TRACK_TABLE_ENTRY_FORMAT_SIZE

    	def toXML(self, writer, ttFont):
    		nTracks = len(self)
    		nSizes = len(self.sizes())
    		writer.comment("nTracks=%d, nSizes=%d" % (nTracks, nSizes))
    		writer.newline()
    		for track, entry in sorted(self.items()):
    			assert entry.nameIndex is not None
    			entry.track = track
    			entry.toXML(writer, ttFont)

    	def fromXML(self, name, attrs, content, ttFont):
    		if name != 'trackEntry':
    			return
    		entry = TrackTableEntry()
    		entry.fromXML(name, attrs, content, ttFont)
    		self[entry.track] = entry

    	def sizes(self):
    		if not self:
    			return frozenset()
    		tracks = list(self.tracks())
    		sizes = self[tracks.pop(0)].sizes()
    		for track in tracks:
    			entrySizes = self[track].sizes()
    			if sizes != entrySizes:
    				raise TTLibError(
    					"'trak' table entries must specify the same sizes: "
    					"%s != %s" % (sorted(sizes), sorted(entrySizes)))
    		return frozenset(sizes)

    	def __getitem__(self, track):
    		return self._map[track]

    	def __delitem__(self, track):
    		del self._map[track]

    	def __setitem__(self, track, entry):
    		self._map[track] = entry

    	def __len__(self):
    		return len(self._map)

    	def __iter__(self):
    		return iter(self._map)

    	def keys(self):
    		return self._map.keys()

    	tracks = keys

    	def __repr__(self):
    		return "TrackData({})".format(self._map if self else "")

    class TrackTableEntry(MutableMapping):

    	def __init__(self, values={}, nameIndex=None):
    		self.nameIndex = nameIndex
    		self._map = dict(values)

    	def toXML(self, writer, ttFont):
    		name = ttFont["name"].getDebugName(self.nameIndex)
    		writer.begintag(
    			"trackEntry",
    			(('value', fl2str(self.track, 16)), ('nameIndex', self.nameIndex)))
    		writer.newline()
    		if name:
    			writer.comment(name)
    			writer.newline()
    		for size, perSizeValue in sorted(self.items()):
    			writer.simpletag("track", size=fl2str(size, 16), value=perSizeValue)
    			writer.newline()
    		writer.endtag("trackEntry")
    		writer.newline()

    	def fromXML(self, name, attrs, content, ttFont):
    		self.track = str2fl(attrs['value'], 16)
    		self.nameIndex = safeEval(attrs['nameIndex'])
    		for element in content:
    			if not isinstance(element, tuple):
    				continue
    			name, attrs, _ = element
    			if name != 'track':
    				continue
    			size = str2fl(attrs['size'], 16)
    			self[size] = safeEval(attrs['value'])

    	def __getitem__(self, size):
    		return self._map[size]

    	def __delitem__(self, size):
    		del self._map[size]

    	def __setitem__(self, size, value):
    		self._map[size] = value

    	def __len__(self):
    		return len(self._map)

    	def __iter__(self):
    		return iter(self._map)

    	def keys(self):
    		return self._map.keys()

    	sizes = keys

    	def __repr__(self):
    		return "TrackTableEntry({}, nameIndex={})".format(self._map, self.nameIndex)

    	def __eq__(self, other):
    		if not isinstance(other, self.__class__):
    			return NotImplemented
    		return self.nameIndex == other.nameIndex and dict(self) == dict(other)

    	def __ne__(self, other):
    		result = self.__eq__(other)
    		return result if result is NotImplemented else not result