def test_decompileCoord_roundTrip(self): # Make sure we are not affected by https://github.com/behdad/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_decompilePoints_roundTrip(self): numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding compile = lambda points: TupleVariation.compilePoints(points, numPointsInGlyph) decompile = lambda data: set(TupleVariation.decompilePoints_(numPointsInGlyph, data, 0, "gvar")[0]) for i in range(50): points = set(random.sample(range(numPointsInGlyph), 30)) self.assertSetEqual(points, decompile(compile(points)), "failed round-trip decompile/compilePoints; points=%s" % points) allPoints = set(range(numPointsInGlyph)) self.assertSetEqual(allPoints, decompile(compile(allPoints)))
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_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_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_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"], sharedCoordIndices={}, sharedPoints={0, 1, 2}) # 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_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" max="0.5" min="0.3" value="0.4"/>', '<!-- bad delta #0 -->', '</tuple>', ], TupleVariationTest.xml_lines(writer))
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 _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_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"], sharedCoordIndices={}, sharedPoints=None) # 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_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"], sharedCoordIndices={}, sharedPoints={0, 1, 2}) # 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_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, sharedPoints={0,1,2}) # 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_sharedPeaks_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)]) axisTags = ["wght", "wdth"] sharedPeakIndices = { var.compileCoord(axisTags): 0x77 } tup, deltas = var.compile(axisTags, sharedPeakIndices, sharedPoints={0,1,2}) # len(deltas)=8; flags=None; tupleIndex=0x77 # embeddedPeaks=[]; intermediateCoord=[] self.assertEqual("00 08 00 77", 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_sharedPeaks_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)]) axisTags = ["wght", "wdth"] sharedPeakIndices = { var.compileCoord(axisTags): 0x77 } tup, deltas, _ = var.compile(axisTags, sharedPeakIndices, sharedPoints=None) # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77 # embeddedPeak=[]; intermediateCoord=[] self.assertEqual("00 09 20 77", 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_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_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"], sharedCoordIndices={}, sharedPoints=None) # 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_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_decompileDeltas_roundTrip(self): numDeltas = 30 compile = TupleVariation.compileDeltaValues_ decompile = lambda data: TupleVariation.decompileDeltas_(numDeltas, data, 0)[0] for i in range(50): deltas = random.sample(range(-128, 127), 10) deltas.extend(random.sample(range(-32768, 32767), 10)) deltas.extend([0] * 10) random.shuffle(deltas) self.assertListEqual(deltas, decompile(compile(deltas)))
def test_compileDeltaValues(self): compileDeltaValues = lambda values: hexencode(TupleVariation.compileDeltaValues_(values)) # zeroes self.assertEqual("80", compileDeltaValues([0])) self.assertEqual("BF", compileDeltaValues([0] * 64)) self.assertEqual("BF 80", compileDeltaValues([0] * 65)) self.assertEqual("BF A3", compileDeltaValues([0] * 100)) self.assertEqual("BF BF BF BF", compileDeltaValues([0] * 256)) # bytes self.assertEqual("00 01", compileDeltaValues([1])) self.assertEqual("06 01 02 03 7F 80 FF FE", compileDeltaValues([1, 2, 3, 127, -128, -1, -2])) self.assertEqual("3F" + (64 * " 7F"), compileDeltaValues([127] * 64)) self.assertEqual("3F" + (64 * " 7F") + " 00 7F", compileDeltaValues([127] * 65)) # words self.assertEqual("40 66 66", compileDeltaValues([0x6666])) self.assertEqual("43 66 66 7F FF FF FF 80 00", compileDeltaValues([0x6666, 32767, -1, -32768])) self.assertEqual("7F" + (64 * " 11 22"), compileDeltaValues([0x1122] * 64)) self.assertEqual("7F" + (64 * " 11 22") + " 40 11 22", compileDeltaValues([0x1122] * 65)) # bytes, zeroes, bytes: a single zero is more compact when encoded as part of the bytes run self.assertEqual("04 7F 7F 00 7F 7F", compileDeltaValues([127, 127, 0, 127, 127])) self.assertEqual("01 7F 7F 81 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 127, 127])) self.assertEqual("01 7F 7F 82 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 127, 127])) self.assertEqual("01 7F 7F 83 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 0, 127, 127])) # bytes, zeroes self.assertEqual("01 01 00", compileDeltaValues([1, 0])) self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0])) # words, bytes, words: a single byte is more compact when encoded as part of the words run self.assertEqual("42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777])) self.assertEqual("40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777])) # words, zeroes, words self.assertEqual("40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777])) self.assertEqual("40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777])) self.assertEqual("40 66 66 82 40 77 77", compileDeltaValues([0x6666, 0, 0, 0, 0x7777])) # words, zeroes, bytes self.assertEqual("40 66 66 80 02 01 02 03", compileDeltaValues([0x6666, 0, 1, 2, 3])) self.assertEqual("40 66 66 81 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 1, 2, 3])) self.assertEqual("40 66 66 82 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 0, 1, 2, 3])) # words, zeroes self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0])) self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0])) # bytes or words from floats self.assertEqual("00 01", compileDeltaValues([1.1])) self.assertEqual("00 02", compileDeltaValues([1.9])) self.assertEqual("40 66 66", compileDeltaValues([0x6666 + 0.1])) self.assertEqual("40 66 66", compileDeltaValues([0x6665 + 0.9]))
def test_compilePoints(self): compilePoints = lambda p: TupleVariation.compilePoints(set(p), numPointsInGlyph=999) self.assertEqual("00", hexencode(compilePoints(range(999)))) # all points in glyph self.assertEqual("01 00 07", hexencode(compilePoints([7]))) self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535]))) self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15]))) self.assertEqual("06 05 07 01 F7 02 01 F2", hexencode(compilePoints([7, 8, 255, 257, 258, 500]))) self.assertEqual("03 01 07 01 80 01 EC", hexencode(compilePoints([7, 8, 500]))) self.assertEqual("04 01 07 01 81 BE E7 0C 0F", hexencode(compilePoints([7, 8, 0xBEEF, 0xCAFE]))) self.maxDiff = None self.assertEqual("81 2C" + # 300 points (0x12c) in total " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127] " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255] " 2B" + (44 * " 01"), # third run, contains 44 points: [256 .. 299] hexencode(compilePoints(range(300)))) self.assertEqual("81 8F" + # 399 points (0x18f) in total " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127] " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255] " 7F" + (128 * " 01") + # third run, contains 128 points: [256 .. 383] " 0E" + (15 * " 01"), # fourth run, contains 15 points: [384 .. 398] hexencode(compilePoints(range(399))))
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_compileDeltas_constants(self): var = TupleVariation({}, [0, 1, 2, None, 4, 5]) cvts = {1, 2, 3, 4} # delta for cvts: [1, 2, 4] self.assertEqual("02 01 02 04", hexencode(var.compileDeltas(cvts)))
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 _add_gvar(font, masterModel, 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] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if not models.allEqual(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 decompilePoints(data, offset): points, offset = TupleVariation.decompilePoints_(numPointsInGlyph, deHexStr(data), offset, "gvar") # Conversion to list needed for Python 3. return (list(points), offset)
def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5): log.info("Merging TT hinting") assert "cvar" not in font # Check that the existing hinting is compatible # fpgm and prep table for tag in ("fpgm", "prep"): all_pgms = [m[tag].program for m in master_ttfs if tag in m] if len(all_pgms) == 0: continue if tag in font: font_pgm = font[tag].program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms): log.warning( "Masters have incompatible %s tables, hinting is discarded." % tag) _remove_TTHinting(font) return # glyf table for name, glyph in font["glyf"].glyphs.items(): all_pgms = [ m["glyf"][name].program for m in master_ttfs if name in m['glyf'] and hasattr(m["glyf"][name], "program") ] if not any(all_pgms): continue glyph.expand(font["glyf"]) if hasattr(glyph, "program"): font_pgm = glyph.program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms if pgm): log.warning( "Masters have incompatible glyph programs in glyph '%s', hinting is discarded." % name) # TODO Only drop hinting from this glyph. _remove_TTHinting(font) return # cvt table all_cvs = [ Vector(m["cvt "].values) if 'cvt ' in m else None for m in master_ttfs ] nonNone_cvs = models.nonNone(all_cvs) if not nonNone_cvs: # There is no cvt table to make a cvar table from, we're done here. return if not models.allEqual(len(c) for c in nonNone_cvs): log.warning( "Masters have incompatible cvt tables, hinting is discarded.") _remove_TTHinting(font) return # We can build the cvar table now. cvar = font["cvar"] = newTable('cvar') cvar.version = 1 cvar.variations = [] deltas, supports = masterModel.getDeltasAndSupports(all_cvs) for i, (delta, support) in enumerate(zip(deltas[1:], supports[1:])): delta = [otRound(d) for d in delta] if all(abs(v) <= tolerance for v in delta): continue var = TupleVariation(support, delta) cvar.variations.append(var)
def test_hasImpact_allDeltasZero(self): axes = {"wght":(0.0, 1.0, 1.0)} var = TupleVariation(axes, [(0,0), (0,0), (0,0)]) self.assertTrue(var.hasImpact())
def _add_gvar(font, masterModel, 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 = {} glyf = font['glyf'] # use hhea.ascent of base master as default vertical origin when vmtx is missing defaultVerticalOrigin = font['hhea'].ascent for glyph in font.getGlyphOrder(): isComposite = glyf[glyph].isComposite() allData = [ _GetCoordinates(m, glyph, defaultVerticalOrigin=defaultVerticalOrigin) for m in master_ttfs ] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if not models.allEqual(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) and not isComposite: continue var = TupleVariation(support, delta) if optimize: delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance) if None in delta_opt: """In composite glyphs, there should be one 0 entry to make sure the gvar entry is written to the font. This is to work around an issue with macOS 10.14 and can be removed once the behaviour of macOS is changed. https://github.com/fonttools/fonttools/issues/1381 """ if all(d is None for d in delta_opt): delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1) # 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 _add_gvar(font, masterModel, 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 = {} glyf = font['glyf'] for glyph in font.getGlyphOrder(): isComposite = glyf[glyph].isComposite() allData = [_GetCoordinates(m, glyph) for m in master_ttfs] model, allData = masterModel.getSubModel(allData) allCoords = [d[0] for d in allData] allControls = [d[1] for d in allData] control = allControls[0] if not models.allEqual(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) and not isComposite: continue var = TupleVariation(support, delta) if optimize: delta_opt = iup_delta_optimize(delta, origCoords, endPts, tolerance=tolerance) if None in delta_opt: """In composite glyphs, there should be one 0 entry to make sure the gvar entry is written to the font. This is to work around an issue with macOS 10.14 and can be removed once the behaviour of macOS is changed. https://github.com/fonttools/fonttools/issues/1381 """ if all(d is None for d in delta_opt): delta_opt = [(0, 0)] + [None] * (len(delta_opt) - 1) # 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_hasImpact_someDeltasNotZero(self): axes = {"wght":(0.0, 1.0, 1.0)} var = TupleVariation(axes, [(0,0), (9,8), (7,6)]) self.assertTrue(var.hasImpact())
def test_compileIntermediateCoord(self): var = TupleVariation({"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4) self.assertEqual("C0 00 19 9A 00 00 26 66", hexencode(var.compileIntermediateCoord(["wght", "wdth"]))) self.assertEqual("19 9A C0 00 26 66 00 00", hexencode(var.compileIntermediateCoord(["wdth", "wght"]))) self.assertEqual(None, var.compileIntermediateCoord(["wght"])) self.assertEqual("19 9A 26 66", hexencode(var.compileIntermediateCoord(["wdth"])))
def test_hasImpact_allDeltasNone(self): axes = {"wght":(0.0, 1.0, 1.0)} var = TupleVariation(axes, [None, None, None]) self.assertFalse(var.hasImpact())
def test_build_var(tmpdir): outPath = os.path.join(str(tmpdir), "test_var.ttf") fb, advanceWidths, nameStrings = _setupFontBuilder(True) pen = TTGlyphPen(None) pen.moveTo((100, 0)) pen.lineTo((100, 400)) pen.lineTo((500, 400)) pen.lineTo((500, 000)) pen.closePath() glyph = pen.glyph() pen = TTGlyphPen(None) emptyGlyph = pen.glyph() glyphs = { ".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph } fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) axes = [ ('LEFT', 0, 0, 100, "Left"), ('RGHT', 0, 0, 100, "Right"), ('UPPP', 0, 0, 100, "Up"), ('DOWN', 0, 0, 100, "Down"), ] instances = [ dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"), dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"), ] fb.setupFvar(axes, instances) variations = {} # Four (x, y) pairs and four phantom points: leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None] rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None] upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None] downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None] variations['a'] = [ TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas), TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas), TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas), TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas), ] fb.setupGvar(variations) fb.setupOS2() fb.setupPost() fb.setupDummyDSIG() fb.save(outPath) _verifyOutput(outPath)