Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove redundant name table records #3185

Merged
merged 7 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 13 additions & 42 deletions Lib/fontTools/subset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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
Expand Down Expand Up @@ -2585,25 +2586,10 @@ def collect_colors_by_index(paint):

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)


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -3463,7 +3427,14 @@ def _subset_glyphs(self, font):
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:
m4rc1e marked this conversation as resolved.
Show resolved Hide resolved
tableTags.remove("name")
tableTags.append("name")
for tag in tableTags:
if tag == "GlyphOrder":
continue
if tag == "OS/2":
Expand Down
73 changes: 73 additions & 0 deletions Lib/fontTools/ttLib/tables/_n_a_m_e.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
)
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
Expand Down Expand Up @@ -223,6 +227,28 @@
)
]

@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()

Check warning on line 236 in Lib/fontTools/ttLib/tables/_n_a_m_e.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_n_a_m_e.py#L234-L236

Added lines #L234 - L236 were not covered by tests
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

Check warning on line 241 in Lib/fontTools/ttLib/tables/_n_a_m_e.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_n_a_m_e.py#L241

Added line #L241 was not covered by tests
if record.nameID not in visitor.seen:
toDelete.add(record.nameID)

Check warning on line 243 in Lib/fontTools/ttLib/tables/_n_a_m_e.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_n_a_m_e.py#L243

Added line #L243 was not covered by tests

if not toDelete:
return
log.info(f"Deleting name records with NameIDs {toDelete}")

Check warning on line 247 in Lib/fontTools/ttLib/tables/_n_a_m_e.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_n_a_m_e.py#L246-L247

Added lines #L246 - L247 were not covered by tests
for nameID in toDelete:
ttFont["name"].removeNames(nameID)
return toDelete

Check warning on line 250 in Lib/fontTools/ttLib/tables/_n_a_m_e.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/ttLib/tables/_n_a_m_e.py#L249-L250

Added lines #L249 - L250 were not covered by tests

def _findUnusedNameID(self, minNameID=256):
"""Finds an unused name id.

Expand Down Expand Up @@ -1138,3 +1164,50 @@
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)