diff options
Diffstat (limited to 'Tests/varLib/varLib_test.py')
-rw-r--r-- | Tests/varLib/varLib_test.py | 280 |
1 files changed, 259 insertions, 21 deletions
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py index ec05d565..b8e7183b 100644 --- a/Tests/varLib/varLib_test.py +++ b/Tests/varLib/varLib_test.py @@ -1,14 +1,16 @@ -from __future__ import print_function, division, absolute_import -from fontTools.misc.py23 import * from fontTools.ttLib import TTFont, newTable -from fontTools.varLib import build +from fontTools.varLib import build, load_designspace +from fontTools.varLib.errors import VarLibValidationError +import fontTools.varLib.errors as varLibErrors from fontTools.varLib.mutator import instantiateVariableFont from fontTools.varLib import main as varLib_main, load_masters from fontTools.varLib import set_default_weight_width_slant from fontTools.designspaceLib import ( DesignSpaceDocumentError, DesignSpaceDocument, SourceDescriptor, ) +from fontTools.feaLib.builder import addOpenTypeFeaturesFromString import difflib +from io import BytesIO import os import shutil import sys @@ -21,6 +23,9 @@ def reload_font(font): """(De)serialize to get final binary layout.""" buf = BytesIO() font.save(buf) + # Close the font to release filesystem resources so that on Windows the tearDown + # method can successfully remove the temporary directory created during setUp. + font.close() buf.seek(0) return TTFont(buf) @@ -41,10 +46,15 @@ class BuildTest(unittest.TestCase): if self.tempdir: shutil.rmtree(self.tempdir) - @staticmethod - def get_test_input(test_file_or_folder): - path, _ = os.path.split(__file__) - return os.path.join(path, "data", test_file_or_folder) + def get_test_input(self, test_file_or_folder, copy=False): + parent_dir = os.path.dirname(__file__) + path = os.path.join(parent_dir, "data", test_file_or_folder) + if copy: + copied_path = os.path.join(self.tempdir, test_file_or_folder) + shutil.copy2(path, copied_path) + return copied_path + else: + return path @staticmethod def get_test_output(test_file_or_folder): @@ -108,7 +118,8 @@ class BuildTest(unittest.TestCase): return font, savepath def _run_varlib_build_test(self, designspace_name, font_name, tables, - expected_ttx_name, save_before_dump=False): + expected_ttx_name, save_before_dump=False, + post_process_master=None): suffix = '.ttf' ds_path = self.get_test_input(designspace_name + '.designspace') ufo_dir = self.get_test_input('master_ufo') @@ -117,7 +128,9 @@ class BuildTest(unittest.TestCase): self.temp_dir() ttx_paths = self.get_file_list(ttx_dir, '.ttx', font_name + '-') for path in ttx_paths: - self.compile_font(path, suffix, self.tempdir) + font, savepath = self.compile_font(path, suffix, self.tempdir) + if post_process_master is not None: + post_process_master(font, savepath) finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix) varfont, model, _ = build(ds_path, finder) @@ -214,6 +227,83 @@ class BuildTest(unittest.TestCase): save_before_dump=True, ) + def test_varlib_build_feature_variations_custom_tag(self): + """Designspace file contains <rules> element, used to build + GSUB FeatureVariations table. + """ + self._run_varlib_build_test( + designspace_name="FeatureVarsCustomTag", + font_name="TestFamily", + tables=["fvar", "GSUB"], + expected_ttx_name="FeatureVarsCustomTag", + save_before_dump=True, + ) + + def test_varlib_build_feature_variations_whole_range(self): + """Designspace file contains <rules> element specifying the entire design + space, used to build GSUB FeatureVariations table. + """ + self._run_varlib_build_test( + designspace_name="FeatureVarsWholeRange", + font_name="TestFamily", + tables=["fvar", "GSUB"], + expected_ttx_name="FeatureVarsWholeRange", + save_before_dump=True, + ) + + def test_varlib_build_feature_variations_whole_range_empty(self): + """Designspace file contains <rules> element without a condition, specifying + the entire design space, used to build GSUB FeatureVariations table. + """ + self._run_varlib_build_test( + designspace_name="FeatureVarsWholeRangeEmpty", + font_name="TestFamily", + tables=["fvar", "GSUB"], + expected_ttx_name="FeatureVarsWholeRange", + save_before_dump=True, + ) + + def test_varlib_build_feature_variations_with_existing_rclt(self): + """Designspace file contains <rules> element, used to build GSUB + FeatureVariations table. <rules> is specified to do its OT processing + "last", so a 'rclt' feature will be used or created. This test covers + the case when a 'rclt' already exists in the masters. + + We dynamically add a 'rclt' feature to an existing set of test + masters, to avoid adding more test data. + + The multiple languages are done to verify whether multiple existing + 'rclt' features are updated correctly. + """ + def add_rclt(font, savepath): + features = """ + languagesystem DFLT dflt; + languagesystem latn dflt; + languagesystem latn NLD; + + feature rclt { + script latn; + language NLD; + lookup A { + sub uni0041 by uni0061; + } A; + language dflt; + lookup B { + sub uni0041 by uni0061; + } B; + } rclt; + """ + addOpenTypeFeaturesFromString(font, features) + font.save(savepath) + self._run_varlib_build_test( + designspace_name="FeatureVars", + font_name="TestFamily", + tables=["fvar", "GSUB"], + expected_ttx_name="FeatureVars_rclt", + save_before_dump=True, + post_process_master=add_rclt, + ) + def test_varlib_gvar_explicit_delta(self): """The variable font contains a composite glyph odieresis which does not need a gvar entry, because all its deltas are 0, but it must be added @@ -230,11 +320,12 @@ class BuildTest(unittest.TestCase): ) def test_varlib_nonmarking_CFF2(self): - ds_path = self.get_test_input('TestNonMarkingCFF2.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestNonMarkingCFF2.designspace', copy=True) ttx_dir = self.get_test_input("master_non_marking_cff2") expected_ttx_path = self.get_test_output("TestNonMarkingCFF2.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestNonMarkingCFF2_'): self.compile_font(path, ".otf", self.tempdir) @@ -252,11 +343,35 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_CFF2(self): - ds_path = self.get_test_input('TestCFF2.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestCFF2.designspace', copy=True) ttx_dir = self.get_test_input("master_cff2") expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx") + for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'): + self.compile_font(path, ".otf", self.tempdir) + + ds = DesignSpaceDocument.fromfile(ds_path) + for source in ds.sources: + source.path = os.path.join( + self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf") + ) + ds.updatePaths() + + varfont, _, _ = build(ds) + varfont = reload_font(varfont) + + tables = ["fvar", "CFF2"] + self.expect_ttx(varfont, expected_ttx_path, tables) + + def test_varlib_build_CFF2_from_CFF2(self): self.temp_dir() + + ds_path = self.get_test_input('TestCFF2Input.designspace', copy=True) + ttx_dir = self.get_test_input("master_cff2_input") + expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx") + for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'): self.compile_font(path, ".otf", self.tempdir) @@ -274,11 +389,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_sparse_CFF2(self): - ds_path = self.get_test_input('TestSparseCFF2VF.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestSparseCFF2VF.designspace', copy=True) ttx_dir = self.get_test_input("master_sparse_cff2") expected_ttx_path = self.get_test_output("TestSparseCFF2VF.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'MasterSet_Kanji-'): self.compile_font(path, ".otf", self.tempdir) @@ -296,11 +412,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_vpal(self): - ds_path = self.get_test_input('test_vpal.designspace') + self.temp_dir() + + ds_path = self.get_test_input('test_vpal.designspace', copy=True) ttx_dir = self.get_test_input("master_vpal_test") expected_ttx_path = self.get_test_output("test_vpal.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'master_vpal_test_'): self.compile_font(path, ".otf", self.tempdir) @@ -388,11 +505,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_from_ttf_paths(self): - ds_path = self.get_test_input("Build.designspace") + self.temp_dir() + + ds_path = self.get_test_input("Build.designspace", copy=True) ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf") expected_ttx_path = self.get_test_output("BuildMain.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'): self.compile_font(path, ".ttf", self.tempdir) @@ -434,6 +552,27 @@ class BuildTest(unittest.TestCase): tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"] self.expect_ttx(varfont, expected_ttx_path, tables) + def test_varlib_build_lazy_masters(self): + # See https://github.com/fonttools/fonttools/issues/1808 + ds_path = self.get_test_input("SparseMasters.designspace") + expected_ttx_path = self.get_test_output("SparseMasters.ttx") + + def _open_font(master_path, master_finder=lambda s: s): + font = TTFont() + font.importXML(master_path) + buf = BytesIO() + font.save(buf, reorderTables=False) + buf.seek(0) + font = TTFont(buf, lazy=True) # reopen in lazy mode, to reproduce #1808 + return font + + ds = DesignSpaceDocument.fromfile(ds_path) + ds.loadSourceFonts(_open_font) + varfont, _, _ = build(ds) + varfont = reload_font(varfont) + tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"] + self.expect_ttx(varfont, expected_ttx_path, tables) + def test_varlib_build_sparse_masters_MVAR(self): import fontTools.varLib.mvar @@ -516,12 +655,13 @@ class BuildTest(unittest.TestCase): assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES) def test_varlib_build_VVAR_CFF2(self): - ds_path = self.get_test_input('TestVVAR.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestVVAR.designspace', copy=True) ttx_dir = self.get_test_input("master_vvar_cff2") expected_ttx_name = 'TestVVAR' suffix = '.otf' - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestVVAR'): font, savepath = self.compile_font(path, suffix, self.tempdir) @@ -540,6 +680,41 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix) + def test_varlib_build_BASE(self): + self.temp_dir() + + ds_path = self.get_test_input('TestBASE.designspace', copy=True) + ttx_dir = self.get_test_input("master_base_test") + expected_ttx_name = 'TestBASE' + suffix = '.otf' + + for path in self.get_file_list(ttx_dir, '.ttx', 'TestBASE'): + font, savepath = self.compile_font(path, suffix, self.tempdir) + + ds = DesignSpaceDocument.fromfile(ds_path) + for source in ds.sources: + source.path = os.path.join( + self.tempdir, os.path.basename(source.filename).replace(".ufo", suffix) + ) + ds.updatePaths() + + varfont, _, _ = build(ds) + varfont = reload_font(varfont) + + expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx') + tables = ["BASE"] + self.expect_ttx(varfont, expected_ttx_path, tables) + self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix) + + def test_varlib_build_single_master(self): + self._run_varlib_build_test( + designspace_name='SingleMaster', + font_name='TestFamily', + tables=['GDEF', 'HVAR', 'MVAR', 'STAT', 'fvar', 'cvar', 'gvar', 'name'], + expected_ttx_name='SingleMaster', + save_before_dump=True, + ) + def test_kerning_merging(self): """Test the correct merging of class-based pair kerning. @@ -632,6 +807,69 @@ class BuildTest(unittest.TestCase): ("B", "D"): 40, } + def test_designspace_fill_in_location(self): + ds_path = self.get_test_input("VarLibLocationTest.designspace") + ds = DesignSpaceDocument.fromfile(ds_path) + ds_loaded = load_designspace(ds) + + assert ds_loaded.instances[0].location == {"weight": 0, "width": 50} + + def test_varlib_build_incompatible_features(self): + with pytest.raises( + varLibErrors.ShouldBeConstant, + match = """ + +Couldn't merge the fonts, because some values were different, but should have +been the same. This happened while performing the following operation: +GPOS.table.FeatureList.FeatureCount + +The problem is likely to be in Simple Two Axis Bold: + +Incompatible features between masters. +Expected: kern, mark. +Got: kern. +"""): + + self._run_varlib_build_test( + designspace_name="IncompatibleFeatures", + font_name="IncompatibleFeatures", + tables=["GPOS"], + expected_ttx_name="IncompatibleFeatures", + save_before_dump=True, + ) + + def test_varlib_build_incompatible_lookup_types(self): + with pytest.raises( + varLibErrors.MismatchedTypes, + match = r"MarkBasePos, instead saw PairPos" + ): + self._run_varlib_build_test( + designspace_name="IncompatibleLookupTypes", + font_name="IncompatibleLookupTypes", + tables=["GPOS"], + expected_ttx_name="IncompatibleLookupTypes", + save_before_dump=True, + ) + + def test_varlib_build_incompatible_arrays(self): + with pytest.raises( + varLibErrors.ShouldBeConstant, + match = """ + +Couldn't merge the fonts, because some values were different, but should have +been the same. This happened while performing the following operation: +GPOS.table.ScriptList.ScriptCount + +The problem is likely to be in Simple Two Axis Bold: +Expected to see .ScriptCount==1, instead saw 0""" + ): + self._run_varlib_build_test( + designspace_name="IncompatibleArrays", + font_name="IncompatibleArrays", + tables=["GPOS"], + expected_ttx_name="IncompatibleArrays", + save_before_dump=True, + ) def test_load_masters_layerName_without_required_font(): ds = DesignSpaceDocument() @@ -641,7 +879,7 @@ def test_load_masters_layerName_without_required_font(): ds.addSource(s) with pytest.raises( - AttributeError, + VarLibValidationError, match="specified a layer name but lacks the required TTFont object", ): load_masters(ds) |