Last active
January 26, 2022 21:45
-
-
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.
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 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