Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use ufo2ft.compileVariable{TTF,CFF2} #551

Merged
merged 9 commits into from
May 28, 2019
17 changes: 14 additions & 3 deletions Lib/fontmake/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def _loadFeatureWriters(parser, specs):
return feature_writers


def exclude_args(parser, args, excluded_args, target):
def exclude_args(parser, args, excluded_args, target, positive=True):
"""Delete options that are not appropriate for a following code path; exit
with an error if excluded options were passed in by the user.

Expand All @@ -54,8 +54,10 @@ def exclude_args(parser, args, excluded_args, target):
for argname in excluded_args:
if argname not in args:
continue
if args[argname]:
optname = "--%s" % argname.replace("_", "-")
if bool(args[argname]) is positive:
optname = "--{}{}".format(
"" if positive else "no-", argname.replace("_", "-")
)
parser.error(msg % (optname, target))
del args[argname]

Expand Down Expand Up @@ -262,6 +264,13 @@ def main(args=None):
help="0 disables all optimizations; 1 specializes the CFF charstring "
"operators; 2 (default) also enables subroutinization",
)
contourGroup.add_argument(
"--no-optimize-gvar",
dest="optimize_gvar",
action="store_false",
help="Do not perform IUP optimization on variable font's 'gvar' table. "
"(only works with 'variable' TrueType-flavored output)",
)

layoutGroup = parser.add_argument_group(title="Handling of OpenType Layout")
layoutGroup.add_argument(
Expand Down Expand Up @@ -367,6 +376,8 @@ def main(args=None):
["interpolate", "masters_as_instances", "interpolate_binary_layout"],
"variable output",
)
else:
exclude_args(parser, args, ["optimize_gvar"], "static output", positive=False)

try:
project = FontProject(
Expand Down
92 changes: 48 additions & 44 deletions Lib/fontmake/font_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from defcon.objects.base import setUfoLibReadValidate, setUfoLibWriteValidate
from fontmake.errors import FontmakeError, TTFAError
from fontmake.ttfautohint import ttfautohint
from fontTools import designspaceLib, varLib
from fontTools import designspaceLib
from fontTools.misc.loggingTools import Timer, configLogger
from fontTools.misc.py23 import basestring, tobytes, zip
from fontTools.misc.transform import Transform
Expand Down Expand Up @@ -273,15 +273,20 @@ def build_ttfs(self, ufos, **kwargs):

@staticmethod
def _load_designspace_sources(designspace):
# set source.font attributes, but only load fonts once
masters = {}
for source in designspace.sources:
if source.path in masters:
source.font = masters[source.path]
else:
assert source.path is not None
source.font = Font(source.path)
masters[source.path] = source.font
if hasattr(designspace, "__fspath__"):
ds_path = designspace.__fspath__()
if isinstance(designspace, basestring):
ds_path = designspace
else:
# reload designspace from its path so we have a new copy
# that can be modified in-place.
ds_path = designspace.path
if ds_path is not None:
designspace = designspaceLib.DesignSpaceDocument.fromfile(ds_path)

designspace.loadSourceFonts(opener=Font)

return designspace

def _build_interpolatable_masters(
self,
Expand All @@ -294,18 +299,7 @@ def _build_interpolatable_masters(
cff_round_tolerance=None,
**kwargs
):
if hasattr(designspace, "__fspath__"):
ds_path = designspace.__fspath__()
if isinstance(designspace, basestring):
ds_path = designspace
else:
# reload designspace from its path so we have a new copy
# that can be modified in-place.
ds_path = designspace.path
if ds_path is not None:
designspace = designspaceLib.DesignSpaceDocument.fromfile(ds_path)

self._load_designspace_sources(designspace)
designspace = self._load_designspace_sources(designspace)

if ttf:
return ufo2ft.compileInterpolatableTTFsFromDS(
Expand Down Expand Up @@ -342,36 +336,49 @@ def build_variable_font(
designspace,
output_path=None,
output_dir=None,
master_bin_dir=None,
ttf=True,
optimize_gvar=True,
use_production_names=None,
reverse_direction=True,
conversion_error=None,
feature_writers=None,
cff_round_tolerance=None,
**kwargs
):
"""Build OpenType variable font from masters in a designspace."""
assert not (output_path and output_dir), "mutually exclusive args"

ext = "ttf" if ttf else "otf"

if hasattr(designspace, "__fspath__"):
designspace = designspace.__fspath__()
if isinstance(designspace, basestring):
designspace = designspaceLib.DesignSpaceDocument.fromfile(designspace)
if master_bin_dir is None:
master_bin_dir = self._output_dir(ext, interpolatable=True)
finder = partial(_varLib_finder, directory=master_bin_dir)
else:
assert all(isinstance(s.font, TTFont) for s in designspace.sources)
finder = lambda s: s # noqa: E731
designspace = self._load_designspace_sources(designspace)

if output_path is None:
output_path = (
os.path.splitext(os.path.basename(designspace.path))[0] + "-VF"
)
ext = "ttf" if ttf else "otf"
output_path = self._output_path(
output_path, ext, is_variable=True, output_dir=output_dir
)

logger.info("Building variable font " + output_path)

font, _, _ = varLib.build(designspace, finder)
if ttf:
font = ufo2ft.compileVariableTTF(
designspace,
featureWriters=feature_writers,
useProductionNames=use_production_names,
cubicConversionError=conversion_error,
reverseDirection=reverse_direction,
optimizeGvar=optimize_gvar,
inplace=True,
)
else:
font = ufo2ft.compileVariableCFF2(
designspace,
featureWriters=feature_writers,
useProductionNames=use_production_names,
roundTolerance=cff_round_tolerance,
inplace=True,
)

font.save(output_path)

Expand Down Expand Up @@ -915,28 +922,25 @@ def _run_from_designspace_interpolatable(
ttf_designspace = otf_designspace = None

if "variable" in outputs:
ttf_designspace = self.build_interpolatable_ttfs(designspace, **kwargs)
self.build_variable_font(
ttf_designspace, output_path=output_path, output_dir=output_dir
designspace, output_path=output_path, output_dir=output_dir, **kwargs
)

if "ttf-interpolatable" in outputs:
if ttf_designspace is None:
ttf_designspace = self.build_interpolatable_ttfs(designspace, **kwargs)
ttf_designspace = self.build_interpolatable_ttfs(designspace, **kwargs)
self._save_interpolatable_fonts(ttf_designspace, output_dir, ttf=True)

if "variable-cff2" in outputs:
otf_designspace = self.build_interpolatable_otfs(designspace, **kwargs)
self.build_variable_font(
otf_designspace,
designspace,
output_path=output_path,
output_dir=output_dir,
ttf=False,
**kwargs
)

if "otf-interpolatable" in outputs:
if otf_designspace is None:
otf_designspace = self.build_interpolatable_otfs(designspace, **kwargs)
otf_designspace = self.build_interpolatable_otfs(designspace, **kwargs)
self._save_interpolatable_fonts(otf_designspace, output_dir, ttf=False)

def run_from_ufos(self, ufos, output=(), **kwargs):
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
fonttools[lxml,unicode,ufo]==3.41.0
fonttools[lxml,unicode,ufo]==3.42.0
cu2qu==1.6.5
glyphsLib==3.3.1
ufo2ft[pathops]==2.8.0
ufo2ft[pathops]==2.9.0
MutatorMath==2.1.2
defcon[lxml]==0.6.0
booleanOperations==0.8.2
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@
entry_points={"console_scripts": ["fontmake = fontmake.__main__:main"]},
setup_requires=wheel + ["setuptools_scm"],
install_requires=[
"fonttools[ufo,lxml,unicode]>=3.41.0",
"fonttools[ufo,lxml,unicode]>=3.42.0",
"cu2qu>=1.6.5",
"glyphsLib>=3.3.1",
"ufo2ft>=2.8.0",
"ufo2ft>=2.9.0",
"MutatorMath>=2.1.2",
"defcon[lxml]>=0.6.0",
"booleanOperations>=0.8.2",
Expand Down
9 changes: 6 additions & 3 deletions test/test_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,21 @@ def test_build_otfs(self, mock_build_ttfs, mock_build_otfs):


class TestOutputFileName(unittest.TestCase):
@patch("fontTools.varLib.build")
@patch("ufo2ft.compileVariableTTF")
@patch("fontTools.ttLib.TTFont.save")
@patch("fontTools.designspaceLib.DesignSpaceDocument.fromfile")
def test_variable_output_filename(
self, mock_DesignSpaceDocument_fromfile, mock_TTFont_save, mock_varLib_build
self,
mock_DesignSpaceDocument_fromfile,
mock_TTFont_save,
mock_compileVariableTTF,
):
project = FontProject()
path = "path/to/designspace.designspace"
doc = DesignSpaceDocument()
doc.path = path
mock_DesignSpaceDocument_fromfile.return_value = doc
mock_varLib_build.return_value = TTFont(), None, None
mock_compileVariableTTF.return_value = TTFont()
project.build_variable_font(path)
self.assertTrue(mock_TTFont_save.called)
self.assertTrue(mock_TTFont_save.call_count == 1)
Expand Down