Apply avar to variable locations

This commit is contained in:
Nikolaus Waxweiler 2023-03-15 16:19:39 +00:00
parent a993247e47
commit cf43ff5d22
3 changed files with 81 additions and 11 deletions

View File

@ -1614,6 +1614,7 @@ class Builder(object):
deviceX = otl.buildDevice(dict(anchor.xDeviceTable))
if anchor.yDeviceTable is not None:
deviceY = otl.buildDevice(dict(anchor.yDeviceTable))
avar = self.font.get("avar")
for dim in ("x", "y"):
if not isinstance(getattr(anchor, dim), VariableScalar):
continue
@ -1627,7 +1628,9 @@ class Builder(object):
)
varscalar = getattr(anchor, dim)
varscalar.axes = self.axes
default, index = varscalar.add_to_variation_store(self.varstorebuilder)
default, index = varscalar.add_to_variation_store(
self.varstorebuilder, avar
)
setattr(anchor, dim, default)
if index is not None and index != 0xFFFFFFFF:
if dim == "x":
@ -1654,6 +1657,7 @@ class Builder(object):
if not v:
return None
avar = self.font.get("avar")
vr = {}
for astName, (otName, isDevice) in self._VALUEREC_ATTRS.items():
val = getattr(v, astName, None)
@ -1674,7 +1678,7 @@ class Builder(object):
location,
)
val.axes = self.axes
default, index = val.add_to_variation_store(self.varstorebuilder)
default, index = val.add_to_variation_store(self.varstorebuilder, avar)
vr[otName] = default
if index is not None and index != 0xFFFFFFFF:
vr[otDeviceName] = buildVarDevTable(index)

View File

@ -1,4 +1,4 @@
from fontTools.varLib.models import VariationModel, normalizeValue
from fontTools.varLib.models import VariationModel, normalizeValue, piecewiseLinearMap
def Location(loc):
@ -83,29 +83,37 @@ class VariableScalar:
# I *guess* we could interpolate one, but I don't know how.
return self.values[key]
def value_at_location(self, location):
def value_at_location(self, location, avar=None):
loc = location
if loc in self.values.keys():
return self.values[loc]
values = list(self.values.values())
return self.model.interpolateFromMasters(loc, values)
return self.model(avar).interpolateFromMasters(loc, values)
@property
def model(self):
def model(self, avar=None):
key = tuple(self.values.keys())
if key in self.model_pool:
return self.model_pool[key]
locations = [dict(self._normalized_location(k)) for k in self.values.keys()]
if avar is not None:
mapping = avar.segments
locations = [
{
k: piecewiseLinearMap(v, mapping[k]) if k in mapping else v
for k, v in location.items()
}
for location in locations
]
m = VariationModel(locations)
self.model_pool[key] = m
return m
def get_deltas_and_supports(self):
def get_deltas_and_supports(self, avar=None):
values = list(self.values.values())
return self.model.getDeltasAndSupports(values)
return self.model(avar).getDeltasAndSupports(values)
def add_to_variation_store(self, store_builder):
deltas, supports = self.get_deltas_and_supports()
def add_to_variation_store(self, store_builder, avar=None):
deltas, supports = self.get_deltas_and_supports(avar)
store_builder.setSupports(supports)
index = store_builder.storeDeltas(deltas)
return int(self.default), index

View File

@ -1034,6 +1034,64 @@ class BuilderTest(unittest.TestCase):
assert condition_table[0].FilterRangeMinValue == 0.5
assert condition_table[1].FilterRangeMinValue == 0.7
def test_variable_scalar_avar(self):
"""Test that the `avar` table is consulted when normalizing user-space
values."""
features = """
languagesystem DFLT dflt;
feature kern {
pos cursive one <anchor 0 (wght=200:12 wght=900:22 wdth=150,wght=900:42)> <anchor NULL>;
pos two <0 (wght=200:12 wght=900:22 wdth=150,wght=900:42) 0 0>;
} kern;
"""
def make_mock_vf():
font = makeTTFont()
font["name"] = newTable("name")
addFvar(font, self.VARFONT_AXES, [])
del font["name"]
return font
def get_region(var_region_axis):
return (
var_region_axis.StartCoord,
var_region_axis.PeakCoord,
var_region_axis.EndCoord,
)
# Without `avar` (wght=200, wdth=100 is the default location):
font = make_mock_vf()
addOpenTypeFeaturesFromString(font, features)
var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
assert get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
assert get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0)
var_region_axis_wght = var_region_list.Region[1].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
assert get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
assert get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)
# With `avar`, shifting the wght axis' positive midpoint 0.5 a bit to
# the right, but leaving the wdth axis alone:
font = make_mock_vf()
font["avar"] = newTable("avar")
font["avar"].segments = {"wght": {-1.0: -1.0, 0.0: 0.0, 0.5: 0.625, 1.0: 1.0}}
addOpenTypeFeaturesFromString(font, features)
var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
assert get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
assert get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0)
var_region_axis_wght = var_region_list.Region[1].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
assert get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
assert get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)
def generate_feature_file_test(name):
return lambda self: self.check_feature_file(name)