diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 9bd77f0db..af2764646 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -3,9 +3,12 @@ from enum import IntEnum from fontTools import varLib +from fontTools.designspaceLib import DesignSpaceDocument +from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts from fontTools.otlLib.optimize.gpos import GPOS_COMPACT_MODE_ENV_KEY from ufo2ft.constants import SPARSE_OTF_MASTER_TABLES, SPARSE_TTF_MASTER_TABLES +from ufo2ft.errors import InvalidDesignSpaceData from ufo2ft.featureCompiler import ( MTI_FEATURES_PREFIX, FeatureCompiler, @@ -20,7 +23,7 @@ ) from ufo2ft.util import ( _getDefaultNotdefGlyph, - getDefaultMasterFont, + ensure_all_sources_have_names, init_kwargs, prune_unknown_kwargs, ) @@ -371,8 +374,7 @@ def compileInterpolatableTTFsFromDS(designSpaceDoc, **kwargs): if kwargs["inplace"]: result = designSpaceDoc else: - # TODO try a more efficient copy method that doesn't involve (de)serializing - result = designSpaceDoc.__class__.fromstring(designSpaceDoc.tostring()) + result = designSpaceDoc.deepcopyExceptFonts() for source, ttf in zip(result.sources, ttfs): source.font = ttf return result @@ -451,8 +453,7 @@ def compileInterpolatableOTFsFromDS(designSpaceDoc, **kwargs): if kwargs["inplace"]: result = designSpaceDoc else: - # TODO try a more efficient copy method that doesn't involve (de)serializing - result = designSpaceDoc.__class__.fromstring(designSpaceDoc.tostring()) + result = designSpaceDoc.deepcopyExceptFonts() for source, otf in zip(result.sources, otfs): source.font = otf @@ -466,7 +467,7 @@ def compileFeatures( glyphSet=None, featureCompilerClass=None, debugFeatureFile=None, - **kwargs + **kwargs, ): """Compile OpenType Layout features from `ufo` into FontTools OTL tables. If `ttFont` is None, a new TTFont object is created containing the new @@ -534,40 +535,78 @@ def compileVariableTTF(designSpaceDoc, **kwargs): Returns a new variable TTFont object. """ kwargs = init_kwargs(kwargs, compileVariableTTF_args) - baseUfo = getDefaultMasterFont(designSpaceDoc) + fonts = compileVariableTTFs(designSpaceDoc, **kwargs) + if len(fonts) != 1: + raise ValueError( + "Tried to build a DesignSpace version 5 with multiple variable " + "fonts using the old ufo2ft API `compileVariableTTF`. " + "Use the new API instead `compileVariableTTFs`" + ) + return next(iter(fonts.values())) - excludeVariationTables = kwargs.pop("excludeVariationTables") + +compileVariableTTFs_args = { + **compileVariableTTF_args, + **dict(variableFontNames=None), +} + + +def compileVariableTTFs(designSpaceDoc: DesignSpaceDocument, **kwargs): + """Create FontTools TrueType variable fonts for each variable font defined + in the given DesignSpaceDocument, using their UFO sources + with interpolatable outlines, using fontTools.varLib.build. + + *optimizeGvar*, if set to False, will not perform IUP optimization on the + generated 'gvar' table. + + *excludeVariationTables* is a list of sfnt table tags (str) that is passed on + to fontTools.varLib.build, to skip building some variation tables. + + *variableFontNames* is an optional list of names of variable fonts + to build. If not provided, all variable fonts listed in the given + designspace will by built. + + The rest of the arguments works the same as in the other compile functions. + + Returns a dictionary that maps each variable font filename to a new variable + TTFont object. If no variable fonts are defined in the Designspace, returns + an empty dictionary. + + .. versionadded:: 2.28.0 + """ + kwargs = init_kwargs(kwargs, compileVariableTTFs_args) optimizeGvar = kwargs.pop("optimizeGvar") + excludeVariationTables = kwargs.pop("excludeVariationTables") + variableFontNames = kwargs.pop("variableFontNames") - # FIXME: Hack until we get a fontTools config module. Disable GPOS - # compaction while building masters because the compaction will be undone - # anyway by varLib merge and then done again on the VF - gpos_compact_value = os.environ.pop(GPOS_COMPACT_MODE_ENV_KEY, None) - try: - ttfDesignSpace = compileInterpolatableTTFsFromDS( - designSpaceDoc, - **{ - **kwargs, - **dict( - useProductionNames=False, # will rename glyphs after varfont is built - # No need to post-process intermediate fonts. - postProcessorClass=None, - ), - }, - ) - finally: - if gpos_compact_value is not None: - os.environ[GPOS_COMPACT_MODE_ENV_KEY] = gpos_compact_value + # Pop inplace because we'll make a copy at this level so deeper functions + # don't need to worry + inplace = kwargs.pop("inplace") + if not inplace: + designSpaceDoc = designSpaceDoc.deepcopyExceptFonts() + + vfNameToBaseUfo = _compileNeededSources( + kwargs, designSpaceDoc, variableFontNames, compileInterpolatableTTFsFromDS + ) + + if not vfNameToBaseUfo: + return {} - logger.info("Building variable TTF font") + logger.info("Building variable TTF fonts: %s", ", ".join(vfNameToBaseUfo)) - varfont = varLib.build( - ttfDesignSpace, + vfNameToTTFont = varLib.build_many( + designSpaceDoc, exclude=excludeVariationTables, optimize=optimizeGvar, - )[0] + skip_vf=lambda vf_name: variableFontNames and vf_name not in variableFontNames, + ) + + for vfName, varfont in list(vfNameToTTFont.items()): + vfNameToTTFont[vfName] = call_postprocessor( + varfont, vfNameToBaseUfo[vfName], glyphSet=None, **kwargs + ) - return call_postprocessor(varfont, baseUfo, glyphSet=None, **kwargs) + return vfNameToTTFont compileVariableCFF2_args = { @@ -602,46 +641,153 @@ def compileVariableCFF2(designSpaceDoc, **kwargs): Returns a new variable TTFont object. """ kwargs = init_kwargs(kwargs, compileVariableCFF2_args) - baseUfo = getDefaultMasterFont(designSpaceDoc) + fonts = compileVariableCFF2s(designSpaceDoc, **kwargs) + if len(fonts) != 1: + raise ValueError( + "Tried to build a DesignSpace version 5 with multiple variable " + "fonts using the old ufo2ft API `compileVariableCFF2`. " + "Use the new API instead `compileVariableCFF2s`" + ) + return next(iter(fonts.values())) - excludeVariationTables = kwargs.pop("excludeVariationTables") - # FIXME: Hack until we get a fontTools config module. Disable GPOS - # compaction while building masters because the compaction will be undone - # anyway by varLib merge and then done again on the VF - gpos_compact_value = os.environ.pop(GPOS_COMPACT_MODE_ENV_KEY, None) - try: - otfDesignSpace = compileInterpolatableOTFsFromDS( - designSpaceDoc, - **{ - **kwargs, - **dict( - useProductionNames=False, # will rename glyphs after varfont is built - # No need to post-process intermediate fonts. - postProcessorClass=None, - ), - }, - ) - finally: - if gpos_compact_value is not None: - os.environ[GPOS_COMPACT_MODE_ENV_KEY] = gpos_compact_value +compileVariableCFF2s_args = { + **compileVariableCFF2_args, + **dict(variableFontNames=None), +} - logger.info("Building variable CFF2 font") +def compileVariableCFF2s(designSpaceDoc, **kwargs): + """Create FontTools CFF2 variable fonts for each variable font defined + in the given DesignSpaceDocument, using their UFO sources + with interpolatable outlines, using fontTools.varLib.build. + + *excludeVariationTables* is a list of sfnt table tags (str) that is passed on + to fontTools.varLib.build, to skip building some variation tables. + + *optimizeCFF* (int) defines whether the CFF charstrings should be + specialized and subroutinized. 1 (default) only enables the specialization; + 2 (default) does both specialization and subroutinization. The value 0 is supposed + to disable both optimizations, however it's currently unused, because fontTools + has some issues generating a VF with non-specialized CFF2 charstrings: + fonttools/fonttools#1979. + NOTE: Subroutinization of variable CFF2 requires the "cffsubr" extra requirement. + + *variableFontNames* is an optional list of filenames of variable fonts + to build. If not provided, all variable fonts listed in the given + designspace will by built. + + The rest of the arguments works the same as in the other compile functions. + + Returns a dictionary that maps each variable font filename to a new variable + TTFont object. + + .. versionadded:: 2.28.0 + """ + kwargs = init_kwargs(kwargs, compileVariableCFF2s_args) + excludeVariationTables = kwargs.pop("excludeVariationTables") optimizeCFF = CFFOptimization(kwargs.pop("optimizeCFF")) + variableFontNames = kwargs.pop("variableFontNames") + + # Pop inplace because we'll make a copy at this level so deeper functions + # don't need to worry + inplace = kwargs.pop("inplace") + if not inplace: + designSpaceDoc = designSpaceDoc.deepcopyExceptFonts() + + vfNameToBaseUfo = _compileNeededSources( + kwargs, designSpaceDoc, variableFontNames, compileInterpolatableOTFsFromDS + ) - varfont = varLib.build( - otfDesignSpace, + if not vfNameToBaseUfo: + logger.warning("No variable fonts to build") + return {} + + logger.info(f"Building variable CFF2 fonts: {', '.join(vfNameToBaseUfo)}") + + vfNameToTTFont = varLib.build_many( + designSpaceDoc, exclude=excludeVariationTables, # NOTE optimize=False won't change anything until this PR is merged # https://github.com/fonttools/fonttools/pull/1979 optimize=optimizeCFF >= CFFOptimization.SPECIALIZE, - )[0] - - return call_postprocessor( - varfont, - baseUfo, - glyphSet=None, - **kwargs, - optimizeCFF=optimizeCFF >= CFFOptimization.SUBROUTINIZE, + skip_vf=lambda vf_name: variableFontNames and vf_name not in variableFontNames, ) + + for vfName, varfont in list(vfNameToTTFont.items()): + vfNameToTTFont[vfName] = call_postprocessor( + varfont, + vfNameToBaseUfo[vfName], + glyphSet=None, + **kwargs, + optimizeCFF=optimizeCFF >= CFFOptimization.SUBROUTINIZE, + ) + + return vfNameToTTFont + + +def _compileNeededSources( + kwargs, designSpaceDoc, variableFontNames, compileInterpolatableFunc +): + # We'll need to map elements to TTFonts, to do so make sure that + # each has a name. + ensure_all_sources_have_names(designSpaceDoc) + + # Go through VFs to build and gather list of needed sources to compile + interpolableSubDocs = [ + subDoc for _location, subDoc in splitInterpolable(designSpaceDoc) + ] + vfNameToBaseUfo = {} + sourcesToCompile = set() + for subDoc in interpolableSubDocs: + for vfName, vfDoc in splitVariableFonts(subDoc): + if variableFontNames is not None and vfName not in variableFontNames: + # This VF is not needed so we don't need to compile its sources + continue + default_source = vfDoc.findDefault() + if default_source is None: + raise InvalidDesignSpaceData("No default source.") + vfNameToBaseUfo[vfName] = default_source.font + for source in vfDoc.sources: + sourcesToCompile.add(source.name) + + # Match sources to compile to their Descriptor in the original designspace + sourcesByName = {} + for source in designSpaceDoc.sources: + if source.name in sourcesToCompile: + sourcesByName[source.name] = source + + # Compile all needed sources in each interpolable subspace to make sure + # they're all compatible; that also ensures that sub-vfs within the same + # interpolable sub-space are compatible too. + for subDoc in interpolableSubDocs: + # Only keep the sources that we've identified earlier as need-to-compile + subDoc.sources = [s for s in subDoc.sources if s.name in sourcesToCompile] + if not subDoc.sources: + continue + + # FIXME: Hack until we get a fontTools config module. Disable GPOS + # compaction while building masters because the compaction will be undone + # anyway by varLib merge and then done again on the VF + gpos_compact_value = os.environ.pop(GPOS_COMPACT_MODE_ENV_KEY, None) + try: + ttfDesignSpace = compileInterpolatableFunc( + subDoc, + **{ + **kwargs, + **dict( + useProductionNames=False, # will rename glyphs after varfont is built + # No need to post-process intermediate fonts. + postProcessorClass=None, + ), + }, + ) + finally: + if gpos_compact_value is not None: + os.environ[GPOS_COMPACT_MODE_ENV_KEY] = gpos_compact_value + + # Stick TTFs back into original big DS + for ttfSource in ttfDesignSpace.sources: + sourcesByName[ttfSource.name].font = ttfSource.font + + return vfNameToBaseUfo diff --git a/Lib/ufo2ft/util.py b/Lib/ufo2ft/util.py index dd9687231..0a1511b39 100644 --- a/Lib/ufo2ft/util.py +++ b/Lib/ufo2ft/util.py @@ -3,8 +3,10 @@ import re from copy import deepcopy from inspect import currentframe, getfullargspec +from typing import Set from fontTools import subset, ttLib, unicodedata +from fontTools.designspaceLib import DesignSpaceDocument from fontTools.feaLib.builder import addOpenTypeFeatures from fontTools.misc.fixedTools import otRound from fontTools.misc.transform import Identity, Transform @@ -528,3 +530,19 @@ def prune_unknown_kwargs(kwargs, *callables): for func in callables: known_args.update(getfullargspec(func).args) return {k: v for k, v in kwargs.items() if k in known_args} + + +def ensure_all_sources_have_names(doc: DesignSpaceDocument) -> None: + """Change in-place the given document to make sure that all elements + have a unique name assigned. + + This may rename sources with a "temp_master.N" name, designspaceLib's default + stand-in. + """ + used_names: Set[str] = set() + counter = 0 + for source in doc.sources: + while source.name is None or source.name in used_names: + source.name = f"temp_master.{counter}" + counter += 1 + used_names.add(source.name) diff --git a/requirements.txt b/requirements.txt index 70a585146..c4113ed14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -fonttools[lxml,ufo]==4.29.1 +fonttools[lxml,ufo]==4.33.3 defcon==0.10.0 cu2qu==1.6.7.post1 compreffor==0.5.1.post1 diff --git a/setup.py b/setup.py index 6ac04665b..968d88aab 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup_requires=pytest_runner + wheel + ["setuptools_scm"], tests_require=["pytest>=2.8"], install_requires=[ - "fonttools[ufo]>=4.28.5", + "fonttools[ufo]>=4.33.3", "cu2qu>=1.6.7", "cffsubr>=0.2.8", "booleanOperations>=0.9.0", diff --git a/tests/conftest.py b/tests/conftest.py index 9ee1acfd0..103618523 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -91,3 +91,55 @@ def designspace(layertestrgufo, layertestbdufo): ds.addSource(s3) return ds + + +@pytest.fixture +def designspace_v5(FontClass): + def draw_rectangle(pen, x_offset, y_offset): + pen.moveTo((0 + x_offset, 0 + y_offset)) + pen.lineTo((10 + x_offset, 0 + y_offset)) + pen.lineTo((10 + x_offset, 10 + y_offset)) + pen.lineTo((0 + x_offset, 10 + y_offset)) + pen.closePath() + + def draw_something(glyph, number, is_sans): + # Ensure Sans and Serif sources are incompatible to make sure that the + # DS5 code treats them separately when using e.g. cu2qu. Use some number + # to offset the drawings so we get some variation. + if is_sans: + draw_rectangle(glyph.getPen(), 10 * number, 0) + else: + draw_rectangle(glyph.getPen(), -10 * number, -20) + draw_rectangle(glyph.getPen(), 10 * number, 20) + + ds5 = designspaceLib.DesignSpaceDocument.fromfile( + "tests/data/DSv5/test_v5_MutatorSans_and_Serif.designspace" + ) + + sources = {} + # Create base UFOs + for index, source in enumerate(ds5.sources): + if source.layerName is not None: + continue + font = FontClass() + for name in ("I", "S", "I.narrow", "S.closed", "a"): + glyph = font.newGlyph(name) + draw_something(glyph, index, "Serif" not in source.filename) + font.lib["public.glyphOrder"] = sorted(font.keys()) + sources[source.filename] = font + + # Fill in sparse UFOs + for index, source in enumerate(ds5.sources): + if source.layerName is None: + continue + font = sources[source.filename] + layer = font.newLayer(source.layerName) + for name in ("I", "S", "I.narrow", "S.closed"): + glyph = layer.newGlyph(name) + draw_something(glyph, index, "Serif" not in source.filename) + + # Assign UFOs to their attribute + for source in ds5.sources: + source.font = sources[source.filename] + + return ds5 diff --git a/tests/data/DSv5/MutatorSansVariable_Weight-CFF2.ttx b/tests/data/DSv5/MutatorSansVariable_Weight-CFF2.ttx new file mode 100644 index 000000000..30075ab7e --- /dev/null +++ b/tests/data/DSv5/MutatorSansVariable_Weight-CFF2.ttx @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Weight + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 -200 rmoveto + 400 1000 -400 -1000 hlineto + 50 50 rmoveto + 900 300 -900 -300 vlineto + + + 0 40 10 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 40 10 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 40 10 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 40 10 1 blend + hmoveto + 10 10 -10 hlineto + + + 1 vsindex + 0 10 1 blend + hmoveto + 10 10 -10 hlineto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 300.0 + 300.0 + 700.0 + 256 + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSansVariable_Weight-TTF.ttx b/tests/data/DSv5/MutatorSansVariable_Weight-TTF.ttx new file mode 100644 index 000000000..a2d8d3d14 --- /dev/null +++ b/tests/data/DSv5/MutatorSansVariable_Weight-TTF.ttx @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Weight + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 300.0 + 300.0 + 700.0 + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx b/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx new file mode 100644 index 000000000..cf25c07c7 --- /dev/null +++ b/tests/data/DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx @@ -0,0 +1,999 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + Sans Bold Extended + + + MutatorMathTest-SansBoldExtended + + + Sans Medium + + + MutatorMathTest-SansMedium + + + MutatorMathTest-SansMedium + + + Sans Bold + + + MutatorMathTest-SansBold + + + Sans Medium Extended + + + MutatorMathTest-SansMediumExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + Normal + + + Extended + + + S1 + + + S2 + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Weight + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + Sans Bold Extended + + + MutatorMathTest-SansBoldExtended + + + Sans Medium + + + MutatorMathTest-SansMedium + + + MutatorMathTest-SansMedium + + + Sans Bold + + + MutatorMathTest-SansBold + + + Sans Medium Extended + + + MutatorMathTest-SansMediumExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + Normal + + + Extended + + + S1 + + + S2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 -200 rmoveto + 400 1000 -400 -1000 hlineto + 50 50 rmoveto + 900 300 -900 -300 vlineto + + + 0 40 10 20 -10 0 14 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 40 10 20 -10 0 14 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 40 10 20 -10 0 14 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 40 10 20 -10 0 14 1 blend + hmoveto + 10 10 -10 hlineto + + + 1 vsindex + 0 10 20 0 1 blend + hmoveto + 10 10 -10 hlineto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 300.0 + 300.0 + 700.0 + 256 + + + + + wdth + 0x0 + 50.0 + 50.0 + 200.0 + 257 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSansVariable_Weight_Width-TTF.ttx b/tests/data/DSv5/MutatorSansVariable_Weight_Width-TTF.ttx new file mode 100644 index 000000000..ce057704e --- /dev/null +++ b/tests/data/DSv5/MutatorSansVariable_Weight_Width-TTF.ttx @@ -0,0 +1,1021 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + Sans Bold Extended + + + MutatorMathTest-SansBoldExtended + + + Sans Medium + + + MutatorMathTest-SansMedium + + + MutatorMathTest-SansMedium + + + Sans Bold + + + MutatorMathTest-SansBold + + + Sans Medium Extended + + + MutatorMathTest-SansMediumExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + Normal + + + Extended + + + S1 + + + S2 + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Weight + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Bold Condensed + + + MutatorMathTest-SansBoldCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + Sans Bold Extended + + + MutatorMathTest-SansBoldExtended + + + Sans Medium + + + MutatorMathTest-SansMedium + + + MutatorMathTest-SansMedium + + + Sans Bold + + + MutatorMathTest-SansBold + + + Sans Medium Extended + + + MutatorMathTest-SansMediumExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + Medium + + + Bold + + + width + + + Condensed + + + Normal + + + Extended + + + S1 + + + S2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 300.0 + 300.0 + 700.0 + 256 + + + + + wdth + 0x0 + 50.0 + 50.0 + 200.0 + 257 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSansVariable_Width-CFF2.ttx b/tests/data/DSv5/MutatorSansVariable_Width-CFF2.ttx new file mode 100644 index 000000000..653d42f31 --- /dev/null +++ b/tests/data/DSv5/MutatorSansVariable_Width-CFF2.ttx @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 -200 rmoveto + 400 1000 -400 -1000 hlineto + 50 50 rmoveto + 900 300 -900 -300 vlineto + + + 0 20 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 20 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 20 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 20 1 blend + hmoveto + 10 10 -10 hlineto + + + 0 20 1 blend + hmoveto + 10 10 -10 hlineto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wdth + 0x0 + 50.0 + 50.0 + 200.0 + 256 + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSansVariable_Width-TTF.ttx b/tests/data/DSv5/MutatorSansVariable_Width-TTF.ttx new file mode 100644 index 000000000..73df35863 --- /dev/null +++ b/tests/data/DSv5/MutatorSansVariable_Width-TTF.ttx @@ -0,0 +1,616 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Width + + + Sans Light Condensed + + + MutatorMathTest-SansLightCondensed + + + Sans Light Extended + + + MutatorMathTest-SansLightExtended + + + MutatorMathTest-SansLightCondensed + + + Regular + + + serif + + + Sans + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wdth + 0x0 + 50.0 + 50.0 + 200.0 + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSerifVariable_Width-CFF2.ttx b/tests/data/DSv5/MutatorSerifVariable_Width-CFF2.ttx new file mode 100644 index 000000000..5f1cc8c0b --- /dev/null +++ b/tests/data/DSv5/MutatorSerifVariable_Width-CFF2.ttx @@ -0,0 +1,464 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Width + + + Serif Light Condensed + + + MutatorMathTest-SerifLightCondensed + + + Regular + + + serif + + + Serif + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Width + + + Serif Light Condensed + + + MutatorMathTest-SerifLightCondensed + + + Regular + + + serif + + + Serif + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 50 -200 rmoveto + 400 1000 -400 -1000 hlineto + 50 50 rmoveto + 900 300 -900 -300 vlineto + + + -70 -10 1 blend + -20 rmoveto + 10 10 -10 hlineto + 140 20 1 blend + 30 rmoveto + 10 10 -10 hlineto + + + -70 -10 1 blend + -20 rmoveto + 10 10 -10 hlineto + 140 20 1 blend + 30 rmoveto + 10 10 -10 hlineto + + + -70 -10 1 blend + -20 rmoveto + 10 10 -10 hlineto + 140 20 1 blend + 30 rmoveto + 10 10 -10 hlineto + + + -70 -10 1 blend + -20 rmoveto + 10 10 -10 hlineto + 140 20 1 blend + 30 rmoveto + 10 10 -10 hlineto + + + -70 -10 1 blend + -20 rmoveto + 10 10 -10 hlineto + 140 20 1 blend + 30 rmoveto + 10 10 -10 hlineto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wdth + 0x0 + 50.0 + 50.0 + 200.0 + 256 + + + + + + + + + + diff --git a/tests/data/DSv5/MutatorSerifVariable_Width-TTF.ttx b/tests/data/DSv5/MutatorSerifVariable_Width-TTF.ttx new file mode 100644 index 000000000..634b24180 --- /dev/null +++ b/tests/data/DSv5/MutatorSerifVariable_Width-TTF.ttx @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Width + + + Serif Light Condensed + + + MutatorMathTest-SerifLightCondensed + + + Regular + + + serif + + + Serif + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Width + + + Serif Light Condensed + + + MutatorMathTest-SerifLightCondensed + + + Regular + + + serif + + + Serif + + + weight + + + Light + + + width + + + Condensed + + + Normal + + + Extended + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wdth + 0x0 + 50.0 + 50.0 + 200.0 + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/DSv5/test_v5_MutatorSans_and_Serif.designspace b/tests/data/DSv5/test_v5_MutatorSans_and_Serif.designspace new file mode 100644 index 000000000..477b4bf92 --- /dev/null +++ b/tests/data/DSv5/test_v5_MutatorSans_and_Serif.designspace @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/integration_test.py b/tests/integration_test.py index 426d1d31e..fc1a8bb6e 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -11,7 +11,9 @@ compileOTF, compileTTF, compileVariableCFF2, + compileVariableCFF2s, compileVariableTTF, + compileVariableTTFs, ) from ufo2ft.constants import KEEP_GLYPH_NAMES from ufo2ft.filters import TransformationsFilter @@ -328,6 +330,80 @@ def test_compileInterpolatableTTFs(self, FontClass): assert pen1.bounds[2] == pen2.bounds[2] assert pen1.bounds[3] + 10 == pen2.bounds[3] + def test_compileVariableTTFs(self, designspace_v5): + fonts = compileVariableTTFs(designspace_v5) + + # NOTE: Test dumps were generated like this: + # for k, font in fonts.items(): + # font.recalcTimestamp = False + # font["head"].created, font["head"].modified = 3570196637, 3601822698 + # font["head"].checkSumAdjustment = 0x12345678 + # font.saveXML(f"tests/data/DSv5/{k}-TTF.ttx") + + assert set(fonts.keys()) == { + "MutatorSansVariable_Weight_Width", + "MutatorSansVariable_Weight", + "MutatorSansVariable_Width", + "MutatorSerifVariable_Width", + } + # The STAT table is set to [SRIF=0, wght=[300, 700], wdth=[50, 200]] + S1 + S2 + expectTTX( + fonts["MutatorSansVariable_Weight_Width"], + "DSv5/MutatorSansVariable_Weight_Width-TTF.ttx", + ) + # The STAT table is set to [SRIF=0, wght=[300, 700], wdth=50] + expectTTX( + fonts["MutatorSansVariable_Weight"], + "DSv5/MutatorSansVariable_Weight-TTF.ttx", + ) + # The STAT table is set to [SRIF=0, wght=300, wdth=[50, 200]] + expectTTX( + fonts["MutatorSansVariable_Width"], + "DSv5/MutatorSansVariable_Width-TTF.ttx", + ) + # The STAT table is set to [SRIF=1, wght=300, wdth=[50, 200]] + expectTTX( + fonts["MutatorSerifVariable_Width"], + "DSv5/MutatorSerifVariable_Width-TTF.ttx", + ) + + def test_compileVariableCFF2s(self, designspace_v5): + fonts = compileVariableCFF2s(designspace_v5) + + # NOTE: Test dumps were generated like this: + # for k, font in fonts.items(): + # font.recalcTimestamp = False + # font["head"].created, font["head"].modified = 3570196637, 3601822698 + # font["head"].checkSumAdjustment = 0x12345678 + # font.saveXML(f"tests/data/DSv5/{k}-CFF2.ttx") + + assert set(fonts.keys()) == { + "MutatorSansVariable_Weight_Width", + "MutatorSansVariable_Weight", + "MutatorSansVariable_Width", + "MutatorSerifVariable_Width", + } + # The STAT table is set to [SRIF=0, wght=[300, 700], wdth=[50, 200]] + S1 + S2 + expectTTX( + fonts["MutatorSansVariable_Weight_Width"], + "DSv5/MutatorSansVariable_Weight_Width-CFF2.ttx", + ) + # The STAT table is set to [SRIF=0, wght=[300, 700], wdth=50] + expectTTX( + fonts["MutatorSansVariable_Weight"], + "DSv5/MutatorSansVariable_Weight-CFF2.ttx", + ) + # The STAT table is set to [SRIF=0, wght=300, wdth=[50, 200]] + expectTTX( + fonts["MutatorSansVariable_Width"], + "DSv5/MutatorSansVariable_Width-CFF2.ttx", + ) + # The STAT table is set to [SRIF=1, wght=300, wdth=[50, 200]] + expectTTX( + fonts["MutatorSerifVariable_Width"], + "DSv5/MutatorSerifVariable_Width-CFF2.ttx", + ) + if __name__ == "__main__": sys.exit(pytest.main(sys.argv))