Last active
May 21, 2024 13:32
-
-
Save justvanrossum/1f816294c55abd1e762976702b01e6b7 to your computer and use it in GitHub Desktop.
Rename glyph names in OpenType feature files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import singledispatch | |
from io import StringIO | |
from fontTools.feaLib import ast | |
from fontTools.feaLib.error import FeatureLibError | |
from fontTools.feaLib.parser import Parser | |
def renameGlyphs(feaSource, renameFunc, glyphNames=()): | |
features = Parser(StringIO(feaSource), glyphNames=glyphNames).parse() | |
renameStatement(features, renameFunc) | |
return features.asFea() | |
@singledispatch | |
def renameStatement(statement, renameFunc): | |
if not hasattr(statement, "__dict__"): | |
return | |
for value in statement.__dict__.values(): | |
renameStatement(value, renameFunc) | |
@renameStatement.register | |
def _(statement: list | tuple, renameFunc): | |
for st in statement: | |
renameStatement(st, renameFunc) | |
@renameStatement.register | |
def _( | |
statement: ast.LanguageSystemStatement | ast.Comment | ast.FeatureLibLocation, | |
renameFunc, | |
): | |
pass | |
@renameStatement.register | |
def _(statement: ast.GlyphClass, renameFunc): | |
if hasattr(statement, "_has_been_renamed"): | |
return | |
statement._has_been_renamed = True | |
statement.glyphs = [renameFunc(g) for g in statement.glyphs] | |
statement.original = [ | |
(renameFunc(start), renameFunc(end)) for start, end in statement.original | |
] | |
@renameStatement.register | |
def _(statement: ast.GlyphName, renameFunc): | |
if hasattr(statement, "_has_been_renamed"): | |
return | |
statement._has_been_renamed = True | |
statement.glyph = renameFunc(statement.glyph) | |
@renameStatement.register | |
def _(statement: ast.LigatureSubstStatement, renameFunc): | |
if hasattr(statement, "_has_been_renamed"): | |
return | |
statement._has_been_renamed = True | |
renameStatement(statement.prefix, renameFunc) | |
renameStatement(statement.glyphs, renameFunc) | |
renameStatement(statement.suffix, renameFunc) | |
if isinstance(statement.replacement, str): | |
statement.replacement = renameFunc(statement.replacement) | |
else: | |
renameStatement(statement.replacement, renameFunc) | |
@renameStatement.register | |
def _(statement: ast.MarkClass, renameFunc): | |
# Skip, MarkClassDefinition deals with this? | |
# Else we'll get RecursionError: maximum recursion depth exceeded | |
pass | |
if __name__ == "__main__": | |
import sys | |
def renameFunc(gn): | |
assert not gn.endswith(".XXX"), gn | |
return gn + ".XXX" | |
for p in sys.argv[1:]: | |
print("#", p) | |
with open(p) as f: | |
feaSource = f.read() | |
try: | |
newFea = renameGlyphs(feaSource, renameFunc) | |
except FeatureLibError as e: | |
print("**************** FeatureLibError", e) | |
print(newFea) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment