fonttools/Tests/varLib/featureVars_test.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

307 lines
10 KiB
Python
Raw Permalink Normal View History

from collections import OrderedDict
from fontTools.designspaceLib import AxisDescriptor
from fontTools.ttLib import TTFont, newTable
from fontTools import varLib
from fontTools.varLib.featureVars import (
addFeatureVariations,
overlayFeatureVariations,
overlayBox,
)
import pytest
def makeVariableFont(glyphOrder, axes):
font = TTFont()
font.setGlyphOrder(glyphOrder)
font["name"] = newTable("name")
ds_axes = OrderedDict()
for axisTag, (minimum, default, maximum) in axes.items():
axis = AxisDescriptor()
axis.name = axis.tag = axis.labelNames["en"] = axisTag
axis.minimum, axis.default, axis.maximum = minimum, default, maximum
ds_axes[axisTag] = axis
varLib._add_fvar(font, ds_axes, instances=())
return font
@pytest.fixture
def varfont():
return makeVariableFont(
[".notdef", "space", "A", "B", "A.alt", "B.alt"],
{"wght": (100, 400, 900)},
)
def test_addFeatureVariations(varfont):
assert "GSUB" not in varfont
addFeatureVariations(varfont, [([{"wght": (0.5, 1.0)}], {"A": "A.alt"})])
assert "GSUB" in varfont
gsub = varfont["GSUB"].table
assert len(gsub.ScriptList.ScriptRecord) == 1
assert gsub.ScriptList.ScriptRecord[0].ScriptTag == "DFLT"
assert len(gsub.FeatureList.FeatureRecord) == 1
assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "rvrn"
assert len(gsub.LookupList.Lookup) == 1
assert gsub.LookupList.Lookup[0].LookupType == 1
assert len(gsub.LookupList.Lookup[0].SubTable) == 1
assert gsub.LookupList.Lookup[0].SubTable[0].mapping == {"A": "A.alt"}
assert gsub.FeatureVariations is not None
assert len(gsub.FeatureVariations.FeatureVariationRecord) == 1
fvr = gsub.FeatureVariations.FeatureVariationRecord[0]
assert len(fvr.ConditionSet.ConditionTable) == 1
cst = fvr.ConditionSet.ConditionTable[0]
assert cst.AxisIndex == 0
assert cst.FilterRangeMinValue == 0.5
assert cst.FilterRangeMaxValue == 1.0
assert len(fvr.FeatureTableSubstitution.SubstitutionRecord) == 1
ftsr = fvr.FeatureTableSubstitution.SubstitutionRecord[0]
assert ftsr.FeatureIndex == 0
assert ftsr.Feature.LookupListIndex == [0]
def _substitution_features(gsub, rec_index):
fea_tags = [feature.FeatureTag for feature in gsub.FeatureList.FeatureRecord]
fea_indices = [
gsub.FeatureVariations.FeatureVariationRecord[rec_index]
.FeatureTableSubstitution.SubstitutionRecord[i]
.FeatureIndex
for i in range(
len(
gsub.FeatureVariations.FeatureVariationRecord[
rec_index
].FeatureTableSubstitution.SubstitutionRecord
)
)
]
return [(i, fea_tags[i]) for i in fea_indices]
def test_addFeatureVariations_existing_variable_feature(varfont):
assert "GSUB" not in varfont
addFeatureVariations(varfont, [([{"wght": (0.5, 1.0)}], {"A": "A.alt"})])
gsub = varfont["GSUB"].table
assert len(gsub.FeatureList.FeatureRecord) == 1
assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "rvrn"
assert len(gsub.FeatureVariations.FeatureVariationRecord) == 1
assert _substitution_features(gsub, rec_index=0) == [(0, "rvrn")]
# can't add feature variations for an existing feature tag that already has some,
# in this case the default 'rvrn'
with pytest.raises(
varLib.VarLibError,
match=r"FeatureVariations already exist for feature tag\(s\): {'rvrn'}",
):
addFeatureVariations(varfont, [([{"wght": (0.5, 1.0)}], {"A": "A.alt"})])
def test_addFeatureVariations_new_feature(varfont):
assert "GSUB" not in varfont
addFeatureVariations(varfont, [([{"wght": (0.5, 1.0)}], {"A": "A.alt"})])
gsub = varfont["GSUB"].table
assert len(gsub.FeatureList.FeatureRecord) == 1
assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "rvrn"
assert len(gsub.LookupList.Lookup) == 1
assert len(gsub.FeatureVariations.FeatureVariationRecord) == 1
assert _substitution_features(gsub, rec_index=0) == [(0, "rvrn")]
# we can add feature variations for a feature tag that does not have
# any feature variations yet
addFeatureVariations(
varfont, [([{"wght": (-1.0, 0.0)}], {"B": "B.alt"})], featureTag="rclt"
)
assert len(gsub.FeatureList.FeatureRecord) == 2
# Note 'rclt' is now first (index=0) in the feature list sorted by tag, and
# 'rvrn' is second (index=1)
assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "rclt"
assert gsub.FeatureList.FeatureRecord[1].FeatureTag == "rvrn"
assert len(gsub.LookupList.Lookup) == 2
assert len(gsub.FeatureVariations.FeatureVariationRecord) == 2
# The new 'rclt' feature variation record is appended to the end;
# the feature index for 'rvrn' feature table substitution record is now 1
assert _substitution_features(gsub, rec_index=0) == [(1, "rvrn")]
assert _substitution_features(gsub, rec_index=1) == [(0, "rclt")]
2018-11-09 11:47:49 -05:00
def test_addFeatureVariations_existing_condition(varfont):
assert "GSUB" not in varfont
# Add a feature variation for 'ccmp' feature tag with a condition
addFeatureVariations(
varfont, [([{"wght": (0.5, 1.0)}], {"A": "A.alt"})], featureTag="ccmp"
)
gsub = varfont["GSUB"].table
# Should now have one feature record, one lookup, and one feature variation record
assert len(gsub.FeatureList.FeatureRecord) == 1
assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "ccmp"
assert len(gsub.LookupList.Lookup) == 1
assert len(gsub.FeatureVariations.FeatureVariationRecord) == 1
assert _substitution_features(gsub, rec_index=0) == [(0, "ccmp")]
# Add a feature variation for 'rlig' feature tag with the same condition
addFeatureVariations(
varfont, [([{"wght": (0.5, 1.0)}], {"B": "B.alt"})], featureTag="rlig"
)
# Should now have two feature records, two lookups, and one feature variation
# record, since the condition is the same for both feature variations
assert len(gsub.FeatureList.FeatureRecord) == 2
assert gsub.FeatureList.FeatureRecord[0].FeatureTag == "ccmp"
assert gsub.FeatureList.FeatureRecord[1].FeatureTag == "rlig"
assert len(gsub.LookupList.Lookup) == 2
assert len(gsub.FeatureVariations.FeatureVariationRecord) == 1
assert _substitution_features(gsub, rec_index=0) == [(0, "ccmp"), (1, "rlig")]
2023-02-08 14:18:49 -07:00
def _test_linear(n):
2018-11-09 11:47:49 -05:00
conds = []
for i in range(n):
end = i / n
start = end - 1.0
region = [{"X": (start, end)}]
2018-11-09 11:47:49 -05:00
subst = {"g%.2g" % start: "g%.2g" % end}
conds.append((region, subst))
overlaps = overlayFeatureVariations(conds)
assert len(overlaps) == 2 * n - 1, overlaps
2018-11-09 11:47:49 -05:00
return conds, overlaps
2023-02-08 14:27:48 -07:00
2023-02-08 14:18:49 -07:00
def test_linear():
_test_linear(10)
2022-12-13 11:26:36 +00:00
2023-02-08 14:27:48 -07:00
2023-02-08 14:18:49 -07:00
def _test_quadratic(n):
conds = []
for i in range(1, n + 1):
region = [{"X": (0, i / n), "Y": (0, (n + 1 - i) / n)}]
subst = {str(i): str(n + 1 - i)}
conds.append((region, subst))
overlaps = overlayFeatureVariations(conds)
assert len(overlaps) == n * (n + 1) // 2, overlaps
return conds, overlaps
2023-02-08 14:27:48 -07:00
2023-02-08 14:18:49 -07:00
def test_quadratic():
_test_quadratic(10)
2022-12-13 11:26:36 +00:00
2023-02-08 14:27:48 -07:00
2018-12-05 19:54:14 +01:00
def _merge_substitutions(substitutions):
merged = {}
for subst in substitutions:
merged.update(subst)
return merged
2022-12-13 11:26:36 +00:00
2018-12-05 19:54:14 +01:00
def _match_condition(location, overlaps):
for box, substitutions in overlaps:
for tag, coord in location.items():
start, end = box[tag]
if start <= coord <= end:
return _merge_substitutions(substitutions)
return {} # no match
2022-12-13 11:26:36 +00:00
2018-12-05 19:54:14 +01:00
def test_overlaps_1():
# https://github.com/fonttools/fonttools/issues/1400
conds = [
([{"abcd": (4, 9)}], {0: 0}),
([{"abcd": (5, 10)}], {1: 1}),
([{"abcd": (0, 8)}], {2: 2}),
([{"abcd": (3, 7)}], {3: 3}),
]
overlaps = overlayFeatureVariations(conds)
subst = _match_condition({"abcd": 0}, overlaps)
assert subst == {2: 2}
subst = _match_condition({"abcd": 1}, overlaps)
assert subst == {2: 2}
subst = _match_condition({"abcd": 3}, overlaps)
assert subst == {2: 2, 3: 3}
subst = _match_condition({"abcd": 4}, overlaps)
assert subst == {0: 0, 2: 2, 3: 3}
subst = _match_condition({"abcd": 5}, overlaps)
assert subst == {0: 0, 1: 1, 2: 2, 3: 3}
subst = _match_condition({"abcd": 7}, overlaps)
assert subst == {0: 0, 1: 1, 2: 2, 3: 3}
subst = _match_condition({"abcd": 8}, overlaps)
assert subst == {0: 0, 1: 1, 2: 2}
subst = _match_condition({"abcd": 9}, overlaps)
assert subst == {0: 0, 1: 1}
subst = _match_condition({"abcd": 10}, overlaps)
assert subst == {1: 1}
2022-12-13 11:26:36 +00:00
2018-12-05 19:54:14 +01:00
def test_overlaps_2():
# https://github.com/fonttools/fonttools/issues/1400
conds = [
([{"abcd": (1, 9)}], {0: 0}),
([{"abcd": (8, 10)}], {1: 1}),
([{"abcd": (3, 4)}], {2: 2}),
([{"abcd": (1, 10)}], {3: 3}),
]
overlaps = overlayFeatureVariations(conds)
subst = _match_condition({"abcd": 0}, overlaps)
assert subst == {}
subst = _match_condition({"abcd": 1}, overlaps)
assert subst == {0: 0, 3: 3}
subst = _match_condition({"abcd": 2}, overlaps)
assert subst == {0: 0, 3: 3}
subst = _match_condition({"abcd": 3}, overlaps)
assert subst == {0: 0, 2: 2, 3: 3}
subst = _match_condition({"abcd": 5}, overlaps)
assert subst == {0: 0, 3: 3}
subst = _match_condition({"abcd": 10}, overlaps)
assert subst == {1: 1, 3: 3}
def test_overlayBox():
# https://github.com/fonttools/fonttools/issues/3003
top = {"opsz": (0.75, 1.0), "wght": (0.5, 1.0)}
bot = {"wght": (0.25, 1.0)}
intersection, remainder = overlayBox(top, bot)
assert intersection == {"opsz": (0.75, 1.0), "wght": (0.5, 1.0)}
assert remainder == {"wght": (0.25, 1.0)}
def run(test, n, quiet):
print()
print("%s:" % test.__name__)
input, output = test(n)
2018-11-09 11:54:40 -05:00
if quiet:
print(len(output))
else:
print()
2018-11-09 11:54:40 -05:00
print("Input:")
pprint(input)
print()
print("Output:")
pprint(output)
print()
2022-12-13 11:26:36 +00:00
if __name__ == "__main__":
import sys
from pprint import pprint
2022-12-13 11:26:36 +00:00
quiet = False
n = 3
if len(sys.argv) > 1 and sys.argv[1] == "-q":
quiet = True
del sys.argv[1]
if len(sys.argv) > 1:
n = int(sys.argv[1])
2023-02-08 14:18:49 -07:00
run(_test_linear, n=n, quiet=quiet)
run(_test_quadratic, n=n, quiet=quiet)