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

[feaLib] Support variable ligature caret position #3130

Merged
merged 1 commit into from
Jun 8, 2023
Merged
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
9 changes: 9 additions & 0 deletions Lib/fontTools/feaLib/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,16 @@
if glyph not in self.ligCaretPoints_:
self.ligCaretPoints_[glyph] = carets

def makeLigCaret(self, location, caret):
if not isinstance(caret, VariableScalar):
return caret
default, device = self.makeVariablePos(location, caret)
if device is not None:
return (default, device)
return default

Check warning on line 1562 in Lib/fontTools/feaLib/builder.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/feaLib/builder.py#L1562

Added line #L1562 was not covered by tests

def add_ligatureCaretByPos_(self, location, glyphs, carets):
carets = [self.makeLigCaret(location, caret) for caret in carets]
for glyph in glyphs:
if glyph not in self.ligCaretCoords_:
self.ligCaretCoords_[glyph] = carets
Expand Down
4 changes: 2 additions & 2 deletions Lib/fontTools/feaLib/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,9 +603,9 @@ def parse_ligatureCaretByPos_(self):
assert self.is_cur_keyword_("LigatureCaretByPos")
location = self.cur_token_location_
glyphs = self.parse_glyphclass_(accept_glyphname=True)
carets = [self.expect_number_()]
carets = [self.expect_number_(variable=True)]
while self.next_token_ != ";":
carets.append(self.expect_number_())
carets.append(self.expect_number_(variable=True))
self.expect_symbol_(";")
return self.ast.LigatureCaretByPosStatement(glyphs, carets, location=location)

Expand Down
12 changes: 9 additions & 3 deletions Lib/fontTools/otlLib/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2509,9 +2509,14 @@ def buildAttachPoint(points):

def buildCaretValueForCoord(coord):
# 500 --> otTables.CaretValue, format 1
# (500, DeviceTable) --> otTables.CaretValue, format 3
self = ot.CaretValue()
self.Format = 1
self.Coordinate = coord
if isinstance(coord, tuple):
anthrotype marked this conversation as resolved.
Show resolved Hide resolved
self.Format = 3
self.Coordinate, self.DeviceTable = coord
else:
self.Format = 1
self.Coordinate = coord
return self


Expand Down Expand Up @@ -2573,7 +2578,8 @@ def buildLigGlyph(coords, points):
# ([500], [4]) --> otTables.LigGlyph; None for empty coords/points
carets = []
if coords:
carets.extend([buildCaretValueForCoord(c) for c in sorted(coords)])
coords = sorted(coords, key=lambda c: c[0] if isinstance(c, tuple) else c)
carets.extend([buildCaretValueForCoord(c) for c in coords])
if points:
carets.extend([buildCaretValueForPoint(p) for p in sorted(points)])
if not carets:
Expand Down
73 changes: 49 additions & 24 deletions Tests/feaLib/builder_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,21 @@ def normal_fea(self, lines):
output.append(l)
return output

def make_mock_vf(self):
font = makeTTFont()
font["name"] = newTable("name")
addFvar(font, self.VARFONT_AXES, [])
del font["name"]
return font

@staticmethod
def get_region(var_region_axis):
return (
var_region_axis.StartCoord,
var_region_axis.PeakCoord,
var_region_axis.EndCoord,
)

def test_alternateSubst_multipleSubstitutionsForSameGlyph(self):
self.assertRaisesRegex(
FeatureLibError,
Expand Down Expand Up @@ -1046,50 +1061,60 @@ def test_variable_scalar_avar(self):
} 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()
font = self.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)
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
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_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)
assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
assert self.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 = self.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)
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
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_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)
assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)

def test_ligatureCaretByPos_variable_scalar(self):
"""Test that the `avar` table is consulted when normalizing user-space
values."""

features = """
table GDEF {
LigatureCaretByPos f_i (wght=200:400 wght=900:1000) 380;
} GDEF;
"""

font = self.make_mock_vf()
addOpenTypeFeaturesFromString(font, features)

table = font["GDEF"].table
lig_glyph = table.LigCaretList.LigGlyph[0]
assert lig_glyph.CaretValue[0].Format == 1
assert lig_glyph.CaretValue[0].Coordinate == 380
assert lig_glyph.CaretValue[1].Format == 3
assert lig_glyph.CaretValue[1].Coordinate == 400

var_region_list = table.VarStore.VarRegionList
var_region_axis = var_region_list.Region[0].VarRegionAxis[0]
assert self.get_region(var_region_axis) == (0.0, 0.875, 0.875)


def generate_feature_file_test(name):
Expand Down
11 changes: 11 additions & 0 deletions Tests/feaLib/parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,17 @@ def test_ligatureCaretByPos_singleGlyph(self):
self.assertEqual(glyphstr([s.glyphs]), "f_i")
self.assertEqual(s.carets, [400, 380])

def test_ligatureCaretByPos_variable_scalar(self):
doc = self.parse(
"table GDEF {LigatureCaretByPos f_i (wght=200:400 wght=900:1000) 380;} GDEF;"
)
s = doc.statements[0].statements[0]
self.assertIsInstance(s, ast.LigatureCaretByPosStatement)
self.assertEqual(glyphstr([s.glyphs]), "f_i")
self.assertEqual(len(s.carets), 2)
self.assertEqual(str(s.carets[0]), "(wght=200:400 wght=900:1000)")
self.assertEqual(s.carets[1], 380)

def test_lookup_block(self):
[lookup] = self.parse("lookup Ligatures {} Ligatures;").statements
self.assertEqual(lookup.name, "Ligatures")
Expand Down