123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- 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
|