diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index e0e102864..42d1f8f24 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -1553,7 +1553,16 @@ class Builder(object): 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 diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py index eefac24ee..49667f450 100644 --- a/Lib/fontTools/feaLib/parser.py +++ b/Lib/fontTools/feaLib/parser.py @@ -603,9 +603,9 @@ class Parser(object): 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) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index 7a5848ad8..6cbac7059 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -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 @@ -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: diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py index 6377db346..adcb058f8 100644 --- a/Tests/feaLib/builder_test.py +++ b/Tests/feaLib/builder_test.py @@ -226,6 +226,21 @@ class BuilderTest(unittest.TestCase): 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, @@ -1046,37 +1061,23 @@ class BuilderTest(unittest.TestCase): } 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) @@ -1084,12 +1085,36 @@ class BuilderTest(unittest.TestCase): 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): diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py index 2923f50ab..c140629ab 100644 --- a/Tests/feaLib/parser_test.py +++ b/Tests/feaLib/parser_test.py @@ -707,6 +707,17 @@ class ParserTest(unittest.TestCase): 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")