xref: /aosp_15_r20/external/fonttools/Tests/varLib/varLib_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesfrom fontTools.colorLib.builder import buildCOLR
2*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont, newTable
3*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otTables as ot
4*e1fe3e4aSElliott Hughesfrom fontTools.varLib import (
5*e1fe3e4aSElliott Hughes    build,
6*e1fe3e4aSElliott Hughes    build_many,
7*e1fe3e4aSElliott Hughes    load_designspace,
8*e1fe3e4aSElliott Hughes    _add_COLR,
9*e1fe3e4aSElliott Hughes    addGSUBFeatureVariations,
10*e1fe3e4aSElliott Hughes)
11*e1fe3e4aSElliott Hughesfrom fontTools.varLib.errors import VarLibValidationError
12*e1fe3e4aSElliott Hughesimport fontTools.varLib.errors as varLibErrors
13*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import VariationModel
14*e1fe3e4aSElliott Hughesfrom fontTools.varLib.mutator import instantiateVariableFont
15*e1fe3e4aSElliott Hughesfrom fontTools.varLib import main as varLib_main, load_masters
16*e1fe3e4aSElliott Hughesfrom fontTools.varLib import set_default_weight_width_slant
17*e1fe3e4aSElliott Hughesfrom fontTools.designspaceLib import (
18*e1fe3e4aSElliott Hughes    DesignSpaceDocumentError,
19*e1fe3e4aSElliott Hughes    DesignSpaceDocument,
20*e1fe3e4aSElliott Hughes    SourceDescriptor,
21*e1fe3e4aSElliott Hughes)
22*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.builder import addOpenTypeFeaturesFromString
23*e1fe3e4aSElliott Hughesimport difflib
24*e1fe3e4aSElliott Hughesfrom copy import deepcopy
25*e1fe3e4aSElliott Hughesfrom io import BytesIO
26*e1fe3e4aSElliott Hughesimport os
27*e1fe3e4aSElliott Hughesimport shutil
28*e1fe3e4aSElliott Hughesimport sys
29*e1fe3e4aSElliott Hughesimport tempfile
30*e1fe3e4aSElliott Hughesimport unittest
31*e1fe3e4aSElliott Hughesimport pytest
32*e1fe3e4aSElliott Hughes
33*e1fe3e4aSElliott Hughes
34*e1fe3e4aSElliott Hughesdef reload_font(font):
35*e1fe3e4aSElliott Hughes    """(De)serialize to get final binary layout."""
36*e1fe3e4aSElliott Hughes    buf = BytesIO()
37*e1fe3e4aSElliott Hughes    font.save(buf)
38*e1fe3e4aSElliott Hughes    # Close the font to release filesystem resources so that on Windows the tearDown
39*e1fe3e4aSElliott Hughes    # method can successfully remove the temporary directory created during setUp.
40*e1fe3e4aSElliott Hughes    font.close()
41*e1fe3e4aSElliott Hughes    buf.seek(0)
42*e1fe3e4aSElliott Hughes    return TTFont(buf)
43*e1fe3e4aSElliott Hughes
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughesclass BuildTest(unittest.TestCase):
46*e1fe3e4aSElliott Hughes    def __init__(self, methodName):
47*e1fe3e4aSElliott Hughes        unittest.TestCase.__init__(self, methodName)
48*e1fe3e4aSElliott Hughes        # Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
49*e1fe3e4aSElliott Hughes        # and fires deprecation warnings if a program uses the old name.
50*e1fe3e4aSElliott Hughes        if not hasattr(self, "assertRaisesRegex"):
51*e1fe3e4aSElliott Hughes            self.assertRaisesRegex = self.assertRaisesRegexp
52*e1fe3e4aSElliott Hughes
53*e1fe3e4aSElliott Hughes    def setUp(self):
54*e1fe3e4aSElliott Hughes        self.tempdir = None
55*e1fe3e4aSElliott Hughes        self.num_tempfiles = 0
56*e1fe3e4aSElliott Hughes
57*e1fe3e4aSElliott Hughes    def tearDown(self):
58*e1fe3e4aSElliott Hughes        if self.tempdir:
59*e1fe3e4aSElliott Hughes            shutil.rmtree(self.tempdir)
60*e1fe3e4aSElliott Hughes
61*e1fe3e4aSElliott Hughes    def get_test_input(self, test_file_or_folder, copy=False):
62*e1fe3e4aSElliott Hughes        parent_dir = os.path.dirname(__file__)
63*e1fe3e4aSElliott Hughes        path = os.path.join(parent_dir, "data", test_file_or_folder)
64*e1fe3e4aSElliott Hughes        if copy:
65*e1fe3e4aSElliott Hughes            copied_path = os.path.join(self.tempdir, test_file_or_folder)
66*e1fe3e4aSElliott Hughes            shutil.copy2(path, copied_path)
67*e1fe3e4aSElliott Hughes            return copied_path
68*e1fe3e4aSElliott Hughes        else:
69*e1fe3e4aSElliott Hughes            return path
70*e1fe3e4aSElliott Hughes
71*e1fe3e4aSElliott Hughes    @staticmethod
72*e1fe3e4aSElliott Hughes    def get_test_output(test_file_or_folder):
73*e1fe3e4aSElliott Hughes        path, _ = os.path.split(__file__)
74*e1fe3e4aSElliott Hughes        return os.path.join(path, "data", "test_results", test_file_or_folder)
75*e1fe3e4aSElliott Hughes
76*e1fe3e4aSElliott Hughes    @staticmethod
77*e1fe3e4aSElliott Hughes    def get_file_list(folder, suffix, prefix=""):
78*e1fe3e4aSElliott Hughes        all_files = os.listdir(folder)
79*e1fe3e4aSElliott Hughes        file_list = []
80*e1fe3e4aSElliott Hughes        for p in all_files:
81*e1fe3e4aSElliott Hughes            if p.startswith(prefix) and p.endswith(suffix):
82*e1fe3e4aSElliott Hughes                file_list.append(os.path.abspath(os.path.join(folder, p)))
83*e1fe3e4aSElliott Hughes        return file_list
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughes    def temp_path(self, suffix):
86*e1fe3e4aSElliott Hughes        self.temp_dir()
87*e1fe3e4aSElliott Hughes        self.num_tempfiles += 1
88*e1fe3e4aSElliott Hughes        return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
89*e1fe3e4aSElliott Hughes
90*e1fe3e4aSElliott Hughes    def temp_dir(self):
91*e1fe3e4aSElliott Hughes        if not self.tempdir:
92*e1fe3e4aSElliott Hughes            self.tempdir = tempfile.mkdtemp()
93*e1fe3e4aSElliott Hughes
94*e1fe3e4aSElliott Hughes    def read_ttx(self, path):
95*e1fe3e4aSElliott Hughes        lines = []
96*e1fe3e4aSElliott Hughes        with open(path, "r", encoding="utf-8") as ttx:
97*e1fe3e4aSElliott Hughes            for line in ttx.readlines():
98*e1fe3e4aSElliott Hughes                # Elide ttFont attributes because ttLibVersion may change.
99*e1fe3e4aSElliott Hughes                if line.startswith("<ttFont "):
100*e1fe3e4aSElliott Hughes                    lines.append("<ttFont>\n")
101*e1fe3e4aSElliott Hughes                else:
102*e1fe3e4aSElliott Hughes                    lines.append(line.rstrip() + "\n")
103*e1fe3e4aSElliott Hughes        return lines
104*e1fe3e4aSElliott Hughes
105*e1fe3e4aSElliott Hughes    def expect_ttx(self, font, expected_ttx, tables):
106*e1fe3e4aSElliott Hughes        path = self.temp_path(suffix=".ttx")
107*e1fe3e4aSElliott Hughes        font.saveXML(path, tables=tables)
108*e1fe3e4aSElliott Hughes        actual = self.read_ttx(path)
109*e1fe3e4aSElliott Hughes        expected = self.read_ttx(expected_ttx)
110*e1fe3e4aSElliott Hughes        if actual != expected:
111*e1fe3e4aSElliott Hughes            for line in difflib.unified_diff(
112*e1fe3e4aSElliott Hughes                expected, actual, fromfile=expected_ttx, tofile=path
113*e1fe3e4aSElliott Hughes            ):
114*e1fe3e4aSElliott Hughes                sys.stdout.write(line)
115*e1fe3e4aSElliott Hughes            self.fail("TTX output is different from expected")
116*e1fe3e4aSElliott Hughes
117*e1fe3e4aSElliott Hughes    def check_ttx_dump(self, font, expected_ttx, tables, suffix):
118*e1fe3e4aSElliott Hughes        """Ensure the TTX dump is the same after saving and reloading the font."""
119*e1fe3e4aSElliott Hughes        path = self.temp_path(suffix=suffix)
120*e1fe3e4aSElliott Hughes        font.save(path)
121*e1fe3e4aSElliott Hughes        self.expect_ttx(TTFont(path), expected_ttx, tables)
122*e1fe3e4aSElliott Hughes
123*e1fe3e4aSElliott Hughes    def compile_font(self, path, suffix, temp_dir):
124*e1fe3e4aSElliott Hughes        ttx_filename = os.path.basename(path)
125*e1fe3e4aSElliott Hughes        savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
126*e1fe3e4aSElliott Hughes        font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
127*e1fe3e4aSElliott Hughes        font.importXML(path)
128*e1fe3e4aSElliott Hughes        font.save(savepath, reorderTables=None)
129*e1fe3e4aSElliott Hughes        return font, savepath
130*e1fe3e4aSElliott Hughes
131*e1fe3e4aSElliott Hughes    def _run_varlib_build_test(
132*e1fe3e4aSElliott Hughes        self,
133*e1fe3e4aSElliott Hughes        designspace_name,
134*e1fe3e4aSElliott Hughes        font_name,
135*e1fe3e4aSElliott Hughes        tables,
136*e1fe3e4aSElliott Hughes        expected_ttx_name,
137*e1fe3e4aSElliott Hughes        save_before_dump=False,
138*e1fe3e4aSElliott Hughes        post_process_master=None,
139*e1fe3e4aSElliott Hughes    ):
140*e1fe3e4aSElliott Hughes        suffix = ".ttf"
141*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input(designspace_name + ".designspace")
142*e1fe3e4aSElliott Hughes        ufo_dir = self.get_test_input("master_ufo")
143*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
144*e1fe3e4aSElliott Hughes
145*e1fe3e4aSElliott Hughes        self.temp_dir()
146*e1fe3e4aSElliott Hughes        ttx_paths = self.get_file_list(ttx_dir, ".ttx", font_name + "-")
147*e1fe3e4aSElliott Hughes        for path in ttx_paths:
148*e1fe3e4aSElliott Hughes            font, savepath = self.compile_font(path, suffix, self.tempdir)
149*e1fe3e4aSElliott Hughes            if post_process_master is not None:
150*e1fe3e4aSElliott Hughes                post_process_master(font, savepath)
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes        finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
153*e1fe3e4aSElliott Hughes        varfont, model, _ = build(ds_path, finder)
154*e1fe3e4aSElliott Hughes
155*e1fe3e4aSElliott Hughes        if save_before_dump:
156*e1fe3e4aSElliott Hughes            # some data (e.g. counts printed in TTX inline comments) is only
157*e1fe3e4aSElliott Hughes            # calculated at compile time, so before we can compare the TTX
158*e1fe3e4aSElliott Hughes            # dumps we need to save to a temporary stream, and realod the font
159*e1fe3e4aSElliott Hughes            varfont = reload_font(varfont)
160*e1fe3e4aSElliott Hughes
161*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx")
162*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
163*e1fe3e4aSElliott Hughes        self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
164*e1fe3e4aSElliott Hughes
165*e1fe3e4aSElliott Hughes    # -----
166*e1fe3e4aSElliott Hughes    # Tests
167*e1fe3e4aSElliott Hughes    # -----
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughes    def test_varlib_build_ttf(self):
170*e1fe3e4aSElliott Hughes        """Designspace file contains <axes> element."""
171*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
172*e1fe3e4aSElliott Hughes            designspace_name="Build",
173*e1fe3e4aSElliott Hughes            font_name="TestFamily",
174*e1fe3e4aSElliott Hughes            tables=["GDEF", "HVAR", "MVAR", "fvar", "gvar"],
175*e1fe3e4aSElliott Hughes            expected_ttx_name="Build",
176*e1fe3e4aSElliott Hughes        )
177*e1fe3e4aSElliott Hughes
178*e1fe3e4aSElliott Hughes    def test_varlib_build_no_axes_ttf(self):
179*e1fe3e4aSElliott Hughes        """Designspace file does not contain an <axes> element."""
180*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("InterpolateLayout3.designspace")
181*e1fe3e4aSElliott Hughes        with self.assertRaisesRegex(DesignSpaceDocumentError, "No axes defined"):
182*e1fe3e4aSElliott Hughes            build(ds_path)
183*e1fe3e4aSElliott Hughes
184*e1fe3e4aSElliott Hughes    def test_varlib_avar_single_axis(self):
185*e1fe3e4aSElliott Hughes        """Designspace file contains a 'weight' axis with <map> elements
186*e1fe3e4aSElliott Hughes        modifying the normalization mapping. An 'avar' table is generated.
187*e1fe3e4aSElliott Hughes        """
188*e1fe3e4aSElliott Hughes        test_name = "BuildAvarSingleAxis"
189*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
190*e1fe3e4aSElliott Hughes            designspace_name=test_name,
191*e1fe3e4aSElliott Hughes            font_name="TestFamily3",
192*e1fe3e4aSElliott Hughes            tables=["avar"],
193*e1fe3e4aSElliott Hughes            expected_ttx_name=test_name,
194*e1fe3e4aSElliott Hughes        )
195*e1fe3e4aSElliott Hughes
196*e1fe3e4aSElliott Hughes    def test_varlib_avar_with_identity_maps(self):
197*e1fe3e4aSElliott Hughes        """Designspace file contains two 'weight' and 'width' axes both with
198*e1fe3e4aSElliott Hughes        <map> elements.
199*e1fe3e4aSElliott Hughes
200*e1fe3e4aSElliott Hughes        The 'width' axis only contains identity mappings, however the resulting
201*e1fe3e4aSElliott Hughes        avar segment will not be empty but will contain the default axis value
202*e1fe3e4aSElliott Hughes        maps: {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}.
203*e1fe3e4aSElliott Hughes
204*e1fe3e4aSElliott Hughes        This is to work around an issue with some rasterizers:
205*e1fe3e4aSElliott Hughes        https://github.com/googlei18n/fontmake/issues/295
206*e1fe3e4aSElliott Hughes        https://github.com/fonttools/fonttools/issues/1011
207*e1fe3e4aSElliott Hughes        """
208*e1fe3e4aSElliott Hughes        test_name = "BuildAvarIdentityMaps"
209*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
210*e1fe3e4aSElliott Hughes            designspace_name=test_name,
211*e1fe3e4aSElliott Hughes            font_name="TestFamily3",
212*e1fe3e4aSElliott Hughes            tables=["avar"],
213*e1fe3e4aSElliott Hughes            expected_ttx_name=test_name,
214*e1fe3e4aSElliott Hughes        )
215*e1fe3e4aSElliott Hughes
216*e1fe3e4aSElliott Hughes    def test_varlib_avar_empty_axis(self):
217*e1fe3e4aSElliott Hughes        """Designspace file contains two 'weight' and 'width' axes, but
218*e1fe3e4aSElliott Hughes        only one axis ('weight') has some <map> elements.
219*e1fe3e4aSElliott Hughes
220*e1fe3e4aSElliott Hughes        Even if no <map> elements are defined for the 'width' axis, the
221*e1fe3e4aSElliott Hughes        resulting avar segment still contains the default axis value maps:
222*e1fe3e4aSElliott Hughes        {-1.0: -1.0, 0.0: 0.0, 1.0: 1.0}.
223*e1fe3e4aSElliott Hughes
224*e1fe3e4aSElliott Hughes        This is again to work around an issue with some rasterizers:
225*e1fe3e4aSElliott Hughes        https://github.com/googlei18n/fontmake/issues/295
226*e1fe3e4aSElliott Hughes        https://github.com/fonttools/fonttools/issues/1011
227*e1fe3e4aSElliott Hughes        """
228*e1fe3e4aSElliott Hughes        test_name = "BuildAvarEmptyAxis"
229*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
230*e1fe3e4aSElliott Hughes            designspace_name=test_name,
231*e1fe3e4aSElliott Hughes            font_name="TestFamily3",
232*e1fe3e4aSElliott Hughes            tables=["avar"],
233*e1fe3e4aSElliott Hughes            expected_ttx_name=test_name,
234*e1fe3e4aSElliott Hughes        )
235*e1fe3e4aSElliott Hughes
236*e1fe3e4aSElliott Hughes    def test_varlib_avar2(self):
237*e1fe3e4aSElliott Hughes        """Designspace file contains a 'weight' axis with <map> elements
238*e1fe3e4aSElliott Hughes        modifying the normalization mapping as well as <mappings> element
239*e1fe3e4aSElliott Hughes        modifying it post-normalization. An 'avar' table is generated.
240*e1fe3e4aSElliott Hughes        """
241*e1fe3e4aSElliott Hughes        test_name = "BuildAvar2"
242*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
243*e1fe3e4aSElliott Hughes            designspace_name=test_name,
244*e1fe3e4aSElliott Hughes            font_name="TestFamily3",
245*e1fe3e4aSElliott Hughes            tables=["avar"],
246*e1fe3e4aSElliott Hughes            expected_ttx_name=test_name,
247*e1fe3e4aSElliott Hughes        )
248*e1fe3e4aSElliott Hughes
249*e1fe3e4aSElliott Hughes    def test_varlib_build_feature_variations(self):
250*e1fe3e4aSElliott Hughes        """Designspace file contains <rules> element, used to build
251*e1fe3e4aSElliott Hughes        GSUB FeatureVariations table.
252*e1fe3e4aSElliott Hughes        """
253*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
254*e1fe3e4aSElliott Hughes            designspace_name="FeatureVars",
255*e1fe3e4aSElliott Hughes            font_name="TestFamily",
256*e1fe3e4aSElliott Hughes            tables=["fvar", "GSUB"],
257*e1fe3e4aSElliott Hughes            expected_ttx_name="FeatureVars",
258*e1fe3e4aSElliott Hughes            save_before_dump=True,
259*e1fe3e4aSElliott Hughes        )
260*e1fe3e4aSElliott Hughes
261*e1fe3e4aSElliott Hughes    def test_varlib_build_feature_variations_custom_tag(self):
262*e1fe3e4aSElliott Hughes        """Designspace file contains <rules> element, used to build
263*e1fe3e4aSElliott Hughes        GSUB FeatureVariations table.
264*e1fe3e4aSElliott Hughes        """
265*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
266*e1fe3e4aSElliott Hughes            designspace_name="FeatureVarsCustomTag",
267*e1fe3e4aSElliott Hughes            font_name="TestFamily",
268*e1fe3e4aSElliott Hughes            tables=["fvar", "GSUB"],
269*e1fe3e4aSElliott Hughes            expected_ttx_name="FeatureVarsCustomTag",
270*e1fe3e4aSElliott Hughes            save_before_dump=True,
271*e1fe3e4aSElliott Hughes        )
272*e1fe3e4aSElliott Hughes
273*e1fe3e4aSElliott Hughes    def test_varlib_build_feature_variations_whole_range(self):
274*e1fe3e4aSElliott Hughes        """Designspace file contains <rules> element specifying the entire design
275*e1fe3e4aSElliott Hughes        space, used to build GSUB FeatureVariations table.
276*e1fe3e4aSElliott Hughes        """
277*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
278*e1fe3e4aSElliott Hughes            designspace_name="FeatureVarsWholeRange",
279*e1fe3e4aSElliott Hughes            font_name="TestFamily",
280*e1fe3e4aSElliott Hughes            tables=["fvar", "GSUB"],
281*e1fe3e4aSElliott Hughes            expected_ttx_name="FeatureVarsWholeRange",
282*e1fe3e4aSElliott Hughes            save_before_dump=True,
283*e1fe3e4aSElliott Hughes        )
284*e1fe3e4aSElliott Hughes
285*e1fe3e4aSElliott Hughes    def test_varlib_build_feature_variations_whole_range_empty(self):
286*e1fe3e4aSElliott Hughes        """Designspace file contains <rules> element without a condition, specifying
287*e1fe3e4aSElliott Hughes        the entire design space, used to build GSUB FeatureVariations table.
288*e1fe3e4aSElliott Hughes        """
289*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
290*e1fe3e4aSElliott Hughes            designspace_name="FeatureVarsWholeRangeEmpty",
291*e1fe3e4aSElliott Hughes            font_name="TestFamily",
292*e1fe3e4aSElliott Hughes            tables=["fvar", "GSUB"],
293*e1fe3e4aSElliott Hughes            expected_ttx_name="FeatureVarsWholeRange",
294*e1fe3e4aSElliott Hughes            save_before_dump=True,
295*e1fe3e4aSElliott Hughes        )
296*e1fe3e4aSElliott Hughes
297*e1fe3e4aSElliott Hughes    def test_varlib_build_feature_variations_with_existing_rclt(self):
298*e1fe3e4aSElliott Hughes        """Designspace file contains <rules> element, used to build GSUB
299*e1fe3e4aSElliott Hughes        FeatureVariations table. <rules> is specified to do its OT processing
300*e1fe3e4aSElliott Hughes        "last", so a 'rclt' feature will be used or created. This test covers
301*e1fe3e4aSElliott Hughes        the case when a 'rclt' already exists in the masters.
302*e1fe3e4aSElliott Hughes
303*e1fe3e4aSElliott Hughes        We dynamically add a 'rclt' feature to an existing set of test
304*e1fe3e4aSElliott Hughes        masters, to avoid adding more test data.
305*e1fe3e4aSElliott Hughes
306*e1fe3e4aSElliott Hughes        The multiple languages are done to verify whether multiple existing
307*e1fe3e4aSElliott Hughes        'rclt' features are updated correctly.
308*e1fe3e4aSElliott Hughes        """
309*e1fe3e4aSElliott Hughes
310*e1fe3e4aSElliott Hughes        def add_rclt(font, savepath):
311*e1fe3e4aSElliott Hughes            features = """
312*e1fe3e4aSElliott Hughes            languagesystem DFLT dflt;
313*e1fe3e4aSElliott Hughes            languagesystem latn dflt;
314*e1fe3e4aSElliott Hughes            languagesystem latn NLD;
315*e1fe3e4aSElliott Hughes
316*e1fe3e4aSElliott Hughes            feature rclt {
317*e1fe3e4aSElliott Hughes                script latn;
318*e1fe3e4aSElliott Hughes                language NLD;
319*e1fe3e4aSElliott Hughes                lookup A {
320*e1fe3e4aSElliott Hughes                    sub uni0041 by uni0061;
321*e1fe3e4aSElliott Hughes                } A;
322*e1fe3e4aSElliott Hughes                language dflt;
323*e1fe3e4aSElliott Hughes                lookup B {
324*e1fe3e4aSElliott Hughes                    sub uni0041 by uni0061;
325*e1fe3e4aSElliott Hughes                } B;
326*e1fe3e4aSElliott Hughes            } rclt;
327*e1fe3e4aSElliott Hughes            """
328*e1fe3e4aSElliott Hughes            addOpenTypeFeaturesFromString(font, features)
329*e1fe3e4aSElliott Hughes            font.save(savepath)
330*e1fe3e4aSElliott Hughes
331*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
332*e1fe3e4aSElliott Hughes            designspace_name="FeatureVars",
333*e1fe3e4aSElliott Hughes            font_name="TestFamily",
334*e1fe3e4aSElliott Hughes            tables=["fvar", "GSUB"],
335*e1fe3e4aSElliott Hughes            expected_ttx_name="FeatureVars_rclt",
336*e1fe3e4aSElliott Hughes            save_before_dump=True,
337*e1fe3e4aSElliott Hughes            post_process_master=add_rclt,
338*e1fe3e4aSElliott Hughes        )
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughes    def test_varlib_gvar_explicit_delta(self):
341*e1fe3e4aSElliott Hughes        """The variable font contains a composite glyph odieresis which does not
342*e1fe3e4aSElliott Hughes        need a gvar entry, because all its deltas are 0, but it must be added
343*e1fe3e4aSElliott Hughes        anyway to work around an issue with macOS 10.14.
344*e1fe3e4aSElliott Hughes
345*e1fe3e4aSElliott Hughes        https://github.com/fonttools/fonttools/issues/1381
346*e1fe3e4aSElliott Hughes        """
347*e1fe3e4aSElliott Hughes        test_name = "BuildGvarCompositeExplicitDelta"
348*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
349*e1fe3e4aSElliott Hughes            designspace_name=test_name,
350*e1fe3e4aSElliott Hughes            font_name="TestFamily4",
351*e1fe3e4aSElliott Hughes            tables=["gvar"],
352*e1fe3e4aSElliott Hughes            expected_ttx_name=test_name,
353*e1fe3e4aSElliott Hughes        )
354*e1fe3e4aSElliott Hughes
355*e1fe3e4aSElliott Hughes    def test_varlib_nonmarking_CFF2(self):
356*e1fe3e4aSElliott Hughes        self.temp_dir()
357*e1fe3e4aSElliott Hughes
358*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("TestNonMarkingCFF2.designspace", copy=True)
359*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_non_marking_cff2")
360*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("TestNonMarkingCFF2.ttx")
361*e1fe3e4aSElliott Hughes
362*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestNonMarkingCFF2_"):
363*e1fe3e4aSElliott Hughes            self.compile_font(path, ".otf", self.tempdir)
364*e1fe3e4aSElliott Hughes
365*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
366*e1fe3e4aSElliott Hughes        for source in ds.sources:
367*e1fe3e4aSElliott Hughes            source.path = os.path.join(
368*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
369*e1fe3e4aSElliott Hughes            )
370*e1fe3e4aSElliott Hughes        ds.updatePaths()
371*e1fe3e4aSElliott Hughes
372*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
373*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
374*e1fe3e4aSElliott Hughes
375*e1fe3e4aSElliott Hughes        tables = ["CFF2"]
376*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
377*e1fe3e4aSElliott Hughes
378*e1fe3e4aSElliott Hughes    def test_varlib_build_CFF2(self):
379*e1fe3e4aSElliott Hughes        self.temp_dir()
380*e1fe3e4aSElliott Hughes
381*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("TestCFF2.designspace", copy=True)
382*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_cff2")
383*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx")
384*e1fe3e4aSElliott Hughes
385*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestCFF2_"):
386*e1fe3e4aSElliott Hughes            self.compile_font(path, ".otf", self.tempdir)
387*e1fe3e4aSElliott Hughes
388*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
389*e1fe3e4aSElliott Hughes        for source in ds.sources:
390*e1fe3e4aSElliott Hughes            source.path = os.path.join(
391*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
392*e1fe3e4aSElliott Hughes            )
393*e1fe3e4aSElliott Hughes        ds.updatePaths()
394*e1fe3e4aSElliott Hughes
395*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
396*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
397*e1fe3e4aSElliott Hughes
398*e1fe3e4aSElliott Hughes        tables = ["fvar", "CFF2"]
399*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
400*e1fe3e4aSElliott Hughes
401*e1fe3e4aSElliott Hughes    def test_varlib_build_CFF2_from_CFF2(self):
402*e1fe3e4aSElliott Hughes        self.temp_dir()
403*e1fe3e4aSElliott Hughes
404*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("TestCFF2Input.designspace", copy=True)
405*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_cff2_input")
406*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx")
407*e1fe3e4aSElliott Hughes
408*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestCFF2_"):
409*e1fe3e4aSElliott Hughes            self.compile_font(path, ".otf", self.tempdir)
410*e1fe3e4aSElliott Hughes
411*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
412*e1fe3e4aSElliott Hughes        for source in ds.sources:
413*e1fe3e4aSElliott Hughes            source.path = os.path.join(
414*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
415*e1fe3e4aSElliott Hughes            )
416*e1fe3e4aSElliott Hughes        ds.updatePaths()
417*e1fe3e4aSElliott Hughes
418*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
419*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
420*e1fe3e4aSElliott Hughes
421*e1fe3e4aSElliott Hughes        tables = ["fvar", "CFF2"]
422*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
423*e1fe3e4aSElliott Hughes
424*e1fe3e4aSElliott Hughes    def test_varlib_build_sparse_CFF2(self):
425*e1fe3e4aSElliott Hughes        self.temp_dir()
426*e1fe3e4aSElliott Hughes
427*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("TestSparseCFF2VF.designspace", copy=True)
428*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_sparse_cff2")
429*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("TestSparseCFF2VF.ttx")
430*e1fe3e4aSElliott Hughes
431*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "MasterSet_Kanji-"):
432*e1fe3e4aSElliott Hughes            self.compile_font(path, ".otf", self.tempdir)
433*e1fe3e4aSElliott Hughes
434*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
435*e1fe3e4aSElliott Hughes        for source in ds.sources:
436*e1fe3e4aSElliott Hughes            source.path = os.path.join(
437*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
438*e1fe3e4aSElliott Hughes            )
439*e1fe3e4aSElliott Hughes        ds.updatePaths()
440*e1fe3e4aSElliott Hughes
441*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
442*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
443*e1fe3e4aSElliott Hughes
444*e1fe3e4aSElliott Hughes        tables = ["fvar", "CFF2"]
445*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
446*e1fe3e4aSElliott Hughes
447*e1fe3e4aSElliott Hughes    def test_varlib_build_vpal(self):
448*e1fe3e4aSElliott Hughes        self.temp_dir()
449*e1fe3e4aSElliott Hughes
450*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("test_vpal.designspace", copy=True)
451*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_vpal_test")
452*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("test_vpal.ttx")
453*e1fe3e4aSElliott Hughes
454*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "master_vpal_test_"):
455*e1fe3e4aSElliott Hughes            self.compile_font(path, ".otf", self.tempdir)
456*e1fe3e4aSElliott Hughes
457*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
458*e1fe3e4aSElliott Hughes        for source in ds.sources:
459*e1fe3e4aSElliott Hughes            source.path = os.path.join(
460*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf")
461*e1fe3e4aSElliott Hughes            )
462*e1fe3e4aSElliott Hughes        ds.updatePaths()
463*e1fe3e4aSElliott Hughes
464*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
465*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
466*e1fe3e4aSElliott Hughes
467*e1fe3e4aSElliott Hughes        tables = ["GPOS"]
468*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
469*e1fe3e4aSElliott Hughes
470*e1fe3e4aSElliott Hughes    def test_varlib_main_ttf(self):
471*e1fe3e4aSElliott Hughes        """Mostly for testing varLib.main()"""
472*e1fe3e4aSElliott Hughes        suffix = ".ttf"
473*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("Build.designspace")
474*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
475*e1fe3e4aSElliott Hughes
476*e1fe3e4aSElliott Hughes        self.temp_dir()
477*e1fe3e4aSElliott Hughes        ttf_dir = os.path.join(self.tempdir, "master_ttf_interpolatable")
478*e1fe3e4aSElliott Hughes        os.makedirs(ttf_dir)
479*e1fe3e4aSElliott Hughes        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
480*e1fe3e4aSElliott Hughes        for path in ttx_paths:
481*e1fe3e4aSElliott Hughes            self.compile_font(path, suffix, ttf_dir)
482*e1fe3e4aSElliott Hughes
483*e1fe3e4aSElliott Hughes        ds_copy = os.path.join(self.tempdir, "BuildMain.designspace")
484*e1fe3e4aSElliott Hughes        shutil.copy2(ds_path, ds_copy)
485*e1fe3e4aSElliott Hughes
486*e1fe3e4aSElliott Hughes        # by default, varLib.main finds master TTFs inside a
487*e1fe3e4aSElliott Hughes        # 'master_ttf_interpolatable' subfolder in current working dir
488*e1fe3e4aSElliott Hughes        cwd = os.getcwd()
489*e1fe3e4aSElliott Hughes        os.chdir(self.tempdir)
490*e1fe3e4aSElliott Hughes        try:
491*e1fe3e4aSElliott Hughes            varLib_main([ds_copy])
492*e1fe3e4aSElliott Hughes        finally:
493*e1fe3e4aSElliott Hughes            os.chdir(cwd)
494*e1fe3e4aSElliott Hughes
495*e1fe3e4aSElliott Hughes        varfont_path = os.path.splitext(ds_copy)[0] + "-VF" + suffix
496*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(varfont_path))
497*e1fe3e4aSElliott Hughes
498*e1fe3e4aSElliott Hughes        # try again passing an explicit --master-finder
499*e1fe3e4aSElliott Hughes        os.remove(varfont_path)
500*e1fe3e4aSElliott Hughes        finder = "%s/master_ttf_interpolatable/{stem}.ttf" % self.tempdir
501*e1fe3e4aSElliott Hughes        varLib_main([ds_copy, "--master-finder", finder])
502*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(varfont_path))
503*e1fe3e4aSElliott Hughes
504*e1fe3e4aSElliott Hughes        # and also with explicit -o output option
505*e1fe3e4aSElliott Hughes        os.remove(varfont_path)
506*e1fe3e4aSElliott Hughes        varfont_path = os.path.splitext(varfont_path)[0] + "-o" + suffix
507*e1fe3e4aSElliott Hughes        varLib_main([ds_copy, "-o", varfont_path, "--master-finder", finder])
508*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(varfont_path))
509*e1fe3e4aSElliott Hughes
510*e1fe3e4aSElliott Hughes        varfont = TTFont(varfont_path)
511*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
512*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("BuildMain.ttx")
513*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
514*e1fe3e4aSElliott Hughes
515*e1fe3e4aSElliott Hughes    def test_varLib_main_output_dir(self):
516*e1fe3e4aSElliott Hughes        self.temp_dir()
517*e1fe3e4aSElliott Hughes        outdir = os.path.join(self.tempdir, "output_dir_test")
518*e1fe3e4aSElliott Hughes        self.assertFalse(os.path.exists(outdir))
519*e1fe3e4aSElliott Hughes
520*e1fe3e4aSElliott Hughes        ds_path = os.path.join(self.tempdir, "BuildMain.designspace")
521*e1fe3e4aSElliott Hughes        shutil.copy2(self.get_test_input("Build.designspace"), ds_path)
522*e1fe3e4aSElliott Hughes
523*e1fe3e4aSElliott Hughes        shutil.copytree(
524*e1fe3e4aSElliott Hughes            self.get_test_input("master_ttx_interpolatable_ttf"),
525*e1fe3e4aSElliott Hughes            os.path.join(outdir, "master_ttx"),
526*e1fe3e4aSElliott Hughes        )
527*e1fe3e4aSElliott Hughes
528*e1fe3e4aSElliott Hughes        finder = "%s/output_dir_test/master_ttx/{stem}.ttx" % self.tempdir
529*e1fe3e4aSElliott Hughes
530*e1fe3e4aSElliott Hughes        varLib_main([ds_path, "--output-dir", outdir, "--master-finder", finder])
531*e1fe3e4aSElliott Hughes
532*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.isdir(outdir))
533*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(os.path.join(outdir, "BuildMain-VF.ttf")))
534*e1fe3e4aSElliott Hughes
535*e1fe3e4aSElliott Hughes    def test_varLib_main_filter_variable_fonts(self):
536*e1fe3e4aSElliott Hughes        self.temp_dir()
537*e1fe3e4aSElliott Hughes        outdir = os.path.join(self.tempdir, "filter_variable_fonts_test")
538*e1fe3e4aSElliott Hughes        self.assertFalse(os.path.exists(outdir))
539*e1fe3e4aSElliott Hughes
540*e1fe3e4aSElliott Hughes        ds_path = os.path.join(self.tempdir, "BuildMain.designspace")
541*e1fe3e4aSElliott Hughes        shutil.copy2(self.get_test_input("Build.designspace"), ds_path)
542*e1fe3e4aSElliott Hughes
543*e1fe3e4aSElliott Hughes        shutil.copytree(
544*e1fe3e4aSElliott Hughes            self.get_test_input("master_ttx_interpolatable_ttf"),
545*e1fe3e4aSElliott Hughes            os.path.join(outdir, "master_ttx"),
546*e1fe3e4aSElliott Hughes        )
547*e1fe3e4aSElliott Hughes
548*e1fe3e4aSElliott Hughes        finder = "%s/filter_variable_fonts_test/master_ttx/{stem}.ttx" % self.tempdir
549*e1fe3e4aSElliott Hughes
550*e1fe3e4aSElliott Hughes        cmd = [ds_path, "--output-dir", outdir, "--master-finder", finder]
551*e1fe3e4aSElliott Hughes
552*e1fe3e4aSElliott Hughes        with pytest.raises(SystemExit):
553*e1fe3e4aSElliott Hughes            varLib_main(cmd + ["--variable-fonts", "FooBar"])  # no font matches
554*e1fe3e4aSElliott Hughes
555*e1fe3e4aSElliott Hughes        varLib_main(cmd + ["--variable-fonts", "Build.*"])  # this does match
556*e1fe3e4aSElliott Hughes
557*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.isdir(outdir))
558*e1fe3e4aSElliott Hughes        self.assertTrue(os.path.exists(os.path.join(outdir, "BuildMain-VF.ttf")))
559*e1fe3e4aSElliott Hughes
560*e1fe3e4aSElliott Hughes    def test_varLib_main_drop_implied_oncurves(self):
561*e1fe3e4aSElliott Hughes        self.temp_dir()
562*e1fe3e4aSElliott Hughes        outdir = os.path.join(self.tempdir, "drop_implied_oncurves_test")
563*e1fe3e4aSElliott Hughes        self.assertFalse(os.path.exists(outdir))
564*e1fe3e4aSElliott Hughes
565*e1fe3e4aSElliott Hughes        ttf_dir = os.path.join(outdir, "master_ttf_interpolatable")
566*e1fe3e4aSElliott Hughes        os.makedirs(ttf_dir)
567*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_drop_oncurves")
568*e1fe3e4aSElliott Hughes        ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
569*e1fe3e4aSElliott Hughes        for path in ttx_paths:
570*e1fe3e4aSElliott Hughes            self.compile_font(path, ".ttf", ttf_dir)
571*e1fe3e4aSElliott Hughes
572*e1fe3e4aSElliott Hughes        ds_copy = os.path.join(outdir, "DropOnCurves.designspace")
573*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("DropOnCurves.designspace")
574*e1fe3e4aSElliott Hughes        shutil.copy2(ds_path, ds_copy)
575*e1fe3e4aSElliott Hughes
576*e1fe3e4aSElliott Hughes        finder = "%s/master_ttf_interpolatable/{stem}.ttf" % outdir
577*e1fe3e4aSElliott Hughes        varLib_main([ds_copy, "--master-finder", finder, "--drop-implied-oncurves"])
578*e1fe3e4aSElliott Hughes
579*e1fe3e4aSElliott Hughes        vf_path = os.path.join(outdir, "DropOnCurves-VF.ttf")
580*e1fe3e4aSElliott Hughes        varfont = TTFont(vf_path)
581*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
582*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("DropOnCurves.ttx")
583*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
584*e1fe3e4aSElliott Hughes
585*e1fe3e4aSElliott Hughes    def test_varLib_build_many_no_overwrite_STAT(self):
586*e1fe3e4aSElliott Hughes        # Ensure that varLib.build_many doesn't overwrite a pre-existing STAT table,
587*e1fe3e4aSElliott Hughes        # e.g. one built by feaLib from features.fea; the VF simply should inherit the
588*e1fe3e4aSElliott Hughes        # STAT from the base master: https://github.com/googlefonts/fontmake/issues/985
589*e1fe3e4aSElliott Hughes        base_master = TTFont()
590*e1fe3e4aSElliott Hughes        base_master.importXML(
591*e1fe3e4aSElliott Hughes            self.get_test_input("master_no_overwrite_stat/Test-CondensedThin.ttx")
592*e1fe3e4aSElliott Hughes        )
593*e1fe3e4aSElliott Hughes        assert "STAT" in base_master
594*e1fe3e4aSElliott Hughes
595*e1fe3e4aSElliott Hughes        vf = next(
596*e1fe3e4aSElliott Hughes            iter(
597*e1fe3e4aSElliott Hughes                build_many(
598*e1fe3e4aSElliott Hughes                    DesignSpaceDocument.fromfile(
599*e1fe3e4aSElliott Hughes                        self.get_test_input("TestNoOverwriteSTAT.designspace")
600*e1fe3e4aSElliott Hughes                    )
601*e1fe3e4aSElliott Hughes                ).values()
602*e1fe3e4aSElliott Hughes            )
603*e1fe3e4aSElliott Hughes        )
604*e1fe3e4aSElliott Hughes        assert "STAT" in vf
605*e1fe3e4aSElliott Hughes
606*e1fe3e4aSElliott Hughes        assert vf["STAT"].table == base_master["STAT"].table
607*e1fe3e4aSElliott Hughes
608*e1fe3e4aSElliott Hughes    def test_varlib_build_from_ds_object_in_memory_ttfonts(self):
609*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("Build.designspace")
610*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
611*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("BuildMain.ttx")
612*e1fe3e4aSElliott Hughes
613*e1fe3e4aSElliott Hughes        self.temp_dir()
614*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestFamily-"):
615*e1fe3e4aSElliott Hughes            self.compile_font(path, ".ttf", self.tempdir)
616*e1fe3e4aSElliott Hughes
617*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
618*e1fe3e4aSElliott Hughes        for source in ds.sources:
619*e1fe3e4aSElliott Hughes            filename = os.path.join(
620*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".ttf")
621*e1fe3e4aSElliott Hughes            )
622*e1fe3e4aSElliott Hughes            source.font = TTFont(
623*e1fe3e4aSElliott Hughes                filename, recalcBBoxes=False, recalcTimestamp=False, lazy=True
624*e1fe3e4aSElliott Hughes            )
625*e1fe3e4aSElliott Hughes            source.filename = None  # Make sure no file path gets into build()
626*e1fe3e4aSElliott Hughes
627*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
628*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
629*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
630*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
631*e1fe3e4aSElliott Hughes
632*e1fe3e4aSElliott Hughes    def test_varlib_build_from_ttf_paths(self):
633*e1fe3e4aSElliott Hughes        self.temp_dir()
634*e1fe3e4aSElliott Hughes
635*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("Build.designspace", copy=True)
636*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
637*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("BuildMain.ttx")
638*e1fe3e4aSElliott Hughes
639*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestFamily-"):
640*e1fe3e4aSElliott Hughes            self.compile_font(path, ".ttf", self.tempdir)
641*e1fe3e4aSElliott Hughes
642*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
643*e1fe3e4aSElliott Hughes        for source in ds.sources:
644*e1fe3e4aSElliott Hughes            source.path = os.path.join(
645*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", ".ttf")
646*e1fe3e4aSElliott Hughes            )
647*e1fe3e4aSElliott Hughes        ds.updatePaths()
648*e1fe3e4aSElliott Hughes
649*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
650*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
651*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
652*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
653*e1fe3e4aSElliott Hughes
654*e1fe3e4aSElliott Hughes    def test_varlib_build_from_ttx_paths(self):
655*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("Build.designspace")
656*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
657*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("BuildMain.ttx")
658*e1fe3e4aSElliott Hughes
659*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
660*e1fe3e4aSElliott Hughes        for source in ds.sources:
661*e1fe3e4aSElliott Hughes            source.path = os.path.join(
662*e1fe3e4aSElliott Hughes                ttx_dir, os.path.basename(source.filename).replace(".ufo", ".ttx")
663*e1fe3e4aSElliott Hughes            )
664*e1fe3e4aSElliott Hughes        ds.updatePaths()
665*e1fe3e4aSElliott Hughes
666*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
667*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
668*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
669*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
670*e1fe3e4aSElliott Hughes
671*e1fe3e4aSElliott Hughes    def test_varlib_build_sparse_masters(self):
672*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("SparseMasters.designspace")
673*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("SparseMasters.ttx")
674*e1fe3e4aSElliott Hughes
675*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds_path)
676*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
677*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
678*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
679*e1fe3e4aSElliott Hughes
680*e1fe3e4aSElliott Hughes    def test_varlib_build_lazy_masters(self):
681*e1fe3e4aSElliott Hughes        # See https://github.com/fonttools/fonttools/issues/1808
682*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("SparseMasters.designspace")
683*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("SparseMasters.ttx")
684*e1fe3e4aSElliott Hughes
685*e1fe3e4aSElliott Hughes        def _open_font(master_path, master_finder=lambda s: s):
686*e1fe3e4aSElliott Hughes            font = TTFont()
687*e1fe3e4aSElliott Hughes            font.importXML(master_path)
688*e1fe3e4aSElliott Hughes            buf = BytesIO()
689*e1fe3e4aSElliott Hughes            font.save(buf, reorderTables=False)
690*e1fe3e4aSElliott Hughes            buf.seek(0)
691*e1fe3e4aSElliott Hughes            font = TTFont(buf, lazy=True)  # reopen in lazy mode, to reproduce #1808
692*e1fe3e4aSElliott Hughes            return font
693*e1fe3e4aSElliott Hughes
694*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
695*e1fe3e4aSElliott Hughes        ds.loadSourceFonts(_open_font)
696*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
697*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
698*e1fe3e4aSElliott Hughes        tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
699*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
700*e1fe3e4aSElliott Hughes
701*e1fe3e4aSElliott Hughes    def test_varlib_build_sparse_masters_MVAR(self):
702*e1fe3e4aSElliott Hughes        import fontTools.varLib.mvar
703*e1fe3e4aSElliott Hughes
704*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("SparseMasters.designspace")
705*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
706*e1fe3e4aSElliott Hughes        load_masters(ds)
707*e1fe3e4aSElliott Hughes
708*e1fe3e4aSElliott Hughes        # Trigger MVAR generation so varLib is forced to create deltas with a
709*e1fe3e4aSElliott Hughes        # sparse master inbetween.
710*e1fe3e4aSElliott Hughes        font_0_os2 = ds.sources[0].font["OS/2"]
711*e1fe3e4aSElliott Hughes        font_0_os2.sTypoAscender = 1
712*e1fe3e4aSElliott Hughes        font_0_os2.sTypoDescender = 1
713*e1fe3e4aSElliott Hughes        font_0_os2.sTypoLineGap = 1
714*e1fe3e4aSElliott Hughes        font_0_os2.usWinAscent = 1
715*e1fe3e4aSElliott Hughes        font_0_os2.usWinDescent = 1
716*e1fe3e4aSElliott Hughes        font_0_os2.sxHeight = 1
717*e1fe3e4aSElliott Hughes        font_0_os2.sCapHeight = 1
718*e1fe3e4aSElliott Hughes        font_0_os2.ySubscriptXSize = 1
719*e1fe3e4aSElliott Hughes        font_0_os2.ySubscriptYSize = 1
720*e1fe3e4aSElliott Hughes        font_0_os2.ySubscriptXOffset = 1
721*e1fe3e4aSElliott Hughes        font_0_os2.ySubscriptYOffset = 1
722*e1fe3e4aSElliott Hughes        font_0_os2.ySuperscriptXSize = 1
723*e1fe3e4aSElliott Hughes        font_0_os2.ySuperscriptYSize = 1
724*e1fe3e4aSElliott Hughes        font_0_os2.ySuperscriptXOffset = 1
725*e1fe3e4aSElliott Hughes        font_0_os2.ySuperscriptYOffset = 1
726*e1fe3e4aSElliott Hughes        font_0_os2.yStrikeoutSize = 1
727*e1fe3e4aSElliott Hughes        font_0_os2.yStrikeoutPosition = 1
728*e1fe3e4aSElliott Hughes        font_0_vhea = newTable("vhea")
729*e1fe3e4aSElliott Hughes        font_0_vhea.ascent = 1
730*e1fe3e4aSElliott Hughes        font_0_vhea.descent = 1
731*e1fe3e4aSElliott Hughes        font_0_vhea.lineGap = 1
732*e1fe3e4aSElliott Hughes        font_0_vhea.caretSlopeRise = 1
733*e1fe3e4aSElliott Hughes        font_0_vhea.caretSlopeRun = 1
734*e1fe3e4aSElliott Hughes        font_0_vhea.caretOffset = 1
735*e1fe3e4aSElliott Hughes        ds.sources[0].font["vhea"] = font_0_vhea
736*e1fe3e4aSElliott Hughes        font_0_hhea = ds.sources[0].font["hhea"]
737*e1fe3e4aSElliott Hughes        font_0_hhea.caretSlopeRise = 1
738*e1fe3e4aSElliott Hughes        font_0_hhea.caretSlopeRun = 1
739*e1fe3e4aSElliott Hughes        font_0_hhea.caretOffset = 1
740*e1fe3e4aSElliott Hughes        font_0_post = ds.sources[0].font["post"]
741*e1fe3e4aSElliott Hughes        font_0_post.underlineThickness = 1
742*e1fe3e4aSElliott Hughes        font_0_post.underlinePosition = 1
743*e1fe3e4aSElliott Hughes
744*e1fe3e4aSElliott Hughes        font_2_os2 = ds.sources[2].font["OS/2"]
745*e1fe3e4aSElliott Hughes        font_2_os2.sTypoAscender = 800
746*e1fe3e4aSElliott Hughes        font_2_os2.sTypoDescender = 800
747*e1fe3e4aSElliott Hughes        font_2_os2.sTypoLineGap = 800
748*e1fe3e4aSElliott Hughes        font_2_os2.usWinAscent = 800
749*e1fe3e4aSElliott Hughes        font_2_os2.usWinDescent = 800
750*e1fe3e4aSElliott Hughes        font_2_os2.sxHeight = 800
751*e1fe3e4aSElliott Hughes        font_2_os2.sCapHeight = 800
752*e1fe3e4aSElliott Hughes        font_2_os2.ySubscriptXSize = 800
753*e1fe3e4aSElliott Hughes        font_2_os2.ySubscriptYSize = 800
754*e1fe3e4aSElliott Hughes        font_2_os2.ySubscriptXOffset = 800
755*e1fe3e4aSElliott Hughes        font_2_os2.ySubscriptYOffset = 800
756*e1fe3e4aSElliott Hughes        font_2_os2.ySuperscriptXSize = 800
757*e1fe3e4aSElliott Hughes        font_2_os2.ySuperscriptYSize = 800
758*e1fe3e4aSElliott Hughes        font_2_os2.ySuperscriptXOffset = 800
759*e1fe3e4aSElliott Hughes        font_2_os2.ySuperscriptYOffset = 800
760*e1fe3e4aSElliott Hughes        font_2_os2.yStrikeoutSize = 800
761*e1fe3e4aSElliott Hughes        font_2_os2.yStrikeoutPosition = 800
762*e1fe3e4aSElliott Hughes        font_2_vhea = newTable("vhea")
763*e1fe3e4aSElliott Hughes        font_2_vhea.ascent = 800
764*e1fe3e4aSElliott Hughes        font_2_vhea.descent = 800
765*e1fe3e4aSElliott Hughes        font_2_vhea.lineGap = 800
766*e1fe3e4aSElliott Hughes        font_2_vhea.caretSlopeRise = 800
767*e1fe3e4aSElliott Hughes        font_2_vhea.caretSlopeRun = 800
768*e1fe3e4aSElliott Hughes        font_2_vhea.caretOffset = 800
769*e1fe3e4aSElliott Hughes        ds.sources[2].font["vhea"] = font_2_vhea
770*e1fe3e4aSElliott Hughes        font_2_hhea = ds.sources[2].font["hhea"]
771*e1fe3e4aSElliott Hughes        font_2_hhea.caretSlopeRise = 800
772*e1fe3e4aSElliott Hughes        font_2_hhea.caretSlopeRun = 800
773*e1fe3e4aSElliott Hughes        font_2_hhea.caretOffset = 800
774*e1fe3e4aSElliott Hughes        font_2_post = ds.sources[2].font["post"]
775*e1fe3e4aSElliott Hughes        font_2_post.underlineThickness = 800
776*e1fe3e4aSElliott Hughes        font_2_post.underlinePosition = 800
777*e1fe3e4aSElliott Hughes
778*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
779*e1fe3e4aSElliott Hughes        mvar_tags = [vr.ValueTag for vr in varfont["MVAR"].table.ValueRecord]
780*e1fe3e4aSElliott Hughes        assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES)
781*e1fe3e4aSElliott Hughes
782*e1fe3e4aSElliott Hughes    def test_varlib_build_VVAR_CFF2(self):
783*e1fe3e4aSElliott Hughes        self.temp_dir()
784*e1fe3e4aSElliott Hughes
785*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("TestVVAR.designspace", copy=True)
786*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_vvar_cff2")
787*e1fe3e4aSElliott Hughes        expected_ttx_name = "TestVVAR"
788*e1fe3e4aSElliott Hughes        suffix = ".otf"
789*e1fe3e4aSElliott Hughes
790*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestVVAR"):
791*e1fe3e4aSElliott Hughes            font, savepath = self.compile_font(path, suffix, self.tempdir)
792*e1fe3e4aSElliott Hughes
793*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
794*e1fe3e4aSElliott Hughes        for source in ds.sources:
795*e1fe3e4aSElliott Hughes            source.path = os.path.join(
796*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", suffix)
797*e1fe3e4aSElliott Hughes            )
798*e1fe3e4aSElliott Hughes        ds.updatePaths()
799*e1fe3e4aSElliott Hughes
800*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
801*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
802*e1fe3e4aSElliott Hughes
803*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx")
804*e1fe3e4aSElliott Hughes        tables = ["VVAR"]
805*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
806*e1fe3e4aSElliott Hughes        self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
807*e1fe3e4aSElliott Hughes
808*e1fe3e4aSElliott Hughes    def test_varlib_build_BASE(self):
809*e1fe3e4aSElliott Hughes        self.temp_dir()
810*e1fe3e4aSElliott Hughes
811*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("TestBASE.designspace", copy=True)
812*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_base_test")
813*e1fe3e4aSElliott Hughes        expected_ttx_name = "TestBASE"
814*e1fe3e4aSElliott Hughes        suffix = ".otf"
815*e1fe3e4aSElliott Hughes
816*e1fe3e4aSElliott Hughes        for path in self.get_file_list(ttx_dir, ".ttx", "TestBASE"):
817*e1fe3e4aSElliott Hughes            font, savepath = self.compile_font(path, suffix, self.tempdir)
818*e1fe3e4aSElliott Hughes
819*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
820*e1fe3e4aSElliott Hughes        for source in ds.sources:
821*e1fe3e4aSElliott Hughes            source.path = os.path.join(
822*e1fe3e4aSElliott Hughes                self.tempdir, os.path.basename(source.filename).replace(".ufo", suffix)
823*e1fe3e4aSElliott Hughes            )
824*e1fe3e4aSElliott Hughes        ds.updatePaths()
825*e1fe3e4aSElliott Hughes
826*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
827*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
828*e1fe3e4aSElliott Hughes
829*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx")
830*e1fe3e4aSElliott Hughes        tables = ["BASE"]
831*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
832*e1fe3e4aSElliott Hughes        self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
833*e1fe3e4aSElliott Hughes
834*e1fe3e4aSElliott Hughes    def test_varlib_build_single_master(self):
835*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
836*e1fe3e4aSElliott Hughes            designspace_name="SingleMaster",
837*e1fe3e4aSElliott Hughes            font_name="TestFamily",
838*e1fe3e4aSElliott Hughes            tables=["GDEF", "HVAR", "MVAR", "STAT", "fvar", "cvar", "gvar", "name"],
839*e1fe3e4aSElliott Hughes            expected_ttx_name="SingleMaster",
840*e1fe3e4aSElliott Hughes            save_before_dump=True,
841*e1fe3e4aSElliott Hughes        )
842*e1fe3e4aSElliott Hughes
843*e1fe3e4aSElliott Hughes    def test_kerning_merging(self):
844*e1fe3e4aSElliott Hughes        """Test the correct merging of class-based pair kerning.
845*e1fe3e4aSElliott Hughes
846*e1fe3e4aSElliott Hughes        Problem description at https://github.com/fonttools/fonttools/pull/1638.
847*e1fe3e4aSElliott Hughes        Test font and Designspace generated by
848*e1fe3e4aSElliott Hughes        https://gist.github.com/madig/183d0440c9f7d05f04bd1280b9664bd1.
849*e1fe3e4aSElliott Hughes        """
850*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("KerningMerging.designspace")
851*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_kerning_merging")
852*e1fe3e4aSElliott Hughes
853*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
854*e1fe3e4aSElliott Hughes        for source in ds.sources:
855*e1fe3e4aSElliott Hughes            ttx_dump = TTFont()
856*e1fe3e4aSElliott Hughes            ttx_dump.importXML(
857*e1fe3e4aSElliott Hughes                os.path.join(
858*e1fe3e4aSElliott Hughes                    ttx_dir, os.path.basename(source.filename).replace(".ttf", ".ttx")
859*e1fe3e4aSElliott Hughes                )
860*e1fe3e4aSElliott Hughes            )
861*e1fe3e4aSElliott Hughes            source.font = reload_font(ttx_dump)
862*e1fe3e4aSElliott Hughes
863*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds)
864*e1fe3e4aSElliott Hughes        varfont = reload_font(varfont)
865*e1fe3e4aSElliott Hughes
866*e1fe3e4aSElliott Hughes        class_kerning_tables = [
867*e1fe3e4aSElliott Hughes            t
868*e1fe3e4aSElliott Hughes            for l in varfont["GPOS"].table.LookupList.Lookup
869*e1fe3e4aSElliott Hughes            for t in l.SubTable
870*e1fe3e4aSElliott Hughes            if t.Format == 2
871*e1fe3e4aSElliott Hughes        ]
872*e1fe3e4aSElliott Hughes        assert len(class_kerning_tables) == 1
873*e1fe3e4aSElliott Hughes        class_kerning_table = class_kerning_tables[0]
874*e1fe3e4aSElliott Hughes
875*e1fe3e4aSElliott Hughes        # Test that no class kerned against class zero (containing all glyphs not
876*e1fe3e4aSElliott Hughes        # classed) has a `XAdvDevice` table attached, which in the variable font
877*e1fe3e4aSElliott Hughes        # context is a "VariationIndex" table and points to kerning deltas in the GDEF
878*e1fe3e4aSElliott Hughes        # table. Variation deltas of any kerning class against class zero should
879*e1fe3e4aSElliott Hughes        # probably never exist.
880*e1fe3e4aSElliott Hughes        for class1_record in class_kerning_table.Class1Record:
881*e1fe3e4aSElliott Hughes            class2_zero = class1_record.Class2Record[0]
882*e1fe3e4aSElliott Hughes            assert getattr(class2_zero.Value1, "XAdvDevice", None) is None
883*e1fe3e4aSElliott Hughes
884*e1fe3e4aSElliott Hughes        # Assert the variable font's kerning table (without deltas) is equal to the
885*e1fe3e4aSElliott Hughes        # default font's kerning table. The bug fixed in
886*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/pull/1638 caused rogue kerning
887*e1fe3e4aSElliott Hughes        # values to be written to the variable font.
888*e1fe3e4aSElliott Hughes        assert _extract_flat_kerning(varfont, class_kerning_table) == {
889*e1fe3e4aSElliott Hughes            ("A", ".notdef"): 0,
890*e1fe3e4aSElliott Hughes            ("A", "A"): 0,
891*e1fe3e4aSElliott Hughes            ("A", "B"): -20,
892*e1fe3e4aSElliott Hughes            ("A", "C"): 0,
893*e1fe3e4aSElliott Hughes            ("A", "D"): -20,
894*e1fe3e4aSElliott Hughes            ("B", ".notdef"): 0,
895*e1fe3e4aSElliott Hughes            ("B", "A"): 0,
896*e1fe3e4aSElliott Hughes            ("B", "B"): 0,
897*e1fe3e4aSElliott Hughes            ("B", "C"): 0,
898*e1fe3e4aSElliott Hughes            ("B", "D"): 0,
899*e1fe3e4aSElliott Hughes        }
900*e1fe3e4aSElliott Hughes
901*e1fe3e4aSElliott Hughes        instance_thin = instantiateVariableFont(varfont, {"wght": 100})
902*e1fe3e4aSElliott Hughes        instance_thin_kerning_table = (
903*e1fe3e4aSElliott Hughes            instance_thin["GPOS"].table.LookupList.Lookup[0].SubTable[0]
904*e1fe3e4aSElliott Hughes        )
905*e1fe3e4aSElliott Hughes        assert _extract_flat_kerning(instance_thin, instance_thin_kerning_table) == {
906*e1fe3e4aSElliott Hughes            ("A", ".notdef"): 0,
907*e1fe3e4aSElliott Hughes            ("A", "A"): 0,
908*e1fe3e4aSElliott Hughes            ("A", "B"): 0,
909*e1fe3e4aSElliott Hughes            ("A", "C"): 10,
910*e1fe3e4aSElliott Hughes            ("A", "D"): 0,
911*e1fe3e4aSElliott Hughes            ("B", ".notdef"): 0,
912*e1fe3e4aSElliott Hughes            ("B", "A"): 0,
913*e1fe3e4aSElliott Hughes            ("B", "B"): 0,
914*e1fe3e4aSElliott Hughes            ("B", "C"): 10,
915*e1fe3e4aSElliott Hughes            ("B", "D"): 0,
916*e1fe3e4aSElliott Hughes        }
917*e1fe3e4aSElliott Hughes
918*e1fe3e4aSElliott Hughes        instance_black = instantiateVariableFont(varfont, {"wght": 900})
919*e1fe3e4aSElliott Hughes        instance_black_kerning_table = (
920*e1fe3e4aSElliott Hughes            instance_black["GPOS"].table.LookupList.Lookup[0].SubTable[0]
921*e1fe3e4aSElliott Hughes        )
922*e1fe3e4aSElliott Hughes        assert _extract_flat_kerning(instance_black, instance_black_kerning_table) == {
923*e1fe3e4aSElliott Hughes            ("A", ".notdef"): 0,
924*e1fe3e4aSElliott Hughes            ("A", "A"): 0,
925*e1fe3e4aSElliott Hughes            ("A", "B"): 0,
926*e1fe3e4aSElliott Hughes            ("A", "C"): 0,
927*e1fe3e4aSElliott Hughes            ("A", "D"): 40,
928*e1fe3e4aSElliott Hughes            ("B", ".notdef"): 0,
929*e1fe3e4aSElliott Hughes            ("B", "A"): 0,
930*e1fe3e4aSElliott Hughes            ("B", "B"): 0,
931*e1fe3e4aSElliott Hughes            ("B", "C"): 0,
932*e1fe3e4aSElliott Hughes            ("B", "D"): 40,
933*e1fe3e4aSElliott Hughes        }
934*e1fe3e4aSElliott Hughes
935*e1fe3e4aSElliott Hughes    def test_designspace_fill_in_location(self):
936*e1fe3e4aSElliott Hughes        ds_path = self.get_test_input("VarLibLocationTest.designspace")
937*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(ds_path)
938*e1fe3e4aSElliott Hughes        ds_loaded = load_designspace(ds)
939*e1fe3e4aSElliott Hughes
940*e1fe3e4aSElliott Hughes        assert ds_loaded.instances[0].location == {"weight": 0, "width": 50}
941*e1fe3e4aSElliott Hughes
942*e1fe3e4aSElliott Hughes    def test_varlib_build_incompatible_features(self):
943*e1fe3e4aSElliott Hughes        with pytest.raises(
944*e1fe3e4aSElliott Hughes            varLibErrors.ShouldBeConstant,
945*e1fe3e4aSElliott Hughes            match="""
946*e1fe3e4aSElliott Hughes
947*e1fe3e4aSElliott HughesCouldn't merge the fonts, because some values were different, but should have
948*e1fe3e4aSElliott Hughesbeen the same. This happened while performing the following operation:
949*e1fe3e4aSElliott HughesGPOS.table.FeatureList.FeatureCount
950*e1fe3e4aSElliott Hughes
951*e1fe3e4aSElliott HughesThe problem is likely to be in Simple Two Axis Bold:
952*e1fe3e4aSElliott HughesExpected to see .FeatureCount==2, instead saw 1
953*e1fe3e4aSElliott Hughes
954*e1fe3e4aSElliott HughesIncompatible features between masters.
955*e1fe3e4aSElliott HughesExpected: kern, mark.
956*e1fe3e4aSElliott HughesGot: kern.
957*e1fe3e4aSElliott Hughes""",
958*e1fe3e4aSElliott Hughes        ):
959*e1fe3e4aSElliott Hughes            self._run_varlib_build_test(
960*e1fe3e4aSElliott Hughes                designspace_name="IncompatibleFeatures",
961*e1fe3e4aSElliott Hughes                font_name="IncompatibleFeatures",
962*e1fe3e4aSElliott Hughes                tables=["GPOS"],
963*e1fe3e4aSElliott Hughes                expected_ttx_name="IncompatibleFeatures",
964*e1fe3e4aSElliott Hughes                save_before_dump=True,
965*e1fe3e4aSElliott Hughes            )
966*e1fe3e4aSElliott Hughes
967*e1fe3e4aSElliott Hughes    def test_varlib_build_incompatible_lookup_types(self):
968*e1fe3e4aSElliott Hughes        with pytest.raises(
969*e1fe3e4aSElliott Hughes            varLibErrors.MismatchedTypes, match=r"'MarkBasePos', instead saw 'PairPos'"
970*e1fe3e4aSElliott Hughes        ):
971*e1fe3e4aSElliott Hughes            self._run_varlib_build_test(
972*e1fe3e4aSElliott Hughes                designspace_name="IncompatibleLookupTypes",
973*e1fe3e4aSElliott Hughes                font_name="IncompatibleLookupTypes",
974*e1fe3e4aSElliott Hughes                tables=["GPOS"],
975*e1fe3e4aSElliott Hughes                expected_ttx_name="IncompatibleLookupTypes",
976*e1fe3e4aSElliott Hughes                save_before_dump=True,
977*e1fe3e4aSElliott Hughes            )
978*e1fe3e4aSElliott Hughes
979*e1fe3e4aSElliott Hughes    def test_varlib_build_incompatible_arrays(self):
980*e1fe3e4aSElliott Hughes        with pytest.raises(
981*e1fe3e4aSElliott Hughes            varLibErrors.ShouldBeConstant,
982*e1fe3e4aSElliott Hughes            match="""
983*e1fe3e4aSElliott Hughes
984*e1fe3e4aSElliott HughesCouldn't merge the fonts, because some values were different, but should have
985*e1fe3e4aSElliott Hughesbeen the same. This happened while performing the following operation:
986*e1fe3e4aSElliott HughesGPOS.table.ScriptList.ScriptCount
987*e1fe3e4aSElliott Hughes
988*e1fe3e4aSElliott HughesThe problem is likely to be in Simple Two Axis Bold:
989*e1fe3e4aSElliott HughesExpected to see .ScriptCount==1, instead saw 0""",
990*e1fe3e4aSElliott Hughes        ):
991*e1fe3e4aSElliott Hughes            self._run_varlib_build_test(
992*e1fe3e4aSElliott Hughes                designspace_name="IncompatibleArrays",
993*e1fe3e4aSElliott Hughes                font_name="IncompatibleArrays",
994*e1fe3e4aSElliott Hughes                tables=["GPOS"],
995*e1fe3e4aSElliott Hughes                expected_ttx_name="IncompatibleArrays",
996*e1fe3e4aSElliott Hughes                save_before_dump=True,
997*e1fe3e4aSElliott Hughes            )
998*e1fe3e4aSElliott Hughes
999*e1fe3e4aSElliott Hughes    def test_varlib_build_variable_colr(self):
1000*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
1001*e1fe3e4aSElliott Hughes            designspace_name="TestVariableCOLR",
1002*e1fe3e4aSElliott Hughes            font_name="TestVariableCOLR",
1003*e1fe3e4aSElliott Hughes            tables=["GlyphOrder", "fvar", "glyf", "COLR", "CPAL"],
1004*e1fe3e4aSElliott Hughes            expected_ttx_name="TestVariableCOLR-VF",
1005*e1fe3e4aSElliott Hughes            save_before_dump=True,
1006*e1fe3e4aSElliott Hughes        )
1007*e1fe3e4aSElliott Hughes
1008*e1fe3e4aSElliott Hughes    def test_varlib_build_variable_cff2_with_empty_sparse_glyph(self):
1009*e1fe3e4aSElliott Hughes        # https://github.com/fonttools/fonttools/issues/3233
1010*e1fe3e4aSElliott Hughes        self._run_varlib_build_test(
1011*e1fe3e4aSElliott Hughes            designspace_name="SparseCFF2",
1012*e1fe3e4aSElliott Hughes            font_name="SparseCFF2",
1013*e1fe3e4aSElliott Hughes            tables=["GlyphOrder", "CFF2", "fvar", "hmtx", "HVAR"],
1014*e1fe3e4aSElliott Hughes            expected_ttx_name="SparseCFF2-VF",
1015*e1fe3e4aSElliott Hughes            save_before_dump=True,
1016*e1fe3e4aSElliott Hughes        )
1017*e1fe3e4aSElliott Hughes
1018*e1fe3e4aSElliott Hughes    def test_varlib_addGSUBFeatureVariations(self):
1019*e1fe3e4aSElliott Hughes        ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
1020*e1fe3e4aSElliott Hughes
1021*e1fe3e4aSElliott Hughes        ds = DesignSpaceDocument.fromfile(
1022*e1fe3e4aSElliott Hughes            self.get_test_input("FeatureVars.designspace")
1023*e1fe3e4aSElliott Hughes        )
1024*e1fe3e4aSElliott Hughes        for source in ds.sources:
1025*e1fe3e4aSElliott Hughes            ttx_dump = TTFont()
1026*e1fe3e4aSElliott Hughes            ttx_dump.importXML(
1027*e1fe3e4aSElliott Hughes                os.path.join(
1028*e1fe3e4aSElliott Hughes                    ttx_dir, os.path.basename(source.filename).replace(".ufo", ".ttx")
1029*e1fe3e4aSElliott Hughes                )
1030*e1fe3e4aSElliott Hughes            )
1031*e1fe3e4aSElliott Hughes            source.font = ttx_dump
1032*e1fe3e4aSElliott Hughes
1033*e1fe3e4aSElliott Hughes        varfont, _, _ = build(ds, exclude=["GSUB"])
1034*e1fe3e4aSElliott Hughes        assert "GSUB" not in varfont
1035*e1fe3e4aSElliott Hughes
1036*e1fe3e4aSElliott Hughes        addGSUBFeatureVariations(varfont, ds)
1037*e1fe3e4aSElliott Hughes        assert "GSUB" in varfont
1038*e1fe3e4aSElliott Hughes
1039*e1fe3e4aSElliott Hughes        tables = ["fvar", "GSUB"]
1040*e1fe3e4aSElliott Hughes        expected_ttx_path = self.get_test_output("FeatureVars.ttx")
1041*e1fe3e4aSElliott Hughes        self.expect_ttx(varfont, expected_ttx_path, tables)
1042*e1fe3e4aSElliott Hughes        self.check_ttx_dump(varfont, expected_ttx_path, tables, ".ttf")
1043*e1fe3e4aSElliott Hughes
1044*e1fe3e4aSElliott Hughes
1045*e1fe3e4aSElliott Hughesdef test_load_masters_layerName_without_required_font():
1046*e1fe3e4aSElliott Hughes    ds = DesignSpaceDocument()
1047*e1fe3e4aSElliott Hughes    s = SourceDescriptor()
1048*e1fe3e4aSElliott Hughes    s.font = None
1049*e1fe3e4aSElliott Hughes    s.layerName = "Medium"
1050*e1fe3e4aSElliott Hughes    ds.addSource(s)
1051*e1fe3e4aSElliott Hughes
1052*e1fe3e4aSElliott Hughes    with pytest.raises(
1053*e1fe3e4aSElliott Hughes        VarLibValidationError,
1054*e1fe3e4aSElliott Hughes        match="specified a layer name but lacks the required TTFont object",
1055*e1fe3e4aSElliott Hughes    ):
1056*e1fe3e4aSElliott Hughes        load_masters(ds)
1057*e1fe3e4aSElliott Hughes
1058*e1fe3e4aSElliott Hughes
1059*e1fe3e4aSElliott Hughesdef _extract_flat_kerning(font, pairpos_table):
1060*e1fe3e4aSElliott Hughes    extracted_kerning = {}
1061*e1fe3e4aSElliott Hughes    for glyph_name_1 in pairpos_table.Coverage.glyphs:
1062*e1fe3e4aSElliott Hughes        class_def_1 = pairpos_table.ClassDef1.classDefs.get(glyph_name_1, 0)
1063*e1fe3e4aSElliott Hughes        for glyph_name_2 in font.getGlyphOrder():
1064*e1fe3e4aSElliott Hughes            class_def_2 = pairpos_table.ClassDef2.classDefs.get(glyph_name_2, 0)
1065*e1fe3e4aSElliott Hughes            kern_value = (
1066*e1fe3e4aSElliott Hughes                pairpos_table.Class1Record[class_def_1]
1067*e1fe3e4aSElliott Hughes                .Class2Record[class_def_2]
1068*e1fe3e4aSElliott Hughes                .Value1.XAdvance
1069*e1fe3e4aSElliott Hughes            )
1070*e1fe3e4aSElliott Hughes            extracted_kerning[(glyph_name_1, glyph_name_2)] = kern_value
1071*e1fe3e4aSElliott Hughes    return extracted_kerning
1072*e1fe3e4aSElliott Hughes
1073*e1fe3e4aSElliott Hughes
1074*e1fe3e4aSElliott Hughes@pytest.fixture
1075*e1fe3e4aSElliott Hughesdef ttFont():
1076*e1fe3e4aSElliott Hughes    f = TTFont()
1077*e1fe3e4aSElliott Hughes    f["OS/2"] = newTable("OS/2")
1078*e1fe3e4aSElliott Hughes    f["OS/2"].usWeightClass = 400
1079*e1fe3e4aSElliott Hughes    f["OS/2"].usWidthClass = 100
1080*e1fe3e4aSElliott Hughes    f["post"] = newTable("post")
1081*e1fe3e4aSElliott Hughes    f["post"].italicAngle = 0
1082*e1fe3e4aSElliott Hughes    return f
1083*e1fe3e4aSElliott Hughes
1084*e1fe3e4aSElliott Hughes
1085*e1fe3e4aSElliott Hughesclass SetDefaultWeightWidthSlantTest(object):
1086*e1fe3e4aSElliott Hughes    @pytest.mark.parametrize(
1087*e1fe3e4aSElliott Hughes        "location, expected",
1088*e1fe3e4aSElliott Hughes        [
1089*e1fe3e4aSElliott Hughes            ({"wght": 0}, 1),
1090*e1fe3e4aSElliott Hughes            ({"wght": 1}, 1),
1091*e1fe3e4aSElliott Hughes            ({"wght": 100}, 100),
1092*e1fe3e4aSElliott Hughes            ({"wght": 1000}, 1000),
1093*e1fe3e4aSElliott Hughes            ({"wght": 1001}, 1000),
1094*e1fe3e4aSElliott Hughes        ],
1095*e1fe3e4aSElliott Hughes    )
1096*e1fe3e4aSElliott Hughes    def test_wght(self, ttFont, location, expected):
1097*e1fe3e4aSElliott Hughes        set_default_weight_width_slant(ttFont, location)
1098*e1fe3e4aSElliott Hughes
1099*e1fe3e4aSElliott Hughes        assert ttFont["OS/2"].usWeightClass == expected
1100*e1fe3e4aSElliott Hughes
1101*e1fe3e4aSElliott Hughes    @pytest.mark.parametrize(
1102*e1fe3e4aSElliott Hughes        "location, expected",
1103*e1fe3e4aSElliott Hughes        [
1104*e1fe3e4aSElliott Hughes            ({"wdth": 0}, 1),
1105*e1fe3e4aSElliott Hughes            ({"wdth": 56}, 1),
1106*e1fe3e4aSElliott Hughes            ({"wdth": 57}, 2),
1107*e1fe3e4aSElliott Hughes            ({"wdth": 62.5}, 2),
1108*e1fe3e4aSElliott Hughes            ({"wdth": 75}, 3),
1109*e1fe3e4aSElliott Hughes            ({"wdth": 87.5}, 4),
1110*e1fe3e4aSElliott Hughes            ({"wdth": 100}, 5),
1111*e1fe3e4aSElliott Hughes            ({"wdth": 112.5}, 6),
1112*e1fe3e4aSElliott Hughes            ({"wdth": 125}, 7),
1113*e1fe3e4aSElliott Hughes            ({"wdth": 150}, 8),
1114*e1fe3e4aSElliott Hughes            ({"wdth": 200}, 9),
1115*e1fe3e4aSElliott Hughes            ({"wdth": 201}, 9),
1116*e1fe3e4aSElliott Hughes            ({"wdth": 1000}, 9),
1117*e1fe3e4aSElliott Hughes        ],
1118*e1fe3e4aSElliott Hughes    )
1119*e1fe3e4aSElliott Hughes    def test_wdth(self, ttFont, location, expected):
1120*e1fe3e4aSElliott Hughes        set_default_weight_width_slant(ttFont, location)
1121*e1fe3e4aSElliott Hughes
1122*e1fe3e4aSElliott Hughes        assert ttFont["OS/2"].usWidthClass == expected
1123*e1fe3e4aSElliott Hughes
1124*e1fe3e4aSElliott Hughes    @pytest.mark.parametrize(
1125*e1fe3e4aSElliott Hughes        "location, expected",
1126*e1fe3e4aSElliott Hughes        [
1127*e1fe3e4aSElliott Hughes            ({"slnt": -91}, -90),
1128*e1fe3e4aSElliott Hughes            ({"slnt": -90}, -90),
1129*e1fe3e4aSElliott Hughes            ({"slnt": 0}, 0),
1130*e1fe3e4aSElliott Hughes            ({"slnt": 11.5}, 11.5),
1131*e1fe3e4aSElliott Hughes            ({"slnt": 90}, 90),
1132*e1fe3e4aSElliott Hughes            ({"slnt": 91}, 90),
1133*e1fe3e4aSElliott Hughes        ],
1134*e1fe3e4aSElliott Hughes    )
1135*e1fe3e4aSElliott Hughes    def test_slnt(self, ttFont, location, expected):
1136*e1fe3e4aSElliott Hughes        set_default_weight_width_slant(ttFont, location)
1137*e1fe3e4aSElliott Hughes
1138*e1fe3e4aSElliott Hughes        assert ttFont["post"].italicAngle == expected
1139*e1fe3e4aSElliott Hughes
1140*e1fe3e4aSElliott Hughes    def test_all(self, ttFont):
1141*e1fe3e4aSElliott Hughes        set_default_weight_width_slant(
1142*e1fe3e4aSElliott Hughes            ttFont, {"wght": 500, "wdth": 150, "slnt": -12.0}
1143*e1fe3e4aSElliott Hughes        )
1144*e1fe3e4aSElliott Hughes
1145*e1fe3e4aSElliott Hughes        assert ttFont["OS/2"].usWeightClass == 500
1146*e1fe3e4aSElliott Hughes        assert ttFont["OS/2"].usWidthClass == 8
1147*e1fe3e4aSElliott Hughes        assert ttFont["post"].italicAngle == -12.0
1148*e1fe3e4aSElliott Hughes
1149*e1fe3e4aSElliott Hughes
1150*e1fe3e4aSElliott Hughesdef test_variable_COLR_without_VarIndexMap():
1151*e1fe3e4aSElliott Hughes    # test we don't add a no-op VarIndexMap to variable COLR when not needed
1152*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/2800
1153*e1fe3e4aSElliott Hughes
1154*e1fe3e4aSElliott Hughes    font1 = TTFont()
1155*e1fe3e4aSElliott Hughes    font1.setGlyphOrder([".notdef", "A"])
1156*e1fe3e4aSElliott Hughes    font1["COLR"] = buildCOLR({"A": (ot.PaintFormat.PaintSolid, 0, 1.0)})
1157*e1fe3e4aSElliott Hughes    # font2 == font1 except for PaintSolid.Alpha
1158*e1fe3e4aSElliott Hughes    font2 = deepcopy(font1)
1159*e1fe3e4aSElliott Hughes    font2["COLR"].table.BaseGlyphList.BaseGlyphPaintRecord[0].Paint.Alpha = 0.0
1160*e1fe3e4aSElliott Hughes    master_fonts = [font1, font2]
1161*e1fe3e4aSElliott Hughes
1162*e1fe3e4aSElliott Hughes    varfont = deepcopy(font1)
1163*e1fe3e4aSElliott Hughes    axis_order = ["XXXX"]
1164*e1fe3e4aSElliott Hughes    model = VariationModel([{}, {"XXXX": 1.0}], axis_order)
1165*e1fe3e4aSElliott Hughes
1166*e1fe3e4aSElliott Hughes    _add_COLR(varfont, model, master_fonts, axis_order)
1167*e1fe3e4aSElliott Hughes
1168*e1fe3e4aSElliott Hughes    colr = varfont["COLR"].table
1169*e1fe3e4aSElliott Hughes
1170*e1fe3e4aSElliott Hughes    assert len(colr.BaseGlyphList.BaseGlyphPaintRecord) == 1
1171*e1fe3e4aSElliott Hughes    baserec = colr.BaseGlyphList.BaseGlyphPaintRecord[0]
1172*e1fe3e4aSElliott Hughes    assert baserec.Paint.Format == ot.PaintFormat.PaintVarSolid
1173*e1fe3e4aSElliott Hughes    assert baserec.Paint.VarIndexBase == 0
1174*e1fe3e4aSElliott Hughes
1175*e1fe3e4aSElliott Hughes    assert colr.VarStore is not None
1176*e1fe3e4aSElliott Hughes    assert len(colr.VarStore.VarData) == 1
1177*e1fe3e4aSElliott Hughes    assert len(colr.VarStore.VarData[0].Item) == 1
1178*e1fe3e4aSElliott Hughes    assert colr.VarStore.VarData[0].Item[0] == [-16384]
1179*e1fe3e4aSElliott Hughes
1180*e1fe3e4aSElliott Hughes    assert colr.VarIndexMap is None
1181*e1fe3e4aSElliott Hughes
1182*e1fe3e4aSElliott Hughes
1183*e1fe3e4aSElliott Hughesif __name__ == "__main__":
1184*e1fe3e4aSElliott Hughes    sys.exit(unittest.main())
1185