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): """(numDeltas, data, offset) --> ([delta, delta, ...], newOffset)""" 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 # assert data[serialized_block_ptr] == 0 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) # Read a TVH 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, ) )