varLib.instancer: make 'overlap' an enum; add --remove-overlaps CLI option

'overlap' parameter in instantiateVariableFont was a bool, now it is a tri-state IntEnum, with value 2 meaning 'remove the overlaps'. The old bool False/True (0/1) values continue to work like before.

Also added a new --remove-overlaps commandline option to fonttools varLib.instancer script.
This commit is contained in:
Cosimo Lupo 2020-09-27 17:48:52 +01:00
parent 7531b71717
commit 85947cabb3
No known key found for this signature in database
GPG Key ID: 179A8F0895A02F4F

View File

@ -87,6 +87,7 @@ from fontTools.varLib.merger import MutatorMerger
from contextlib import contextmanager from contextlib import contextmanager
import collections import collections
from copy import deepcopy from copy import deepcopy
from enum import IntEnum
import logging import logging
from itertools import islice from itertools import islice
import os import os
@ -121,6 +122,12 @@ class NormalizedAxisRange(AxisRange):
return self return self
class OverlapsMode(IntEnum):
KEEP_AND_DONT_SET_FLAGS = 0
KEEP_AND_SET_FLAGS = 1
REMOVE = 2
def instantiateTupleVariationStore( def instantiateTupleVariationStore(
variations, axisLimits, origCoords=None, endPts=None variations, axisLimits, origCoords=None, endPts=None
): ):
@ -578,7 +585,7 @@ class _TupleVarStoreAdapter(object):
def instantiateItemVariationStore(itemVarStore, fvarAxes, axisLimits): def instantiateItemVariationStore(itemVarStore, fvarAxes, axisLimits):
""" Compute deltas at partial location, and update varStore in-place. """Compute deltas at partial location, and update varStore in-place.
Remove regions in which all axes were instanced, or fall outside the new axis Remove regions in which all axes were instanced, or fall outside the new axis
limits. Scale the deltas of the remaining regions where only some of the axes limits. Scale the deltas of the remaining regions where only some of the axes
@ -1175,9 +1182,13 @@ def populateAxisDefaults(varfont, axisLimits):
def instantiateVariableFont( def instantiateVariableFont(
varfont, axisLimits, inplace=False, optimize=True, overlap=True varfont,
axisLimits,
inplace=False,
optimize=True,
overlap=OverlapsMode.KEEP_AND_SET_FLAGS,
): ):
""" Instantiate variable font, either fully or partially. """Instantiate variable font, either fully or partially.
Depending on whether the `axisLimits` dictionary references all or some of the Depending on whether the `axisLimits` dictionary references all or some of the
input varfont's axes, the output font will either be a full instance (static input varfont's axes, the output font will either be a full instance (static
@ -1198,13 +1209,20 @@ def instantiateVariableFont(
remaining 'gvar' table's deltas. Possibly faster, and might work around remaining 'gvar' table's deltas. Possibly faster, and might work around
rendering issues in some buggy environments, at the cost of a slightly rendering issues in some buggy environments, at the cost of a slightly
larger file size. larger file size.
overlap (bool): variable fonts usually contain overlapping contours, and some overlap (OverlapsMode): variable fonts usually contain overlapping contours, and
font rendering engines on Apple platforms require that the `OVERLAP_SIMPLE` some font rendering engines on Apple platforms require that the
and `OVERLAP_COMPOUND` flags in the 'glyf' table be set to force rendering `OVERLAP_SIMPLE` and `OVERLAP_COMPOUND` flags in the 'glyf' table be set to
using a non-zero fill rule. Thus we always set these flags on all glyphs force rendering using a non-zero fill rule. Thus we always set these flags
to maximise cross-compatibility of the generated instance. You can disable on all glyphs to maximise cross-compatibility of the generated instance.
this by setting `overalap` to False. You can disable this by passing OverlapsMode.KEEP_AND_DONT_SET_FLAGS.
If you want to remove the overlaps altogether and merge overlapping
contours and components, you can pass OverlapsMode.REMOVE. Note that this
requires the skia-pathops package (available to pip install).
The overlap parameter only has effect when generating full static instances.
""" """
# 'overlap' used to be bool and is now enum; for backward compat keep accepting bool
overlap = OverlapsMode(int(overlap))
sanityCheckVariableTables(varfont) sanityCheckVariableTables(varfont)
axisLimits = populateAxisDefaults(varfont, axisLimits) axisLimits = populateAxisDefaults(varfont, axisLimits)
@ -1245,8 +1263,14 @@ def instantiateVariableFont(
instantiateFvar(varfont, axisLimits) instantiateFvar(varfont, axisLimits)
if "fvar" not in varfont: if "fvar" not in varfont:
if "glyf" in varfont and overlap: if "glyf" in varfont:
setMacOverlapFlags(varfont["glyf"]) if overlap == OverlapsMode.KEEP_AND_SET_FLAGS:
setMacOverlapFlags(varfont["glyf"])
elif overlap == OverlapsMode.REMOVE:
from fontTools.ttLib.removeOverlaps import removeOverlaps
log.info("Removing overlaps from glyf table")
removeOverlaps(varfont)
varLib.set_default_weight_width_slant( varLib.set_default_weight_width_slant(
varfont, varfont,
@ -1346,6 +1370,13 @@ def parseArgs(args):
help="Don't set OVERLAP_SIMPLE/OVERLAP_COMPOUND glyf flags (only applicable " help="Don't set OVERLAP_SIMPLE/OVERLAP_COMPOUND glyf flags (only applicable "
"when generating a full instance)", "when generating a full instance)",
) )
parser.add_argument(
"--remove-overlaps",
dest="remove_overlaps",
action="store_true",
help="Merge overlapping contours and components (only applicable "
"when generating a full instance). Requires skia-pathops",
)
loggingGroup = parser.add_mutually_exclusive_group(required=False) loggingGroup = parser.add_mutually_exclusive_group(required=False)
loggingGroup.add_argument( loggingGroup.add_argument(
"-v", "--verbose", action="store_true", help="Run more verbosely." "-v", "--verbose", action="store_true", help="Run more verbosely."
@ -1355,6 +1386,11 @@ def parseArgs(args):
) )
options = parser.parse_args(args) options = parser.parse_args(args)
if options.remove_overlaps:
options.overlap = OverlapsMode.REMOVE
else:
options.overlap = OverlapsMode(int(options.overlap))
infile = options.input infile = options.input
if not os.path.isfile(infile): if not os.path.isfile(infile):
parser.error("No such file '{}'".format(infile)) parser.error("No such file '{}'".format(infile))