class ToucheTool(): def __init__(self): NSUserDefaults.standardUserDefaults().registerDefaults_( {"ToucheWindowHeight": 340}) self.windowHeight = NSUserDefaults.standardUserDefaults( ).integerForKey_("ToucheWindowHeight") self.minWindowHeight = 340 if self.windowHeight < self.minWindowHeight: self.windowHeight = self.minWindowHeight self.closedWindowHeight = 100 self.w = FloatingWindow((180, self.windowHeight), 'Touché!', minSize=(180, 340), maxSize=(250, 898)) self.w.bind("resize", self.windowResized_) self.isResizing = False p = 10 w = 160 # options self.w.options = Group((0, 0, 180, 220)) buttons = { "checkSelBtn": { "text": "Check selected glyphs", "callback": self.checkSel_, "y": p }, } for button, data in buttons.iteritems(): setattr( self.w.options, button, Button((p, data["y"], w - 22, 22), data["text"], callback=data["callback"], sizeStyle="small")) self.w.options.zeroCheck = CheckBox((p, 35, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small") self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16), sizeStyle="small") # results self.w.results = Group((0, 220, 180, -0)) self.w.results.show(False) textBoxes = {"stats": -34, "result": -18} for box, y in textBoxes.iteritems(): setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small")) # list and preview self.w.outputList = List((0, 58, -0, -40), [{ "left glyph": "", "right glyph": "" }], columnDescriptions=[{ "title": "left glyph", "width": 90 }, { "title": "right glyph" }], showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair_) self.w.outputList._setColumnAutoresizing() self._resizeWindow(False) self.w.open() # callbacks def checkAll_(self, sender=None): self.check_(useSelection=False) def checkSel_(self, sender=None): self.check_(useSelection=True) def check_(self, useSelection): self._resizeWindow(enlarge=False) self.checkFont(useSelection=useSelection, excludeZeroWidth=self.w.options.zeroCheck.get()) def showPair_(self, sender=None): try: index = sender.getSelection()[0] glyphs = [self.f[gName] for gName in self.touchingPairs[index]] ActiveFont = self.f._font EditViewController = ActiveFont.currentTab if EditViewController is None: tabText = "/%s/%s" % (glyphs[0].name, glyphs[1].name) ActiveFont.newTab(tabText) else: textStorage = EditViewController.graphicView() if not hasattr(textStorage, "replaceCharactersInRange_withString_" ): # compatibility with API change in 2.5 textStorage = EditViewController.graphicView().textStorage( ) LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object) RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object) if LeftChar != 0 and RightChar != 0: selection = textStorage.selectedRange() if selection.length < 2: selection.length = 2 if selection.location > 0: selection.location -= 1 NewString = "" if LeftChar < 0xffff and RightChar < 0xffff: NewString = "%s%s" % (unichr(LeftChar), unichr(RightChar)) else: print("Upper plane codes are not supported yet") textStorage.replaceCharactersInRange_withString_( selection, NewString) selection.length = 0 selection.location += 1 textStorage.setSelectedRange_(selection) #self.w.preview.set(glyphs) except IndexError: pass # checking @objc.python_method def _hasSufficientWidth(self, g): # to ignore combining accents and the like if self.excludeZeroWidth: # also skips 1-unit wide glyphs which many use instead of 0 if g.width < 2 or g._object.subCategory == "Nonspacing": return False return True @objc.python_method def _trimGlyphList(self, glyphList): newGlyphList = [] for g in glyphList: if g.box is not None and self._hasSufficientWidth(g): newGlyphList.append(g) return newGlyphList def windowResized_(self, window): posSize = self.w.getPosSize() Height = posSize[3] if Height > self.closedWindowHeight and self.isResizing is False: print("set new Height", Height) NSUserDefaults.standardUserDefaults().setInteger_forKey_( Height, "ToucheWindowHeight") self.windowHeight = Height @objc.python_method def _resizeWindow(self, enlarge=True): posSize = self.w.getPosSize() if enlarge: self.w.results.show(True) self.w.outputList.show(True) targetHeight = self.windowHeight if targetHeight < 340: targetHeight = 340 else: self.w.results.show(False) self.w.outputList.show(False) targetHeight = self.closedWindowHeight self.isResizing = True self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight)) self.isResizing = False # ok let's do this @objc.python_method def checkFont(self, useSelection=False, excludeZeroWidth=True): f = CurrentFont() if f is not None: # initialize things self.w.options.progress.start() time0 = time.time() self.excludeZeroWidth = excludeZeroWidth self.f = f glyphNames = f.selection if useSelection else f.keys() glyphList = [f[x] for x in glyphNames] glyphList = self._trimGlyphList(glyphList) self.touchingPairs = Touche(f).findTouchingPairs(glyphList) # display output self.w.results.stats.set("%d glyphs checked" % len(glyphList)) self.w.results.result.set("%d touching pairs found" % len(self.touchingPairs)) self.w.results.show(True) outputList = [{ "left glyph": g1, "right glyph": g2 } for (g1, g2) in self.touchingPairs] self.w.outputList.set(outputList) if len(self.touchingPairs) > 0: self.w.outputList.setSelection([0]) #self.w.preview.setFont(f) self.w.options.progress.stop() self._resizeWindow(enlarge=True) time1 = time.time() print('Touché: finished checking %d glyphs in %.2f seconds' % (len(glyphList), time1 - time0)) else: Message('Touché: Can’t find a font to check')
class Script(object): def __init__(self): self.valuesPrefsKey = prefsKey + ".delta." + basename( Glyphs.font.filepath) # UI metrics spacing = 8 height = 22 # calculate window height minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing))) # create UI window self.w = FloatingWindow(minWinSize, "Adjust sidebearings", minSize=minWinSize, maxSize=(500, 500), autosaveName=prefsKey + ".win") # layout UI controls y = 16 self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:") y += height + spacing inputWidth = 64 for master in Glyphs.font.masters: setattr(self.w, "deltaLabel%s" % master.id, TextBox((16, y, -16 - inputWidth, height), master.name)) setattr(self.w, "deltaInput%s" % master.id, EditText((-16 - inputWidth, y, -16, height), "16")) # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id)) y += height + spacing self.w.submitButton = Button((16, -16 - height, -16, height), "Adjust all sidebearings", callback=self.onSubmit) # finalize UI self.w.setDefaultButton(self.w.submitButton) self.loadPreferences() self.w.bind("close", self.savePreferences) # make sure window is large enough to show all masters x, y, w, h = self.w.getPosSize() if w < minWinSize[0] and h < minWinSize[1]: self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False) elif w < minWinSize[0]: self.w.setPosSize((x, y, minWinSize[0], h), animate=False) elif h < minWinSize[1]: self.w.setPosSize((x, y, w, minWinSize[1]), animate=False) self.w.open() self.w.makeKey() def getDeltaInputForMaster(self, masterId): return getattr(self.w, "deltaInput%s" % masterId) def loadPreferences(self): try: Glyphs.registerDefault(self.valuesPrefsKey, []) for t in Glyphs.defaults[self.valuesPrefsKey]: try: masterId, value = t self.getDeltaInputForMaster(masterId).set(value) except: pass except: print("failed to load preferences") def savePreferences(self, sender): try: values = [] for master in Glyphs.font.masters: values.append( (master.id, self.getDeltaInputForMaster(master.id).get())) Glyphs.defaults[self.valuesPrefsKey] = values except: print("failed to save preferences") def onSubmit(self, sender): try: sender.enable(False) if performFontChanges(self.action1): self.w.close() except Exception, e: Glyphs.showMacroWindow() print("error: %s" % e) finally:
class ToucheTool(): def __init__(self): NSUserDefaults.standardUserDefaults().registerDefaults_({"ToucheWindowHeight":340}) self.windowHeight = NSUserDefaults.standardUserDefaults().integerForKey_("ToucheWindowHeight") self.minWindowHeight = 340 if self.windowHeight < self.minWindowHeight: self.windowHeight = self.minWindowHeight self.closedWindowHeight = 100 self.w = FloatingWindow((180, self.windowHeight), u'Touché!', minSize=(180,340), maxSize=(250,898)) self.w.bind("resize", self.windowResized) self.isResizing = False p = 10 w = 160 # options self.w.options = Group((0, 0, 180, 220)) buttons = { "checkSelBtn": {"text": "Check selected glyphs", "callback": self.checkSel, "y": p}, } for button, data in buttons.iteritems(): setattr(self.w.options, button, Button((p, data["y"], w - 22, 22), data["text"], callback=data["callback"], sizeStyle="small")) self.w.options.zeroCheck = CheckBox((p, 35, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small") self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16), sizeStyle="small") # results self.w.results = Group((0, 220, 180, -0)) self.w.results.show(False) textBoxes = {"stats": -34, "result": -18} for box, y in textBoxes.iteritems(): setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small")) # list and preview self.w.outputList = List((0,58,-0,-40), [{"left glyph": "", "right glyph": ""}], columnDescriptions=[{"title": "left glyph", "width": 90}, {"title": "right glyph"}], showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair) self.w.outputList._setColumnAutoresizing() self._resizeWindow(False) self.w.open() # callbacks def checkAll(self, sender=None): self.check(useSelection=False) def checkSel(self, sender=None): self.check(useSelection=True) def check(self, useSelection): self._resizeWindow(enlarge=False) self.checkFont(useSelection=useSelection, excludeZeroWidth=self.w.options.zeroCheck.get()) def showPair(self, sender=None): try: index = sender.getSelection()[0] glyphs = [self.f[gName] for gName in self.touchingPairs[index]] ActiveFont = self.f._font EditViewController = ActiveFont.currentTab if EditViewController is None: tabText = "/%s/%s" %(glyphs[0].name, glyphs[1].name) ActiveFont.newTab(tabText) else: textStorage = EditViewController.graphicView() if not hasattr(textStorage, "replaceCharactersInRange_withString_"): # compatibility with API change in 2.5 textStorage = EditViewController.graphicView().textStorage() LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object) RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object) if LeftChar != 0 and RightChar != 0: selection = textStorage.selectedRange() if selection.length < 2: selection.length = 2 if selection.location > 0: selection.location -= 1 NewString = "" if LeftChar < 0xffff and RightChar < 0xffff: NewString = u"%s%s" % (unichr(LeftChar), unichr(RightChar)) else: print "Upper plane codes are not supported yet" textStorage.replaceCharactersInRange_withString_(selection, NewString) selection.length = 0 selection.location += 1 textStorage.setSelectedRange_(selection) #self.w.preview.set(glyphs) except IndexError: pass # checking def _hasSufficientWidth(self, g): # to ignore combining accents and the like if self.excludeZeroWidth: # also skips 1-unit wide glyphs which many use instead of 0 if g.width < 2 or g._object.subCategory == "Nonspacing": return False return True def _trimGlyphList(self, glyphList): newGlyphList = [] for g in glyphList: if g.box is not None and self._hasSufficientWidth(g): newGlyphList.append(g) return newGlyphList def windowResized(self, window): posSize = self.w.getPosSize() Height = posSize[3] if Height > self.closedWindowHeight and self.isResizing is False: print "set new Height", Height NSUserDefaults.standardUserDefaults().setInteger_forKey_(Height, "ToucheWindowHeight") self.windowHeight = Height def _resizeWindow(self, enlarge=True): posSize = self.w.getPosSize() if enlarge: self.w.results.show(True) self.w.outputList.show(True) targetHeight = self.windowHeight if targetHeight < 340: targetHeight = 340 else: self.w.results.show(False) self.w.outputList.show(False) targetHeight = self.closedWindowHeight self.isResizing = True self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight)) self.isResizing = False # ok let's do this def checkFont(self, useSelection=False, excludeZeroWidth=True): f = CurrentFont() if f is not None: # initialize things self.w.options.progress.start() time0 = time.time() self.excludeZeroWidth = excludeZeroWidth self.f = f glyphNames = f.selection if useSelection else f.keys() glyphList = [f[x] for x in glyphNames] glyphList = self._trimGlyphList(glyphList) self.touchingPairs = Touche(f).findTouchingPairs(glyphList) # display output self.w.results.stats.set("%d glyphs checked" % len(glyphList)) self.w.results.result.set("%d touching pairs found" % len(self.touchingPairs)) self.w.results.show(True) outputList = [{"left glyph": g1, "right glyph": g2} for (g1, g2) in self.touchingPairs] self.w.outputList.set(outputList) if len(self.touchingPairs) > 0: self.w.outputList.setSelection([0]) #self.w.preview.setFont(f) self.w.options.progress.stop() self._resizeWindow(enlarge=True) time1 = time.time() print u'Touché: finished checking %d glyphs in %.2f seconds' % (len(glyphList), time1-time0) else: Message(u'Touché: Can’t find a font to check')
class Script( object ): def __init__(self): self.valuesPrefsKey = prefsKey + ".delta." + basename(Glyphs.font.filepath) # UI metrics spacing = 8 height = 22 # calculate window height minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing))) # create UI window self.w = FloatingWindow( minWinSize, "Adjust sidebearings", minSize=minWinSize, maxSize=(500, 500), autosaveName=prefsKey + ".win") # layout UI controls y = 16 self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:") y += height + spacing inputWidth = 64 for master in Glyphs.font.masters: setattr(self.w, "deltaLabel%s" % master.id, TextBox((16, y, -16-inputWidth, height), master.name)) setattr(self.w, "deltaInput%s" % master.id, EditText((-16-inputWidth, y, -16, height), "16")) # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id)) y += height + spacing self.w.submitButton = Button( (16, -16-height, -16, height), "Adjust all sidebearings", callback=self.onSubmit) # finalize UI self.w.setDefaultButton(self.w.submitButton) self.loadPreferences() self.w.bind("close", self.savePreferences) # make sure window is large enough to show all masters x, y, w, h = self.w.getPosSize() if w < minWinSize[0] and h < minWinSize[1]: self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False) elif w < minWinSize[0]: self.w.setPosSize((x, y, minWinSize[0], h), animate=False) elif h < minWinSize[1]: self.w.setPosSize((x, y, w, minWinSize[1]), animate=False) self.w.open() self.w.makeKey() def getDeltaInputForMaster(self, masterId): return getattr(self.w, "deltaInput%s" % masterId) def loadPreferences(self): try: Glyphs.registerDefault(self.valuesPrefsKey, []) for t in Glyphs.defaults[self.valuesPrefsKey]: try: masterId, value = t self.getDeltaInputForMaster(masterId).set(value) except: pass except: print("failed to load preferences") def savePreferences(self, sender): try: values = [] for master in Glyphs.font.masters: values.append((master.id, self.getDeltaInputForMaster(master.id).get())) Glyphs.defaults[self.valuesPrefsKey] = values except: print("failed to save preferences") def onSubmit(self, sender): try: sender.enable(False) if performFontChanges(self.action1): self.w.close() except Exception(e): Glyphs.showMacroWindow() print("error: %s" % e) finally: sender.enable(True) def action1(self, font): print(font) deltas = {} # keyed on master.id for master in Glyphs.font.masters: deltas[master.id] = int(self.getDeltaInputForMaster(master.id).get()) syncLayers = set() # layers that need syncMetrics() to be called # [debug] alterntive loop over a few select glyphs, for debugging. # debugGlyphNames = set([ # ".null", # "A", "Adieresis", "Lambda", # "Bhook", # "C", "Chook", # "endash", # "space", # ]) # for g in [g for g in font.glyphs if g.name in debugGlyphNames]: for g in font.glyphs: # print(">> %s (%s)" % (g.name, g.productionName)) if g.name in excludedGlyphNames: # glyph is exlcuded print("ignoring excluded glyph %r" % g.name) continue g.beginUndo() try: for master in font.masters: layer = g.layers[master.id] delta = deltas[master.id] if delta == 0: # no adjustment continue if layer.width == 0: # ignore glyphs with zero-width layers print("ignoring zero-width glyph", g.name) break if layer.isAligned: # ignore glyphs which are auto-aligned from components print("ignoring auto-aligned glyph", g.name) break if len(layer.paths) == 0 and len(layer.components) == 0: # adjust width instead of LSB & RSB of empty glyphs if layer.widthMetricsKey is None: layer.width = max(0, layer.width + (delta * 2)) else: syncLayers.add(layer) continue # offset components by delta to counter-act effect of also applying delta # to the component glyphs. if layer.components is not None: for cn in layer.components: m = cn.transform # auto-aligned or offset x or offset y or contains shapes if not cn.automaticAlignment or m[4] != 0 or m[5] != 0 or len(layer.paths) > 0: cn.transform = ( m[0], # x scale factor m[1], # x skew factor m[2], # y skew factor m[3], # y scale factor m[4] - delta, # x position (+ since transform is inverse) m[5] # y position ) # Note: # layer metrics keys are expressions, e.g. "==H+10" # glyph metrics keys are other glyphs, e.g. U+0041 if g.leftMetricsKey is None and layer.leftMetricsKey is None: layer.LSB = layer.LSB + delta else: syncLayers.add(layer) if g.rightMetricsKey is None and layer.rightMetricsKey is None: layer.RSB = layer.RSB + delta else: syncLayers.add(layer) finally: g.endUndo() # end for g in font # sync layers that use metricsKey if len(syncLayers) > 0: print("Syncing LSB & RSB for %r layers..." % len(syncLayers)) for layer in syncLayers: layer.syncMetrics() print("Done")
class TestMetrics(BaseWindowController): testOptions = [ 'Complete report', 'Vertical alignments', 'Accented letters', 'Interpunction', 'Figures', 'Dnom Inf Num Sup', 'Fractions', 'Flipped Margins', 'LC ligatures', 'UC ligatures', 'LC extra', 'UC extra' ] testOptionsAbbr = [ 'completeReport', 'verticalAlignments', 'accented', 'interpunction', 'figures', 'dnomInfNumSup', 'fractions', 'flippedMargins', 'LCliga', 'UCliga', 'LCextra', 'UCextra' ] testFunctions = { 'Complete report': [ checkVerticalExtremes, checkAccented, checkInterpunction, checkFigures, checkDnomNumrSubsSups, checkFractions, checkLCligatures, checkUCligatures, checkLCextra, checkUCextra, checkFlippedMargins ], 'Vertical alignments': [checkVerticalExtremes], 'Accented letters': [checkAccented], 'Interpunction': [checkInterpunction], 'Figures': [checkFigures], 'Dnom Inf Num Sup': [checkDnomNumrSubsSups], 'Fractions': [checkFractions], 'Flipped Margins': [checkFlippedMargins], 'LC ligatures': [checkLCligatures], 'UC ligatures': [checkUCligatures], 'LC extra': [checkLCextra], 'UC extra': [checkUCextra] } chosenTest = testOptions[0] showMissingGlyph = False fontOptions = None chosenFont = None def __init__(self): super(TestMetrics, self).__init__() # load data self.fontOptions = AllFonts() if self.fontOptions: self.chosenFont = self.fontOptions[0] # init win self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT), "Metrics Tester") # target pop up jumpingY = MARGIN_TOP self.w.targetPopUp = PopUpButton( (MARGIN_LFT, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), [ '{} {}'.format(font.info.familyName, font.info.styleName) for font in self.fontOptions ], callback=self.targetPopUpCallback) # test choice jumpingY += MARGIN_HALFROW + vanillaControlsSize[ 'PopUpButtonRegularHeight'] self.w.testChoice = PopUpButton( (MARGIN_LFT, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), self.testOptions, callback=self.testChoiceCallback) # separation line jumpingY += vanillaControlsSize[ 'PopUpButtonRegularHeight'] + MARGIN_HALFROW # self.w.separationLine = HorizontalLine((MARGIN_LFT, jumpingY, NET_WIDTH, 1)) # jumpingY += RADIO_GROUP_HEIGHT + MARGIN_ROW self.w.missingGlyphCheck = CheckBox( (MARGIN_LFT, jumpingY, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight']), 'Show missing glyphs', callback=self.missingGlyphCheckCallback) jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW self.w.runButton = Button((MARGIN_LFT, jumpingY, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight']), "Run Tests", callback=self.runButtonCallback) # adjust height jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_BTM self.w.setPosSize((0, 0, PLUGIN_WIDTH, jumpingY)) # observers addObserver(self, "updateFontList", "fontWillClose") addObserver(self, "updateFontList", "fontDidOpen") addObserver(self, "updateFontList", "newFontDidOpen") self.setUpBaseWindowBehavior() # open win self.w.open() def windowCloseCallback(self, sender): removeObserver(self, "fontWillClose") removeObserver(self, "fontDidOpen") removeObserver(self, "newFontDidOpen") super(TestMetrics, self).windowCloseCallback(sender) def updateFontList(self, sender): self.fontOptions = AllFonts() self.w.targetPopUp.setItems([ '{} {}'.format(font.info.familyName, font.info.styleName) for font in self.fontOptions ]) if self.fontOptions: self.chosenFont = self.fontOptions[0] else: self.chosenFont = None def targetPopUpCallback(self, sender): self.chosenFont = self.fontOptions[sender.get()] assert isinstance(self.chosenFont, RFont), '{0!r} is not a RFont instance'.format( self.chosenFont) def testChoiceCallback(self, sender): self.chosenTest = self.testOptions[sender.get()] def missingGlyphCheckCallback(self, sender): self.showMissingGlyph = bool(sender.get()) def runButtonCallback(self, sender): testsToRun = self.testFunctions[self.chosenTest] rightNow = datetime.now() fileNameProposal = '{}{:0>2d}{:0>2d}_{}_{}.txt'.format( rightNow.year, rightNow.month, rightNow.day, os.path.basename(self.chosenFont.path)[:-4], self.testOptionsAbbr[self.testOptions.index(self.chosenTest)]) progressWindow = self.startProgress('Running Tests') with open(PutFile('Choose where to save the report', fileNameProposal), 'w') as reportFile: for eachFunc in testsToRun: errorLines, missingGlyphs = eachFunc(self.chosenFont) report = convertLinesToString(errorLines, missingGlyphs, self.showMissingGlyph) reportFile.write(report) progressWindow.close()
class CopyAndMove(object): isOffsetAllowed = False verticalOffset = 0 def __init__(self): self.transferList = NAME_2_GLYPHLIST[GLYPHLISTS_OPTIONS[0]] self.target = TARGET_OPTIONS[0] self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, 400), PLUGIN_TITLE) jumpingY = MARGIN_VER # target fonts self.w.targetFontsPopUp = PopUpButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), TARGET_OPTIONS, callback=self.targetFontsPopUpCallback) jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER # transfer lists pop up self.w.glyphListPopUp = PopUpButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), GLYPHLISTS_OPTIONS, callback=self.glyphListPopUpCallback) jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER # offset caption self.w.offsetCaption = TextBox( (MARGIN_HOR, jumpingY + 2, NET_WIDTH * .22, vanillaControlsSize['TextBoxRegularHeight']), 'Offset:') # offset edit text self.w.offsetEdit = EditText( (MARGIN_HOR + NET_WIDTH * .25, jumpingY, NET_WIDTH * .35, vanillaControlsSize['EditTextRegularHeight']), continuous=False, callback=self.offsetEditCallback) self.w.offsetEdit.set('%d' % self.verticalOffset) jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER # clean button self.w.cleanButton = Button( (MARGIN_HOR, jumpingY, NET_WIDTH * .45, vanillaControlsSize['ButtonRegularHeight']), 'Clean', callback=self.cleanButtonCallback) # run button self.w.runButton = Button( (MARGIN_HOR + NET_WIDTH * .55, jumpingY, NET_WIDTH * .45, vanillaControlsSize['ButtonRegularHeight']), 'Run!', callback=self.runButtonCallback) jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_VER self.w.setPosSize((0, 0, PLUGIN_WIDTH, jumpingY)) self.w.open() def targetFontsPopUpCallback(self, sender): self.target = TARGET_OPTIONS[sender.get()] def glyphListPopUpCallback(self, sender): if GLYPHLISTS_OPTIONS[sender.get()] in ACTIVE_OFFSET: self.w.offsetEdit.enable(True) else: self.w.offsetEdit.enable(False) self.transferList = NAME_2_GLYPHLIST[GLYPHLISTS_OPTIONS[sender.get()]] self.isOffsetAllowed = False def offsetEditCallback(self, sender): try: self.verticalOffset = int(sender.get()) except ValueError: self.w.offsetEdit.set('%d' % self.verticalOffset) def cleanButtonCallback(self, sender): if self.target == 'All Fonts': fontsToProcess = AllFonts() else: fontsToProcess = [CurrentFont()] for eachFont in fontsToProcess: for eachTargetName, _ in self.transferList: if eachTargetName in eachFont: eachFont[eachTargetName].clear() def runButtonCallback(self, sender): if self.target == 'All Fonts': fontsToProcess = AllFonts() else: fontsToProcess = [CurrentFont()] for eachFont in fontsToProcess: for eachRow in self.transferList: if len(eachRow) == 2: eachTarget, eachSource = eachRow else: eachTarget, eachSource = eachRow[0], eachRow[1:] if eachTarget not in eachFont: targetGlyph = eachFont.newGlyph(eachTarget) else: targetGlyph = eachFont[eachTarget] targetGlyph.clear() if isinstance(eachSource, types.ListType) is False: targetGlyph.width = eachFont[eachSource].width if eachFont.info.italicAngle: anchorAngle = radians(-eachFont.info.italicAngle) else: anchorAngle = radians(0) tangentOffset = tan(anchorAngle) * self.verticalOffset targetGlyph.appendComponent( eachSource, (tangentOffset, self.verticalOffset)) else: offsetX = 0 for eachSourceGlyphName in eachSource: targetGlyph.appendComponent(eachSourceGlyphName, (offsetX, 0)) offsetX += eachFont[eachSourceGlyphName].width targetGlyph.width = offsetX
class GridFitter(object): bcpHandlesFit = False def __init__(self): self.fontTarget = FONT_TARGET_OPTIONS[0] self.glyphTarget = GLYPH_TARGET_OPTIONS[0] self.gridSize = 4 self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, 400), PLUGIN_TITLE) jumpingY = MARGIN_VER # font target self.w.fontTargetPopUp = PopUpButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), FONT_TARGET_OPTIONS, callback=self.fontTargetPopUpCallback) jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER # glyph target self.w.glyphTargetPopUp = PopUpButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), GLYPH_TARGET_OPTIONS, callback=self.glyphTargetPopUpCallback) jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER # grid size captions self.w.gridSizeCaption = TextBox( (MARGIN_HOR, jumpingY + 2, NET_WIDTH * .32, vanillaControlsSize['TextBoxRegularHeight']), 'Grid Size:') # grid size edit self.w.gridSizeEdit = EditText( (MARGIN_HOR + NET_WIDTH * .32, jumpingY, NET_WIDTH * .3, vanillaControlsSize['EditTextRegularHeight']), text='{:d}'.format(self.gridSize), callback=self.gridSizeEditCallback) jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER self.w.moveBcpHandlesCheck = CheckBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight']), 'move bcp handles', value=self.bcpHandlesFit, callback=self.moveBcpHandlesCheckCallback) jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_VER # fit button self.w.fitButton = Button((MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight']), 'Fit!', callback=self.fitButtonCallback) jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_VER self.w.setPosSize((0, 0, PLUGIN_WIDTH, jumpingY)) self.w.open() def fontTargetPopUpCallback(self, sender): self.fontTarget = FONT_TARGET_OPTIONS[sender.get()] def glyphTargetPopUpCallback(self, sender): self.glyphTarget = GLYPH_TARGET_OPTIONS[sender.get()] def gridSizeEditCallback(self, sender): try: self.gridSize = int(sender.get()) except ValueError: self.w.gridSizeEdit.set('{:d}'.format(self.gridSize)) def moveBcpHandlesCheckCallback(self, sender): self.bcpHandlesFit = bool(sender.get()) def fitButtonCallback(self, sender): if self.fontTarget == 'All Fonts': fontsToProcess = AllFonts() else: fontsToProcess = [CurrentFont()] for eachFont in fontsToProcess: if self.glyphTarget == 'All Glyphs': glyphNamesToProcess = eachFont.glyphOrder elif self.glyphTarget == 'Selection': glyphNamesToProcess = CurrentFont().selection else: glyphNamesToProcess = [CurrentGlyph().name] for eachName in glyphNamesToProcess: eachGlyph = eachFont[eachName] gridFit(eachGlyph, self.gridSize, bcpHandlesFit=self.bcpHandlesFit)
class BroadNib(object): preview = False drawValues = False elementShape = SHAPE_OPTIONS[0] pluginWidth = 120 pluginHeight = 300 marginTop = 10 marginRgt = 10 marginBtm = 10 marginLft = 10 marginRow = 8 netWidth = pluginWidth - marginLft - marginRgt def __init__(self): # init window self.win = FloatingWindow((self.pluginWidth, self.pluginHeight), "Broad Nib") # checkBox preview jumpingY = self.marginTop self.win.preview = CheckBox( (self.marginLft, jumpingY, self.netWidth, CheckBoxHeight), "Preview", value=self.preview, sizeStyle='small', callback=self.previewCallback) jumpingY += CheckBoxHeight self.win.shapeRadio = RadioGroup( (self.marginLft + 10, jumpingY, self.netWidth, 32), SHAPE_OPTIONS, sizeStyle='small', callback=self.shapeRadioCallback) self.win.shapeRadio.enable(False) self.win.shapeRadio.set(0) # checkBox draw values jumpingY += self.marginRow + self.win.shapeRadio.getPosSize()[3] - 3 self.win.drawValues = CheckBox( (self.marginLft, jumpingY, self.netWidth, CheckBoxHeight), "Draw Values", value=self.drawValues, sizeStyle='small', callback=self.drawValuesCallback) # oval width jumpingY += self.marginRow + CheckBoxHeight self.win.widthCaption = TextBox( (self.marginLft, jumpingY + 3, self.netWidth * .5, TextBoxHeight), "Width:", sizeStyle='small') self.win.widthEdit = EditText( (self.marginLft + self.netWidth * .5, jumpingY, self.netWidth * .4, EditTextHeight), sizeStyle='small', callback=self.widthEditCallback) # oval height jumpingY += self.marginRow + EditTextHeight self.win.heightCaption = TextBox( (self.marginLft, jumpingY + 3, self.netWidth * .5, TextBoxHeight), "Height:", sizeStyle='small') self.win.heightEdit = EditText( (self.marginLft + self.netWidth * .5, jumpingY, self.netWidth * .4, EditTextHeight), sizeStyle='small', callback=self.heightEditCallback) # oval angle jumpingY += self.marginRow + EditTextHeight self.win.angleCaption = TextBox( (self.marginLft, jumpingY + 3, self.netWidth * .5, TextBoxHeight), "Angle:", sizeStyle='small') self.win.angleEdit = EditText( (self.marginLft + self.netWidth * .5, jumpingY, self.netWidth * .4, EditTextHeight), sizeStyle='small', callback=self.angleEditCallback) # add jumpingY += self.marginRow + EditTextHeight self.win.addToLib = SquareButton( (self.marginLft, jumpingY, self.netWidth, SquareButtonHeight), "Add to lib", sizeStyle='small', callback=self.addToLibCallback) # clear jumpingY += self.marginRow + EditTextHeight self.win.clearLib = SquareButton( (self.marginLft, jumpingY, self.netWidth, SquareButtonHeight), "Clear lib", sizeStyle='small', callback=self.clearLibCallback) jumpingY += self.marginRow + EditTextHeight self.win.expandToForeground = SquareButton( (self.marginLft, jumpingY, self.netWidth, SquareButtonHeight), "Expand", sizeStyle='small', callback=self.expandToForegroundCallback) # managing observers addObserver(self, "_drawBackground", "drawBackground") addObserver(self, "_drawBackground", "drawInactive") addObserver(self, "_drawBlack", "drawPreview") self.win.bind("close", self.closing) # adjust window jumpingY += self.marginRow + EditTextHeight self.win.setPosSize((200, 200, self.pluginWidth, jumpingY)) # opening window self.win.open() def previewCallback(self, sender): self.preview = bool(sender.get()) self.win.shapeRadio.enable(self.preview) UpdateCurrentGlyphView() def shapeRadioCallback(self, sender): self.elementShape = SHAPE_OPTIONS[sender.get()] UpdateCurrentGlyphView() def drawValuesCallback(self, sender): self.drawValues = bool(sender.get()) UpdateCurrentGlyphView() def widthEditCallback(self, sender): try: self.nibWidth = int(sender.get()) except ValueError: print traceback.format_exc() self.nibWidth = None self.win.widthEdit.set('') def heightEditCallback(self, sender): try: self.nibHeight = int(sender.get()) except ValueError: print traceback.format_exc() self.nibHeight = None self.win.heightEdit.set('') def angleEditCallback(self, sender): try: self.nibAngle = int(sender.get()) except ValueError: print traceback.format_exc() self.nibAngle = None self.win.angleEdit.set('') def addToLibCallback(self, sender): assert self.nibWidth is not None, 'nibWidth is None' assert self.nibHeight is not None, 'nibHeight is None' assert self.nibAngle is not None, 'nibAngle is None' if PLUGIN_KEY not in CurrentGlyph().lib: CurrentGlyph().lib[PLUGIN_KEY] = {} selectedIDs = collectIDsFromSelectedPoints(CurrentGlyph()) for eachSelectedID in selectedIDs: CurrentGlyph().lib[PLUGIN_KEY][eachSelectedID] = { 'width': self.nibWidth, 'height': self.nibHeight, 'angle': self.nibAngle } if version[0] == '2': CurrentGlyph().changed() else: CurrentGlyph().update() UpdateCurrentGlyphView() def clearLibCallback(self, sender): if PLUGIN_KEY in CurrentGlyph().lib: del CurrentGlyph().lib[PLUGIN_KEY] if version[0] == '2': CurrentGlyph().changed() else: CurrentGlyph().update() UpdateCurrentGlyphView() def expandToForegroundCallback(self, sender): self._drawElements(CurrentGlyph(), None, 2, 'foreground') def loadDataFromLib(self, glyph, ID): nibData = glyph.lib[PLUGIN_KEY][ID] self.win.widthEdit.set('%s' % nibData['width']) self.win.heightEdit.set('%s' % nibData['height']) self.win.angleEdit.set('%s' % nibData['angle']) self.nibWidth = nibData['width'] self.nibHeight = nibData['height'] self.nibAngle = nibData['angle'] def _drawBackground(self, infoDict): assert self.elementShape in SHAPE_OPTIONS glyph = infoDict['glyph'] currentTool = getActiveEventTool() view = currentTool.getNSView() textAttributes = { NSFontAttributeName: NSFont.systemFontOfSize_(bodySizeCaption), NSForegroundColorAttributeName: NSColor.blackColor() } # load data if point is selected if PLUGIN_KEY in glyph.lib: for eachContour in glyph: for eachPt in eachContour.points: if eachPt.selected is True and eachPt.type != 'offCurve' and eachPt.naked( ).uniqueID in glyph.lib[PLUGIN_KEY]: self.loadDataFromLib(glyph, eachPt.naked().uniqueID) # draw interpolateValued ovals if self.preview is True and PLUGIN_KEY in glyph.lib: self._drawElements(glyph, SUB_COLOR, 4, 'canvas') # draw master ovals if self.preview is True and PLUGIN_KEY in glyph.lib: for eachContour in glyph: for eachSegment in eachContour: ID = eachSegment.onCurve.naked().uniqueID if ID in glyph.lib[PLUGIN_KEY]: elementDict = glyph.lib[PLUGIN_KEY][ID] save() fill(*MASTER_COLOR) translate(eachSegment.onCurve.x, eachSegment.onCurve.y) rotate(elementDict['angle']) if self.elementShape == 'Oval': oval(-elementDict['width'] / 2., -elementDict['height'] / 2., elementDict['width'], elementDict['height']) else: rect(-elementDict['width'] / 2., -elementDict['height'] / 2., elementDict['width'], elementDict['height']) restore() # draw values if self.drawValues is True and PLUGIN_KEY in glyph.lib: for eachContour in glyph: for eachSegment in eachContour: ID = eachSegment.onCurve.naked().uniqueID if ID in glyph.lib[PLUGIN_KEY]: nibData = glyph.lib[PLUGIN_KEY][ID] values = '%s: %s\n%s: %s\n%s: %s' % ( 'width', nibData['width'], 'height', nibData['height'], 'angle', nibData['angle']) view._drawTextAtPoint( values, textAttributes, (eachSegment.onCurve.x, eachSegment.onCurve.y), yOffset=2.8 * bodySizeCaption) def _drawBlack(self, infoDict): glyph = infoDict['glyph'] if self.preview is True and PLUGIN_KEY in glyph.lib: # background fill(*LIGHT_YELLOW) rect(-1000, -1000, 2000, 2000) # calligraphy self._drawElements(glyph, BLACK_COLOR, 4, 'canvas') def _drawElements(self, glyph, color, distance, mode): assert mode == 'canvas' or mode == 'foreground' assert self.elementShape in SHAPE_OPTIONS if mode == 'foreground': phantomGlyph = RGlyph() for eachContour in glyph: for indexSegment, eachSegment in enumerate(eachContour): if indexSegment != len(eachContour) - 1: nextSegment = eachContour[indexSegment + 1] else: if eachContour.open is True: continue else: nextSegment = eachContour[0] pt1 = eachSegment.onCurve.x, eachSegment.onCurve.y pt4 = nextSegment.onCurve.x, nextSegment.onCurve.y if nextSegment.offCurve: pt2 = nextSegment.offCurve[0].x, nextSegment.offCurve[0].y pt3 = nextSegment.offCurve[1].x, nextSegment.offCurve[1].y else: pt2 = pt1 pt3 = pt4 pt1 = eachSegment.onCurve.x, eachSegment.onCurve.y pt4 = nextSegment.onCurve.x, nextSegment.onCurve.y if eachSegment.onCurve.naked().uniqueID in glyph.lib[PLUGIN_KEY] and \ nextSegment.onCurve.naked().uniqueID in glyph.lib[PLUGIN_KEY]: startLib = glyph.lib[PLUGIN_KEY][ eachSegment.onCurve.naked().uniqueID] endLib = glyph.lib[PLUGIN_KEY][ nextSegment.onCurve.naked().uniqueID] bezPoints = collectsPointsOnBezierCurveWithFixedDistance( pt1, pt2, pt3, pt4, distance) for indexBezPt, eachBezPt in enumerate(bezPoints): factor = indexBezPt / float(len(bezPoints)) width = lerp(startLib['width'], endLib['width'], factor) height = lerp(startLib['height'], endLib['height'], factor) angle = lerp(startLib['angle'], endLib['angle'], factor) if mode == 'canvas': save() fill(*color) translate(eachBezPt[0][0], eachBezPt[0][1]) rotate(angle) if self.elementShape == 'Oval': oval(-width / 2., -height / 2., width, height) else: rect(-width / 2., -height / 2., width, height) restore() else: matrix = Identity matrix = matrix.translate(eachBezPt[0][0], eachBezPt[0][1]) matrix = matrix.rotate(math.radians(angle)) if self.elementShape == 'Oval': robofabOval(phantomGlyph, 0, 0, width, height) else: robofabRect(phantomGlyph, 0, 0, width, height) phantomGlyph[len(phantomGlyph) - 1].transform(matrix) if mode == 'foreground': phantomGlyph.removeOverlap() flattenGlyph(phantomGlyph, 20) thresholdGlyph(phantomGlyph, 5) if version[0] == '2': glyph.getLayer('public.default', clear=True).appendGlyph(phantomGlyph, (0, 0)) else: glyph.getLayer('foreground', clear=True).appendGlyph(phantomGlyph, (0, 0)) def closing(self, sender): removeObserver(self, "drawBackground") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview")