[varLib.models] By default, assume OpenType-like normalized space

See:
https://github.com/fonttools/fonttools/pull/2846#issuecomment-2267750076

I *think* this is an improvement, and no one should have been relying
on the broken existing behavior.

Docs need updating.
This commit is contained in:
Behdad Esfahbod 2024-08-05 11:27:12 -06:00
parent ead2a18d4b
commit 0c2652011e
6 changed files with 33 additions and 28 deletions

View File

@ -209,10 +209,14 @@ def supportScalar(location, support, ot=True, extrapolate=False, axisRanges=None
class VariationModel(object): class VariationModel(object):
"""Locations must have the base master at the origin (ie. 0). """Locations must have the base master at the origin (ie. 0).
If axis-ranges are not provided, values are assumed to be normalized to
the range [-1, 1].
If the extrapolate argument is set to True, then values are extrapolated If the extrapolate argument is set to True, then values are extrapolated
outside the axis range. outside the axis range.
>>> from pprint import pprint >>> from pprint import pprint
>>> axisRanges = {'wght': (-180, +180), 'wdth': (-1, +1)}
>>> locations = [ \ >>> locations = [ \
{'wght':100}, \ {'wght':100}, \
{'wght':-100}, \ {'wght':-100}, \
@ -224,7 +228,7 @@ class VariationModel(object):
{'wght':+180,'wdth':.3}, \ {'wght':+180,'wdth':.3}, \
{'wght':+180}, \ {'wght':+180}, \
] ]
>>> model = VariationModel(locations, axisOrder=['wght']) >>> model = VariationModel(locations, axisOrder=['wght'], axisRanges=axisRanges)
>>> pprint(model.locations) >>> pprint(model.locations)
[{}, [{},
{'wght': -100}, {'wght': -100},
@ -252,14 +256,22 @@ class VariationModel(object):
7: 0.6666666666666667}] 7: 0.6666666666666667}]
""" """
def __init__(self, locations, axisOrder=None, extrapolate=False): def __init__(
self, locations, axisOrder=None, extrapolate=False, *, axisRanges=None
):
if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations): if len(set(tuple(sorted(l.items())) for l in locations)) != len(locations):
raise VariationModelError("Locations must be unique.") raise VariationModelError("Locations must be unique.")
self.origLocations = locations self.origLocations = locations
self.axisOrder = axisOrder if axisOrder is not None else [] self.axisOrder = axisOrder if axisOrder is not None else []
self.extrapolate = extrapolate self.extrapolate = extrapolate
self.axisRanges = self.computeAxisRanges(locations) if extrapolate else None if axisRanges is None:
if extrapolate:
axisRanges = self.computeAxisRanges(locations)
else:
allAxes = {axis for loc in locations for axis in loc.keys()}
axisRanges = {axis: (-1, 1) for axis in allAxes}
self.axisRanges = axisRanges
locations = [{k: v for k, v in loc.items() if v != 0.0} for loc in locations] locations = [{k: v for k, v in loc.items() if v != 0.0} for loc in locations]
keyFunc = self.getMasterLocationsSortKeyFunc( keyFunc = self.getMasterLocationsSortKeyFunc(
@ -425,23 +437,16 @@ class VariationModel(object):
def _locationsToRegions(self): def _locationsToRegions(self):
locations = self.locations locations = self.locations
# Compute min/max across each axis, use it as total range. axisRanges = self.axisRanges
# TODO Take this as input from outside?
minV = {}
maxV = {}
for l in locations:
for k, v in l.items():
minV[k] = min(v, minV.get(k, v))
maxV[k] = max(v, maxV.get(k, v))
regions = [] regions = []
for loc in locations: for loc in locations:
region = {} region = {}
for axis, locV in loc.items(): for axis, locV in loc.items():
if locV > 0: if locV > 0:
region[axis] = (0, locV, maxV[axis]) region[axis] = (0, locV, axisRanges[axis][1])
else: else:
region[axis] = (minV[axis], locV, 0) region[axis] = (axisRanges[axis][0], locV, 0)
regions.append(region) regions.append(region)
return regions return regions

View File

@ -1112,12 +1112,12 @@ class BuilderTest(unittest.TestCase):
var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0] var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1] var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875) assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 1.0)
assert self.get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0) assert self.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_wght = var_region_list.Region[1].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1] var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875) assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 1.0)
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5) assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 1.0)
# With `avar`, shifting the wght axis' positive midpoint 0.5 a bit to # With `avar`, shifting the wght axis' positive midpoint 0.5 a bit to
# the right, but leaving the wdth axis alone: # the right, but leaving the wdth axis alone:
@ -1129,12 +1129,12 @@ class BuilderTest(unittest.TestCase):
var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0] var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1] var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625) assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 1.0)
assert self.get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0) assert self.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_wght = var_region_list.Region[1].VarRegionAxis[0]
var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1] var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625) assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 1.0)
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5) assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 1.0)
def test_ligatureCaretByPos_variable_scalar(self): def test_ligatureCaretByPos_variable_scalar(self):
"""Test that the `avar` table is consulted when normalizing user-space """Test that the `avar` table is consulted when normalizing user-space
@ -1158,7 +1158,7 @@ class BuilderTest(unittest.TestCase):
var_region_list = table.VarStore.VarRegionList var_region_list = table.VarStore.VarRegionList
var_region_axis = var_region_list.Region[0].VarRegionAxis[0] var_region_axis = var_region_list.Region[0].VarRegionAxis[0]
assert self.get_region(var_region_axis) == (0.0, 0.875, 0.875) assert self.get_region(var_region_axis) == (0.0, 0.875, 1.0)
def generate_feature_file_test(name): def generate_feature_file_test(name):

