Example #1
0
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 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
Example #3
0
class VariableController(object):
    def __init__(self, attributes, callback, document=None):
        self._callback = callback
        self._attributes = None
        self.w = FloatingWindow((250, 50))
        self.buildUI(attributes)
        self.w.open()
        if document:
            self.w.assignToDocument(document)
        self.w.setTitle("Variables")

    def buildUI(self, attributes):
        if self._attributes == attributes:
            return
        self._attributes = attributes
        if hasattr(self.w, "ui"):
            del self.w.ui
        self.w.ui = ui = vanilla.Group((0, 0, -0, -0))
        y = 10
        labelSize = 100
        gutter = 5
        for attribute in self._attributes:
            uiElement = attribute["ui"]
            name = attribute["name"]
            args = dict(attribute.get("args", {}))
            height = 19
            # adjust the height if a radioGroup is vertical
            if uiElement == "RadioGroup":
                if args.get("isVertical", True):
                    height = height * len(args.get("titles", [""]))
            # create a label for every ui element except a checkbox
            if uiElement not in ("CheckBox", "Button"):
                # create the label view
                label = vanilla.TextBox((0, y + 2, labelSize - gutter, height),
                                        "%s:" % name,
                                        alignment="right",
                                        sizeStyle="small")
                # set the label view
                setattr(ui, "%sLabel" % name, label)
            else:
                args["title"] = name
            # check the provided args and add required keys
            if uiElement == "ColorWell":
                # a color well needs a color to be set
                # no size style
                if "color" not in args:
                    args["color"] = AppKit.NSColor.blackColor()
            elif uiElement == "TextEditor":
                # different control height
                # no size style
                height = attribute.get("height", 75)
            else:
                # all other get a size style
                args["sizeStyle"] = "small"
            # create the control view
            attr = getattr(vanilla, uiElement)((labelSize, y, -10, height),
                                               callback=self.changed,
                                               **args)
            # set the control view
            setattr(ui, name, attr)
            y += height + 6
        # resize the window according the provided ui elements
        self.w.resize(250, y)

    def changed(self, sender):
        self.documentWindowToFront()
        if self._callback:
            self._callback()

    def get(self):
        data = {}
        for attribute in self._attributes:
            if attribute["ui"] in ("Button", ):
                continue
            name = attribute["name"]
            data[name] = getattr(self.w.ui, name).get()
        return data

    def show(self):
        self.w.show()

    def documentWindowToFront(self, sender=None):
        self.w.makeKey()
Example #4
0
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:
Example #5
0
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")
Example #6
0
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()
Example #8
0
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