Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[instancer] Start avar2 instancing #3485

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 79 additions & 4 deletions Lib/fontTools/ttLib/tables/_a_v_a_r.py
Expand Up @@ -10,8 +10,10 @@
from fontTools.varLib.models import piecewiseLinearMap
from fontTools.varLib.varStore import VarStoreInstancer, NO_VARIATION_INDEX
from fontTools.ttLib import TTLibError
from fontTools.varLib import instancer
from . import DefaultTable
from . import otTables
from copy import deepcopy
import struct
import logging

Expand Down Expand Up @@ -142,7 +144,6 @@
super().fromXML(name, attrs, content, ttFont)

def renormalizeLocation(self, location, font):

if self.majorVersion not in (1, 2):
raise NotImplementedError("Unknown avar table version")

Expand All @@ -163,18 +164,17 @@
varStore = self.table.VarStore
axes = font["fvar"].axes
if varStore is not None:
instancer = VarStoreInstancer(varStore, axes, mappedLocation)
varStoreInstancer = VarStoreInstancer(varStore, axes, mappedLocation)

coords = list(fl2fi(mappedLocation.get(axis.axisTag, 0), 14) for axis in axes)

out = []
for varIdx, v in enumerate(coords):

if varIdxMap is not None:
varIdx = varIdxMap[varIdx]

if varStore is not None:
delta = instancer[varIdx]
delta = varStoreInstancer[varIdx]
v += otRound(delta)
v = min(max(v, -(1 << 14)), +(1 << 14))

Expand All @@ -185,3 +185,78 @@
}

return mappedLocation

def renormalizeAxisLimits(self, axisLimits, font, *, versionOneOnly=False):
version = getattr(self, "majorVersion", 1)
if version not in (1, 2):
raise NotImplementedError("Unknown avar table version")

avarSegments = self.segments
mappedAxisLimits = {}
for axisTag, triple in axisLimits.items():
avarMapping = avarSegments.get(axisTag, None)
if avarMapping is not None:
triple = tuple(
piecewiseLinearMap(value, avarMapping) for value in triple
)
mappedAxisLimits[axisTag] = triple

if version == 1 or versionOneOnly:
return instancer.NormalizedAxisLimits(mappedAxisLimits)

# Version 2

limits = deepcopy(axisLimits)
fvar = font["fvar"]
avar = self

Check warning on line 211 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L209-L211

Added lines #L209 - L211 were not covered by tests

varIdxMap = getattr(avar.table, "VarIdxMap", None)
varStore = getattr(avar.table, "VarStore", None)

Check warning on line 214 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L213-L214

Added lines #L213 - L214 were not covered by tests
if varStore is not None:
varStore = deepcopy(varStore)
axes = font["fvar"].axes

Check warning on line 217 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L216-L217

Added lines #L216 - L217 were not covered by tests

pinnedAxes = axisLimits.pinnedLocation()

Check warning on line 219 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L219

Added line #L219 was not covered by tests

defaultDeltas = instancer.instantiateItemVariationStore(

Check warning on line 221 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L221

Added line #L221 was not covered by tests
varStore, fvar.axes, limits
)

for axis in fvar.axes:
if axis.axisTag in limits:
continue
private = axis.flags & 0x1

Check warning on line 228 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L227-L228

Added lines #L227 - L228 were not covered by tests
if not private:
continue

Check warning on line 230 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L230

Added line #L230 was not covered by tests
# if private, pin at default
limits[axis.axisTag] = instancer.NormalizedAxisTripleAndDistances(0, 0, 0)

Check warning on line 232 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L232

Added line #L232 was not covered by tests

newLimits = {}

Check warning on line 234 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L234

Added line #L234 was not covered by tests
for axisIdx, axis in enumerate(fvar.axes):
if axis.axisTag in pinnedAxes:
v = axisLimits[axis.axisTag][0]
newLimits[axis.axisTag] = instancer.NormalizedAxisTripleAndDistances(

Check warning on line 238 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L237-L238

Added lines #L237 - L238 were not covered by tests
v, v, v
)
continue
varIdx = axisIdx

Check warning on line 242 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L241-L242

Added lines #L241 - L242 were not covered by tests
if varIdxMap is not None:
varIdx = varIdxMap[varIdx]

Check warning on line 244 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L244

Added line #L244 was not covered by tests
# Only for public axes
private = axis.flags & 0x1
identityAxisIndex = None if private else axisIdx
minV, maxV = varStore.getExtremes(

Check warning on line 248 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L246-L248

Added lines #L246 - L248 were not covered by tests
varIdx, fvar.axes, limits, identityAxisIndex
)
# TODO: To 2.14 and back...
default = defaultDeltas[varIdx]
newLimits[axis.axisTag] = instancer.NormalizedAxisTripleAndDistances(

Check warning on line 253 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L252-L253

Added lines #L252 - L253 were not covered by tests
max(-1, min((default + minV) / 16384, +1)),
default / 16384,
max(-1, min((default + maxV) / 16384, +1)),
axis.defaultValue - axis.minValue,
axis.maxValue - axis.defaultValue,
)
limits = newLimits

