From 1bf578a8678f44270afde16044e9d75098c7908a Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 29 Nov 2023 15:12:29 +0000 Subject: [PATCH 01/18] Refactor into compilers --- Lib/ufo2ft/__init__.py | 843 +----------------- Lib/ufo2ft/compilers/__init__.py | 0 Lib/ufo2ft/compilers/baseCompiler.py | 306 +++++++ .../compilers/interpolatableOTFCompiler.py | 93 ++ .../compilers/interpolatableTTFCompiler.py | 117 +++ Lib/ufo2ft/compilers/otfCompiler.py | 95 ++ Lib/ufo2ft/compilers/ttfCompiler.py | 57 ++ Lib/ufo2ft/compilers/variableCFF2sCompiler.py | 45 + Lib/ufo2ft/compilers/variableTTFsCompiler.py | 50 ++ Lib/ufo2ft/constants.py | 8 + 10 files changed, 789 insertions(+), 825 deletions(-) create mode 100644 Lib/ufo2ft/compilers/__init__.py create mode 100644 Lib/ufo2ft/compilers/baseCompiler.py create mode 100644 Lib/ufo2ft/compilers/interpolatableOTFCompiler.py create mode 100644 Lib/ufo2ft/compilers/interpolatableTTFCompiler.py create mode 100644 Lib/ufo2ft/compilers/otfCompiler.py create mode 100644 Lib/ufo2ft/compilers/ttfCompiler.py create mode 100644 Lib/ufo2ft/compilers/variableCFF2sCompiler.py create mode 100644 Lib/ufo2ft/compilers/variableTTFsCompiler.py diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 72102292c..51ce9a2bd 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -1,582 +1,34 @@ -import logging -import os -from collections import defaultdict -from enum import IntEnum - -from fontTools import varLib -from fontTools.designspaceLib import DesignSpaceDocument -from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts -from fontTools.misc.loggingTools import Timer -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, - MtiFeatureCompiler, -) -from ufo2ft.outlineCompiler import OutlineOTFCompiler, OutlineTTFCompiler -from ufo2ft.postProcessor import PostProcessor -from ufo2ft.preProcessor import ( - OTFPreProcessor, - TTFInterpolatablePreProcessor, - TTFPreProcessor, -) -from ufo2ft.util import ( - _notdefGlyphFallback, - colrClipBoxQuantization, - ensure_all_sources_have_names, - init_kwargs, - location_to_string, - prune_unknown_kwargs, -) - -try: - from ._version import version as __version__ -except ImportError: - __version__ = "0.0.0+unknown" - - -logger = logging.getLogger(__name__) -timer = Timer(logging.getLogger("ufo2ft.timer"), level=logging.DEBUG) - - -class CFFOptimization(IntEnum): - NONE = 0 - SPECIALIZE = 1 - SUBROUTINIZE = 2 - - -@timer("preprocess UFO") -def call_preprocessor(ufo_or_ufos, *, preProcessorClass, **kwargs): - logger.info("Pre-processing glyphs") - if kwargs["skipExportGlyphs"] is None: - if isinstance(ufo_or_ufos, (list, tuple)): - kwargs["skipExportGlyphs"] = set() - for ufo in ufo_or_ufos: - kwargs["skipExportGlyphs"].update( - ufo.lib.get("public.skipExportGlyphs", []) - ) - else: - kwargs["skipExportGlyphs"] = ufo_or_ufos.lib.get( - "public.skipExportGlyphs", [] - ) - - # Preprocessors expect this parameter under a different name. - if "cubicConversionError" in kwargs: - kwargs["conversionError"] = kwargs.pop("cubicConversionError") - - callables = [preProcessorClass] - if hasattr(preProcessorClass, "initDefaultFilters"): - callables.append(preProcessorClass.initDefaultFilters) - preProcessor = preProcessorClass( - ufo_or_ufos, **prune_unknown_kwargs(kwargs, *callables) - ) - return preProcessor.process() - - -@timer("compile a basic TTF") -def call_outline_compiler(ufo, glyphSet, *, outlineCompilerClass, **kwargs): - kwargs = prune_unknown_kwargs(kwargs, outlineCompilerClass) - outlineCompiler = outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) - return outlineCompiler.compile() - - -def call_postprocessor(otf, ufo, glyphSet, *, postProcessorClass, **kwargs): - with timer("postprocess TTF"): - if postProcessorClass is not None: - postProcessor = postProcessorClass(otf, ufo, glyphSet=glyphSet) - kwargs = prune_unknown_kwargs(kwargs, postProcessor.process) - otf = postProcessor.process(**kwargs) - return otf - - -base_args = dict( - postProcessorClass=PostProcessor, - featureCompilerClass=None, - featureWriters=None, - filters=None, - glyphOrder=None, - useProductionNames=None, - removeOverlaps=False, - overlapsBackend=None, - inplace=False, - layerName=None, - skipExportGlyphs=None, - debugFeatureFile=None, - notdefGlyph=None, - colrLayerReuse=True, - colrAutoClipBoxes=True, - colrClipBoxQuantization=colrClipBoxQuantization, # Callable[[Font], int] - feaIncludeDir=None, -) - -compileOTF_args = { - **base_args, - **dict( - preProcessorClass=OTFPreProcessor, - outlineCompilerClass=OutlineOTFCompiler, - optimizeCFF=CFFOptimization.SUBROUTINIZE, - roundTolerance=None, - cffVersion=1, - subroutinizer=None, - _tables=None, - extraSubstitutions=None, - ), -} - - -def compileOTF(ufo, **kwargs): - """Create FontTools CFF font from a UFO. - - *removeOverlaps* performs a union operation on all the glyphs' contours. - - *optimizeCFF* (int) defines whether the CFF charstrings should be - specialized and subroutinized. By default both optimization are enabled. - A value of 0 disables both; 1 only enables the specialization; 2 (default) - does both specialization and subroutinization. - - *roundTolerance* (float) controls the rounding of point coordinates. - It is defined as the maximum absolute difference between the original - float and the rounded integer value. - By default, all floats are rounded to integer (tolerance 0.5); a value - of 0 completely disables rounding; values in between only round floats - which are close to their integral part within the tolerated range. - - *featureWriters* argument is a list of BaseFeatureWriter subclasses or - pre-initialized instances. Features will be written by each feature - writer in the given order. If featureWriters is None, the default - feature writers [KernFeatureWriter, MarkFeatureWriter] are used. - - *filters* argument is a list of BaseFilters subclasses or pre-initialized - instances. Filters with 'pre' attribute set to True will be pre-filters - called before the default filters, otherwise they will be post-filters, - called after the default filters. - Filters will modify glyphs or the glyph set. The default filters cannot - be disabled. - - *useProductionNames* renames glyphs in TrueType 'post' or OpenType 'CFF ' - tables based on the 'public.postscriptNames' mapping in the UFO lib, - if present. Otherwise, uniXXXX names are generated from the glyphs' - unicode values. The default value (None) will first check if the UFO lib - has the 'com.github.googlei18n.ufo2ft.useProductionNames' key. If this - is missing or True (default), the glyphs are renamed. Set to False - to keep the original names. - - **inplace** (bool) specifies whether the filters should modify the input - UFO's glyphs, a copy should be made first. - - *layerName* specifies which layer should be compiled. When compiling something - other than the default layer, feature compilation is skipped. - - *skipExportGlyphs* is a list or set of glyph names to not be exported to the - final font. If these glyphs are used as components in any other glyph, those - components get decomposed. If the parameter is not passed in, the UFO's - "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, - all glyphs are exported. UFO groups and kerning will be pruned of skipped - glyphs. - - *cffVersion* (int) is the CFF format, choose between 1 (default) and 2. - - *subroutinizer* (Optional[str]) is the name of the library to use for - compressing CFF charstrings, if subroutinization is enabled by optimizeCFF - parameter. Choose between "cffsubr" or "compreffor". - By default "cffsubr" is used for both CFF 1 and CFF 2. - NOTE: cffsubr is required for subroutinizing CFF2 tables, as compreffor - currently doesn't support it. - """ - kwargs = init_kwargs(kwargs, compileOTF_args) - glyphSet = call_preprocessor(ufo, **kwargs) - - logger.info("Building OpenType tables") - optimizeCFF = CFFOptimization(kwargs.pop("optimizeCFF")) - tables = kwargs.pop("_tables") - otf = call_outline_compiler( - ufo, - glyphSet, - **kwargs, - optimizeCFF=optimizeCFF >= CFFOptimization.SPECIALIZE, - tables=tables, - ) - - # Only the default layer is likely to have all glyphs used in feature code. - if kwargs["layerName"] is None: - compileFeatures(ufo, otf, glyphSet=glyphSet, **kwargs) - - return call_postprocessor( - otf, - ufo, - glyphSet, - **kwargs, - optimizeCFF=optimizeCFF >= CFFOptimization.SUBROUTINIZE, - ) - - -compileTTF_args = { - **base_args, - **dict( - preProcessorClass=TTFPreProcessor, - outlineCompilerClass=OutlineTTFCompiler, - convertCubics=True, - cubicConversionError=None, - reverseDirection=True, - rememberCurveType=True, - flattenComponents=False, - autoUseMyMetrics=True, - dropImpliedOnCurves=False, - allQuadratic=True, - ), -} +from ufo2ft.compilers.interpolatableOTFCompiler import InterpolatableOTFCompiler +from ufo2ft.compilers.interpolatableTTFCompiler import InterpolatableTTFCompiler +from ufo2ft.compilers.otfCompiler import OTFCompiler +from ufo2ft.compilers.ttfCompiler import TTFCompiler +from ufo2ft.compilers.variableCFF2sCompiler import VariableCFF2sCompiler +from ufo2ft.compilers.variableTTFsCompiler import VariableTTFsCompiler +from ufo2ft.constants import CFFOptimization # noqa: F401 (fontmake uses it) def compileTTF(ufo, **kwargs): - """Create FontTools TrueType font from a UFO. - - *removeOverlaps* performs a union operation on all the glyphs' contours. - - *flattenComponents* un-nests glyphs so that they have at most one level of - components. - - *convertCubics* and *cubicConversionError* specify how the conversion from cubic - to quadratic curves should be handled. - - *layerName* specifies which layer should be compiled. When compiling something - other than the default layer, feature compilation is skipped. - - *skipExportGlyphs* is a list or set of glyph names to not be exported to the - final font. If these glyphs are used as components in any other glyph, those - components get decomposed. If the parameter is not passed in, the UFO's - "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, - all glyphs are exported. UFO groups and kerning will be pruned of skipped - glyphs. + return TTFCompiler(**kwargs).compile(ufo) - *dropImpliedOnCurves* (bool) specifies whether on-curve points that are exactly - in between two off-curves can be dropped when building glyphs (default: False). - *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True - by default, builds traditional glyf v0 table. If False, quadratic curves or cubic - curves are generated depending on which has fewer points; a glyf v1 is generated. - """ - kwargs = init_kwargs(kwargs, compileTTF_args) - - glyphSet = call_preprocessor(ufo, **kwargs) - - logger.info("Building OpenType tables") - - otf = call_outline_compiler( - ufo, glyphSet, glyphDataFormat=(0 if kwargs["allQuadratic"] else 1), **kwargs - ) - - # Only the default layer is likely to have all glyphs used in feature code. - if kwargs["layerName"] is None: - compileFeatures(ufo, otf, glyphSet=glyphSet, **kwargs) - - return call_postprocessor(otf, ufo, glyphSet, **kwargs) - - -compileInterpolatableTTFs_args = { - **base_args, - **dict( - preProcessorClass=TTFInterpolatablePreProcessor, - outlineCompilerClass=OutlineTTFCompiler, - convertCubics=True, - cubicConversionError=None, - reverseDirection=True, - flattenComponents=False, - layerNames=None, - colrLayerReuse=False, - colrAutoClipBoxes=False, - extraSubstitutions=None, - autoUseMyMetrics=True, - allQuadratic=True, - ), -} +def compileOTF(ufo, **kwargs): + return OTFCompiler(**kwargs).compile(ufo) def compileInterpolatableTTFs(ufos, **kwargs): - """Create FontTools TrueType fonts from a list of UFOs with interpolatable - outlines. Cubic curves are converted compatibly to quadratic curves using - the Cu2Qu conversion algorithm. - - Return an iterator object that yields a TTFont instance for each UFO. - - *layerNames* refers to the layer names to use glyphs from in the order of - the UFOs in *ufos*. By default, this is a list of `[None]` times the number - of UFOs, i.e. using the default layer from all the UFOs. - - When the layerName is not None for a given UFO, the corresponding TTFont object - will contain only a minimum set of tables ("head", "hmtx", "glyf", "loca", "maxp", - "post" and "vmtx"), and no OpenType layout tables. - - *skipExportGlyphs* is a list or set of glyph names to not be exported to the - final font. If these glyphs are used as components in any other glyph, those - components get decomposed. If the parameter is not passed in, the union of - all UFO's "public.skipExportGlyphs" lib keys will be used. If they don't - exist, all glyphs are exported. UFO groups and kerning will be pruned of - skipped glyphs. - """ - from ufo2ft.util import _LazyFontName - - kwargs = init_kwargs(kwargs, compileInterpolatableTTFs_args) - - if kwargs["layerNames"] is None: - kwargs["layerNames"] = [None] * len(ufos) - assert len(ufos) == len(kwargs["layerNames"]) - - glyphSets = call_preprocessor(ufos, **kwargs) - - for ufo, glyphSet, layerName in zip(ufos, glyphSets, kwargs["layerNames"]): - fontName = _LazyFontName(ufo) - if layerName is not None: - logger.info("Building OpenType tables for %s-%s", fontName, layerName) - else: - logger.info("Building OpenType tables for %s", fontName) - - ttf = call_outline_compiler( - ufo, - glyphSet, - **kwargs, - glyphDataFormat=(0 if kwargs["allQuadratic"] else 1), - tables=SPARSE_TTF_MASTER_TABLES if layerName else None, - # we want to keep coordinates as floats in glyf masters so that fonttools - # can compute impliable on-curve points from unrounded coordinates before - # building the VF - roundCoordinates=False, - ) - - # Only the default layer is likely to have all glyphs used in feature - # code. - if layerName is None: - if kwargs["debugFeatureFile"]: - kwargs["debugFeatureFile"].write("\n### %s ###\n" % fontName) - compileFeatures(ufo, ttf, glyphSet=glyphSet, **kwargs) + return InterpolatableTTFCompiler(**kwargs).compile(ufos) - ttf = call_postprocessor(ttf, ufo, glyphSet, **kwargs) - if layerName is not None: - # for sparse masters (i.e. containing only a subset of the glyphs), we - # need to include the post table in order to store glyph names, so that - # fontTools.varLib can interpolate glyphs with same name across masters. - # However we want to prevent the underlinePosition/underlineThickness - # fields in such sparse masters to be included when computing the deltas - # for the MVAR table. Thus, we set them to this unlikely, limit value - # (-36768) which is a signal varLib should ignore them when building MVAR. - ttf["post"].underlinePosition = -0x8000 - ttf["post"].underlineThickness = -0x8000 - - yield ttf +def compileVariableTTFs(designSpaceDoc, **kwargs): + return VariableTTFsCompiler(**kwargs).compile_variable(designSpaceDoc) def compileInterpolatableTTFsFromDS(designSpaceDoc, **kwargs): - """Create FontTools TrueType fonts from the DesignSpaceDocument UFO sources - with interpolatable outlines. Cubic curves are converted compatibly to - quadratic curves using the Cu2Qu conversion algorithm. - - If the Designspace contains a "public.skipExportGlyphs" lib key, these - glyphs will not be exported to the final font. If these glyphs are used as - components in any other glyph, those components get decomposed. If the lib - key doesn't exist in the Designspace, all glyphs are exported (keys in - individual UFOs are ignored). UFO groups and kerning will be pruned of - skipped glyphs. - - The DesignSpaceDocument should contain SourceDescriptor objects with 'font' - attribute set to an already loaded defcon.Font object (or compatible UFO - Font class). If 'font' attribute is unset or None, an AttributeError exception - is thrown. - - Return a copy of the DesignSpaceDocument object (or the same one if - inplace=True) with the source's 'font' attribute set to the corresponding - TTFont instance. - - For sources that have the 'layerName' attribute defined, the corresponding TTFont - object will contain only a minimum set of tables ("head", "hmtx", "glyf", "loca", - "maxp", "post" and "vmtx"), and no OpenType layout tables. - """ - kwargs = init_kwargs(kwargs, compileInterpolatableTTFs_args) - ufos, kwargs["layerNames"] = [], [] - for source in designSpaceDoc.sources: - if source.font is None: - raise AttributeError( - "designspace source '%s' is missing required 'font' attribute" - % getattr(source, "name", "") - ) - ufos.append(source.font) - # 'layerName' is None for the default layer - kwargs["layerNames"].append(source.layerName) - - kwargs["skipExportGlyphs"] = designSpaceDoc.lib.get("public.skipExportGlyphs", []) - - if kwargs["notdefGlyph"] is None: - kwargs["notdefGlyph"] = _notdefGlyphFallback(designSpaceDoc) - - kwargs["extraSubstitutions"] = defaultdict(set) - for rule in designSpaceDoc.rules: - for left, right in rule.subs: - kwargs["extraSubstitutions"][left].add(right) - - ttfs = compileInterpolatableTTFs(ufos, **kwargs) - - if kwargs["inplace"]: - result = designSpaceDoc - else: - result = designSpaceDoc.deepcopyExceptFonts() - for source, ttf in zip(result.sources, ttfs): - source.font = ttf - return result - - -compileInterpolatableOTFs_args = { - **base_args, - **dict( - preProcessorClass=OTFPreProcessor, - outlineCompilerClass=OutlineOTFCompiler, - featureCompilerClass=None, - roundTolerance=None, - optimizeCFF=CFFOptimization.NONE, - colrLayerReuse=False, - colrAutoClipBoxes=False, - extraSubstitutions=None, - ), -} + return InterpolatableTTFCompiler(**kwargs).compile_designspace(designSpaceDoc) def compileInterpolatableOTFsFromDS(designSpaceDoc, **kwargs): - """Create FontTools CFF fonts from the DesignSpaceDocument UFO sources - with interpolatable outlines. - - Interpolatable means without subroutinization and specializer optimizations - and no removal of overlaps. - - If the Designspace contains a "public.skipExportGlyphs" lib key, these - glyphs will not be exported to the final font. If these glyphs are used as - components in any other glyph, those components get decomposed. If the lib - key doesn't exist in the Designspace, all glyphs are exported (keys in - individual UFOs are ignored). UFO groups and kerning will be pruned of - skipped glyphs. - - The DesignSpaceDocument should contain SourceDescriptor objects with 'font' - attribute set to an already loaded defcon.Font object (or compatible UFO - Font class). If 'font' attribute is unset or None, an AttributeError exception - is thrown. - - Return a copy of the DesignSpaceDocument object (or the same one if - inplace=True) with the source's 'font' attribute set to the corresponding - TTFont instance. - - For sources that have the 'layerName' attribute defined, the corresponding TTFont - object will contain only a minimum set of tables ("head", "hmtx", "CFF ", "maxp", - "vmtx" and "VORG"), and no OpenType layout tables. - """ - kwargs = init_kwargs(kwargs, compileInterpolatableOTFs_args) - for source in designSpaceDoc.sources: - if source.font is None: - raise AttributeError( - "designspace source '%s' is missing required 'font' attribute" - % getattr(source, "name", "") - ) - - kwargs["skipExportGlyphs"] = designSpaceDoc.lib.get("public.skipExportGlyphs", []) - - if kwargs["notdefGlyph"] is None: - kwargs["notdefGlyph"] = _notdefGlyphFallback(designSpaceDoc) - - kwargs["extraSubstitutions"] = defaultdict(set) - for rule in designSpaceDoc.rules: - for left, right in rule.subs: - kwargs["extraSubstitutions"][left].add(right) - - otfs = [] - for source in designSpaceDoc.sources: - otfs.append( - compileOTF( - ufo=source.font, - **{ - **kwargs, - **dict( - layerName=source.layerName, - removeOverlaps=False, - overlapsBackend=None, - optimizeCFF=CFFOptimization.NONE, - _tables=SPARSE_OTF_MASTER_TABLES if source.layerName else None, - ), - }, - ) - ) - - if kwargs["inplace"]: - result = designSpaceDoc - else: - result = designSpaceDoc.deepcopyExceptFonts() - - for source, otf in zip(result.sources, otfs): - source.font = otf - - return result - - -def compileFeatures( - ufo, - ttFont=None, - glyphSet=None, - featureCompilerClass=None, - debugFeatureFile=None, - **kwargs, -): - """Compile OpenType Layout features from `ufo` into FontTools OTL tables. - If `ttFont` is None, a new TTFont object is created containing the new - tables, else the provided `ttFont` is updated with the new tables. - - If no explicit `featureCompilerClass` is provided, the one used will - depend on whether the ufo contains any MTI feature files in its 'data' - directory (thus the `MTIFeatureCompiler` is used) or not (then the - default FeatureCompiler for Adobe FDK features is used). - - If skipExportGlyphs is provided (see description in the ``compile*`` - functions), the feature compiler will prune groups (removing them if empty) - and kerning of the UFO of these glyphs. The feature file is left untouched. - - `debugFeatureFile` can be a file or file-like object opened in text mode, - in which to dump the text content of the feature file, useful for debugging - auto-generated OpenType features like kern, mark, mkmk etc. - """ - if featureCompilerClass is None: - if any( - fn.startswith(MTI_FEATURES_PREFIX) and fn.endswith(".mti") - for fn in ufo.data.fileNames - ): - featureCompilerClass = MtiFeatureCompiler - else: - featureCompilerClass = FeatureCompiler - - kwargs = prune_unknown_kwargs(kwargs, featureCompilerClass) - featureCompiler = featureCompilerClass(ufo, ttFont, glyphSet=glyphSet, **kwargs) - otFont = featureCompiler.compile() - - if debugFeatureFile: - if hasattr(featureCompiler, "writeFeatures"): - featureCompiler.writeFeatures(debugFeatureFile) - - return otFont - - -compileVariableTTF_args = { - **base_args, - **dict( - preProcessorClass=TTFInterpolatablePreProcessor, - outlineCompilerClass=OutlineTTFCompiler, - convertCubics=True, - cubicConversionError=None, - reverseDirection=True, - flattenComponents=False, - excludeVariationTables=(), - optimizeGvar=True, - colrAutoClipBoxes=False, - autoUseMyMetrics=True, - dropImpliedOnCurves=False, - allQuadratic=True, - ), -} + return InterpolatableOTFCompiler(**kwargs).compile_designspace(designSpaceDoc) def compileVariableTTF(designSpaceDoc, **kwargs): @@ -593,8 +45,7 @@ def compileVariableTTF(designSpaceDoc, **kwargs): Returns a new variable TTFont object. """ - kwargs = init_kwargs(kwargs, compileVariableTTF_args) - fonts = compileVariableTTFs(designSpaceDoc, **kwargs) + fonts = VariableTTFsCompiler(**kwargs).compile_variable(designSpaceDoc) if len(fonts) != 1: raise ValueError( "Tried to build a DesignSpace version 5 with multiple variable " @@ -604,114 +55,8 @@ def compileVariableTTF(designSpaceDoc, **kwargs): return next(iter(fonts.values())) -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. - - *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True - by default, builds traditional glyf v0 table. If False, quadratic curves or cubic - curves are generated depending on which has fewer points; a glyf v1 is generated. - - 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") - colrLayerReuse = kwargs.pop("colrLayerReuse") - dropImpliedOnCurves = kwargs.pop("dropImpliedOnCurves") - - # 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 fonts: %s", ", ".join(vfNameToBaseUfo)) - - with timer("merge fonts to variable"): - vfNameToTTFont = varLib.build_many( - designSpaceDoc, - exclude=excludeVariationTables, - optimize=optimizeGvar, - skip_vf=lambda vf_name: variableFontNames - and vf_name not in variableFontNames, - colr_layer_reuse=colrLayerReuse, - drop_implied_oncurves=dropImpliedOnCurves, - ) - - for vfName, varfont in list(vfNameToTTFont.items()): - vfNameToTTFont[vfName] = call_postprocessor( - varfont, vfNameToBaseUfo[vfName], glyphSet=None, **kwargs - ) - - return vfNameToTTFont - - -compileVariableCFF2_args = { - **base_args, - **dict( - preProcessorClass=OTFPreProcessor, - outlineCompilerClass=OutlineOTFCompiler, - roundTolerance=None, - excludeVariationTables=(), - optimizeCFF=CFFOptimization.SPECIALIZE, - colrAutoClipBoxes=False, - ), -} - - def compileVariableCFF2(designSpaceDoc, **kwargs): - """Create FontTools CFF2 variable font from the DesignSpaceDocument 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. - - The rest of the arguments works the same as in the other compile functions. - - Returns a new variable TTFont object. - """ - kwargs = init_kwargs(kwargs, compileVariableCFF2_args) - fonts = compileVariableCFF2s(designSpaceDoc, **kwargs) + fonts = VariableCFF2sCompiler(**kwargs).compile_variable(designSpaceDoc) if len(fonts) != 1: raise ValueError( "Tried to build a DesignSpace version 5 with multiple variable " @@ -721,157 +66,5 @@ def compileVariableCFF2(designSpaceDoc, **kwargs): return next(iter(fonts.values())) -compileVariableCFF2s_args = { - **compileVariableCFF2_args, - **dict(variableFontNames=None), -} - - 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") - colrLayerReuse = kwargs.pop("colrLayerReuse") - - # 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 - ) - - 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, - skip_vf=lambda vf_name: variableFontNames and vf_name not in variableFontNames, - colr_layer_reuse=colrLayerReuse, - ) - - 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: - default_location = location_to_string(vfDoc.newDefaultLocation()) - master_locations = [] - for sourceDescriptor in vfDoc.sources: - master_location = sourceDescriptor.name + " at " - master_location += location_to_string( - sourceDescriptor.getFullDesignLocation(vfDoc) - ) - master_locations.append(master_location) - master_location_descriptions = "\n".join(master_locations) - raise InvalidDesignSpaceData( - f"No default source; expected default master at {default_location}." - f" Found master locations:\n{master_location_descriptions}" - ) - 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 + return VariableCFF2sCompiler(**kwargs).compile_variable(designSpaceDoc) diff --git a/Lib/ufo2ft/compilers/__init__.py b/Lib/ufo2ft/compilers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Lib/ufo2ft/compilers/baseCompiler.py b/Lib/ufo2ft/compilers/baseCompiler.py new file mode 100644 index 000000000..934e75f49 --- /dev/null +++ b/Lib/ufo2ft/compilers/baseCompiler.py @@ -0,0 +1,306 @@ +import logging +import os +from collections import defaultdict +from dataclasses import dataclass +from typing import Callable, Optional, Type + +from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts +from fontTools.misc.loggingTools import Timer +from fontTools.otlLib.optimize.gpos import GPOS_COMPACT_MODE_ENV_KEY + +from ufo2ft.constants import MTI_FEATURES_PREFIX +from ufo2ft.errors import InvalidDesignSpaceData +from ufo2ft.featureCompiler import FeatureCompiler, MtiFeatureCompiler +from ufo2ft.postProcessor import PostProcessor +from ufo2ft.util import ( + _notdefGlyphFallback, + colrClipBoxQuantization, + ensure_all_sources_have_names, + location_to_string, + prune_unknown_kwargs, +) + + +@dataclass +class BaseCompiler: + postProcessorClass: Type = PostProcessor + featureCompilerClass: Optional[Type] = None + featureWriters: list = None + filters: list = None + glyphOrder: list = None + useProductionNames: bool = None + removeOverlaps: bool = False + overlapsBackend: str = None + inplace: bool = False + layerName: str = None + skipExportGlyphs: bool = None + debugFeatureFile: str = None + notdefGlyph: str = None + colrLayerReuse: bool = True + colrAutoClipBoxes: bool = True + colrClipBoxQuantization: Callable[[object], int] = colrClipBoxQuantization + feaIncludeDir: str = None + skipFeatureCompilation: bool = False + flattenComponents: bool = None + _tables: list = None + + def __post_init__(self): + self.logger = logging.getLogger(self.__class__.__name__) + self.timer = Timer(logging.getLogger("ufo2ft.timer"), level=logging.DEBUG) + + def compile(self, ufo): + glyphSet = self.preprocess(ufo) + font = self.compileOutlines(ufo, glyphSet) + if self.layerName is None and not self.skipFeatureCompilation: + self.compileFeatures(ufo, font, glyphSet=glyphSet) + return self.postprocess(font, ufo, glyphSet) + + def preprocess(self, ufo_or_ufos): + self.logger.info("Pre-processing glyphs") + if self.skipExportGlyphs is None: + if isinstance(ufo_or_ufos, (list, tuple)): + self.skipExportGlyphs = set() + for ufo in ufo_or_ufos: + self.skipExportGlyphs.update( + ufo.lib.get("public.skipExportGlyphs", []) + ) + else: + self.skipExportGlyphs = ufo_or_ufos.lib.get( + "public.skipExportGlyphs", [] + ) + + # Preprocessors expect this parameter under a different name. + if hasattr(self, "cubicConversionError"): + self.conversionError = self.cubicConversionError + + callables = [self.preProcessorClass] + if hasattr(self.preProcessorClass, "initDefaultFilters"): + callables.append(self.preProcessorClass.initDefaultFilters) + preprocessor_args = prune_unknown_kwargs(self.__dict__, *callables) + preProcessor = self.preProcessorClass(ufo_or_ufos, **preprocessor_args) + return preProcessor.process() + + # @timer("compile a basic TTF") + def compileOutlines(self, ufo, glyphSet): + kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) + kwargs["tables"] = self._tables + outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) + return outlineCompiler.compile() + + # @timer("postprocess TTF"): + def postprocess(self, ttf, ufo, glyphSet): + if self.postProcessorClass is not None: + postProcessor = self.postProcessorClass(ttf, ufo, glyphSet=glyphSet) + kwargs = prune_unknown_kwargs(self.__dict__, postProcessor.process) + ttf = postProcessor.process(**kwargs) + return ttf + + def compileFeatures( + self, + ufo, + ttFont=None, + glyphSet=None, + ): + """Compile OpenType Layout features from `ufo` into FontTools OTL tables. + If `ttFont` is None, a new TTFont object is created containing the new + tables, else the provided `ttFont` is updated with the new tables. + + If no explicit `featureCompilerClass` is provided, the one used will + depend on whether the ufo contains any MTI feature files in its 'data' + directory (thus the `MTIFeatureCompiler` is used) or not (then the + default FeatureCompiler for Adobe FDK features is used). + + If skipExportGlyphs is provided (see description in the ``compile*`` + functions), the feature compiler will prune groups (removing them if empty) + and kerning of the UFO of these glyphs. The feature file is left untouched. + + `debugFeatureFile` can be a file or file-like object opened in text mode, + in which to dump the text content of the feature file, useful for debugging + auto-generated OpenType features like kern, mark, mkmk etc. + """ + if self.featureCompilerClass is None: + if any( + fn.startswith(MTI_FEATURES_PREFIX) and fn.endswith(".mti") + for fn in ufo.data.fileNames + ): + self.featureCompilerClass = MtiFeatureCompiler + else: + self.featureCompilerClass = FeatureCompiler + + kwargs = prune_unknown_kwargs(self.__dict__, self.featureCompilerClass) + featureCompiler = self.featureCompilerClass( + ufo, ttFont, glyphSet=glyphSet, **kwargs + ) + otFont = featureCompiler.compile() + + if self.debugFeatureFile: + if hasattr(featureCompiler, "writeFeatures"): + featureCompiler.writeFeatures(self.debugFeatureFile) + + return otFont + + +class DesignspaceCompiler(BaseCompiler): + variableFontNames: list = None + """Create FontTools TrueType fonts from the DesignSpaceDocument UFO sources + with interpolatable outlines. Cubic curves are converted compatibly to + quadratic curves using the Cu2Qu conversion algorithm. + + If the Designspace contains a "public.skipExportGlyphs" lib key, these + glyphs will not be exported to the final font. If these glyphs are used as + components in any other glyph, those components get decomposed. If the lib + key doesn't exist in the Designspace, all glyphs are exported (keys in + individual UFOs are ignored). UFO groups and kerning will be pruned of + skipped glyphs. + + The DesignSpaceDocument should contain SourceDescriptor objects with 'font' + attribute set to an already loaded defcon.Font object (or compatible UFO + Font class). If 'font' attribute is unset or None, an AttributeError exception + is thrown. + + Return a copy of the DesignSpaceDocument object (or the same one if + inplace=True) with the source's 'font' attribute set to the corresponding + TTFont instance. + + For sources that have the 'layerName' attribute defined, the corresponding TTFont + object will contain only a minimum set of tables ("head", "hmtx", "glyf", "loca", + "maxp", "post" and "vmtx"), and no OpenType layout tables. + """ + + def _pre_compile_designspace(self, designSpaceDoc): + ufos, self.layerNames = [], [] + for source in designSpaceDoc.sources: + if source.font is None: + raise AttributeError( + "designspace source '%s' is missing required 'font' attribute" + % getattr(source, "name", "") + ) + ufos.append(source.font) + # 'layerName' is None for the default layer + self.layerNames.append(source.layerName) + + self.skipExportGlyphs = designSpaceDoc.lib.get("public.skipExportGlyphs", []) + + if self.notdefGlyph is None: + self.notdefGlyph = _notdefGlyphFallback(designSpaceDoc) + + self.extraSubstitutions = defaultdict(set) + for rule in designSpaceDoc.rules: + for left, right in rule.subs: + self.extraSubstitutions[left].add(right) + + return ufos + + def _post_compile_designspace(self, designSpaceDoc, fonts): + if self.inplace: + result = designSpaceDoc + else: + result = designSpaceDoc.deepcopyExceptFonts() + for source, font in zip(result.sources, fonts): + source.font = font + return result + + def _compileNeededSources(self, designSpaceDoc): + # 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 ( + self.variableFontNames is not None + and vfName not in self.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: + default_location = location_to_string(vfDoc.newDefaultLocation()) + master_locations = [] + for sourceDescriptor in vfDoc.sources: + master_location = sourceDescriptor.name + " at " + master_location += location_to_string( + sourceDescriptor.getFullDesignLocation(vfDoc) + ) + master_locations.append(master_location) + master_location_descriptions = "\n".join(master_locations) + raise InvalidDesignSpaceData( + f"No default source; expected default master at {default_location}." + f" Found master locations:\n{master_location_descriptions}" + ) + 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 + + originalSources = {} + + # 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) + save_production_names = self.useProductionNames + self.useProductionNames = False + save_postprocessor = self.postProcessorClass + self.postProcessorClass = None + try: + ttfDesignSpace = self.compile_designspace(subDoc) + finally: + if gpos_compact_value is not None: + os.environ[GPOS_COMPACT_MODE_ENV_KEY] = gpos_compact_value + self.postProcessorClass = save_postprocessor + self.useProductionNames = save_production_names + + # Stick TTFs back into original big DS + for ttfSource in ttfDesignSpace.sources: + sourcesByName[ttfSource.name].font = ttfSource.font + + return vfNameToBaseUfo, originalSources + + def compile_variable(self, designSpaceDoc): + # Pop inplace because we'll make a copy at this level so deeper functions + # don't need to worry + if not self.inplace: + designSpaceDoc = designSpaceDoc.deepcopyExceptFonts() + + ( + vfNameToBaseUfo, + originalSources, + ) = self._compileNeededSources(designSpaceDoc) + + if not vfNameToBaseUfo: + return {} + + self.logger.info("Building variable TTF fonts: %s", ", ".join(vfNameToBaseUfo)) + + excludeVariationTables = self.excludeVariationTables + + with self.timer("merge fonts to variable"): + vfNameToTTFont = self._merge(designSpaceDoc, excludeVariationTables) + + for vfName, varfont in list(vfNameToTTFont.items()): + vfNameToTTFont[vfName] = self.postprocess( + varfont, vfNameToBaseUfo[vfName], glyphSet=None + ) + + return vfNameToTTFont diff --git a/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py new file mode 100644 index 000000000..5fbf40fda --- /dev/null +++ b/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py @@ -0,0 +1,93 @@ +import dataclasses +from dataclasses import dataclass +from typing import Type + +from fontTools import varLib + +from ufo2ft.constants import SPARSE_OTF_MASTER_TABLES, CFFOptimization +from ufo2ft.outlineCompiler import OutlineOTFCompiler +from ufo2ft.preProcessor import OTFPreProcessor +from ufo2ft.util import prune_unknown_kwargs + +from .baseCompiler import DesignspaceCompiler + + +@dataclass +class InterpolatableOTFCompiler(DesignspaceCompiler): + preProcessorClass: Type = OTFPreProcessor + outlineCompilerClass: Type = OutlineOTFCompiler + featureCompilerClass: Type = None + roundTolerance: float = None + optimizeCFF: CFFOptimization = CFFOptimization.NONE + colrLayerReuse: bool = False + colrAutoClipBoxes: bool = False + extraSubstitutions: bool = None + skipFeatureCompilation: bool = False + excludeVariationTables: tuple = () + """Create FontTools CFF fonts from the DesignSpaceDocument UFO sources + with interpolatable outlines. + + Interpolatable means without subroutinization and specializer optimizations + and no removal of overlaps. + + If the Designspace contains a "public.skipExportGlyphs" lib key, these + glyphs will not be exported to the final font. If these glyphs are used as + components in any other glyph, those components get decomposed. If the lib + key doesn't exist in the Designspace, all glyphs are exported (keys in + individual UFOs are ignored). UFO groups and kerning will be pruned of + skipped glyphs. + + The DesignSpaceDocument should contain SourceDescriptor objects with 'font' + attribute set to an already loaded defcon.Font object (or compatible UFO + Font class). If 'font' attribute is unset or None, an AttributeError exception + is thrown. + + Return a copy of the DesignSpaceDocument object (or the same one if + inplace=True) with the source's 'font' attribute set to the corresponding + TTFont instance. + + For sources that have the 'layerName' attribute defined, the corresponding TTFont + object will contain only a minimum set of tables ("head", "hmtx", "CFF ", "maxp", + "vmtx" and "VORG"), and no OpenType layout tables. + """ + + # XXX temporary, fix with multiple inheritance + def compileOutlines(self, ufo, glyphSet): + kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) + kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SPECIALIZE + kwargs["tables"] = self._tables + outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) + return outlineCompiler.compile() + + def compile_designspace(self, designSpaceDoc): + self._pre_compile_designspace(designSpaceDoc) + otfs = [] + for source in designSpaceDoc.sources: + # There's a Python bug where dataclasses.asdict() doesn't work with + # dataclasses that contain a defaultdict. + save_extraSubstitutions = self.extraSubstitutions + self.extraSubstitutions = None + args = { + **dataclasses.asdict(self), + **dict( + layerName=source.layerName, + removeOverlaps=False, + overlapsBackend=None, + optimizeCFF=CFFOptimization.NONE, + _tables=SPARSE_OTF_MASTER_TABLES if source.layerName else None, + ), + } + compiler = InterpolatableOTFCompiler(**args) + self.extraSubstitutions = save_extraSubstitutions + otfs.append(compiler.compile(source.font)) + return self._post_compile_designspace(designSpaceDoc, otfs) + + def _merge(self, designSpaceDoc, excludeVariationTables): + return varLib.build_many( + designSpaceDoc, + exclude=excludeVariationTables, + optimize=self.optimizeCFF >= CFFOptimization.SPECIALIZE, + skip_vf=lambda vf_name: self.variableFontNames + and vf_name not in self.variableFontNames, + colr_layer_reuse=self.colrLayerReuse, + ) diff --git a/Lib/ufo2ft/compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/compilers/interpolatableTTFCompiler.py new file mode 100644 index 000000000..4540e9dd9 --- /dev/null +++ b/Lib/ufo2ft/compilers/interpolatableTTFCompiler.py @@ -0,0 +1,117 @@ +from dataclasses import dataclass +from typing import Type + +from fontTools import varLib + +from ufo2ft.constants import SPARSE_TTF_MASTER_TABLES +from ufo2ft.outlineCompiler import OutlineTTFCompiler +from ufo2ft.preProcessor import TTFInterpolatablePreProcessor +from ufo2ft.util import _LazyFontName, prune_unknown_kwargs + +from .baseCompiler import DesignspaceCompiler + + +@dataclass +class InterpolatableTTFCompiler(DesignspaceCompiler): + preProcessorClass: Type = TTFInterpolatablePreProcessor + outlineCompilerClass: Type = OutlineTTFCompiler + convertCubics: bool = True + cubicConversionError: float = None + reverseDirection: bool = True + flattenComponents: bool = False + layerNames: str = None + colrLayerReuse: bool = False + colrAutoClipBoxes: bool = False + extraSubstitutions: bool = None + autoUseMyMetrics: bool = True + allQuadratic: bool = True + skipFeatureCompilation: bool = False + """Create FontTools TrueType fonts from a list of UFOs with interpolatable + outlines. Cubic curves are converted compatibly to quadratic curves using + the Cu2Qu conversion algorithm. + + Return an iterator object that yields a TTFont instance for each UFO. + + *layerNames* refers to the layer names to use glyphs from in the order of + the UFOs in *ufos*. By default, this is a list of `[None]` times the number + of UFOs, i.e. using the default layer from all the UFOs. + + When the layerName is not None for a given UFO, the corresponding TTFont object + will contain only a minimum set of tables ("head", "hmtx", "glyf", "loca", "maxp", + "post" and "vmtx"), and no OpenType layout tables. + + *skipExportGlyphs* is a list or set of glyph names to not be exported to the + final font. If these glyphs are used as components in any other glyph, those + components get decomposed. If the parameter is not passed in, the union of + all UFO's "public.skipExportGlyphs" lib keys will be used. If they don't + exist, all glyphs are exported. UFO groups and kerning will be pruned of + skipped glyphs. + """ + + def compile(self, ufos): + if self.layerNames is None: + self.layerNames = [None] * len(ufos) + assert len(ufos) == len(self.layerNames) + glyphSets = self.preprocess(ufos) + + for ufo, glyphSet, layerName in zip(ufos, glyphSets, self.layerNames): + yield self.compile_one(ufo, glyphSet, layerName) + + def compile_one(self, ufo, glyphSet, layerName): + fontName = _LazyFontName(ufo) + if layerName is not None: + self.logger.info("Building OpenType tables for %s-%s", fontName, layerName) + else: + self.logger.info("Building OpenType tables for %s", fontName) + + ttf = self.compileOutlines(ufo, glyphSet, layerName) + + # Only the default layer is likely to have all glyphs used in feature + # code. + if layerName is None and not self.skipFeatureCompilation: + if self.debugFeatureFile: + self.debugFeatureFile.write("\n### %s ###\n" % fontName) + self.compileFeatures(ufo, ttf, glyphSet=glyphSet) + + ttf = self.postprocess(ttf, ufo, glyphSet) + + if layerName is not None: + # for sparse masters (i.e. containing only a subset of the glyphs), we + # need to include the post table in order to store glyph names, so that + # fontTools.varLib can interpolate glyphs with same name across masters. + # However we want to prevent the underlinePosition/underlineThickness + # fields in such sparse masters to be included when computing the deltas + # for the MVAR table. Thus, we set them to this unlikely, limit value + # (-36768) which is a signal varLib should ignore them when building MVAR. + ttf["post"].underlinePosition = -0x8000 + ttf["post"].underlineThickness = -0x8000 + + return ttf + + # @timer("compile a basic TTF") + def compileOutlines(self, ufo, glyphSet, layerName=None): + kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) + kwargs["glyphDataFormat"] = 0 if self.allQuadratic else 1 + kwargs["tables"] = SPARSE_TTF_MASTER_TABLES if layerName else None + # we want to keep coordinates as floats in glyf masters so that fonttools + # can compute impliable on-curve points from unrounded coordinates before + # building the VF + kwargs["roundCoordinates"] = False + outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) + return outlineCompiler.compile() + + def compile_designspace(self, designSpaceDoc): + ufos = self._pre_compile_designspace(designSpaceDoc) + ttfs = self.compile(ufos) + return self._post_compile_designspace(designSpaceDoc, ttfs) + + def _merge(self, designSpaceDoc, excludeVariationTables): + return varLib.build_many( + designSpaceDoc, + exclude=excludeVariationTables, + optimize=self.optimizeGvar, + skip_vf=lambda vf_name: self.variableFontNames + and vf_name not in self.variableFontNames, + colr_layer_reuse=self.colrLayerReuse, + drop_implied_oncurves=self.dropImpliedOnCurves, + ) diff --git a/Lib/ufo2ft/compilers/otfCompiler.py b/Lib/ufo2ft/compilers/otfCompiler.py new file mode 100644 index 000000000..27a2d76f6 --- /dev/null +++ b/Lib/ufo2ft/compilers/otfCompiler.py @@ -0,0 +1,95 @@ +from dataclasses import dataclass +from typing import Optional, Type + +from ufo2ft.constants import CFFOptimization +from ufo2ft.outlineCompiler import OutlineOTFCompiler +from ufo2ft.preProcessor import OTFPreProcessor +from ufo2ft.util import prune_unknown_kwargs + +from .baseCompiler import BaseCompiler + + +@dataclass +class OTFCompiler(BaseCompiler): + preProcessorClass: Type = OTFPreProcessor + outlineCompilerClass: Type = OutlineOTFCompiler + optimizeCFF: CFFOptimization = CFFOptimization.SUBROUTINIZE + roundTolerance: float = None + cffVersion: int = 1 + subroutinizer: Optional[str] = None + _tables: list = None + extraSubstitutions: dict = None + + """Create FontTools CFF font from a UFO. + + *removeOverlaps* performs a union operation on all the glyphs' contours. + + *optimizeCFF* (int) defines whether the CFF charstrings should be + specialized and subroutinized. By default both optimization are enabled. + A value of 0 disables both; 1 only enables the specialization; 2 (default) + does both specialization and subroutinization. + + *roundTolerance* (float) controls the rounding of point coordinates. + It is defined as the maximum absolute difference between the original + float and the rounded integer value. + By default, all floats are rounded to integer (tolerance 0.5); a value + of 0 completely disables rounding; values in between only round floats + which are close to their integral part within the tolerated range. + + *featureWriters* argument is a list of BaseFeatureWriter subclasses or + pre-initialized instances. Features will be written by each feature + writer in the given order. If featureWriters is None, the default + feature writers [KernFeatureWriter, MarkFeatureWriter] are used. + + *filters* argument is a list of BaseFilters subclasses or pre-initialized + instances. Filters with 'pre' attribute set to True will be pre-filters + called before the default filters, otherwise they will be post-filters, + called after the default filters. + Filters will modify glyphs or the glyph set. The default filters cannot + be disabled. + + *useProductionNames* renames glyphs in TrueType 'post' or OpenType 'CFF ' + tables based on the 'public.postscriptNames' mapping in the UFO lib, + if present. Otherwise, uniXXXX names are generated from the glyphs' + unicode values. The default value (None) will first check if the UFO lib + has the 'com.github.googlei18n.ufo2ft.useProductionNames' key. If this + is missing or True (default), the glyphs are renamed. Set to False + to keep the original names. + + **inplace** (bool) specifies whether the filters should modify the input + UFO's glyphs, a copy should be made first. + + *layerName* specifies which layer should be compiled. When compiling something + other than the default layer, feature compilation is skipped. + + *skipExportGlyphs* is a list or set of glyph names to not be exported to the + final font. If these glyphs are used as components in any other glyph, those + components get decomposed. If the parameter is not passed in, the UFO's + "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, + all glyphs are exported. UFO groups and kerning will be pruned of skipped + glyphs. + + *cffVersion* (int) is the CFF format, choose between 1 (default) and 2. + + *subroutinizer* (Optional[str]) is the name of the library to use for + compressing CFF charstrings, if subroutinization is enabled by optimizeCFF + parameter. Choose between "cffsubr" or "compreffor". + By default "cffsubr" is used for both CFF 1 and CFF 2. + NOTE: cffsubr is required for subroutinizing CFF2 tables, as compreffor + currently doesn't support it. + """ + + def compileOutlines(self, ufo, glyphSet): + kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) + kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SPECIALIZE + kwargs["tables"] = self._tables + outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) + return outlineCompiler.compile() + + def postprocess(self, font, ufo, glyphSet): + if self.postProcessorClass is not None: + postProcessor = self.postProcessorClass(font, ufo, glyphSet=glyphSet) + kwargs = prune_unknown_kwargs(self.__dict__, postProcessor.process) + kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SUBROUTINIZE + ttf = postProcessor.process(**kwargs) + return ttf diff --git a/Lib/ufo2ft/compilers/ttfCompiler.py b/Lib/ufo2ft/compilers/ttfCompiler.py new file mode 100644 index 000000000..7c2cc8ac3 --- /dev/null +++ b/Lib/ufo2ft/compilers/ttfCompiler.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass +from typing import Type + +from ufo2ft.outlineCompiler import OutlineTTFCompiler +from ufo2ft.preProcessor import TTFPreProcessor +from ufo2ft.util import prune_unknown_kwargs + +from .baseCompiler import BaseCompiler + + +@dataclass +class TTFCompiler(BaseCompiler): + preProcessorClass: Type = TTFPreProcessor + outlineCompilerClass: Type = OutlineTTFCompiler + convertCubics: bool = True + cubicConversionError: float = None + reverseDirection: bool = True + rememberCurveType: bool = True + flattenComponents: bool = False + autoUseMyMetrics: bool = True + dropImpliedOnCurves: bool = False + allQuadratic: bool = True + + """Create FontTools TrueType font from a UFO. + + *removeOverlaps* performs a union operation on all the glyphs' contours. + + *flattenComponents* un-nests glyphs so that they have at most one level of + components. + + *convertCubics* and *cubicConversionError* specify how the conversion from cubic + to quadratic curves should be handled. + + *layerName* specifies which layer should be compiled. When compiling something + other than the default layer, feature compilation is skipped. + + *skipExportGlyphs* is a list or set of glyph names to not be exported to the + final font. If these glyphs are used as components in any other glyph, those + components get decomposed. If the parameter is not passed in, the UFO's + "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, + all glyphs are exported. UFO groups and kerning will be pruned of skipped + glyphs. + + *dropImpliedOnCurves* (bool) specifies whether on-curve points that are exactly + in between two off-curves can be dropped when building glyphs (default: False). + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + """ + + # @timer("compile a basic TTF") + def compileOutlines(self, ufo, glyphSet, layerName=None): + kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) + kwargs["glyphDataFormat"] = 0 if self.allQuadratic else 1 + outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) + return outlineCompiler.compile() diff --git a/Lib/ufo2ft/compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/compilers/variableCFF2sCompiler.py new file mode 100644 index 000000000..593d1cf1c --- /dev/null +++ b/Lib/ufo2ft/compilers/variableCFF2sCompiler.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from typing import Type + +from ufo2ft.constants import CFFOptimization +from ufo2ft.outlineCompiler import OutlineOTFCompiler +from ufo2ft.preProcessor import OTFPreProcessor + +from .interpolatableOTFCompiler import InterpolatableOTFCompiler + + +@dataclass +class VariableCFF2sCompiler(InterpolatableOTFCompiler): + preProcessorClass: Type = OTFPreProcessor + outlineCompilerClass: Type = OutlineOTFCompiler + roundTolerance: float = None + colrAutoClipBoxes: bool = False + # This should probably be SPECIALIZE, but our tests expect no optimization! + optimizeCFF: CFFOptimization = CFFOptimization.NONE + excludeVariationTables: tuple = () + """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. + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + + 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 + """ diff --git a/Lib/ufo2ft/compilers/variableTTFsCompiler.py b/Lib/ufo2ft/compilers/variableTTFsCompiler.py new file mode 100644 index 000000000..71e5feb1f --- /dev/null +++ b/Lib/ufo2ft/compilers/variableTTFsCompiler.py @@ -0,0 +1,50 @@ +from dataclasses import dataclass +from typing import Type + +from ufo2ft.outlineCompiler import OutlineTTFCompiler +from ufo2ft.preProcessor import TTFInterpolatablePreProcessor + +from .interpolatableTTFCompiler import InterpolatableTTFCompiler + + +@dataclass +class VariableTTFsCompiler(InterpolatableTTFCompiler): + preProcessorClass: Type = TTFInterpolatablePreProcessor + outlineCompilerClass: Type = OutlineTTFCompiler + convertCubics: bool = True + cubicConversionError: float = None + reverseDirection: bool = True + flattenComponents: bool = False + excludeVariationTables: tuple = () + optimizeGvar: bool = True + colrAutoClipBoxes: bool = False + autoUseMyMetrics: bool = True + dropImpliedOnCurves: bool = False + allQuadratic: bool = True + """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. + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + + 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 + """ + pass diff --git a/Lib/ufo2ft/constants.py b/Lib/ufo2ft/constants.py index eec1a9dfa..696839fcc 100644 --- a/Lib/ufo2ft/constants.py +++ b/Lib/ufo2ft/constants.py @@ -1,5 +1,13 @@ +from enum import IntEnum from types import MappingProxyType + +class CFFOptimization(IntEnum): + NONE = 0 + SPECIALIZE = 1 + SUBROUTINIZE = 2 + + SPARSE_TTF_MASTER_TABLES = frozenset( ["glyf", "head", "hmtx", "loca", "maxp", "post", "vmtx", "cvt ", "fpgm", "prep"] ) From f45809e7e59e063bc571e476584bbec961fc6812 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Wed, 29 Nov 2023 16:42:39 +0000 Subject: [PATCH 02/18] Use multiple inheritance in InterpolatableOFF to correct pre/post process --- Lib/ufo2ft/compilers/interpolatableOTFCompiler.py | 12 ++---------- Lib/ufo2ft/compilers/otfCompiler.py | 4 ++-- Lib/ufo2ft/compilers/variableCFF2sCompiler.py | 1 + 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py index 5fbf40fda..aa7db90c5 100644 --- a/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py @@ -7,13 +7,13 @@ from ufo2ft.constants import SPARSE_OTF_MASTER_TABLES, CFFOptimization from ufo2ft.outlineCompiler import OutlineOTFCompiler from ufo2ft.preProcessor import OTFPreProcessor -from ufo2ft.util import prune_unknown_kwargs from .baseCompiler import DesignspaceCompiler +from .otfCompiler import OTFCompiler @dataclass -class InterpolatableOTFCompiler(DesignspaceCompiler): +class InterpolatableOTFCompiler(OTFCompiler, DesignspaceCompiler): preProcessorClass: Type = OTFPreProcessor outlineCompilerClass: Type = OutlineOTFCompiler featureCompilerClass: Type = None @@ -51,14 +51,6 @@ class InterpolatableOTFCompiler(DesignspaceCompiler): "vmtx" and "VORG"), and no OpenType layout tables. """ - # XXX temporary, fix with multiple inheritance - def compileOutlines(self, ufo, glyphSet): - kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) - kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SPECIALIZE - kwargs["tables"] = self._tables - outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) - return outlineCompiler.compile() - def compile_designspace(self, designSpaceDoc): self._pre_compile_designspace(designSpaceDoc) otfs = [] diff --git a/Lib/ufo2ft/compilers/otfCompiler.py b/Lib/ufo2ft/compilers/otfCompiler.py index 27a2d76f6..a8729f75d 100644 --- a/Lib/ufo2ft/compilers/otfCompiler.py +++ b/Lib/ufo2ft/compilers/otfCompiler.py @@ -91,5 +91,5 @@ def postprocess(self, font, ufo, glyphSet): postProcessor = self.postProcessorClass(font, ufo, glyphSet=glyphSet) kwargs = prune_unknown_kwargs(self.__dict__, postProcessor.process) kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SUBROUTINIZE - ttf = postProcessor.process(**kwargs) - return ttf + font = postProcessor.process(**kwargs) + return font diff --git a/Lib/ufo2ft/compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/compilers/variableCFF2sCompiler.py index 593d1cf1c..430749247 100644 --- a/Lib/ufo2ft/compilers/variableCFF2sCompiler.py +++ b/Lib/ufo2ft/compilers/variableCFF2sCompiler.py @@ -14,6 +14,7 @@ class VariableCFF2sCompiler(InterpolatableOTFCompiler): outlineCompilerClass: Type = OutlineOTFCompiler roundTolerance: float = None colrAutoClipBoxes: bool = False + cffVersion: int = 2 # This should probably be SPECIALIZE, but our tests expect no optimization! optimizeCFF: CFFOptimization = CFFOptimization.NONE excludeVariationTables: tuple = () From 91a2238c1bb7e51f59cfef579d3f09764c78c243 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Fri, 1 Dec 2023 09:23:32 +0000 Subject: [PATCH 03/18] Remove stale comment --- Lib/ufo2ft/compilers/baseCompiler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/ufo2ft/compilers/baseCompiler.py b/Lib/ufo2ft/compilers/baseCompiler.py index 934e75f49..e2c60d606 100644 --- a/Lib/ufo2ft/compilers/baseCompiler.py +++ b/Lib/ufo2ft/compilers/baseCompiler.py @@ -278,8 +278,6 @@ def _compileNeededSources(self, designSpaceDoc): return vfNameToBaseUfo, originalSources def compile_variable(self, designSpaceDoc): - # Pop inplace because we'll make a copy at this level so deeper functions - # don't need to worry if not self.inplace: designSpaceDoc = designSpaceDoc.deepcopyExceptFonts() From 22451abf28563ec46a35da08b05173ec0b973784 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:23:02 +0000 Subject: [PATCH 04/18] Rename .compilers to ._compilers --- Lib/ufo2ft/__init__.py | 12 ++++++------ Lib/ufo2ft/{compilers => _compilers}/__init__.py | 0 Lib/ufo2ft/{compilers => _compilers}/baseCompiler.py | 0 .../interpolatableOTFCompiler.py | 0 .../interpolatableTTFCompiler.py | 0 Lib/ufo2ft/{compilers => _compilers}/otfCompiler.py | 0 Lib/ufo2ft/{compilers => _compilers}/ttfCompiler.py | 0 .../variableCFF2sCompiler.py | 0 .../variableTTFsCompiler.py | 0 9 files changed, 6 insertions(+), 6 deletions(-) rename Lib/ufo2ft/{compilers => _compilers}/__init__.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/baseCompiler.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/interpolatableOTFCompiler.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/interpolatableTTFCompiler.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/otfCompiler.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/ttfCompiler.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/variableCFF2sCompiler.py (100%) rename Lib/ufo2ft/{compilers => _compilers}/variableTTFsCompiler.py (100%) diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 51ce9a2bd..6bc5c8e63 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -1,9 +1,9 @@ -from ufo2ft.compilers.interpolatableOTFCompiler import InterpolatableOTFCompiler -from ufo2ft.compilers.interpolatableTTFCompiler import InterpolatableTTFCompiler -from ufo2ft.compilers.otfCompiler import OTFCompiler -from ufo2ft.compilers.ttfCompiler import TTFCompiler -from ufo2ft.compilers.variableCFF2sCompiler import VariableCFF2sCompiler -from ufo2ft.compilers.variableTTFsCompiler import VariableTTFsCompiler +from ufo2ft._compilers.interpolatableOTFCompiler import InterpolatableOTFCompiler +from ufo2ft._compilers.interpolatableTTFCompiler import InterpolatableTTFCompiler +from ufo2ft._compilers.otfCompiler import OTFCompiler +from ufo2ft._compilers.ttfCompiler import TTFCompiler +from ufo2ft._compilers.variableCFF2sCompiler import VariableCFF2sCompiler +from ufo2ft._compilers.variableTTFsCompiler import VariableTTFsCompiler from ufo2ft.constants import CFFOptimization # noqa: F401 (fontmake uses it) diff --git a/Lib/ufo2ft/compilers/__init__.py b/Lib/ufo2ft/_compilers/__init__.py similarity index 100% rename from Lib/ufo2ft/compilers/__init__.py rename to Lib/ufo2ft/_compilers/__init__.py diff --git a/Lib/ufo2ft/compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/baseCompiler.py rename to Lib/ufo2ft/_compilers/baseCompiler.py diff --git a/Lib/ufo2ft/compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/interpolatableOTFCompiler.py rename to Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py diff --git a/Lib/ufo2ft/compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/interpolatableTTFCompiler.py rename to Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py diff --git a/Lib/ufo2ft/compilers/otfCompiler.py b/Lib/ufo2ft/_compilers/otfCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/otfCompiler.py rename to Lib/ufo2ft/_compilers/otfCompiler.py diff --git a/Lib/ufo2ft/compilers/ttfCompiler.py b/Lib/ufo2ft/_compilers/ttfCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/ttfCompiler.py rename to Lib/ufo2ft/_compilers/ttfCompiler.py diff --git a/Lib/ufo2ft/compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/variableCFF2sCompiler.py rename to Lib/ufo2ft/_compilers/variableCFF2sCompiler.py diff --git a/Lib/ufo2ft/compilers/variableTTFsCompiler.py b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py similarity index 100% rename from Lib/ufo2ft/compilers/variableTTFsCompiler.py rename to Lib/ufo2ft/_compilers/variableTTFsCompiler.py From dfd59739941b0439b199bdd684ebe3704b4bf2a2 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:27:09 +0000 Subject: [PATCH 05/18] Fix up typings --- Lib/ufo2ft/_compilers/baseCompiler.py | 23 +++++++++---------- .../_compilers/interpolatableOTFCompiler.py | 8 +++---- .../_compilers/interpolatableTTFCompiler.py | 8 +++---- Lib/ufo2ft/_compilers/otfCompiler.py | 6 ++--- Lib/ufo2ft/_compilers/ttfCompiler.py | 4 ++-- .../_compilers/variableCFF2sCompiler.py | 4 ++-- Lib/ufo2ft/_compilers/variableTTFsCompiler.py | 4 ++-- 7 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index e2c60d606..4f4b7ffa6 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -25,24 +25,23 @@ class BaseCompiler: postProcessorClass: Type = PostProcessor featureCompilerClass: Optional[Type] = None - featureWriters: list = None - filters: list = None - glyphOrder: list = None - useProductionNames: bool = None + featureWriters: Optional[list] = None + filters: Optional[list] = None + glyphOrder: Optional[list] = None + useProductionNames: Optional[bool] = None removeOverlaps: bool = False - overlapsBackend: str = None + overlapsBackend: Optional[str] = None inplace: bool = False - layerName: str = None - skipExportGlyphs: bool = None - debugFeatureFile: str = None - notdefGlyph: str = None + layerName: Optional[str] = None + skipExportGlyphs: Optional[bool] = None + debugFeatureFile: Optional[str] = None + notdefGlyph: Optional[str] = None colrLayerReuse: bool = True colrAutoClipBoxes: bool = True colrClipBoxQuantization: Callable[[object], int] = colrClipBoxQuantization - feaIncludeDir: str = None + feaIncludeDir: Optional[str] = None skipFeatureCompilation: bool = False - flattenComponents: bool = None - _tables: list = None + _tables: Optional[list] = None def __post_init__(self): self.logger = logging.getLogger(self.__class__.__name__) diff --git a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py index aa7db90c5..8f03c06d6 100644 --- a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py @@ -1,6 +1,6 @@ import dataclasses from dataclasses import dataclass -from typing import Type +from typing import Type, Optional from fontTools import varLib @@ -16,12 +16,12 @@ class InterpolatableOTFCompiler(OTFCompiler, DesignspaceCompiler): preProcessorClass: Type = OTFPreProcessor outlineCompilerClass: Type = OutlineOTFCompiler - featureCompilerClass: Type = None - roundTolerance: float = None + featureCompilerClass: Optional[Type] = None + roundTolerance: Optional[float] = None optimizeCFF: CFFOptimization = CFFOptimization.NONE colrLayerReuse: bool = False colrAutoClipBoxes: bool = False - extraSubstitutions: bool = None + extraSubstitutions: Optional[dict] = None skipFeatureCompilation: bool = False excludeVariationTables: tuple = () """Create FontTools CFF fonts from the DesignSpaceDocument UFO sources diff --git a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py index 4540e9dd9..c78e489ae 100644 --- a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type +from typing import Type, Optional from fontTools import varLib @@ -16,13 +16,13 @@ class InterpolatableTTFCompiler(DesignspaceCompiler): preProcessorClass: Type = TTFInterpolatablePreProcessor outlineCompilerClass: Type = OutlineTTFCompiler convertCubics: bool = True - cubicConversionError: float = None + cubicConversionError: Optional[float] = None reverseDirection: bool = True flattenComponents: bool = False - layerNames: str = None + layerNames: Optional[str] = None colrLayerReuse: bool = False colrAutoClipBoxes: bool = False - extraSubstitutions: bool = None + extraSubstitutions: Optional[bool] = None autoUseMyMetrics: bool = True allQuadratic: bool = True skipFeatureCompilation: bool = False diff --git a/Lib/ufo2ft/_compilers/otfCompiler.py b/Lib/ufo2ft/_compilers/otfCompiler.py index a8729f75d..df7e3db3c 100644 --- a/Lib/ufo2ft/_compilers/otfCompiler.py +++ b/Lib/ufo2ft/_compilers/otfCompiler.py @@ -14,11 +14,11 @@ class OTFCompiler(BaseCompiler): preProcessorClass: Type = OTFPreProcessor outlineCompilerClass: Type = OutlineOTFCompiler optimizeCFF: CFFOptimization = CFFOptimization.SUBROUTINIZE - roundTolerance: float = None + roundTolerance: Optional[float] = None cffVersion: int = 1 subroutinizer: Optional[str] = None - _tables: list = None - extraSubstitutions: dict = None + _tables: Optional[list] = None + extraSubstitutions: Optional[dict] = None """Create FontTools CFF font from a UFO. diff --git a/Lib/ufo2ft/_compilers/ttfCompiler.py b/Lib/ufo2ft/_compilers/ttfCompiler.py index 7c2cc8ac3..465090ecc 100644 --- a/Lib/ufo2ft/_compilers/ttfCompiler.py +++ b/Lib/ufo2ft/_compilers/ttfCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type +from typing import Type, Optional from ufo2ft.outlineCompiler import OutlineTTFCompiler from ufo2ft.preProcessor import TTFPreProcessor @@ -13,7 +13,7 @@ class TTFCompiler(BaseCompiler): preProcessorClass: Type = TTFPreProcessor outlineCompilerClass: Type = OutlineTTFCompiler convertCubics: bool = True - cubicConversionError: float = None + cubicConversionError: Optional[float] = None reverseDirection: bool = True rememberCurveType: bool = True flattenComponents: bool = False diff --git a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py index 430749247..2bfe2ac83 100644 --- a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py +++ b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type +from typing import Type, Optional from ufo2ft.constants import CFFOptimization from ufo2ft.outlineCompiler import OutlineOTFCompiler @@ -12,7 +12,7 @@ class VariableCFF2sCompiler(InterpolatableOTFCompiler): preProcessorClass: Type = OTFPreProcessor outlineCompilerClass: Type = OutlineOTFCompiler - roundTolerance: float = None + roundTolerance: Optional[float] = None colrAutoClipBoxes: bool = False cffVersion: int = 2 # This should probably be SPECIALIZE, but our tests expect no optimization! diff --git a/Lib/ufo2ft/_compilers/variableTTFsCompiler.py b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py index 71e5feb1f..d6c76fd7d 100644 --- a/Lib/ufo2ft/_compilers/variableTTFsCompiler.py +++ b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type +from typing import Type, Optional from ufo2ft.outlineCompiler import OutlineTTFCompiler from ufo2ft.preProcessor import TTFInterpolatablePreProcessor @@ -12,7 +12,7 @@ class VariableTTFsCompiler(InterpolatableTTFCompiler): preProcessorClass: Type = TTFInterpolatablePreProcessor outlineCompilerClass: Type = OutlineTTFCompiler convertCubics: bool = True - cubicConversionError: float = None + cubicConversionError: Optional[float] = None reverseDirection: bool = True flattenComponents: bool = False excludeVariationTables: tuple = () From 3cc9631b3827f384f9e457352fe065ac8ad584ac Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:28:51 +0000 Subject: [PATCH 06/18] Use ufo2ft package for logger --- Lib/ufo2ft/_compilers/baseCompiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index 4f4b7ffa6..411a04550 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -44,7 +44,7 @@ class BaseCompiler: _tables: Optional[list] = None def __post_init__(self): - self.logger = logging.getLogger(self.__class__.__name__) + self.logger = logging.getLogger("ufo2ft") self.timer = Timer(logging.getLogger("ufo2ft.timer"), level=logging.DEBUG) def compile(self, ufo): From d91301e86598df303dfd4b9f72fb0e07123bacef Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:29:04 +0000 Subject: [PATCH 07/18] Rename DesignspaceCompiler to BaseInterpolatableCompiler --- Lib/ufo2ft/_compilers/baseCompiler.py | 2 +- Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py | 4 ++-- Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index 411a04550..f2bfa758f 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -139,7 +139,7 @@ def compileFeatures( return otFont -class DesignspaceCompiler(BaseCompiler): +class BaseInterpolatableCompiler(BaseCompiler): variableFontNames: list = None """Create FontTools TrueType fonts from the DesignSpaceDocument UFO sources with interpolatable outlines. Cubic curves are converted compatibly to diff --git a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py index 8f03c06d6..6f5bbf602 100644 --- a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py @@ -8,12 +8,12 @@ from ufo2ft.outlineCompiler import OutlineOTFCompiler from ufo2ft.preProcessor import OTFPreProcessor -from .baseCompiler import DesignspaceCompiler +from .baseCompiler import BaseInterpolatableCompiler from .otfCompiler import OTFCompiler @dataclass -class InterpolatableOTFCompiler(OTFCompiler, DesignspaceCompiler): +class InterpolatableOTFCompiler(OTFCompiler, BaseInterpolatableCompiler): preProcessorClass: Type = OTFPreProcessor outlineCompilerClass: Type = OutlineOTFCompiler featureCompilerClass: Optional[Type] = None diff --git a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py index c78e489ae..a1dc4c56e 100644 --- a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py @@ -8,11 +8,11 @@ from ufo2ft.preProcessor import TTFInterpolatablePreProcessor from ufo2ft.util import _LazyFontName, prune_unknown_kwargs -from .baseCompiler import DesignspaceCompiler +from .baseCompiler import BaseInterpolatableCompiler @dataclass -class InterpolatableTTFCompiler(DesignspaceCompiler): +class InterpolatableTTFCompiler(BaseInterpolatableCompiler): preProcessorClass: Type = TTFInterpolatablePreProcessor outlineCompilerClass: Type = OutlineTTFCompiler convertCubics: bool = True From f82bb610ef6d2cf404daa20ec76725d8c20db142 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:32:13 +0000 Subject: [PATCH 08/18] Make SPECIALIZE the default --- Lib/ufo2ft/_compilers/variableCFF2sCompiler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py index 2bfe2ac83..8c3831486 100644 --- a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py +++ b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py @@ -15,8 +15,7 @@ class VariableCFF2sCompiler(InterpolatableOTFCompiler): roundTolerance: Optional[float] = None colrAutoClipBoxes: bool = False cffVersion: int = 2 - # This should probably be SPECIALIZE, but our tests expect no optimization! - optimizeCFF: CFFOptimization = CFFOptimization.NONE + optimizeCFF: CFFOptimization = CFFOptimization.SPECIALIZE excludeVariationTables: tuple = () """Create FontTools TrueType variable fonts for each variable font defined in the given DesignSpaceDocument, using their UFO sources From 36e1f78a6b540329d23ae18532d7442a97620848 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:33:09 +0000 Subject: [PATCH 09/18] isort --- Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py | 2 +- Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py | 2 +- Lib/ufo2ft/_compilers/ttfCompiler.py | 2 +- Lib/ufo2ft/_compilers/variableCFF2sCompiler.py | 2 +- Lib/ufo2ft/_compilers/variableTTFsCompiler.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py index 6f5bbf602..0c3c0a839 100644 --- a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py @@ -1,6 +1,6 @@ import dataclasses from dataclasses import dataclass -from typing import Type, Optional +from typing import Optional, Type from fontTools import varLib diff --git a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py index a1dc4c56e..fba5df455 100644 --- a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type, Optional +from typing import Optional, Type from fontTools import varLib diff --git a/Lib/ufo2ft/_compilers/ttfCompiler.py b/Lib/ufo2ft/_compilers/ttfCompiler.py index 465090ecc..213cf4486 100644 --- a/Lib/ufo2ft/_compilers/ttfCompiler.py +++ b/Lib/ufo2ft/_compilers/ttfCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type, Optional +from typing import Optional, Type from ufo2ft.outlineCompiler import OutlineTTFCompiler from ufo2ft.preProcessor import TTFPreProcessor diff --git a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py index 8c3831486..764f651d5 100644 --- a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py +++ b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type, Optional +from typing import Optional, Type from ufo2ft.constants import CFFOptimization from ufo2ft.outlineCompiler import OutlineOTFCompiler diff --git a/Lib/ufo2ft/_compilers/variableTTFsCompiler.py b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py index d6c76fd7d..3ea1d146e 100644 --- a/Lib/ufo2ft/_compilers/variableTTFsCompiler.py +++ b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Type, Optional +from typing import Optional, Type from ufo2ft.outlineCompiler import OutlineTTFCompiler from ufo2ft.preProcessor import TTFInterpolatablePreProcessor From 03f3ac0e526d9dbd05ab7c557ef234ddce2a9460 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:37:32 +0000 Subject: [PATCH 10/18] Don't modify self just to rename a parameter --- Lib/ufo2ft/_compilers/baseCompiler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index f2bfa758f..b3e31b40c 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -68,14 +68,14 @@ def preprocess(self, ufo_or_ufos): "public.skipExportGlyphs", [] ) - # Preprocessors expect this parameter under a different name. - if hasattr(self, "cubicConversionError"): - self.conversionError = self.cubicConversionError - callables = [self.preProcessorClass] if hasattr(self.preProcessorClass, "initDefaultFilters"): callables.append(self.preProcessorClass.initDefaultFilters) + preprocessor_args = prune_unknown_kwargs(self.__dict__, *callables) + # Preprocessors expect this parameter under a different name. + if hasattr(self, "cubicConversionError"): + preprocessor_args["conversionError"] = self.cubicConversionError preProcessor = self.preProcessorClass(ufo_or_ufos, **preprocessor_args) return preProcessor.process() From 65b09e529660cd1ddf61a956c82f5d159e4048ae Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 12:39:57 +0000 Subject: [PATCH 11/18] Move docstrings back --- Lib/ufo2ft/__init__.py | 184 ++++++++++++++++++ .../_compilers/interpolatableOTFCompiler.py | 26 --- .../_compilers/interpolatableTTFCompiler.py | 21 -- Lib/ufo2ft/_compilers/otfCompiler.py | 59 ------ Lib/ufo2ft/_compilers/ttfCompiler.py | 28 --- .../_compilers/variableCFF2sCompiler.py | 26 --- Lib/ufo2ft/_compilers/variableTTFsCompiler.py | 26 --- 7 files changed, 184 insertions(+), 186 deletions(-) diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 6bc5c8e63..5e8f0ad5c 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -8,18 +8,150 @@ def compileTTF(ufo, **kwargs): + """Create FontTools TrueType font from a UFO. + + *removeOverlaps* performs a union operation on all the glyphs' contours. + + *flattenComponents* un-nests glyphs so that they have at most one level of + components. + + *convertCubics* and *cubicConversionError* specify how the conversion from cubic + to quadratic curves should be handled. + + *layerName* specifies which layer should be compiled. When compiling something + other than the default layer, feature compilation is skipped. + + *skipExportGlyphs* is a list or set of glyph names to not be exported to the + final font. If these glyphs are used as components in any other glyph, those + components get decomposed. If the parameter is not passed in, the UFO's + "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, + all glyphs are exported. UFO groups and kerning will be pruned of skipped + glyphs. + + *dropImpliedOnCurves* (bool) specifies whether on-curve points that are exactly + in between two off-curves can be dropped when building glyphs (default: False). + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + """ return TTFCompiler(**kwargs).compile(ufo) def compileOTF(ufo, **kwargs): + """Create FontTools CFF font from a UFO. + + *removeOverlaps* performs a union operation on all the glyphs' contours. + + *optimizeCFF* (int) defines whether the CFF charstrings should be + specialized and subroutinized. By default both optimization are enabled. + A value of 0 disables both; 1 only enables the specialization; 2 (default) + does both specialization and subroutinization. + + *roundTolerance* (float) controls the rounding of point coordinates. + It is defined as the maximum absolute difference between the original + float and the rounded integer value. + By default, all floats are rounded to integer (tolerance 0.5); a value + of 0 completely disables rounding; values in between only round floats + which are close to their integral part within the tolerated range. + + *featureWriters* argument is a list of BaseFeatureWriter subclasses or + pre-initialized instances. Features will be written by each feature + writer in the given order. If featureWriters is None, the default + feature writers [KernFeatureWriter, MarkFeatureWriter] are used. + + *filters* argument is a list of BaseFilters subclasses or pre-initialized + instances. Filters with 'pre' attribute set to True will be pre-filters + called before the default filters, otherwise they will be post-filters, + called after the default filters. + Filters will modify glyphs or the glyph set. The default filters cannot + be disabled. + + *useProductionNames* renames glyphs in TrueType 'post' or OpenType 'CFF ' + tables based on the 'public.postscriptNames' mapping in the UFO lib, + if present. Otherwise, uniXXXX names are generated from the glyphs' + unicode values. The default value (None) will first check if the UFO lib + has the 'com.github.googlei18n.ufo2ft.useProductionNames' key. If this + is missing or True (default), the glyphs are renamed. Set to False + to keep the original names. + + **inplace** (bool) specifies whether the filters should modify the input + UFO's glyphs, a copy should be made first. + + *layerName* specifies which layer should be compiled. When compiling something + other than the default layer, feature compilation is skipped. + + *skipExportGlyphs* is a list or set of glyph names to not be exported to the + final font. If these glyphs are used as components in any other glyph, those + components get decomposed. If the parameter is not passed in, the UFO's + "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, + all glyphs are exported. UFO groups and kerning will be pruned of skipped + glyphs. + + *cffVersion* (int) is the CFF format, choose between 1 (default) and 2. + + *subroutinizer* (Optional[str]) is the name of the library to use for + compressing CFF charstrings, if subroutinization is enabled by optimizeCFF + parameter. Choose between "cffsubr" or "compreffor". + By default "cffsubr" is used for both CFF 1 and CFF 2. + NOTE: cffsubr is required for subroutinizing CFF2 tables, as compreffor + currently doesn't support it. + """ return OTFCompiler(**kwargs).compile(ufo) def compileInterpolatableTTFs(ufos, **kwargs): + """Create FontTools TrueType fonts from a list of UFOs with interpolatable + outlines. Cubic curves are converted compatibly to quadratic curves using + the Cu2Qu conversion algorithm. + + Return an iterator object that yields a TTFont instance for each UFO. + + *layerNames* refers to the layer names to use glyphs from in the order of + the UFOs in *ufos*. By default, this is a list of `[None]` times the number + of UFOs, i.e. using the default layer from all the UFOs. + + When the layerName is not None for a given UFO, the corresponding TTFont object + will contain only a minimum set of tables ("head", "hmtx", "glyf", "loca", "maxp", + "post" and "vmtx"), and no OpenType layout tables. + + *skipExportGlyphs* is a list or set of glyph names to not be exported to the + final font. If these glyphs are used as components in any other glyph, those + components get decomposed. If the parameter is not passed in, the union of + all UFO's "public.skipExportGlyphs" lib keys will be used. If they don't + exist, all glyphs are exported. UFO groups and kerning will be pruned of + skipped glyphs. + """ return InterpolatableTTFCompiler(**kwargs).compile(ufos) def compileVariableTTFs(designSpaceDoc, **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. + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + + 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 + """ return VariableTTFsCompiler(**kwargs).compile_variable(designSpaceDoc) @@ -28,6 +160,32 @@ def compileInterpolatableTTFsFromDS(designSpaceDoc, **kwargs): def compileInterpolatableOTFsFromDS(designSpaceDoc, **kwargs): + """Create FontTools CFF fonts from the DesignSpaceDocument UFO sources + with interpolatable outlines. + + Interpolatable means without subroutinization and specializer optimizations + and no removal of overlaps. + + If the Designspace contains a "public.skipExportGlyphs" lib key, these + glyphs will not be exported to the final font. If these glyphs are used as + components in any other glyph, those components get decomposed. If the lib + key doesn't exist in the Designspace, all glyphs are exported (keys in + individual UFOs are ignored). UFO groups and kerning will be pruned of + skipped glyphs. + + The DesignSpaceDocument should contain SourceDescriptor objects with 'font' + attribute set to an already loaded defcon.Font object (or compatible UFO + Font class). If 'font' attribute is unset or None, an AttributeError exception + is thrown. + + Return a copy of the DesignSpaceDocument object (or the same one if + inplace=True) with the source's 'font' attribute set to the corresponding + TTFont instance. + + For sources that have the 'layerName' attribute defined, the corresponding TTFont + object will contain only a minimum set of tables ("head", "hmtx", "CFF ", "maxp", + "vmtx" and "VORG"), and no OpenType layout tables. + """ return InterpolatableOTFCompiler(**kwargs).compile_designspace(designSpaceDoc) @@ -67,4 +225,30 @@ def compileVariableCFF2(designSpaceDoc, **kwargs): def compileVariableCFF2s(designSpaceDoc, **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. + + *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True + by default, builds traditional glyf v0 table. If False, quadratic curves or cubic + curves are generated depending on which has fewer points; a glyf v1 is generated. + + 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 + """ return VariableCFF2sCompiler(**kwargs).compile_variable(designSpaceDoc) diff --git a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py index 0c3c0a839..00c82d642 100644 --- a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py @@ -24,32 +24,6 @@ class InterpolatableOTFCompiler(OTFCompiler, BaseInterpolatableCompiler): extraSubstitutions: Optional[dict] = None skipFeatureCompilation: bool = False excludeVariationTables: tuple = () - """Create FontTools CFF fonts from the DesignSpaceDocument UFO sources - with interpolatable outlines. - - Interpolatable means without subroutinization and specializer optimizations - and no removal of overlaps. - - If the Designspace contains a "public.skipExportGlyphs" lib key, these - glyphs will not be exported to the final font. If these glyphs are used as - components in any other glyph, those components get decomposed. If the lib - key doesn't exist in the Designspace, all glyphs are exported (keys in - individual UFOs are ignored). UFO groups and kerning will be pruned of - skipped glyphs. - - The DesignSpaceDocument should contain SourceDescriptor objects with 'font' - attribute set to an already loaded defcon.Font object (or compatible UFO - Font class). If 'font' attribute is unset or None, an AttributeError exception - is thrown. - - Return a copy of the DesignSpaceDocument object (or the same one if - inplace=True) with the source's 'font' attribute set to the corresponding - TTFont instance. - - For sources that have the 'layerName' attribute defined, the corresponding TTFont - object will contain only a minimum set of tables ("head", "hmtx", "CFF ", "maxp", - "vmtx" and "VORG"), and no OpenType layout tables. - """ def compile_designspace(self, designSpaceDoc): self._pre_compile_designspace(designSpaceDoc) diff --git a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py index fba5df455..a3435390f 100644 --- a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py @@ -26,27 +26,6 @@ class InterpolatableTTFCompiler(BaseInterpolatableCompiler): autoUseMyMetrics: bool = True allQuadratic: bool = True skipFeatureCompilation: bool = False - """Create FontTools TrueType fonts from a list of UFOs with interpolatable - outlines. Cubic curves are converted compatibly to quadratic curves using - the Cu2Qu conversion algorithm. - - Return an iterator object that yields a TTFont instance for each UFO. - - *layerNames* refers to the layer names to use glyphs from in the order of - the UFOs in *ufos*. By default, this is a list of `[None]` times the number - of UFOs, i.e. using the default layer from all the UFOs. - - When the layerName is not None for a given UFO, the corresponding TTFont object - will contain only a minimum set of tables ("head", "hmtx", "glyf", "loca", "maxp", - "post" and "vmtx"), and no OpenType layout tables. - - *skipExportGlyphs* is a list or set of glyph names to not be exported to the - final font. If these glyphs are used as components in any other glyph, those - components get decomposed. If the parameter is not passed in, the union of - all UFO's "public.skipExportGlyphs" lib keys will be used. If they don't - exist, all glyphs are exported. UFO groups and kerning will be pruned of - skipped glyphs. - """ def compile(self, ufos): if self.layerNames is None: diff --git a/Lib/ufo2ft/_compilers/otfCompiler.py b/Lib/ufo2ft/_compilers/otfCompiler.py index df7e3db3c..0e3acfb80 100644 --- a/Lib/ufo2ft/_compilers/otfCompiler.py +++ b/Lib/ufo2ft/_compilers/otfCompiler.py @@ -20,65 +20,6 @@ class OTFCompiler(BaseCompiler): _tables: Optional[list] = None extraSubstitutions: Optional[dict] = None - """Create FontTools CFF font from a UFO. - - *removeOverlaps* performs a union operation on all the glyphs' contours. - - *optimizeCFF* (int) defines whether the CFF charstrings should be - specialized and subroutinized. By default both optimization are enabled. - A value of 0 disables both; 1 only enables the specialization; 2 (default) - does both specialization and subroutinization. - - *roundTolerance* (float) controls the rounding of point coordinates. - It is defined as the maximum absolute difference between the original - float and the rounded integer value. - By default, all floats are rounded to integer (tolerance 0.5); a value - of 0 completely disables rounding; values in between only round floats - which are close to their integral part within the tolerated range. - - *featureWriters* argument is a list of BaseFeatureWriter subclasses or - pre-initialized instances. Features will be written by each feature - writer in the given order. If featureWriters is None, the default - feature writers [KernFeatureWriter, MarkFeatureWriter] are used. - - *filters* argument is a list of BaseFilters subclasses or pre-initialized - instances. Filters with 'pre' attribute set to True will be pre-filters - called before the default filters, otherwise they will be post-filters, - called after the default filters. - Filters will modify glyphs or the glyph set. The default filters cannot - be disabled. - - *useProductionNames* renames glyphs in TrueType 'post' or OpenType 'CFF ' - tables based on the 'public.postscriptNames' mapping in the UFO lib, - if present. Otherwise, uniXXXX names are generated from the glyphs' - unicode values. The default value (None) will first check if the UFO lib - has the 'com.github.googlei18n.ufo2ft.useProductionNames' key. If this - is missing or True (default), the glyphs are renamed. Set to False - to keep the original names. - - **inplace** (bool) specifies whether the filters should modify the input - UFO's glyphs, a copy should be made first. - - *layerName* specifies which layer should be compiled. When compiling something - other than the default layer, feature compilation is skipped. - - *skipExportGlyphs* is a list or set of glyph names to not be exported to the - final font. If these glyphs are used as components in any other glyph, those - components get decomposed. If the parameter is not passed in, the UFO's - "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, - all glyphs are exported. UFO groups and kerning will be pruned of skipped - glyphs. - - *cffVersion* (int) is the CFF format, choose between 1 (default) and 2. - - *subroutinizer* (Optional[str]) is the name of the library to use for - compressing CFF charstrings, if subroutinization is enabled by optimizeCFF - parameter. Choose between "cffsubr" or "compreffor". - By default "cffsubr" is used for both CFF 1 and CFF 2. - NOTE: cffsubr is required for subroutinizing CFF2 tables, as compreffor - currently doesn't support it. - """ - def compileOutlines(self, ufo, glyphSet): kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) kwargs["optimizeCFF"] = self.optimizeCFF >= CFFOptimization.SPECIALIZE diff --git a/Lib/ufo2ft/_compilers/ttfCompiler.py b/Lib/ufo2ft/_compilers/ttfCompiler.py index 213cf4486..59e3782eb 100644 --- a/Lib/ufo2ft/_compilers/ttfCompiler.py +++ b/Lib/ufo2ft/_compilers/ttfCompiler.py @@ -21,34 +21,6 @@ class TTFCompiler(BaseCompiler): dropImpliedOnCurves: bool = False allQuadratic: bool = True - """Create FontTools TrueType font from a UFO. - - *removeOverlaps* performs a union operation on all the glyphs' contours. - - *flattenComponents* un-nests glyphs so that they have at most one level of - components. - - *convertCubics* and *cubicConversionError* specify how the conversion from cubic - to quadratic curves should be handled. - - *layerName* specifies which layer should be compiled. When compiling something - other than the default layer, feature compilation is skipped. - - *skipExportGlyphs* is a list or set of glyph names to not be exported to the - final font. If these glyphs are used as components in any other glyph, those - components get decomposed. If the parameter is not passed in, the UFO's - "public.skipExportGlyphs" lib key will be consulted. If it doesn't exist, - all glyphs are exported. UFO groups and kerning will be pruned of skipped - glyphs. - - *dropImpliedOnCurves* (bool) specifies whether on-curve points that are exactly - in between two off-curves can be dropped when building glyphs (default: False). - - *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True - by default, builds traditional glyf v0 table. If False, quadratic curves or cubic - curves are generated depending on which has fewer points; a glyf v1 is generated. - """ - # @timer("compile a basic TTF") def compileOutlines(self, ufo, glyphSet, layerName=None): kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) diff --git a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py index 764f651d5..83c8044e3 100644 --- a/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py +++ b/Lib/ufo2ft/_compilers/variableCFF2sCompiler.py @@ -17,29 +17,3 @@ class VariableCFF2sCompiler(InterpolatableOTFCompiler): cffVersion: int = 2 optimizeCFF: CFFOptimization = CFFOptimization.SPECIALIZE excludeVariationTables: tuple = () - """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. - - *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True - by default, builds traditional glyf v0 table. If False, quadratic curves or cubic - curves are generated depending on which has fewer points; a glyf v1 is generated. - - 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 - """ diff --git a/Lib/ufo2ft/_compilers/variableTTFsCompiler.py b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py index 3ea1d146e..50ca7ac9f 100644 --- a/Lib/ufo2ft/_compilers/variableTTFsCompiler.py +++ b/Lib/ufo2ft/_compilers/variableTTFsCompiler.py @@ -21,30 +21,4 @@ class VariableTTFsCompiler(InterpolatableTTFCompiler): autoUseMyMetrics: bool = True dropImpliedOnCurves: bool = False allQuadratic: bool = True - """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. - - *allQuadratic* (bool) specifies whether to convert all curves to quadratic - True - by default, builds traditional glyf v0 table. If False, quadratic curves or cubic - curves are generated depending on which has fewer points; a glyf v1 is generated. - - 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 - """ pass From 0874fa15dcff20e8fbc1530d82266c703a10491f Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 13:09:07 +0000 Subject: [PATCH 12/18] Add __all__ --- Lib/ufo2ft/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 5e8f0ad5c..89f4ba4a8 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -6,6 +6,18 @@ from ufo2ft._compilers.variableTTFsCompiler import VariableTTFsCompiler from ufo2ft.constants import CFFOptimization # noqa: F401 (fontmake uses it) +__all__ = [ + "compileTTF", + "compileOTF", + "compileInterpolatableTTFs", + "compileVariableTTFs", + "compileInterpolatableTTFsFromDS", + "compileInterpolatableOTFsFromDS", + "compileVariableTTF", + "compileVariableCFF2", + "compileVariableCFF2s", +] + def compileTTF(ufo, **kwargs): """Create FontTools TrueType font from a UFO. From 9b6d15c31d9a93067f46f5e4d4a418e3ade5e1cb Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 13:17:48 +0000 Subject: [PATCH 13/18] Fix timers --- Lib/ufo2ft/_compilers/baseCompiler.py | 12 +++++++----- Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py | 1 - Lib/ufo2ft/_compilers/ttfCompiler.py | 1 - 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index b3e31b40c..c50d64ac9 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -48,11 +48,15 @@ def __post_init__(self): self.timer = Timer(logging.getLogger("ufo2ft.timer"), level=logging.DEBUG) def compile(self, ufo): - glyphSet = self.preprocess(ufo) - font = self.compileOutlines(ufo, glyphSet) + with self.timer("preprocess UFO"): + glyphSet = self.preprocess(ufo) + with self.timer("compile a basic TTF"): + font = self.compileOutlines(ufo, glyphSet) if self.layerName is None and not self.skipFeatureCompilation: self.compileFeatures(ufo, font, glyphSet=glyphSet) - return self.postprocess(font, ufo, glyphSet) + with self.timer("postprocess TTF"): + font = self.postprocess(font, ufo, glyphSet) + return font def preprocess(self, ufo_or_ufos): self.logger.info("Pre-processing glyphs") @@ -79,14 +83,12 @@ def preprocess(self, ufo_or_ufos): preProcessor = self.preProcessorClass(ufo_or_ufos, **preprocessor_args) return preProcessor.process() - # @timer("compile a basic TTF") def compileOutlines(self, ufo, glyphSet): kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) kwargs["tables"] = self._tables outlineCompiler = self.outlineCompilerClass(ufo, glyphSet=glyphSet, **kwargs) return outlineCompiler.compile() - # @timer("postprocess TTF"): def postprocess(self, ttf, ufo, glyphSet): if self.postProcessorClass is not None: postProcessor = self.postProcessorClass(ttf, ufo, glyphSet=glyphSet) diff --git a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py index a3435390f..a15e2e07d 100644 --- a/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableTTFCompiler.py @@ -67,7 +67,6 @@ def compile_one(self, ufo, glyphSet, layerName): return ttf - # @timer("compile a basic TTF") def compileOutlines(self, ufo, glyphSet, layerName=None): kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) kwargs["glyphDataFormat"] = 0 if self.allQuadratic else 1 diff --git a/Lib/ufo2ft/_compilers/ttfCompiler.py b/Lib/ufo2ft/_compilers/ttfCompiler.py index 59e3782eb..a5c32f4c7 100644 --- a/Lib/ufo2ft/_compilers/ttfCompiler.py +++ b/Lib/ufo2ft/_compilers/ttfCompiler.py @@ -21,7 +21,6 @@ class TTFCompiler(BaseCompiler): dropImpliedOnCurves: bool = False allQuadratic: bool = True - # @timer("compile a basic TTF") def compileOutlines(self, ufo, glyphSet, layerName=None): kwargs = prune_unknown_kwargs(self.__dict__, self.outlineCompilerClass) kwargs["glyphDataFormat"] = 0 if self.allQuadratic else 1 From f0227d07dd247b7d475d684f911a35b3f3ecd8ce Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 13:17:54 +0000 Subject: [PATCH 14/18] Make this a dataclass! --- Lib/ufo2ft/_compilers/baseCompiler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/ufo2ft/_compilers/baseCompiler.py b/Lib/ufo2ft/_compilers/baseCompiler.py index c50d64ac9..b2704f8f4 100644 --- a/Lib/ufo2ft/_compilers/baseCompiler.py +++ b/Lib/ufo2ft/_compilers/baseCompiler.py @@ -141,8 +141,9 @@ def compileFeatures( return otFont +@dataclass class BaseInterpolatableCompiler(BaseCompiler): - variableFontNames: list = None + variableFontNames: Optional[list] = None """Create FontTools TrueType fonts from the DesignSpaceDocument UFO sources with interpolatable outlines. Cubic curves are converted compatibly to quadratic curves using the Cu2Qu conversion algorithm. From 2242439adca62de4e13c05415f0ce47045fe1d28 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 13:19:51 +0000 Subject: [PATCH 15/18] Don't re-export CFFOptimization (requires fontmake change) --- Lib/ufo2ft/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index 89f4ba4a8..c02ea78f9 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -4,7 +4,6 @@ from ufo2ft._compilers.ttfCompiler import TTFCompiler from ufo2ft._compilers.variableCFF2sCompiler import VariableCFF2sCompiler from ufo2ft._compilers.variableTTFsCompiler import VariableTTFsCompiler -from ufo2ft.constants import CFFOptimization # noqa: F401 (fontmake uses it) __all__ = [ "compileTTF", From 0d85f269e406f1406907be466cdf7b0aebb0d726 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 13:31:43 +0000 Subject: [PATCH 16/18] Explain the multiple inheritance --- Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py index 00c82d642..26a139ae3 100644 --- a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py @@ -12,6 +12,9 @@ from .otfCompiler import OTFCompiler +# We want the designspace handling of BaseInterpolatableCompiler but +# we also need to pick up the OTF-specific compileOutlines/postprocess +# methods from OTFCompiler. @dataclass class InterpolatableOTFCompiler(OTFCompiler, BaseInterpolatableCompiler): preProcessorClass: Type = OTFPreProcessor From 36a4408e3b4aa4fbe49145e6ba25177662474daa Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 13:41:49 +0000 Subject: [PATCH 17/18] Tweak args and use OTFCompiler to compile each source --- Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py index 26a139ae3..c9c668e18 100644 --- a/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py +++ b/Lib/ufo2ft/_compilers/interpolatableOTFCompiler.py @@ -46,7 +46,10 @@ def compile_designspace(self, designSpaceDoc): _tables=SPARSE_OTF_MASTER_TABLES if source.layerName else None, ), } - compiler = InterpolatableOTFCompiler(**args) + # Remove interpolatable-specific args + del args["variableFontNames"] + del args["excludeVariationTables"] + compiler = OTFCompiler(**args) self.extraSubstitutions = save_extraSubstitutions otfs.append(compiler.compile(source.font)) return self._post_compile_designspace(designSpaceDoc, otfs) From 4ecdc846874d40777d3e97f55393ab058f7bb0e9 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Mon, 4 Dec 2023 14:09:29 +0000 Subject: [PATCH 18/18] Revert "Don't re-export CFFOptimization (requires fontmake change)" This reverts commit 2242439adca62de4e13c05415f0ce47045fe1d28. --- Lib/ufo2ft/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/ufo2ft/__init__.py b/Lib/ufo2ft/__init__.py index c02ea78f9..89f4ba4a8 100644 --- a/Lib/ufo2ft/__init__.py +++ b/Lib/ufo2ft/__init__.py @@ -4,6 +4,7 @@ from ufo2ft._compilers.ttfCompiler import TTFCompiler from ufo2ft._compilers.variableCFF2sCompiler import VariableCFF2sCompiler from ufo2ft._compilers.variableTTFsCompiler import VariableTTFsCompiler +from ufo2ft.constants import CFFOptimization # noqa: F401 (fontmake uses it) __all__ = [ "compileTTF",