Repository: fonttools/fonttools

Issue:
[subset] `--name-IDs='*'` keeps also unused user name IDs
When `--name-IDs='*'` is used, all name IDs in the font are kept including user name IDs, but the option should only affect pre-defined name IDs (or any name ID < 256) and user name IDs should be pruned as usual.

I believe this is caused by https://github.com/fonttools/fonttools/blob/969d09dcafd3669753e30060ccca7e5fd4c356cd/Lib/fontTools/subset/__init__.py#L2915-L2917

I think it should be something like:
```python
    nameIDs = set(options.name_IDs) | visitor.seen
    if "*" in options.name_IDs:
        nameIDs |= {n.nameID for n in self.names if n.nameID < 256}
    self.names = [n for n in self.names if n.nameID in nameIDs]
```


Discussion:
SGTM.



Patch:
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index 250a07ef1a..7e6985db25 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -2913,8 +2913,9 @@ def prune_post_subset(self, font, options):
     visitor = NameRecordVisitor()
     visitor.visit(font)
     nameIDs = set(options.name_IDs) | visitor.seen
-    if "*" not in options.name_IDs:
-        self.names = [n for n in self.names if n.nameID in nameIDs]
+    if "*" in options.name_IDs:
+        nameIDs |= {n.nameID for n in self.names if n.nameID < 256}
+    self.names = [n for n in self.names if n.nameID in nameIDs]
     if not options.name_legacy:
         # TODO(behdad) Sometimes (eg Apple Color Emoji) there's only a macroman
         # entry for Latin and no Unicode names.


Test patch:
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index 2b57633c29..5c6dfbb2fc 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -1915,10 +1915,6 @@ def test_subset_recalc_xAvgCharWidth(ttf_path):
     assert xAvgCharWidth_after == subset_font["OS/2"].xAvgCharWidth
 
 
-if __name__ == "__main__":
-    sys.exit(unittest.main())
-
-
 def test_subset_prune_gdef_markglyphsetsdef():
     # GDEF_MarkGlyphSetsDef
     fb = FontBuilder(unitsPerEm=1000, isTTF=True)
@@ -2023,3 +2019,57 @@ def test_subset_prune_gdef_markglyphsetsdef():
     assert lookups[1].MarkFilteringSet == None
     marksets = font["GDEF"].table.MarkGlyphSetsDef.Coverage
     assert marksets[0].glyphs == ["acutecomb"]
+
+
+def test_prune_user_name_IDs_with_keep_all(ttf_path):
+    font = TTFont(ttf_path)
+
+    keepNameIDs = {n.nameID for n in font["name"].names}
+
+    for i in range(10):
+        font["name"].addName(f"Test{i}")
+
+    options = subset.Options()
+    options.name_IDs = ["*"]
+    options.name_legacy = True
+    options.name_languages = ["*"]
+
+    subsetter = subset.Subsetter(options)
+    subsetter.populate(unicodes=font.getBestCmap().keys())
+    subsetter.subset(font)
+
+    nameIDs = {n.nameID for n in font["name"].names}
+    assert not any(n > 255 for n in nameIDs)
+    assert nameIDs == keepNameIDs
+
+
+def test_prune_unused_user_name_IDs_with_keep_all(ttf_path):
+    font = TTFont(ttf_path)
+
+    keepNameIDs = {n.nameID for n in font["name"].names}
+
+    for i in range(10):
+        font["name"].addName(f"Test{i}")
+
+    nameID = font["name"].addName("Test STAT")
+    keepNameIDs.add(nameID)
+
+    font["STAT"] = newTable("STAT")
+    font["STAT"].table = ot.STAT()
+    font["STAT"].table.ElidedFallbackNameID = nameID
+
+    options = subset.Options()
+    options.name_IDs = ["*"]
+    options.name_legacy = True
+    options.name_languages = ["*"]
+
+    subsetter = subset.Subsetter(options)
+    subsetter.populate(unicodes=font.getBestCmap().keys())
+    subsetter.subset(font)
+
+    nameIDs = {n.nameID for n in font["name"].names}
+    assert nameIDs == keepNameIDs
+
+
+if __name__ == "__main__":
+    sys.exit(unittest.main())