| import math |
| import shutil |
| from pathlib import Path |
| |
| import pytest |
| from fontTools.designspaceLib import DesignSpaceDocument |
| from fontTools.designspaceLib.split import ( |
| _conditionSetFrom, |
| convert5to4, |
| splitInterpolable, |
| splitVariableFonts, |
| ) |
| from fontTools.designspaceLib.types import ConditionSet, Range |
| |
| from .fixtures import datadir |
| |
| UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING = False |
| |
| |
| @pytest.mark.parametrize( |
| "test_ds,expected_interpolable_spaces", |
| [ |
| ( |
| "test_v5_aktiv.designspace", |
| [ |
| ( |
| {}, |
| { |
| "AktivGroteskVF_Italics_Wght", |
| "AktivGroteskVF_Italics_WghtWdth", |
| "AktivGroteskVF_Wght", |
| "AktivGroteskVF_WghtWdth", |
| "AktivGroteskVF_WghtWdthItal", |
| }, |
| ) |
| ], |
| ), |
| ( |
| "test_v5_sourceserif.designspace", |
| [ |
| ( |
| {"italic": 0}, |
| {"SourceSerif4Variable-Roman"}, |
| ), |
| ( |
| {"italic": 1}, |
| {"SourceSerif4Variable-Italic"}, |
| ), |
| ], |
| ), |
| ( |
| "test_v5_MutatorSans_and_Serif.designspace", |
| [ |
| ( |
| {"serif": 0}, |
| { |
| "MutatorSansVariable_Weight_Width", |
| "MutatorSansVariable_Weight", |
| "MutatorSansVariable_Width", |
| }, |
| ), |
| ( |
| {"serif": 1}, |
| { |
| "MutatorSerifVariable_Width", |
| }, |
| ), |
| ], |
| ), |
| ], |
| ) |
| def test_split(datadir, tmpdir, test_ds, expected_interpolable_spaces): |
| data_in = datadir / test_ds |
| temp_in = Path(tmpdir) / test_ds |
| shutil.copy(data_in, temp_in) |
| doc = DesignSpaceDocument.fromfile(temp_in) |
| |
| for i, (location, sub_doc) in enumerate(splitInterpolable(doc)): |
| expected_location, expected_vf_names = expected_interpolable_spaces[i] |
| assert location == expected_location |
| vfs = list(splitVariableFonts(sub_doc)) |
| assert expected_vf_names == set(vf[0] for vf in vfs) |
| |
| loc_str = "_".join( |
| f"{name}_{value}" for name, value in sorted(location.items()) |
| ) |
| data_out = datadir / "split_output" / f"{temp_in.stem}_{loc_str}.designspace" |
| temp_out = Path(tmpdir) / "out" / f"{temp_in.stem}_{loc_str}.designspace" |
| temp_out.parent.mkdir(exist_ok=True) |
| sub_doc.write(temp_out) |
| |
| if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING: |
| data_out.write_text(temp_out.read_text(encoding="utf-8"), encoding="utf-8") |
| else: |
| assert data_out.read_text(encoding="utf-8") == temp_out.read_text( |
| encoding="utf-8" |
| ) |
| |
| for vf_name, vf_doc in vfs: |
| data_out = (datadir / "split_output" / vf_name).with_suffix(".designspace") |
| temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace") |
| temp_out.parent.mkdir(exist_ok=True) |
| vf_doc.write(temp_out) |
| |
| if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING: |
| data_out.write_text( |
| temp_out.read_text(encoding="utf-8"), encoding="utf-8" |
| ) |
| else: |
| assert data_out.read_text(encoding="utf-8") == temp_out.read_text( |
| encoding="utf-8" |
| ) |
| |
| |
| @pytest.mark.parametrize( |
| "test_ds,expected_vfs", |
| [ |
| ( |
| "test_v5_aktiv.designspace", |
| { |
| "AktivGroteskVF_Italics_Wght", |
| "AktivGroteskVF_Italics_WghtWdth", |
| "AktivGroteskVF_Wght", |
| "AktivGroteskVF_WghtWdth", |
| "AktivGroteskVF_WghtWdthItal", |
| }, |
| ), |
| ( |
| "test_v5_sourceserif.designspace", |
| { |
| "SourceSerif4Variable-Italic", |
| "SourceSerif4Variable-Roman", |
| }, |
| ), |
| ], |
| ) |
| def test_convert5to4(datadir, tmpdir, test_ds, expected_vfs): |
| data_in = datadir / test_ds |
| temp_in = tmpdir / test_ds |
| shutil.copy(data_in, temp_in) |
| doc = DesignSpaceDocument.fromfile(temp_in) |
| |
| variable_fonts = convert5to4(doc) |
| |
| assert variable_fonts.keys() == expected_vfs |
| for vf_name, vf in variable_fonts.items(): |
| data_out = (datadir / "convert5to4_output" / vf_name).with_suffix( |
| ".designspace" |
| ) |
| temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace") |
| temp_out.parent.mkdir(exist_ok=True) |
| vf.write(temp_out) |
| |
| if UPDATE_REFERENCE_OUT_FILES_INSTEAD_OF_TESTING: |
| data_out.write_text(temp_out.read_text(encoding="utf-8"), encoding="utf-8") |
| else: |
| assert data_out.read_text(encoding="utf-8") == temp_out.read_text( |
| encoding="utf-8" |
| ) |
| |
| |
| @pytest.mark.parametrize( |
| ["unbounded_condition"], |
| [ |
| ({"name": "Weight", "minimum": 500},), |
| ({"name": "Weight", "maximum": 500},), |
| ({"name": "Weight", "minimum": 500, "maximum": None},), |
| ({"name": "Weight", "minimum": None, "maximum": 500},), |
| ], |
| ) |
| def test_optional_min_max(unbounded_condition): |
| """Check that split functions can handle conditions that are partially |
| unbounded without tripping over None values and missing keys.""" |
| doc = DesignSpaceDocument() |
| |
| doc.addAxisDescriptor( |
| name="Weight", tag="wght", minimum=400, maximum=1000, default=400 |
| ) |
| |
| doc.addRuleDescriptor( |
| name="unbounded", |
| conditionSets=[[unbounded_condition]], |
| ) |
| |
| assert len(list(splitInterpolable(doc))) == 1 |
| assert len(list(splitVariableFonts(doc))) == 1 |
| |
| |
| @pytest.mark.parametrize( |
| ["condition", "expected_set"], |
| [ |
| ( |
| {"name": "axis", "minimum": 0.5}, |
| {"axis": Range(minimum=0.5, maximum=math.inf)}, |
| ), |
| ( |
| {"name": "axis", "maximum": 0.5}, |
| {"axis": Range(minimum=-math.inf, maximum=0.5)}, |
| ), |
| ( |
| {"name": "axis", "minimum": 0.5, "maximum": None}, |
| {"axis": Range(minimum=0.5, maximum=math.inf)}, |
| ), |
| ( |
| {"name": "axis", "minimum": None, "maximum": 0.5}, |
| {"axis": Range(minimum=-math.inf, maximum=0.5)}, |
| ), |
| ], |
| ) |
| def test_optional_min_max_internal(condition, expected_set: ConditionSet): |
| """Check that split's internal helper functions produce the correct output |
| for conditions that are partially unbounded.""" |
| assert _conditionSetFrom([condition]) == expected_set |
| |
| |
| def test_avar2(datadir): |
| ds = DesignSpaceDocument() |
| ds.read(datadir / "test_avar2.designspace") |
| _, subDoc = next(splitInterpolable(ds)) |
| assert len(subDoc.axisMappings) == 2 |
| |
| subDocs = list(splitVariableFonts(ds)) |
| assert len(subDocs) == 5 |
| for i, (_, subDoc) in enumerate(subDocs): |
| # Only the first one should have a mapping, according to the document |
| if i == 0: |
| assert len(subDoc.axisMappings) == 2 |
| else: |
| assert len(subDoc.axisMappings) == 0 |