Check warning on line 260 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L260

Added line #L260 was not covered by tests

return instancer.NormalizedAxisLimits(limits)

Check warning on line 262 in Lib/fontTools/ttLib/tables/_a_v_a_r.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_a_v_a_r.py#L262

Added line #L262 was not covered by tests
114 changes: 93 additions & 21 deletions Lib/fontTools/varLib/instancer/__init__.py
Expand Up @@ -108,6 +108,7 @@
from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.merger import MutatorMerger
from fontTools.varLib.instancer import names
from fontTools.varLib.varStore import OnlineVarStoreBuilder
from .featureVars import instantiateFeatureVariations
from fontTools.misc.cliTools import makeOutputFileName
from fontTools.varLib.instancer import solver
Expand Down Expand Up @@ -342,6 +343,9 @@
def __getitem__(self, key: str) -> AxisTriple:
return self._data[key]

def __setitem__(self, key, value):
self._data[key] = value

Check warning on line 347 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L347

Added line #L347 was not covered by tests

def __iter__(self) -> Iterable[str]:
return iter(self._data)

Expand Down Expand Up @@ -414,34 +418,46 @@
if a.axisTag in self
}

avarSegments = {}
if usingAvar and "avar" in varfont:
avarSegments = varfont["avar"].segments

normalizedLimits = {}

for axis_tag, triple in axes.items():
distanceNegative = triple[1] - triple[0]
distancePositive = triple[2] - triple[1]

if self[axis_tag] is None:
normalizedLimits[axis_tag] = NormalizedAxisTripleAndDistances(
0, 0, 0, distanceNegative, distancePositive
)
if self[axis_tag] is None: # Drop
normalizedLimits[axis_tag] = (0, 0, 0)

Check warning on line 425 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L425

Added line #L425 was not covered by tests
continue

minV, defaultV, maxV = self[axis_tag]

if defaultV is None:
defaultV = triple[1]

