def test_sum_deltas_gvar(self): var1 = TupleVariation({}, [ (-20, 0), (-20, 0), (20, 0), (20, 0), (0, 0), (0, 0), (0, 0), (0, 0), ]) var2 = TupleVariation({}, [ (-10, 0), (-10, 0), (10, 0), (10, 0), (0, 0), (20, 0), (0, 0), (0, 0), ]) var1 += var2 self.assertEqual(var1.coordinates, [ (-30, 0), (-30, 0), (30, 0), (30, 0), (0, 0), (20, 0), (0, 0), (0, 0), ])
def test_compileTupleVariationStore_roundTrip_gvar(self): deltas = [(1, 1), (2, 2), (3, 3), (4, 4)] variations = [ TupleVariation({ "wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0) }, deltas), TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0) }, deltas) ] tupleVariationCount, tuples, data = compileTupleVariationStore( variations, pointCount=4, axisTags=["wght", "wdth"], sharedTupleIndices={}) self.assertEqual( decompileTupleVariationStore("gvar", ["wght", "wdth"], tupleVariationCount, pointCount=4, sharedTuples={}, data=(tuples + data), pos=0, dataPos=len(tuples)), variations)
def test_sum_deltas_gvar_invalid_length(self): var1 = TupleVariation({}, [(1, 2)]) var2 = TupleVariation({}, [(1, 2), (3, 4)]) with self.assertRaisesRegex(ValueError, "deltas with different lengths"): var1 += var2
def _add_gvar(font, model, master_ttfs, tolerance=0.5, optimize=True): assert tolerance >= 0 log.info("Generating gvar") assert "gvar" not in font gvar = font["gvar"] = newTable('gvar') gvar.version = 1 gvar.reserved = 0 gvar.variations = {} for glyph in font.getGlyphOrder(): allData = [_GetCoordinates(m, glyph) for m in master_ttfs] allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if (any(c != control for c in allControls)): log.warning("glyph %s has incompatible masters; skipping" % glyph) continue del allControls # Update gvar gvar.variations[glyph] = [] deltas = model.getDeltas(allCoords) supports = model.supports assert len(deltas) == len(supports) # Prepare for IUP optimization origCoords = deltas[0] endPts = control[1] if control[0] >= 1 else list(range(len( control[1]))) for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): if all(abs(v) <= tolerance for v in delta.array): continue var = TupleVariation(support, delta) if optimize: delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance) if None in delta_opt: # Use "optimized" version only if smaller... var_opt = TupleVariation(support, delta_opt) axis_tags = sorted(support.keys( )) # Shouldn't matter that this is different from fvar...? tupleData, auxData, _ = var.compile(axis_tags, [], None) unoptimized_len = len(tupleData) + len(auxData) tupleData, auxData, _ = var_opt.compile( axis_tags, [], None) optimized_len = len(tupleData) + len(auxData) if optimized_len < unoptimized_len: var = var_opt gvar.variations[glyph].append(var)
def test_sum_deltas_cvar(self): axes = {"wght": (0.0, 1.0, 1.0)} var1 = TupleVariation(axes, [0, 1, None, None]) var2 = TupleVariation(axes, [None, 2, None, 3]) var3 = TupleVariation(axes, [None, None, None, 4]) var1 += var2 var1 += var3 self.assertEqual(var1.coordinates, [0, 3, None, 7])
def test_compileTupleVariationStore_allVariationsRedundant(self): axes = {"wght": (0.3, 0.4, 0.5), "opsz": (0.7, 0.8, 0.9)} variations = [ TupleVariation(axes, [None] * 4), TupleVariation(axes, [None] * 4), TupleVariation(axes, [None] * 4) ] self.assertEqual( compileTupleVariationStore(variations, pointCount=8, axisTags=["wght", "opsz"], sharedTupleIndices={}), (0, b"", b""))
def test_optimize_isComposite(self): # when a composite glyph's deltas are all (0, 0), we still want # to write out an entry in gvar, else macOS doesn't apply any # variations to the composite glyph (even if its individual components # do vary). # https://github.com/fonttools/fonttools/issues/1381 var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 5) var.optimize([(0, 0)] * 5, [0], isComposite=True) self.assertEqual(var.coordinates, [(0, 0)] * 5) # it takes more than 128 (0, 0) deltas before the optimized tuple with # (None) inferred deltas (except for the first) becomes smaller than # the un-optimized one that has all deltas explicitly set to (0, 0). var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 129) var.optimize([(0, 0)] * 129, list(range(129 - 4)), isComposite=True) self.assertEqual(var.coordinates, [(0, 0)] + [None] * 128)
def test_calcInferredDeltas(self): var = TupleVariation({}, [(0, 0), None, None, None]) coords = [(1, 1), (1, 1), (1, 1), (1, 1)] var.calcInferredDeltas(coords, []) self.assertEqual(var.coordinates, [(0, 0), (0, 0), (0, 0), (0, 0)])
def test_compileDeltas_points(self): var = TupleVariation({}, [(0, 0), (1, 0), (2, 0), None, (4, 0), (5, 0)]) points = {1, 2, 3, 4} # deltaX for points: [1, 2, 4]; deltaY for points: [0, 0, 0] self.assertEqual("02 01 02 04 82", hexencode(var.compileDeltas(points)))
def test_decompileCoord_roundTrip(self): # Make sure we are not affected by https://github.com/fonttools/fonttools/issues/286 data = deHexStr("7F B9 80 35") values, _ = TupleVariation.decompileCoord_(["wght", "wdth"], data, 0) axisValues = {axis:(val, val, val) for axis, val in values.items()} var = TupleVariation(axisValues, [None] * 4) self.assertEqual("7F B9 80 35", hexencode(var.compileCoord(["wght", "wdth"])))
def test_fromXML_badDeltaFormat(self): g = TupleVariation({}, []) with CapturingLogHandler(log, "WARNING") as captor: for name, attrs, content in parseXML('<delta a="1" b="2"/>'): g.fromXML(name, attrs, content) self.assertIn("bad delta format: a, b", [r.msg for r in captor.records])
def test_calcInferredDeltas_invalid(self): # cvar tuples can't have inferred deltas with self.assertRaises(TypeError): TupleVariation({}, [0]).calcInferredDeltas([], []) # origCoords must have same length as self.coordinates with self.assertRaises(ValueError): TupleVariation({}, [(0, 0), None]).calcInferredDeltas([], []) # at least 4 phantom points required with self.assertRaises(AssertionError): TupleVariation({}, [(0, 0), None]).calcInferredDeltas([(0, 0), (0, 0)], []) with self.assertRaises(AssertionError): TupleVariation({}, [(0, 0)] + [None]*5).calcInferredDeltas( [(0, 0)]*6, [1, 0] # endPts not in increasing order )
def test_getCoordWidth(self): empty = TupleVariation({}, []) self.assertEqual(empty.getCoordWidth(), 0) empty = TupleVariation({}, [None]) self.assertEqual(empty.getCoordWidth(), 0) gvarTuple = TupleVariation({}, [None, (0, 0)]) self.assertEqual(gvarTuple.getCoordWidth(), 2) cvarTuple = TupleVariation({}, [None, 0]) self.assertEqual(cvarTuple.getCoordWidth(), 1) cvarTuple.coordinates[1] *= 1.0 self.assertEqual(cvarTuple.getCoordWidth(), 1) with self.assertRaises(TypeError): TupleVariation({}, [None, "a"]).getCoordWidth()
def test_toXML_allDeltasNone(self): writer = XMLWriter(BytesIO()) axes = {"wght": (0.0, 1.0, 1.0)} g = TupleVariation(axes, [None] * 5) g.toXML(writer, ["wght", "wdth"]) self.assertEqual([ '<tuple>', '<coord axis="wght" value="1.0"/>', '<!-- no deltas -->', '</tuple>' ], TupleVariationTest.xml_lines(writer))
def test_compile_embeddedPeak_nonIntermediate_sharedConstants(self): var = TupleVariation( {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [3, 1, 4]) tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'') # len(deltas)=4; flags=EMBEDDED_PEAK_TUPLE # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 04 80 00 20 00 33 33", hexencode(tup)) self.assertEqual("02 03 01 04", # delta: [3, 1, 4] hexencode(deltas))
def test_compile_embeddedPeak_nonIntermediate_sharedPoints(self): var = TupleVariation( {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7,4), (8,5), (9,6)]) tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'') # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tup)) self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(deltas))
def test_fromXML_points(self): g = TupleVariation({}, [None] * 4) for name, attrs, content in parseXML( '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>' '<coord axis="wght" value="1.0"/>' '<coord axis="opsz" value="-0.7"/>' '<delta pt="1" x="33" y="44"/>' '<delta pt="2" x="-2" y="170"/>'): g.fromXML(name, attrs, content) self.assertEqual(AXES, g.axes) self.assertEqual([None, (33, 44), (-2, 170), None], g.coordinates)
def test_compileCoord(self): var = TupleVariation( { "wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6) }, [None] * 4) self.assertEqual("C0 00 20 00", hexencode(var.compileCoord(["wght", "wdth"]))) self.assertEqual("20 00 C0 00", hexencode(var.compileCoord(["wdth", "wght"]))) self.assertEqual("C0 00", hexencode(var.compileCoord(["wght"])))
def test_toXML_constants(self): writer = XMLWriter(BytesIO()) g = TupleVariation(AXES, [42, None, 23, 0, -17, None]) g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta cvt="0" value="42"/>', '<delta cvt="2" value="23"/>', '<delta cvt="3" value="0"/>', '<delta cvt="4" value="-17"/>', '</tuple>' ], TupleVariationTest.xml_lines(writer))
def test_compile_embeddedPeak_nonIntermediate_privateConstants(self): var = TupleVariation( {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [7, 8, 9]) tup, deltas = var.compile(axisTags=["wght", "wdth"]) # len(deltas)=5; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 05 A0 00 20 00 33 33", hexencode(tup)) self.assertEqual("00 " # all points in glyph "02 07 08 09", # delta: [7, 8, 9] hexencode(deltas))
def test_fromXML_constants(self): g = TupleVariation({}, [None] * 4) for name, attrs, content in parseXML( '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>' '<coord axis="wght" value="1.0"/>' '<coord axis="opsz" value="-0.7"/>' '<delta cvt="1" value="42"/>' '<delta cvt="2" value="-23"/>'): g.fromXML(name, attrs, content) self.assertEqual(AXES, g.axes) self.assertEqual([None, 42, -23, None], g.coordinates)
def test_toXML_badDeltaFormat(self): writer = XMLWriter(BytesIO()) g = TupleVariation(AXES, ["String"]) with CapturingLogHandler(log, "ERROR") as captor: g.toXML(writer, ["wdth"]) self.assertIn("bad delta format", [r.msg for r in captor.records]) self.assertEqual([ '<tuple>', '<coord axis="wdth" min="0.3" value="0.4" max="0.5"/>', '<!-- bad delta #0 -->', '</tuple>', ], TupleVariationTest.xml_lines(writer))
def test_toXML_points(self): writer = XMLWriter(BytesIO()) g = TupleVariation(AXES, [(9, 8), None, (7, 6), (0, 0), (-1, -2), None]) g.toXML(writer, ["wdth", "wght", "opsz"]) self.assertEqual([ '<tuple>', '<coord axis="wdth" max="0.5" min="0.3" value="0.4"/>', '<coord axis="wght" value="1.0"/>', '<coord axis="opsz" value="-0.7"/>', '<delta pt="0" x="9" y="8"/>', '<delta pt="2" x="7" y="6"/>', '<delta pt="3" x="0" y="0"/>', '<delta pt="4" x="-1" y="-2"/>', '</tuple>' ], TupleVariationTest.xml_lines(writer))
def test_scaleDeltas_cvar(self): var = TupleVariation({}, [100, None]) var.scaleDeltas(1.0) self.assertEqual(var.coordinates, [100, None]) var.scaleDeltas(0.333) self.assertAlmostEqual(var.coordinates[0], 33.3) self.assertIsNone(var.coordinates[1]) var.scaleDeltas(0.0) self.assertEqual(var.coordinates, [0, None])
def fromXML(self, name, attrs, content, ttFont): if name == "version": self.majorVersion = int(attrs.get("major", "1")) self.minorVersion = int(attrs.get("minor", "0")) elif name == "tuple": valueCount = len(ttFont["cvt "].values) var = TupleVariation({}, [None] * valueCount) self.variations.append(var) for tupleElement in content: if isinstance(tupleElement, tuple): tupleName, tupleAttrs, tupleContent = tupleElement var.fromXML(tupleName, tupleAttrs, tupleContent)
def test_compileSharedTuples(self): # Below, the peak coordinate {"wght": 1.0, "wdth": 0.7} appears # three times; {"wght": 1.0, "wdth": 0.8} appears twice. # Because the start and end of variation ranges is not encoded # into the shared pool, they should get ignored. deltas = [None] * 4 variations = [ TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.7, 1.0) }, deltas), TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.7, 1.0) }, deltas), TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.8, 1.0) }, deltas), TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.7, 1.0) }, deltas), TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.8, 1.0) }, deltas), TupleVariation({ "wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.9, 1.0) }, deltas) ] result = compileSharedTuples(["wght", "wdth"], variations) self.assertEqual([hexencode(c) for c in result], ["40 00 2C CD", "40 00 33 33"])
def test_compile_sharedPeaks_intermediate_sharedPoints(self): var = TupleVariation( {"wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9)}, [(7,4), (8,5), (9,6)]) axisTags = ["wght", "wdth"] sharedPeakIndices = { var.compileCoord(axisTags): 0x77 } tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b'') # len(deltas)=8; flags=INTERMEDIATE_REGION; tupleIndex=0x77 # embeddedPeak=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)] self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tup)) self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(deltas))
def test_compile_embeddedPeak_nonIntermediate_privatePoints(self): var = TupleVariation( {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7,4), (8,5), (9,6)]) tup, deltas, _ = var.compile( axisTags=["wght", "wdth"], sharedCoordIndices={}, sharedPoints=None) # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[] self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tup)) self.assertEqual("00 " # all points in glyph "02 07 08 09 " # deltaX: [7, 8, 9] "02 04 05 06", # deltaY: [4, 5, 6] hexencode(deltas))
def test_compile_embeddedPeak_intermediate_privateConstants(self): var = TupleVariation( {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)}, [7, 8, 9]) tup, deltas = var.compile(axisTags = ["wght", "wdth"]) # len(deltas)=5; # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)] self.assertEqual("00 05 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A", hexencode(tup)) self.assertEqual("00 " # all points in glyph "02 07 08 09", # delta: [7, 8, 9] hexencode(deltas))
def test_scaleDeltas_gvar(self): var = TupleVariation({}, [(100, 200), None]) var.scaleDeltas(1.0) self.assertEqual(var.coordinates, [(100, 200), None]) var.scaleDeltas(0.333) self.assertAlmostEqual(var.coordinates[0][0], 33.3) self.assertAlmostEqual(var.coordinates[0][1], 66.6) self.assertIsNone(var.coordinates[1]) var.scaleDeltas(0.0) self.assertEqual(var.coordinates, [(0, 0), None])