Cosimo Lupo 0227945781 _t_r_a_k: do not import unicode_literals as it makes struct fail on some old Python 2.7.x
On the latest Python 2.7.10, struct accepts both unicode and str as 'fmt'.
However on Travis for py27 we use the built-in OSX Python, which on OSX 10.9 is Python 2.7.5.

Travis log: https://travis-ci.org/anthrotype/fonttools/jobs/96473892#L3010

Python issue: https://bugs.python.org/issue19099
2015-12-12 19:28:59 +00:00

316 lines
9.2 KiB
Python

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, progress=None):
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, progress=None):
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, progress=None):
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 (isinstance(other, TrackTableEntry)
and self.nameIndex == other.nameIndex
and dict(self) == dict(other)):
return True
return False
def __ne__(self, other):
return not self.__eq__(other)