Skip to content

Instantly share code, notes, and snippets.

@connordavenport
Created February 19, 2025 18:29
Show Gist options
  • Save connordavenport/89f48283e95318989fe5fae3235193df to your computer and use it in GitHub Desktop.
Save connordavenport/89f48283e95318989fe5fae3235193df to your computer and use it in GitHub Desktop.
add smart sets from internal feature file on opening
from mojo.subscriber import Subscriber, registerRoboFontSubscriber
from vanilla import *
import AppKit
import os
from compositor.textUtilities import convertCase
from defconAppKit.windows.baseWindow import BaseWindowController
from defconAppKit.controls.openTypeControlsView import OpenTypeControlsView
from defconAppKit.controls.glyphSequenceEditText import GlyphSequenceEditText
from defconAppKit.controls.glyphLineView import GlyphLineView
from ufo2fdk.makeotfParts import forceAbsoluteIncludesInFeatures, extractFeaturesAndTables
from ufo2ft.featureWriters.kernFeatureWriter import KernFeatureWriter, ast
from ufo2ft.util import makeOfficialGlyphOrder
import uharfbuzz as hb
from fontTools import unicodedata
from fontTools.feaLib.parser import Parser as FeatureParser
from fontTools.fontBuilder import FontBuilder
import io
# `FeatureFont` code from FeaturePreview.roboFontExt, thanks Tal and Frederik!
class Table(object):
def wrapValue(self, attribute, value):
def callback():
return value
setattr(self, attribute, callback)
class FeatureFont(object):
def __init__(self, font):
self.font = font
self.buildCMAP()
self.buildBinaryFont()
self.loadFeatures()
self.loadStylisticSetNames()
self.loadAlternates()
def buildCMAP(self):
font = self.font
self.cmap = {uni: names[0] for uni, names in font.unicodeData.items()}
# add all glyphs, even the un encoded ones at a high unicode...
# see https://github.com/harfbuzz/uharfbuzz/issues/22
unicodeOffset = 0x110000
unencodedCount = 0
for glyph in font:
if not glyph.unicodes:
self.cmap[unicodeOffset + unencodedCount] = glyph.name
unencodedCount += 1
def buildBinaryFont(self):
font = self.font
glyphOrder = sorted(set(font.glyphOrder) | set(self.cmap.values()))
ff = FontBuilder(int(round(font.info.unitsPerEm)), isTTF=True)
ff.setupGlyphOrder(glyphOrder)
ff.addOpenTypeFeatures(self._getFeatureText(font))
data = io.BytesIO()
ff.save(data)
self.source = ff.font
self._data = data.getvalue()
def loadFeatures(self):
ft = self.source
self.gsub = None
if "GSUB" in ft and ft["GSUB"].table.FeatureList is not None:
self.gsub = Table()
GSUBFeatureTags = set()
GSUBScriptList = set()
GSUBLanguageList = set()
for record in ft["GSUB"].table.FeatureList.FeatureRecord:
GSUBFeatureTags.add(record.FeatureTag)
for record in ft["GSUB"].table.ScriptList.ScriptRecord:
GSUBScriptList.add(record.ScriptTag)
script = record.Script
if script.LangSysCount:
for langSysRecord in script.LangSysRecord:
GSUBLanguageList.add(langSysRecord.LangSysTag)
self.gsub.wrapValue("getFeatureList", list(sorted(GSUBFeatureTags)))
def loadStylisticSetNames(self):
ft = self.source
self.stylisticSetNames = dict()
if "GSUB" in ft and ft["GSUB"].table.FeatureList is not None:
# names
nameIDs = {}
if "name" in ft:
for nameRecord in ft["name"].names:
nameID = nameRecord.nameID
platformID = nameRecord.platformID
platEncID = nameRecord.platEncID
langID = nameRecord.langID
nameIDs[nameID, platformID, platEncID, langID] = nameRecord.toUnicode()
for record in ft["GSUB"].table.FeatureList.FeatureRecord:
params = record.Feature.FeatureParams
if hasattr(params, "UINameID"):
ssNameID = params.UINameID
namePriority = [(ssNameID, 1, 0, 0), (ssNameID, 1, None, None), (ssNameID, 3, 1, 1033), (ssNameID, 3, None, None)]
ssName = self._skimNameIDs(nameIDs, namePriority)
if ssName:
self.stylisticSetNames[record.FeatureTag] = ssName
def loadAlternates(self):
self.alternates = {}
ft = self.source
if "GSUB" in ft:
lookup = ft["GSUB"].table.LookupList.Lookup
for record in ft["GSUB"].table.FeatureList.FeatureRecord:
if record.FeatureTag == "aalt":
for lookupIndex in record.Feature.LookupListIndex:
for subTable in lookup[lookupIndex].SubTable:
if subTable.LookupType == 1:
for key, value in subTable.mapping.items():
if key not in self.alternates:
self.alternates[key] = set()
self.alternates[key].add(value)
elif subTable.LookupType == 3:
for key, values in subTable.alternates.items():
if key not in self.alternates:
self.alternates[key] = set()
self.alternates[key] |= set(values)
def _getFeatureText(self, font):
if font.path is None:
fea = font.features.text
featuretags, _ = extractFeaturesAndTables(fea)
else:
fea = forceAbsoluteIncludesInFeatures(font.features.text, os.path.dirname(font.path))
featuretags, _ = extractFeaturesAndTables(fea, scannedFiles=[os.path.join(font.path, "features.fea")])
if "kern" not in featuretags:
languageSystems = set()
for glyph in font:
for uni in glyph.unicodes:
scriptTag = unicodedata.script(chr(uni))
languageSystems.add(scriptTag.lower())
languageSystems -= set(["common", "zyyy", "zinh", "zzzz"])
languageSystems = ["DFLT"] + sorted(languageSystems)
data = io.StringIO(fea)
feaParser = FeatureParser(data, set(font.keys()))
feaFile = feaParser.parse()
existingLanguageSystems = set()
DFLTindex = 0
# search for existing language systems
# and keep the index of the DFLT if existing
for index, st in enumerate(feaFile.statements):
if isinstance(st, ast.LanguageSystemStatement):
existingLanguageSystems.add(st.script)
if st.script == "DFLT":
DFLTindex = index + 1
addedStatement = []
for script in reversed(languageSystems):
if script not in existingLanguageSystems:
statement = ast.LanguageSystemStatement(script=script, language="dflt")
addedStatement.append(statement)
feaFile.statements.insert(DFLTindex, statement)
writer = KernFeatureWriter()
def _kernFeatureWriterSetOrderedGlyphSet():
"""Return OrderedDict[glyphName, glyph] sorted by glyphOrder."""
glyphOrder = makeOfficialGlyphOrder(font, font.glyphOrder)
return {glyphName: font[glyphName] for glyphName in glyphOrder}
writer.getOrderedGlyphSet = _kernFeatureWriterSetOrderedGlyphSet
writer.write(font, feaFile)
# clean up
for statement in addedStatement:
feaFile.statements.remove(statement)
def removeScriptlanguage(block):
for statement in list(block.statements):
if hasattr(statement, "statements"):
removeScriptlanguage(statement)
if isinstance(statement, (ast.ScriptStatement, ast.LanguageStatement)):
block.statements.remove(statement)
for block in ast.iterFeatureBlocks(feaFile, tag="kern"):
removeScriptlanguage(block)
fea = feaFile.asFea()
return fea
def _skimNameIDs(self, nameIDs, priority):
for (nameID, platformID, platEncID, langID) in priority:
for (nID, pID, pEID, lID), text in nameIDs.items():
if nID != nameID:
continue
if pID != platformID and platformID is not None:
continue
if pEID != platEncID and platEncID is not None:
continue
if lID != langID and langID is not None:
continue
return text
class set_fea_sets(Subscriber):
def fontDocumentWillOpen(self,info):
font = info["font"]
ff = FeatureFont(font.naked())
#query = []
query = font.lib.get("com.typemytype.robofont.smartSets",[])
current = [Q["smartSetName"] for Q in query]
for tag in ff.gsub.getFeatureList():
subq = {}
GSUB = ff.source["GSUB"]
for r in GSUB.table.FeatureList.FeatureRecord:
if r.Feature.LookupListIndex:
if r.FeatureTag == tag:
Lookup = GSUB.table.LookupList.Lookup[r.Feature.LookupListIndex[0]]
for Subtable in Lookup.SubTable:
if Subtable.LookupType == 1:
mapping = list(Subtable.mapping.values())
name = f"{tag} {ff.stylisticSetNames.get(tag, "")}".strip()
# overwrite old sets
if name in current:
ii = [i for i in query if i.get("smartSetName", "") == name]
if ii:
query.remove(ii[0])
subq["smartSetName"] = name
subq["glyphNames"] = mapping
query.append(subq)
if query:
font.lib["com.typemytype.robofont.smartSets"] = query
registerRoboFontSubscriber(set_fea_sets)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment