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)


[docs] class table__t_r_a_k(DefaultTable.DefaultTable): dependencies = ["name"]
[docs] 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
[docs] 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)
[docs] 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()
[docs] 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)
[docs] class TrackData(MutableMapping): def __init__(self, initialdata={}): self._map = dict(initialdata)
[docs] 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
[docs] 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
[docs] 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)
[docs] def fromXML(self, name, attrs, content, ttFont): if name != "trackEntry": return entry = TrackTableEntry() entry.fromXML(name, attrs, content, ttFont) self[entry.track] = entry
[docs] 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)
[docs] def keys(self): return self._map.keys()
tracks = keys def __repr__(self): return "TrackData({})".format(self._map if self else "")
[docs] class TrackTableEntry(MutableMapping): def __init__(self, values={}, nameIndex=None): self.nameIndex = nameIndex self._map = dict(values)
[docs] 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()
[docs] 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)
[docs] 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