Repository: googlefonts/ufo2ft
Pull request #873 (2024-09-23 08:46:38)

[featureWriters] Support insert marker in the middle of a feature block
Split the feature block into two and insert the generated code in the middle.

See https://github.com/googlefonts/ufo2ft/issues/351#issuecomment-765294436


Patch:
diff --git a/Lib/ufo2ft/featureWriters/baseFeatureWriter.py b/Lib/ufo2ft/featureWriters/baseFeatureWriter.py
index 5916a0f..de31a19 100644
--- a/Lib/ufo2ft/featureWriters/baseFeatureWriter.py
+++ b/Lib/ufo2ft/featureWriters/baseFeatureWriter.py
@@ -6,7 +6,6 @@ from fontTools.designspaceLib import DesignSpaceDocument
 from fontTools.feaLib.variableScalar import VariableScalar
 from fontTools.misc.fixedTools import otRound
 
-from ufo2ft.errors import InvalidFeaturesData
 from ufo2ft.featureWriters import ast
 from ufo2ft.util import (
     OpenTypeCategories,
@@ -218,17 +217,14 @@ class BaseFeatureWriter:
 
                 # insertFeatureMarker is in the middle of a feature block
                 # preceded and followed by statements that are not comments
-                #
-                # Glyphs3 can insert a feature block when rules are before
-                # and after the insert marker.
-                # See
-                # https://github.com/googlefonts/ufo2ft/issues/351#issuecomment-765294436
-                # This is currently not supported.
                 else:
-                    raise InvalidFeaturesData(
-                        "Insert marker has rules before and after, feature "
-                        f"{block.name} cannot be inserted. This is not supported."
-                    )
+                    index = statements.index(block) + 1
+                    # Split statements after the insertFeatureMarker into a new block
+                    afterBlock = ast.FeatureBlock(block.name)
+                    afterBlock.statements = block.statements[markerIndex:]
+                    statements.insert(index, afterBlock)
+                    # And remove them from the original block
+                    block.statements = block.statements[:markerIndex]
 
                 statements.insert(index, feature)
                 indices.append(index)


Test patch:
diff --git a/tests/featureWriters/__snapshots__/kernFeatureWriter2_test.ambr b/tests/featureWriters/__snapshots__/kernFeatureWriter2_test.ambr
index def5d3d..dfb691e 100644
--- a/tests/featureWriters/__snapshots__/kernFeatureWriter2_test.ambr
+++ b/tests/featureWriters/__snapshots__/kernFeatureWriter2_test.ambr
@@ -423,6 +423,31 @@
   '''
 # ---
 # name: test_insert_comment_middle
+  '''
+  feature kern {
+      pos one four' -50 six;
+      #
+  } kern;
+  
+  lookup kern_dflt {
+      lookupflag IgnoreMarks;
+      pos seven six 25;
+  } kern_dflt;
+  
+  feature kern {
+      script DFLT;
+      language dflt;
+      lookup kern_dflt;
+  } kern;
+  
+  feature kern {
+      #
+      pos one six' -50 six;
+  } kern;
+  
+  '''
+# ---
+# name: test_insert_comment_middle.1
   '''
   lookup kern_dflt {
       lookupflag IgnoreMarks;
diff --git a/tests/featureWriters/kernFeatureWriter2_test.py b/tests/featureWriters/kernFeatureWriter2_test.py
index 61c3a52..510b774 100644
--- a/tests/featureWriters/kernFeatureWriter2_test.py
+++ b/tests/featureWriters/kernFeatureWriter2_test.py
@@ -8,7 +8,6 @@ from syrupy.location import PyTestLocation
 from syrupy.types import SnapshotIndex
 
 from ufo2ft.constants import UNICODE_SCRIPT_ALIASES
-from ufo2ft.errors import InvalidFeaturesData
 from ufo2ft.featureCompiler import FeatureCompiler, parseLayoutFeatures
 from ufo2ft.featureWriters.kernFeatureWriter2 import KernFeatureWriter
 from ufo2ft.util import DFLT_SCRIPTS, unicodeScriptExtensions
@@ -348,12 +347,8 @@ def test_insert_comment_middle(snapshot, FontClass):
     writer = KernFeatureWriter()
     feaFile = parseLayoutFeatures(ufo)
 
-    with pytest.raises(
-        InvalidFeaturesData,
-        match="Insert marker has rules before and after, feature kern "
-        "cannot be inserted.",
-    ):
-        writer.write(ufo, feaFile)
+    writer.write(ufo, feaFile)
+    assert str(feaFile) == snapshot
 
     # test append mode ignores insert marker
     generated = KernFeatureWriterTest.writeFeatures(ufo, mode="append")
diff --git a/tests/featureWriters/kernFeatureWriter_test.py b/tests/featureWriters/kernFeatureWriter_test.py
index a4caa61..f55ec5e 100644
--- a/tests/featureWriters/kernFeatureWriter_test.py
+++ b/tests/featureWriters/kernFeatureWriter_test.py
@@ -6,7 +6,6 @@ import pytest
 from fontTools import unicodedata
 
 from ufo2ft.constants import UNICODE_SCRIPT_ALIASES
-from ufo2ft.errors import InvalidFeaturesData
 from ufo2ft.featureCompiler import FeatureCompiler, parseLayoutFeatures
 from ufo2ft.featureWriters import KernFeatureWriter, ast
 from ufo2ft.util import DFLT_SCRIPTS, unicodeScriptExtensions
@@ -170,7 +169,7 @@ class KernFeatureWriterTest(FeatureWriterTest):
                 language dflt;
                 lookup kern_Latn;
                 lookup kern_Latn_marks;
-                
+
                 script latn;
                 language dflt;
                 lookup kern_Latn;
@@ -191,7 +190,7 @@ class KernFeatureWriterTest(FeatureWriterTest):
                 script DFLT;
                 language dflt;
                 lookup kern_Latn;
-                
+
                 script latn;
                 language dflt;
                 lookup kern_Latn;
@@ -503,12 +502,31 @@ class KernFeatureWriterTest(FeatureWriterTest):
         writer = KernFeatureWriter()
         feaFile = parseLayoutFeatures(ufo)
 
-        with pytest.raises(
-            InvalidFeaturesData,
-            match="Insert marker has rules before and after, feature kern "
-            "cannot be inserted.",
-        ):
-            writer.write(ufo, feaFile)
+        writer.write(ufo, feaFile)
+        assert str(feaFile) == dedent(
+            """\
+            feature kern {
+                pos one four' -50 six;
+                #
+            } kern;
+
+            lookup kern_Default {
+                lookupflag IgnoreMarks;
+                pos seven six 25;
+            } kern_Default;
+
+            feature kern {
+                script DFLT;
+                language dflt;
+                lookup kern_Default;
+            } kern;
+
+            feature kern {
+                #
+                pos one six' -50 six;
+            } kern;
+            """
+        )
 
         # test append mode ignores insert marker
         generated = self.writeFeatures(ufo, mode="append")
@@ -1087,7 +1105,7 @@ class KernFeatureWriterTest(FeatureWriterTest):
                 script DFLT;
                 language dflt;
                 lookup kern_Latn;
- 
+
                 script arab;
                 language dflt;
                 lookup kern_Arab;
@@ -2010,7 +2028,7 @@ def test_defining_classdefs(FontClass):
         @kern1.Telu.ssatelugu.alt = [ssa-telugu.alt];
         @kern2.Telu.katelugu.below = [ka-telugu.below];
         @kern2.Telu.rVocalicMatratelugu = [rVocalicMatra-telugu];
-        
+
         lookup kern_Telu {
             lookupflag IgnoreMarks;
             enum pos @kern1.Telu.ssatelugu.alt sha-telugu.below 150;
@@ -2018,30 +2036,30 @@ def test_defining_classdefs(FontClass):
             pos @kern1.Default.ssatelugu.alt @kern2.Telu.katelugu.below 60;
             pos @kern1.Telu.ssatelugu.alt @kern2.Telu.katelugu.below 60;
         } kern_Telu;
-        
+
         lookup kern_Telu_marks {
             pos @kern1.Default.ssatelugu.alt @kern2.Telu.rVocalicMatratelugu 180;
             pos @kern1.Telu.ssatelugu.alt @kern2.Telu.rVocalicMatratelugu 180;
         } kern_Telu_marks;
-        
+
         lookup kern_Default {
             lookupflag IgnoreMarks;
             enum pos @kern1.Default.ssatelugu.alt sha-telugu.below 150;
         } kern_Default;
-        
+
         feature kern {
             script DFLT;
             language dflt;
             lookup kern_Default;
         } kern;
-        
+
         feature dist {
             script tel2;
             language dflt;
             lookup kern_Default;
             lookup kern_Telu;
             lookup kern_Telu_marks;
-        
+
             script telu;
             language dflt;
             lookup kern_Default;
diff --git a/tests/featureWriters/markFeatureWriter_test.py b/tests/featureWriters/markFeatureWriter_test.py
index e00daa4..a504f60 100644
--- a/tests/featureWriters/markFeatureWriter_test.py
+++ b/tests/featureWriters/markFeatureWriter_test.py
@@ -6,7 +6,6 @@ from textwrap import dedent
 import pytest
 
 from ufo2ft.constants import OBJECT_LIBS_KEY
-from ufo2ft.errors import InvalidFeaturesData
 from ufo2ft.featureCompiler import FeatureCompiler, parseLayoutFeatures
 from ufo2ft.featureWriters import ast
 from ufo2ft.featureWriters.markFeatureWriter import (
@@ -479,12 +478,56 @@ class MarkFeatureWriterTest(FeatureWriterTest):
         )
         feaFile = parseLayoutFeatures(testufo)
 
-        with pytest.raises(
-            InvalidFeaturesData,
-            match="Insert marker has rules before and after, feature mark "
-            "cannot be inserted.",
-        ):
-            writer.write(testufo, feaFile)
+        writer.write(testufo, feaFile)
+        assert str(feaFile) == dedent(
+            """\
+            markClass tildecomb <anchor 100 200> @MC_top;
+
+            markClass acutecomb <anchor 100 200> @MC_top;
+            feature mark {
+                lookup mark1 {
+                    pos base a
+                        <anchor 100 200> mark @MC_top;
+                } mark1;
+
+                #
+            } mark;
+
+            feature mark {
+                lookup mark2base {
+                    pos base a
+                        <anchor 100 200> mark @MC_top;
+                } mark2base;
+
+                lookup mark2liga {
+                    pos ligature f_i
+                            <anchor 100 500> mark @MC_top
+                        ligComponent
+                            <anchor 600 500> mark @MC_top;
+                } mark2liga;
+
+            } mark;
+
+            feature mark {
+                #
+                lookup mark2 {
+                    pos base a
+                        <anchor 150 250> mark @MC_top;
+                } mark2;
+
+            } mark;
+
+            feature mkmk {
+                lookup mark2mark_top {
+                    @MFS_mark2mark_top = [acutecomb tildecomb];
+                    lookupflag UseMarkFilteringSet @MFS_mark2mark_top;
+                    pos mark tildecomb
+                        <anchor 100 300> mark @MC_top;
+                } mark2mark_top;
+
+            } mkmk;
+            """
+        )
 
         # test append mode ignores insert marker
         generated = self.writeFeatures(testufo, mode="append")