Skip to content

Commit

Permalink
Merge pull request #3130 from fonttools/fealib-ligcaret-variable
Browse files Browse the repository at this point in the history
[feaLib] Support variable ligature caret position
  • Loading branch information
khaledhosny committed Jun 8, 2023
2 parents f5c43ce + a43f824 commit 9ad4a36
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 29 deletions.
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 @@ def add_ligatureCaretByIndex_(self, location, glyphs, carets):
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

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):
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

0 comments on commit 9ad4a36

Please sign in to comment.