After partial instancing, multiple FeatureVariationRecords may end up with
the same set of conditions (e.g. if one references two axes, one of which
is dropped, and a subsequent one also references the same axis that was
kept in the preceding record's condition set, and the min/max values are
the same for both records).
Therefore, we make sure only the first unique record with a given
configuration of conditions is kept. Any additional records with identical
conditions will never match the current context so they can be dropped.
ConditionTable.AxisIndex needs to change when dropping axes, to
refer to the same axis in the modified fvar.axes array.
There was also another bug when a condition was not met,
and the `applies` flag (initialised to `True`) was not set to
`False`, thus substutions were incorrectly applied.
This allows to drop an axis (aka L1 instancing) without knowing the
axis' actual default value from fvar table. One can simply call
`instantiateVariableFont` function with a `None` value for a given
axis (i.e. axis_limits={'wght': None}); the `None` value is replaced
by the axis default value as per fvar table.
The same can be done from the console script as well.
The special string literal 'None' is parsed as the Python `None`
object. E.g.:
$ fonttools varLib.instancer MyFont-VF.ttf wght=None
Do not round them to integer, but let the caller do the rounding immediately before adding them to the default instance (or just before compiling the binary table as with glyf).
This ensures that the glyphs' left sidebearings are calculated in the same way as they were by varLib.mutator.
If we round deltas too early, then we may get off-by-one differences.
See the glyf table setCoordinates method where left sidebearings are computed.
varLib.mutator does the same.
Ideally we would keep STAT if has any extra (inter-family) DesignAxis or it font was only partially instanced. We can improve on this later as needed.
for avar, we drop segments of the axes being pinned.
for fvar, we drop the pinned axes and all the named instances whose coordinates are different from the pinned location.
if the original VarStore had any regions in VarRegionList that wasn't
even referenced in any VarData VarRegionIndex, this makes sure we
remove those as well from VarRegionList (and remap the VarRegionIndex
accordingly)
We don't actually apply deltas to hmtx since these have already been applied
from the gvar deltas when we call glyf.setCoordinates method using the glyf
phantom points.
We simply call instantiateItemVariationStore on HVAR.VarStore to remove
regions and scale remaining deltas, but ignore the return value.
We only run VarStore.optimize() if the HVAR originally has an AdvWidthMap,
if it does not then it uses a direct implicit GID->VariationIndex mapping
for advance widths deltas, and we keep the VariationIndex unchanged by
not optimizing VarStore.
If all axes in fvar are being instanced, then we simply delete HVAR
(just like varLib.mutator currently does).
VVAR is not supported yet because we do not set the 3rd and 4th phantom points
from gvar to the vmtx table yet (this should be done in glyf.setCoordinates).
Also, supporting CFF2 would need more work, in that HVAR there is required
and we need to apply the deltas to hmtx/vmtx in here.
If we modify the default instance coordinates, then the inferred deltas that
are left in gvar are no longer valid, so we need to calculate them using the
original default coordinates.
They are then re-optimized using the modified default coordinates.
Also, the default deltas returned from instantiateTupleVariationStore are now
already rounded to integer.
The instantiateTupleVariationStore function now groups TupleVariation
tables that have the same axes 'tents', then merges them into a single
TupleVariation by summing their deltas. The rounding to integer happens
after summing the scaled deltas as floats, to reduce off-by-one errors.
To be able to sum gvar TupleVariation, it needs to calculate the inferred
deltas so it now takes two optional lists (origCoords and endPts) that
are passed on to iup_delta function. These only make sense for gvar
type of TupleVariation, of course, and are unused for cvar tuples.
It also run iup_delta_optimize on the gvar deltas that are left after
partial instancing and whose inferred deltas had to be interpolated.
This can be disabled with --no-optimize CLI option.
Also added calcInferredDeltas and optimize methods to TupleVariation
class, which use functions from varLib.iup module, plus tests
that exercise them.
The function now takes a VarStore instance, the fvar axes and a partial
location, and returns an array of delta-sets to be applied to the
default instance.
The algorithm is now more similar to the one used for instantiating the
tuple variation store.
Tests are coming soon.
update VarData.VarRegionCount
also set StartCoord and EndCoord to 0 (same end result as only setting PeakCoord
to 0, but this produces less noise when inspeciting the generated XML dump)
we can reuse the prune_regions method defined in varStore.py to update
the VarRegionList.
also update the counts at the end (will be done automatically on compile anyway).