Repository: googlefonts/ufo2ft
Pull request #931 (2025-07-12 13:40:35)

Build multi-context anchors
In the latest Glyphs contextual anchors approach, it is possible to add more contexts to an anchor, and each context is separated by newlines.

It's my first time contributing here, so feel free to comment on my pull request if there is anything I misunderstood. :)

References to the issue I reported: #922


Patch:
diff --git a/Lib/ufo2ft/featureWriters/markFeatureWriter.py b/Lib/ufo2ft/featureWriters/markFeatureWriter.py
index e778599..5c000e1 100644
--- a/Lib/ufo2ft/featureWriters/markFeatureWriter.py
+++ b/Lib/ufo2ft/featureWriters/markFeatureWriter.py
@@ -762,6 +762,7 @@ class MarkFeatureWriter(BaseFeatureWriter):
                     continue
 
                 anchor_context = anchor.libData[ANCHOR_LIB_GPOS_CONTEXT_KEY].strip()
+
                 if not anchor_context:
                     self.log.warning(
                         "contextual anchor '%s' in glyph '%s' has no context data; skipped",
@@ -769,7 +770,12 @@ class MarkFeatureWriter(BaseFeatureWriter):
                         glyphName,
                     )
                     continue
-                dest[anchor_context].append((glyphName, anchor))
+
+                for context in anchor_context.splitlines():
+                    context = context.strip().rstrip(";")
+                    if not context:
+                        continue
+                    dest[context].append((glyphName, anchor))
         return baseResult, ligatureResult, markResult
 
     @staticmethod


Test patch:
diff --git a/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.alt.glif b/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.alt.glif
index 90e8991..4cd3478 100644
--- a/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.alt.glif
+++ b/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.alt.glif
@@ -20,7 +20,9 @@
         <key>051ABFBB-3A07-48C5-A5A3-82D4291DEAD1</key>
         <dict>
           <key>GPOS_Context</key>
-          <string>lookupflag UseMarrkFilteringSet [twodotsverticalbelow]; reh-ar *</string>
+          <string>lookupflag UseMarkFilteringSet [twodotsverticalbelow-ar]; reh-ar *
+lookupflag UseMarkFilteringSet [twodotsverticalbelow-ar]; dotbelow-ar *
+          </string>
         </dict>
       </dict>
     </dict>
diff --git a/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.glif b/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.glif
index abf21cf..ec157a7 100644
--- a/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.glif
+++ b/tests/data/ContextualAnchorsTest-Regular.ufo/glyphs/behD_otless-ar.init.glif
@@ -22,7 +22,7 @@
         <key>50838DB6-85ED-49AE-8935-C6201FC3FCD8</key>
         <dict>
           <key>GPOS_Context</key>
-          <string>lookupflag UseMarrkFilteringSet [twodotsverticalbelow]; reh-ar *</string>
+          <string>lookupflag UseMarkFilteringSet [twodotsverticalbelow-ar]; reh-ar *</string>
         </dict>
         <key>575ADDBE-67A1-4781-8244-18DEC9396567</key>
         <dict>
@@ -32,7 +32,7 @@
         <key>8AEC6EF0-8B9B-481A-AC0E-A95051FD2D12</key>
         <dict>
           <key>GPOS_Context</key>
-          <string>lookupflag UseMarrkFilteringSet [twodotshorizontalbelow]; reh-ar * behDotess-ar.medi &amp;</string>
+          <string>lookupflag UseMarkFilteringSet [twodotshorizontalbelow-ar]; reh-ar * behDotless-ar.medi &amp;</string>
         </dict>
       </dict>
     </dict>
diff --git a/tests/featureWriters/markFeatureWriter_test.py b/tests/featureWriters/markFeatureWriter_test.py
index 970fb50..3f90f74 100644
--- a/tests/featureWriters/markFeatureWriter_test.py
+++ b/tests/featureWriters/markFeatureWriter_test.py
@@ -1782,9 +1782,9 @@ class MarkFeatureWriterTest(FeatureWriterTest):
         lookup = feature.statements[-3].lookup
         assert str(lookup) == (
             "lookup ContextualMarkDispatch_0 {\n"
-            "    lookupflag UseMarrkFilteringSet [twodotshorizontalbelow];\n"
-            "    # reh-ar * behDotess-ar.medi &\n"
-            "    pos reh-ar [behDotless-ar.init] behDotess-ar.medi"
+            "    lookupflag UseMarkFilteringSet [twodotshorizontalbelow-ar];\n"
+            "    # reh-ar * behDotless-ar.medi &\n"
+            "    pos reh-ar [behDotless-ar.init] behDotless-ar.medi"
             " @MC_bottom'"
             " lookup ContextualMark_0;\n"
             "} ContextualMarkDispatch_0;\n"
@@ -1793,11 +1793,15 @@ class MarkFeatureWriterTest(FeatureWriterTest):
         lookup = feature.statements[-2].lookup
         assert str(lookup) == (
             "lookup ContextualMarkDispatch_1 {\n"
-            "    lookupflag UseMarrkFilteringSet [twodotsverticalbelow];\n"
+            "    lookupflag UseMarkFilteringSet [twodotsverticalbelow-ar];\n"
+            "    # dotbelow-ar *\n"
+            "    pos dotbelow-ar [behDotless-ar.init.alt]"
+            " @MC_bottom'"
+            " lookup ContextualMark_1;\n"
             "    # reh-ar *\n"
             "    pos reh-ar [behDotless-ar.init behDotless-ar.init.alt]"
             " @MC_bottom'"
-            " lookup ContextualMark_1;\n"
+            " lookup ContextualMark_2;\n"
             "} ContextualMarkDispatch_1;\n"
         )
 
@@ -1807,7 +1811,7 @@ class MarkFeatureWriterTest(FeatureWriterTest):
             "    # reh-ar *\n"
             "    pos reh-ar [behDotless-ar.init]"
             " @MC_bottom'"
-            " lookup ContextualMark_2;\n"
+            " lookup ContextualMark_3;\n"
             "} ContextualMarkDispatch_2;\n"
         )