Skip to content

Instantly share code, notes, and snippets.

@connordavenport
Last active January 26, 2022 21:45
Show Gist options
  • Select an option

  • Save connordavenport/ca7a33591605610a2ca9fbf3f82edb09 to your computer and use it in GitHub Desktop.

Select an option

Save connordavenport/ca7a33591605610a2ca9fbf3f82edb09 to your computer and use it in GitHub Desktop.
My version of PyFtFeaFreeze.py (By Adam Twardoch). Some code taken from it.
from AppKit import NSFilenamesPboardType, NSDragOperationCopy, NSEvent
from mojo.events import BaseEventObserver
from mojo.UI import GetFile,PostBannerNotification
import os
from fontTools.ttLib import TTFont
import vanilla
from fontParts.world import RGlyph
try:
from AppKit import NSRadioButtonType
except ImportError:
from AppKit import NSRadioButton
NSRadioButtonType = NSRadioButton
'''
My version of PyFtFeaFreeze with a GUI.
• Drag SFNT fonts on the list, left side will populate with GSUB features
• Select the feature you want to freeze and click Freeze
'''
class IceBath(BaseEventObserver):
def __init__(self):
self.L = 0 # left
self.T = 0 # top
self.W = 600 # width
self.H = 300 # height
self.p = 5 # padding
self.buttonHeight = 20
self.savePath = None
self.features = None
self.selectedFea = None
title = f"Ice Bath"
self.w = vanilla.Window((self.W, self.H), title)
self.w.radioGroup = vanilla.RadioGroup((self.p, self.p, 200, -(self.p * 2 + self.buttonHeight)),
[],
callback=self.OTCallback)
self.w.dropList = vanilla.List(
(self.p+200, self.p, 400-self.p*2, -(self.p * 2 + self.buttonHeight)),
[],
columnDescriptions=[
{"title": "?", "width": 25}, # status
{"title": "Files", "allowsSorting": True}], # files
drawFocusRing=False,
showColumnTitles=False,
allowsMultipleSelection=True,
enableDelete=True,
otherApplicationDropSettings = dict(
type=NSFilenamesPboardType,
operation=NSDragOperationCopy,
callback=self.dropCallback),
)
self.w.searchebutton = vanilla.Button((self.p,-(self.p + self.buttonHeight),25,self.buttonHeight),
"↑", callback=self.searchButtonCallback)
self.w.compilebutton = vanilla.Button((self.p*2+25, -(self.p + self.buttonHeight), -self.p, self.buttonHeight),
"Freeze", callback=self.compileButtonCallback)
self.w.compilebutton.enable(False)
self.w.open()
def OTCallback(self,sender):
self.selectedFea = self.features[sender.get()]
def searchButtonCallback(self,sender):
paths = GetFile(message="Import Master UFOs", allowsMultipleSelection=True, fileTypes=["ufo"])
if paths:
existingPaths = [ff['Files'] for ff in self.w.dropList.get()]
paths = [path for path in paths if path not in existingPaths]
if not paths:
self.w.compilebutton.enable(False)
return False
else:
self.w.compilebutton.enable(True)
for path in paths:
directory, ufoPath = os.path.split(path)
item = {}
item['?'] = '○'
item['Files'] = path
self.w.dropList.append(item)
def getUnicodeFromName(self, name, subtable):
u = None
for uni,CIDname in list(subtable.cmap.items()):
if CIDname == name:
u = uni
if u:
return u
# t = RGlyph()
# t.name = name
# t.autoUnicodes()
# return t.unicode
def getAllFeatures(self,fontList):
allFeatures = []
for font in fontList:
f = TTFont(font)
for fr in f["GSUB"].table.FeatureList.FeatureRecord:
allFeatures.append(fr.FeatureTag)
return sorted(list(set(allFeatures)))
def compileButtonCallback(self, sender):
mapping = None
alternates = None
for ps in self.w.dropList:
path = ps["Files"]
ext = os.path.splitext(path)[1]
savePath = path.replace(ext,f"-{self.selectedFea}{ext}")
f = TTFont(path)
GSUB = f["GSUB"]
cmap = f['cmap']
i = []
for r in GSUB.table.FeatureList.FeatureRecord:
if r.FeatureTag == self.selectedFea:
i.append(r.Feature.LookupListIndex[0])
i = sorted(list(set(i)))
if i:
for LookupID in i:
Lookup = GSUB.table.LookupList.Lookup[LookupID]
for Subtable in Lookup.SubTable:
mapping = None
alternates = None
if Subtable.LookupType == 1:
mapping = Subtable.mapping
elif Subtable.LookupType == 3:
alternates = Subtable.alternates
elif Subtable.LookupType == 7:
ExtSubTable = Subtable.ExtSubTable
if ExtSubTable.LookupType == 1:
mapping = ExtSubTable.mapping
elif ExtSubTable.LookupType == 3:
alternates = ExtSubTable.alternates
if mapping:
# print(mapping)
Subtable.mapping = {b:a for a,b in mapping.items()}
m = mapping
if alternates:
# print(alternates)
Subtable.mapping = {b:a for a,b in alternates.items()}
m = alternates
if m:
remap = list(m.keys())
for table in cmap.tables:
for n in remap:
c = self.getUnicodeFromName(n, table)
if c:
table.cmap[c] = m[n]
f.save(savePath)
else:
PostBannerNotification("IceBath", f"{self.selectedFea} isn't present in {f['name'].getName(4,1,0,0)}")
def dropCallback(self, sender, dropInfo):
isProposal = dropInfo["isProposal"]
existingPaths = [ff['Files'] for ff in self.w.dropList.get()]
paths = dropInfo["data"]
paths = [path for path in paths if path not in existingPaths]
paths = [path for path in paths if os.path.splitext(path)[-1].lower() in ['.otf','.ttf'] or os.path.isdir(path)]
if not paths:
self.w.compilebutton.enable(False)
return False
if not isProposal:
for path in paths:
directory, ufoPath = os.path.split(path)
item = {}
item['?'] = '○'
item['Files'] = path
self.w.dropList.append(item)
self.features = self.getAllFeatures([b["Files"] for b in self.w.dropList.get()])
winX, winY, width, height = self.w.getPosSize()
if len(self.features) > 14:
offset = len(self.features) - 14
newheight = (offset*20) + height
self.w.setPosSize((winX, winY, width, newheight))
else:
if self.w.getPosSize()[3] == self.H:
pass
else:
self.w.setPosSize((winX, winY, width, self.H))
if hasattr(self.w, "radioGroup"):
delattr(self.w, "radioGroup")
setattr(self.w, "radioGroup", vanilla.RadioGroup((self.p, self.p, 200, -(self.p * 2 + self.buttonHeight)),self.features,callback=self.OTCallback))
self.w.compilebutton.enable(True)
return True
IceBath()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment