class ToucheTool: def __init__(self): self.w = Window((180, 340), u"Touché!", minSize=(180, 340), maxSize=(1000, 898)) p = 10 w = 160 # options self.w.options = Group((0, 0, 180, 220)) buttons = { "checkSelBtn": {"text": "Check selected glyphs\nfor touching pairs", "callback": self.checkSel, "y": p}, "checkAllBtn": { "text": "Check entire font\n(can take several minutes!)", "callback": self.checkAll, "y": 60, }, } for button, data in buttons.iteritems(): setattr( self.w.options, button, SquareButton((p, data["y"], w, 40), data["text"], callback=data["callback"], sizeStyle="small"), ) self.w.options.zeroCheck = CheckBox((p, 108, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small") self.w.options.progress = ProgressSpinner((82, 174, 16, 16), sizeStyle="small") # results self.w.results = Group((0, 220, 180, -0)) self.w.results.show(False) textBoxes = {"stats": 24, "result": 42} for box, y in textBoxes.iteritems(): setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small")) moreButtons = { "spaceView": {"text": "View all in Space Center", "callback": self.showAllPairs, "y": 65}, "exportTxt": {"text": "Export as MM pair list", "callback": self.exportPairList, "y": 90}, } for button, data in moreButtons.iteritems(): setattr( self.w.results, button, SquareButton((p, data["y"], w, 20), data["text"], callback=data["callback"], sizeStyle="small"), ) # list and preview self.w.outputList = List( (180, 0, 188, -0), [{"left glyph": "", "right glyph": ""}], columnDescriptions=[{"title": "left glyph"}, {"title": "right glyph"}], showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair, ) self.w.preview = MultiLineView((368, 0, -0, -0), pointSize=256) 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.w.results.show(False) 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]] self.w.preview.set(glyphs) except IndexError: pass def showAllPairs(self, sender=None): # open all resulting pairs in Space Center rawString = "" for g1, g2 in self.touchingPairs: rawString += "/%s/%s/space" % (g1, g2) s = OpenSpaceCenter(self.f) s.setRaw(rawString) def exportPairList(self, sender=None): # Save the list of found touching pairs in a text file which can be read by MetricsMachine as a pair list path = PutFile("Choose save location", "TouchingPairs.txt") if path is not None: reportString = "#KPL:P: TouchingPairs\n" for g1, g2 in self.touchingPairs: reportString += "%s %s\n" % (g1, g2) fi = open(path, "w+") fi.write(reportString) fi.close() # 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: 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 _resizeWindow(self, enlarge=True): posSize = self.w.getPosSize() targetWidth = 700 if enlarge else 180 self.w.setPosSize((posSize[0], posSize[1], targetWidth, posSize[3])) # 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]) else: self.w.preview.set("") outputButtons = [self.w.results.spaceView, self.w.results.exportTxt] for b in outputButtons: b.enable(False) if len(self.touchingPairs) == 0 else b.enable(True) 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 OCCToucheTool(): 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 = Window((180, self.windowHeight), u'2ché!', 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") # Modification(Nic): prev / next buttons self.w.options.prevButton = Button((w + 22, 35, 10, 10), "Prev Button", callback=self.prevPair_) self.w.options.prevButton.bind("uparrow", []) self.w.options.nextButton = Button((w + 22, 35, 10, 10), "Next Button", callback=self.nextPair_) self.w.options.nextButton.bind("downarrow", []) # /Modification(Nic) # Modification(Nic): prev / next buttons self.w.options.decrementKerningHigh = Button( (w + 22, 35, 10, 10), "Decrement Kerning High", callback=self.decrementKerningByHigh_) self.w.options.decrementKerningHigh.bind("leftarrow", ["command"]) self.w.options.decrementKerningLow = Button( (w + 22, 35, 10, 10), "Decrement Kerning Low", callback=self.decrementKerningByLow_) self.w.options.decrementKerningLow.bind("leftarrow", []) self.w.options.incrementKerningHigh = Button( (w + 22, 35, 10, 10), "Increment Kerning High", callback=self.incrementKerningByHigh_) self.w.options.incrementKerningHigh.bind("rightarrow", ["command"]) self.w.options.incrementKerningLow = Button( (w + 22, 35, 10, 10), "Increment Kerning Low", callback=self.incrementKerningByLow_) self.w.options.incrementKerningLow.bind("rightarrow", []) # /Modification(Nic) # 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 # Modification(Nic): Incrementing and Decrementing the Pair Index in the list. def prevPair_(self, sender=None): currentIndices = self.w.outputList.getSelection() prevIndices = map(lambda i: max(i - 1, 0), currentIndices) if (len(self.w.outputList) > 0): self.w.outputList.setSelection(prevIndices) def nextPair_(self, sender=None): currentIndices = self.w.outputList.getSelection() nextIndices = map(lambda i: min(i + 1, len(self.w.outputList) - 1), currentIndices) if (len(self.w.outputList) > 0): self.w.outputList.setSelection(nextIndices) # /Modification(Nic) # Modification(Nic): Incrementing and Decrementing the Pair Index in the list. def decrementKerningByLow_(self, sender=None): print('dec by low') currentIndices = self.w.outputList.getSelection() currentPair = self.w.outputList[currentIndices[0]] increment = Glyphs.intDefaults['GSKerningIncrementLow'] self.bumpKerningForPair(currentPair['left glyph'], currentPair['right glyph'], -increment) def decrementKerningByHigh_(self, sender=None): print('dec by high') currentIndices = self.w.outputList.getSelection() currentPair = self.w.outputList[currentIndices[0]] increment = Glyphs.intDefaults['GSKerningIncrementHigh'] self.bumpKerningForPair(currentPair['left glyph'], currentPair['right glyph'], -increment) def incrementKerningByLow_(self, sender=None): print('inc by low') currentIndices = self.w.outputList.getSelection() currentPair = self.w.outputList[currentIndices[0]] increment = Glyphs.intDefaults['GSKerningIncrementLow'] self.bumpKerningForPair(currentPair['left glyph'], currentPair['right glyph'], increment) def incrementKerningByHigh_(self, sender=None): print('inc by high') currentIndices = self.w.outputList.getSelection() currentPair = self.w.outputList[currentIndices[0]] increment = Glyphs.intDefaults['GSKerningIncrementHigh'] self.bumpKerningForPair(currentPair['left glyph'], currentPair['right glyph'], increment) # /Modification(Nic) # Modification(Nic): Routine to increment/decrement kerning properly def bumpKerningForPair(self, left, right, increment): leftGlyph, rightGlyph = Glyphs.font[left], Glyphs.font[right] k = Glyphs.font.kerningForPair(Glyphs.font.selectedFontMaster.id, leftGlyph.rightKerningKey, rightGlyph.leftKerningKey) if k > 10000: k = 0 # Glyphs uses MAXINT to signal no kerning. Glyphs.font.setKerningForPair(Glyphs.font.selectedFontMaster.id, leftGlyph.rightKerningKey, rightGlyph.leftKerningKey, k + increment) # Modification(Nic) 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 = OCCTouche(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'2ché: finished checking %d glyphs in %.2f seconds' % ( len(glyphList), time1 - time0) else: Message(u'2ché: Can’t find a font to check')
class KerningController(BaseWindowController): """this is the main controller of TT kerning editor, it handles different controllers and dialogues with font data""" # these attributes take good care of undo/redo stack archive = [] recordIndex = 0 displayedWord = '' displayedPairs = [] activePair = None canvasScalingFactor = CANVAS_SCALING_FACTOR_INIT fontsOrder = None navCursor_X = 0 # related to pairs navCursor_Y = 0 # related to active fonts isPreviewOn = False areVerticalLettersDrawn = True areGroupsShown = True areCollisionsShown = False isKerningDisplayActive = True isSidebearingsActive = True isCorrectionActive = True isMetricsActive = True isColorsActive = True prevGraphicsValues = None isSymmetricalEditingOn = False isSwappedEditingOn = False isVerticalAlignedEditingOn = False autoSave = True autoSaveSpan = 5 # mins kerningLogger = None def __init__(self): super(KerningController, self).__init__() # init time for the autosave self.initTime = datetime.now() # init logging self._initLogger() # init fonts if AllFonts() == []: message( 'No fonts, no party!', 'Please, open some fonts before starting the mighty MultiFont Kerning Controller' ) return None self.w = Window((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT), PLUGIN_TITLE, minSize=(PLUGIN_WIDTH, PLUGIN_HEIGHT)) self.w.bind('resize', self.mainWindowResize) # load opened fonts self.initFontsOrder() # exception window (will appear only if needed) self.exceptionWindow = ChooseExceptionWindow( ['group2glyph', 'glyph2group', 'glyph2glyph'], callback=self.exceptionWindowCallback) self.exceptionWindow.enable(False) # jump to line window (will appear only if invoked) self.jumpToLineWindow = JumpToLineWindow( callback=self.jumpToLineWindowCallback) self.jumpToLineWindow.enable(False) self.jumping_Y = MARGIN_VER self.jumping_X = MARGIN_HOR self.w.wordListController = WordListController( (self.jumping_X, self.jumping_Y, LEFT_COLUMN, 260), callback=self.wordListControllerCallback) self.displayedWord = self.w.wordListController.get() self.jumping_Y += self.w.wordListController.getPosSize()[3] + 4 self.w.word_font_separationLine = HorizontalLine( (self.jumping_X, self.jumping_Y, LEFT_COLUMN, vanillaControlsSize['HorizontalLineThickness'])) self.jumping_Y += MARGIN_VER fontsOrderControllerHeight = FONT_ROW_HEIGHT * len( self.fontsOrder) + MARGIN_HOR self.w.fontsOrderController = FontsOrderController( (self.jumping_X, self.jumping_Y, LEFT_COLUMN, fontsOrderControllerHeight), self.fontsOrder, callback=self.fontsOrderControllerCallback) self.jumping_Y += fontsOrderControllerHeight self.w.fonts_controller_separationLine = HorizontalLine( (self.jumping_X, self.jumping_Y, LEFT_COLUMN, vanillaControlsSize['HorizontalLineThickness'])) self.jumping_Y += MARGIN_VER self.w.joystick = JoystickController( (self.jumping_X, self.jumping_Y, LEFT_COLUMN, 348), fontObj=self.fontsOrder[self.navCursor_Y], isSymmetricalEditingOn=self.isSymmetricalEditingOn, isSwappedEditingOn=self.isSwappedEditingOn, isVerticalAlignedEditingOn=self.isVerticalAlignedEditingOn, autoSave=self.autoSave, autoSaveSpan=self.autoSaveSpan, activePair=None, callback=self.joystickCallback) self.jumping_Y += self.w.joystick.getPosSize()[3] + MARGIN_VER self.w.graphicsManager = GraphicsManager( (self.jumping_X, -202, LEFT_COLUMN, 202), isKerningDisplayActive=self.isKerningDisplayActive, areVerticalLettersDrawn=self.areVerticalLettersDrawn, areGroupsShown=self.areGroupsShown, areCollisionsShown=self.areCollisionsShown, isSidebearingsActive=self.isSidebearingsActive, isCorrectionActive=self.isCorrectionActive, isMetricsActive=self.isMetricsActive, isColorsActive=self.isColorsActive, callback=self.graphicsManagerCallback) self.jumping_X += LEFT_COLUMN + MARGIN_COL * 2 self.jumping_Y = MARGIN_VER self.w.displayedWordCaption = TextBox( (self.jumping_X, self.jumping_Y, 200, vanillaControlsSize['TextBoxRegularHeight']), self.displayedWord) self.w.scalingFactorController = FactorController( (-MARGIN_HOR - 72, self.jumping_Y, 72, vanillaControlsSize['TextBoxRegularHeight']), self.canvasScalingFactor, callback=self.scalingFactorControllerCallback) self.jumping_Y += self.w.displayedWordCaption.getPosSize( )[3] + MARGIN_COL self.initWordDisplays() self.w.joystick.setActivePair( self.getActiveWordDisplay().getActivePair()) # observers! addObserver(self, 'openCloseFontCallback', "fontDidOpen") addObserver(self, 'openCloseFontCallback', "fontDidClose") self.setUpBaseWindowBehavior() self.w.open() def _initLogger(self): # create a logger self.kerningLogger = logging.getLogger('kerningLogger') self.kerningLogger.setLevel(logging.INFO) # create file handler which logs info messages fileHandle = logging.FileHandler('kerningLogger.log') fileHandle.setLevel(logging.INFO) # create console handler with a higher log level, only errors consoleHandler = logging.StreamHandler() consoleHandler.setLevel(logging.ERROR) # create formatter and add it to the handlers formatter = logging.Formatter(u'%(asctime)s – %(message)s') fileHandle.setFormatter(formatter) consoleHandler.setFormatter(formatter) # add the handlers to the logger self.kerningLogger.addHandler(fileHandle) self.kerningLogger.addHandler(consoleHandler) def windowCloseCallback(self, sender): removeObserver(self, "fontDidOpen") removeObserver(self, "fontWillClose") self.exceptionWindow.close() super(KerningController, self).windowCloseCallback(sender) self.w.close() def openCloseFontCallback(self, sender): self.deleteWordDisplays() self.initFontsOrder() self.w.fontsOrderController.setFontsOrder(self.fontsOrder) self.initWordDisplays() fontsOrderControllerHeight = FONT_ROW_HEIGHT * len( self.fontsOrder) + MARGIN_HOR prevFontsOrderPos = self.w.fontsOrderController.getPosSize() self.w.fontsOrderController.setPosSize( (prevFontsOrderPos[0], prevFontsOrderPos[1], prevFontsOrderPos[2], fontsOrderControllerHeight)) prevSepLinePos = self.w.fonts_controller_separationLine.getPosSize() self.w.fonts_controller_separationLine.setPosSize( (prevSepLinePos[0], prevFontsOrderPos[1] + fontsOrderControllerHeight, prevSepLinePos[2], prevSepLinePos[3])) prevJoystickPos = self.w.joystick.getPosSize() self.w.joystick.setPosSize( (prevJoystickPos[0], prevFontsOrderPos[1] + fontsOrderControllerHeight + MARGIN_VER, prevJoystickPos[2], prevJoystickPos[3])) def initFontsOrder(self): if self.fontsOrder is None: fontsOrder = [f for f in AllFonts() if f.path is not None] self.fontsOrder = sorted(fontsOrder, key=lambda f: os.path.basename(f.path)) else: newFontsOrder = [f for f in AllFonts() if f in self.fontsOrder] + [ f for f in AllFonts() if f not in self.fontsOrder ] self.fontsOrder = newFontsOrder for eachFont in self.fontsOrder: status, report = checkGroupConflicts(eachFont) if status is False: self.kerningLogger.error('groups conflict in {}'.format( eachFont.path)) self.kerningLogger.error(report) def deleteWordDisplays(self): for eachI in xrange(len(self.fontsOrder)): try: delattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) self.jumping_Y = MARGIN_VER + vanillaControlsSize[ 'TextBoxRegularHeight'] except Exception as e: self.kerningLogger.error(traceback.format_exc()) def initWordDisplays(self): windowWidth, windowHeight = self.w.getPosSize()[2], self.w.getPosSize( )[3] netTotalWindowHeight = windowHeight - MARGIN_COL - MARGIN_VER * 2 - vanillaControlsSize[ 'TextBoxRegularHeight'] - MARGIN_HOR * (len(self.fontsOrder) - 1) try: singleWindowHeight = netTotalWindowHeight / len(self.fontsOrder) except ZeroDivisionError: singleWindowHeight = 0 rightColumnWidth = windowWidth - LEFT_COLUMN - MARGIN_COL self.jumping_Y = MARGIN_VER + vanillaControlsSize[ 'TextBoxRegularHeight'] + MARGIN_COL for eachI in xrange(len(self.fontsOrder)): if eachI == self.navCursor_Y: initPairIndex = self.navCursor_X else: initPairIndex = None try: wordCtrl = WordDisplay( (self.jumping_X, self.jumping_Y, rightColumnWidth, singleWindowHeight), displayedWord=self.displayedWord, canvasScalingFactor=self.canvasScalingFactor, fontObj=self.fontsOrder[eachI], isKerningDisplayActive=self.isKerningDisplayActive, areVerticalLettersDrawn=self.areVerticalLettersDrawn, areGroupsShown=self.areGroupsShown, areCollisionsShown=self.areCollisionsShown, isSidebearingsActive=self.isSidebearingsActive, isCorrectionActive=self.isCorrectionActive, isMetricsActive=self.isMetricsActive, isColorsActive=self.isColorsActive, isSymmetricalEditingOn=self.isSymmetricalEditingOn, isSwappedEditingOn=self.isSwappedEditingOn, indexPair=initPairIndex) except Exception: self.kerningLogger.error(traceback.format_exc()) self.jumping_Y += singleWindowHeight + MARGIN_HOR setattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1), wordCtrl) def updateWordDisplays(self): for eachI in xrange(len(self.fontsOrder)): eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) eachDisplay.setSymmetricalEditingMode(self.isSymmetricalEditingOn) eachDisplay.setSwappedEditingMode(self.isSwappedEditingOn) eachDisplay.setScalingFactor(self.canvasScalingFactor) eachDisplay.setGraphicsBooleans( self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive) eachDisplay.wordCanvasGroup.update() def nextWord(self, isRecording=True): self.w.wordListController.nextWord() self.displayedWord = self.w.wordListController.get() self.isSwappedEditingOn = False self.w.joystick.setSwappedEditing(self.isSwappedEditingOn) self.updateEditorAccordingToDiplayedWord() if isRecording is True: self.appendRecord('nextWord') def previousWord(self, isRecording=True): self.w.wordListController.previousWord() self.displayedWord = self.w.wordListController.get() self.isSwappedEditingOn = False self.w.joystick.setSwappedEditing(self.isSwappedEditingOn) self.updateEditorAccordingToDiplayedWord() if isRecording is True: self.appendRecord('previousWord') def jumpToLine(self, lineIndex, isRecording=True): if isRecording is True: self.appendRecord( 'jumpToLine', (self.w.wordListController.getActiveIndex(), lineIndex)) self.w.wordListController.jumpToLine(lineIndex) self.displayedWord = self.w.wordListController.get() self.isSwappedEditingOn = False self.w.joystick.setSwappedEditing(self.isSwappedEditingOn) self.updateEditorAccordingToDiplayedWord() def oneStepGroupSwitch(self, location): if self.isVerticalAlignedEditingOn is True: for eachI in xrange(len(self.fontsOrder)): eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) eachDisplay.switchGlyphFromGroup(location, self.navCursor_X) else: self.getActiveWordDisplay().switchGlyphFromGroup( location, self.navCursor_X) self.w.joystick.setActivePair( self.getActiveWordDisplay().getActivePair()) def updateEditorAccordingToDiplayedWord(self): self.w.displayedWordCaption.set(self.displayedWord) if len(self.displayedWord) - 1 < (self.navCursor_X + 1): self.navCursor_X = len(self.displayedWord) - 2 for eachI in xrange(len(self.fontsOrder)): eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) if self.isVerticalAlignedEditingOn is False: if eachI == self.navCursor_Y: eachDisplay.setActivePairIndex(self.navCursor_X) else: eachDisplay.setActivePairIndex(self.navCursor_X) eachDisplay.setDisplayedWord(self.displayedWord) self.updateWordDisplays() self.getActiveWordDisplay().setActivePairIndex(self.navCursor_X) self.w.joystick.setActivePair( self.getActiveWordDisplay().getActivePair()) def getActiveWordDisplay(self): return getattr(self.w, 'wordCtrl_{:0>2d}'.format(self.navCursor_Y + 1)) def setGraphicsManagerForPreviewMode(self): self.prevGraphicsValues = (self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive) # set preview mode variables self.isKerningDisplayActive = True self.areVerticalLettersDrawn = False self.areGroupsShown = False self.areCollisionsShown = False self.isSidebearingsActive = False self.isCorrectionActive = False self.isMetricsActive = False self.isColorsActive = False self.w.graphicsManager.set( self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive) def restoreGraphicsManagerValues(self): self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive = self.prevGraphicsValues self.prevGraphicsValues = None self.w.graphicsManager.set( self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive) def switchPreviewAttribute(self, isRecording=True): if self.isPreviewOn is True: self.isPreviewOn = False self.restoreGraphicsManagerValues() self.w.graphicsManager.switchControls(True) else: self.isPreviewOn = True self.setGraphicsManagerForPreviewMode() self.w.graphicsManager.switchControls(False) self.updateWordDisplays() self.updateWordDisplays() if isRecording is True: self.appendRecord('preview') def switchSolvedAttribute(self, isRecording=True): self.w.wordListController.switchActiveWordSolvedAttribute() if isRecording is True: self.appendRecord('solved') def switchSwappedEditing(self, isRecording=True): self.isSwappedEditingOn = not self.isSwappedEditingOn if self.isSwappedEditingOn is True and self.isSymmetricalEditingOn is True: self.isSymmetricalEditingOn = False self.w.joystick.setSymmetricalEditing(self.isSymmetricalEditingOn) self.updateWordDisplays() if isRecording is True: self.appendRecord('swappedEditing') def switchSymmetricalEditing(self, isRecording=True): self.isSymmetricalEditingOn = not self.isSymmetricalEditingOn if self.isSwappedEditingOn is True and self.isSymmetricalEditingOn is True: self.isSwappedEditingOn = False self.w.joystick.setSwappedEditing(self.isSwappedEditingOn) self.updateWordDisplays() if isRecording is True: self.appendRecord('symmetricalEditing') def switchVerticalAlignedEditing(self, isRecording=True): self.isVerticalAlignedEditingOn = not self.isVerticalAlignedEditingOn for eachI in xrange(len(self.fontsOrder)): eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) if self.isVerticalAlignedEditingOn is True: eachDisplay.setActivePairIndex(self.navCursor_X) else: if eachI != self.navCursor_Y: eachDisplay.setActivePairIndex(None) self.updateWordDisplays() if isRecording is True: self.appendRecord('verticalAlignedEditing') def exceptionTrigger(self): activeFont = self.fontsOrder[self.navCursor_Y] selectedPair = self.getActiveWordDisplay().getActivePair() correction, kerningReference, pairKind = getCorrection( selectedPair, activeFont) isException, doesExists, parentPair = isPairException( kerningReference, activeFont) # delete exception if isException: if self.isVerticalAlignedEditingOn is True: selectedFonts = self.fontsOrder else: selectedFonts = [self.fontsOrder[self.navCursor_Y]] for eachFont in selectedFonts: isException, doesExists, parentPair = isPairException( kerningReference, eachFont) if isException: deletePair(kerningReference, eachFont) self.appendRecord('deletePair', (kerningReference, eachFont, correction)) else: if not correction: # set standard pair to zero self.setPairCorrection(0) correction, kerningReference, pairKind = getCorrection( selectedPair, activeFont) isException, doesExists, parentPair = isPairException( kerningReference, activeFont) # trigger exception window exceptionOptions = possibleExceptions(selectedPair, kerningReference, activeFont) if len(exceptionOptions) == 1: self.exceptionWindow.set(exceptionOptions[0]) self.exceptionWindow.trigger() elif len(exceptionOptions) > 1: self.exceptionWindow.setOptions(exceptionOptions) self.exceptionWindow.enable(True) else: self.showMessage( 'no possible exceptions', 'kerning exceptions can be triggered only starting from class kerning corrections' ) self.updateWordDisplays() # manipulate data def deletePair(self, isRecording=True): if self.autoSave is True: self.checkAutoSave() selectedPair = self.getActiveWordDisplay().getActivePair() if self.isVerticalAlignedEditingOn is True: selectedFonts = self.fontsOrder else: selectedFonts = [self.fontsOrder[self.navCursor_Y]] for eachFont in selectedFonts: previousAmount, kerningReference, pairKind = getCorrection( selectedPair, eachFont) deletePair(kerningReference, eachFont) if isRecording is True: self.appendRecord('deletePair', (kerningReference, eachFont, previousAmount)) self.kerningLogger.info( DELETE_PAIR_LOG.format(leftGlyphName=selectedPair[0], rightGlyphName=selectedPair[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) if self.isSwappedEditingOn is True: swappedCorrectionKey = selectedPair[1], selectedPair[0] previousAmount, kerningReference, pairKind = getCorrection( swappedCorrectionKey, eachFont) deletePair(kerningReference, eachFont) if isRecording is True: self.appendRecord('deletePair', kerningReference, eachFont, previousAmount) self.kerningLogger.info( DELETE_PAIR_LOG.format(leftGlyphName=kerningReference[0], rightGlyphName=kerningReference[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) if self.isSymmetricalEditingOn is True: symmetricalCorrectionKey = findSymmetricalPair(selectedPair) previousAmount, kerningReference, pairKind = getCorrection( symmetricalCorrectionKey, eachFont) deletePair(kerningReference, eachFont) if isRecording is True: self.appendRecord('deletePair', kerningReference, eachFont, previousAmount) self.kerningLogger.info( DELETE_PAIR_LOG.format(leftGlyphName=kerningReference[0], rightGlyphName=kerningReference[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) self.updateWordDisplays() def setPairCorrection(self, amount, isRecording=True): if self.autoSave is True: self.checkAutoSave() selectedPair = self.getActiveWordDisplay().getActivePair() if self.isVerticalAlignedEditingOn is True: selectedFonts = self.fontsOrder else: selectedFonts = [self.fontsOrder[self.navCursor_Y]] for eachFont in selectedFonts: if isRecording is True: previousAmount = getCorrection(selectedPair, eachFont)[0] self.appendRecord('setCorrection', (selectedPair, eachFont, previousAmount)) setCorrection(selectedPair, eachFont, amount) self.kerningLogger.info( SET_CORRECTION_LOG.format(leftGlyphName=selectedPair[0], rightGlyphName=selectedPair[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName, amount=amount)) if self.isSwappedEditingOn is True: swappedCorrectionKey = selectedPair[1], selectedPair[0] if isRecording is True: previousAmount = getCorrection(swappedCorrectionKey, eachFont)[0] self.appendRecord( 'setCorrection', (swappedCorrectionKey, eachFont, previousAmount)) setCorrection(swappedCorrectionKey, eachFont, amount) self.kerningLogger.info( SET_CORRECTION_LOG.format( leftGlyphName=swappedCorrectionKey[0], rightGlyphName=swappedCorrectionKey[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName, amount=amount)) if self.isSymmetricalEditingOn is True: symmetricalCorrectionKey = findSymmetricalPair(selectedPair) if symmetricalCorrectionKey: if isRecording is True: previousAmount = getCorrection( symmetricalCorrectionKey, eachFont)[0] self.appendRecord('setCorrection', (symmetricalCorrectionKey, eachFont, previousAmount)) setCorrection(symmetricalCorrectionKey, eachFont, amount) self.kerningLogger.info( SET_CORRECTION_LOG.format( leftGlyphName=symmetricalCorrectionKey[0], rightGlyphName=symmetricalCorrectionKey[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName, amount=amount)) self.updateWordDisplays() def modifyPairCorrection(self, amount, isRecording=True): if self.autoSave is True: self.checkAutoSave() selectedPair = self.getActiveWordDisplay().getActivePair() if self.isVerticalAlignedEditingOn is True: selectedFonts = self.fontsOrder else: selectedFonts = [self.fontsOrder[self.navCursor_Y]] for eachFont in selectedFonts: correction, correctionKey, pairKind = getCorrection( selectedPair, eachFont) correction = 0 if correction is None else correction if isRecording is True: previousAmount = getCorrection(selectedPair, eachFont)[0] self.appendRecord('setCorrection', (selectedPair, eachFont, previousAmount)) setCorrection(selectedPair, eachFont, correction + amount) self.kerningLogger.info( SET_CORRECTION_LOG.format(leftGlyphName=selectedPair[0], rightGlyphName=selectedPair[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName, amount=amount)) if self.isSwappedEditingOn is True: swappedPair = selectedPair[1], selectedPair[0] if isRecording is True: previousAmount = getCorrection(selectedPair, eachFont)[0] self.appendRecord('setCorrection', (swappedPair, eachFont, previousAmount)) setCorrection(swappedPair, eachFont, correction + amount) self.kerningLogger.info( SET_CORRECTION_LOG.format( leftGlyphName=swappedPair[0], rightGlyphName=swappedPair[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName, amount=amount)) if self.isSymmetricalEditingOn is True: symmetricalCorrectionKey = findSymmetricalPair(selectedPair) if symmetricalCorrectionKey: if isRecording is True: previousAmount = getCorrection( symmetricalCorrectionKey, eachFont)[0] self.appendRecord('setCorrection', (symmetricalCorrectionKey, eachFont, previousAmount)) setCorrection(symmetricalCorrectionKey, eachFont, correction + amount) self.kerningLogger.info( SET_CORRECTION_LOG.format( leftGlyphName=symmetricalCorrectionKey[0], rightGlyphName=symmetricalCorrectionKey[1], familyName=eachFont.info.familyName, styleName=eachFont.info.styleName, amount=amount)) self.w.joystick.updateCorrectionValue() self.updateWordDisplays() # cursor methods def cursorLeftRight(self, direction, isRecording=True): assert direction in ['left', 'right'] if direction == 'left': step = -1 if isRecording is True: self.appendRecord('cursorLeft') else: step = +1 if isRecording is True: self.appendRecord('cursorRight') self.navCursor_X = (self.navCursor_X + step) % (len(self.displayedWord) - 1) for eachI in xrange(len(self.fontsOrder)): eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) if self.isVerticalAlignedEditingOn is False: if eachI == self.navCursor_Y: eachDisplay.setActivePairIndex(self.navCursor_X) else: eachDisplay.setActivePairIndex(self.navCursor_X) self.w.joystick.setActivePair( self.getActiveWordDisplay().getActivePair()) self.updateWordDisplays() def cursorUpDown(self, direction, isRecording=True): assert direction in ['up', 'down'] if direction == 'up': step = -1 if isRecording is True: self.appendRecord('cursorUp') else: step = +1 if isRecording is True: self.appendRecord('cursorDown') if self.isVerticalAlignedEditingOn is False: self.getActiveWordDisplay().setActivePairIndex(None) # old self.navCursor_Y = (self.navCursor_Y + step) % len(self.fontsOrder) self.getActiveWordDisplay().setActivePairIndex( self.navCursor_X) # new self.w.joystick.setFontObj(self.fontsOrder[self.navCursor_Y]) self.updateWordDisplays() ### callbacks def exceptionWindowCallback(self, sender): if self.isVerticalAlignedEditingOn is False: selectedFonts = [self.fontsOrder[self.navCursor_Y]] else: selectedFonts = self.fontsOrder selectedPair = self.getActiveWordDisplay().getActivePair() if sender.lastEvent == 'submit': for indexFont, eachFont in enumerate(selectedFonts): exceptionKey = sender.get() correction, kerningReference, pairKind = getCorrection( selectedPair, eachFont) setRawCorrection(exceptionKey, eachFont, correction) self.appendRecord('createException', (exceptionKey, eachFont, correction)) if indexFont == self.navCursor_Y: self.w.joystick.updateCorrectionValue() self.updateWordDisplays() def jumpToLineWindowCallback(self, sender): if sender.get() is not None: self.jumpToLine(sender.get()) self.jumpToLineWindow.enable(False) def mainWindowResize(self, mainWindow): windowWidth, windowHeight = mainWindow.getPosSize( )[2], mainWindow.getPosSize()[3] rightColumnWidth = windowWidth - LEFT_COLUMN # caption prevdisplayedWordCaptionSize = self.w.displayedWordCaption.getPosSize() self.w.displayedWordCaption.setPosSize( (prevdisplayedWordCaptionSize[0], prevdisplayedWordCaptionSize[1], rightColumnWidth, prevdisplayedWordCaptionSize[3])) # displayers initY = MARGIN_VER + vanillaControlsSize[ 'TextBoxRegularHeight'] + MARGIN_COL netTotalWindowHeight = windowHeight - initY - MARGIN_VER - MARGIN_HOR * ( len(self.fontsOrder) - 1) try: singleWordDisplayHeight = netTotalWindowHeight / len( self.fontsOrder) except ZeroDivisionError: singleWordDisplayHeight = 0 y = initY for eachI in xrange(len(self.fontsOrder)): eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1)) eachDisplay.adjustSize( (self.jumping_X, y, rightColumnWidth, singleWordDisplayHeight)) eachDisplay.setCtrlSize(rightColumnWidth, singleWordDisplayHeight) y += singleWordDisplayHeight + MARGIN_HOR def scalingFactorControllerCallback(self, sender): self.canvasScalingFactor = sender.getScalingFactor() self.updateWordDisplays() def wordListControllerCallback(self, sender): self.displayedWord = sender.get() self.updateEditorAccordingToDiplayedWord() def fontsOrderControllerCallback(self, sender): self.deleteWordDisplays() self.fontsOrder = [] for indexFont, eachFont in enumerate(sender.getFontsOrder()): if sender.getIsDisplayedOrder()[indexFont] is True: self.fontsOrder.append(eachFont) self.initWordDisplays() def graphicsManagerCallback(self, sender): self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive = sender.get( ) self.updateWordDisplays() def joystickCallback(self, sender): joystickEvent = sender.getLastEvent() assert joystickEvent in JOYSTICK_EVENTS if joystickEvent == 'minusMajor': if self.isKerningDisplayActive is True: self.modifyPairCorrection(-MAJOR_STEP) else: self.showMessage('Be aware!', KERNING_NOT_DISPLAYED_ERROR, callback=None) elif joystickEvent == 'minusMinor': if self.isKerningDisplayActive is True: self.modifyPairCorrection(-MINOR_STEP) else: self.showMessage('Be aware!', KERNING_NOT_DISPLAYED_ERROR, callback=None) elif joystickEvent == 'plusMinor': if self.isKerningDisplayActive is True: self.modifyPairCorrection(MINOR_STEP) else: self.showMessage('Be aware!', KERNING_NOT_DISPLAYED_ERROR, callback=None) elif joystickEvent == 'plusMajor': if self.isKerningDisplayActive is True: self.modifyPairCorrection(MAJOR_STEP) else: self.showMessage('Be aware!', KERNING_NOT_DISPLAYED_ERROR, callback=None) elif joystickEvent == 'preview': self.switchPreviewAttribute() elif joystickEvent == 'solved': self.switchSolvedAttribute() self.nextWord() elif joystickEvent == 'symmetricalEditing': self.switchSymmetricalEditing() elif joystickEvent == 'swappedEditing': self.switchSwappedEditing() elif joystickEvent == 'verticalAlignedEditing': self.switchVerticalAlignedEditing() elif joystickEvent == 'previousWord': self.previousWord() elif joystickEvent == 'cursorUp': self.cursorUpDown('up') elif joystickEvent == 'cursorLeft': self.cursorLeftRight('left') elif joystickEvent == 'cursorRight': self.cursorLeftRight('right') elif joystickEvent == 'cursorDown': self.cursorUpDown('down') elif joystickEvent == 'nextWord': self.nextWord() elif joystickEvent == 'deletePair': if self.isKerningDisplayActive is True: self.deletePair() self.w.joystick.setActivePair( self.getActiveWordDisplay().getActivePair()) elif joystickEvent == 'switchLftGlyph': self.oneStepGroupSwitch(location='left') elif joystickEvent == 'switchRgtGlyph': self.oneStepGroupSwitch(location='right') elif joystickEvent == 'keyboardEdit': if self.isKerningDisplayActive is True: correctionAmount = self.w.joystick.getKeyboardCorrection() if correctionAmount is None: self.deletePair() else: self.setPairCorrection(correctionAmount) self.updateWordDisplays() else: self.showMessage('Be aware!', KERNING_NOT_DISPLAYED_ERROR, callback=None) self.w.joystick.updateCorrectionValue() elif joystickEvent == 'jumpToLineTrigger': self.jumpToLineWindow.enable(True) elif joystickEvent == 'exceptionTrigger': self.exceptionTrigger() # from here on events are not archived in undo/redo stack elif joystickEvent == 'undo': self.undo() elif joystickEvent == 'redo': self.redo() elif joystickEvent == 'autoSave': self.autoSave, self.autoSaveSpan = self.w.joystick.getAutoSaveState( ) # autosaving fonts def checkAutoSave(self): justNow = datetime.now() if (justNow - self.initTime).seconds > self.autoSaveSpan * 60: self.saveFontsOrder() self.initTime = datetime.now() def saveFontsOrder(self): for eachFont in self.fontsOrder: eachFont.save() self.kerningLogger.info("all fonts saved") # undo/redo stack def appendRecord(self, actionName, data=None): if self.recordIndex < 0: self.archive = self.archive[:self.recordIndex] self.recordIndex = 0 if data is None: self.archive.append(actionName) else: self.archive.append((actionName, data)) def undo(self): if abs(self.recordIndex) <= len(self.archive) - 1: self.recordIndex -= 1 self.pullRecordFromArchive('undo') def redo(self): if self.recordIndex < 0: self.recordIndex += 1 self.pullRecordFromArchive('redo') def pullRecordFromArchive(self, direction): """we miss these methods: switchLftGlyph, switchRgtGlyph""" assert direction in ['redo', 'undo'] if direction == 'redo': record = self.archive[ self.recordIndex - 1] # othwerwise we won't get back to the initial status else: record = self.archive[self.recordIndex] # these records, we can simply invert (they related to events in UI) if isinstance(record, types.StringType) is True: if record == 'nextWord': if direction == 'undo': self.previousWord(isRecording=False) else: self.nextWord(isRecording=False) elif record == 'previousWord': if direction == 'undo': self.nextWord(isRecording=False) else: self.previousWord(isRecording=False) elif record == 'preview': self.switchPreviewAttribute(isRecording=False) elif record == 'solved': self.switchSolvedAttribute(isRecording=False) elif record == 'swappedEditing': self.switchSwappedEditing(isRecording=False) elif record == 'symmetricalEditing': self.switchSymmetricalEditing(isRecording=False) elif record == 'verticalAlignedEditing': self.switchVerticalAlignedEditing(isRecording=False) elif record == 'cursorLeft': if direction == 'undo': self.cursorLeftRight('right', isRecording=False) else: self.cursorLeftRight('left', isRecording=False) elif record == 'cursorRight': if direction == 'undo': self.cursorLeftRight('left', isRecording=False) else: self.cursorLeftRight('right', isRecording=False) elif record == 'cursorUp': if direction == 'undo': self.cursorUpDown('down', isRecording=False) else: self.cursorUpDown('up', isRecording=False) elif record == 'cursorDown': if direction == 'undo': self.cursorUpDown('up', isRecording=False) else: self.cursorUpDown('down', isRecording=False) # these relate to data manipulation... else: recordTitle, data = record if recordTitle == 'setCorrection': pair, font, amount = data setCorrection(pair, font, amount) self.updateWordDisplays() elif recordTitle == 'createException': pair, font, amount = data if direction == 'undo': deletePair(pair, font) else: setRawCorrection(pair, font, amount) elif recordTitle == 'deletePair': pair, font, amount = data if direction == 'undo': setRawCorrection(pair, font, amount) else: deletePair(pair, font) elif recordTitle == 'jumpToLine': previousIndex, nextIndex = data if direction == 'undo': self.jumpToLine(previousIndex) else: self.jumpToLine(nextIndex, isRecording=False)
class ToucheTool(): def __init__(self): self.w = Window((180, 340), u'Touché!', minSize=(180, 340), maxSize=(1000, 898)) p = 10 w = 160 # options self.w.options = Group((0, 0, 180, 220)) buttons = { "checkSelBtn": { "text": "Check selected glyphs\nfor touching pairs", "callback": self.checkSel, "y": p }, "checkAllBtn": { "text": "Check entire font\n(can take several minutes!)", "callback": self.checkAll, "y": 60 } } for button, data in buttons.items(): setattr( self.w.options, button, SquareButton((p, data["y"], w, 40), data["text"], callback=data["callback"], sizeStyle="small")) self.w.options.zeroCheck = CheckBox((p, 108, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small") self.w.options.progress = ProgressSpinner((82, 174, 16, 16), sizeStyle="small") # results self.w.results = Group((0, 220, 180, -0)) self.w.results.show(False) textBoxes = {"stats": 24, "result": 42} for box, y in textBoxes.items(): setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small")) moreButtons = { "spaceView": { "text": "View all in Space Center", "callback": self.showAllPairs, "y": 65 }, "exportTxt": { "text": "Export as MM pair list", "callback": self.exportPairList, "y": 90 } } for button, data in moreButtons.items(): setattr( self.w.results, button, SquareButton((p, data["y"], w, 20), data["text"], callback=data["callback"], sizeStyle="small")) # list and preview self.w.outputList = List((180, 0, 188, -0), [{ "left glyph": "", "right glyph": "" }], columnDescriptions=[{ "title": "left glyph" }, { "title": "right glyph" }], showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair) self.w.preview = MultiLineView((368, 0, -0, -0), pointSize=256) 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.w.results.show(False) 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]] self.w.preview.set(glyphs) except IndexError: pass def showAllPairs(self, sender=None): # open all resulting pairs in Space Center rawString = "" for g1, g2 in self.touchingPairs: rawString += "/%s/%s/space" % (g1, g2) s = OpenSpaceCenter(self.f) s.setRaw(rawString) def exportPairList(self, sender=None): # Save the list of found touching pairs in a text file which can be read by MetricsMachine as a pair list path = PutFile(message="Choose save location", fileName="TouchingPairs.txt") if path is not None: reportString = "#KPL:P: TouchingPairs\n" for g1, g2 in self.touchingPairs: reportString += "%s %s\n" % (g1, g2) with open(path, 'w+') as fi: try: fi.write(reportString) except: #py2! fi.write(unicode(reportString)) # 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: return False return True def _trimGlyphList(self, glyphList): newGlyphList = [] for g in glyphList: bounds = g.bounds if version > '2.0' else g.box if bounds is not None and self._hasSufficientWidth(g): newGlyphList.append(g) return newGlyphList def _resizeWindow(self, enlarge=True): posSize = self.w.getPosSize() targetWidth = 700 if enlarge else 180 self.w.setPosSize((posSize[0], posSize[1], targetWidth, posSize[3])) # 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]) else: self.w.preview.set("") outputButtons = [ self.w.results.spaceView, self.w.results.exportTxt ] for b in outputButtons: b.enable(False) if len( self.touchingPairs) == 0 else b.enable(True) self.w.preview.setFont(f) self.w.preview.setApplyKerning(True) 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 MultiFontMetricsWindow(BaseWindowController): # attrs textModeOptions = ['Loaded Strings', 'Typewriter'] textMode = textModeOptions[0] selectedGlyph = None subscribedGlyph = None fontsDB = None unicodeMinimum = {} fontsOrder = None glyphNamesToDisplay = [] showMetrics = False applyKerning = False leftToRight = True verticalMode = False multiLineOptions = {} bodySizeOptions = [ 10, 11, 12, 13, 18, 24, 30, 36, 48, 60, 72, 144, 216, 360 ] lineHeightOptions = range(0, 301, 10) bodySize = bodySizeOptions[10] lineHeight = lineHeightOptions[0] fontsAmountOptions = range(1, 9) def __init__(self): super(MultiFontMetricsWindow, self).__init__() # ui vars originLeft = 0 originRight = 0 width = 956 height = 640 netWidth = width - MARGIN_LFT - MARGIN_RGT jumpingY = MARGIN_TOP # let's see if there are opened fonts (fontDB, ctrlFontsList, fontsOrder) self.loadFontsOrder() self.updateUnicodeMinimum() # defining plugin windows self.w = Window((originLeft, originRight, width, height), "Multi Font Metrics Window", minSize=(800, 400)) self.w.bind('resize', self.mainWindowResize) switchButtonWdt = 140 self.w.switchButton = PopUpButton( (MARGIN_LFT, jumpingY, switchButtonWdt, vanillaControlsSize['PopUpButtonRegularHeight']), self.textModeOptions, sizeStyle='regular', callback=self.switchButtonCallback) # free text textCtrlX = MARGIN_LFT + MARGIN_COL + switchButtonWdt self.w.typewriterCtrl = Typewriter( (textCtrlX, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT), vanillaControlsSize['EditTextRegularHeight']), self.unicodeMinimum, callback=self.typewriterCtrlCallback) self.w.typewriterCtrl.show(False) # strings ctrls self.w.textStringsControls = TextStringsControls( (textCtrlX, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT), vanillaControlsSize['PopUpButtonRegularHeight'] + 1), self.unicodeMinimum, callback=self.textStringsControlsCallback) self.stringDisplayMode, self.glyphNamesToDisplay = self.w.textStringsControls.get( ) # multi line jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_ROW self.calcSpacingMatrixHeight() self.multiLineOptions = { 'Show Kerning': self.applyKerning, 'Center': False, 'Show Space Matrix': False, 'Single Line': False, 'displayMode': u'Multi Line', 'Stroke': False, 'xHeight Cut': False, 'Upside Down': False, 'Beam': False, 'Water Fall': False, 'Inverse': False, 'Show Template Glyphs': True, 'Multi Line': True, 'Right to Left': False, 'Show Metrics': self.showMetrics, 'Show Control glyphs': True, 'Fill': True, 'Left to Right': self.leftToRight } self.w.lineView = MultiLineView( (MARGIN_LFT, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT), -MARGIN_BTM - MARGIN_HALFROW - self.spacingMatrixHeight), pointSize=self.bodySize, lineHeight=self.lineHeight, doubleClickCallback=self.lineViewDoubleClickCallback, selectionCallback=self.lineViewSelectionCallback, bordered=True, applyKerning=self.applyKerning, hasHorizontalScroller=False, hasVerticalScroller=True, displayOptions=self.multiLineOptions, updateUserDefaults=False, menuForEventCallback=None) # static options # body self.w.bodyCtrl = ComboBoxWithCaption( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, vanillaControlsSize['ComboBoxSmallHeight'] + 1), 'Body Size:', self.bodySizeOptions, '{}'.format(self.bodySize), sizeStyle='small', callback=self.bodyCtrlCallback) # line height jumpingY += vanillaControlsSize['ComboBoxSmallHeight'] + int( MARGIN_HALFROW) self.w.lineHgtCtrl = ComboBoxWithCaption( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, vanillaControlsSize['ComboBoxSmallHeight'] + 1), 'Line Height:', self.lineHeightOptions, '{}'.format(self.lineHeight), sizeStyle='small', callback=self.lineHgtCtrlCallback) # show metrics jumpingY += vanillaControlsSize['ComboBoxSmallHeight'] + MARGIN_ROW self.w.showMetricsCheck = CheckBox( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, vanillaControlsSize['CheckBoxSmallHeight']), "Show Metrics", value=self.showMetrics, sizeStyle='small', callback=self.showMetricsCheckCallback) # show kerning checkbox jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + MARGIN_ROW * .3 self.w.applyKerningCheck = CheckBox( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, vanillaControlsSize['CheckBoxSmallHeight']), "Show Kerning", value=self.applyKerning, sizeStyle='small', callback=self.applyKerningCheckCallback) jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + MARGIN_ROW * .3 self.w.leftToRightCheck = CheckBox( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, vanillaControlsSize['CheckBoxSmallHeight']), "Left to right", value=self.leftToRight, sizeStyle='small', callback=self.leftToRightCheckCallback) # separationLine jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + int( MARGIN_HALFROW) self.w.separationLineOne = HorizontalLine( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 1)) jumpingY += int(MARGIN_HALFROW) fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(self.fontsOrder) + 2 self.w.fontsOrderController = FontsOrderController( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, fontsOrderControllerHeight), self.fontsOrder, callback=self.fontsOrderControllerCallback) jumpingY += fontsOrderControllerHeight self.w.separationLineTwo = HorizontalLine( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 1)) # joystick jumpingY += MARGIN_HALFROW self.w.joystick = SpacingJoystick( (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 0), verticalMode=self.verticalMode, marginCallback=self.joystickMarginCallback, verticalCallback=self.joystickVerticalCallback) # edit metrics self.w.spacingMatrix = SpacingMatrix( (MARGIN_LFT, -(MARGIN_BTM + self.spacingMatrixHeight), self.w.getPosSize()[2] - MARGIN_LFT - MARGIN_RGT, self.spacingMatrixHeight), glyphNamesToDisplay=self.glyphNamesToDisplay, verticalMode=self.verticalMode, fontsOrder=self.fontsOrder, callback=self.spacingMatrixCallback) # add observer addObserver(self, 'newFontOpened', "newFontDidOpen") addObserver(self, 'openCloseFontCallback', "fontDidOpen") addObserver(self, 'openCloseFontCallback', "fontDidClose") # lit up! self.updateSubscriptions() self.updateLineView() self.setUpBaseWindowBehavior() self.w.open() # defcon observers (glyph obj) def updateSubscriptions(self): self.unsubscribeGlyphs() # subscribe self.subscribedGlyph = [] for eachFont in self.fontsOrder: for eachGlyphName in self.glyphNamesToDisplay: if eachFont.has_key(eachGlyphName): eachGlyph = eachFont[eachGlyphName] if eachGlyph not in self.subscribedGlyph: # you can't subscribe a glyph twice eachGlyph.addObserver(self, "glyphChangedCallback", "Glyph.Changed") self.subscribedGlyph.append(eachGlyph) def unsubscribeGlyphs(self): if self.subscribedGlyph: for eachGlyph in self.subscribedGlyph: eachGlyph.removeObserver(self, "Glyph.Changed") def glyphChangedCallback(self, notification): self.w.lineView.update() # observers funcs def mainWindowResize(self, mainWindow): windowWidth = mainWindow.getPosSize()[2] self.w.spacingMatrix.adjustSize( (windowWidth - MARGIN_LFT - MARGIN_RGT, self.spacingMatrixHeight)) def openCloseFontCallback(self, sender): self.loadFontsOrder() self.w.fontsOrderController.setFontsOrder(self.fontsOrder) self.w.spacingMatrix.setFontsOrder(self.fontsOrder) self.updateUnicodeMinimum() self.updateSubscriptions() self.adjustSpacingMatrixHeight() self.w.spacingMatrix.update() self.adjustFontsOrderControllerHeight() self.updateLineView() def loadFontsOrder(self): if self.fontsOrder is None: fontsOrder = [f for f in AllFonts() if f.path is not None] self.fontsOrder = sorted(fontsOrder, key=lambda f: os.path.basename(f.path)) else: newFontsOrder = [f for f in AllFonts() if f in self.fontsOrder] + [ f for f in AllFonts() if f not in self.fontsOrder ] self.fontsOrder = newFontsOrder def newFontOpened(self, notification): message( 'The MultiFont Metrics Window works only with saved font, please save the new font and re-open the plugin' ) def calcSpacingMatrixHeight(self): self.spacingMatrixHeight = vanillaControlsSize[ 'EditTextSmallHeight'] + len( self.fontsOrder ) * vanillaControlsSize['EditTextSmallHeight'] * 2 def spacingMatrixCallback(self, sender): self.w.lineView.update() # other funcs def adjustSpacingMatrixHeight(self): self.calcSpacingMatrixHeight() lineViewPosSize = self.w.lineView.getPosSize() self.w.lineView.setPosSize( (lineViewPosSize[0], lineViewPosSize[1], lineViewPosSize[2], -MARGIN_BTM - MARGIN_HALFROW - self.spacingMatrixHeight)) spacingMatrixPosSize = self.w.spacingMatrix.getPosSize() self.w.spacingMatrix.setPosSize( (spacingMatrixPosSize[0], -MARGIN_BTM - self.spacingMatrixHeight, spacingMatrixPosSize[2], self.spacingMatrixHeight)) def adjustFontsOrderControllerHeight(self): fontsOrderControllerHeight = FONT_ROW_HEIGHT * len( self.fontsOrder) + MARGIN_COL fontsOrderControllerPosSize = self.w.fontsOrderController.getPosSize() self.w.fontsOrderController.setPosSize( (fontsOrderControllerPosSize[0], fontsOrderControllerPosSize[1], fontsOrderControllerPosSize[2], fontsOrderControllerHeight)) def windowCloseCallback(self, sender): self.unsubscribeGlyphs() removeObserver(self, "newFontDidOpen") removeObserver(self, "fontDidOpen") removeObserver(self, "fontDidClose") super(MultiFontMetricsWindow, self).windowCloseCallback(sender) def updateUnicodeMinimum(self): # collect everything allUnicodeData = {} for eachFont in self.fontsOrder: for eachKey, eachValue in eachFont.naked().unicodeData.items(): if eachKey not in allUnicodeData: allUnicodeData[eachKey] = eachValue # filter self.unicodeMinimum = {} for eachKey, eachValue in allUnicodeData.items(): for eachFont in self.fontsOrder: if eachKey not in eachFont.naked().unicodeData: break if eachValue: self.unicodeMinimum[eachKey] = eachValue def updateLineView(self): try: displayedGlyphs = [] if self.stringDisplayMode == 'Waterfall': for indexFont, eachFont in enumerate(self.fontsOrder): assert isinstance( eachFont, RFont), 'object in self.fontsOrder not a RFont' if indexFont != 0: newLineGlyph = self.w.lineView.createNewLineGlyph() displayedGlyphs.append(newLineGlyph) for eachGlyphName in self.glyphNamesToDisplay: if eachFont.has_key(eachGlyphName): displayedGlyphs.append(eachFont[eachGlyphName]) elif eachGlyphName == '.newLine': newLineGlyph = self.w.lineView.createNewLineGlyph() displayedGlyphs.append(newLineGlyph) else: if eachFont.has_key('.notdef'): displayedGlyphs.append(eachFont['.notdef']) else: displayedGlyphs.append(NOT_DEF_GLYPH) else: for eachGlyphName in self.glyphNamesToDisplay: for indexFont, eachFont in enumerate(self.fontsOrder): assert isinstance( eachFont, RFont), 'object in self.fontsOrder not a RFont' if eachFont.has_key(eachGlyphName): displayedGlyphs.append(eachFont[eachGlyphName]) else: if eachFont.has_key('.notdef'): displayedGlyphs.append(eachFont['.notdef']) else: displayedGlyphs.append(NOT_DEF_GLYPH) self.w.lineView.set(displayedGlyphs) # add kerning if needed if self.applyKerning is True: glyphRecords = self.w.lineView.contentView().getGlyphRecords() leftRecord = glyphRecords[0] rightRecord = glyphRecords[1] for indexRecords, eachGlyphRecord in enumerate(glyphRecords): if indexRecords == len( glyphRecords) and indexRecords == len( 0): # avoid first and last continue leftRecord = glyphRecords[indexRecords - 1] rightRecord = glyphRecords[indexRecords] leftGlyph = leftRecord.glyph rightGlyph = rightRecord.glyph # if they come from the same font... leftFont = leftGlyph.getParent() rightFont = rightGlyph.getParent() if leftFont is rightFont: if (leftGlyph.name, rightGlyph.name) in leftFont.flatKerning: leftRecord.xAdvance += leftFont.flatKerning[( leftGlyph.name, rightGlyph.name)] # manually refresh the line view ctrl, it should help self.w.lineView.update() # update spacing matrix self.w.spacingMatrix.canvas.update() except Exception, error: print traceback.format_exc()