avarMapping = avarSegments.get(axis_tag, None)
normalizedLimits[axis_tag] = NormalizedAxisTripleAndDistances(
*(normalize(v, triple, avarMapping) for v in (minV, defaultV, maxV)),
distanceNegative,
distancePositive,
normalizedLimits[axis_tag] = tuple(
normalize(v, triple) for v in (minV, defaultV, maxV)
)

fvarAxes = fvar.getAxes()
for tag, triple in normalizedLimits.items():
minV, defaultV, maxV = fvarAxes[tag]
if defaultV is None:
defaultV = triple[1]

Check warning on line 441 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L441

Added line #L441 was not covered by tests
distanceNegative = defaultV - minV
distancePositive = maxV - defaultV

if triple is None: # Drop
normalizedLimits[tag] = NormalizedAxisTripleAndDistances(

Check warning on line 446 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L446

Added line #L446 was not covered by tests
0, 0, 0, distanceNegative, distancePositive
)
continue

Check warning on line 449 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L449

Added line #L449 was not covered by tests

normalizedLimits[tag] = NormalizedAxisTripleAndDistances(
*triple, distanceNegative, distancePositive
)

normalizedLimits = NormalizedAxisLimits(normalizedLimits)

if usingAvar and "avar" in varfont:
avar = varfont["avar"]
normalizedLimits = avar.renormalizeAxisLimits(normalizedLimits, varfont)

return NormalizedAxisLimits(normalizedLimits)


Expand Down Expand Up @@ -1313,12 +1329,10 @@
return True


def instantiateAvar(varfont, axisLimits):
def instantiateAvar(varfont, axisLimits, normalizedLimits):
# 'axisLimits' dict must contain user-space (non-normalized) coordinates.

avar = varfont["avar"]
if getattr(avar, "majorVersion", 1) >= 2 and avar.table.VarStore:
raise NotImplementedError("avar table with VarStore is not supported")

segments = avar.segments

Expand Down Expand Up @@ -1383,6 +1397,64 @@
newSegments[axisTag] = mapping
avar.segments = newSegments

version = getattr(avar, "majorVersion", 1)
if version == 1:
return

assert version == 2
fvarAxes = varfont["fvar"].axes
varStore = getattr(avar.table, "VarStore", None)

Check warning on line 1406 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1404-L1406

Added lines #L1404 - L1406 were not covered by tests

if varStore is not None:
# Compute scalar for each region, based on the new axis limits
regionList = varStore.VarRegionList.Region
scalars = []

Check warning on line 1411 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1410-L1411

Added lines #L1410 - L1411 were not covered by tests
for region in regionList:
regionAxes = region.get_support(fvarAxes)
scalar = 1.0

Check warning on line 1414 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1413-L1414

Added lines #L1413 - L1414 were not covered by tests
for axisTag, axis in regionAxes.items():
if axis[1] == 0:
continue

Check warning on line 1417 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1417

Added line #L1417 was not covered by tests
if axisTag in axisLimits:
axisRange = axisLimits[axisTag]
scalar *= (axisRange.maximum - axisRange.minimum) / 2.0
scalars.append(scalar)

Check warning on line 1421 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1419-L1421

Added lines #L1419 - L1421 were not covered by tests

varIdxMap = getattr(avar.table, "VarIdxMap", None)

Check warning on line 1423 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1423

Added line #L1423 was not covered by tests
varStoreBuilder = OnlineVarStoreBuilder([axis.axisTag for axis in fvarAxes])
newVarIdxMapping = []

Check warning on line 1425 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1425

Added line #L1425 was not covered by tests
for i in range(len(fvarAxes)):
if i in pinnedAxes:
continue
varIdx = i

Check warning on line 1429 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1428-L1429

Added lines #L1428 - L1429 were not covered by tests
if varIdxMap:
varIdx = varIdxMap[varIdx]

Check warning on line 1431 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1431

Added line #L1431 was not covered by tests
if varIdx != varStore.NO_VARIATION_INDEX:
VarData = varStore.VarData[varIdx >> 16]

Check warning on line 1433 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1433

Added line #L1433 was not covered by tests
supports = [
regionList[regionIndex].get_support(fvarAxes)
for regionIndex in VarData.VarRegionIndex
]
varStoreBuilder.setSupports(supports)
row = VarData.Item[varIdx & 0xFFFF]

Check warning on line 1439 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1438-L1439

Added lines #L1438 - L1439 were not covered by tests
row = [
delta / scalars[idx]
for delta, idx in zip(row, VarData.VarRegionIndex)
]
varIdx = varStoreBuilder.storeDeltas(row)

Check warning on line 1444 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1444

Added line #L1444 was not covered by tests

newVarIdxMapping.append(varIdx)

Check warning on line 1446 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1446

Added line #L1446 was not covered by tests

varStore = avar.table.VarStore = varStoreBuilder.finish()

Check warning on line 1448 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1448

Added line #L1448 was not covered by tests
if varIdxMap is not None:
varIdxMap.mapping = newVarIdxMapping

Check warning on line 1450 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1450

Added line #L1450 was not covered by tests

# TODO: Optimize VarStore

defaultDeltas = instantiateItemVariationStore(

Check warning on line 1454 in Lib/fontTools/varLib/instancer/__init__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/varLib/instancer/__init__.py#L1454

Added line #L1454 was not covered by tests
varStore, fvarAxes, normalizedLimits
)


def isInstanceWithinAxisRanges(location, axisRanges):
for axisTag, coord in location.items():
Expand Down Expand Up @@ -1505,7 +1577,7 @@
glyph.flags[0] |= flagOverlapSimple


def normalize(value, triple, avarMapping):
def normalize(value, triple, avarMapping=None):
value = normalizeValue(value, triple)
if avarMapping:
value = piecewiseLinearMap(value, avarMapping)
Expand Down Expand Up @@ -1621,7 +1693,7 @@
instantiateFeatureVariations(varfont, normalizedLimits)

if "avar" in varfont:
instantiateAvar(varfont, axisLimits)
instantiateAvar(varfont, axisLimits, normalizedLimits)

with names.pruningUnusedNames(varfont):
if "STAT" in varfont:
Expand Down
42 changes: 42 additions & 0 deletions Lib/fontTools/varLib/instancer/avar2.py
@@ -0,0 +1,42 @@
from fontTools.varLib.varStore import VarStoreInstancer, NO_VARIATION_INDEX
from fontTools.ttLib.tables.otTables import VarStore
from fontTools.ttLib.tables._f_v_a_r import Axis
from fontTools.varLib import instancer


if __name__ == "__main__":
import sys
from fontTools.ttLib import TTFont

font = TTFont(sys.argv[1])

limits = sys.argv[2:]
limits = instancer.parseLimits(limits)
limits = instancer.AxisLimits(limits).limitAxesAndPopulateDefaults(font)
limits = limits.normalize(font, usingAvar=False)
print(limits)

fvar = font["fvar"]
avar = font.get("avar", None)

varIdxMap = None
varStore = None
if avar and hasattr(avar, "table"):
varIdxMap = getattr(avar.table, "VarIdxMap", None)
varStore = getattr(avar.table, "VarStore", None)

pinnedAxes = limits.pinnedLocation()
unpinnedAxes = [axis for axis in fvar.axes if axis.axisTag not in pinnedAxes]

if avar:
limits = avar.renormalizeAxisLimits(limits, font)

for axisIdx, axis in enumerate(fvar.axes):
varIdx = axisIdx
if varIdxMap is not None:
varIdx = varIdxMap[varIdx]
private = axis.flags & 0x1
print(
"%s%s" % (axis.axisTag, "*" if private else ""),
limits.get(axis.axisTag, (0, 0, 0) if private else (-1, 0, +1)),
)