aboutsummaryrefslogtreecommitdiff
path: root/Tests/varLib/varLib_test.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/varLib/varLib_test.py')
-rw-r--r--Tests/varLib/varLib_test.py280
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)