class TesterWindow: def __init__(self): self.w = FloatingWindow((100, 100), "Tester") self.w.bind("close", self.closeCB) self.guideView = None addObserver(self, "glyphWindowOpenCB", "glyphWindowDidOpen") def glyphWindowOpenCB(self, info): glyphWindow = info["window"] self.guideView = GuideStatusView() self.guideView.view.statusText.set("TEST") self.guideView.addViewToWindow(glyphWindow) self.guideView.turnStatusTextOn() def closeCB(self, sender): removeObserver(self, "glyphWindowDidOpen")
class DemoWindowTool: def __init__(self): self.glyph = None # Typical RoboFont function self.updating = False pad = 10 leading = 32 y = pad w = 300 h = 400 buttonWidth = 100 buttonHeight = 48 self.w = FloatingWindow((100, 100, w, h), 'DemoWindowTool') self.w.doDraw = CheckBox((pad, y, 100, 24), 'Draw', callback=self.doDrawCallback) y += leading self.w.mySlider = Slider((pad, y, -pad, 24), minValue=0, maxValue=2000, value=0, tickMarkCount=10, callback=self.mySliderCallback) y = self.w.saveFont = Button((-buttonWidth - pad, -buttonHeight - pad, buttonWidth, buttonHeight), 'Save', callback=self.saveFontCallback) addObserver(self, "currentGlyphChanged", "currentGlyphChanged") # Subscribe to application event addObserver(self, 'draw', 'draw') addObserver(self, 'drawBackground', 'drawBackground') self.w.bind('close', self.windowCloseCallback) self.w.open() def windowCloseCallback(self, sender): removeObserver( self, "currentGlyphChanged") # Unsubscribing from application event removeObserver(self, 'draw') removeObserver(self, 'drawBackground') print('removeObserver currentGlyphChanged') def mySliderCallback(self, sender): if self.glyph is not None: self.glyph.width = sender.get() # same as self.w.mySlider self.updating = True self.glyph.changed() UpdateCurrentGlyphView() self.updating = False def doDrawCallback(self, sender): UpdateCurrentGlyphView() def saveFontCallback(self, sender): print('Saving') def draw(self, info): #print('Drawing' ,info['glyph']) if not self.updating: glyph = info['glyph'] self.w.mySlider.set(glyph.width) if self.w.doDraw.get(): fill(1, 0, 0, 0.5) rect(0, 0, glyph.width, 50) def drawBackground(self, info): #print('Drawing' ,info['glyph']) if not self.updating: glyph = info['glyph'] self.w.mySlider.set(glyph.width) if self.w.doDraw.get(): fill(0, 0, 1, 0.5) rect(0, 0, glyph.width, 50) #def glyphChanged(self, info): # print('Glyph changed', info['glyph']) def currentGlyphChanged(self, info): #if self.glyph is not None: # self.glyph.removeObserver(self, 'Glyph.Change') self.glyph = info['glyph']
class AdjustAnchors(BaseWindowController): def __init__(self): self.font = CurrentFont() self.glyph = CurrentGlyph() self.upm = self.font.info.unitsPerEm self.rf3 = int(roboFontVersion.split(".")[0]) >= 3 if self.rf3: self.layer = CurrentLayer() # key: glyph name -- value: list containing assembled glyphs self.glyphPreviewCacheDict = {} # key: anchor name -- value: list of mark glyph names self.anchorsOnMarksDict = {} # key: anchor name -- value: list of base glyph names self.anchorsOnBasesDict = {} self.CXTanchorsOnBasesDict = {} # key: mark glyph name -- value: anchor name # NOTE: It's expected that each mark glyph only has one type of anchor self.marksDict = {} self.fillAnchorsAndMarksDicts() # list of glyph names that will be displayed in the UI list self.glyphNamesList = [] # list of glyph names selected in the UI list self.selectedGlyphNamesList = [] # list of the glyph objects that should be inserted # before and after the accented glyphs self.extraGlyphsList = [] self.Blue, self.Alpha = 1, 0.6 self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") addObserver(self, "_fontWillClose", "fontWillClose") addObserver(self, "_currentFontChanged", "fontResignCurrent") addObserver(self, "_currentGlyphChanged", "currentGlyphChanged") addObserver(self, "_drawFill", "draw") addObserver(self, "_drawFill", "drawInactive") addObserver(self, "_previewFill", "drawPreview") # observer for the draw event addObserver(self, "_drawGlyphs", "draw") # draw the glyphs when the glyph window is not in focus addObserver(self, "_drawGlyphs", "drawInactive") addObserver(self, "_drawGlyphs", "drawPreview") integerNumFormatter = NSNumberFormatter.alloc().init() integerNumFormatter.setAllowsFloats_(False) integerNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init() intPosMinZeroNumFormatter.setAllowsFloats_(False) intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0)) intPosMinOneNumFormatter = NSNumberFormatter.alloc().init() intPosMinOneNumFormatter.setAllowsFloats_(False) intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1)) self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize")) if not self.textSize: self.textSize = 150 self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight")) if not self.lineHeight: self.lineHeight = 200 self.extraSidebearings = getExtensionDefault( "%s.%s" % (extensionKey, "extraSidebearings")) if not self.extraSidebearings: self.extraSidebearings = [0, 0] self.extraGlyphs = getExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs")) if not self.extraGlyphs: self.extraGlyphs = '' posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize")) if not posSize: posSize = (100, 100, 1200, 400) self.calibrateMode = getExtensionDefault( "%s.%s" % (extensionKey, "calibrateMode")) if not self.calibrateMode: self.calibrateMode = False calibrateModeStrings = getExtensionDefault( "%s.%s" % (extensionKey, "calibrateModeStrings")) if not calibrateModeStrings: calibrateModeStrings = { 'group1.baseInput': 'dotlessi o s', 'group1.markInput': 'dieresis circumflex macron breve caron', 'group2.baseInput': 'I O S', 'group2.markInput': 'dieresis.cap circumflex.cap macron.cap ' 'breve.cap caron.cap', 'group3.baseInput': 'I.sc O.sc S.sc', 'group3.markInput': 'dieresis circumflex macron breve caron', 'group4.baseInput': '', 'group4.markInput': '', } # -- Window -- self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400)) self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback) if roboFontVersion < '1.7': # use the full width of the column self.w.fontList.getNSTableView().sizeToFit() self.w.fontList.show(not self.calibrateMode) self.w.lineView = MultiLineView((210, 10, -10, -41), pointSize=self.textSize, lineHeight=self.lineHeight, displayOptions={ "Beam": False, "displayMode": "Multi Line" }) self.w.lineView.setFont(self.font) # -- Calibration Mode -- baseLabel = "Bases" markLabel = "Marks" width, height = 190, 140 self.cm = Group((0, 0, 0, 0)) # --- self.cm.group1 = Group((5, height * 0, width, height - 10)) self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group1.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group1.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group2 = Group((5, height * 1, width, height - 10)) self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group2.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group2.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group3 = Group((5, height * 2, width, height - 10)) self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group3.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group3.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group4 = Group((5, height * 3, width, height - 10)) self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group4.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group4.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False) # --- view = DefconAppKitTopAnchoredNSView.alloc().init() view.addSubview_(self.cm.getNSView()) view.setFrame_(((0, 0), (width + 10, height * 4 - 23))) self.cm.setPosSize((0, 0, width + 10, height * 4 - 22)) self.w.scrollView = ScrollView((5, 10, width + 10, -41), view, drawsBackground=False, hasHorizontalScroller=False) self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder) # NSScrollElasticityNone self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) self.w.scrollView.show(self.calibrateMode) # -- Footer -- self.w.footer = Group((10, -32, -10, -10)) self.w.footer.calibrateModeCheck = CheckBox( (0, 0, 200, -0), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode) self.w.footer.textSizeLabel = TextBox((200, 2, 100, -0), "Text Size") self.w.footer.textSize = EditText((260, 0, 35, -0), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter) self.w.footer.lineHeightLabel = TextBox((310, 2, 100, -0), "Line Height") self.w.footer.lineHeight = EditText((385, 0, 35, -0), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter) self.w.footer.extraSidebearingsLabel = TextBox((436, 2, 180, -0), "Extra Sidebearings") self.w.footer.extraSidebearingsChar = TextBox((592, 2, 20, -0), "&") self.w.footer.extraSidebearingLeft = EditText( (557, 0, 35, -0), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.footer.extraSidebearingRight = EditText( (604, 0, 35, -0), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.footer.extraGlyphsLabel = TextBox((655, 2, 180, -0), "Extra Glyphs") self.w.footer.extraGlyphs = EditText((739, 0, -0, -0), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False) # trigger the initial state and contents of the window self.extraGlyphsCallback() # calls self.updateExtensionWindow() self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def calibrateModeCallback(self, sender): self.calibrateMode = not self.calibrateMode self.w.fontList.show(not sender.get()) self.w.scrollView.show(self.calibrateMode) self.updateExtensionWindow() def textSizeCallback(self, sender): try: # in case the user submits an empty field self.textSize = int(sender.get()) except Exception: # reset to the previous value NSBeep() self.sender.set(self.textSize) self.w.lineView.setPointSize(self.textSize) def lineHeightCallback(self, sender): try: self.lineHeight = int(sender.get()) except Exception: NSBeep() self.sender.set(self.lineHeight) self.w.lineView.setLineHeight(self.lineHeight) def extraSidebearingsCallback(self, sender): left = self.w.footer.extraSidebearingLeft right = self.w.footer.extraSidebearingRight try: self.extraSidebearings = [int(left.get()), int(right.get())] except Exception: NSBeep() left.set(self.extraSidebearings[0]) right.set(self.extraSidebearings[1]) self.extraGlyphsCallback() # calls self.updateExtensionWindow() def extraGlyphsCallback(self, *sender): del self.extraGlyphsList[:] # empty the list self.extraGlyphs = self.w.footer.extraGlyphs.get() glyphNamesList = self.extraGlyphs.split() for gName in glyphNamesList: try: extraGlyph = self.font[gName] # must create a new glyph in order to be able to # increase the sidebearings without modifying the font newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # must use deepAppend because the extra glyph may have # components (which will cause problems to the MultiLineView) newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph) newGlyph.width = extraGlyph.width except Exception: continue newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] self.extraGlyphsList.append(newGlyph) self.glyphPreviewCacheDict.clear() self.updateExtensionWindow() def windowClose(self, sender): self.font.naked().removeObserver(self, "Font.Changed") removeObserver(self, "fontWillClose") removeObserver(self, "fontResignCurrent") removeObserver(self, "currentGlyphChanged") removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") self.saveExtensionDefaults() def getCalibrateModeStrings(self): calibrateModeStringsDict = {} for i in range(1, 5): group = getattr(self.cm, "group%d" % i) calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get() calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get() return calibrateModeStringsDict def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize()) setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize) setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight) setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings) setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs) setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode) setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings()) def _previewFill(self, info): self.Blue, self.Alpha = 0, 1 def _drawFill(self, info): self.Blue, self.Alpha = 1, 0.6 def _fontWillClose(self, info): """ Close the window when the last font is closed """ if len(AllFonts()) < 2: self.windowClose(self) self.w.close() def _currentFontChanged(self, info): self.font.naked().removeObserver(self, "Font.Changed") self.font = CurrentFont() self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") self.w.lineView.setFont(self.font) self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def _currentGlyphChanged(self, info): self.updateExtensionWindow() def fontWasModified(self, info): OutputWindow().clear() self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def deepAppendGlyph(self, glyph, gToAppend, offset=(0, 0)): if not gToAppend.components: glyph.appendGlyph(gToAppend, offset) else: for component in gToAppend.components: # avoid traceback in the case where the selected glyph is # referencing a component whose glyph is not in the font if component.baseGlyph not in self.font.keys(): print("WARNING: %s is referencing a glyph named %s, which " "does not exist in the font." % (self.font.selection[0], component.baseGlyph)) continue compGlyph = self.font[component.baseGlyph].copy() # handle component transformations componentTransformation = component.transformation # when undoing a paste anchor or a delete anchor action, # RoboFont returns component.transformation as a list instead # of a tuple if type(componentTransformation) is list: componentTransformation = tuple(componentTransformation) # if component is skewed and/or is shifted if componentTransformation != (1, 0, 0, 1, 0, 0): matrix = componentTransformation[0:4] if matrix != (1, 0, 0, 1): # if component is skewed # ignore the original component's shifting values transformObj = Identity.transform(matrix + (0, 0)) compGlyph.transform(transformObj) # add the two tuples of offset glyph.appendGlyph( compGlyph, tuple(map(sum, zip(component.offset, offset)))) for contour in gToAppend: glyph.appendContour(contour, offset) # if the assembled glyph still has components, recursively # remove and replace them 1-by-1 by the glyphs they reference if glyph.components: nestedComponent = glyph.components[-1] # start from the end glyph.removeComponent(nestedComponent) glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset) return glyph def updateCalibrateMode(self, *sender): glyphsList = [] newLine = self.w.lineView.createNewLineGlyph() # cycle thru the UI Groups and collect the strings for i in range(1, 5): group = getattr(self.cm, "group%d" % i) baseGlyphsNamesList = group.baseInput.get().split() markGlyphsNamesList = group.markInput.get().split() # iterate thru the base+mark combinations for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList): newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # skip invalid glyph names try: baseGlyph = self.font[gBaseName] markGlyph = self.font[gMarkName] except Exception: continue # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph)) # set the advanced width dfltSidebearings = self.upm * .05 # 5% of UPM newGlyph.leftMargin = (dfltSidebearings + self.extraSidebearings[0]) newGlyph.rightMargin = (dfltSidebearings + self.extraSidebearings[1]) # append the assembled glyph to the list glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) # add line break, if both input fields have content if baseGlyphsNamesList and markGlyphsNamesList: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newLine) # update the contents of the MultiLineView self.w.lineView.set(glyphsList) def updateExtensionWindow(self): if self.calibrateMode: self.updateCalibrateMode() return # NOTE: CurrentGlyph() will return zero (its length), # so "is not None" is necessary if CurrentGlyph() is not None: self.glyph = CurrentGlyph() self.glyphNamesList = self.makeGlyphNamesList(self.glyph) self.updateListView() currentGlyphName = self.glyph.name # base glyph + accent combinations preview # first check if there's a cached glyph if currentGlyphName in self.glyphPreviewCacheDict: self.w.lineView.set( self.glyphPreviewCacheDict[currentGlyphName]) # assemble the glyphs else: glyphsList = [] for glyphNameInUIList in self.glyphNamesList: # trim the contextual portion of the UI glyph name # and keep track of it if CONTEXTUAL_ANCHOR_TAG in glyphNameInUIList: cxtTagIndex = glyphNameInUIList.find( CONTEXTUAL_ANCHOR_TAG) glyphNameCXTportion = glyphNameInUIList[cxtTagIndex:] # this line must be last! glyphNameInUIList = glyphNameInUIList[:cxtTagIndex] else: glyphNameCXTportion = '' newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # the glyph in the UI list is a mark if glyphNameInUIList in self.marksDict: markGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph, glyphNameCXTportion)) # set the advanced width # combining marks or other glyphs with # a small advanced width if self.glyph.width < 10: newGlyph.leftMargin = self.upm * .05 # 5% of UPM newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = self.glyph.width # the glyph in the UI list is a base else: baseGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph)) # set the advanced width # combining marks or other glyphs with # a small advanced width if self.glyph.width < 10: newGlyph.leftMargin = self.upm * .05 newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = baseGlyph.width # pad the new glyph if it has too much overhang if newGlyph.leftMargin < self.upm * .15: newGlyph.leftMargin = self.upm * .05 if newGlyph.rightMargin < self.upm * .15: newGlyph.rightMargin = self.upm * .05 # add extra sidebearings newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] # one last check for making sure the new glyph # can be displayed if not newGlyph.components: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) else: print("Combination with mark glyph %s can't be " "previewed because it contains component %s." % (glyphNameInUIList + glyphNameCXTportion, newGlyph.components[0].baseGlyph)) glyphsList.extend(self.extraGlyphsList) self.w.lineView.set(glyphsList) # add to the cache self.glyphPreviewCacheDict[currentGlyphName] = glyphsList else: self.w.lineView.set([]) def listSelectionCallback(self, sender): selectedGlyphNamesList = [] for index in sender.getSelection(): selectedGlyphNamesList.append(self.glyphNamesList[index]) self.selectedGlyphNamesList = selectedGlyphNamesList self.updateGlyphView() def updateGlyphView(self): UpdateCurrentGlyphView() def fillAnchorsAndMarksDicts(self): # reset all the dicts self.glyphPreviewCacheDict.clear() self.anchorsOnMarksDict.clear() self.anchorsOnBasesDict.clear() self.CXTanchorsOnBasesDict.clear() self.marksDict.clear() markGlyphsWithMoreThanOneAnchorTypeList = [] for glyphName in self.font.glyphOrder: glyphAnchorsList = self.font[glyphName].anchors for anchor in glyphAnchorsList: if anchor.name[0] == '_': anchorName = anchor.name[1:] # add to AnchorsOnMarks dictionary if anchorName not in self.anchorsOnMarksDict: self.anchorsOnMarksDict[anchorName] = [glyphName] else: tempList = self.anchorsOnMarksDict[anchorName] tempList.append(glyphName) self.anchorsOnMarksDict[anchorName] = tempList # add to Marks dictionary if glyphName not in self.marksDict: self.marksDict[glyphName] = anchorName else: if (glyphName not in markGlyphsWithMoreThanOneAnchorTypeList): markGlyphsWithMoreThanOneAnchorTypeList.append( glyphName) else: anchorName = anchor.name if CONTEXTUAL_ANCHOR_TAG in anchorName: # add to AnchorsOnBases dictionary if anchorName not in self.CXTanchorsOnBasesDict: self.CXTanchorsOnBasesDict[anchorName] = [ glyphName ] else: tempList = self.CXTanchorsOnBasesDict[anchorName] tempList.append(glyphName) self.CXTanchorsOnBasesDict[anchorName] = tempList else: # add to AnchorsOnBases dictionary if anchorName not in self.anchorsOnBasesDict: self.anchorsOnBasesDict[anchorName] = [glyphName] else: tempList = self.anchorsOnBasesDict[anchorName] tempList.append(glyphName) self.anchorsOnBasesDict[anchorName] = tempList if markGlyphsWithMoreThanOneAnchorTypeList: for glyphName in markGlyphsWithMoreThanOneAnchorTypeList: print("ERROR: Glyph %s has more than one type of anchor." % glyphName) def makeGlyphNamesList(self, glyph): glyphNamesList = [] markGlyphIsAbleToBeBase = False # NOTE: "if glyph" will return zero (its length), # so "is not None" is necessary if glyph is not None: # assemble the list for the UI list for anchor in glyph.anchors: anchorName = anchor.name # the glyph selected is a base if anchorName in self.anchorsOnMarksDict: glyphNamesList.extend(self.anchorsOnMarksDict[anchorName]) # the glyph selected is a mark # skips the leading underscore elif anchorName[1:] in self.anchorsOnBasesDict: glyphNamesList.extend( self.anchorsOnBasesDict[anchorName[1:]]) # the glyph selected is a base elif anchorName[0] != '_' and (anchorName in self.CXTanchorsOnBasesDict): cxtTagIndex = anchorName.find(CONTEXTUAL_ANCHOR_TAG) anchorNameNOTCXTportion = anchorName[:cxtTagIndex] anchorNameCXTportion = anchorName[cxtTagIndex:] # XXX here only the first mark glyph that has an anchor of # the kind 'anchorNameNOTCXTportion' is considered. # This is probably harmless, but... glyphName = '%s%s' % ( self.anchorsOnMarksDict[anchorNameNOTCXTportion][0], anchorNameCXTportion) glyphNamesList.append(glyphName) # for mark glyphs, test if they're able to get # other mark glyphs attached to them. # this will (correctly) prevent the UI list from including # glyph names that cannot be displayed with the current glyph if glyph.name in self.marksDict: for anchor in glyph.anchors: # the current mark glyph has anchors that # allow it to be a base for other marks if anchor.name[0] != '_': markGlyphIsAbleToBeBase = True break # remove marks from the glyph list if the # current mark glyph can't work as a base if not markGlyphIsAbleToBeBase: # iterate from the end of the list for glyphName in glyphNamesList[::-1]: if glyphName in self.marksDict: glyphNamesList.remove(glyphName) glyphNamesList.sort() return glyphNamesList def updateListView(self): self.w.fontList.set(self.glyphNamesList) def getAnchorOffsets(self, canvasGlyph, glyphToDraw, anchorNameCXTportion=''): # the current glyph is a mark if canvasGlyph.name in self.marksDict: # glyphToDraw is also a mark (mark-to-mark case) if glyphToDraw.name in self.marksDict: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] != '_': anchorName = anchor.name markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == '_' + anchorName: baseAnchor = anchor break except UnboundLocalError: continue # glyphToDraw is not a mark else: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] == '_': anchorName = anchor.name[1:] markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == anchorName: baseAnchor = anchor break except UnboundLocalError: continue try: offsetX = markAnchor.x - baseAnchor.x offsetY = markAnchor.y - baseAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 # the current glyph is a base else: try: anchorName = self.marksDict[glyphToDraw.name] except KeyError: anchorName = None if anchorName: # pick the (base glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name == anchorName + anchorNameCXTportion: baseAnchor = anchor break # pick the (mark glyph) anchor to draw on for anchor in glyphToDraw.anchors: if anchor.name == '_' + anchorName: markAnchor = anchor break try: offsetX = baseAnchor.x - markAnchor.x offsetY = baseAnchor.y - markAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 return (offsetX, offsetY) def _drawGlyphs(self, info): """ draw stuff in the glyph window view """ translateBefore = (0, 0) for glyphName in self.selectedGlyphNamesList: # trim the contextual portion of the UI glyph name # and keep track of it if CONTEXTUAL_ANCHOR_TAG in glyphName: cxtTagIndex = glyphName.find(CONTEXTUAL_ANCHOR_TAG) glyphNameCXTportion = glyphName[cxtTagIndex:] glyphName = glyphName[:cxtTagIndex] # this line must be last! else: glyphNameCXTportion = '' glyphToDraw = self.font[glyphName] # determine the offset of the anchors offset = self.getAnchorOffsets(self.glyph, glyphToDraw, glyphNameCXTportion) # set the offset of the drawing translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1]) # record the shift amounts (these are needed for resetting the # drawing position when more than one mark is selected on the list) translateBefore = offset # set the fill & stroke fill(0, 0, self.Blue, self.Alpha) strokeWidth(None) # draw it mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font) glyphToDraw.draw(mojoPen) mojoPen.draw()
class spacingObserver(object): def __init__(self): self.enableGroupSpacing = False self.popupOpen = False addObserver(self, 'glyphEditCallback', 'spaceCenterKeyDown') addObserver(self, 'glyphEditedCallback', 'spaceCenterKeyUp') addObserver(self, 'spaceCenterOpenCallback', 'spaceCenterDidOpen') addObserver(self, 'fontOpenCallback', 'fontDidOpen') self.previousMargins = {'left': 0, 'right': 0} def processMetricsGroups(self, baseGlyph=None): for groupName in self.metricsGroups: if (baseGlyph is None) and len(self.font.groups[groupName]) > 0: baseGlyph = self.font.groups[groupName][0] self.previousMargins['left'] = self.font[baseGlyph].angledLeftMargin self.previousMargins['right'] = self.font[baseGlyph].angledRightMargin if (metricsPrefix in groupName) and (baseGlyph in self.font.groups[groupName]): if (leftIndicator in groupName) and (self.previousMargins['left'] != self.font[baseGlyph].angledLeftMargin): self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Left') elif (rightIndicator in groupName) and (self.previousMargins['right'] != self.font[baseGlyph].angledRightMargin): self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Right') def setGroupSpacing(self, baseGlyphName, group, side): for glyphName in group: baseGlyph = self.font[baseGlyphName] targetGlyph = self.font[glyphName] if glyphName is not baseGlyphName: if (len(targetGlyph.components) > 0) and (side == 'Left'): for component in targetGlyph.components: if component.baseGlyph in group: component.move((self.previousMargins['left']-baseGlyph.angledLeftMargin, 0)) self.setSidebearing(baseGlyph, targetGlyph, side) elif glyphName is baseGlyphName: if (len(baseGlyph.components) > 0) and (side == 'Left'): for component in baseGlyph.components: if component.baseGlyph in group: component.move((self.previousMargins['left']-baseGlyph.angledLeftMargin, 0)) targetGlyph.update() def setSidebearing(self, baseGlyph, targetGlyph, side): baseMargin = getattr(baseGlyph, 'angled' + side + 'Margin') targetMargin = getattr(targetGlyph, 'angled' + side + 'Margin') if targetMargin != baseMargin: setattr(targetGlyph, 'angled' + side + 'Margin', baseMargin) def getMetricsGroups(self, notification=None): self.font = CurrentFont() if self.font is not None: self.metricsGroups = [group for group in self.font.groups.keys() if metricsPrefix in group and leftIndicator in group or rightIndicator in group] if (notification is not None) and (self.enableGroupSpacing == True): self.processMetricsGroups() def enableGroupSpacingCallback(self, sender): self.enableGroupSpacing = sender.get() def glyphEditCallback(self, notification): edGlyph = notification['glyph'] self.previousMargins = {'width': edGlyph.width, 'left': edGlyph.angledLeftMargin, 'right': edGlyph.angledRightMargin} def glyphEditedCallback(self, notification): if self.enableGroupSpacing == True: edGlyph = notification['glyph'] if self.font != CurrentFont(): self.getMetricsGroups() self.processMetricsGroups(edGlyph.name) def spaceCenterOpenCallback(self, notification): if (not self.popupOpen) and (len(self.metricsGroups) > 0): self.w = FloatingWindow((160, 36), 'Group Spacing') self.w.activateGroups = CheckBox((9, -27, 151, 18), "Activate Group spacing", value=self.enableGroupSpacing, callback=self.enableGroupSpacingCallback, sizeStyle="small") self.w.bind('close', self.windowCloseCallback) self.w.open() self.popupOpen = True def windowCloseCallback(self, notification): self.popupOpen = False def fontOpenCallback(self, notification): font = notification['font'] font.groups.addObserver(self, 'getMetricsGroups', 'Groups.Changed') self.getMetricsGroups(notification)
class Adhesiontext(BaseWindowController): def __init__(self): flushAlign = 76 firstRowY = 12 rowOffsetY = 30 firstCheckY = 135 checkOffsetY = 27 rightMarginX = -12 self.windowWidth = 410 self.windowHeightWithoutOptions = 45 self.windowHeightWithOptions = 280 self.scriptIsRTL = False windowPos = getExtensionDefault("%s.%s" % (extensionKey, "windowPos")) if not windowPos: windowPos = (100, 100) self.optionsVisible = getExtensionDefault("%s.%s" % (extensionKey, "optionsVisible")) if self.optionsVisible: optionsButtonSign = '-' windowHeight = self.windowHeightWithOptions else: self.optionsVisible = False # needs to be set because the first time the extension runs self.optionsVisible will be None optionsButtonSign = '+' windowHeight = self.windowHeightWithoutOptions self.chars = getExtensionDefault("%s.%s" % (extensionKey, "chars")) if not self.chars: self.chars = '' self.sliderValue = getExtensionDefault("%s.%s" % (extensionKey, "sliderValue")) if not self.sliderValue: self.sliderValue = 25 self.scriptsIndex = getExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex")) if not self.scriptsIndex: self.scriptsIndex = 0 self.langsIndex = getExtensionDefault("%s.%s" % (extensionKey, "langsIndex")) if not self.langsIndex: self.langsIndex = 0 self.w = FloatingWindow((windowPos[0], windowPos[1], self.windowWidth, windowHeight), "adhesiontext") # 1st row self.w.labelChars = TextBox((10, firstRowY, flushAlign, 20), "Characters:", alignment="right") self.w.chars = EditText((flushAlign +15, firstRowY -1, 199, 22), self.chars, callback=self.charsCallback) self.w.button = Button((300, firstRowY, 68, 20), "Get text", callback=self.buttonCallback) self.w.spinner = FixedSpinner((325, firstRowY, 20, 20), displayWhenStopped=False) self.w.optionsButton = SquareButton((378, firstRowY +1, 18, 18), optionsButtonSign, sizeStyle="small", callback=self.optionsCallback) # set the initial state of the button according to the content of the chars EditText if len(self.w.chars.get()): self.w.button.enable(True) else: self.w.button.enable(False) # keep track of the content of chars EditText self.previousChars = self.w.chars.get() # 2nd row self.w.labelWords = TextBox((10, firstRowY + rowOffsetY, flushAlign, 20), "Words:", alignment="right") self.w.wordCount = TextBox((flushAlign +12, firstRowY + rowOffsetY, 40, 20), alignment="left") self.w.slider = Slider((flushAlign +47, firstRowY + rowOffsetY +1, 165, 20), value=self.sliderValue, minValue=5, maxValue=200, callback=self.sliderCallback) # set the initial wordCount value according to the position of the slider self.w.wordCount.set(int(self.w.slider.get())) # 3rd row self.w.labelScripts = TextBox((10, firstRowY + rowOffsetY *2, flushAlign, 20), "Script:", alignment="right") self.w.scriptsPopup = PopUpButton((flushAlign +15, firstRowY + rowOffsetY *2, 150, 20), scriptsNameList, callback=self.scriptsCallback) self.w.scriptsPopup.set(self.scriptsIndex) # 4th row self.w.labelLangs = TextBox((10, firstRowY + rowOffsetY *3, flushAlign, 20), "Language:", alignment="right") self.w.langsPopup = PopUpButton((flushAlign +15, firstRowY + rowOffsetY *3, 150, 20), []) # set the initial list of languages according to the script value self.w.langsPopup.setItems(langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]]) self.w.langsPopup.set(self.langsIndex) self.punctCheck = getExtensionDefault("%s.%s" % (extensionKey, "punctCheck")) if not self.punctCheck: self.punctCheck = 0 self.figsCheck = getExtensionDefault("%s.%s" % (extensionKey, "figsCheck")) if not self.figsCheck: self.figsCheck = 0 self.figsPopup = getExtensionDefault("%s.%s" % (extensionKey, "figsPopup")) if not self.figsPopup: self.figsPopup = 0 self.trimCheck = getExtensionDefault("%s.%s" % (extensionKey, "trimCheck")) if not self.trimCheck: self.trimCheck = 0 self.caseCheck = getExtensionDefault("%s.%s" % (extensionKey, "caseCheck")) if not self.caseCheck: self.caseCheck = 0 self.casingCheck = getExtensionDefault("%s.%s" % (extensionKey, "casingCheck")) if not self.casingCheck: self.casingCheck = 0 self.casingPopup = getExtensionDefault("%s.%s" % (extensionKey, "casingPopup")) if not self.casingPopup: self.casingPopup = 0 # 1st checkbox self.w.punctCheck = CheckBox((flushAlign +15, firstCheckY, 130, 20), "Add punctuation") self.w.punctCheck.set(self.punctCheck) # 2nd checkbox self.w.figsCheck = CheckBox((flushAlign +15, firstCheckY + checkOffsetY, 120, 20), "Insert numbers", callback=self.figsCallback) self.w.figsCheck.set(self.figsCheck) self.w.figsPopup = PopUpButton((210, firstCheckY + checkOffsetY, 90, 20), figOptionsList) self.w.figsPopup.set(self.figsPopup) # enable or disable the figure options PopUp depending on the figures CheckBox if scriptsNameList[self.w.scriptsPopup.get()] in enableFigOptionList: self.w.figsPopup.show(True) if self.w.figsCheck.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) else: self.w.figsPopup.show(False) # 3rd checkbox self.w.trimCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *2, 120, 20), "Trim accents") self.w.trimCheck.set(self.trimCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableTrimCheckList: self.w.trimCheck.enable(True) else: self.w.trimCheck.enable(False) # 4th checkbox self.w.caseCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *3, 120, 20), "Ignore casing") self.w.caseCheck.set(self.caseCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList: self.w.caseCheck.enable(True) else: self.w.caseCheck.enable(False) # 5th checkbox self.w.casingCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *4, 115, 20), "Change casing", callback=self.casingCallback) self.w.casingCheck.set(self.casingCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList: self.w.casingCheck.enable(True) else: self.w.casingCheck.enable(False) self.w.casingPopup = PopUpButton((210, firstCheckY + checkOffsetY *4, 90, 20), casingNameList) self.w.casingPopup.set(self.casingPopup) # enable or disable the casing PopUp depending on the casing CheckBox if self.w.casingCheck.get() and self.w.casingCheck.isEnable(): self.w.casingPopup.enable(True) else: self.w.casingPopup.enable(False) self.nsTextField = self.w.chars.getNSTextField() self.w.setDefaultButton(self.w.button) self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def windowClose(self, sender): self.saveExtensionDefaults() def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "windowPos"), self.w.getPosSize()[0:2]) setExtensionDefault("%s.%s" % (extensionKey, "optionsVisible"), self.optionsVisible) setExtensionDefault("%s.%s" % (extensionKey, "chars"), self.w.chars.get()) setExtensionDefault("%s.%s" % (extensionKey, "sliderValue"), int(self.w.slider.get())) setExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex"), int(self.w.scriptsPopup.get())) setExtensionDefault("%s.%s" % (extensionKey, "langsIndex"), int(self.w.langsPopup.get())) setExtensionDefault("%s.%s" % (extensionKey, "punctCheck"), self.w.punctCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "figsCheck"), self.w.figsCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "figsPopup"), self.w.figsPopup.get()) setExtensionDefault("%s.%s" % (extensionKey, "trimCheck"), self.w.trimCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "caseCheck"), self.w.caseCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "casingCheck"), self.w.casingCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "casingPopup"), self.w.casingPopup.get()) def buttonCallback(self, sender): sender.enable(False) self.w.spinner.start() self.getText() self.w.spinner.stop() sender.enable(True) def optionsCallback(self, sender): sign = sender.getTitle() if sign == "+": sender.setTitle("-") self.w.resize(self.windowWidth, self.windowHeightWithOptions, animate=True) self.optionsVisible = True else: sender.setTitle("+") self.w.resize(self.windowWidth, self.windowHeightWithoutOptions, animate=True) self.optionsVisible = False def charsCallback(self, sender): charsContent = sender.get() if len(charsContent): self.w.button.enable(True) nsTextView = self.nsTextField.currentEditor() # NOTE: the field editor is only available when NSTextField is in editing mode. # when only one glyph is selected and copied, the contents of the clipboard are the glyph's XML # instead of its unicode character or its name; therefore, post-process the pasted content. if xmlHeader in charsContent: caretIndex = charsContent.index(xmlHeader) codepointString = re_glyphUnicode.search(charsContent) glyphName = re_glyphName.search(charsContent) if codepointString: replacement = unichr(eval('0x' + codepointString.group(1))) elif glyphName: replacement = '/' + glyphName.group(1) else: replacement = '' # replace the glyph's XML by its unicode character or its name self.w.chars.set(re_glyph.sub(replacement, charsContent)) # restore the location of the caret location = caretIndex + len(replacement) nsTextView.setSelectedRange_((location, 0)) # update the variable charsContent = sender.get() caretIndex = nsTextView.selectedRanges()[0].rangeValue().location # Limit the number of characters numeralWasFound = self.stringHasNumeral(charsContent) if len(charsContent) > maxChars or numeralWasFound: NSBeep() if numeralWasFound: self.showMessage("Sorry, numerals are not allowed.", "") else: self.showMessage("You've reached the maximum \rnumber of characters.", "The limit is %d." % maxChars) # restore the content of chars EditText to the previous string sender.set(self.previousChars) # restore the focus on the chars EditText and restore the location of the caret caretIndexAdjust = len(self.previousChars) - len(charsContent) self.w.getNSWindow().makeFirstResponder_(self.nsTextField) nsTextView.setSelectedRange_((caretIndex + caretIndexAdjust, 0)) # update the stored string self.previousChars = sender.get() else: self.w.button.enable(False) def sliderCallback(self, sender): self.w.wordCount.set(int(sender.get())) def scriptsCallback(self, sender): self.w.langsPopup.setItems(langsNameDict[scriptsNameList[sender.get()]]) # toggle RTL/LTR if scriptsNameList[sender.get()] in rightToLeftList: self.scriptIsRTL = True self.nsTextField.setBaseWritingDirection_(NSWritingDirectionRightToLeft) self.nsTextField.setAlignment_(NSRightTextAlignment) else: self.scriptIsRTL = False self.nsTextField.setBaseWritingDirection_(NSWritingDirectionLeftToRight) self.nsTextField.setAlignment_(NSLeftTextAlignment) # restore the focus on the chars EditText self.w.getNSWindow().makeFirstResponder_(self.nsTextField) # toggle figsPopup if scriptsNameList[sender.get()] in enableFigOptionList: self.w.figsPopup.show(True) if self.w.figsCheck.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) else: self.w.figsPopup.show(False) # toggle trimCheck if scriptsNameList[sender.get()] in enableTrimCheckList: self.w.trimCheck.enable(True) else: self.w.trimCheck.enable(False) # toggle caseCheck and casingCheck if scriptsNameList[sender.get()] in enableCaseCheckList: self.w.caseCheck.enable(True) self.w.casingCheck.enable(True) if self.w.casingCheck.get(): self.w.casingPopup.enable(True) else: self.w.caseCheck.enable(False) self.w.casingCheck.enable(False) self.w.casingPopup.enable(False) def figsCallback(self, sender): if sender.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) def casingCallback(self, sender): if sender.get(): self.w.casingPopup.enable(True) else: self.w.casingPopup.enable(False) def stringHasNumeral(self, string): if re_numeral.search(string): return True return False def isConnected(self): try: urlopen(url, timeout=3) return True except URLError: pass return False def getText(self): if CurrentFont() is None: NSBeep() self.showMessage("Open a font first.", "") return if not self.isConnected(): NSBeep() self.showMessage("Required internet connection not found.", "") return values = {'chars' : self.w.chars.get().encode('utf-8'), 'script' : scriptsTagDict[scriptsNameList[self.w.scriptsPopup.get()]], 'tb' : langsTagDict[langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]][self.w.langsPopup.get()]] } if self.w.punctCheck.get(): values['punct'] = True if self.w.figsCheck.get(): values['figs'] = True if self.w.figsPopup.isVisible(): figsOptTagsList = ["dflt", "locl"] values['figsOpt'] = figsOptTagsList[self.w.figsPopup.get()] if self.w.trimCheck.get() and self.w.trimCheck.isEnable(): values['trim'] = True if self.w.caseCheck.get() and self.w.caseCheck.isEnable(): values['case'] = True if self.w.casingCheck.get() and self.w.casingCheck.isEnable(): values['casing'] = casingNameList[self.w.casingPopup.get()].lower() data = urlencode(values) data = data.encode('utf-8') print(data) request = Request(url, data) response = urlopen(request) text = response.read() textU = unicode(text, 'utf-8') if (msgStr in textU): textU = textU.replace(msgStr, "") NSBeep() self.showMessage(textU, "") return elif (wrnStr in textU): resultIndex = textU.find(rsltStr) secmsgIndex = textU.find(sndStr) frstmsgU = textU[:secmsgIndex].replace(wrnStr, "") scndmsgU = textU[secmsgIndex:resultIndex].replace(sndStr, "") textU = textU[resultIndex:].replace(rsltStr, "") NSBeep() self.showMessage(frstmsgU, scndmsgU) textList = textU.split() trimmedText = ' '.join(textList[:int(self.w.slider.get())]) if CurrentSpaceCenter() is None: OpenSpaceCenter(CurrentFont(), newWindow=False) sp = CurrentSpaceCenter() print(trimmedText) sp.setRaw(trimmedText) # Toggle RTL-LTR try: sp.setLeftToRight(not self.scriptIsRTL) sp.setInputWritingDirection('Right to Left' if self.scriptIsRTL else 'Left to Right') except AttributeError: pass return
class CornerController: def __init__(self): self.modifiedGlyph = None self.w = FloatingWindow((400, 170), 'Corner Tool') self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor()) self.modes = ['Break', 'Build','Pit'] self.objectTypes = {'Build':'Segment', 'Break':'Corner point', 'Pit':'Corner point'} self.parameters = { 'radius': VanillaSingleValueParameter('radius', 20, (-200, 200), numType='int'), 'roundness': VanillaSingleValueParameter('roundness', 1.25, (0, 4), numType='float'), 'depth': VanillaSingleValueParameter('depth', 30, (-100, 100), numType='int'), 'breadth': VanillaSingleValueParameter('breadth', 30, (0, 150), numType='int'), 'bottom': VanillaSingleValueParameter('bottom', 5, (0, 40), numType='int') } self.currentMode = 'Break' self.previewGlyph = None self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode) for i, mode in enumerate(self.modes): setattr(self.w, mode, Group((120, 15, -15, -15))) modeGroup = getattr(self.w, mode) modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u'>', callback=self.apply) modeGroup.infoBox = Box((0, 0, -50, 35)) modeGroup.info = TextBox((10, 8, -50, 20), 'No selection') if i > 0: modeGroup.show(False) self.w.Break.radius = ParameterSliderTextInput(self.parameters['radius'], (0, 60, -25, 25), title='Radius', callback=self.makePreviewGlyph) self.w.Break.roundness = ParameterSliderTextInput(self.parameters['roundness'], (0, 95, -25, 25), title='Roundness', callback=self.makePreviewGlyph) self.w.Pit.depth = ParameterSliderTextInput(self.parameters['depth'], (0, 50, -25, 25), title='Depth', callback=self.makePreviewGlyph) self.w.Pit.breadth = ParameterSliderTextInput(self.parameters['breadth'], (0, 80, -25, 25), title='Breadth', callback=self.makePreviewGlyph) self.w.Pit.bottom = ParameterSliderTextInput(self.parameters['bottom'], (0, 110, -25, 25), title='bottom', callback=self.makePreviewGlyph) addObserver(self, 'preview', 'draw') addObserver(self, 'preview', 'drawInactive') addObserver(self, 'previewSolid', 'drawPreview') addObserver(self, 'makePreviewGlyph', 'mouseDown') addObserver(self, 'makePreviewGlyph', 'mouseDragged') addObserver(self, 'makePreviewGlyph', 'keyDown') addObserver(self, 'makePreviewGlyph', 'keyUp') addObserver(self, 'setControls', 'mouseUp') addObserver(self, 'setControls', 'selectAll') addObserver(self, 'setControls', 'deselectAll') addObserver(self, 'setControls', 'currentGlyphChanged') self.w.bind('close', self.windowClose) self.setControls() self.w.open() def changeMode(self, sender): index = sender.get() previousModeGroup = getattr(self.w, self.currentMode) previousModeGroup.show(False) self.currentMode = self.modes[index] modeGroup = getattr(self.w, self.currentMode) modeGroup.show(True) self.setControls() def setControls(self, notification=None): mode = self.currentMode selection = self.getSelection() modeGroup = getattr(self.w, mode) if not len(selection): modeGroup.apply.enable(False) modeGroup.info.set('No selection (%ss)'%(self.objectTypes[mode].lower())) elif len(selection): modeGroup.apply.enable(True) info = '%s valid %s'%(len(selection), self.objectTypes[mode].lower()) if len(selection) > 1: info += 's' modeGroup.info.set(info) self.makePreviewGlyph() def getSelection(self, notification=None): glyph = CurrentGlyph() if len(glyph.selection) == 0: return [] elif len(glyph.selection) > 0: iG = IntelGlyph(glyph) if self.currentMode == 'Build': selection = iG.getSelection(True) elif self.currentMode in ['Break', 'Pit']: selection = [point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi/18)] return selection def preview(self, notification): sc = notification['scale'] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor) def previewSolid(self, notification): sc = notification['scale'] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, plain=True) def makePreviewGlyph(self, sender=None): if (sender is not None) and isinstance(sender, dict): if sender.has_key('notificationName') and sender['notificationName'] == 'mouseDragged': g = sender['glyph'] if not len(g.selection): return self.previewGlyph = self.makeCornerGlyph() UpdateCurrentGlyphView() def makeCornerGlyph(self, sender=None): mode = self.currentMode if mode == 'Build': cornerGlyph = self.buildCorners() elif mode == 'Break': cornerGlyph = self.breakCorners() elif mode == 'Pit': cornerGlyph = self.pitCorners() return cornerGlyph def buildCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) for contour in iG: segments = contour.collectSegments()['selection'] l = len(segments) lines, curves = self.checkComposition(segments) if l > 1 and lines and curves: segments = [segment for segment in segments if len(segment) == 4] elif l > 1 and lines and not curves: segments = segments[:1] + segments[-1:] for segment in reversed(segments): contour.buildCorner(segment) return iG def breakCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) radius = self.parameters['radius'].get() roundness = self.parameters['roundness'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.breakCorner(point, radius, velocity=roundness) contour.correctSmoothness() return iG def pitCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) depth = self.parameters['depth'].get() breadth = self.parameters['breadth'].get() bottom = self.parameters['bottom'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.pitCorner(point, depth, breadth, bottom) contour.removeOverlappingPoints() contour.correctSmoothness() return iG def apply(self, sender): targetGlyph = CurrentGlyph() modifiedGlyph = self.makeCornerGlyph() targetGlyph.prepareUndo('un.round') targetGlyph.clearContours() for p in targetGlyph.selection: p.selected = False pen = targetGlyph.getPointPen() modifiedGlyph.drawPoints(pen) targetGlyph.performUndo() targetGlyph.update() def checkComposition(self, segmentsList): lines = 0 curves = 0 for segment in segmentsList: if len(segment) == 2: lines += 1 elif len(segment) == 4: curves += 1 return lines, curves def windowClose(self, notification): removeObserver(self, 'draw') removeObserver(self, 'drawInactive') removeObserver(self, 'drawPreview') removeObserver(self, 'mouseUp') removeObserver(self, 'mouseDown') removeObserver(self, 'mouseDragged') removeObserver(self, 'keyDown') removeObserver(self, 'keyUp') removeObserver(self, 'selectAll') removeObserver(self, 'deselectAll') removeObserver(self, 'currentGlyphChanged')
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 CornerController: def __init__(self): self.modifiedGlyph = None self.w = FloatingWindow((400, 170), 'Corner Tool') self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor()) self.modes = ['Break', 'Build','Pit'] self.objectTypes = {'Build':'Segment', 'Break':'Corner point', 'Pit':'Corner point'} self.parameters = { 'radius': VanillaSingleValueParameter('radius', 20, (-200, 200), numType='int'), 'roundness': VanillaSingleValueParameter('roundness', 1.25, (0, 4), numType='float'), 'depth': VanillaSingleValueParameter('depth', 30, (-100, 100), numType='int'), 'breadth': VanillaSingleValueParameter('breadth', 30, (0, 150), numType='int'), 'bottom': VanillaSingleValueParameter('bottom', 5, (0, 40), numType='int') } self.currentMode = 'Break' self.previewGlyph = None self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode) for i, mode in enumerate(self.modes): setattr(self.w, mode, Group((120, 15, -15, -15))) modeGroup = getattr(self.w, mode) modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u'>', callback=self.apply) modeGroup.infoBox = Box((0, 0, -50, 35)) modeGroup.info = TextBox((10, 8, -50, 20), 'No selection') if i > 0: modeGroup.show(False) self.w.Break.radius = ParameterSliderTextInput(self.parameters['radius'], (0, 60, -25, 25), title='Radius', callback=self.makePreviewGlyph) self.w.Break.roundness = ParameterSliderTextInput(self.parameters['roundness'], (0, 95, -25, 25), title='Roundness', callback=self.makePreviewGlyph) self.w.Pit.depth = ParameterSliderTextInput(self.parameters['depth'], (0, 50, -25, 25), title='Depth', callback=self.makePreviewGlyph) self.w.Pit.breadth = ParameterSliderTextInput(self.parameters['breadth'], (0, 80, -25, 25), title='Breadth', callback=self.makePreviewGlyph) self.w.Pit.bottom = ParameterSliderTextInput(self.parameters['bottom'], (0, 110, -25, 25), title='bottom', callback=self.makePreviewGlyph) addObserver(self, 'preview', 'draw') addObserver(self, 'preview', 'drawInactive') addObserver(self, 'makePreviewGlyph', 'mouseDown') addObserver(self, 'makePreviewGlyph', 'mouseDragged') addObserver(self, 'makePreviewGlyph', 'keyDown') addObserver(self, 'makePreviewGlyph', 'keyUp') addObserver(self, 'setControls', 'mouseUp') addObserver(self, 'setControls', 'selectAll') addObserver(self, 'setControls', 'deselectAll') addObserver(self, 'setControls', 'currentGlyphChanged') self.w.bind('close', self.windowClose) self.setControls() self.w.open() def changeMode(self, sender): index = sender.get() previousModeGroup = getattr(self.w, self.currentMode) previousModeGroup.show(False) self.currentMode = self.modes[index] modeGroup = getattr(self.w, self.currentMode) modeGroup.show(True) self.setControls() def setControls(self, notification=None): mode = self.currentMode selection = self.getSelection() modeGroup = getattr(self.w, mode) if not len(selection): modeGroup.apply.enable(False) modeGroup.info.set('No selection (%ss)'%(self.objectTypes[mode].lower())) elif len(selection): modeGroup.apply.enable(True) info = '%s valid %s'%(len(selection), self.objectTypes[mode].lower()) if len(selection) > 1: info += 's' modeGroup.info.set(info) self.makePreviewGlyph() def getSelection(self, notification=None): glyph = CurrentGlyph() if len(glyph.selection) == 0: return [] elif len(glyph.selection) > 0: iG = IntelGlyph(glyph) if self.currentMode == 'Build': selection = iG.getSelection(True) elif self.currentMode in ['Break', 'Pit']: selection = [point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi/18)] return selection def preview(self, notification): sc = notification['scale'] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor) def makePreviewGlyph(self, sender=None): if (sender is not None) and isinstance(sender, dict): if sender.has_key('notificationName') and sender['notificationName'] == 'mouseDragged': g = sender['glyph'] if not len(g.selection): return self.previewGlyph = self.makeCornerGlyph() UpdateCurrentGlyphView() def makeCornerGlyph(self, sender=None): mode = self.currentMode if mode == 'Build': cornerGlyph = self.buildCorners() elif mode == 'Break': cornerGlyph = self.breakCorners() elif mode == 'Pit': cornerGlyph = self.pitCorners() return cornerGlyph def buildCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) for contour in iG: segments = contour.collectSegments()['selection'] l = len(segments) lines, curves = self.checkComposition(segments) if l > 1 and lines and curves: segments = [segment for segment in segments if len(segment) == 4] elif l > 1 and lines and not curves: segments = segments[:1] + segments[-1:] for segment in reversed(segments): contour.buildCorner(segment) return iG def breakCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) radius = self.parameters['radius'].get() roundness = self.parameters['roundness'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.breakCorner(point, radius, velocity=roundness) contour.correctSmoothness() return iG def pitCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) depth = self.parameters['depth'].get() breadth = self.parameters['breadth'].get() bottom = self.parameters['bottom'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.pitCorner(point, depth, breadth, bottom) contour.removeOverlappingPoints() contour.correctSmoothness() return iG def apply(self, sender): targetGlyph = CurrentGlyph() modifiedGlyph = self.makeCornerGlyph() targetGlyph.prepareUndo('un.round') targetGlyph.clearContours() for p in targetGlyph.selection: p.selected = False pen = targetGlyph.getPointPen() modifiedGlyph.drawPoints(pen) targetGlyph.performUndo() targetGlyph.update() def checkComposition(self, segmentsList): lines = 0 curves = 0 for segment in segmentsList: if len(segment) == 2: lines += 1 elif len(segment) == 4: curves += 1 return lines, curves def windowClose(self, notification): removeObserver(self, 'draw') removeObserver(self, 'drawInactive') removeObserver(self, 'mouseUp') removeObserver(self, 'mouseDown') removeObserver(self, 'mouseDragged') removeObserver(self, 'keyDown') removeObserver(self, 'keyUp') removeObserver(self, 'selectAll') removeObserver(self, 'deselectAll') removeObserver(self, 'currentGlyphChanged')
class spacingObserver(object): def __init__(self): self.enableGroupSpacing = False self.popupOpen = False addObserver(self, 'glyphEditCallback', 'spaceCenterKeyDown') addObserver(self, 'glyphEditedCallback', 'spaceCenterKeyUp') addObserver(self, 'spaceCenterOpenCallback', 'spaceCenterDidOpen') addObserver(self, 'fontOpenCallback', 'fontDidOpen') self.previousMargins = {'left': 0, 'right': 0} def processMetricsGroups(self, baseGlyph=None): for groupName in self.metricsGroups: if (baseGlyph is None) and len(self.font.groups[groupName]) > 0: baseGlyph = self.font.groups[groupName][0] self.previousMargins['left'] = self.font[ baseGlyph].angledLeftMargin self.previousMargins['right'] = self.font[ baseGlyph].angledRightMargin if (metricsPrefix in groupName) and (baseGlyph in self.font.groups[groupName]): if (leftIndicator in groupName) and ( self.previousMargins['left'] != self.font[baseGlyph].angledLeftMargin): self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Left') elif (rightIndicator in groupName) and ( self.previousMargins['right'] != self.font[baseGlyph].angledRightMargin): self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Right') def setGroupSpacing(self, baseGlyphName, group, side): for glyphName in group: baseGlyph = self.font[baseGlyphName] targetGlyph = self.font[glyphName] if glyphName is not baseGlyphName: if (len(targetGlyph.components) > 0) and (side == 'Left'): for component in targetGlyph.components: if component.baseGlyph in group: component.move((self.previousMargins['left'] - baseGlyph.angledLeftMargin, 0)) self.setSidebearing(baseGlyph, targetGlyph, side) elif glyphName is baseGlyphName: if (len(baseGlyph.components) > 0) and (side == 'Left'): for component in baseGlyph.components: if component.baseGlyph in group: component.move((self.previousMargins['left'] - baseGlyph.angledLeftMargin, 0)) targetGlyph.update() def setSidebearing(self, baseGlyph, targetGlyph, side): baseMargin = getattr(baseGlyph, 'angled' + side + 'Margin') targetMargin = getattr(targetGlyph, 'angled' + side + 'Margin') if targetMargin != baseMargin: setattr(targetGlyph, 'angled' + side + 'Margin', baseMargin) def getMetricsGroups(self, notification=None): self.font = CurrentFont() if self.font is not None: self.metricsGroups = [ group for group in self.font.groups.keys() if metricsPrefix in group and leftIndicator in group or rightIndicator in group ] if (notification is not None) and (self.enableGroupSpacing == True): self.processMetricsGroups() def enableGroupSpacingCallback(self, sender): self.enableGroupSpacing = sender.get() def glyphEditCallback(self, notification): edGlyph = notification['glyph'] self.previousMargins = { 'width': edGlyph.width, 'left': edGlyph.angledLeftMargin, 'right': edGlyph.angledRightMargin } def glyphEditedCallback(self, notification): if self.enableGroupSpacing == True: edGlyph = notification['glyph'] if self.font != CurrentFont(): self.getMetricsGroups() self.processMetricsGroups(edGlyph.name) def spaceCenterOpenCallback(self, notification): if (not self.popupOpen) and (len(self.metricsGroups) > 0): self.w = FloatingWindow((160, 36), 'Group Spacing') self.w.activateGroups = CheckBox( (9, -27, 151, 18), "Activate Group spacing", value=self.enableGroupSpacing, callback=self.enableGroupSpacingCallback, sizeStyle="small") self.w.bind('close', self.windowCloseCallback) self.w.open() self.popupOpen = True def windowCloseCallback(self, notification): self.popupOpen = False def fontOpenCallback(self, notification): font = notification['font'] font.groups.addObserver(self, 'getMetricsGroups', 'Groups.Changed') self.getMetricsGroups(notification)
class KernBot: # Object Globals mtrcUnit = 4 kernUnit = 4 # constructor def __init__(self): w = 300 # window width h = 700 # window height m = 8 # margin spacer s = 0.25 # scale to draw glyphs pb = 100 # padding bottom to space controls lsh = 150 # list height tbh = 24 # text box height cbh = 20 # checkbox height btw = 140 # button width bth = 24 # button height col_1_of_2 = w/2-m-m/2 col_2_of_2 = w/2+m/2 # GSFont obj self.font = Glyphs.font # current Font obj self.selectedMaster = Glyphs.font.selectedFontMaster # current Master obj self.KBtab = Glyphs.font.tabs[0] # current GSEditViewController obj tabs self.actvglyphleft = None self.actvglyphright = None self.SyncGlyphMetrics = True self.initComplete = False # Highly Used Letter in Draw F(x)s self.spaceLetter = self._getCurrentLayerForLetter(cGlyph=self.font.glyphs["space"]) # Vanilla Window Obj self.w = FloatingWindow( (50,100,w,h), "KernBot.io", autosaveName="com.joeygrable.kernbot.ui" ) # Vanilla Element Styles KgValueStyle = dict(alignment="center", sizeStyle="regular") KgLabelStyle = dict(alignment="center", sizeStyle="mini") KgBtnStyles = dict(sizeStyle="regular") # ------------------------------------------------------------------------------------------------- # create Left and Right list of Kerning Groups keys self.w.leftKernTxt = TextBox( (m, m, col_1_of_2, tbh), "Right KERN KEY" ) self.w.rightKernTxt = TextBox( (col_2_of_2, m, -m, tbh), "Left KERN KEY" ) self.w.leftKerns = List( (m, tbh+m, col_1_of_2, lsh), [], selectionCallback=self.updateKernGroupsList ) self.w.rightKerns = List( (col_2_of_2, tbh+m, -m, lsh), [], selectionCallback=self.updateKernGroupsList ) # (below KGroupsList) create Left and Right list of glyphs that share the selected Kerning Group Key self.w.leftGlyphsListTxt = TextBox( (m, tbh+lsh+m*2, col_1_of_2, tbh), "Left GLYPH" ) self.w.leftGlyphsListTxtSub = TextBox( (m, tbh*2+lsh+m*2, col_1_of_2, tbh), "w/ Right Kern Group", sizeStyle="mini" ) self.w.rightGlyphsListTxt = TextBox( (col_2_of_2, tbh+lsh+m*2, -m, tbh), "Right GLYPH" ) self.w.rightGlyphsListTxtSub = TextBox( (col_2_of_2, tbh*2+lsh+m*2, -m, tbh), "w/ Left Kern Group", sizeStyle="mini" ) self.w.leftGlyphsList = List( (m, tbh*3+lsh+m+m/2, col_1_of_2, lsh), [], selectionCallback=self.updateGlyphWithKernGroupList) self.w.rightGlyphsList = List( (col_2_of_2, tbh*3+lsh+m*1.5, -m, lsh), [], selectionCallback=self.updateGlyphWithKernGroupList ) # make kerning groups and cache them in the App --> list of kern groups self.kernGroups = makeKerningGroups( Glyphs.font.glyphs ) # make a cross reference for glyphName --> (kernGroupOnLeft, kernGroupOnRight) self.glyph2Group1, self.glyph2Group2 = glyphToKerningGroups( self.kernGroups ) # ------------------------------------------------------------------------------------------------- # populate List elements with initial font data self.leftKernKeysList = self._getCleanKernKeysAsList( self.glyph2Group1.keys() ) self.leftKernKeysList.sort(key=lambda x:(x.islower(), x)) self.rightKernKeysList = self._getCleanKernKeysAsList( self.glyph2Group2.keys() ) self.rightKernKeysList.sort(key=lambda x:(x.islower(), x)) # ------------------------------------------------------------------------------------------------- # display buttons for outputing kern/glyph strings to screen # divider line self.w.listsHorizontalDvd = HorizontalLine( (m, tbh*3+lsh*2+m*2.5, w-m-m, 1) ) # label text for this section of the app self.w.drawGlyphsActionsLabel = TextBox( (m, tbh*3+lsh*2+m*3.5, w-m-m, tbh), "Draw To Window Actions:" ) # show current letter pair self.w.showCurrentLetterPair = Button( (m, tbh*4+lsh*2+m*3.5, col_1_of_2, bth), "current letter pair", callback=self.drawCurrentLetterPair, **KgBtnStyles ) # label text for possible kern pairs list self.w.allPossibleLetterPairsLabel = TextBox( (m, tbh*5+lsh*2+m*4.25, col_1_of_2, tbh), "all possible letter pairs:", sizeStyle="mini" ) # show all letter pairs with kern pair self.w.showAllLetterPairsWithKerning = List( (m, tbh*5+lsh*2+m*6, col_1_of_2, lsh*0.76), [], selectionCallback=self.drawSelectedAllPossibleLetterPairsWithKerning) # show a randomly selected word from list self.w.showRandomSelectedWordBtn = Button( (col_2_of_2, tbh*4+lsh*2+m*3.5, -m, bth), "show random word", callback=self.drawRandomSelectedMatchingWord ) # label text for matching words list self.w.allMatchingWordsLabel = TextBox( (col_2_of_2, tbh*5+lsh*2+m*4.25, -m, tbh), "all words conaining pair:", sizeStyle="mini" ) # show list of all matching words self.w.showAllMatchingWords = List( (col_2_of_2, tbh*5+lsh*2+m*6, -m, lsh*0.76), [], selectionCallback=self.drawNewSelectedMatchingWord ) # divider line self.w.drawGlyphsActionsHorizontalDvd = HorizontalLine( (m, -pb-m, w-m-m, 1) ) # ------------------------------------------------------------------------------------------------- # display the current selected GLYPHS, their METRIC Keys/Values, and KERNING Pair/Value # show the current left glyph self.w.currentLeftGlyph = TextBox( (0, -pb, w/5, tbh), "H", **KgValueStyle ) self.w.currentLeftGlyphUC = TextBox( (0, -pb+tbh, w/5, tbh), "U+0048", **KgLabelStyle ) self.w.currentLeftGlyphUClabel = TextBox( (0, -pb+tbh*1.8, w/5, tbh), "LEFT GLYPH", **KgLabelStyle ) # divider line self.w.LeftGlyphDvdr = VerticalLine( (w/5, -pb, 1, tbh*2.5) ) # show the current left glyph right metric self.w.currentLeftGlyphRightMetricVal = TextBox( (w/5, -pb, w/5, tbh), "+0", **KgValueStyle ) self.w.currentLeftGlyphRightMetricKey = TextBox( (w/5, -pb+tbh, w/5, tbh), "=[value]", **KgLabelStyle ) self.w.currentLeftGlyphRightMetricLabel = TextBox( (w/5, -pb+tbh*3, w/5, tbh), "RGT MTRC", **KgLabelStyle ) # divider line-------- self.w.LeftGlyphRightMetricDvd = VerticalLine( (w/5+w/5, -pb, 1, tbh*2.5) ) # show the active kern pair self.w.currectKernPairVal = TextBox( (w/5+w/5, -pb, w/5, tbh), "-0", **KgValueStyle ) self.w.currectKernPairKey = TextBox( (w/5+w/5, -pb+tbh, w/5, tbh), "V|V", **KgLabelStyle ) self.w.currectKernPairLabel = TextBox( (w/5+w/5, -pb+tbh*3, w/5, tbh), "KERN PAIR", **KgLabelStyle ) # divider line self.w.RightGlyphLeftMetricDvdr = VerticalLine( (w/5+w/5+w/5, -pb, 1, tbh*2.5) ) # show the current right glyph left metric self.w.currentRightGlyphLeftMetricVal = TextBox( (-w/5-w/5, -pb, w/5, tbh), "+0", **KgValueStyle ) self.w.currentRightGlyphLeftMetricKey = TextBox( (-w/5-w/5, -pb+tbh, w/5, tbh), "=[value]", **KgLabelStyle ) self.w.currentRightGlyphLeftMetricLabel = TextBox( (-w/5-w/5, -pb+tbh*3, w/5, tbh), "LFT MTRC", **KgLabelStyle ) # divider line self.w.RightGlyphDvdr = VerticalLine( (-w/5, -pb, 1, tbh*2.5) ) # show the current right glyph self.w.currentRightGlyph = TextBox( (-w/5, -pb, -0, tbh), "I", **KgValueStyle ) self.w.currentRightGlyphUC = TextBox( (-w/5, -pb+tbh, -0, tbh), "U+0049", **KgLabelStyle ) self.w.currentRightGlyphUClabel = TextBox( (-w/5, -pb+tbh*1.8, -0, tbh), "RIGHT GLYPH", **KgLabelStyle ) # buttons for kerning in steps. Round if kerning is not a multiple of a step. # LEFT Glyph RIGHT Metric Value self.w.LgRmBtnPlus = Button( (w/5+((w/5)/2), -pb+tbh*1.75, (w/5)/2, bth), "+", callback=self.leftGlyphRightMetricPlus, **KgBtnStyles ) self.w.LgRmBtnMinus = Button( (w/5, -pb+tbh*1.75, (w/5)/2, bth), "-", callback=self.leftGlyphRightMetricMinus, **KgBtnStyles ) # LEFT+RIGHT Kerning Pair Value self.w.KernPairBtnPlus = Button( (w/5+w/5+((w/5)/2), -pb+tbh*1.75, (w/5)/2, bth), "+", callback=self.glyphsKerningPairPlus, **KgBtnStyles ) self.w.KernPairBtnMinus = Button( (w/5+w/5, -pb+tbh*1.75, (w/5)/2, bth), "-", callback=self.glyphsKerningPairMinus, **KgBtnStyles ) # RIGHT Glyph LEFT Metric Value self.w.RgLmBtnPlus = Button( (-w/5-w/5+((w/5)/2), -pb+tbh*1.75, (w/5)/2, bth), "+", callback=self.rightGlyphLeftMetricPlus, **KgBtnStyles ) self.w.RgLmBtnMinus = Button( (-w/5-w/5, -pb+tbh*1.75, (w/5)/2, bth), "-", callback=self.rightGlyphLeftMetricMinus, **KgBtnStyles ) # ------------------------------------------------------------------------------------------------- # addCallbacks and listen to CONSTANTS Glyphs.addCallback( self.documentWasSaved, DOCUMENTWASSAVED ) Glyphs.addCallback( self.drawBackground, DRAWBACKGROUND ) # load initial ALL_WORDS dataset self.allMatchingWords = [] self.allPossibleGlyphPairs = [] self.allPossibleLetterPairs = [] self.currentLetterPair = None # run initializing functions self.w.bind("close", self.closeCleanUp) # call if window closes self.updating = False # Flag to avoid recursive Updating # fill the controls with real values (ie. Lists) self.updateAppUI() self.initComplete = True self.w.open() # open window self.w.makeKey() # focus window # ---------------------------------------------------------------------------------------------------- # KERN BOT USER INTERFACE updater # update the app each time the user clicks one of the glyphs list def updateAppUI(self, sender=None): if not self.updating: # only act if not already updating self.updating = True # set updating Flag check # ----- GET LEFT Kern Keys ------------------------------------------------------------------- # fill the left kern list with kern keys (unfiltered) self.w.leftKerns.set( self.leftKernKeysList ) # select top item in list if item not already selected if not self.w.leftKerns.getSelection(): self.w.leftKerns.setSelection([0]) lk_KeySelect = 0 else: lk_KeySelect = self.w.leftKerns.getSelection()[0] # ----- GET LEFT Glyphs w/ key --------------------------------------------------------------- lk_KeyFind = self.w.leftKerns[lk_KeySelect] lk_glyphs = self._getGlyphNamesListFromObj( self._getGlyphsWithKernKey("L", lk_KeyFind) ) lk_glyphs.sort(key=lambda x:(x.islower(), x)) # fill the left glyphs list with glyphs with matching kern key (unfiltered) self.w.leftGlyphsList.set( lk_glyphs ) # select top item in list of item is not already selected if not self.w.leftGlyphsList.getSelection(): self.w.leftGlyphsList.setSelection([0]) leftCharSelect = 0 else: leftCharSelect = int(self.w.leftGlyphsList.getSelection()[0]) # ----- GET RIGHT Kern Keys ------------------------------------------------------------------- # fill the right kern list with kern keys (unfiltered) self.w.rightKerns.set( self.rightKernKeysList ) # select top item in list if item not already selected if not self.w.rightKerns.getSelection(): self.w.rightKerns.setSelection([0]) rk_KeySelect = 0 else: rk_KeySelect = self.w.rightKerns.getSelection()[0] # ----- GET RIGHT Glyphs w/ key --------------------------------------------------------------- rk_KeyFind = self.w.rightKerns[rk_KeySelect] rk_glyphs = self._getGlyphNamesListFromObj( self._getGlyphsWithKernKey("R", rk_KeyFind) ) rk_glyphs.sort(key=lambda x:(x.islower(), x)) # fill the right glyphs list with glyphs with matching kern key (unfiltered) self.w.rightGlyphsList.set( rk_glyphs ) # select top item in list of item is not already selected if not self.w.rightGlyphsList.getSelection(): self.w.rightGlyphsList.setSelection([0]) rightCharSelect = 0 else: rightCharSelect = int(self.w.rightGlyphsList.getSelection()[0]) # ----- LEFT Glyph ---------------------------------------------------------------------------- leftGlyph = self._getGlyphFromCharStr( lk_glyphs[leftCharSelect] ) leftGlyphLayer = leftGlyph.layers[self.selectedMaster.id] leftGlyphName = str(leftGlyph.name) leftGlyphUC = "U+%s" % leftGlyph.unicode leftGlyphRightSB = "+ %d" % leftGlyphLayer.RSB leftGlyphRightMetricKey = str(leftGlyph.rightMetricsKey) if not "None" == str(leftGlyph.rightMetricsKey) else "=%d" % leftGlyphLayer.RSB self.actvGlyphLeft = leftGlyph self.actvLeftKernKey = lk_KeyFind # ----- RIGHT Glyph --------------------------------------------------------------------------- rightGlyph = self._getGlyphFromCharStr( rk_glyphs[rightCharSelect] ) rightGlyphLayer = rightGlyph.layers[self.selectedMaster.id] rightGlyphName = str(rightGlyph.name) rightGlyphUC = "U+%s" % rightGlyph.unicode rightGlyphLeftSB = "+ %d" % rightGlyphLayer.LSB rightGlyphLeftMetricKey = str(rightGlyph.leftMetricsKey) if not "None" == str(rightGlyph.leftMetricsKey) else "=%d" % rightGlyphLayer.LSB self.actvGlyphRight = rightGlyph self.actvRightKernKey = rk_KeyFind # ----- KERNING PAIR Information -------------------------------------------------------------- kernPairKey = "%s | %s" % (lk_KeyFind, rk_KeyFind) kernAmt = Glyphs.font.kerningForPair( self.selectedMaster.id, "@MMK_L_%s" % lk_KeyFind, "@MMK_R_%s" % rk_KeyFind ) if kernAmt > 100: kernAmt = 0 if kernAmt > 0: kernPairVal = "+ %d" % kernAmt elif kernAmt == 0: kernPairVal = "= %d" % kernAmt elif kernAmt < 0: kernPairVal = "- %s" % str(kernAmt)[1:] # ----- SET Glyphs Metric & Kerning Information ----------------------------------------------- # LEFT Glyph self.w.currentLeftGlyph.set( leftGlyphName ) self.w.currentLeftGlyphUC.set( leftGlyphUC ) self.w.currentLeftGlyphRightMetricVal.set( leftGlyphRightSB ) self.w.currentLeftGlyphRightMetricKey.set( leftGlyphRightMetricKey ) # LEFT+RIGHT Kerning self.w.currectKernPairVal.set( kernPairVal ) self.w.currectKernPairKey.set( kernPairKey ) # RIGHT Glyph self.w.currentRightGlyph.set( rightGlyphName ) self.w.currentRightGlyphUC.set( rightGlyphUC ) self.w.currentRightGlyphLeftMetricVal.set( rightGlyphLeftSB ) self.w.currentRightGlyphLeftMetricKey.set( rightGlyphLeftMetricKey ) # ----- UPDATE DATA SETS ---------------------------------------------------------------------- # if initComplete = False, first time running updater if not self.initComplete: # set current letter pair self.currentLetterPair = leftGlyphName+rightGlyphName # set all possible glyph pairs self.allPossibleGlyphPairs = self._determineAllPossibleLetterPairsWithKerning() # calculate initial matching words list self.allMatchingWords = self._determineMatchingWordsContaining( leftGlyphName, rightGlyphName ) # not our first rodeo in the updater else: # ONLY RERUN MATCHING WORDS DETERMINER IF THE LETTER PAIR CHANGES # check if pair changed oldPair = self.currentLetterPair newPair = leftGlyphName+rightGlyphName # check if either letter in pair changed if not oldPair == newPair: # set new current letter pair self.currentLetterPair = newPair # set all possible glyph pairs self.allPossibleGlyphPairs = self._determineAllPossibleLetterPairsWithKerning() # recalc all matching words list self.allMatchingWords = self._determineMatchingWordsContaining( leftGlyphName, rightGlyphName ) # ----- POPULATE ALL POSSIBLE LETTER PAIRS ---------------------------------------------------- # check to see if all possble pairs already populated if not self.allPossibleGlyphPairs: self.allPossibleGlyphPairs = self._determineAllPossibleLetterPairsWithKerning() # make sure there's at least one possible letter pair else: # get a list of the glyph pairs and show in list self.allPossibleLetterPairs = self._getCleanPossibleGlyphPairs( self.allPossibleGlyphPairs ) self.w.showAllLetterPairsWithKerning.set( self.allPossibleLetterPairs ) # ----- POPULATE ALL MATCHING WORDS WITH LETTER PAIR ------------------------------------------ # check to see if we've already searched for all matching words if not self.allMatchingWords: self.allMatchingWords = self._determineMatchingWordsContaining( leftGlyphName, rightGlyphName ) # make sure there are at least some words to show else: # fill the list of matching words (unfiltered) self.w.showAllMatchingWords.set( self.allMatchingWords ) # ----- Redraw & Finish Glyphs App updating --------------------------------------------------- Glyphs.redraw() # set Flag update complete self.updating = False # ----------------------------------------------------------------------------------------------------- # CALLBACKS: GENERAL APP callbacks # every time the Glyphs App saves def documentWasSaved(self, passedobject): # update app for now self.updateAppUI() # every time the Glyphs App tries to reload def drawBackground(self, layer, info): # pass for now pass # when the window closes, unsubscribe from events def closeCleanUp(self, sender): Glyphs.removeCallback( self.documentWasSaved, DOCUMENTWASSAVED ) Glyphs.removeCallback( self.drawBackground, DRAWBACKGROUND ) # ----------------------------------------------------------------------------------------------------- # CALLBACKS: UPDATE TAB to display strings of letters # open new tab with current letter pair def drawCurrentLetterPair(self, sender): # first make sure the UI state is current self.updateAppUI() # get layer of current glyphs currentLeftLetter = self._getCurrentLayerForLetter(side="left") currentRightLetter = self._getCurrentLayerForLetter(side="right") # make list of all letters to draw allLetters = [currentLeftLetter, currentRightLetter] # draw letters to current tab open self._drawGlyphLayersToScreen( allLetters ) # open new tab with current letter pair in a WORD def drawNewSelectedMatchingWord(self, sender): # first make sure the UI state is current self.updateAppUI() # pull from all matching words list if self.allMatchingWords: # all letters to draw allLetters = [] selectedWordLetters = None selectionMax = 5 # select top item in list if item not already selected if not sender.getSelection(): sender.setSelection([0]) possibleMatchesSelection = [0] else: possibleMatchesSelection = sender.getSelection()[:selectionMax] # multiple selection if len(possibleMatchesSelection) > 1: # get subset of selected words for sWordIndex in possibleMatchesSelection: selectedWord = self.allMatchingWords[ sWordIndex ] selectedWordLetters = self._getWordLettersToDraw( selectedWord ) # add all word letters to end of letters list, then add space allLetters += selectedWordLetters allLetters += [self.spaceLetter] # single selection else: # get the word selected to be drawn selectedWord = self.allMatchingWords[ possibleMatchesSelection[0] ] selectedWordLetters = self._getWordLettersToDraw( selectedWord ) # add all word letters to end of letters list, then add space allLetters += selectedWordLetters allLetters += [self.spaceLetter] # draw letters to current tab open self._drawGlyphLayersToScreen( allLetters ) # get a random word from list of all matching words and draw to tab def drawRandomSelectedMatchingWord(self, sender): # first make sure the UI state is current self.updateAppUI() # pull from all matching words list if self.allMatchingWords: # randomly get one word out of list selectedWord = random.choice(self.allMatchingWords) selectedWordLetters = self._getWordLettersToDraw(selectedWord) # draw letters to current tab open self._drawGlyphLayersToScreen( selectedWordLetters ) # get & set cursor position to position of letter pair in word cursorPos = self._getCursorPositionOfPair( selectedWord ) self._setCursorPositionOfPair(cursorPos) # open new tab with all possible letter combinations that use the current kerning pair def drawSelectedAllPossibleLetterPairsWithKerning(self, sender): # first make sure the UI state is current self.updateAppUI() # pull from all possible glyph pairs if self.allPossibleGlyphPairs: # all letters to draw allLetters = [] # select top item in list if item not already selected if not sender.getSelection(): sender.setSelection([0]) possiblePairsSelection = [0] else: possiblePairsSelection = sender.getSelection() # multiple selection if len(possiblePairsSelection) > 1: # loop through each selected pair multipleGlyphPairs = self.allPossibleGlyphPairs[ possiblePairsSelection[0]:possiblePairsSelection[-1]+1 ] for gPair in multipleGlyphPairs: # add pair and space to all letters allLetters.append( self._getCurrentLayerForLetter(cGlyph=gPair[0]) ) allLetters.append( self._getCurrentLayerForLetter(cGlyph=gPair[1]) ) allLetters.append( self.spaceLetter ) # single selection else: # get current pair glyph info snglPair = self.allPossibleGlyphPairs[ possiblePairsSelection[0] ] allLetters.append( self._getCurrentLayerForLetter(cGlyph=snglPair[0]) ) allLetters.append( self._getCurrentLayerForLetter(cGlyph=snglPair[1]) ) allLetters.append( self.spaceLetter ) # draw letters to current tab open self._drawGlyphLayersToScreen( allLetters ) # ----------------------------------------------------------------------------------------------------- # KERN GROUP & GLYPHS ——> callback functions for Lists, Buttons, and TextBox (oh my!) # every time either kern group list changes def updateKernGroupsList(self, sender): # update App UI self.updateAppUI() # every time the glyph w/ kern group list changes def updateGlyphWithKernGroupList(self, sender): # update App UI self.updateAppUI() # LEFT GLYPH decrease right metric multiplier def leftGlyphRightMetricMinus(self, sender): self._adjustLeftGlyphRightMetric(direction="-") self.updateAppUI() # LEFT GLYPH increase right metric multiplier def leftGlyphRightMetricPlus(self, sender): self._adjustLeftGlyphRightMetric(direction="+") self.updateAppUI() # RIGHT GLYPH decrease left metric multiplier def rightGlyphLeftMetricMinus(self, sender): self._adjustRightGlyphLeftMetric(direction="-") self.updateAppUI() # RIGHT GLYPH increase left metric multiplier def rightGlyphLeftMetricPlus(self, sender): self._adjustRightGlyphLeftMetric(direction="+") self.updateAppUI() # LEFT+RIGHT GLYPH decrease kerning for pair def glyphsKerningPairMinus(self, sender): self._adjustKerningForPair(direction="-") self.updateAppUI() # LEFT+RIGHT GLYPH increase kerning for pair def glyphsKerningPairPlus(self, sender): self._adjustKerningForPair(direction="+") self.updateAppUI() # ----------------------------------------------------------------------------------------------------- # METRIC & KERNING METHODS ––> sets new glyph value # ADJUST left glyph right metric +/- def _adjustLeftGlyphRightMetric(self, direction="-"): # get left glyph layer information leftGlyphLayer = self.actvGlyphLeft.layers[self.selectedMaster.id] leftGlyphRMK = str(self.actvGlyphLeft.rightMetricsKey) if not None == self.actvGlyphLeft.rightMetricsKey else leftGlyphLayer.RSB # if is a mltp of another glyph if type(leftGlyphRMK) == str: # check metric key has mltp attached if "*" in leftGlyphRMK: leftGlyphMMltp = float(leftGlyphRMK.rsplit("*", 1)[-1]) else: leftGlyphMMltp = 1.0 # INCREASE OR DECREASE if direction == "-": NEWleftGlyphMMltp = leftGlyphMMltp - 0.05 elif direction == "+": NEWleftGlyphMMltp = leftGlyphMMltp + 0.05 NEWleftGlyphRMK = unicode((leftGlyphRMK.rsplit("*", 1)[0] + "*" + str(NEWleftGlyphMMltp))[1:], "UTF-8") # set new RMK for Glyph self.actvGlyphLeft.rightMetricsKey = self.actvGlyphLeft.rightMetricsKey.replace( leftGlyphRMK[1:], NEWleftGlyphRMK ) # if is a multiple of the metric spacing unit elif type(leftGlyphRMK) == float: # INCREASE OR DECREASE if direction == "-": # set new RSB for Glyph leftGlyphLayer.RSB = leftGlyphLayer.RSB - self.mtrcUnit elif direction == "+": # set new RSB for Glyph leftGlyphLayer.RSB = leftGlyphLayer.RSB + self.mtrcUnit # sync metrics if self.SyncGlyphMetrics: for thisLeftGlyphLayer in self.actvGlyphLeft.layers: thisLeftGlyphLayer.syncMetrics() # ADJUST right glyph left metric +/- def _adjustRightGlyphLeftMetric(self, direction="-"): # get right glyph layer information rightGlyphLayer = self.actvGlyphRight.layers[self.selectedMaster.id] rightGlyphLMK = str(self.actvGlyphRight.leftMetricsKey) if not None == self.actvGlyphRight.leftMetricsKey else rightGlyphLayer.LSB # if is a mltp of another glyph if type(rightGlyphLMK) == str: # check metric key has mltp attached if "*" in rightGlyphLMK: rightGlyphMMltp = float(rightGlyphLMK.rsplit("*", 1)[-1]) else: rightGlyphMMltp = 1.0 # INCREASE OR DECREASE if direction == "-": NEWrightGlyphMMltp = rightGlyphMMltp - 0.05 elif direction == "+": NEWrightGlyphMMltp = rightGlyphMMltp + 0.05 NEWrightGlyphLMK = unicode((rightGlyphLMK.rsplit("*", 1)[0] + "*" + str(NEWrightGlyphMMltp))[1:], "UTF-8") # set new LMK for Glyph self.actvGlyphRight.leftMetricsKey = self.actvGlyphRight.leftMetricsKey.replace( rightGlyphLMK[1:], NEWrightGlyphLMK ) # if is a multiple of the metric spacing unit elif type(rightGlyphLMK) == float: # INCREASE OR DECREASE if direction == "-": # set new LSB for Glyph rightGlyphLayer.LSB = rightGlyphLayer.LSB - self.mtrcUnit elif direction == "+": # set new LSB for Glyph rightGlyphLayer.LSB = rightGlyphLayer.LSB + self.mtrcUnit # sync metrics if self.SyncGlyphMetrics: for thisRightGlyphLayer in self.actvGlyphRight.layers: thisRightGlyphLayer.syncMetrics() # ADJUST kerning value for right+left glyph pair +/- def _adjustKerningForPair(self, direction="-"): # set kern direction if direction == "-": kdVal = -1 elif direction == "+": kdVal = 1 # get current kerning kpVal = Glyphs.font.kerningForPair( self.selectedMaster.id, "@MMK_L_%s" % self.actvLeftKernKey, "@MMK_R_%s" % self.actvRightKernKey ) if kpVal > 100: kpVal = -1 # calculate the next kernunit interval +/- NEWkpVal = float(int(round(kpVal/self.kernUnit + kdVal ))*self.kernUnit) # set new value for kern pair Glyphs.font.setKerningForPair( self.selectedMaster.id, "@MMK_L_%s" % self.actvLeftKernKey, "@MMK_R_%s" % self.actvRightKernKey, NEWkpVal ) # ----------------------------------------------------------------------------------------------------- # UTILITY METHODS ——> return something # returns a list of all glyphs that has the desired kerning group on the specified side def _getGlyphsWithKernKey(self, side, searchForKey): for kGroup in self.kernGroups.keys(): findMMK = "@MMK_"+side+"_"+searchForKey if findMMK in kGroup: return self.kernGroups[kGroup] # returns the kern key letter as a string (from a unicode u"@MMK_L_v" string ——> "v") def _getCleanKernKeysAsList(self, uListItems): cleanList = [] for lItem in uListItems: cleanList.append( str(lItem.rsplit("_", 1)[-1]) ) return cleanList # returns a list of letter-pairs give a list of glyph pairs def _getCleanPossibleGlyphPairs(self, gListItems): cleanList = [] for gItem in gListItems: cleanList.append( str(gItem[0].name+gItem[1].name) ) return cleanList # returns s list of letter names given a list of GSGlyph obj def _getGlyphNamesListFromObj(self, gListItems): cleanList = [] for gItem in gListItems: cleanList.append( str(gItem.name) ) return cleanList # returns a single glyph Obj given a specific letter def _getGlyphFromCharStr(self, letter): for glyph in Glyphs.font.glyphs: if glyph.name == letter: return glyph # returns the current layer of the active left glyph def _getCurrentLayerForLetter(self, side="left", cGlyph=None): # if want a specific glyph if not None == cGlyph: cGlyphLayer = cGlyph.layers[ self.selectedMaster.id ] else: # default get the current active left glyph if side == "left": cGlyphLayer = self.actvGlyphLeft.layers[ self.selectedMaster.id ] # otherwise get the current active right glyph elif side == "right": cGlyphLayer = self.actvGlyphRight.layers[ self.selectedMaster.id ] # return the letter return cGlyphLayer # search for a word in ALL_WORDS list containing chars Left and Right def _determineMatchingWordsContaining(self, cLeft, cRight): # get a word list that matches the possible word cases L_CAP = cLeft.isupper() R_CAP = cRight.isupper() # get list of found words foundWords = [] # CASE #1: CAP-CAP word -------------------------------------- if L_CAP and R_CAP: # find any matching word findString = str( (cLeft+cRight).lower() ) for cWord in ALL_WORDS: if findString in cWord: # make entire work uppercase # add word to found list foundWords.append( cWord.upper() ) # CASE #2: CAP-lower word ------------------------------------ elif L_CAP and not R_CAP: # find any first car is CAP and second car is lower for cWord in ALL_WORDS: check1 = str(cLeft.lower()) check2 = str(cRight) # word must have at least two characters if len(cWord) > 2: if cWord[0] == check1 and cWord[1] == check2: # convert first letter in every word to CAP cWordCap1 = str(cWord).capitalize() # add word to found list foundWords.append( cWordCap1 ) # CASE #3: lower-CAP word ------------------------------------ elif not L_CAP and R_CAP: # find two matching words findLeft = str(cLeft).lower() findRight = str(cRight).lower() wordSet1 = [] wordSet2 = [] for cWord in ALL_WORDS: # word must have at least two characters if len(cWord) > 2: # word1 match if last char match cLeft if cWord[-1] == findLeft: wordSet1.append( cWord ) # word2 match if first char match cRight if cWord[0] == findRight: wordSet2.append( cWord ) # check both matches exist if wordSet1 and wordSet2: # make NEW word camelCase ——> "camel"+"Case" = word1 + word2.uppercase() newWord = str(random.choice(wordSet1)) + str(random.choice(wordSet2)).capitalize() foundWords.append( newWord ) # CASE #4: lower-lower word ---------------------------------- elif not L_CAP and not R_CAP: # find any matching word findString = str(cLeft+cRight) for cWord in ALL_WORDS: if findString in cWord: # add word to found list foundWords.append( cWord.upper() ) # CASE #ERROR: improper C1 or C2 values else: print("improper C1 or C2 values") # return random word that matches input return foundWords # iterate through list of glyph names in word and # return list of all letters glyph layer to draw def _getWordLettersToDraw(self, inputWord): # format all chars glyphs in word allChars = [] for currentChar in inputWord: allChars.append( self.font.glyphs[currentChar] ) # get all letter glyph layers to draw allLetters = [] for cGlyph in allChars: # get current glyph layer currentLetter = self._getCurrentLayerForLetter(cGlyph=cGlyph) allLetters.append( currentLetter ) # return the list of all letter layers to draw return allLetters # return a list of all possible combinations of the current left/right kern key pair def _determineAllPossibleLetterPairsWithKerning(self): # gather char information allLeftChars = self._getGlyphNamesListFromObj( self._getGlyphsWithKernKey("L", self.actvLeftKernKey) ) allLeftChars.sort(key=lambda x:(x.islower(), x)) allRightChars = self._getGlyphNamesListFromObj( self._getGlyphsWithKernKey("R", self.actvRightKernKey) ) allRightChars.sort(key=lambda x:(x.islower(), x)) # make list of all possible chars allCombinations = [] for leftChar in allLeftChars: for rightChar in allRightChars: allCombinations.append( (self.font.glyphs[leftChar], self.font.glyphs[rightChar]) ) # return list of all pairs return allCombinations # ----------------------------------------------------------------------------------------------------- # DRAW METHODS ——> does something with the GlyphsApp EditViewController # draw a list of GSLayer objs with the current active KernBot TAB def _drawGlyphLayersToScreen(self, allGlyphs): # empty tab to display new string self.KBtab.layers = [] # loop all chars in string for gChar in allGlyphs: # add char to tab string self.KBtab.layers.append( gChar ) # returns an index postiont between the current letter pairs within a given word def _getCursorPositionOfPair(self, word): foundCharsIndex = word.find(self.currentLetterPair) return foundCharsIndex+1 # sets current Glyphs App tab cursor position with a given index position def _setCursorPositionOfPair(self, posIndex): Glyphs.font.currentTab.textCursor = posIndex return True
class AdjustAnchors(BaseWindowController): def __init__(self): self.font = CurrentFont() self.glyph = CurrentGlyph() self.upm = self.font.info.unitsPerEm self.glyphPreviewCacheDict = {} # key: glyph name -- value: list containing assembled glyphs self.anchorsOnMarksDict = {} # key: anchor name -- value: list of mark glyph names self.anchorsOnBasesDict = {} # key: anchor name -- value: list of base glyph names self.marksDict = {} # key: mark glyph name -- value: anchor name (NOTE: It's expected that each mark glyph only has one type of anchor) self.fillAnchorsAndMarksDicts() self.glyphNamesList = [] # list of glyph names that will be displayed in the UI list self.selectedGlyphNamesList = [] # list of glyph names selected in the UI list self.extraGlyphsList = [] # list of the glyph objects that should be inserted before and after the accented glyphs self.Blue, self.Alpha = 1, 0.6 self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") addObserver(self, "_fontWillClose", "fontWillClose") addObserver(self, "_currentFontChanged", "fontResignCurrent") addObserver(self, "_currentGlyphChanged", "currentGlyphChanged") addObserver(self, "_drawFill", "draw") addObserver(self, "_drawFill", "drawInactive") addObserver(self, "_previewFill", "drawPreview") addObserver(self, "_drawGlyphs", "draw") # observer for the draw event addObserver(self, "_drawGlyphs", "drawInactive") # draw the glyphs when the glyph window is not in focus addObserver(self, "_drawGlyphs", "drawPreview") integerNumFormatter = NSNumberFormatter.alloc().init() integerNumFormatter.setAllowsFloats_(False) integerNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init() intPosMinZeroNumFormatter.setAllowsFloats_(False) intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0)) intPosMinOneNumFormatter = NSNumberFormatter.alloc().init() intPosMinOneNumFormatter.setAllowsFloats_(False) intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1)) self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize")) if not self.textSize: self.textSize = 150 self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight")) if not self.lineHeight: self.lineHeight = 200 self.extraSidebearings = getExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings")) if not self.extraSidebearings: self.extraSidebearings = [0, 0] self.extraGlyphs = getExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs")) if not self.extraGlyphs: self.extraGlyphs = '' posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize")) if not posSize: posSize = (100, 100, 1200, 400) self.calibrateMode = getExtensionDefault("%s.%s" % (extensionKey, "calibrateMode")) if not self.calibrateMode: self.calibrateMode = False calibrateModeStrings = getExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings")) if not calibrateModeStrings: calibrateModeStrings = { 'group1.baseInput': 'dotlessi o s', 'group1.markInput': 'dieresis circumflex macron breve caron', 'group2.baseInput': 'I O S', 'group2.markInput': 'dieresis.cap circumflex.cap macron.cap breve.cap caron.cap', 'group3.baseInput': 'I.sc O.sc S.sc', 'group3.markInput': 'dieresis circumflex macron breve caron', 'group4.baseInput': '', 'group4.markInput': '', } # -- Window -- self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400)) self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback) if roboFontVersion < '1.7': self.w.fontList.getNSTableView().sizeToFit() # use the full width of the column self.w.fontList.show(not self.calibrateMode) self.w.lineView = MultiLineView((210, 10, -10, -41), pointSize = self.textSize, lineHeight = self.lineHeight, displayOptions={"Beam" : False, "displayMode" : "Multi Line"} ) # -- Calibration Mode -- baseLabel = "Bases" markLabel = "Marks" width, height = 190, 140 self.cm = Group((0, 0, 0, 0)) # --- self.cm.group1 = Group((5, height*0, width, height-10)) self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group1.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group1.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group2 = Group((5, height*1, width, height-10)) self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group2.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group2.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group3 = Group((5, height*2, width, height-10)) self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group3.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group3.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group4 = Group((5, height*3, width, height-10)) self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group4.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group4.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False) # --- view = DefconAppKitTopAnchoredNSView.alloc().init() view.addSubview_(self.cm.getNSView()) view.setFrame_(((0, 0), (width+10, height*4-23))) self.cm.setPosSize((0, 0, width+10, height*4-22)) self.w.scrollView = ScrollView((5, 10, width+10, -41), view, drawsBackground=False, hasHorizontalScroller=False) self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder) self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) # NSScrollElasticityNone self.w.scrollView.show(self.calibrateMode) # -- Footer -- self.w.calibrateModeCheck = CheckBox((10, -32, 200, -10), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode) self.w.textSizeLabel = TextBox((210, -30, 100, -10), "Text Size") self.w.textSize = EditText((270, -32, 35, -10), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter) self.w.lineHeightLabel = TextBox((320, -30, 100, -10), "Line Height") self.w.lineHeight = EditText((395, -32, 35, -10), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter) self.w.extraSidebearingsLabel = TextBox((446, -30, 180, -10), "Extra Sidebearings") self.w.extraSidebearingsChar = TextBox((602, -30, 20, -10), "&") self.w.extraSidebearingLeft = EditText((567, -32, 35, -10), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.extraSidebearingRight = EditText((614, -32, 35, -10), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.extraGlyphsLabel = TextBox((665, -30, 180, -10), "Extra Glyphs") self.w.extraGlyphs = EditText((749, -32, -10, -10), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False) # trigger the initial state and contents of the window self.extraGlyphsCallback() # this will call self.updateExtensionWindow() self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def calibrateModeCallback(self, sender): self.calibrateMode = not self.calibrateMode self.w.fontList.show(not sender.get()) self.w.scrollView.show(self.calibrateMode) self.updateExtensionWindow() def textSizeCallback(self, sender): try: # in case the user submits an empty field self.textSize = int(sender.get()) except: # reset to the previous value NSBeep() self.sender.set(self.textSize) self.w.lineView.setPointSize(self.textSize) def lineHeightCallback(self, sender): try: self.lineHeight = int(sender.get()) except: NSBeep() self.sender.set(self.lineHeight) self.w.lineView.setLineHeight(self.lineHeight) def extraSidebearingsCallback(self, sender): left = self.w.extraSidebearingLeft right = self.w.extraSidebearingRight try: self.extraSidebearings = [int(left.get()), int(right.get())] except: NSBeep() left.set(self.extraSidebearings[0]) right.set(self.extraSidebearings[1]) self.extraGlyphsCallback() # this will call self.updateExtensionWindow() def extraGlyphsCallback(self, *sender): del self.extraGlyphsList[:] # empty the list self.extraGlyphs = self.w.extraGlyphs.get() glyphNamesList = self.extraGlyphs.split() for gName in glyphNamesList: try: extraGlyph = self.font[gName] # must create a new glyph in order to be able to increase the sidebearings without modifying the font newGlyph = RGlyph() newGlyph.setParent(self.font) # must use deepAppend because the extra glyph may have components (which will cause problems to the MultiLineView) newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph) newGlyph.width = extraGlyph.width except RoboFontError: continue newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] self.extraGlyphsList.append(newGlyph) self.glyphPreviewCacheDict.clear() self.updateExtensionWindow() def windowClose(self, sender): self.font.naked().removeObserver(self, "Font.Changed") removeObserver(self, "fontWillClose") removeObserver(self, "fontResignCurrent") removeObserver(self, "currentGlyphChanged") removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") self.saveExtensionDefaults() def getCalibrateModeStrings(self): calibrateModeStringsDict = {} for i in range(1,5): group = getattr(self.cm, "group%d" % i) calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get() calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get() return calibrateModeStringsDict def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize()) setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize) setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight) setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings) setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs) setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode) setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings()) def _previewFill(self, info): self.Blue, self.Alpha = 0, 1 def _drawFill(self, info): self.Blue, self.Alpha = 1, 0.6 def _fontWillClose(self, info): """ Close the window when the last font is closed """ if len(AllFonts()) < 2: self.windowClose(self) self.w.close() def _currentFontChanged(self, info): self.font.naked().removeObserver(self, "Font.Changed") self.font = CurrentFont() self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def _currentGlyphChanged(self, info): self.updateExtensionWindow() def fontWasModified(self, info): OutputWindow().clear() self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def deepAppendGlyph(self, glyph, gToAppend, offset=(0,0)): if not gToAppend.components: glyph.appendGlyph(gToAppend, offset) else: for component in gToAppend.components: compGlyph = self.font[component.baseGlyph].copy() # handle component transformations componentTransformation = component.transformation # when undoing a paste anchor or a delete anchor action, RoboFont returns component.transformation as a list instead of a tuple if type(componentTransformation) is list: componentTransformation = tuple(componentTransformation) if componentTransformation != (1, 0, 0, 1, 0, 0): # if component is skewed and/or is shifted matrix = componentTransformation[0:4] if matrix != (1, 0, 0, 1): # if component is skewed transformObj = Identity.transform(matrix + (0, 0)) # ignore the original component's shifting values compGlyph.transform(transformObj) glyph.appendGlyph(compGlyph, map(sum, zip(component.offset, offset))) # add the two tuples of offset for contour in gToAppend: glyph.appendContour(contour, offset) # if the assembled glyph still has components, recursively remove and replace them 1-by-1 by the glyphs they reference if glyph.components: nestedComponent = glyph.components[-1] # start from the end glyph.removeComponent(nestedComponent) glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset) return glyph def updateCalibrateMode(self, *sender): glyphsList = [] newLine = self.w.lineView.createNewLineGlyph() # cycle thru the UI Groups and collect the strings for i in range(1,5): group = getattr(self.cm, "group%d" % i) baseGlyphsNamesList = group.baseInput.get().split() markGlyphsNamesList = group.markInput.get().split() # iterate thru the base+mark combinations for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList): newGlyph = RGlyph() newGlyph.setParent(self.font) # skip invalid glyph names try: baseGlyph = self.font[gBaseName] markGlyph = self.font[gMarkName] except RoboFontError: continue # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph)) # set the advanced width dfltSidebearings = self.upm * .05 # 5% of the UPM newGlyph.leftMargin = dfltSidebearings + self.extraSidebearings[0] newGlyph.rightMargin = dfltSidebearings + self.extraSidebearings[1] # append the assembled glyph to the list glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) # add line break, if both input fields have content if baseGlyphsNamesList and markGlyphsNamesList: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newLine) # update the contents of the MultiLineView self.w.lineView.set(glyphsList) def updateExtensionWindow(self): if self.calibrateMode: self.updateCalibrateMode() return if CurrentGlyph() is not None: # NOTE: CurrentGlyph() will return zero (its length), so "is not None" is necessary self.glyph = CurrentGlyph() self.glyphNamesList = self.makeGlyphNamesList(self.glyph) self.updateListView() currentGlyphName = self.glyph.name # base glyph + accent combinations preview # first check if there's a cached glyph if currentGlyphName in self.glyphPreviewCacheDict: self.w.lineView.set(self.glyphPreviewCacheDict[currentGlyphName]) # assemble the glyphs else: glyphsList = [] for glyphNameInUIList in self.glyphNamesList: newGlyph = RGlyph() newGlyph.setParent(self.font) # the glyph in the UI list is a mark if glyphNameInUIList in self.marksDict: markGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph)) # set the advanced width if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width newGlyph.leftMargin = self.upm * .05 # 5% of the UPM newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = self.glyph.width # the glyph in the UI list is a base else: baseGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph)) # set the advanced width if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width newGlyph.leftMargin = self.upm * .05 newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = baseGlyph.width # pad the new glyph if it has too much overhang if newGlyph.leftMargin < self.upm * .15: newGlyph.leftMargin = self.upm * .05 if newGlyph.rightMargin < self.upm * .15: newGlyph.rightMargin = self.upm * .05 # add extra sidebearings newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] # one last check for making sure the new glyph can be displayed if not newGlyph.components: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) else: print "Combination with mark glyph %s can't be previewed because it contains component %s." % (glyphNameInUIList, newGlyph.components[0].baseGlyph) glyphsList.extend(self.extraGlyphsList) self.w.lineView.set(glyphsList) # add to the cache self.glyphPreviewCacheDict[currentGlyphName] = glyphsList else: self.w.lineView.set([]) def listSelectionCallback(self, sender): selectedGlyphNamesList = [] for index in sender.getSelection(): selectedGlyphNamesList.append(self.glyphNamesList[index]) self.selectedGlyphNamesList = selectedGlyphNamesList self.updateGlyphView() def updateGlyphView(self): # update the current glyph view UpdateCurrentGlyphView() def fillAnchorsAndMarksDicts(self): # reset all the dicts self.glyphPreviewCacheDict.clear() self.anchorsOnMarksDict.clear() self.anchorsOnBasesDict.clear() self.marksDict.clear() markGlyphsWithMoreThanOneAnchorTypeList = [] for glyphName in self.font.glyphOrder: glyphAnchorsList = self.font[glyphName].anchors for anchor in glyphAnchorsList: if anchor.name[0] == '_': anchorName = anchor.name[1:] # add to AnchorsOnMarks dictionary if anchorName not in self.anchorsOnMarksDict: self.anchorsOnMarksDict[anchorName] = [glyphName] else: tempList = self.anchorsOnMarksDict[anchorName] tempList.append(glyphName) self.anchorsOnMarksDict[anchorName] = tempList # add to Marks dictionary if glyphName not in self.marksDict: self.marksDict[glyphName] = anchorName else: if glyphName not in markGlyphsWithMoreThanOneAnchorTypeList: markGlyphsWithMoreThanOneAnchorTypeList.append(glyphName) else: anchorName = anchor.name # add to AnchorsOnBases dictionary if anchorName not in self.anchorsOnBasesDict: self.anchorsOnBasesDict[anchorName] = [glyphName] else: tempList = self.anchorsOnBasesDict[anchorName] tempList.append(glyphName) self.anchorsOnBasesDict[anchorName] = tempList if markGlyphsWithMoreThanOneAnchorTypeList: for glyphName in markGlyphsWithMoreThanOneAnchorTypeList: print "ERROR: Glyph %s has more than one type of anchor." % glyphName def makeGlyphNamesList(self, glyph): glyphNamesList = [] markGlyphIsAbleToBeBase = False if glyph is not None: # NOTE: "if glyph" will return zero (its length), so "is not None" is necessary # assemble the list for the UI list for anchor in glyph.anchors: anchorName = anchor.name if anchorName in self.anchorsOnMarksDict: glyphNamesList.extend(self.anchorsOnMarksDict[anchorName]) elif anchorName[1:] in self.anchorsOnBasesDict: # skips the leading underscore glyphNamesList.extend(self.anchorsOnBasesDict[anchorName[1:]]) # for mark glyphs, test if they're able to get other mark glyphs attached to them # this will (correctly) prevent the UI list from including glyph names that cannot be displayed with the current glyph if glyph.name in self.marksDict: for anchor in glyph.anchors: if anchor.name[0] != '_': # the current mark glyph has anchors that allow it to be a base for other marks markGlyphIsAbleToBeBase = True break # remove marks from the glyph list if the current mark glyph can't work as a base if not markGlyphIsAbleToBeBase: for glyphName in glyphNamesList[::-1]: # iterate from the end of the list if glyphName in self.marksDict: glyphNamesList.remove(glyphName) return glyphNamesList def updateListView(self): self.w.fontList.set(self.glyphNamesList) def getAnchorOffsets(self, canvasGlyph, glyphToDraw): # the current glyph is a mark if canvasGlyph.name in self.marksDict: # glyphToDraw is also a mark (mark-to-mark case) if glyphToDraw.name in self.marksDict: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] != '_': anchorName = anchor.name markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == '_'+ anchorName: baseAnchor = anchor break except UnboundLocalError: continue # glyphToDraw is not a mark else: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] == '_': anchorName = anchor.name[1:] markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == anchorName: baseAnchor = anchor break except UnboundLocalError: continue try: offsetX = markAnchor.x - baseAnchor.x offsetY = markAnchor.y - baseAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 # the current glyph is a base else: try: anchorName = self.marksDict[glyphToDraw.name] except KeyError: anchorName = None if anchorName: # pick the (base glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name == anchorName: baseAnchor = anchor break # pick the (mark glyph) anchor to draw on for anchor in glyphToDraw.anchors: if anchor.name == '_'+ anchorName: markAnchor = anchor break try: offsetX = baseAnchor.x - markAnchor.x offsetY = baseAnchor.y - markAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 return (offsetX, offsetY) def _drawGlyphs(self, info): """ draw stuff in the glyph window view """ translateBefore = (0, 0) for glyphName in self.selectedGlyphNamesList: glyphToDraw = self.font[glyphName] # determine the offset of the anchors offset = self.getAnchorOffsets(self.glyph, glyphToDraw) # set the offset of the drawing translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1]) # record the shift amounts (these are needed for resetting the drawing position when more than one mark is selected on the list) translateBefore = offset # set the fill & stroke fill(0, 0, self.Blue, self.Alpha) strokeWidth(None) # draw it mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font) glyphToDraw.draw(mojoPen) mojoPen.draw()
class Adhesiontext(BaseWindowController): def __init__(self): flushAlign = 76 firstRowY = 12 rowOffsetY = 30 firstCheckY = 135 checkOffsetY = 27 rightMarginX = -12 self.windowWidth = 410 self.windowHeightWithoutOptions = 45 self.windowHeightWithOptions = 280 self.scriptIsRTL = False windowPos = getExtensionDefault("%s.%s" % (extensionKey, "windowPos")) if not windowPos: windowPos = (100, 100) self.optionsVisible = getExtensionDefault( "%s.%s" % (extensionKey, "optionsVisible")) if self.optionsVisible: optionsButtonSign = '-' windowHeight = self.windowHeightWithOptions else: self.optionsVisible = False # needs to be set because the first time the extension runs self.optionsVisible will be None optionsButtonSign = '+' windowHeight = self.windowHeightWithoutOptions self.chars = getExtensionDefault("%s.%s" % (extensionKey, "chars")) if not self.chars: self.chars = '' self.sliderValue = getExtensionDefault("%s.%s" % (extensionKey, "sliderValue")) if not self.sliderValue: self.sliderValue = 25 self.scriptsIndex = getExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex")) if not self.scriptsIndex: self.scriptsIndex = 0 self.langsIndex = getExtensionDefault("%s.%s" % (extensionKey, "langsIndex")) if not self.langsIndex: self.langsIndex = 0 self.w = FloatingWindow( (windowPos[0], windowPos[1], self.windowWidth, windowHeight), "adhesiontext") # 1st row self.w.labelChars = TextBox((10, firstRowY, flushAlign, 20), "Characters:", alignment="right") self.w.chars = EditText((flushAlign + 15, firstRowY - 1, 199, 22), self.chars, callback=self.charsCallback) self.w.button = Button((300, firstRowY, 68, 20), "Get text", callback=self.buttonCallback) self.w.spinner = FixedSpinner((325, firstRowY, 20, 20), displayWhenStopped=False) self.w.optionsButton = SquareButton((378, firstRowY + 1, 18, 18), optionsButtonSign, sizeStyle="small", callback=self.optionsCallback) # set the initial state of the button according to the content of the chars EditText if len(self.w.chars.get()): self.w.button.enable(True) else: self.w.button.enable(False) # keep track of the content of chars EditText self.previousChars = self.w.chars.get() # 2nd row self.w.labelWords = TextBox( (10, firstRowY + rowOffsetY, flushAlign, 20), "Words:", alignment="right") self.w.wordCount = TextBox( (flushAlign + 12, firstRowY + rowOffsetY, 40, 20), alignment="left") self.w.slider = Slider( (flushAlign + 47, firstRowY + rowOffsetY + 1, 165, 20), value=self.sliderValue, minValue=5, maxValue=200, callback=self.sliderCallback) # set the initial wordCount value according to the position of the slider self.w.wordCount.set(int(self.w.slider.get())) # 3rd row self.w.labelScripts = TextBox( (10, firstRowY + rowOffsetY * 2, flushAlign, 20), "Script:", alignment="right") self.w.scriptsPopup = PopUpButton( (flushAlign + 15, firstRowY + rowOffsetY * 2, 150, 20), scriptsNameList, callback=self.scriptsCallback) self.w.scriptsPopup.set(self.scriptsIndex) # 4th row self.w.labelLangs = TextBox( (10, firstRowY + rowOffsetY * 3, flushAlign, 20), "Language:", alignment="right") self.w.langsPopup = PopUpButton( (flushAlign + 15, firstRowY + rowOffsetY * 3, 150, 20), []) # set the initial list of languages according to the script value self.w.langsPopup.setItems( langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]]) self.w.langsPopup.set(self.langsIndex) self.punctCheck = getExtensionDefault("%s.%s" % (extensionKey, "punctCheck")) if not self.punctCheck: self.punctCheck = 0 self.figsCheck = getExtensionDefault("%s.%s" % (extensionKey, "figsCheck")) if not self.figsCheck: self.figsCheck = 0 self.figsPopup = getExtensionDefault("%s.%s" % (extensionKey, "figsPopup")) if not self.figsPopup: self.figsPopup = 0 self.trimCheck = getExtensionDefault("%s.%s" % (extensionKey, "trimCheck")) if not self.trimCheck: self.trimCheck = 0 self.caseCheck = getExtensionDefault("%s.%s" % (extensionKey, "caseCheck")) if not self.caseCheck: self.caseCheck = 0 self.casingCheck = getExtensionDefault("%s.%s" % (extensionKey, "casingCheck")) if not self.casingCheck: self.casingCheck = 0 self.casingPopup = getExtensionDefault("%s.%s" % (extensionKey, "casingPopup")) if not self.casingPopup: self.casingPopup = 0 # 1st checkbox self.w.punctCheck = CheckBox((flushAlign + 15, firstCheckY, 130, 20), "Add punctuation") self.w.punctCheck.set(self.punctCheck) # 2nd checkbox self.w.figsCheck = CheckBox( (flushAlign + 15, firstCheckY + checkOffsetY, 120, 20), "Insert numbers", callback=self.figsCallback) self.w.figsCheck.set(self.figsCheck) self.w.figsPopup = PopUpButton( (210, firstCheckY + checkOffsetY, 90, 20), figOptionsList) self.w.figsPopup.set(self.figsPopup) # enable or disable the figure options PopUp depending on the figures CheckBox if scriptsNameList[self.w.scriptsPopup.get()] in enableFigOptionList: self.w.figsPopup.show(True) if self.w.figsCheck.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) else: self.w.figsPopup.show(False) # 3rd checkbox self.w.trimCheck = CheckBoxPlus( (flushAlign + 15, firstCheckY + checkOffsetY * 2, 120, 20), "Trim accents") self.w.trimCheck.set(self.trimCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableTrimCheckList: self.w.trimCheck.enable(True) else: self.w.trimCheck.enable(False) # 4th checkbox self.w.caseCheck = CheckBoxPlus( (flushAlign + 15, firstCheckY + checkOffsetY * 3, 120, 20), "Ignore casing") self.w.caseCheck.set(self.caseCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList: self.w.caseCheck.enable(True) else: self.w.caseCheck.enable(False) # 5th checkbox self.w.casingCheck = CheckBoxPlus( (flushAlign + 15, firstCheckY + checkOffsetY * 4, 115, 20), "Change casing", callback=self.casingCallback) self.w.casingCheck.set(self.casingCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList: self.w.casingCheck.enable(True) else: self.w.casingCheck.enable(False) self.w.casingPopup = PopUpButton( (210, firstCheckY + checkOffsetY * 4, 90, 20), casingNameList) self.w.casingPopup.set(self.casingPopup) # enable or disable the casing PopUp depending on the casing CheckBox if self.w.casingCheck.get() and self.w.casingCheck.isEnable(): self.w.casingPopup.enable(True) else: self.w.casingPopup.enable(False) self.nsTextField = self.w.chars.getNSTextField() self.w.setDefaultButton(self.w.button) self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def windowClose(self, sender): self.saveExtensionDefaults() def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "windowPos"), self.w.getPosSize()[0:2]) setExtensionDefault("%s.%s" % (extensionKey, "optionsVisible"), self.optionsVisible) setExtensionDefault("%s.%s" % (extensionKey, "chars"), self.w.chars.get()) setExtensionDefault("%s.%s" % (extensionKey, "sliderValue"), int(self.w.slider.get())) setExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex"), int(self.w.scriptsPopup.get())) setExtensionDefault("%s.%s" % (extensionKey, "langsIndex"), int(self.w.langsPopup.get())) setExtensionDefault("%s.%s" % (extensionKey, "punctCheck"), self.w.punctCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "figsCheck"), self.w.figsCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "figsPopup"), self.w.figsPopup.get()) setExtensionDefault("%s.%s" % (extensionKey, "trimCheck"), self.w.trimCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "caseCheck"), self.w.caseCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "casingCheck"), self.w.casingCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "casingPopup"), self.w.casingPopup.get()) def buttonCallback(self, sender): sender.enable(False) self.w.spinner.start() self.getText() self.w.spinner.stop() sender.enable(True) def optionsCallback(self, sender): sign = sender.getTitle() if sign == "+": sender.setTitle("-") self.w.resize(self.windowWidth, self.windowHeightWithOptions, animate=True) self.optionsVisible = True else: sender.setTitle("+") self.w.resize(self.windowWidth, self.windowHeightWithoutOptions, animate=True) self.optionsVisible = False def charsCallback(self, sender): charsContent = sender.get() if len(charsContent): self.w.button.enable(True) nsTextView = self.nsTextField.currentEditor( ) # NOTE: the field editor is only available when NSTextField is in editing mode. # when only one glyph is selected and copied, the contents of the clipboard are the glyph's XML # instead of its unicode character or its name; therefore, post-process the pasted content. if xmlHeader in charsContent: caretIndex = charsContent.index(xmlHeader) codepointString = re_glyphUnicode.search(charsContent) glyphName = re_glyphName.search(charsContent) if codepointString: replacement = unichr(eval('0x' + codepointString.group(1))) elif glyphName: replacement = '/' + glyphName.group(1) else: replacement = '' # replace the glyph's XML by its unicode character or its name self.w.chars.set(re_glyph.sub(replacement, charsContent)) # restore the location of the caret location = caretIndex + len(replacement) nsTextView.setSelectedRange_((location, 0)) # update the variable charsContent = sender.get() caretIndex = nsTextView.selectedRanges()[0].rangeValue().location # Limit the number of characters numeralWasFound = self.stringHasNumeral(charsContent) if len(charsContent) > maxChars or numeralWasFound: NSBeep() if numeralWasFound: self.showMessage("Sorry, numerals are not allowed.", "") else: self.showMessage( "You've reached the maximum \rnumber of characters.", "The limit is %d." % maxChars) # restore the content of chars EditText to the previous string sender.set(self.previousChars) # restore the focus on the chars EditText and restore the location of the caret caretIndexAdjust = len(self.previousChars) - len(charsContent) self.w.getNSWindow().makeFirstResponder_(self.nsTextField) nsTextView.setSelectedRange_( (caretIndex + caretIndexAdjust, 0)) # update the stored string self.previousChars = sender.get() else: self.w.button.enable(False) def sliderCallback(self, sender): self.w.wordCount.set(int(sender.get())) def scriptsCallback(self, sender): self.w.langsPopup.setItems( langsNameDict[scriptsNameList[sender.get()]]) # toggle RTL/LTR if scriptsNameList[sender.get()] in rightToLeftList: self.scriptIsRTL = True self.nsTextField.setBaseWritingDirection_( NSWritingDirectionRightToLeft) self.nsTextField.setAlignment_(NSRightTextAlignment) else: self.scriptIsRTL = False self.nsTextField.setBaseWritingDirection_( NSWritingDirectionLeftToRight) self.nsTextField.setAlignment_(NSLeftTextAlignment) # restore the focus on the chars EditText self.w.getNSWindow().makeFirstResponder_(self.nsTextField) # toggle figsPopup if scriptsNameList[sender.get()] in enableFigOptionList: self.w.figsPopup.show(True) if self.w.figsCheck.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) else: self.w.figsPopup.show(False) # toggle trimCheck if scriptsNameList[sender.get()] in enableTrimCheckList: self.w.trimCheck.enable(True) else: self.w.trimCheck.enable(False) # toggle caseCheck and casingCheck if scriptsNameList[sender.get()] in enableCaseCheckList: self.w.caseCheck.enable(True) self.w.casingCheck.enable(True) if self.w.casingCheck.get(): self.w.casingPopup.enable(True) else: self.w.caseCheck.enable(False) self.w.casingCheck.enable(False) self.w.casingPopup.enable(False) def figsCallback(self, sender): if sender.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) def casingCallback(self, sender): if sender.get(): self.w.casingPopup.enable(True) else: self.w.casingPopup.enable(False) def stringHasNumeral(self, string): if re_numeral.search(string): return True return False def isConnected(self): try: urlopen(url, timeout=3) return True except URLError: pass return False def getText(self): if CurrentFont() is None: NSBeep() self.showMessage("Open a font first.", "") return if not self.isConnected(): NSBeep() self.showMessage("Required internet connection not found.", "") return values = { 'chars': self.w.chars.get().encode('utf-8'), 'script': scriptsTagDict[scriptsNameList[self.w.scriptsPopup.get()]], 'tb': langsTagDict[langsNameDict[scriptsNameList[ self.w.scriptsPopup.get()]][self.w.langsPopup.get()]] } if self.w.punctCheck.get(): values['punct'] = True if self.w.figsCheck.get(): values['figs'] = True if self.w.figsPopup.isVisible(): figsOptTagsList = ["dflt", "locl"] values['figsOpt'] = figsOptTagsList[self.w.figsPopup.get()] if self.w.trimCheck.get() and self.w.trimCheck.isEnable(): values['trim'] = True if self.w.caseCheck.get() and self.w.caseCheck.isEnable(): values['case'] = True if self.w.casingCheck.get() and self.w.casingCheck.isEnable(): values['casing'] = casingNameList[self.w.casingPopup.get()].lower() data = urlencode(values) data = data.encode('utf-8') print(data) request = Request(url, data) response = urlopen(request) text = response.read() textU = unicode(text, 'utf-8') if (msgStr in textU): textU = textU.replace(msgStr, "") NSBeep() self.showMessage(textU, "") return elif (wrnStr in textU): resultIndex = textU.find(rsltStr) secmsgIndex = textU.find(sndStr) frstmsgU = textU[:secmsgIndex].replace(wrnStr, "") scndmsgU = textU[secmsgIndex:resultIndex].replace(sndStr, "") textU = textU[resultIndex:].replace(rsltStr, "") NSBeep() self.showMessage(frstmsgU, scndmsgU) textList = textU.split() trimmedText = ' '.join(textList[:int(self.w.slider.get())]) if CurrentSpaceCenter() is None: OpenSpaceCenter(CurrentFont(), newWindow=False) sp = CurrentSpaceCenter() print(trimmedText) sp.setRaw(trimmedText) # Toggle RTL-LTR try: sp.setLeftToRight(not self.scriptIsRTL) sp.setInputWritingDirection( 'Right to Left' if self.scriptIsRTL else 'Left to Right') except AttributeError: pass return
class WurstSchreiber(object): def __init__(self): self.draw = False self.swap = True self.radius = getExtensionDefault( "%s.%s" % (WurstSchreiberDefaultKey, "radius"), 60) color = NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 0, 0, .5) colorValue = getExtensionDefaultColor( "%s.%s" % (WurstSchreiberDefaultKey, "color"), color) self.w = FloatingWindow((150, 170), "WurstSchreiber") x = 15 y = 15 self.w.preview = CheckBox( (x, y, -x, 20), "Preview", callback=self.previewChanged, value=True) y+=30 self.w.slider = SliderGroup( (x, y, -x, 22), 0, 100, self.radius, callback=self.sliderChanged) y+=35 self.w.color = ColorWell( (x, y, -x, 40), callback=self.colorChanged, color=colorValue) y+=55 self.w.button = Button( (x, y, -x, 20), "Trace!", callback=self.traceButton) addObserver(self, "drawWurst", "drawBackground") self.w.bind("close", self.closing) self.w.open() def closing(self, sender): removeObserver(self, "drawBackground") def previewChanged(self, sender): UpdateCurrentGlyphView() def sliderChanged(self, sender): self.radius = int(sender.get()) setExtensionDefault( "%s.%s" % (WurstSchreiberDefaultKey, "radius"), self.radius) UpdateCurrentGlyphView() def colorChanged(self, sender): setExtensionDefaultColor( "%s.%s" % (WurstSchreiberDefaultKey, "color"), sender.get()) UpdateCurrentGlyphView() def getColor(self): color = self.w.color.get() return color.getRed_green_blue_alpha_(None, None, None, None) def traceButton(self, sender): if self.w.preview.get(): self.draw = True UpdateCurrentGlyphView() def drawWurst(self, sender): if self.w.preview.get(): radius = self.radius draw = self.draw r,g,b,a = self.getColor() fill(r,g,b,a) glyph = CurrentGlyph() pen = WurstPen(None, radius, draw) glyph.draw(pen) if self.draw: glyph.prepareUndo("WurstTrace") if self.swap: glyph.getLayer("background").clear() glyph.swapToLayer("background") glyph.appendGlyph(pen.glyphcopy) self.draw = False self.w.preview.set(False) glyph.performUndo() glyph.update()
class CornerController: def __init__(self): self.modifiedGlyph = None self.w = FloatingWindow((400, 170), "Corner Tool") self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor()) self.modes = ["Break", "Build", "Pit"] self.objectTypes = {"Build": "Segment", "Break": "Corner point", "Pit": "Corner point"} self.parameters = { "radius": VanillaSingleValueParameter("radius", 20, (-200, 200), numType="int"), "roundness": VanillaSingleValueParameter("roundness", 1.25, (0, 4), numType="float"), "depth": VanillaSingleValueParameter("depth", 30, (-100, 100), numType="int"), "breadth": VanillaSingleValueParameter("breadth", 30, (0, 150), numType="int"), "bottom": VanillaSingleValueParameter("bottom", 5, (0, 40), numType="int"), } self.currentMode = "Break" self.previewGlyph = None self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode) for i, mode in enumerate(self.modes): setattr(self.w, mode, Group((120, 15, -15, -15))) modeGroup = getattr(self.w, mode) modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u">", callback=self.apply) modeGroup.infoBox = Box((0, 0, -50, 35)) modeGroup.info = TextBox((10, 8, -50, 20), "No selection") if i > 0: modeGroup.show(False) self.w.Break.radius = ParameterSliderTextInput( self.parameters["radius"], (0, 60, -25, 25), title="Radius", callback=self.makePreviewGlyph ) self.w.Break.roundness = ParameterSliderTextInput( self.parameters["roundness"], (0, 95, -25, 25), title="Roundness", callback=self.makePreviewGlyph ) self.w.Pit.depth = ParameterSliderTextInput( self.parameters["depth"], (0, 50, -25, 25), title="Depth", callback=self.makePreviewGlyph ) self.w.Pit.breadth = ParameterSliderTextInput( self.parameters["breadth"], (0, 80, -25, 25), title="Breadth", callback=self.makePreviewGlyph ) self.w.Pit.bottom = ParameterSliderTextInput( self.parameters["bottom"], (0, 110, -25, 25), title="bottom", callback=self.makePreviewGlyph ) addObserver(self, "preview", "draw") addObserver(self, "preview", "drawInactive") addObserver(self, "previewSolid", "drawPreview") addObserver(self, "makePreviewGlyph", "mouseDown") addObserver(self, "makePreviewGlyph", "mouseDragged") addObserver(self, "makePreviewGlyph", "keyDown") addObserver(self, "makePreviewGlyph", "keyUp") addObserver(self, "setControls", "mouseUp") addObserver(self, "setControls", "selectAll") addObserver(self, "setControls", "deselectAll") addObserver(self, "setControls", "currentGlyphChanged") self.w.bind("close", self.windowClose) self.setControls() self.w.open() def changeMode(self, sender): index = sender.get() previousModeGroup = getattr(self.w, self.currentMode) previousModeGroup.show(False) self.currentMode = self.modes[index] modeGroup = getattr(self.w, self.currentMode) modeGroup.show(True) self.setControls() def setControls(self, notification=None): mode = self.currentMode selection = self.getSelection() modeGroup = getattr(self.w, mode) if not len(selection): modeGroup.apply.enable(False) modeGroup.info.set("No selection (%ss)" % (self.objectTypes[mode].lower())) elif len(selection): modeGroup.apply.enable(True) info = "%s valid %s" % (len(selection), self.objectTypes[mode].lower()) if len(selection) > 1: info += "s" modeGroup.info.set(info) self.makePreviewGlyph() def getSelection(self, notification=None): glyph = CurrentGlyph() if len(glyph.selection) == 0: return [] elif len(glyph.selection) > 0: iG = IntelGlyph(glyph) if self.currentMode == "Build": selection = iG.getSelection(True) elif self.currentMode in ["Break", "Pit"]: selection = [ point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi / 18) ] return selection def preview(self, notification): sc = notification["scale"] if self.previewGlyph is not None: self.previewGlyph.drawPreview( sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor, ) def previewSolid(self, notification): sc = notification["scale"] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, plain=True) def makePreviewGlyph(self, sender=None): if (sender is not None) and isinstance(sender, dict): if sender.has_key("notificationName") and sender["notificationName"] == "mouseDragged": g = sender["glyph"] if not len(g.selection): return self.previewGlyph = self.makeCornerGlyph() UpdateCurrentGlyphView() def makeCornerGlyph(self, sender=None): mode = self.currentMode if mode == "Build": cornerGlyph = self.buildCorners() elif mode == "Break": cornerGlyph = self.breakCorners() elif mode == "Pit": cornerGlyph = self.pitCorners() return cornerGlyph def buildCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) for contour in iG: segments = contour.collectSegments()["selection"] l = len(segments) lines, curves = self.checkComposition(segments) if l > 1 and lines and curves: segments = [segment for segment in segments if len(segment) == 4] elif l > 1 and lines and not curves: segments = segments[:1] + segments[-1:] for segment in reversed(segments): contour.buildCorner(segment) return iG def breakCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) radius = self.parameters["radius"].get() roundness = self.parameters["roundness"].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.breakCorner(point, radius, velocity=roundness) contour.correctSmoothness() return iG def pitCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) depth = self.parameters["depth"].get() breadth = self.parameters["breadth"].get() bottom = self.parameters["bottom"].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.pitCorner(point, depth, breadth, bottom) contour.removeOverlappingPoints() contour.correctSmoothness() return iG def apply(self, sender): targetGlyph = CurrentGlyph() modifiedGlyph = self.makeCornerGlyph() targetGlyph.prepareUndo("un.round") targetGlyph.clearContours() for p in targetGlyph.selection: p.selected = False pen = targetGlyph.getPointPen() modifiedGlyph.drawPoints(pen) targetGlyph.performUndo() targetGlyph.update() def checkComposition(self, segmentsList): lines = 0 curves = 0 for segment in segmentsList: if len(segment) == 2: lines += 1 elif len(segment) == 4: curves += 1 return lines, curves def windowClose(self, notification): removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") removeObserver(self, "mouseUp") removeObserver(self, "mouseDown") removeObserver(self, "mouseDragged") removeObserver(self, "keyDown") removeObserver(self, "keyUp") removeObserver(self, "selectAll") removeObserver(self, "deselectAll") removeObserver(self, "currentGlyphChanged")
class ProofGroupInspector: def __init__(self, proofGroup): """ Initialize inspector with proofGroup data. proofGroup is a dictionary passed in by ProofDrawer(). """ self.proofGroup = proofGroup self.editedProofGroup = {} left = 10 row = 10 textboxWidth = 92 leftEditText = left + 95 pointSizes = ["6", "8", "10", "12", "14", "18",\ "21", "24", "36", "48", "60", "72"] self.w = FloatingWindow( (400, 275), "Edit Proof Group: %s" % self.proofGroup["name"]) self.w.groupName = TextBox((left, row + 2, textboxWidth, 20), "Group name:", alignment="right") self.w.groupNameEdit = EditText((leftEditText, row, -10, 22), self.proofGroup["name"]) row += 33 self.w.typeSize = TextBox((left, row + 2, textboxWidth, 20), "Type size (pt):", alignment="right") self.w.typeSizeEdit = ComboBox((leftEditText, row, 55, 22), pointSizes, continuous=True, callback=self._checkFloat) self.w.typeSizeEdit.set(self.proofGroup["typeSize"]) self.w.leading = TextBox((leftEditText + 80, row + 2, 60, 22), "Leading:") self.w.leadingEdit = ComboBox((leftEditText + 139, row, 55, 22), pointSizes, continuous=True, callback=self._checkFloat) self.w.leadingEdit.set(self.proofGroup["leading"]) row += 33 self.w.contents = TextBox((left, row, textboxWidth, 20), "Contents:", alignment="right") self.w.contentsEdit = TextEditor( (leftEditText, row, -10, 150), "\n".join(self.proofGroup["contents"])) self.w.contentsEdit.getNSTextView().setFont_(monoFont) row += 160 self.w.cancelButton = Button((leftEditText, row, 138, 20), "Cancel", callback=self.cancelCB) leftEditText += 147 self.w.okButton = Button((leftEditText, row, 138, 20), "OK", callback=self.okCB) self.w.setDefaultButton(self.w.okButton) self.w.bind("close", self._postCloseEvent) def _postCloseEvent(self, sender): postEvent("com.InspectorClosed") def _checkFloat(self, sender): """ Make sure users don't input non-floats by capturing value prior to new input, then using it if user tries to input an illegal character """ # pass # Store everything up to newly-typed character allButLast = sender.get()[:-1] try: float(sender.get()) # Get rid of whitespaces immediately sender.set(sender.get().strip()) except ValueError: sender.set(allButLast) def okCB(self, sender): """ Get everything from fields, save in self.editedProofGroup dict, post event, pass the edited group to observer, and close window """ self.editedProofGroup["name"] = self.w.groupNameEdit.get().strip() self.editedProofGroup["typeSize"] = self.w.typeSizeEdit.get() self.editedProofGroup["leading"] = self.w.leadingEdit.get() self.editedProofGroup["print"] = self.proofGroup[ "print"] # just pass this back for now self.editedProofGroup["contents"] = hf.makeCleanListFromStr( self.w.contentsEdit.get()) postEvent("com.ProofGroupEdited", editedProofGroup=self.editedProofGroup) self.w.close() def cancelCB(self, sender): self.w.close()
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:
class AccentedMaker(BaseWindowController): fontOptions = [] whichFont = None actions = ['Place Anchors', 'Build Accents'] whichAction = actions[0] whichGlyphList = None markEditedGlyphs = False markColor = glyphCollectionColors[glyphCollectionColors.keys()[0]] uppercaseAccents = False def __init__(self): super(AccentedMaker, self).__init__() self.initLogger() self.fontOptions = ['All Fonts', 'Current Font'] + AllFonts() self.whichFont = self.fontOptions[0] self.pluginHeight = PLUGIN_HEIGHT self.loadAccentedData() self.parseGlyphListsFromAccentedData() firstKey = self.glyphLists[self.whichAction].keys()[0] self.whichGlyphList = self.glyphLists[self.whichAction][firstKey] self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, self.pluginHeight), PLUGIN_TITLE) self.w.sharedCtrls = SharedCtrls( (MARGIN_HOR, MARGIN_VER, NET_WIDTH, 104), fontOptions=self.fontOptions, whichFont=self.whichFont, actions=self.actions, whichAction=self.whichAction, glyphLists=self.glyphLists, whichGlyphList=self.whichGlyphList, markColor=self.markColor, markEditedGlyphs=self.markEditedGlyphs, callback=self.sharedCtrlsCallback) self.w.separationLine = HorizontalLine( (MARGIN_HOR, self.w.sharedCtrls.getPosSize()[3] + MARGIN_ROW, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness'])) dependantCtrlsHgt = MARGIN_VER + self.w.sharedCtrls.getPosSize( )[3] + MARGIN_ROW self.w.anchorsCtrls = AnchorsCtrls( (MARGIN_HOR, dependantCtrlsHgt, NET_WIDTH, 76), callbackAttrs=self.anchorsVarsCallback, placeCallback=self.anchorsPlaceCallback, deleteCallback=self.anchorsDeleteCallback) self.w.buildingCtrls = BuildingCtrls( (MARGIN_HOR, dependantCtrlsHgt, NET_WIDTH, 50), self.uppercaseAccents, callbackAttrs=self.buildingVarsCallback, callbackCheck=self.checkAccentedCallback, callbackBuild=self.buildAccentedCallback) self.w.buildingCtrls.show(False) addObserver(self, 'updateFontOptions', "newFontDidOpen") addObserver(self, 'updateFontOptions', "fontDidOpen") addObserver(self, 'updateFontOptions', "fontWillClose") self.w.bind("close", self.windowCloseCallback) self.setUpBaseWindowBehavior() self.adjustPluginHeight() self.w.open() def initLogger(self): # create a logger self.accentedLogger = logging.getLogger('accentedLogger') # create file handler which logs info messages fileBasedHandler = logging.FileHandler('accentedLettersMaker.log') fileBasedHandler.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 | %(levelname)s | line: %(lineno)d | %(funcName)s | %(message)s' ) fileBasedHandler.setFormatter(formatter) consoleHandler.setFormatter(formatter) # add the handlers to the logger self.accentedLogger.addHandler(fileBasedHandler) self.accentedLogger.addHandler(consoleHandler) # deal with font data def prepareFontsToAction(self): if self.whichFont == 'All Fonts': fontsToProcess = AllFonts() elif self.whichFont == 'Current Font': fontsToProcess = [CurrentFont()] else: fontsToProcess = [self.whichFont] return fontsToProcess def deleteAnchors(self): self.accentedLogger.info( START_FUNC.format(funcName=self.deleteAnchors.__name__)) fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: self.accentedLogger.info( START_FONT.format(familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) for eachGlyphName in self.whichGlyphList: eachGlyph = eachFont[eachGlyphName] if self.markEditedGlyphs is True: if version[0] == '2': eachGlyph.markColor = self.markColor else: eachGlyph.mark = self.markColor for eachAnchor in eachGlyph.anchors: eachGlyph.removeAnchor(eachAnchor) self.accentedLogger.info( REMOVE_ANCHOR.format(anchorName=self.anchorName, glyphName=eachGlyphName)) self.accentedLogger.info( END_FUNC.format(funcName=self.deleteAnchors.__name__)) def placeAnchors(self): assert self.anchorName is not None, '[WARNING] no anchor name provided' assert self.anchorHeight is not None, '[WARNING] no anchor height provided' self.accentedLogger.info( START_FUNC.format(funcName=self.placeAnchors.__name__)) fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: self.accentedLogger.info( START_FONT.format(familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) for eachGlyphName in self.whichGlyphList: if eachGlyphName in eachFont: eachGlyph = eachFont[eachGlyphName] if self.markEditedGlyphs is True: if version[0] == '2': eachGlyph.markColor = self.markColor else: eachGlyph.mark = self.markColor if selectAnchorByName(eachGlyph, self.anchorName): anchorToDel = selectAnchorByName( eachGlyph, self.anchorName) eachGlyph.removeAnchor(anchorToDel) if version[0] == '2': xMin, yMin, xMax, yMax = eachGlyph.bounds else: xMin, yMin, xMax, yMax = eachGlyph.box if eachFont.info.italicAngle: anchorAngle = radians(-eachFont.info.italicAngle) else: anchorAngle = radians(0) tangentOffset = tan(anchorAngle) * self.anchorHeight anchorX = ( eachGlyph.width - eachGlyph.angledLeftMargin - eachGlyph.angledRightMargin ) / 2 + eachGlyph.angledLeftMargin + tangentOffset eachGlyph.appendAnchor(self.anchorName, (anchorX, self.anchorHeight)) self.accentedLogger.info( APPEND_ANCHOR.format(anchorName=self.anchorName, anchorX=anchorX, anchorHeight=self.anchorHeight, glyphName=eachGlyphName)) else: self.accentedLogger.error( GLYPH_NOT_IN_FONT.format( glyphName=eachGlyphName, familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) self.accentedLogger.info( END_FUNC.format(funcName=self.placeAnchors.__name__)) def checkAccented(self, isPrinting=True): report = [] notReady = OrderedDict() fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: toSkip = [] report.append('Checking {} {}'.format(eachFont.info.familyName, eachFont.info.styleName)) for eachAccentedName, eachBaseName, eachAccentName, eachAnchorName in self.whichGlyphList: # base glyph if eachFont.has_key(eachBaseName) is False: report.append( BUILD_MISSING_GLYPH.format( glyphName=eachBaseName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) else: eachBaseGlyph = eachFont[eachBaseName] if not eachBaseGlyph.anchors: report.append( NO_ANCHORS.format(glyphName=eachBaseName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachBaseGlyph.markColor = ERROR_MARK_COLOR else: eachBaseGlyph.mark = ERROR_MARK_COLOR else: for eachAnchor in eachBaseGlyph.anchors: if eachAnchor.name == eachAnchorName: break else: report.append( BUILD_MISSING_ANCHOR.format( anchorName=eachAnchorName, glyphName=eachBaseName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachBaseGlyph.markColor = ERROR_MARK_COLOR else: eachBaseGlyph.mark = ERROR_MARK_COLOR # accent if eachFont.has_key(eachAccentName) is False: report.append( BUILD_MISSING_GLYPH.format( glyphName=eachAccentName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) else: eachAccentGlyph = eachFont[eachAccentName] if not eachAccentGlyph.anchors: report.append( NO_ANCHORS.format(glyphName=eachAccentName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachAccentGlyph.markColor = ERROR_MARK_COLOR else: eachAccentGlyph.mark = ERROR_MARK_COLOR else: for eachAnchor in eachAccentGlyph.anchors: if eachAnchor.name == '_{}'.format(eachAnchorName): break else: report.append( BUILD_MISSING_ANCHOR.format( anchorName=eachAnchorName, glyphName=eachAccentName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachAccentGlyph.markColor = ERROR_MARK_COLOR else: eachAccentGlyph.mark = ERROR_MARK_COLOR notReady['{} {}'.format(eachFont.info.familyName, eachFont.info.styleName)] = toSkip report.append('End checking {} {}'.format(eachFont.info.familyName, eachFont.info.styleName)) report.append('\n\n') if isPrinting is True: self.accentedLogger.error('\n'.join(report)) return notReady def buildAccented(self): notReady = self.checkAccented(isPrinting=False) self.accentedLogger.info( START_FUNC.format(funcName=self.buildAccented.__name__)) fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: self.accentedLogger.info( START_FONT.format(familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) for eachAccentedName, eachBaseName, eachAccentName, eachAnchorName in self.whichGlyphList: if eachAccentedName in notReady['{} {}'.format( eachFont.info.familyName, eachFont.info.styleName)]: self.accentedLogger.error( NOT_READY.format(fontName='{} {}'.format( eachFont.info.familyName, eachFont.info.styleName), accentedName=eachAccentedName)) continue eachBaseGlyph = eachFont[eachBaseName] eachBaseAnchor = selectAnchorByName(eachBaseGlyph, eachAnchorName) eachAccentGlyph = eachFont[eachAccentName] eachAccentAnchor = selectAnchorByName( eachAccentGlyph, '_{}'.format(eachAnchorName)) if eachFont.has_key(eachAccentedName) is False: eachAccentedGlyph = eachFont.newGlyph(eachAccentedName) else: eachAccentedGlyph = eachFont[eachAccentedName] eachAccentedGlyph.clear() eachAccentedGlyph.width = eachBaseGlyph.width eachAccentedGlyph.appendComponent(eachBaseName, (0, 0), (1, 1)) accentOffsetX, accentOffsetY = eachBaseAnchor.x - eachAccentAnchor.x, eachBaseAnchor.y - eachAccentAnchor.y eachAccentedGlyph.appendComponent( eachAccentName, (accentOffsetX, accentOffsetY), (1, 1)) self.accentedLogger.info( BUILT_GLYPH.format(accentedName=eachAccentedName, baseName=eachBaseName, accentName=eachAccentName, anchorName=eachAnchorName)) if self.markEditedGlyphs is True: if version[0] == '2': eachAccentedGlyph.markColor = self.markColor else: eachAccentedGlyph.mark = self.markColor self.accentedLogger.info( END_FUNC.format(funcName=self.buildAccented.__name__)) # deal with table data def loadAccentedData(self): self.accentedData = [[cell.strip() for cell in row.split('\t')] for row in open(TABLE_PATH, 'r').readlines()] def parseGlyphListsFromAccentedData(self): assert self.accentedData is not None self.glyphLists = OrderedDict() # anchors self.glyphLists['Place Anchors'] = OrderedDict() accentsTop = [] _ = [ accentsTop.append(row[2]) for row in self.accentedData if row[3] == 'top' and row[2] not in accentsTop ] self.glyphLists['Place Anchors']['ACC TOP'] = accentsTop accentsBtm = [] _ = [ accentsBtm.append(row[2]) for row in self.accentedData if row[3] == 'bottom' and row[2] not in accentsBtm ] self.glyphLists['Place Anchors']['ACC BTM'] = accentsBtm accentsCaseTop = ['{}.case'.format(name) for name in accentsTop] self.glyphLists['Place Anchors']['ACC CASE TOP'] = accentsCaseTop accentsCaseBtm = ['{}.case'.format(name) for name in accentsBtm] self.glyphLists['Place Anchors']['ACC CASE BTM'] = accentsCaseBtm ucBaseTop = [] _ = [ ucBaseTop.append(row[1]) for row in self.accentedData if row[1][0].isupper() and row[3] == 'top' and row[1] not in ucBaseTop ] self.glyphLists['Place Anchors']['UC TOP'] = ucBaseTop ucBaseBtm = [] _ = [ ucBaseBtm.append(row[1]) for row in self.accentedData if row[1][0].isupper() and row[3] == 'bottom' and row[1] not in ucBaseBtm ] self.glyphLists['Place Anchors']['UC BTM'] = ucBaseBtm lcBaseTop = [] _ = [ lcBaseTop.append(row[1]) for row in self.accentedData if row[1][0].islower() and row[3] == 'top' and row[1] not in lcBaseTop ] self.glyphLists['Place Anchors']['LC TOP'] = lcBaseTop lcBaseBtm = [] _ = [ lcBaseBtm.append(row[1]) for row in self.accentedData if row[1][0].islower() and row[3] == 'bottom' and row[1] not in lcBaseBtm ] self.glyphLists['Place Anchors']['LC BTM'] = lcBaseBtm # build self.glyphLists['Build Accents'] = OrderedDict() self.glyphLists['Build Accents']['ALL'] = self.accentedData buildUC = [ row for row in self.accentedData if row[1][0].isupper() is True ] self.glyphLists['Build Accents']['UC'] = buildUC buildLC = [ row for row in self.accentedData if row[1][0].islower() is True ] self.glyphLists['Build Accents']['LC'] = buildLC # ui def adjustPluginHeight(self): if self.whichAction == 'Place Anchors': self.pluginHeight = MARGIN_VER + self.w.sharedCtrls.getPosSize( )[3] + MARGIN_ROW + self.w.anchorsCtrls.getPosSize( )[3] + MARGIN_VER else: self.pluginHeight = MARGIN_VER + self.w.sharedCtrls.getPosSize( )[3] + MARGIN_ROW + self.w.buildingCtrls.getPosSize( )[3] + MARGIN_VER lft, top, wdt, hgt = self.w.getPosSize() self.w.resize(wdt, self.pluginHeight) def switchDependantCtrl(self): if self.whichAction == 'Place Anchors': self.w.anchorsCtrls.show(True) self.w.buildingCtrls.show(False) else: self.w.anchorsCtrls.show(False) self.w.buildingCtrls.show(True) # observers def updateFontOptions(self, sender): self.fontOptions = ['All Fonts', 'Current Font'] + AllFonts() self.w.sharedCtrls.setFontOptions(self.fontOptions) # callbacks def sharedCtrlsCallback(self, sender): self.whichFont = sender.getWhichFont() self.whichAction = sender.getWhichAction() self.switchDependantCtrl() self.adjustPluginHeight() self.whichGlyphList = sender.getWhichGlyphList() self.markEditedGlyphs, self.markColor = sender.getMarkEditedGlyphs() def anchorsVarsCallback(self, sender): self.anchorHeight = sender.getHeight() self.anchorName = sender.getName() def anchorsPlaceCallback(self, sender): self.placeAnchors() def anchorsDeleteCallback(self, sender): self.deleteAnchors() def buildingVarsCallback(self, sender): self.uppercaseAccents = sender.getUppercaseAccents() def checkAccentedCallback(self, sender): self.checkAccented() def buildAccentedCallback(self, sender): self.buildAccented() def windowCloseCallback(self, sender): removeObserver(self, "newFontDidOpen") removeObserver(self, "fontDidOpen") removeObserver(self, "fontWillClose") super(AccentedMaker, self).windowCloseCallback(sender)
class DebuggerWindow(BaseWindowController): def __init__(self): self.w = FloatingWindow((123, 44), 'debug!') ms = MarginSelector() self.w.open() self.w.bind("close", ms.destroy)
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 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")