Skip to content

Instantly share code, notes, and snippets.

@typoman
Last active August 10, 2021 14:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save typoman/e8d683f30e7d33b8527a4faa82d6527c to your computer and use it in GitHub Desktop.
Save typoman/e8d683f30e7d33b8527a4faa82d6527c to your computer and use it in GitHub Desktop.
Find ligature glyph names by parsing the GSUB using fonttools.feaLib
from fontTools.feaLib.parser import Parser
from fontTools.feaLib.ast import *
from io import StringIO
"""
RoboFont Script
- Type: Mastering
- Purpose: To find ligature by parsing the features.
- Specifications:
- Parse the substitutions in the current font features using fontTools.feaLib
- Find lookup references in the provided feature tags
- Find ligature statements only inside the the provided feature tags or lookup references
- Collect name of ligature glyphs and return it as a set
- Publish Date: 10 August 2021
- Author: Bahman Eslami
- License: MIT
"""
def getFeatureFileFeaLibAstElementFromFont(font):
featurefile = StringIO(font.features.text)
featurefile.name = font.path
parser = Parser(featurefile, font.keys())
return parser.parse()
def getLigaturesInFeaLibAstElement(e, featureTags, lookupNames):
ligatureNames = set()
if type(e) is FeatureBlock:
if e.name not in featureTags:
return set()
elif type(e) is LookupBlock:
if e.name not in lookupNames:
return set()
if hasattr(e, 'statements'):
for s in e.statements:
ligatureNames.update(getLigaturesInFeaLibAstElement(s, featureTags, lookupNames))
if type(e) is MultipleSubstStatement:
ligatureNames.add(e.replacement)
elif type(e) is LigatureSubstStatement:
ligatureNames.add(e.replacement)
return ligatureNames
def getLookupNamesInFeaLibAstElement(e, featureTags):
lookupNames = set()
if type(e) is FeatureBlock:
if e.name not in featureTags:
return set()
if hasattr(e, 'statements'):
for s in e.statements:
lookupNames.update(getLookupNamesInFeaLibAstElement(s, featureTags))
if type(e) is LookupReferenceStatement:
lookupNames.add(e.lookup.name)
elif type(e) is LookupBlock:
lookupNames.add(e.name)
return lookupNames
def getGlyphNamesFromListOfAstGlyphNames(astGlyphList):
return set([g.glyph for g in astGlyphList if type(g) is GlyphName])
def getLigatureAlternates(e, ligatureNames):
if hasattr(e, 'statements'):
for s in e.statements:
ligatureNames.update(getLigatureAlternates(s, ligatureNames))
if type(e) is SingleSubstStatement:
glyphs = getGlyphNamesFromListOfAstGlyphNames(e.glyphs)
if glyphs - ligatureNames == set():
ligatureNames.update(getGlyphNamesFromListOfAstGlyphNames(e.replacements))
return ligatureNames
def getLigaturesInFont(font, featureTags):
"""
Returns a set of glyph names which are ligatures. The featureTags argument is an iterable
which defines which features should be parsed.
"""
features = getFeatureFileFeaLibAstElementFromFont(font)
lookupNames = getLookupNamesInFeaLibAstElement(features, featureTags)
ligatureNames = getLigaturesInFeaLibAstElement(features, featureTags, lookupNames)
while True:
numLigas = len(ligatureNames)
ligatureNames = getLigatureAlternates(features, ligatureNames)
if len(ligatureNames) == numLigas:
break
return ligatureNames
if __name__ == "__main__":
font = CurrentFont()
featureTags = set(["liga", "dlig", "rlig"])
ligas = getLigaturesInFont(font, featureTags)
print(ligas)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment