diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 7f08709ef..4b9cb00f6 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -15,6 +15,7 @@ from fontTools.subset.util import _add_method, _uniq_sort from fontTools.subset.cff import * from fontTools.subset.svg import * from fontTools.varLib import varStore # for subset_varidxes +from fontTools.ttLib.tables._n_a_m_e import NameRecordVisitor import sys import struct import array @@ -2585,25 +2586,10 @@ def prune_post_subset(self, font, options): if self.version == 1: kept_labels = [] - dropped_labels = [] for i, label in enumerate(self.paletteEntryLabels): if i in retained_palette_indices: kept_labels.append(label) - else: - dropped_labels.append(label) self.paletteEntryLabels = kept_labels - # Remove dropped labels from the name table. - name_table = font["name"] - name_table.names = [ - n - for n in name_table.names - if ( - n.nameID not in dropped_labels - # Only remove nameIDs in the user range and if they're not explicitly kept - or n.nameID < 256 - or n.nameID in options.name_IDs - ) - ] return bool(self.numPaletteEntries) @@ -2915,32 +2901,10 @@ def prune_pre_subset(self, font, options): @_add_method(ttLib.getTableClass("name")) -def prune_pre_subset(self, font, options): - nameIDs = set(options.name_IDs) - fvar = font.get("fvar") - if fvar: - nameIDs.update([axis.axisNameID for axis in fvar.axes]) - nameIDs.update([inst.subfamilyNameID for inst in fvar.instances]) - nameIDs.update( - [ - inst.postscriptNameID - for inst in fvar.instances - if inst.postscriptNameID != 0xFFFF - ] - ) - stat = font.get("STAT") - if stat: - if stat.table.AxisValueArray: - nameIDs.update( - [val_rec.ValueNameID for val_rec in stat.table.AxisValueArray.AxisValue] - ) - nameIDs.update( - [axis_rec.AxisNameID for axis_rec in stat.table.DesignAxisRecord.Axis] - ) - cpal = font.get("CPAL") - if cpal and cpal.version == 1: - nameIDs.update(cpal.paletteLabels) - nameIDs.update(cpal.paletteEntryLabels) +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 not options.name_legacy: @@ -3463,7 +3427,14 @@ class Subsetter(object): font.setGlyphOrder(self.new_glyph_order) def _prune_post_subset(self, font): - for tag in font.keys(): + tableTags = font.keys() + # Prune the name table last because when we're pruning the name table, + # we visit each table in the font to see what name table records are + # still in use. + if "name" in tableTags: + tableTags.remove("name") + tableTags.append("name") + for tag in tableTags: if tag == "GlyphOrder": continue if tag == "OS/2": diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py index 320cfa820..152e4f268 100644 --- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py +++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py @@ -11,6 +11,10 @@ from fontTools.misc.textTools import ( ) from fontTools.misc.encodingTools import getEncoding from fontTools.ttLib import newTable +from fontTools.ttLib.ttVisitor import TTVisitor +from fontTools import ttLib +import fontTools.ttLib.tables.otTables as otTables +from fontTools.ttLib.tables import C_P_A_L_ from . import DefaultTable import struct import logging @@ -223,6 +227,28 @@ class table__n_a_m_e(DefaultTable.DefaultTable): ) ] + @staticmethod + def removeUnusedNames(ttFont): + """Remove any name records which are not in NameID range 0-255 and not utilized + within the font itself.""" + visitor = NameRecordVisitor() + visitor.visit(ttFont) + toDelete = set() + for record in ttFont["name"].names: + # Name IDs 26 to 255, inclusive, are reserved for future standard names. + # https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids + if record.nameID < 256: + continue + if record.nameID not in visitor.seen: + toDelete.add(record.nameID) + + if not toDelete: + return + log.info(f"Deleting name records with NameIDs {toDelete}") + for nameID in toDelete: + ttFont["name"].removeNames(nameID) + return toDelete + def _findUnusedNameID(self, minNameID=256): """Finds an unused name id. @@ -1138,3 +1164,50 @@ _MAC_LANGUAGE_TO_SCRIPT = { 150: 0, # langAzerbaijanRoman → smRoman 151: 0, # langNynorsk → smRoman } + + +class NameRecordVisitor(TTVisitor): + def __init__(self): + self.seen = set() + + +@NameRecordVisitor.register_attrs( + ( + (otTables.FeatureParamsSize, ("SubfamilyID", "SubfamilyNameID")), + (otTables.FeatureParamsStylisticSet, ("UINameID",)), + ( + otTables.FeatureParamsCharacterVariants, + ( + "FeatUILabelNameID", + "FeatUITooltipTextNameID", + "SampleTextNameID", + "FirstParamUILabelNameID", + ), + ), + (otTables.STAT, ("ElidedFallbackNameID",)), + (otTables.AxisRecord, ("AxisNameID",)), + (otTables.AxisValue, ("ValueNameID",)), + (otTables.FeatureName, ("FeatureNameID",)), + (otTables.Setting, ("SettingNameID",)), + ) +) +def visit(visitor, obj, attr, value): + visitor.seen.add(value) + + +@NameRecordVisitor.register(ttLib.getTableClass("fvar")) +def visit(visitor, obj): + for inst in obj.instances: + if inst.postscriptNameID != 0xFFFF: + visitor.seen.add(inst.postscriptNameID) + visitor.seen.add(inst.subfamilyNameID) + + for axis in obj.axes: + visitor.seen.add(axis.axisNameID) + + +@NameRecordVisitor.register(ttLib.getTableClass("CPAL")) +def visit(visitor, obj): + if obj.version == 1: + visitor.seen.update(obj.paletteLabels) + visitor.seen.update(obj.paletteEntryLabels)