From 0690703b86c2e78c5a9a52291702df31a6e22ba2 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Fri, 2 Jun 2023 15:29:19 +0100 Subject: [PATCH] varLib: add --drop-implied-oncurves option For the test, I used the Tests/varLib/data/Build.designspace as starting point, modified the 'a' glyph so that 1 on-curve point (the first one) becomes impliable for all the masters. --- Lib/fontTools/varLib/__init__.py | 63 ++- Tests/varLib/data/DropOnCurves.designspace | 20 + .../TestFamily-Master1.ttx | 312 +++++++++++ .../TestFamily-Master2.ttx | 313 +++++++++++ .../varLib/data/test_results/DropOnCurves.ttx | 498 ++++++++++++++++++ Tests/varLib/varLib_test.py | 25 + 6 files changed, 1229 insertions(+), 2 deletions(-) create mode 100644 Tests/varLib/data/DropOnCurves.designspace create mode 100644 Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx create mode 100644 Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx create mode 100644 Tests/varLib/data/test_results/DropOnCurves.ttx diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 86fa8d704..2574a8ee9 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -24,7 +24,7 @@ from fontTools.misc.roundTools import noRound, otRound from fontTools.misc.textTools import Tag, tostr from fontTools.ttLib import TTFont, newTable from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance -from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates +from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates, dropImpliedOnCurvePoints from fontTools.ttLib.tables.ttProgram import Program from fontTools.ttLib.tables.TupleVariation import TupleVariation from fontTools.ttLib.tables import otTables as ot @@ -40,7 +40,7 @@ from fontTools.varLib.stat import buildVFStatTable from fontTools.colorLib.builder import buildColrV1 from fontTools.colorLib.unbuilder import unbuildColrV1 from functools import partial -from collections import OrderedDict, namedtuple +from collections import OrderedDict, defaultdict, namedtuple import os.path import logging from copy import deepcopy @@ -965,6 +965,46 @@ def set_default_weight_width_slant(font, location): font["post"].italicAngle = italicAngle +def drop_implied_oncurve_points(*masters: TTFont) -> int: + """Drop impliable on-curve points from all the simple glyphs in masters. + + In TrueType glyf outlines, on-curve points can be implied when they are located + exactly at the midpoint of the line connecting two consecutive off-curve points. + + The input masters' glyf tables are assumed to contain same-named glyphs that are + interpolatable. Oncurve points are only dropped if they can be implied for all + the masters. The fonts are modified in-place. + + Args: + masters: The TTFont(s) to modify + + Returns: + The total number of points that were dropped if any. + + Reference: + https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html + """ + + count = 0 + glyph_masters = defaultdict(list) + # multiple DS source may point to the same TTFont object and we want to + # avoid processing the same glyph twice as they are modified in-place + for font in {id(m): m for m in masters}.values(): + glyf = font["glyf"] + for glyphName in glyf.keys(): + glyph_masters[glyphName].append(glyf[glyphName]) + count = 0 + for glyphName, glyphs in glyph_masters.items(): + try: + dropped = dropImpliedOnCurvePoints(*glyphs) + except ValueError as e: + # we don't fail for incompatible glyphs in _add_gvar so we shouldn't here + log.warning("Failed to drop implied oncurves for %r: %s", glyphName, e) + else: + count += len(dropped) + return count + + def build_many( designspace: DesignSpaceDocument, master_finder=lambda s: s, @@ -972,6 +1012,7 @@ def build_many( optimize=True, skip_vf=lambda vf_name: False, colr_layer_reuse=True, + drop_implied_oncurves=False, ): """ Build variable fonts from a designspace file, version 5 which can define @@ -1015,6 +1056,7 @@ def build_many( exclude=exclude, optimize=optimize, colr_layer_reuse=colr_layer_reuse, + drop_implied_oncurves=drop_implied_oncurves, )[0] if doBuildStatFromDSv5: buildVFStatTable(vf, designspace, name) @@ -1028,6 +1070,7 @@ def build( exclude=[], optimize=True, colr_layer_reuse=True, + drop_implied_oncurves=False, ): """ Build variation font from a designspace file. @@ -1055,6 +1098,13 @@ def build( except AttributeError: master_ttfs.append(None) # in-memory fonts have no path + if drop_implied_oncurves and "glyf" in master_fonts[ds.base_idx]: + drop_count = drop_implied_oncurve_points(*master_fonts) + log.info( + "Dropped %s on-curve points from simple glyphs in the 'glyf' table", + drop_count, + ) + # Copy the base master to work from it vf = deepcopy(master_fonts[ds.base_idx]) @@ -1228,6 +1278,14 @@ def main(args=None): action="store_false", help="do not rebuild variable COLR table to optimize COLR layer reuse", ) + parser.add_argument( + "--drop-implied-oncurves", + action="store_true", + help=( + "drop on-curve points that can be implied when exactly in the middle of " + "two off-curve points (only applies to TrueType fonts)" + ), + ) parser.add_argument( "--master-finder", default="master_ttf_interpolatable/{stem}.ttf", @@ -1312,6 +1370,7 @@ def main(args=None): exclude=options.exclude, optimize=options.optimize, colr_layer_reuse=options.colr_layer_reuse, + drop_implied_oncurves=options.drop_implied_oncurves, ) for vf_name, vf in vfs.items(): diff --git a/Tests/varLib/data/DropOnCurves.designspace b/Tests/varLib/data/DropOnCurves.designspace new file mode 100644 index 000000000..a4769aa2f --- /dev/null +++ b/Tests/varLib/data/DropOnCurves.designspace @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx new file mode 100644 index 000000000..14e64a750 --- /dev/null +++ b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test Family + + + Regular + + + Version 1.001;ADBO;Test Family Regular + + + Test Family + + + Version 1.001 + + + TestFamily-Master1 + + + Frank Grießhammer + + + Master 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx new file mode 100644 index 000000000..1559071a5 --- /dev/null +++ b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Test Family + + + Regular + + + Version 1.001;ADBO;Test Family Regular + + + Test Family + + + Version 1.001 + + + TestFamily-Master2 + + + Frank Grießhammer + + + Master 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/test_results/DropOnCurves.ttx b/Tests/varLib/data/test_results/DropOnCurves.ttx new file mode 100644 index 000000000..4bfd36ad0 --- /dev/null +++ b/Tests/varLib/data/test_results/DropOnCurves.ttx @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + Test Family + + + Regular + + + Version 1.001;ADBO;Test Family Regular + + + Test Family + + + Version 1.001 + + + TestFamily-Master1 + + + Frank Grießhammer + + + Master 1 + + + Weight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 400.0 + 400.0 + 1000.0 + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py index a8f7a4574..5d5b9bbdc 100644 --- a/Tests/varLib/varLib_test.py +++ b/Tests/varLib/varLib_test.py @@ -538,6 +538,31 @@ class BuildTest(unittest.TestCase): self.assertTrue(os.path.isdir(outdir)) self.assertTrue(os.path.exists(os.path.join(outdir, "BuildMain-VF.ttf"))) + def test_varLib_main_drop_implied_oncurves(self): + self.temp_dir() + outdir = os.path.join(self.tempdir, "drop_implied_oncurves_test") + self.assertFalse(os.path.exists(outdir)) + + ttf_dir = os.path.join(outdir, "master_ttf_interpolatable") + os.makedirs(ttf_dir) + ttx_dir = self.get_test_input("master_ttx_drop_oncurves") + ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-") + for path in ttx_paths: + self.compile_font(path, ".ttf", ttf_dir) + + ds_copy = os.path.join(outdir, "DropOnCurves.designspace") + ds_path = self.get_test_input("DropOnCurves.designspace") + shutil.copy2(ds_path, ds_copy) + + finder = "%s/master_ttf_interpolatable/{stem}.ttf" % outdir + varLib_main([ds_copy, "--master-finder", finder, "--drop-implied-oncurves"]) + + vf_path = os.path.join(outdir, "DropOnCurves-VF.ttf") + varfont = TTFont(vf_path) + tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"] + expected_ttx_path = self.get_test_output("DropOnCurves.ttx") + self.expect_ttx(varfont, expected_ttx_path, tables) + def test_varLib_build_many_no_overwrite_STAT(self): # Ensure that varLib.build_many doesn't overwrite a pre-existing STAT table, # e.g. one built by feaLib from features.fea; the VF simply should inherit the