View File

@ -16,7 +16,7 @@
<VarRegionAxis index="0"> <VarRegionAxis index="0">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.875"/> <PeakCoord value="0.875"/>
<EndCoord value="0.875"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
<VarRegionAxis index="1"> <VarRegionAxis index="1">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>

View File

@ -12,7 +12,7 @@
<VarRegionAxis index="0"> <VarRegionAxis index="0">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.875"/> <PeakCoord value="0.875"/>
<EndCoord value="0.875"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
<VarRegionAxis index="1"> <VarRegionAxis index="1">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
@ -24,12 +24,12 @@
<VarRegionAxis index="0"> <VarRegionAxis index="0">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.875"/> <PeakCoord value="0.875"/>
<EndCoord value="0.875"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
<VarRegionAxis index="1"> <VarRegionAxis index="1">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.5"/> <PeakCoord value="0.5"/>
<EndCoord value="0.5"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
</Region> </Region>
</VarRegionList> </VarRegionList>

View File

@ -12,7 +12,7 @@
<VarRegionAxis index="0"> <VarRegionAxis index="0">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.875"/> <PeakCoord value="0.875"/>
<EndCoord value="0.875"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
<VarRegionAxis index="1"> <VarRegionAxis index="1">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
@ -24,12 +24,12 @@
<VarRegionAxis index="0"> <VarRegionAxis index="0">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.875"/> <PeakCoord value="0.875"/>
<EndCoord value="0.875"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
<VarRegionAxis index="1"> <VarRegionAxis index="1">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.5"/> <PeakCoord value="0.5"/>
<EndCoord value="0.5"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
</Region> </Region>
</VarRegionList> </VarRegionList>

View File

@ -23,7 +23,7 @@
<VarRegionAxis index="0"> <VarRegionAxis index="0">
<StartCoord value="0.0"/> <StartCoord value="0.0"/>
<PeakCoord value="0.38"/> <PeakCoord value="0.38"/>
<EndCoord value="0.38"/> <EndCoord value="1.0"/>
</VarRegionAxis> </VarRegionAxis>
</Region> </Region>
</VarRegionList> </VarRegionList>