Skip to content

Commit

Permalink
Do the compile-variable-features optimisation if feature files are co…
Browse files Browse the repository at this point in the history
…mpatible
  • Loading branch information
simoncozens committed Jul 19, 2022
1 parent 2159340 commit 915a24c
Showing 1 changed file with 72 additions and 4 deletions.
76 changes: 72 additions & 4 deletions Lib/ufo2ft/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
import re
from enum import IntEnum

from fontTools import varLib
Expand All @@ -13,8 +14,9 @@
MTI_FEATURES_PREFIX,
FeatureCompiler,
MtiFeatureCompiler,
VariableFeatureCompiler
)
from ufo2ft.outlineCompiler import OutlineOTFCompiler, OutlineTTFCompiler
from ufo2ft.outlineCompiler import OutlineOTFCompiler, OutlineTTFCompiler, StubGlyph
from ufo2ft.postProcessor import PostProcessor
from ufo2ft.preProcessor import (
OTFPreProcessor,
Expand Down Expand Up @@ -596,7 +598,7 @@ def compileVariableTTFs(designSpaceDoc: DesignSpaceDocument, **kwargs):
if not inplace:
designSpaceDoc = designSpaceDoc.deepcopyExceptFonts()

vfNameToBaseUfo = _compileNeededSources(
vfNameToBaseUfo, featuresNeeded, originalSources = _compileNeededSources(
kwargs, designSpaceDoc, variableFontNames, compileInterpolatableTTFsFromDS
)

Expand All @@ -613,6 +615,9 @@ def compileVariableTTFs(designSpaceDoc: DesignSpaceDocument, **kwargs):
colr_layer_reuse=colrLayerReuse,
)

if featuresNeeded:
compile_all_variable_features(designSpaceDoc, vfNameToTTFont, originalSources, kwargs["debugFeatureFile"])

for vfName, varfont in list(vfNameToTTFont.items()):
vfNameToTTFont[vfName] = call_postprocessor(
varfont, vfNameToBaseUfo[vfName], glyphSet=None, **kwargs
Expand Down Expand Up @@ -708,7 +713,7 @@ def compileVariableCFF2s(designSpaceDoc, **kwargs):
if not inplace:
designSpaceDoc = designSpaceDoc.deepcopyExceptFonts()

vfNameToBaseUfo = _compileNeededSources(
vfNameToBaseUfo, doneFeatures, originalSources = _compileNeededSources(
kwargs, designSpaceDoc, variableFontNames, compileInterpolatableOTFsFromDS
)

Expand All @@ -728,6 +733,9 @@ def compileVariableCFF2s(designSpaceDoc, **kwargs):
colr_layer_reuse=colrLayerReuse,
)

if featuresNeeded:
compile_all_variable_features(designSpaceDoc, vfNameToTTFont, originalSources, kwargs["debugFeatureFile"])

for vfName, varfont in list(vfNameToTTFont.items()):
vfNameToTTFont[vfName] = call_postprocessor(
varfont,
Expand All @@ -740,6 +748,18 @@ def compileVariableCFF2s(designSpaceDoc, **kwargs):
return vfNameToTTFont


def _featuresCompatible(designSpaceDoc):
def transform(f):
# Strip comments
text = re.sub("(?m)#.*$","", f.font.features.text)
# Strip extraneous whitespace
text = re.sub(r"\s+", " ", text)
return text

first = transform(designSpaceDoc.sources[0])
return all(transform(s) == first for s in designSpaceDoc.sources[1:])


def _compileNeededSources(
kwargs, designSpaceDoc, variableFontNames, compileInterpolatableFunc
):
Expand Down Expand Up @@ -771,6 +791,14 @@ def _compileNeededSources(
if source.name in sourcesToCompile:
sourcesByName[source.name] = source

# If the feature files are compatible between the sources, we can save
# time by building a variable feature file right at the end.
can_optimize_features = _featuresCompatible(designSpaceDoc)
if can_optimize_features:
logger.info(f"Features are compatible across masters; building later")

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.
Expand All @@ -793,6 +821,7 @@ def _compileNeededSources(
useProductionNames=False, # will rename glyphs after varfont is built
# No need to post-process intermediate fonts.
postProcessorClass=None,
skipFeatureCompilation=can_optimize_features
),
},
)
Expand All @@ -802,6 +831,45 @@ def _compileNeededSources(

# Stick TTFs back into original big DS
for ttfSource in ttfDesignSpace.sources:
if can_optimize_features:
originalSources[ttfSource.name] = sourcesByName[ttfSource.name].font
sourcesByName[ttfSource.name].font = ttfSource.font

return vfNameToBaseUfo
return vfNameToBaseUfo, can_optimize_features, originalSources


def compile_all_variable_features(designSpaceDoc, vfNameToTTFont, originalSources, debugFeatureFile=False):
interpolableSubDocs = [
subDoc for _location, subDoc in splitInterpolable(designSpaceDoc)
]
vfNameToBaseUfo = {}
sourcesToCompile = set()
for subDoc in interpolableSubDocs:
for vfName, vfDoc in splitVariableFonts(subDoc):
if vfName not in vfNameToTTFont:
continue
ttFont = vfNameToTTFont[vfName]
# vfDoc is now full of TTFs, create a UFO-sourced equivalent
ufoDoc = vfDoc.deepcopyExceptFonts()
for ttfSource, ufoSource in zip(vfDoc.sources, ufoDoc.sources):
ufoSource.font = originalSources[ttfSource.name]
compile_variable_features(ufoDoc, ttFont, debugFeatureFile)


def compile_variable_features(designSpaceDoc, ttFont, debugFeatureFile):
default_ufo = designSpaceDoc.findDefault().font

# Delete anything from the UFO glyphset which didn't make it into the font.
fontglyphs = ttFont.getGlyphOrder()
glyphSet = {g.name: g for g in default_ufo if g.name in fontglyphs}

# Add anything we added to the TTF without telling the UFO
if ".notdef" not in glyphSet:
glyphSet[".notdef"] = StubGlyph(".notdef", 0, 0, 0, 0)

featureCompiler = VariableFeatureCompiler(default_ufo, designSpaceDoc, ttFont=ttFont, glyphSet=glyphSet)
otFont = featureCompiler.compile()

if debugFeatureFile:
if hasattr(featureCompiler, "writeFeatures"):
featureCompiler.writeFeatures(debugFeatureFile)

0 comments on commit 915a24c

Please sign in to comment.