from fontTools.ttLib import TTFont
from fontTools.misc.fixedTools import fixedToFloat
from fontTools.ttLib.tables._g_v_a_r import decompileGlyph_, table__g_v_a_r
from fontTools.ttLib.tables.TupleVariation import (
TUPLES_SHARE_POINT_NUMBERS,
TUPLE_COUNT_MASK,
EMBEDDED_PEAK_TUPLE,
INTERMEDIATE_REGION,
PRIVATE_POINT_NUMBERS,
TUPLE_INDEX_MASK,
DELTA_RUN_COUNT_MASK,
DELTAS_ARE_ZERO,
DELTAS_ARE_WORDS,
TupleVariation,
decompileTupleVariation_,
)
import array
import fontTools.ttLib.tables.TupleVariation as tv
import struct
import sys
font = TTFont(sys.argv[1])
order = font.getGlyphOrder()
glyf = font["glyf"]
data = font.reader["gvar"]
ptr = 0
def read_short():
global ptr
global data
val = struct.unpack(">h", data[ptr : ptr + 2])
ptr += 2
return val[0]
def read_long():
global ptr
global data
val = struct.unpack(">l", data[ptr : ptr + 4])
ptr += 4
return val[0]
axis_order = [x.axisTag for x in font["fvar"].axes]
def read_tuple(axis_count):
a_tuple = {}
for j in range(0, axis_count):
a_tuple[axis_order[j]] = fixedToFloat(read_short(), 14)
return a_tuple
def decompileDeltas(numDeltas, data, offset):
result = []
pos = offset
while len(result) < numDeltas:
runHeader = data[pos]
pos += 1
numDeltasInRun = (runHeader & DELTA_RUN_COUNT_MASK) + 1
print(" Num deltas in run: %i " % numDeltasInRun)
if (runHeader & DELTAS_ARE_ZERO) != 0:
result.extend([0] * numDeltasInRun)
else:
if (runHeader & DELTAS_ARE_WORDS) != 0:
deltas = array.array("h")
deltasSize = numDeltasInRun * 2
else:
deltas = array.array("b")
deltasSize = numDeltasInRun
deltas.frombytes(data[pos : pos + deltasSize])
if sys.byteorder != "big":
deltas.byteswap()
assert len(deltas) == numDeltasInRun
pos += deltasSize
result.extend(deltas)
print(" Total length of deltas: %i" % len(result))
return (result, pos)
major_version = read_short()
minor_version = read_short()
assert major_version == 1 and minor_version == 0
axis_count = read_short()
shared_tuple_count = read_short()
shared_tuples_offset = read_long()
print("Axis count: %i" % axis_count)
print("Shared tuple count: %i" % shared_tuple_count)
print("Shared tuples offset: %i" % shared_tuples_offset)
glyph_count = read_short()
flags = read_short()
data_array_offset = read_long()
print("Glyph count: %i" % glyph_count)
print("Flags: %i" % flags)
print("Data array offset: %i" % data_array_offset)
assert glyph_count == len(order)
data_offsets = []
for i in range(0, glyph_count + 1):
if flags > 0:
data_offsets.append(read_long())
else:
data_offsets.append(read_short() * 2)
if ptr != shared_tuples_offset:
print("Warning: Expecting shared tuples")
shared_tuples = []
for i in range(0, shared_tuple_count):
shared_tuples.append(read_tuple(axis_count))
print("\nShared tuples:")
for t in shared_tuples:
print(" %s" % t)
if ptr != data_array_offset:
print("Warning: Expecting glyphVariationDataArray")
for g in range(0, glyph_count):
glyph_name = order[g]
start_of_gvd = ptr = data_array_offset + data_offsets[g]
end_of_gvd = data_array_offset + data_offsets[g + 1]
print(
"\n\nReading table for %s (%i-%i out of %i)"
% (glyph_name, start_of_gvd, end_of_gvd, len(data))
)
if start_of_gvd == end_of_gvd:
print("No data")
continue
tuple_variation_count = read_short()
actual_tvh_count = tuple_variation_count & TUPLE_COUNT_MASK
has_shared_points = (tuple_variation_count & TUPLES_SHARE_POINT_NUMBERS) > 0
print(" Tuple variation count: %i" % actual_tvh_count)
print(" Shared points?: %s" % has_shared_points)
assert actual_tvh_count > 0 and actual_tvh_count < 12
data_offset = read_short()
num_glyph_points = table__g_v_a_r.getNumPoints_(glyf[glyph_name])
serialized_block_ptr = start_of_gvd + data_offset
if has_shared_points:
(shared_points, serialized_block_ptr) = TupleVariation.decompilePoints_(
num_glyph_points, data, serialized_block_ptr, "gvar"
)
print("Shared points: %s" % shared_points)
for h_ix in range(0, tuple_variation_count & TUPLE_COUNT_MASK):
print(" Tuple variation header: %i" % h_ix)
pos = ptr
variation_data_size = read_short()
tuple_index = read_short()
tupleSize = TupleVariation.getTupleSize_(tuple_index, axis_count)
tupleData = data[(pos) : (pos + tupleSize)]
pointDeltaData = data[
serialized_block_ptr : serialized_block_ptr + variation_data_size
]
has_private_points = tuple_index & PRIVATE_POINT_NUMBERS
if tuple_index & EMBEDDED_PEAK_TUPLE:
peak_tuple = read_tuple(axis_count)
print(" Embedded peak tuple: %s" % peak_tuple)
else:
print(" Shared tuple %i" % (tuple_index & TUPLE_INDEX_MASK))
print(
" Shared tuple: %s" % shared_tuples[tuple_index & TUPLE_INDEX_MASK]
)
if tuple_index & INTERMEDIATE_REGION:
start_tuple = read_tuple(axis_count)
end_tuple = read_tuple(axis_count)
print(" Embedded start tuple: %s" % start_tuple)
print(" Embedded end tuple: %s" % end_tuple)
deltaptr = 0
if has_private_points:
print(" Has private points")
print(pointDeltaData[0:5])
(points, deltaptr) = TupleVariation.decompilePoints_(
num_glyph_points, pointDeltaData, deltaptr, "gvar"
)
num_points = len(points)
print(" %s" % points)
else:
num_glyph_points = len(shared_points)
print("Expecting %i deltas " % num_points)
deltas_x, deltaptr = decompileDeltas(num_points, pointDeltaData, deltaptr)
deltas_y, deltaptr = decompileDeltas(num_points, pointDeltaData, deltaptr)
print(list(zip(deltas_x, deltas_y)))
serialized_block_ptr += variation_data_size
print("Decompiling with fontTools:")
print(
tv.decompileTupleVariationStore(
"gvar",
axis_order,
tuple_variation_count,
num_glyph_points,
shared_tuples,
data[start_of_gvd:end_of_gvd],
4,
data_offset,
)
)