Skip to content

Commit

Permalink
remove MutatorMath integration
Browse files Browse the repository at this point in the history
Fixes #834
  • Loading branch information
anthrotype committed Jul 26, 2023
1 parent f64ea00 commit ed0d281
Show file tree
Hide file tree
Showing 5 changed files with 15 additions and 224 deletions.
24 changes: 2 additions & 22 deletions Lib/fontmake/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def main(args=None):
'match a given "name" attribute, you can pass as argument '
"the full instance name or a regular expression. "
'E.g.: -i "Noto Sans Bold"; or -i ".* UI Condensed". '
"(for Glyphs or MutatorMath sources only). ",
"(for Glyphs or DesignSpace sources only). ",
)
outputGroup.add_argument(
"--variable-fonts",
Expand All @@ -308,14 +308,6 @@ def main(args=None):
"""
),
)
outputGroup.add_argument(
"--use-mutatormath",
action="store_true",
help=(
"Use MutatorMath to generate instances (supports extrapolation and "
"anisotropic locations)."
),
)
outputGroup.add_argument(
"-M",
"--masters-as-instances",
Expand Down Expand Up @@ -536,7 +528,7 @@ def main(args=None):
const=True,
metavar="MASTER_DIR",
help="Interpolate layout tables from compiled master binaries. "
"Requires Glyphs or MutatorMath source.",
"Requires Glyphs or DesignSpace source.",
)
layoutGroup.add_argument(
"--feature-writer",
Expand Down Expand Up @@ -643,7 +635,6 @@ def main(args=None):
"interpolate",
"masters_as_instances",
"interpolate_binary_layout",
"use_mutatormath",
],
"variable output",
)
Expand All @@ -656,16 +647,6 @@ def main(args=None):
positive=False,
)

if args.get("use_mutatormath"):
for module in ("defcon", "mutatorMath"):
try:
__import__(module)
except ImportError:
parser.error(
f"{module} module not found; reinstall fontmake with the "
"[mutatormath] extra"
)

PRINT_TRACEBACK = level == "DEBUG"
try:
project = FontProject(validate_ufo=args.pop("validate_ufo"))
Expand Down Expand Up @@ -705,7 +686,6 @@ def main(args=None):
[
"interpolate",
"variable_fonts",
"use_mutatormath",
"interpolate_binary_layout",
"round_instances",
"expand_features_to_instances",
Expand Down
160 changes: 13 additions & 147 deletions Lib/fontmake/font_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,48 +74,6 @@
)


@contextmanager
def temporarily_disabling_axis_maps(designspace_path):
"""Context manager to prevent MutatorMath from warping designspace locations.
MutatorMath assumes that the masters and instances' locations are in
user-space coordinates -- whereas they actually are in internal design-space
coordinates, and thus they do not need any 'bending'. To work around this we
we create a temporary designspace document without the axis maps, and with the
min/default/max triplet mapped "forward" from user-space coordinates (input)
to internal designspace coordinates (output).
Args:
designspace_path: A path to a designspace document.
Yields:
A temporary path string to the thus modified designspace document.
After the context is exited, it removes the temporary file.
Related issues:
https://github.com/LettError/designSpaceDocument/issues/16
https://github.com/fonttools/fonttools/pull/1395
"""
try:
designspace = designspaceLib.DesignSpaceDocument.fromfile(designspace_path)
except Exception as e:
raise FontmakeError("Reading Designspace failed", designspace_path) from e

for axis in designspace.axes:
axis.minimum = axis.map_forward(axis.minimum)
axis.default = axis.map_forward(axis.default)
axis.maximum = axis.map_forward(axis.maximum)
del axis.map[:]

fd, temp_designspace_path = tempfile.mkstemp()
os.close(fd)
try:
designspace.write(temp_designspace_path)
yield temp_designspace_path
finally:
os.remove(temp_designspace_path)


def needs_subsetting(ufo):
if KEEP_GLYPHS_OLD_KEY in ufo.lib or KEEP_GLYPHS_NEW_KEY in ufo.lib:
return True
Expand Down Expand Up @@ -189,7 +147,7 @@ def build_master_ufos(
ufo_structure="package",
glyph_data=None,
):
"""Build UFOs and MutatorMath designspace from Glyphs source."""
"""Build UFOs and designspace from Glyphs source."""
import glyphsLib

if master_dir is None:
Expand Down Expand Up @@ -962,84 +920,6 @@ def interpolate_instance_ufos(

yield instance.font

def interpolate_instance_ufos_mutatormath(
self,
designspace,
include=None,
round_instances=False,
expand_features_to_instances=False,
fea_include_dir=None,
):
"""Interpolate master UFOs with MutatorMath and return instance UFOs.
Args:
designspace: a DesignSpaceDocument object containing sources and
instances.
include (str): optional regular expression pattern to match the
DS instance 'name' attribute and only interpolate the matching
instances.
round_instances (bool): round instances' coordinates to integer.
expand_features_to_instances: parses the master feature file, expands all
include()s and writes the resulting full feature file to all instance
UFOs. Use this if you share feature files among masters in external
files. Otherwise, the relative include paths can break as instances
may end up elsewhere. Only done on interpolation.
Returns:
list of defcon.Font objects corresponding to the UFO instances.
Raises:
FontmakeError: if any of the sources defines a custom 'layer', for
this is not supported by MutatorMath.
ValueError: "expand_features_to_instances" is True but no source in the
designspace document is designated with '<features copy="1"/>'.
"""
from glyphsLib.interpolation import apply_instance_data
from mutatorMath.ufo.document import DesignSpaceDocumentReader

if any(source.layerName is not None for source in designspace.sources):
raise FontmakeError(
"MutatorMath doesn't support DesignSpace sources with 'layer' "
"attribute",
None,
)

with temporarily_disabling_axis_maps(designspace.path) as temp_designspace_path:
builder = DesignSpaceDocumentReader(
temp_designspace_path,
ufoVersion=3,
roundGeometry=round_instances,
verbose=True,
)
logger.info("Interpolating master UFOs from designspace")
if include is not None:
instances = self._search_instances(designspace, pattern=include)
for instance_name in instances:
builder.readInstance(("name", instance_name))
filenames = set(instances.values())
else:
builder.readInstances()
filenames = None # will include all instances

logger.info("Applying instance data from designspace")
instance_ufos = apply_instance_data(designspace, include_filenames=filenames)

if expand_features_to_instances:
logger.debug("Expanding features to instance UFOs")
master_source = next(
(s for s in designspace.sources if s.copyFeatures), None
)
if not master_source:
raise ValueError("No source is designated as the master for features.")
else:
master_source_font = builder.sources[master_source.name][0]
master_source_features = parseLayoutFeatures(
master_source_font, includeDir=fea_include_dir
).asFea()
for instance_ufo in instance_ufos:
instance_ufo.features.text = master_source_features
instance_ufo.save()

return instance_ufos

def run_from_designspace(
self,
designspace_path,
Expand All @@ -1052,7 +932,6 @@ def run_from_designspace(
feature_writers=None,
filters=None,
expand_features_to_instances=False,
use_mutatormath=False,
check_compatibility=None,
**kwargs,
):
Expand All @@ -1074,8 +953,8 @@ def run_from_designspace(
masters_as_instances: If True, output master fonts as instances.
interpolate_binary_layout: Interpolate layout tables from compiled
master binaries.
round_instances: apply integer rounding when interpolating with
MutatorMath.
round_instances: apply integer rounding when interpolating static
instance UFOs.
kwargs: Arguments passed along to run_from_ufos.
Raises:
Expand Down Expand Up @@ -1145,7 +1024,6 @@ def run_from_designspace(
round_instances=round_instances,
feature_writers=feature_writers,
expand_features_to_instances=expand_features_to_instances,
use_mutatormath=use_mutatormath,
filters=filters,
**kwargs,
)
Expand Down Expand Up @@ -1180,7 +1058,6 @@ def _run_from_designspace_static(
feature_writers=None,
expand_features_to_instances=False,
fea_include_dir=None,
use_mutatormath=False,
ufo_structure="package",
**kwargs,
):
Expand All @@ -1189,27 +1066,16 @@ def _run_from_designspace_static(
ufos.extend(s.path for s in designspace.sources if s.path)
if interpolate:
pattern = interpolate if isinstance(interpolate, str) else None
if use_mutatormath:
ufos.extend(
self.interpolate_instance_ufos_mutatormath(
designspace,
include=pattern,
round_instances=round_instances,
expand_features_to_instances=expand_features_to_instances,
fea_include_dir=fea_include_dir,
)
)
else:
ufos.extend(
self.interpolate_instance_ufos(
designspace,
include=pattern,
round_instances=round_instances,
expand_features_to_instances=expand_features_to_instances,
fea_include_dir=fea_include_dir,
ufo_structure=ufo_structure,
)
ufos.extend(
self.interpolate_instance_ufos(
designspace,
include=pattern,
round_instances=round_instances,
expand_features_to_instances=expand_features_to_instances,
fea_include_dir=fea_include_dir,
ufo_structure=ufo_structure,
)
)

if interpolate_binary_layout is False:
interpolate_layout_from = interpolate_layout_dir = None
Expand Down Expand Up @@ -1299,7 +1165,7 @@ def run_from_ufos(self, ufos, output=(), **kwargs):
ufo_paths = [x.path for x in ufos]
else:
raise TypeError(
"UFOs parameter is neither a defcon.Font object, a path or a glob, "
"UFOs parameter is neither a ufoLib2.Font object, a path or a glob, "
f"nor a list of any of these: {ufos:r}."
)

Expand Down
3 changes: 0 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ fonttools[unicode,ufo,lxml]==4.41.1; platform_python_implementation == 'CPython'
fonttools[unicode,ufo]==4.41.1; platform_python_implementation != 'CPython'
glyphsLib==6.2.5
ufo2ft==2.32.0
MutatorMath==3.0.1
fontMath==0.9.3
defcon[lxml]==0.10.2; platform_python_implementation == 'CPython'
defcon==0.10.2; platform_python_implementation != 'CPython'
booleanOperations==0.9.0
ufoLib2==0.16.0
attrs==23.1.0
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"lxml": [
# "lxml>=4.2.4",
],
"mutatormath": ["MutatorMath>=2.1.2"],
"autohint": ["ttfautohint-py>=0.5.0"],
}
# use a special 'all' key as shorthand to includes all the extra dependencies
Expand Down
51 changes: 0 additions & 51 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,57 +71,6 @@ def test_interpolation_designspace_5(data_dir, tmp_path):
}


def test_interpolation_mutatormath(data_dir, tmp_path):
shutil.copytree(data_dir / "DesignspaceTest", tmp_path / "sources")

fontmake.__main__.main(
[
"-m",
str(tmp_path / "sources" / "DesignspaceTest.designspace"),
"-i",
"--use-mutatormath",
"--output-dir",
str(tmp_path),
]
)

assert {p.name for p in tmp_path.glob("*.*")} == {
"MyFont-Regular.ttf",
"MyFont-Regular.otf",
}

test_output_ttf = fontTools.ttLib.TTFont(tmp_path / "MyFont-Regular.ttf")
assert test_output_ttf["OS/2"].usWeightClass == 400
glyph = test_output_ttf["glyf"]["l"]
assert glyph.xMin == 50
assert glyph.xMax == 170

test_output_otf = fontTools.ttLib.TTFont(tmp_path / "MyFont-Regular.otf")
assert test_output_otf["OS/2"].usWeightClass == 400
glyph_set = test_output_otf.getGlyphSet()
charstrings = list(test_output_otf["CFF "].cff.values())[0].CharStrings
glyph = charstrings["l"]
x_min, _, x_max, _ = glyph.calcBounds(glyph_set)
assert x_min == 50
assert x_max == 170


def test_interpolation_mutatormath_source_layer(data_dir, tmp_path):
shutil.copytree(data_dir / "MutatorSans", tmp_path / "layertest")

with pytest.raises(SystemExit, match="sources with 'layer'"):
fontmake.__main__.main(
[
"-m",
str(tmp_path / "layertest" / "MutatorSans.designspace"),
"-i",
"--use-mutatormath",
"--output-dir",
str(tmp_path),
]
)


def test_interpolation_and_masters_as_instances(data_dir, tmp_path):
shutil.copytree(data_dir / "DesignspaceTest", tmp_path / "sources")

Expand Down

0 comments on commit ed0d281

Please sign in to comment.