Last active
July 15, 2021 18:24
-
-
Save okay-type/86a9d86e1daf1b26f9c9236cdc612d06 to your computer and use it in GitHub Desktop.
This file contains 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 mojo.UI import GetFolder | |
from glyphNameFormatter import GlyphName | |
import metricsMachine as mm | |
from vanilla import * | |
from vanilla.dialogs import putFile | |
from mojo.events import addObserver, removeObserver, postEvent | |
from AppKit import NSColor, NSFloatingWindowLevel, NSWindowStyleMaskClosable, NSFullSizeContentViewWindowMask, NSTitledWindowMask | |
from mojo.extensions import registerExtensionDefaults, setExtensionDefault, getExtensionDefault, removeExtensionDefault | |
from AppKit import NSApp | |
''' | |
A simple ui to make small tweaks to a metrics machine pairlist while kerning. | |
ok@yty.pe | |
How to use: | |
1: Open Metrics Machine window | |
2: Load a pairlist.txt | |
3: Run this script | |
4: Use the interface to edit the pairlist. Delete, insert, and reorder pairs | |
5: Save the edited pairlist when you're done | |
Known Issues: | |
- You can have duplicate items in a pairlist, but it can get messy. This tool tries to guess which pair you're targeting (see: closestIndex()) but your mileage may vary. | |
- The MM API prints the entire pairlist in SetPairList(). This is a bug, right? In the meantime, I just commented out that line out in the mmScripting.py file inside the MM extension. | |
Improvements To Make: | |
- shortcuts for bulk deleting pairs | |
(e.g.: ('H', '*') would delete every pair with an H on the left) | |
- detect when a new pairlist is loaded | |
(is this possible? I see a MetricsMachine.currentPairChanged event but that's it. Maybe we should load pairlists through this tool with a [Load] button.) | |
Version 0.6 | |
- flip pair button is now two buttons: | |
↔️ flips left-right pairs | |
🔃 flips and mirrors the glyphs so '(' becomes ')' | |
-- see the dictionary below for the full list of pairs | |
both buttons also now set the flipped pair in the MM window (but don't change your list position) | |
Version 0.5 | |
- Figured out how to throw focus back to the MM window after pressing a button | |
Version 0.4 | |
- Fixed some big bugs (removeobserver works now, nonexisting pairs dont traceback, etc) | |
- Switched to normal vanilla window, but still floats | |
- Save/Load position as preference | |
Version 0.3 | |
- Smaller title bar sized layout | |
Version 0.2 | |
- Delete now mantains the selection in the pairlist | |
- Other little improvements | |
Version 0.1 | |
- Initial version | |
''' | |
class mmPairlistTuner(): | |
def __init__(self): | |
self.f = CurrentFont() | |
self.pairList = mm.GetPairList(font=self.f) | |
self.pairCurrent = mm.GetCurrentPair(font=self.f) | |
self.indexHistory = [0, 0] | |
intialindex = self.pairList.index(mm.GetCurrentPair()) | |
if intialindex != None: | |
self.indexHistory = [intialindex, 0] | |
self.mmWindow = self.getTheMmUI(self.f) | |
self.UIpairList = self.mmWindow.delegate().vanillaWrapper().pairList | |
self.buildUI() | |
addObserver(self, 'pairChanged', 'MetricsMachine.currentPairChanged') | |
def getTheMmUI(self, font=None): | |
if font is None: | |
font = CurrentFont() | |
for other in AllFonts(): | |
if other != font: | |
continue | |
document = other.document() | |
for controller in document.windowControllers(): | |
window = controller.window() | |
if hasattr(window, "windowName") and window.windowName() == "MetricsMachineMainWindow": | |
return window | |
raise MetricsMachineScriptingError("A MetricsMachine window is not open for %r." % font) | |
def buildUI(self): | |
width = 70 | |
shift = width | |
self.posSize = (0, 0, width*9+shift+35, 22) | |
windowname = 'Pairlist Tuner' | |
# DEBUG - close old versions of window | |
for window in [w for w in NSApp().orderedWindows() if w.isVisible()]: | |
if window.title() == windowname: | |
window.close() | |
self.w = Window( | |
self.posSize, | |
windowname, | |
fullSizeContentView=True, | |
titleVisible=False, | |
) | |
ns = self.w.getNSWindow() | |
ns.setLevel_(NSFloatingWindowLevel) | |
ns.setHidesOnDeactivate_(True) | |
i = 0 | |
self.w.flip = Button((width*i+shift, 0, width/3, -0), '↔️', sizeStyle='small',callback=self.flipNewPair) | |
i += .3 | |
self.w.mirror = Button((width*i+shift, 0, width/3, -0), '🔃', sizeStyle='small',callback=self.mirrorPair) | |
i += .7 | |
self.w.left = EditText((width*i+shift, 5, width*2, 25), self.pairCurrent[0], sizeStyle='small') | |
i += 2 | |
self.w.right = EditText((width*i+shift, 5, width*2, 25), self.pairCurrent[1], sizeStyle='small') | |
i += 2 | |
self.w.delete = Button((width*i+shift, 0, width, -0), 'Delete', sizeStyle='small',callback=self.deleteCurrentPair) | |
i += 1 | |
self.w.insert = Button((width*i+shift, 0, width, -0), 'Insert', sizeStyle='small',callback=self.insertNewPair) | |
i += 1 | |
self.w.up = Button((width*i+shift, 0, width/2, -0), '⬆︎', sizeStyle='small',callback=self.movePairUp) | |
i += .5 | |
self.w.down = Button((width*i+shift, 0, width/2, -0), '⬇︎', sizeStyle='small',callback=self.movePairDown) | |
i += 1 | |
self.w.save = Button((width*i+shift, 0, width, -0), 'Save', sizeStyle='small',callback=self.save) | |
self.flatText(self.w.left) | |
self.flatText(self.w.right) | |
self.flatButton(self.w.flip) | |
self.flatButton(self.w.mirror) | |
self.loadPref() | |
self.w.bind('close', self.closeUI) | |
self.w.open() | |
def loadPref(self): | |
self.prefKey = 'com.okaytype.pairlisttuner' | |
# defaults | |
initialDefaults = { | |
self.prefKey + '.win.posSize': (0, 0, self.posSize[2], self.posSize[3]), | |
} | |
# clear saved values | |
# for v, p in initialDefaults.items(): | |
# removeExtensionDefault(v) | |
registerExtensionDefaults(initialDefaults) | |
# load saved values | |
xywh = getExtensionDefault(self.prefKey + '.win.posSize') | |
self.w.setPosSize(xywh) | |
def closeUI(self, sender): | |
xywh = list(self.w.getPosSize()) | |
setExtensionDefault(self.prefKey + '.win.posSize', xywh) | |
removeObserver(self, 'MetricsMachine.currentPairChanged') | |
def pairChanged(self, notification): | |
self.pairCurrent = notification['pair'] | |
if self.pairCurrent in self.pairList: | |
self.indexHistory = [self.closestIndex(self.pairCurrent, self.indexHistory[0]), self.indexHistory[0]] | |
self.w.left.set(self.pairCurrent[0]) | |
self.w.right.set(self.pairCurrent[1]) | |
def deleteCurrentPair(self, sender): | |
index = self.closestIndex(self.pairCurrent, self.indexHistory[0]) | |
# print('deleteCurrentPair', self.pairCurrent, index, self.indexHistory) | |
if index != None: | |
self.pairList.pop(index) | |
self.pairCurrent = cachepair = self.pairList[index] | |
self.updatePairlist() | |
self.UIpairList.setSelection(cachepair) | |
def insertNewPair(self, sender): | |
newPair = (self.w.left.get(), self.w.right.get()) | |
index = self.closestIndex(self.pairCurrent, self.indexHistory[0]) | |
if index != None: | |
self.pairList.insert(index+1, newPair); | |
self.pairCurrent = newPair | |
self.updatePairlist() | |
self.UIpairList.setSelection(newPair) | |
def movePairDown(self, sender): | |
index = self.closestIndex(self.pairCurrent, self.indexHistory[0]) | |
if index != None: | |
self.pairList.insert(index + 1, self.pairList.pop(index)) | |
self.updatePairlist() | |
def movePairUp(self, sender): | |
index = self.closestIndex(self.pairCurrent, self.indexHistory[0]) | |
if index != None: | |
self.pairList.insert(index - 1, self.pairList.pop(index)) | |
self.updatePairlist() | |
def flipNewPair(self, sender): | |
flippedPair = [self.w.right.get(), self.w.left.get()] | |
self.w.left.set(flippedPair[0]) | |
self.w.right.set(flippedPair[1]) | |
mm.SetCurrentPair(flippedPair, font=self.f) | |
def mirrorPair(self, sender): | |
flippedPair = [self.w.left.get(), self.w.right.get()] | |
for openclosepair in openclosepairs.items(): | |
if flippedPair[0] == asGlyphName(openclosepair[0]): | |
flippedPair[0] = asGlyphName(openclosepair[1]) | |
elif flippedPair[0] == asGlyphName(openclosepair[1]): | |
flippedPair[0] = asGlyphName(openclosepair[0]) | |
if flippedPair[1] == asGlyphName(openclosepair[0]): | |
flippedPair[1] = asGlyphName(openclosepair[1]) | |
elif flippedPair[1] == asGlyphName(openclosepair[1]): | |
flippedPair[1] = asGlyphName(openclosepair[0]) | |
self.w.left.set(flippedPair[1]) | |
self.w.right.set(flippedPair[0]) | |
mm.SetCurrentPair(flippedPair, font=self.f) | |
def updatePairlist(self): | |
mm.SetPairList(self.pairList, self.f) | |
mm.SetCurrentPair(self.pairCurrent, font=self.f) | |
self.w.left.set(self.pairCurrent[0]) | |
self.w.right.set(self.pairCurrent[1]) | |
self.mmWindow.makeKeyAndOrderFront_(True) | |
def save(self, sender): | |
fname = 'kern-' + self.f.info.familyName + '-' + self.f.info.styleName | |
fname.replace(' ', '') | |
putFile(messageText="Save Pairlist:", | |
fileName=fname, | |
canCreateDirectories=True, | |
fileTypes=['txt'], | |
parentWindow=self.w, | |
resultCallback=self.finalizeSave | |
) | |
def closestIndex(self, pair, near): | |
indexes = [index for index, value in enumerate(self.pairList) if value == self.pairCurrent] | |
# print('indexes', indexes, pair, near, self.indexHistory) | |
if indexes != [] and near != None: | |
closestindex = indexes[min(range(len(indexes)), key = lambda i: abs(indexes[i]-near))] | |
return closestindex | |
else: | |
return None | |
def finalizeSave(self, path=None): | |
with open(path, 'w') as newfile: | |
newfile.write('#KPL:P: My Pair List' + '\n') | |
for keyglyphpair in self.pairList: | |
line = str(keyglyphpair[0]) + ' ' +str(keyglyphpair[1]) + '\n' | |
newfile.write(line) | |
def flatText(self, this): | |
ns = this.getNSTextField() | |
ns.setBezeled_(False) | |
ns.setBackgroundColor_(NSColor.clearColor()) | |
ns.setFocusRingType_(1) | |
def flatButton(self, this): | |
ns = this.getNSButton() | |
ns.setBordered_(False) | |
def asGlyphName(character): | |
if len(character) > 1: | |
return character | |
else: | |
return GlyphName(ord(character)).getName() | |
openclosepairs = { | |
# initial/final punctuation (from https://www.compart.com/en/unicode/category/Pi and https://www.compart.com/en/unicode/category/Pf) | |
"‚": "‘", | |
"„": "“", | |
"„": "”", | |
"‘": "’", | |
"‛": "’", | |
"“": "”", | |
"‟": "”", | |
"‹": "›", | |
"›": "‹", | |
"«": "»", | |
"»": "«", | |
"⸂": "⸃", | |
"⸄": "⸅", | |
"⸉": "⸊", | |
"⸌": "⸍", | |
"⸜": "⸝", | |
"⸠": "⸡", | |
"”": "”", | |
"’": "’", | |
# Miscellaneous but common open/close pairs | |
"'": "'", | |
'"': '"', | |
# "¡": "!", | |
# "¿": "?", | |
"←": "→", | |
"→": "←", | |
# opening/closing punctuation (from https://www.compart.com/en/unicode/category/Ps & https://www.compart.com/en/unicode/category/Pe) | |
"(": ")", | |
"[": "]", | |
"{": "}", | |
"parenleft.sups": "parenright.sups", | |
"parenleft.subs": "parenright.subs", | |
"parenleft.uc": "parenright.uc", | |
"bracketleft.uc": "bracketright.uc", | |
"braceleft.uc": "braceright.uc", | |
"bracketangleleft.uc": "bracketangleright.uc", | |
"<": ">", | |
">": "<", | |
"༺": "༻", "༼": "༽", "᚛": "᚜", "‚": "‘", "„": "“", "⁅": "⁆", "⁽": "⁾", "₍": "₎", "⌈": "⌉", "⌊": "⌋", "〈": "〉", "❨": "❩", "❪": "❫", "❬": "❭", "❮": "❯", "❰": "❱", "❲": "❳", "❴": "❵", "⟅": "⟆", "⟦": "⟧", "⟨": "⟩", "⟪": "⟫", "⟬": "⟭", "⟮": "⟯", "⦃": "⦄", "⦅": "⦆", "⦇": "⦈", "⦉": "⦊", "⦋": "⦌", "⦍": "⦎", "⦏": "⦐", "⦑": "⦒", "⦓": "⦔", "⦕": "⦖", "⦗": "⦘", "⧘": "⧙", "⧚": "⧛", "⧼": "⧽", "⸢": "⸣", "⸤": "⸥", "⸦": "⸧", "⸨": "⸩", "〈": "〉", "《": "》", "「": "」", "『": "』", "【": "】", "〔": "〕", "〖": "〗", "〘": "〙", "〚": "〛", "〝": "〞", "⹂": "〟", "﴿": "﴾", "︗": "︘", "︵": "︶", "︷": "︸", "︹": "︺", "︻": "︼", "︽": "︾", "︿": "﹀", "﹁": "﹂", "﹃": "﹄", "﹇": "﹈", "﹙": "﹚", "﹛": "﹜", "﹝": "﹞", "(": ")", "[": "]", "{": "}", "⦅": "⦆", "「": "」", | |
} | |
mmPairlistTuner() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment