blob: b5820b27b130e0a8a3661ce3c6d772a3d56d6b04 [file] [log] [blame]
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
from fontTools.misc.textTools import safeEval
from fontTools.ttLib import TTLibError
from . import DefaultTable
import struct
try:
from collections.abc import MutableMapping
except ImportError:
from UserDict import DictMixin as 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', self.track), ('nameIndex', self.nameIndex)))
writer.newline()
if name:
writer.comment(name)
writer.newline()
for size, perSizeValue in sorted(self.items()):
writer.simpletag("track", size=size, value=perSizeValue)
writer.newline()
writer.endtag("trackEntry")
writer.newline()
def fromXML(self, name, attrs, content, ttFont):
self.track = safeEval(attrs['value'])
self.nameIndex = safeEval(attrs['nameIndex'])
for element in content:
if not isinstance(element, tuple):
continue
name, attrs, _ = element
if name != 'track':
continue
size = safeEval(attrs['size'])
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