Example #1
0
    class TesterWindow:
        def __init__(self):
            self.w = FloatingWindow((100, 100), "Tester")
            self.w.bind("close", self.closeCB)

            self.guideView = None

            addObserver(self, "glyphWindowOpenCB", "glyphWindowDidOpen")

        def glyphWindowOpenCB(self, info):
            glyphWindow = info["window"]
            self.guideView = GuideStatusView()
            self.guideView.view.statusText.set("TEST")
            self.guideView.addViewToWindow(glyphWindow)
            self.guideView.turnStatusTextOn()

        def closeCB(self, sender):
            removeObserver(self, "glyphWindowDidOpen")
Example #2
0
class DemoWindowTool:
    def __init__(self):
        self.glyph = None  # Typical RoboFont function
        self.updating = False

        pad = 10
        leading = 32
        y = pad
        w = 300
        h = 400
        buttonWidth = 100
        buttonHeight = 48

        self.w = FloatingWindow((100, 100, w, h), 'DemoWindowTool')
        self.w.doDraw = CheckBox((pad, y, 100, 24),
                                 'Draw',
                                 callback=self.doDrawCallback)
        y += leading
        self.w.mySlider = Slider((pad, y, -pad, 24),
                                 minValue=0,
                                 maxValue=2000,
                                 value=0,
                                 tickMarkCount=10,
                                 callback=self.mySliderCallback)
        y = self.w.saveFont = Button((-buttonWidth - pad, -buttonHeight - pad,
                                      buttonWidth, buttonHeight),
                                     'Save',
                                     callback=self.saveFontCallback)

        addObserver(self, "currentGlyphChanged",
                    "currentGlyphChanged")  # Subscribe to application event
        addObserver(self, 'draw', 'draw')
        addObserver(self, 'drawBackground', 'drawBackground')

        self.w.bind('close', self.windowCloseCallback)
        self.w.open()

    def windowCloseCallback(self, sender):
        removeObserver(
            self,
            "currentGlyphChanged")  # Unsubscribing from application event
        removeObserver(self, 'draw')
        removeObserver(self, 'drawBackground')
        print('removeObserver currentGlyphChanged')

    def mySliderCallback(self, sender):
        if self.glyph is not None:
            self.glyph.width = sender.get()  # same as self.w.mySlider
            self.updating = True
            self.glyph.changed()
            UpdateCurrentGlyphView()
            self.updating = False

    def doDrawCallback(self, sender):
        UpdateCurrentGlyphView()

    def saveFontCallback(self, sender):
        print('Saving')

    def draw(self, info):
        #print('Drawing' ,info['glyph'])
        if not self.updating:
            glyph = info['glyph']
            self.w.mySlider.set(glyph.width)
        if self.w.doDraw.get():
            fill(1, 0, 0, 0.5)
            rect(0, 0, glyph.width, 50)

    def drawBackground(self, info):
        #print('Drawing' ,info['glyph'])
        if not self.updating:
            glyph = info['glyph']
            self.w.mySlider.set(glyph.width)
        if self.w.doDraw.get():
            fill(0, 0, 1, 0.5)
            rect(0, 0, glyph.width, 50)

    #def glyphChanged(self, info):
    #    print('Glyph changed', info['glyph'])

    def currentGlyphChanged(self, info):
        #if self.glyph is not None:
        #    self.glyph.removeObserver(self, 'Glyph.Change')
        self.glyph = info['glyph']
Example #3
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 spacingObserver(object):
    
    def __init__(self):
        self.enableGroupSpacing = False
        self.popupOpen = False
        addObserver(self, 'glyphEditCallback', 'spaceCenterKeyDown')
        addObserver(self, 'glyphEditedCallback', 'spaceCenterKeyUp')
        addObserver(self, 'spaceCenterOpenCallback', 'spaceCenterDidOpen')
        addObserver(self, 'fontOpenCallback', 'fontDidOpen')
        self.previousMargins = {'left': 0, 'right': 0}

    def processMetricsGroups(self, baseGlyph=None):
            
        for groupName in self.metricsGroups:
            
            if (baseGlyph is None) and len(self.font.groups[groupName]) > 0:
                baseGlyph = self.font.groups[groupName][0]
                self.previousMargins['left'] = self.font[baseGlyph].angledLeftMargin
                self.previousMargins['right'] = self.font[baseGlyph].angledRightMargin
            
            if (metricsPrefix in groupName) and (baseGlyph in self.font.groups[groupName]):
                if (leftIndicator in groupName) and (self.previousMargins['left'] != self.font[baseGlyph].angledLeftMargin):
                    self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Left')                    
                elif (rightIndicator in groupName) and (self.previousMargins['right'] != self.font[baseGlyph].angledRightMargin):    
                    self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Right') 
    

    def setGroupSpacing(self, baseGlyphName, group, side):
        
        for glyphName in group:
            
            baseGlyph = self.font[baseGlyphName]
            targetGlyph = self.font[glyphName]

            if glyphName is not baseGlyphName:
        
                if (len(targetGlyph.components) > 0) and (side == 'Left'):
                    for component in targetGlyph.components:
                        if component.baseGlyph in group:
                            component.move((self.previousMargins['left']-baseGlyph.angledLeftMargin, 0))

                self.setSidebearing(baseGlyph, targetGlyph, side)

            elif glyphName is baseGlyphName:

                if (len(baseGlyph.components) > 0) and (side == 'Left'):
                    for component in baseGlyph.components:
                        if component.baseGlyph in group:
                            component.move((self.previousMargins['left']-baseGlyph.angledLeftMargin, 0))

            targetGlyph.update()
                    
    def setSidebearing(self, baseGlyph, targetGlyph, side):
        baseMargin = getattr(baseGlyph, 'angled' + side + 'Margin')
        targetMargin = getattr(targetGlyph, 'angled' + side + 'Margin')
        
        if targetMargin != baseMargin:
            setattr(targetGlyph, 'angled' + side + 'Margin', baseMargin)

                    
    def getMetricsGroups(self, notification=None):
        self.font = CurrentFont()            
        if self.font is not None:
            self.metricsGroups = [group for group in self.font.groups.keys() if metricsPrefix in group and leftIndicator in group or rightIndicator in group]         
            if (notification is not None) and (self.enableGroupSpacing == True):
                self.processMetricsGroups()
         

    def enableGroupSpacingCallback(self, sender):
        self.enableGroupSpacing = sender.get()
 
    def glyphEditCallback(self, notification):

        edGlyph = notification['glyph']
        self.previousMargins = {'width': edGlyph.width, 'left': edGlyph.angledLeftMargin, 'right': edGlyph.angledRightMargin}

    def glyphEditedCallback(self, notification):
        
        if self.enableGroupSpacing == True:
        
            edGlyph = notification['glyph']
            
            if self.font != CurrentFont():
                self.getMetricsGroups()
            
            self.processMetricsGroups(edGlyph.name)   
        

    def spaceCenterOpenCallback(self, notification):
        if (not self.popupOpen) and (len(self.metricsGroups) > 0):
            self.w = FloatingWindow((160, 36), 'Group Spacing')
            self.w.activateGroups = CheckBox((9, -27, 151, 18), "Activate Group spacing", value=self.enableGroupSpacing, callback=self.enableGroupSpacingCallback, sizeStyle="small")
            self.w.bind('close', self.windowCloseCallback)
            self.w.open()
            self.popupOpen = True

    def windowCloseCallback(self, notification):
        self.popupOpen = False

    def fontOpenCallback(self, notification):
        font = notification['font']
        font.groups.addObserver(self, 'getMetricsGroups', 'Groups.Changed')
        self.getMetricsGroups(notification)
class Adhesiontext(BaseWindowController):

	def __init__(self):
		flushAlign = 76
		firstRowY = 12
		rowOffsetY = 30
		firstCheckY = 135
		checkOffsetY = 27
		rightMarginX = -12
		self.windowWidth = 410
		self.windowHeightWithoutOptions = 45
		self.windowHeightWithOptions = 280
		self.scriptIsRTL = False

		windowPos = getExtensionDefault("%s.%s" % (extensionKey, "windowPos"))
		if not windowPos:
			windowPos = (100, 100)

		self.optionsVisible = getExtensionDefault("%s.%s" % (extensionKey, "optionsVisible"))
		if self.optionsVisible:
			optionsButtonSign = '-'
			windowHeight = self.windowHeightWithOptions
		else:
			self.optionsVisible = False # needs to be set because the first time the extension runs self.optionsVisible will be None
			optionsButtonSign = '+'
			windowHeight = self.windowHeightWithoutOptions

		self.chars = getExtensionDefault("%s.%s" % (extensionKey, "chars"))
		if not self.chars:
			self.chars = ''

		self.sliderValue = getExtensionDefault("%s.%s" % (extensionKey, "sliderValue"))
		if not self.sliderValue:
			self.sliderValue = 25

		self.scriptsIndex = getExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex"))
		if not self.scriptsIndex:
			self.scriptsIndex = 0

		self.langsIndex = getExtensionDefault("%s.%s" % (extensionKey, "langsIndex"))
		if not self.langsIndex:
			self.langsIndex = 0


		self.w = FloatingWindow((windowPos[0], windowPos[1], self.windowWidth, windowHeight), "adhesiontext")

		# 1st row
		self.w.labelChars = TextBox((10, firstRowY, flushAlign, 20), "Characters:", alignment="right")
		self.w.chars = EditText((flushAlign +15, firstRowY -1, 199, 22), self.chars, callback=self.charsCallback)
		self.w.button = Button((300, firstRowY, 68, 20), "Get text", callback=self.buttonCallback)
		self.w.spinner = FixedSpinner((325, firstRowY, 20, 20), displayWhenStopped=False)
		self.w.optionsButton = SquareButton((378, firstRowY +1, 18, 18), optionsButtonSign, sizeStyle="small", callback=self.optionsCallback)
		# set the initial state of the button according to the content of the chars EditText
		if len(self.w.chars.get()): self.w.button.enable(True)
		else: self.w.button.enable(False)
		# keep track of the content of chars EditText
		self.previousChars = self.w.chars.get()

		# 2nd row
		self.w.labelWords = TextBox((10, firstRowY + rowOffsetY, flushAlign, 20), "Words:", alignment="right")
		self.w.wordCount = TextBox((flushAlign +12, firstRowY + rowOffsetY, 40, 20), alignment="left")
		self.w.slider = Slider((flushAlign +47, firstRowY + rowOffsetY +1, 165, 20), value=self.sliderValue, minValue=5, maxValue=200, callback=self.sliderCallback)
		# set the initial wordCount value according to the position of the slider
		self.w.wordCount.set(int(self.w.slider.get()))

		# 3rd row
		self.w.labelScripts = TextBox((10, firstRowY + rowOffsetY *2, flushAlign, 20), "Script:", alignment="right")
		self.w.scriptsPopup = PopUpButton((flushAlign +15, firstRowY + rowOffsetY *2, 150, 20), scriptsNameList, callback=self.scriptsCallback)
		self.w.scriptsPopup.set(self.scriptsIndex)

		# 4th row
		self.w.labelLangs = TextBox((10, firstRowY + rowOffsetY *3, flushAlign, 20), "Language:", alignment="right")
		self.w.langsPopup = PopUpButton((flushAlign +15, firstRowY + rowOffsetY *3, 150, 20), [])
		# set the initial list of languages according to the script value
		self.w.langsPopup.setItems(langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]])
		self.w.langsPopup.set(self.langsIndex)

		self.punctCheck = getExtensionDefault("%s.%s" % (extensionKey, "punctCheck"))
		if not self.punctCheck:
			self.punctCheck = 0

		self.figsCheck = getExtensionDefault("%s.%s" % (extensionKey, "figsCheck"))
		if not self.figsCheck:
			self.figsCheck = 0

		self.figsPopup = getExtensionDefault("%s.%s" % (extensionKey, "figsPopup"))
		if not self.figsPopup:
			self.figsPopup = 0

		self.trimCheck = getExtensionDefault("%s.%s" % (extensionKey, "trimCheck"))
		if not self.trimCheck:
			self.trimCheck = 0

		self.caseCheck = getExtensionDefault("%s.%s" % (extensionKey, "caseCheck"))
		if not self.caseCheck:
			self.caseCheck = 0

		self.casingCheck = getExtensionDefault("%s.%s" % (extensionKey, "casingCheck"))
		if not self.casingCheck:
			self.casingCheck = 0

		self.casingPopup = getExtensionDefault("%s.%s" % (extensionKey, "casingPopup"))
		if not self.casingPopup:
			self.casingPopup = 0

		# 1st checkbox
		self.w.punctCheck = CheckBox((flushAlign +15, firstCheckY, 130, 20), "Add punctuation")
		self.w.punctCheck.set(self.punctCheck)

		# 2nd checkbox
		self.w.figsCheck = CheckBox((flushAlign +15, firstCheckY + checkOffsetY, 120, 20), "Insert numbers", callback=self.figsCallback)
		self.w.figsCheck.set(self.figsCheck)
		self.w.figsPopup = PopUpButton((210, firstCheckY + checkOffsetY, 90, 20), figOptionsList)
		self.w.figsPopup.set(self.figsPopup)
		# enable or disable the figure options PopUp depending on the figures CheckBox
		if scriptsNameList[self.w.scriptsPopup.get()] in enableFigOptionList:
			self.w.figsPopup.show(True)
			if self.w.figsCheck.get():
				self.w.figsPopup.enable(True)
			else:
				self.w.figsPopup.enable(False)
		else:
			self.w.figsPopup.show(False)

		# 3rd checkbox
		self.w.trimCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *2, 120, 20), "Trim accents")
		self.w.trimCheck.set(self.trimCheck)
		if scriptsNameList[self.w.scriptsPopup.get()] in enableTrimCheckList:
			self.w.trimCheck.enable(True)
		else:
			self.w.trimCheck.enable(False)

		# 4th checkbox
		self.w.caseCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *3, 120, 20), "Ignore casing")
		self.w.caseCheck.set(self.caseCheck)
		if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList:
			self.w.caseCheck.enable(True)
		else:
			self.w.caseCheck.enable(False)

		# 5th checkbox
		self.w.casingCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *4, 115, 20), "Change casing", callback=self.casingCallback)
		self.w.casingCheck.set(self.casingCheck)
		if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList:
			self.w.casingCheck.enable(True)
		else:
			self.w.casingCheck.enable(False)
		self.w.casingPopup = PopUpButton((210, firstCheckY + checkOffsetY *4, 90, 20), casingNameList)
		self.w.casingPopup.set(self.casingPopup)
		# enable or disable the casing PopUp depending on the casing CheckBox
		if self.w.casingCheck.get() and self.w.casingCheck.isEnable():
			self.w.casingPopup.enable(True)
		else:
			self.w.casingPopup.enable(False)

		self.nsTextField = self.w.chars.getNSTextField()
		self.w.setDefaultButton(self.w.button)
		self.w.bind("close", self.windowClose)
		self.w.open()
		self.w.makeKey()

	def windowClose(self, sender):
		self.saveExtensionDefaults()

	def saveExtensionDefaults(self):
		setExtensionDefault("%s.%s" % (extensionKey, "windowPos"), self.w.getPosSize()[0:2])
		setExtensionDefault("%s.%s" % (extensionKey, "optionsVisible"), self.optionsVisible)
		setExtensionDefault("%s.%s" % (extensionKey, "chars"), self.w.chars.get())
		setExtensionDefault("%s.%s" % (extensionKey, "sliderValue"), int(self.w.slider.get()))
		setExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex"), int(self.w.scriptsPopup.get()))
		setExtensionDefault("%s.%s" % (extensionKey, "langsIndex"), int(self.w.langsPopup.get()))
		setExtensionDefault("%s.%s" % (extensionKey, "punctCheck"), self.w.punctCheck.get())
		setExtensionDefault("%s.%s" % (extensionKey, "figsCheck"), self.w.figsCheck.get())
		setExtensionDefault("%s.%s" % (extensionKey, "figsPopup"), self.w.figsPopup.get())
		setExtensionDefault("%s.%s" % (extensionKey, "trimCheck"), self.w.trimCheck.get())
		setExtensionDefault("%s.%s" % (extensionKey, "caseCheck"), self.w.caseCheck.get())
		setExtensionDefault("%s.%s" % (extensionKey, "casingCheck"), self.w.casingCheck.get())
		setExtensionDefault("%s.%s" % (extensionKey, "casingPopup"), self.w.casingPopup.get())

	def buttonCallback(self, sender):
		sender.enable(False)
		self.w.spinner.start()
		self.getText()
		self.w.spinner.stop()
		sender.enable(True)

	def optionsCallback(self, sender):
		sign = sender.getTitle()
		if sign == "+":
			sender.setTitle("-")
			self.w.resize(self.windowWidth, self.windowHeightWithOptions, animate=True)
			self.optionsVisible = True
		else:
			sender.setTitle("+")
			self.w.resize(self.windowWidth, self.windowHeightWithoutOptions, animate=True)
			self.optionsVisible = False

	def charsCallback(self, sender):
		charsContent = sender.get()
		if len(charsContent):
			self.w.button.enable(True)
			nsTextView = self.nsTextField.currentEditor() # NOTE: the field editor is only available when NSTextField is in editing mode.

			# when only one glyph is selected and copied, the contents of the clipboard are the glyph's XML
			# instead of its unicode character or its name; therefore, post-process the pasted content.
			if xmlHeader in charsContent:
				caretIndex = charsContent.index(xmlHeader)
				codepointString = re_glyphUnicode.search(charsContent)
				glyphName = re_glyphName.search(charsContent)

				if codepointString:
					replacement = unichr(eval('0x' + codepointString.group(1)))
				elif glyphName:
					replacement = '/' + glyphName.group(1)
				else:
					replacement = ''

				# replace the glyph's XML by its unicode character or its name
				self.w.chars.set(re_glyph.sub(replacement, charsContent))
				# restore the location of the caret
				location = caretIndex + len(replacement)
				nsTextView.setSelectedRange_((location, 0))
				# update the variable
				charsContent = sender.get()

			caretIndex = nsTextView.selectedRanges()[0].rangeValue().location

			# Limit the number of characters
			numeralWasFound = self.stringHasNumeral(charsContent)
			if len(charsContent) > maxChars or numeralWasFound:
				NSBeep()
				if numeralWasFound:
					self.showMessage("Sorry, numerals are not allowed.", "")
				else:
					self.showMessage("You've reached the maximum \rnumber of characters.", "The limit is %d." % maxChars)
				# restore the content of chars EditText to the previous string
				sender.set(self.previousChars)
				# restore the focus on the chars EditText and restore the location of the caret
				caretIndexAdjust = len(self.previousChars) - len(charsContent)
				self.w.getNSWindow().makeFirstResponder_(self.nsTextField)
				nsTextView.setSelectedRange_((caretIndex + caretIndexAdjust, 0))

			# update the stored string
			self.previousChars = sender.get()

		else:
			self.w.button.enable(False)

	def sliderCallback(self, sender):
		self.w.wordCount.set(int(sender.get()))

	def scriptsCallback(self, sender):
		self.w.langsPopup.setItems(langsNameDict[scriptsNameList[sender.get()]])
		# toggle RTL/LTR
		if scriptsNameList[sender.get()] in rightToLeftList:
			self.scriptIsRTL = True
			self.nsTextField.setBaseWritingDirection_(NSWritingDirectionRightToLeft)
			self.nsTextField.setAlignment_(NSRightTextAlignment)
		else:
			self.scriptIsRTL = False
			self.nsTextField.setBaseWritingDirection_(NSWritingDirectionLeftToRight)
			self.nsTextField.setAlignment_(NSLeftTextAlignment)
		# restore the focus on the chars EditText
		self.w.getNSWindow().makeFirstResponder_(self.nsTextField)
		# toggle figsPopup
		if scriptsNameList[sender.get()] in enableFigOptionList:
			self.w.figsPopup.show(True)
			if self.w.figsCheck.get():
				self.w.figsPopup.enable(True)
			else:
				self.w.figsPopup.enable(False)
		else:
			self.w.figsPopup.show(False)
		# toggle trimCheck
		if scriptsNameList[sender.get()] in enableTrimCheckList:
			self.w.trimCheck.enable(True)
		else:
			self.w.trimCheck.enable(False)
		# toggle caseCheck and casingCheck
		if scriptsNameList[sender.get()] in enableCaseCheckList:
			self.w.caseCheck.enable(True)
			self.w.casingCheck.enable(True)
			if self.w.casingCheck.get():
				self.w.casingPopup.enable(True)
		else:
			self.w.caseCheck.enable(False)
			self.w.casingCheck.enable(False)
			self.w.casingPopup.enable(False)

	def figsCallback(self, sender):
		if sender.get():
			self.w.figsPopup.enable(True)
		else:
			self.w.figsPopup.enable(False)

	def casingCallback(self, sender):
		if sender.get():
			self.w.casingPopup.enable(True)
		else:
			self.w.casingPopup.enable(False)

	def stringHasNumeral(self, string):
		if re_numeral.search(string):
			return True
		return False

	def isConnected(self):
		try:
			urlopen(url, timeout=3)
			return True
		except URLError:
			pass
		return False

	def getText(self):
		if CurrentFont() is None:
			NSBeep()
			self.showMessage("Open a font first.", "")
			return

		if not self.isConnected():
			NSBeep()
			self.showMessage("Required internet connection not found.", "")
			return

		values = {'chars' : self.w.chars.get().encode('utf-8'),
				  'script' : scriptsTagDict[scriptsNameList[self.w.scriptsPopup.get()]],
				  'tb' : langsTagDict[langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]][self.w.langsPopup.get()]] }

		if self.w.punctCheck.get():
			values['punct'] = True
		if self.w.figsCheck.get():
			values['figs'] = True
			if self.w.figsPopup.isVisible():
				figsOptTagsList = ["dflt", "locl"]
				values['figsOpt'] = figsOptTagsList[self.w.figsPopup.get()]
		if self.w.trimCheck.get() and self.w.trimCheck.isEnable():
			values['trim'] = True
		if self.w.caseCheck.get() and self.w.caseCheck.isEnable():
			values['case'] = True
		if self.w.casingCheck.get() and self.w.casingCheck.isEnable():
			values['casing'] = casingNameList[self.w.casingPopup.get()].lower()

		data = urlencode(values)
		data = data.encode('utf-8')
		print(data)
		request = Request(url, data)
		response = urlopen(request)
		text = response.read()
		textU = unicode(text, 'utf-8')

		if (msgStr in textU):
			textU = textU.replace(msgStr, "")
			NSBeep()
			self.showMessage(textU, "")
			return

		elif (wrnStr in textU):
			resultIndex = textU.find(rsltStr)
			secmsgIndex = textU.find(sndStr)
			frstmsgU = textU[:secmsgIndex].replace(wrnStr, "")
			scndmsgU = textU[secmsgIndex:resultIndex].replace(sndStr, "")
			textU = textU[resultIndex:].replace(rsltStr, "")
			NSBeep()
			self.showMessage(frstmsgU, scndmsgU)

		textList = textU.split()
		trimmedText = ' '.join(textList[:int(self.w.slider.get())])

		if CurrentSpaceCenter() is None:
			OpenSpaceCenter(CurrentFont(), newWindow=False)

		sp = CurrentSpaceCenter()
		print(trimmedText)
		sp.setRaw(trimmedText)

		# Toggle RTL-LTR
		try:
			sp.setLeftToRight(not self.scriptIsRTL)
			sp.setInputWritingDirection('Right to Left' if self.scriptIsRTL else 'Left to Right')
		except AttributeError:
			pass

		return
class CornerController:

    def __init__(self):
        self.modifiedGlyph = None
        self.w = FloatingWindow((400, 170), 'Corner Tool')
        self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor())
        self.modes = ['Break', 'Build','Pit']
        self.objectTypes = {'Build':'Segment', 'Break':'Corner point', 'Pit':'Corner point'}
        self.parameters = {
            'radius': VanillaSingleValueParameter('radius', 20, (-200, 200), numType='int'),
            'roundness': VanillaSingleValueParameter('roundness', 1.25, (0, 4), numType='float'),
            'depth': VanillaSingleValueParameter('depth', 30, (-100, 100), numType='int'),
            'breadth': VanillaSingleValueParameter('breadth', 30, (0, 150), numType='int'),
            'bottom': VanillaSingleValueParameter('bottom', 5, (0, 40), numType='int')
        }
        self.currentMode = 'Break'
        self.previewGlyph = None

        self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode)
        for i, mode in enumerate(self.modes):
            setattr(self.w, mode, Group((120, 15, -15, -15)))
            modeGroup = getattr(self.w, mode)
            modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u'>', callback=self.apply)
            modeGroup.infoBox = Box((0, 0, -50, 35))
            modeGroup.info = TextBox((10, 8, -50, 20), 'No selection')
            if i > 0: modeGroup.show(False)

        self.w.Break.radius = ParameterSliderTextInput(self.parameters['radius'], (0, 60, -25, 25), title='Radius', callback=self.makePreviewGlyph)
        self.w.Break.roundness = ParameterSliderTextInput(self.parameters['roundness'], (0, 95, -25, 25), title='Roundness', callback=self.makePreviewGlyph)

        self.w.Pit.depth = ParameterSliderTextInput(self.parameters['depth'], (0, 50, -25, 25), title='Depth', callback=self.makePreviewGlyph)
        self.w.Pit.breadth = ParameterSliderTextInput(self.parameters['breadth'], (0, 80, -25, 25), title='Breadth', callback=self.makePreviewGlyph)
        self.w.Pit.bottom = ParameterSliderTextInput(self.parameters['bottom'], (0, 110, -25, 25), title='bottom', callback=self.makePreviewGlyph)

        addObserver(self, 'preview', 'draw')
        addObserver(self, 'preview', 'drawInactive')
        addObserver(self, 'previewSolid', 'drawPreview')
        addObserver(self, 'makePreviewGlyph', 'mouseDown')
        addObserver(self, 'makePreviewGlyph', 'mouseDragged')
        addObserver(self, 'makePreviewGlyph', 'keyDown')
        addObserver(self, 'makePreviewGlyph', 'keyUp')
        addObserver(self, 'setControls', 'mouseUp')
        addObserver(self, 'setControls', 'selectAll')
        addObserver(self, 'setControls', 'deselectAll')
        addObserver(self, 'setControls', 'currentGlyphChanged')
        self.w.bind('close', self.windowClose)
        self.setControls()
        self.w.open()

    def changeMode(self, sender):
        index = sender.get()
        previousModeGroup = getattr(self.w, self.currentMode)
        previousModeGroup.show(False)
        self.currentMode = self.modes[index]
        modeGroup = getattr(self.w, self.currentMode)
        modeGroup.show(True)
        self.setControls()

    def setControls(self, notification=None):
        mode = self.currentMode
        selection = self.getSelection()
        modeGroup = getattr(self.w, mode)
        if not len(selection):
            modeGroup.apply.enable(False)
            modeGroup.info.set('No selection (%ss)'%(self.objectTypes[mode].lower()))
        elif len(selection):
            modeGroup.apply.enable(True)
            info = '%s valid %s'%(len(selection), self.objectTypes[mode].lower())
            if len(selection) > 1: info += 's'
            modeGroup.info.set(info)
        self.makePreviewGlyph()

    def getSelection(self, notification=None):
        glyph = CurrentGlyph()
        if len(glyph.selection) == 0:
            return []
        elif len(glyph.selection) > 0:
            iG = IntelGlyph(glyph)
            if self.currentMode == 'Build':
                selection = iG.getSelection(True)
            elif self.currentMode in ['Break', 'Pit']:
                selection = [point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi/18)]
            return selection

    def preview(self, notification):
        sc = notification['scale']
        if self.previewGlyph is not None:
            self.previewGlyph.drawPreview(sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor)

    def previewSolid(self, notification):
        sc = notification['scale']
        if self.previewGlyph is not None:
            self.previewGlyph.drawPreview(sc, plain=True)

    def makePreviewGlyph(self, sender=None):
        if (sender is not None) and isinstance(sender, dict):
            if sender.has_key('notificationName') and sender['notificationName'] == 'mouseDragged':
                g = sender['glyph']
                if not len(g.selection):
                    return
        self.previewGlyph = self.makeCornerGlyph()
        UpdateCurrentGlyphView()

    def makeCornerGlyph(self, sender=None):
        mode = self.currentMode
        if mode == 'Build':
            cornerGlyph = self.buildCorners()
        elif mode == 'Break':
            cornerGlyph = self.breakCorners()
        elif mode == 'Pit':
            cornerGlyph = self.pitCorners()
        return cornerGlyph

    def buildCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        for contour in iG:
            segments = contour.collectSegments()['selection']
            l = len(segments)
            lines, curves = self.checkComposition(segments)
            if l > 1 and lines and curves:
                segments = [segment for segment in segments if len(segment) == 4]
            elif l > 1 and lines and not curves:
                segments = segments[:1] + segments[-1:]
            for segment in reversed(segments):
                contour.buildCorner(segment)
        return iG

    def breakCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        radius = self.parameters['radius'].get()
        roundness = self.parameters['roundness'].get()
        for contour in iG:
            selection = contour.getSelection()
            for point in selection:
                contour.breakCorner(point, radius, velocity=roundness)
            contour.correctSmoothness()
        return iG

    def pitCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        depth = self.parameters['depth'].get()
        breadth = self.parameters['breadth'].get()
        bottom = self.parameters['bottom'].get()
        for contour in iG:
            selection = contour.getSelection()
            for point in selection:
                contour.pitCorner(point, depth, breadth, bottom)
            contour.removeOverlappingPoints()
            contour.correctSmoothness()
        return iG

    def apply(self, sender):
        targetGlyph = CurrentGlyph()
        modifiedGlyph = self.makeCornerGlyph()
        targetGlyph.prepareUndo('un.round')
        targetGlyph.clearContours()
        for p in targetGlyph.selection:
            p.selected = False
        pen = targetGlyph.getPointPen()
        modifiedGlyph.drawPoints(pen)
        targetGlyph.performUndo()
        targetGlyph.update()

    def checkComposition(self, segmentsList):
        lines = 0
        curves = 0
        for segment in segmentsList:
            if len(segment) == 2:
                lines += 1
            elif len(segment) == 4:
                curves += 1
        return lines, curves

    def windowClose(self, notification):
        removeObserver(self, 'draw')
        removeObserver(self, 'drawInactive')
        removeObserver(self, 'drawPreview')
        removeObserver(self, 'mouseUp')
        removeObserver(self, 'mouseDown')
        removeObserver(self, 'mouseDragged')
        removeObserver(self, 'keyDown')
        removeObserver(self, 'keyUp')
        removeObserver(self, 'selectAll')
        removeObserver(self, 'deselectAll')
        removeObserver(self, 'currentGlyphChanged')
Example #7
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")
class CornerController:

    def __init__(self):
        self.modifiedGlyph = None
        self.w = FloatingWindow((400, 170), 'Corner Tool')
        self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor())
        self.modes = ['Break', 'Build','Pit']
        self.objectTypes = {'Build':'Segment', 'Break':'Corner point', 'Pit':'Corner point'}
        self.parameters = {
            'radius': VanillaSingleValueParameter('radius', 20, (-200, 200), numType='int'),
            'roundness': VanillaSingleValueParameter('roundness', 1.25, (0, 4), numType='float'),
            'depth': VanillaSingleValueParameter('depth', 30, (-100, 100), numType='int'),
            'breadth': VanillaSingleValueParameter('breadth', 30, (0, 150), numType='int'),
            'bottom': VanillaSingleValueParameter('bottom', 5, (0, 40), numType='int')
        }
        self.currentMode = 'Break'
        self.previewGlyph = None

        self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode)
        for i, mode in enumerate(self.modes):
            setattr(self.w, mode, Group((120, 15, -15, -15)))
            modeGroup = getattr(self.w, mode)
            modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u'>', callback=self.apply)
            modeGroup.infoBox = Box((0, 0, -50, 35))
            modeGroup.info = TextBox((10, 8, -50, 20), 'No selection')
            if i > 0: modeGroup.show(False)

        self.w.Break.radius = ParameterSliderTextInput(self.parameters['radius'], (0, 60, -25, 25), title='Radius', callback=self.makePreviewGlyph)
        self.w.Break.roundness = ParameterSliderTextInput(self.parameters['roundness'], (0, 95, -25, 25), title='Roundness', callback=self.makePreviewGlyph)

        self.w.Pit.depth = ParameterSliderTextInput(self.parameters['depth'], (0, 50, -25, 25), title='Depth', callback=self.makePreviewGlyph)
        self.w.Pit.breadth = ParameterSliderTextInput(self.parameters['breadth'], (0, 80, -25, 25), title='Breadth', callback=self.makePreviewGlyph)
        self.w.Pit.bottom = ParameterSliderTextInput(self.parameters['bottom'], (0, 110, -25, 25), title='bottom', callback=self.makePreviewGlyph)

        addObserver(self, 'preview', 'draw')
        addObserver(self, 'preview', 'drawInactive')
        addObserver(self, 'makePreviewGlyph', 'mouseDown')
        addObserver(self, 'makePreviewGlyph', 'mouseDragged')
        addObserver(self, 'makePreviewGlyph', 'keyDown')
        addObserver(self, 'makePreviewGlyph', 'keyUp')
        addObserver(self, 'setControls', 'mouseUp')
        addObserver(self, 'setControls', 'selectAll')
        addObserver(self, 'setControls', 'deselectAll')
        addObserver(self, 'setControls', 'currentGlyphChanged')
        self.w.bind('close', self.windowClose)
        self.setControls()
        self.w.open()

    def changeMode(self, sender):
        index = sender.get()
        previousModeGroup = getattr(self.w, self.currentMode)
        previousModeGroup.show(False)
        self.currentMode = self.modes[index]
        modeGroup = getattr(self.w, self.currentMode)
        modeGroup.show(True)
        self.setControls()

    def setControls(self, notification=None):
        mode = self.currentMode
        selection = self.getSelection()
        modeGroup = getattr(self.w, mode)
        if not len(selection):
            modeGroup.apply.enable(False)
            modeGroup.info.set('No selection (%ss)'%(self.objectTypes[mode].lower()))
        elif len(selection):
            modeGroup.apply.enable(True)
            info = '%s valid %s'%(len(selection), self.objectTypes[mode].lower())
            if len(selection) > 1: info += 's'
            modeGroup.info.set(info)
        self.makePreviewGlyph()

    def getSelection(self, notification=None):
        glyph = CurrentGlyph()
        if len(glyph.selection) == 0:
            return []
        elif len(glyph.selection) > 0:
            iG = IntelGlyph(glyph)
            if self.currentMode == 'Build':
                selection = iG.getSelection(True)
            elif self.currentMode in ['Break', 'Pit']:
                selection = [point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi/18)]
            return selection

    def preview(self, notification):
        sc = notification['scale']
        if self.previewGlyph is not None:
            self.previewGlyph.drawPreview(sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor)

    def makePreviewGlyph(self, sender=None):
        if (sender is not None) and isinstance(sender, dict):
            if sender.has_key('notificationName') and sender['notificationName'] == 'mouseDragged':
                g = sender['glyph']
                if not len(g.selection):
                    return
        self.previewGlyph = self.makeCornerGlyph()
        UpdateCurrentGlyphView()

    def makeCornerGlyph(self, sender=None):
        mode = self.currentMode
        if mode == 'Build':
            cornerGlyph = self.buildCorners()
        elif mode == 'Break':
            cornerGlyph = self.breakCorners()
        elif mode == 'Pit':
            cornerGlyph = self.pitCorners()
        return cornerGlyph

    def buildCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        for contour in iG:
            segments = contour.collectSegments()['selection']
            l = len(segments)
            lines, curves = self.checkComposition(segments)
            if l > 1 and lines and curves:
                segments = [segment for segment in segments if len(segment) == 4]
            elif l > 1 and lines and not curves:
                segments = segments[:1] + segments[-1:]
            for segment in reversed(segments):
                contour.buildCorner(segment)
        return iG

    def breakCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        radius = self.parameters['radius'].get()
        roundness = self.parameters['roundness'].get()
        for contour in iG:
            selection = contour.getSelection()
            for point in selection:
                contour.breakCorner(point, radius, velocity=roundness)
            contour.correctSmoothness()
        return iG

    def pitCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        depth = self.parameters['depth'].get()
        breadth = self.parameters['breadth'].get()
        bottom = self.parameters['bottom'].get()
        for contour in iG:
            selection = contour.getSelection()
            for point in selection:
                contour.pitCorner(point, depth, breadth, bottom)
            contour.removeOverlappingPoints()
            contour.correctSmoothness()
        return iG

    def apply(self, sender):
        targetGlyph = CurrentGlyph()
        modifiedGlyph = self.makeCornerGlyph()
        targetGlyph.prepareUndo('un.round')
        targetGlyph.clearContours()
        for p in targetGlyph.selection:
            p.selected = False
        pen = targetGlyph.getPointPen()
        modifiedGlyph.drawPoints(pen)
        targetGlyph.performUndo()
        targetGlyph.update()

    def checkComposition(self, segmentsList):
        lines = 0
        curves = 0
        for segment in segmentsList:
            if len(segment) == 2:
                lines += 1
            elif len(segment) == 4:
                curves += 1
        return lines, curves

    def windowClose(self, notification):
        removeObserver(self, 'draw')
        removeObserver(self, 'drawInactive')
        removeObserver(self, 'mouseUp')
        removeObserver(self, 'mouseDown')
        removeObserver(self, 'mouseDragged')
        removeObserver(self, 'keyDown')
        removeObserver(self, 'keyUp')
        removeObserver(self, 'selectAll')
        removeObserver(self, 'deselectAll')
        removeObserver(self, 'currentGlyphChanged')
class spacingObserver(object):
    def __init__(self):
        self.enableGroupSpacing = False
        self.popupOpen = False
        addObserver(self, 'glyphEditCallback', 'spaceCenterKeyDown')
        addObserver(self, 'glyphEditedCallback', 'spaceCenterKeyUp')
        addObserver(self, 'spaceCenterOpenCallback', 'spaceCenterDidOpen')
        addObserver(self, 'fontOpenCallback', 'fontDidOpen')
        self.previousMargins = {'left': 0, 'right': 0}

    def processMetricsGroups(self, baseGlyph=None):

        for groupName in self.metricsGroups:

            if (baseGlyph is None) and len(self.font.groups[groupName]) > 0:
                baseGlyph = self.font.groups[groupName][0]
                self.previousMargins['left'] = self.font[
                    baseGlyph].angledLeftMargin
                self.previousMargins['right'] = self.font[
                    baseGlyph].angledRightMargin

            if (metricsPrefix
                    in groupName) and (baseGlyph
                                       in self.font.groups[groupName]):
                if (leftIndicator in groupName) and (
                        self.previousMargins['left'] !=
                        self.font[baseGlyph].angledLeftMargin):
                    self.setGroupSpacing(baseGlyph,
                                         self.font.groups[groupName], 'Left')
                elif (rightIndicator in groupName) and (
                        self.previousMargins['right'] !=
                        self.font[baseGlyph].angledRightMargin):
                    self.setGroupSpacing(baseGlyph,
                                         self.font.groups[groupName], 'Right')

    def setGroupSpacing(self, baseGlyphName, group, side):

        for glyphName in group:

            baseGlyph = self.font[baseGlyphName]
            targetGlyph = self.font[glyphName]

            if glyphName is not baseGlyphName:

                if (len(targetGlyph.components) > 0) and (side == 'Left'):
                    for component in targetGlyph.components:
                        if component.baseGlyph in group:
                            component.move((self.previousMargins['left'] -
                                            baseGlyph.angledLeftMargin, 0))

                self.setSidebearing(baseGlyph, targetGlyph, side)

            elif glyphName is baseGlyphName:

                if (len(baseGlyph.components) > 0) and (side == 'Left'):
                    for component in baseGlyph.components:
                        if component.baseGlyph in group:
                            component.move((self.previousMargins['left'] -
                                            baseGlyph.angledLeftMargin, 0))

            targetGlyph.update()

    def setSidebearing(self, baseGlyph, targetGlyph, side):
        baseMargin = getattr(baseGlyph, 'angled' + side + 'Margin')
        targetMargin = getattr(targetGlyph, 'angled' + side + 'Margin')

        if targetMargin != baseMargin:
            setattr(targetGlyph, 'angled' + side + 'Margin', baseMargin)

    def getMetricsGroups(self, notification=None):
        self.font = CurrentFont()
        if self.font is not None:
            self.metricsGroups = [
                group for group in self.font.groups.keys()
                if metricsPrefix in group and leftIndicator in group
                or rightIndicator in group
            ]
            if (notification is not None) and (self.enableGroupSpacing
                                               == True):
                self.processMetricsGroups()

    def enableGroupSpacingCallback(self, sender):
        self.enableGroupSpacing = sender.get()

    def glyphEditCallback(self, notification):

        edGlyph = notification['glyph']
        self.previousMargins = {
            'width': edGlyph.width,
            'left': edGlyph.angledLeftMargin,
            'right': edGlyph.angledRightMargin
        }

    def glyphEditedCallback(self, notification):

        if self.enableGroupSpacing == True:

            edGlyph = notification['glyph']

            if self.font != CurrentFont():
                self.getMetricsGroups()

            self.processMetricsGroups(edGlyph.name)

    def spaceCenterOpenCallback(self, notification):
        if (not self.popupOpen) and (len(self.metricsGroups) > 0):
            self.w = FloatingWindow((160, 36), 'Group Spacing')
            self.w.activateGroups = CheckBox(
                (9, -27, 151, 18),
                "Activate Group spacing",
                value=self.enableGroupSpacing,
                callback=self.enableGroupSpacingCallback,
                sizeStyle="small")
            self.w.bind('close', self.windowCloseCallback)
            self.w.open()
            self.popupOpen = True

    def windowCloseCallback(self, notification):
        self.popupOpen = False

    def fontOpenCallback(self, notification):
        font = notification['font']
        font.groups.addObserver(self, 'getMetricsGroups', 'Groups.Changed')
        self.getMetricsGroups(notification)
Example #10
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 #12
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
class WurstSchreiber(object):

    def __init__(self):

        self.draw = False
        self.swap = True

        self.radius = getExtensionDefault(
            "%s.%s" % (WurstSchreiberDefaultKey, "radius"), 60)

        color = NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 0, 0, .5)
        colorValue = getExtensionDefaultColor(
            "%s.%s" % (WurstSchreiberDefaultKey, "color"), color)

        self.w = FloatingWindow((150, 170), "WurstSchreiber")
        x = 15
        y = 15
        self.w.preview = CheckBox(
            (x, y, -x, 20),
            "Preview",
            callback=self.previewChanged,
            value=True)
        y+=30
        self.w.slider = SliderGroup(
            (x, y, -x, 22), 0, 100, self.radius, callback=self.sliderChanged)
        y+=35
        self.w.color = ColorWell(
            (x, y, -x, 40), callback=self.colorChanged, color=colorValue)
        y+=55
        self.w.button = Button(
            (x, y, -x, 20), "Trace!", callback=self.traceButton)
        addObserver(self, "drawWurst", "drawBackground")
        self.w.bind("close", self.closing)
        self.w.open()

    def closing(self, sender):
        removeObserver(self, "drawBackground")

    def previewChanged(self, sender):
        UpdateCurrentGlyphView()

    def sliderChanged(self, sender):
        self.radius = int(sender.get())
        setExtensionDefault(
            "%s.%s" % (WurstSchreiberDefaultKey, "radius"), self.radius)
        UpdateCurrentGlyphView()

    def colorChanged(self, sender):
        setExtensionDefaultColor(
            "%s.%s" % (WurstSchreiberDefaultKey, "color"), sender.get())
        UpdateCurrentGlyphView()

    def getColor(self):
        color = self.w.color.get()
        return color.getRed_green_blue_alpha_(None, None, None, None)

    def traceButton(self, sender):
        if self.w.preview.get():
            self.draw = True
            UpdateCurrentGlyphView()

    def drawWurst(self, sender):
        if self.w.preview.get():
            radius = self.radius
            draw = self.draw
            r,g,b,a = self.getColor()
            fill(r,g,b,a)
            glyph = CurrentGlyph()
            pen = WurstPen(None, radius, draw)
            glyph.draw(pen)
            if self.draw:
                glyph.prepareUndo("WurstTrace")
                if self.swap:
                    glyph.getLayer("background").clear()
                    glyph.swapToLayer("background")
                glyph.appendGlyph(pen.glyphcopy)
                self.draw = False
                self.w.preview.set(False)
                glyph.performUndo()
            glyph.update()
class CornerController:
    def __init__(self):
        self.modifiedGlyph = None
        self.w = FloatingWindow((400, 170), "Corner Tool")
        self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor())
        self.modes = ["Break", "Build", "Pit"]
        self.objectTypes = {"Build": "Segment", "Break": "Corner point", "Pit": "Corner point"}
        self.parameters = {
            "radius": VanillaSingleValueParameter("radius", 20, (-200, 200), numType="int"),
            "roundness": VanillaSingleValueParameter("roundness", 1.25, (0, 4), numType="float"),
            "depth": VanillaSingleValueParameter("depth", 30, (-100, 100), numType="int"),
            "breadth": VanillaSingleValueParameter("breadth", 30, (0, 150), numType="int"),
            "bottom": VanillaSingleValueParameter("bottom", 5, (0, 40), numType="int"),
        }
        self.currentMode = "Break"
        self.previewGlyph = None

        self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode)
        for i, mode in enumerate(self.modes):
            setattr(self.w, mode, Group((120, 15, -15, -15)))
            modeGroup = getattr(self.w, mode)
            modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u">", callback=self.apply)
            modeGroup.infoBox = Box((0, 0, -50, 35))
            modeGroup.info = TextBox((10, 8, -50, 20), "No selection")
            if i > 0:
                modeGroup.show(False)

        self.w.Break.radius = ParameterSliderTextInput(
            self.parameters["radius"], (0, 60, -25, 25), title="Radius", callback=self.makePreviewGlyph
        )
        self.w.Break.roundness = ParameterSliderTextInput(
            self.parameters["roundness"], (0, 95, -25, 25), title="Roundness", callback=self.makePreviewGlyph
        )

        self.w.Pit.depth = ParameterSliderTextInput(
            self.parameters["depth"], (0, 50, -25, 25), title="Depth", callback=self.makePreviewGlyph
        )
        self.w.Pit.breadth = ParameterSliderTextInput(
            self.parameters["breadth"], (0, 80, -25, 25), title="Breadth", callback=self.makePreviewGlyph
        )
        self.w.Pit.bottom = ParameterSliderTextInput(
            self.parameters["bottom"], (0, 110, -25, 25), title="bottom", callback=self.makePreviewGlyph
        )

        addObserver(self, "preview", "draw")
        addObserver(self, "preview", "drawInactive")
        addObserver(self, "previewSolid", "drawPreview")
        addObserver(self, "makePreviewGlyph", "mouseDown")
        addObserver(self, "makePreviewGlyph", "mouseDragged")
        addObserver(self, "makePreviewGlyph", "keyDown")
        addObserver(self, "makePreviewGlyph", "keyUp")
        addObserver(self, "setControls", "mouseUp")
        addObserver(self, "setControls", "selectAll")
        addObserver(self, "setControls", "deselectAll")
        addObserver(self, "setControls", "currentGlyphChanged")
        self.w.bind("close", self.windowClose)
        self.setControls()
        self.w.open()

    def changeMode(self, sender):
        index = sender.get()
        previousModeGroup = getattr(self.w, self.currentMode)
        previousModeGroup.show(False)
        self.currentMode = self.modes[index]
        modeGroup = getattr(self.w, self.currentMode)
        modeGroup.show(True)
        self.setControls()

    def setControls(self, notification=None):
        mode = self.currentMode
        selection = self.getSelection()
        modeGroup = getattr(self.w, mode)
        if not len(selection):
            modeGroup.apply.enable(False)
            modeGroup.info.set("No selection (%ss)" % (self.objectTypes[mode].lower()))
        elif len(selection):
            modeGroup.apply.enable(True)
            info = "%s valid %s" % (len(selection), self.objectTypes[mode].lower())
            if len(selection) > 1:
                info += "s"
            modeGroup.info.set(info)
        self.makePreviewGlyph()

    def getSelection(self, notification=None):
        glyph = CurrentGlyph()
        if len(glyph.selection) == 0:
            return []
        elif len(glyph.selection) > 0:
            iG = IntelGlyph(glyph)
            if self.currentMode == "Build":
                selection = iG.getSelection(True)
            elif self.currentMode in ["Break", "Pit"]:
                selection = [
                    point
                    for point in iG.getSelection()
                    if (point.segmentType is not None) and (abs(point.turn()) > pi / 18)
                ]
            return selection

    def preview(self, notification):
        sc = notification["scale"]
        if self.previewGlyph is not None:
            self.previewGlyph.drawPreview(
                sc,
                styleFill=True,
                showNodes=False,
                strokeWidth=2,
                fillColor=cornerOutlineSoftColor,
                strokeColor=cornerOutlineStrongColor,
            )

    def previewSolid(self, notification):
        sc = notification["scale"]
        if self.previewGlyph is not None:
            self.previewGlyph.drawPreview(sc, plain=True)

    def makePreviewGlyph(self, sender=None):
        if (sender is not None) and isinstance(sender, dict):
            if sender.has_key("notificationName") and sender["notificationName"] == "mouseDragged":
                g = sender["glyph"]
                if not len(g.selection):
                    return
        self.previewGlyph = self.makeCornerGlyph()
        UpdateCurrentGlyphView()

    def makeCornerGlyph(self, sender=None):
        mode = self.currentMode
        if mode == "Build":
            cornerGlyph = self.buildCorners()
        elif mode == "Break":
            cornerGlyph = self.breakCorners()
        elif mode == "Pit":
            cornerGlyph = self.pitCorners()
        return cornerGlyph

    def buildCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        for contour in iG:
            segments = contour.collectSegments()["selection"]
            l = len(segments)
            lines, curves = self.checkComposition(segments)
            if l > 1 and lines and curves:
                segments = [segment for segment in segments if len(segment) == 4]
            elif l > 1 and lines and not curves:
                segments = segments[:1] + segments[-1:]
            for segment in reversed(segments):
                contour.buildCorner(segment)
        return iG

    def breakCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        radius = self.parameters["radius"].get()
        roundness = self.parameters["roundness"].get()
        for contour in iG:
            selection = contour.getSelection()
            for point in selection:
                contour.breakCorner(point, radius, velocity=roundness)
            contour.correctSmoothness()
        return iG

    def pitCorners(self):
        g = CurrentGlyph()
        iG = IntelGlyph(g)
        depth = self.parameters["depth"].get()
        breadth = self.parameters["breadth"].get()
        bottom = self.parameters["bottom"].get()
        for contour in iG:
            selection = contour.getSelection()
            for point in selection:
                contour.pitCorner(point, depth, breadth, bottom)
            contour.removeOverlappingPoints()
            contour.correctSmoothness()
        return iG

    def apply(self, sender):
        targetGlyph = CurrentGlyph()
        modifiedGlyph = self.makeCornerGlyph()
        targetGlyph.prepareUndo("un.round")
        targetGlyph.clearContours()
        for p in targetGlyph.selection:
            p.selected = False
        pen = targetGlyph.getPointPen()
        modifiedGlyph.drawPoints(pen)
        targetGlyph.performUndo()
        targetGlyph.update()

    def checkComposition(self, segmentsList):
        lines = 0
        curves = 0
        for segment in segmentsList:
            if len(segment) == 2:
                lines += 1
            elif len(segment) == 4:
                curves += 1
        return lines, curves

    def windowClose(self, notification):
        removeObserver(self, "draw")
        removeObserver(self, "drawInactive")
        removeObserver(self, "drawPreview")
        removeObserver(self, "mouseUp")
        removeObserver(self, "mouseDown")
        removeObserver(self, "mouseDragged")
        removeObserver(self, "keyDown")
        removeObserver(self, "keyUp")
        removeObserver(self, "selectAll")
        removeObserver(self, "deselectAll")
        removeObserver(self, "currentGlyphChanged")
class ProofGroupInspector:
    def __init__(self, proofGroup):
        """
        Initialize inspector with proofGroup data.
        proofGroup is a dictionary passed in by ProofDrawer().
        """
        self.proofGroup = proofGroup
        self.editedProofGroup = {}

        left = 10
        row = 10
        textboxWidth = 92
        leftEditText = left + 95
        pointSizes = ["6", "8", "10", "12", "14", "18",\
                      "21", "24", "36", "48", "60", "72"]

        self.w = FloatingWindow(
            (400, 275), "Edit Proof Group: %s" % self.proofGroup["name"])

        self.w.groupName = TextBox((left, row + 2, textboxWidth, 20),
                                   "Group name:",
                                   alignment="right")

        self.w.groupNameEdit = EditText((leftEditText, row, -10, 22),
                                        self.proofGroup["name"])

        row += 33
        self.w.typeSize = TextBox((left, row + 2, textboxWidth, 20),
                                  "Type size (pt):",
                                  alignment="right")

        self.w.typeSizeEdit = ComboBox((leftEditText, row, 55, 22),
                                       pointSizes,
                                       continuous=True,
                                       callback=self._checkFloat)

        self.w.typeSizeEdit.set(self.proofGroup["typeSize"])

        self.w.leading = TextBox((leftEditText + 80, row + 2, 60, 22),
                                 "Leading:")

        self.w.leadingEdit = ComboBox((leftEditText + 139, row, 55, 22),
                                      pointSizes,
                                      continuous=True,
                                      callback=self._checkFloat)

        self.w.leadingEdit.set(self.proofGroup["leading"])

        row += 33
        self.w.contents = TextBox((left, row, textboxWidth, 20),
                                  "Contents:",
                                  alignment="right")

        self.w.contentsEdit = TextEditor(
            (leftEditText, row, -10, 150),
            "\n".join(self.proofGroup["contents"]))
        self.w.contentsEdit.getNSTextView().setFont_(monoFont)

        row += 160
        self.w.cancelButton = Button((leftEditText, row, 138, 20),
                                     "Cancel",
                                     callback=self.cancelCB)

        leftEditText += 147
        self.w.okButton = Button((leftEditText, row, 138, 20),
                                 "OK",
                                 callback=self.okCB)

        self.w.setDefaultButton(self.w.okButton)
        self.w.bind("close", self._postCloseEvent)

    def _postCloseEvent(self, sender):
        postEvent("com.InspectorClosed")

    def _checkFloat(self, sender):
        """
        Make sure users don't input non-floats by capturing
        value prior to new input, then using it
        if user tries to input an illegal character
        """
        # pass
        # Store everything up to newly-typed character
        allButLast = sender.get()[:-1]
        try:
            float(sender.get())

            # Get rid of whitespaces immediately
            sender.set(sender.get().strip())
        except ValueError:
            sender.set(allButLast)

    def okCB(self, sender):
        """
        Get everything from fields, save in self.editedProofGroup dict,
        post event, pass the edited group to observer, and close window
        """
        self.editedProofGroup["name"] = self.w.groupNameEdit.get().strip()
        self.editedProofGroup["typeSize"] = self.w.typeSizeEdit.get()
        self.editedProofGroup["leading"] = self.w.leadingEdit.get()
        self.editedProofGroup["print"] = self.proofGroup[
            "print"]  # just pass this back for now
        self.editedProofGroup["contents"] = hf.makeCleanListFromStr(
            self.w.contentsEdit.get())

        postEvent("com.ProofGroupEdited",
                  editedProofGroup=self.editedProofGroup)
        self.w.close()

    def cancelCB(self, sender):
        self.w.close()
Example #16
0
class ToucheTool():
    
    def __init__(self):
        NSUserDefaults.standardUserDefaults().registerDefaults_({"ToucheWindowHeight":340})
        self.windowHeight = NSUserDefaults.standardUserDefaults().integerForKey_("ToucheWindowHeight")
        self.minWindowHeight = 340
        if self.windowHeight < self.minWindowHeight:
            self.windowHeight = self.minWindowHeight
        self.closedWindowHeight = 100
        self.w = FloatingWindow((180, self.windowHeight), u'Touché!', minSize=(180,340), maxSize=(250,898))
        self.w.bind("resize", self.windowResized)
        self.isResizing = False
        p = 10
        w = 160
        
        # options
        self.w.options = Group((0, 0, 180, 220))
        
        buttons = {
            "checkSelBtn": {"text": "Check selected glyphs", "callback": self.checkSel, "y": p},
        }
        for button, data in buttons.iteritems():
            setattr(self.w.options, button, 
            Button((p, data["y"], w - 22, 22), data["text"], callback=data["callback"], sizeStyle="small"))
            
        self.w.options.zeroCheck = CheckBox((p, 35, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small")
        self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16), sizeStyle="small")
        
        # results
        self.w.results = Group((0, 220, 180, -0))
        self.w.results.show(False)
        
        textBoxes = {"stats": -34, "result": -18}
        for box, y in textBoxes.iteritems():
            setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small"))
            
        # list and preview 
        self.w.outputList = List((0,58,-0,-40),
            [{"left glyph": "", "right glyph": ""}], columnDescriptions=[{"title": "left glyph", "width": 90}, {"title": "right glyph"}],
            showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair)
        self.w.outputList._setColumnAutoresizing()
        self._resizeWindow(False)
        self.w.open()
    
    
    # callbacks
        
    def checkAll(self, sender=None):
        self.check(useSelection=False)
        
    def checkSel(self, sender=None):
        self.check(useSelection=True)
        
    def check(self, useSelection):
        self._resizeWindow(enlarge=False)
        self.checkFont(useSelection=useSelection, excludeZeroWidth=self.w.options.zeroCheck.get())
    
    def showPair(self, sender=None):
        try:
            index = sender.getSelection()[0]
            glyphs = [self.f[gName] for gName in self.touchingPairs[index]]
            ActiveFont = self.f._font
            EditViewController = ActiveFont.currentTab
            if EditViewController is None:
                tabText = "/%s/%s" %(glyphs[0].name, glyphs[1].name)
                ActiveFont.newTab(tabText)
            else:
                textStorage = EditViewController.graphicView()
                if not hasattr(textStorage, "replaceCharactersInRange_withString_"): # compatibility with API change in 2.5
                    textStorage = EditViewController.graphicView().textStorage()
                LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object)
                RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object)
                if LeftChar != 0 and RightChar != 0:
                    selection = textStorage.selectedRange()
                    if selection.length < 2:
                        selection.length = 2
                        if selection.location > 0:
                            selection.location -= 1
                    
                    NewString = ""
                    if LeftChar < 0xffff and RightChar < 0xffff:
                        NewString = u"%s%s" % (unichr(LeftChar), unichr(RightChar))
                    else:
                        print "Upper plane codes are not supported yet"
                    
                    textStorage.replaceCharactersInRange_withString_(selection, NewString)
                    selection.length = 0
                    selection.location += 1
                    textStorage.setSelectedRange_(selection)
            #self.w.preview.set(glyphs)
        except IndexError:
            pass
    
                
    # checking
        
    def _hasSufficientWidth(self, g):
        # to ignore combining accents and the like
        if self.excludeZeroWidth:
            # also skips 1-unit wide glyphs which many use instead of 0
            if g.width < 2 or g._object.subCategory == "Nonspacing":
                return False
        return True
    
    def _trimGlyphList(self, glyphList):
        newGlyphList = []
        for g in glyphList:
            if g.box is not None and self._hasSufficientWidth(g):
                newGlyphList.append(g)
        return newGlyphList
    
    def windowResized(self, window):
        posSize = self.w.getPosSize()
        Height = posSize[3]
        if Height > self.closedWindowHeight and self.isResizing is False:
            print "set new Height", Height
            NSUserDefaults.standardUserDefaults().setInteger_forKey_(Height, "ToucheWindowHeight")
            self.windowHeight = Height
    
    def _resizeWindow(self, enlarge=True):
        posSize = self.w.getPosSize()
        if enlarge:
            self.w.results.show(True)
            self.w.outputList.show(True)
            targetHeight = self.windowHeight
            if targetHeight < 340:
                targetHeight = 340
        else:
            self.w.results.show(False)
            self.w.outputList.show(False)
            targetHeight = self.closedWindowHeight
        self.isResizing = True
        self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight))
        self.isResizing = False
    
    # ok let's do this

    def checkFont(self, useSelection=False, excludeZeroWidth=True):
        f = CurrentFont()
        if f is not None:
            # initialize things
            self.w.options.progress.start()
            time0 = time.time()
            self.excludeZeroWidth = excludeZeroWidth
            self.f = f
    
            glyphNames = f.selection if useSelection else f.keys()
            glyphList = [f[x] for x in glyphNames]
            glyphList = self._trimGlyphList(glyphList)
            
            self.touchingPairs = Touche(f).findTouchingPairs(glyphList)
        
            # display output
            self.w.results.stats.set("%d glyphs checked" % len(glyphList))
            self.w.results.result.set("%d touching pairs found" % len(self.touchingPairs))
            self.w.results.show(True)
            
            outputList = [{"left glyph": g1, "right glyph": g2} for (g1, g2) in self.touchingPairs]
            self.w.outputList.set(outputList)
            if len(self.touchingPairs) > 0:
                self.w.outputList.setSelection([0])
            
            #self.w.preview.setFont(f)
            self.w.options.progress.stop()
            self._resizeWindow(enlarge=True)
        
            time1 = time.time()
            print u'Touché: finished checking %d glyphs in %.2f seconds' % (len(glyphList), time1-time0)
            
        else:
            Message(u'Touché: Can’t find a font to check')
Example #17
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 #18
0
class AccentedMaker(BaseWindowController):

    fontOptions = []
    whichFont = None
    actions = ['Place Anchors', 'Build Accents']
    whichAction = actions[0]
    whichGlyphList = None
    markEditedGlyphs = False
    markColor = glyphCollectionColors[glyphCollectionColors.keys()[0]]

    uppercaseAccents = False

    def __init__(self):
        super(AccentedMaker, self).__init__()
        self.initLogger()

        self.fontOptions = ['All Fonts', 'Current Font'] + AllFonts()
        self.whichFont = self.fontOptions[0]
        self.pluginHeight = PLUGIN_HEIGHT

        self.loadAccentedData()
        self.parseGlyphListsFromAccentedData()

        firstKey = self.glyphLists[self.whichAction].keys()[0]
        self.whichGlyphList = self.glyphLists[self.whichAction][firstKey]

        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, self.pluginHeight),
                                PLUGIN_TITLE)

        self.w.sharedCtrls = SharedCtrls(
            (MARGIN_HOR, MARGIN_VER, NET_WIDTH, 104),
            fontOptions=self.fontOptions,
            whichFont=self.whichFont,
            actions=self.actions,
            whichAction=self.whichAction,
            glyphLists=self.glyphLists,
            whichGlyphList=self.whichGlyphList,
            markColor=self.markColor,
            markEditedGlyphs=self.markEditedGlyphs,
            callback=self.sharedCtrlsCallback)
        self.w.separationLine = HorizontalLine(
            (MARGIN_HOR, self.w.sharedCtrls.getPosSize()[3] + MARGIN_ROW,
             NET_WIDTH, vanillaControlsSize['HorizontalLineThickness']))

        dependantCtrlsHgt = MARGIN_VER + self.w.sharedCtrls.getPosSize(
        )[3] + MARGIN_ROW
        self.w.anchorsCtrls = AnchorsCtrls(
            (MARGIN_HOR, dependantCtrlsHgt, NET_WIDTH, 76),
            callbackAttrs=self.anchorsVarsCallback,
            placeCallback=self.anchorsPlaceCallback,
            deleteCallback=self.anchorsDeleteCallback)

        self.w.buildingCtrls = BuildingCtrls(
            (MARGIN_HOR, dependantCtrlsHgt, NET_WIDTH, 50),
            self.uppercaseAccents,
            callbackAttrs=self.buildingVarsCallback,
            callbackCheck=self.checkAccentedCallback,
            callbackBuild=self.buildAccentedCallback)
        self.w.buildingCtrls.show(False)

        addObserver(self, 'updateFontOptions', "newFontDidOpen")
        addObserver(self, 'updateFontOptions', "fontDidOpen")
        addObserver(self, 'updateFontOptions', "fontWillClose")
        self.w.bind("close", self.windowCloseCallback)
        self.setUpBaseWindowBehavior()
        self.adjustPluginHeight()
        self.w.open()

    def initLogger(self):
        # create a logger
        self.accentedLogger = logging.getLogger('accentedLogger')
        # create file handler which logs info messages
        fileBasedHandler = logging.FileHandler('accentedLettersMaker.log')
        fileBasedHandler.setLevel(logging.INFO)
        # create console handler with a higher log level, only errors
        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.ERROR)
        # create formatter and add it to the handlers
        formatter = logging.Formatter(
            u'%(asctime)s | %(levelname)s | line: %(lineno)d | %(funcName)s | %(message)s'
        )
        fileBasedHandler.setFormatter(formatter)
        consoleHandler.setFormatter(formatter)
        # add the handlers to the logger
        self.accentedLogger.addHandler(fileBasedHandler)
        self.accentedLogger.addHandler(consoleHandler)

    # deal with font data
    def prepareFontsToAction(self):
        if self.whichFont == 'All Fonts':
            fontsToProcess = AllFonts()
        elif self.whichFont == 'Current Font':
            fontsToProcess = [CurrentFont()]
        else:
            fontsToProcess = [self.whichFont]
        return fontsToProcess

    def deleteAnchors(self):
        self.accentedLogger.info(
            START_FUNC.format(funcName=self.deleteAnchors.__name__))
        fontsToProcess = self.prepareFontsToAction()
        for eachFont in fontsToProcess:
            self.accentedLogger.info(
                START_FONT.format(familyName=eachFont.info.familyName,
                                  styleName=eachFont.info.styleName))
            for eachGlyphName in self.whichGlyphList:
                eachGlyph = eachFont[eachGlyphName]
                if self.markEditedGlyphs is True:
                    if version[0] == '2':
                        eachGlyph.markColor = self.markColor
                    else:
                        eachGlyph.mark = self.markColor

                for eachAnchor in eachGlyph.anchors:
                    eachGlyph.removeAnchor(eachAnchor)
                    self.accentedLogger.info(
                        REMOVE_ANCHOR.format(anchorName=self.anchorName,
                                             glyphName=eachGlyphName))
        self.accentedLogger.info(
            END_FUNC.format(funcName=self.deleteAnchors.__name__))

    def placeAnchors(self):
        assert self.anchorName is not None, '[WARNING] no anchor name provided'
        assert self.anchorHeight is not None, '[WARNING] no anchor height provided'
        self.accentedLogger.info(
            START_FUNC.format(funcName=self.placeAnchors.__name__))

        fontsToProcess = self.prepareFontsToAction()
        for eachFont in fontsToProcess:
            self.accentedLogger.info(
                START_FONT.format(familyName=eachFont.info.familyName,
                                  styleName=eachFont.info.styleName))
            for eachGlyphName in self.whichGlyphList:
                if eachGlyphName in eachFont:
                    eachGlyph = eachFont[eachGlyphName]
                    if self.markEditedGlyphs is True:
                        if version[0] == '2':
                            eachGlyph.markColor = self.markColor
                        else:
                            eachGlyph.mark = self.markColor

                    if selectAnchorByName(eachGlyph, self.anchorName):
                        anchorToDel = selectAnchorByName(
                            eachGlyph, self.anchorName)
                        eachGlyph.removeAnchor(anchorToDel)

                    if version[0] == '2':
                        xMin, yMin, xMax, yMax = eachGlyph.bounds
                    else:
                        xMin, yMin, xMax, yMax = eachGlyph.box

                    if eachFont.info.italicAngle:
                        anchorAngle = radians(-eachFont.info.italicAngle)
                    else:
                        anchorAngle = radians(0)

                    tangentOffset = tan(anchorAngle) * self.anchorHeight
                    anchorX = (
                        eachGlyph.width - eachGlyph.angledLeftMargin -
                        eachGlyph.angledRightMargin
                    ) / 2 + eachGlyph.angledLeftMargin + tangentOffset
                    eachGlyph.appendAnchor(self.anchorName,
                                           (anchorX, self.anchorHeight))
                    self.accentedLogger.info(
                        APPEND_ANCHOR.format(anchorName=self.anchorName,
                                             anchorX=anchorX,
                                             anchorHeight=self.anchorHeight,
                                             glyphName=eachGlyphName))
                else:
                    self.accentedLogger.error(
                        GLYPH_NOT_IN_FONT.format(
                            glyphName=eachGlyphName,
                            familyName=eachFont.info.familyName,
                            styleName=eachFont.info.styleName))
        self.accentedLogger.info(
            END_FUNC.format(funcName=self.placeAnchors.__name__))

    def checkAccented(self, isPrinting=True):
        report = []
        notReady = OrderedDict()

        fontsToProcess = self.prepareFontsToAction()
        for eachFont in fontsToProcess:
            toSkip = []
            report.append('Checking {} {}'.format(eachFont.info.familyName,
                                                  eachFont.info.styleName))
            for eachAccentedName, eachBaseName, eachAccentName, eachAnchorName in self.whichGlyphList:

                # base glyph
                if eachFont.has_key(eachBaseName) is False:
                    report.append(
                        BUILD_MISSING_GLYPH.format(
                            glyphName=eachBaseName,
                            accentedName=eachAccentedName))
                    if eachAccentedName not in toSkip:
                        toSkip.append(eachAccentedName)
                else:
                    eachBaseGlyph = eachFont[eachBaseName]
                    if not eachBaseGlyph.anchors:
                        report.append(
                            NO_ANCHORS.format(glyphName=eachBaseName))
                        if eachAccentedName not in toSkip:
                            toSkip.append(eachAccentedName)
                        if version[0] == '2':
                            eachBaseGlyph.markColor = ERROR_MARK_COLOR
                        else:
                            eachBaseGlyph.mark = ERROR_MARK_COLOR
                    else:
                        for eachAnchor in eachBaseGlyph.anchors:
                            if eachAnchor.name == eachAnchorName:
                                break
                        else:
                            report.append(
                                BUILD_MISSING_ANCHOR.format(
                                    anchorName=eachAnchorName,
                                    glyphName=eachBaseName,
                                    accentedName=eachAccentedName))
                            if eachAccentedName not in toSkip:
                                toSkip.append(eachAccentedName)
                            if version[0] == '2':
                                eachBaseGlyph.markColor = ERROR_MARK_COLOR
                            else:
                                eachBaseGlyph.mark = ERROR_MARK_COLOR

                # accent
                if eachFont.has_key(eachAccentName) is False:
                    report.append(
                        BUILD_MISSING_GLYPH.format(
                            glyphName=eachAccentName,
                            accentedName=eachAccentedName))
                    if eachAccentedName not in toSkip:
                        toSkip.append(eachAccentedName)
                else:
                    eachAccentGlyph = eachFont[eachAccentName]
                    if not eachAccentGlyph.anchors:
                        report.append(
                            NO_ANCHORS.format(glyphName=eachAccentName))
                        if eachAccentedName not in toSkip:
                            toSkip.append(eachAccentedName)
                        if version[0] == '2':
                            eachAccentGlyph.markColor = ERROR_MARK_COLOR
                        else:
                            eachAccentGlyph.mark = ERROR_MARK_COLOR
                    else:
                        for eachAnchor in eachAccentGlyph.anchors:
                            if eachAnchor.name == '_{}'.format(eachAnchorName):
                                break
                        else:
                            report.append(
                                BUILD_MISSING_ANCHOR.format(
                                    anchorName=eachAnchorName,
                                    glyphName=eachAccentName,
                                    accentedName=eachAccentedName))
                            if eachAccentedName not in toSkip:
                                toSkip.append(eachAccentedName)
                            if version[0] == '2':
                                eachAccentGlyph.markColor = ERROR_MARK_COLOR
                            else:
                                eachAccentGlyph.mark = ERROR_MARK_COLOR

            notReady['{} {}'.format(eachFont.info.familyName,
                                    eachFont.info.styleName)] = toSkip
            report.append('End checking {} {}'.format(eachFont.info.familyName,
                                                      eachFont.info.styleName))
            report.append('\n\n')

        if isPrinting is True:
            self.accentedLogger.error('\n'.join(report))

        return notReady

    def buildAccented(self):
        notReady = self.checkAccented(isPrinting=False)
        self.accentedLogger.info(
            START_FUNC.format(funcName=self.buildAccented.__name__))

        fontsToProcess = self.prepareFontsToAction()
        for eachFont in fontsToProcess:
            self.accentedLogger.info(
                START_FONT.format(familyName=eachFont.info.familyName,
                                  styleName=eachFont.info.styleName))
            for eachAccentedName, eachBaseName, eachAccentName, eachAnchorName in self.whichGlyphList:
                if eachAccentedName in notReady['{} {}'.format(
                        eachFont.info.familyName, eachFont.info.styleName)]:
                    self.accentedLogger.error(
                        NOT_READY.format(fontName='{} {}'.format(
                            eachFont.info.familyName, eachFont.info.styleName),
                                         accentedName=eachAccentedName))
                    continue

                eachBaseGlyph = eachFont[eachBaseName]
                eachBaseAnchor = selectAnchorByName(eachBaseGlyph,
                                                    eachAnchorName)

                eachAccentGlyph = eachFont[eachAccentName]
                eachAccentAnchor = selectAnchorByName(
                    eachAccentGlyph, '_{}'.format(eachAnchorName))

                if eachFont.has_key(eachAccentedName) is False:
                    eachAccentedGlyph = eachFont.newGlyph(eachAccentedName)
                else:
                    eachAccentedGlyph = eachFont[eachAccentedName]
                    eachAccentedGlyph.clear()

                eachAccentedGlyph.width = eachBaseGlyph.width
                eachAccentedGlyph.appendComponent(eachBaseName, (0, 0), (1, 1))

                accentOffsetX, accentOffsetY = eachBaseAnchor.x - eachAccentAnchor.x, eachBaseAnchor.y - eachAccentAnchor.y
                eachAccentedGlyph.appendComponent(
                    eachAccentName, (accentOffsetX, accentOffsetY), (1, 1))

                self.accentedLogger.info(
                    BUILT_GLYPH.format(accentedName=eachAccentedName,
                                       baseName=eachBaseName,
                                       accentName=eachAccentName,
                                       anchorName=eachAnchorName))

                if self.markEditedGlyphs is True:
                    if version[0] == '2':
                        eachAccentedGlyph.markColor = self.markColor
                    else:
                        eachAccentedGlyph.mark = self.markColor

        self.accentedLogger.info(
            END_FUNC.format(funcName=self.buildAccented.__name__))

    # deal with table data
    def loadAccentedData(self):
        self.accentedData = [[cell.strip() for cell in row.split('\t')]
                             for row in open(TABLE_PATH, 'r').readlines()]

    def parseGlyphListsFromAccentedData(self):
        assert self.accentedData is not None
        self.glyphLists = OrderedDict()

        # anchors
        self.glyphLists['Place Anchors'] = OrderedDict()
        accentsTop = []
        _ = [
            accentsTop.append(row[2]) for row in self.accentedData
            if row[3] == 'top' and row[2] not in accentsTop
        ]
        self.glyphLists['Place Anchors']['ACC TOP'] = accentsTop

        accentsBtm = []
        _ = [
            accentsBtm.append(row[2]) for row in self.accentedData
            if row[3] == 'bottom' and row[2] not in accentsBtm
        ]
        self.glyphLists['Place Anchors']['ACC BTM'] = accentsBtm

        accentsCaseTop = ['{}.case'.format(name) for name in accentsTop]
        self.glyphLists['Place Anchors']['ACC CASE TOP'] = accentsCaseTop

        accentsCaseBtm = ['{}.case'.format(name) for name in accentsBtm]
        self.glyphLists['Place Anchors']['ACC CASE BTM'] = accentsCaseBtm

        ucBaseTop = []
        _ = [
            ucBaseTop.append(row[1]) for row in self.accentedData if
            row[1][0].isupper() and row[3] == 'top' and row[1] not in ucBaseTop
        ]
        self.glyphLists['Place Anchors']['UC TOP'] = ucBaseTop

        ucBaseBtm = []
        _ = [
            ucBaseBtm.append(row[1]) for row in self.accentedData
            if row[1][0].isupper() and row[3] == 'bottom'
            and row[1] not in ucBaseBtm
        ]
        self.glyphLists['Place Anchors']['UC BTM'] = ucBaseBtm

        lcBaseTop = []
        _ = [
            lcBaseTop.append(row[1]) for row in self.accentedData if
            row[1][0].islower() and row[3] == 'top' and row[1] not in lcBaseTop
        ]
        self.glyphLists['Place Anchors']['LC TOP'] = lcBaseTop

        lcBaseBtm = []
        _ = [
            lcBaseBtm.append(row[1]) for row in self.accentedData
            if row[1][0].islower() and row[3] == 'bottom'
            and row[1] not in lcBaseBtm
        ]
        self.glyphLists['Place Anchors']['LC BTM'] = lcBaseBtm

        # build
        self.glyphLists['Build Accents'] = OrderedDict()
        self.glyphLists['Build Accents']['ALL'] = self.accentedData
        buildUC = [
            row for row in self.accentedData if row[1][0].isupper() is True
        ]
        self.glyphLists['Build Accents']['UC'] = buildUC
        buildLC = [
            row for row in self.accentedData if row[1][0].islower() is True
        ]
        self.glyphLists['Build Accents']['LC'] = buildLC

    # ui
    def adjustPluginHeight(self):
        if self.whichAction == 'Place Anchors':
            self.pluginHeight = MARGIN_VER + self.w.sharedCtrls.getPosSize(
            )[3] + MARGIN_ROW + self.w.anchorsCtrls.getPosSize(
            )[3] + MARGIN_VER
        else:
            self.pluginHeight = MARGIN_VER + self.w.sharedCtrls.getPosSize(
            )[3] + MARGIN_ROW + self.w.buildingCtrls.getPosSize(
            )[3] + MARGIN_VER
        lft, top, wdt, hgt = self.w.getPosSize()
        self.w.resize(wdt, self.pluginHeight)

    def switchDependantCtrl(self):
        if self.whichAction == 'Place Anchors':
            self.w.anchorsCtrls.show(True)
            self.w.buildingCtrls.show(False)
        else:
            self.w.anchorsCtrls.show(False)
            self.w.buildingCtrls.show(True)

    # observers
    def updateFontOptions(self, sender):
        self.fontOptions = ['All Fonts', 'Current Font'] + AllFonts()
        self.w.sharedCtrls.setFontOptions(self.fontOptions)

    # callbacks
    def sharedCtrlsCallback(self, sender):
        self.whichFont = sender.getWhichFont()
        self.whichAction = sender.getWhichAction()
        self.switchDependantCtrl()
        self.adjustPluginHeight()

        self.whichGlyphList = sender.getWhichGlyphList()
        self.markEditedGlyphs, self.markColor = sender.getMarkEditedGlyphs()

    def anchorsVarsCallback(self, sender):
        self.anchorHeight = sender.getHeight()
        self.anchorName = sender.getName()

    def anchorsPlaceCallback(self, sender):
        self.placeAnchors()

    def anchorsDeleteCallback(self, sender):
        self.deleteAnchors()

    def buildingVarsCallback(self, sender):
        self.uppercaseAccents = sender.getUppercaseAccents()

    def checkAccentedCallback(self, sender):
        self.checkAccented()

    def buildAccentedCallback(self, sender):
        self.buildAccented()

    def windowCloseCallback(self, sender):
        removeObserver(self, "newFontDidOpen")
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontWillClose")
        super(AccentedMaker, self).windowCloseCallback(sender)
 class DebuggerWindow(BaseWindowController):
     def __init__(self):
         self.w = FloatingWindow((123, 44), 'debug!')
         ms = MarginSelector()
         self.w.open()
         self.w.bind("close", ms.destroy)
Example #20
0
class ToucheTool():
    def __init__(self):
        NSUserDefaults.standardUserDefaults().registerDefaults_(
            {"ToucheWindowHeight": 340})
        self.windowHeight = NSUserDefaults.standardUserDefaults(
        ).integerForKey_("ToucheWindowHeight")
        self.minWindowHeight = 340
        if self.windowHeight < self.minWindowHeight:
            self.windowHeight = self.minWindowHeight
        self.closedWindowHeight = 100
        self.w = FloatingWindow((180, self.windowHeight),
                                'Touché!',
                                minSize=(180, 340),
                                maxSize=(250, 898))
        self.w.bind("resize", self.windowResized_)
        self.isResizing = False
        p = 10
        w = 160

        # options
        self.w.options = Group((0, 0, 180, 220))

        buttons = {
            "checkSelBtn": {
                "text": "Check selected glyphs",
                "callback": self.checkSel_,
                "y": p
            },
        }
        for button, data in buttons.iteritems():
            setattr(
                self.w.options, button,
                Button((p, data["y"], w - 22, 22),
                       data["text"],
                       callback=data["callback"],
                       sizeStyle="small"))

        self.w.options.zeroCheck = CheckBox((p, 35, w, 20),
                                            "Ignore zero-width glyphs",
                                            value=True,
                                            sizeStyle="small")
        self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16),
                                                  sizeStyle="small")

        # results
        self.w.results = Group((0, 220, 180, -0))
        self.w.results.show(False)

        textBoxes = {"stats": -34, "result": -18}
        for box, y in textBoxes.iteritems():
            setattr(self.w.results, box,
                    TextBox((p, y, w, 14), "", sizeStyle="small"))

        # list and preview
        self.w.outputList = List((0, 58, -0, -40), [{
            "left glyph": "",
            "right glyph": ""
        }],
                                 columnDescriptions=[{
                                     "title": "left glyph",
                                     "width": 90
                                 }, {
                                     "title": "right glyph"
                                 }],
                                 showColumnTitles=False,
                                 allowsMultipleSelection=False,
                                 enableDelete=False,
                                 selectionCallback=self.showPair_)
        self.w.outputList._setColumnAutoresizing()
        self._resizeWindow(False)
        self.w.open()

    # callbacks

    def checkAll_(self, sender=None):
        self.check_(useSelection=False)

    def checkSel_(self, sender=None):
        self.check_(useSelection=True)

    def check_(self, useSelection):
        self._resizeWindow(enlarge=False)
        self.checkFont(useSelection=useSelection,
                       excludeZeroWidth=self.w.options.zeroCheck.get())

    def showPair_(self, sender=None):
        try:
            index = sender.getSelection()[0]
            glyphs = [self.f[gName] for gName in self.touchingPairs[index]]
            ActiveFont = self.f._font
            EditViewController = ActiveFont.currentTab
            if EditViewController is None:
                tabText = "/%s/%s" % (glyphs[0].name, glyphs[1].name)
                ActiveFont.newTab(tabText)
            else:
                textStorage = EditViewController.graphicView()
                if not hasattr(textStorage,
                               "replaceCharactersInRange_withString_"
                               ):  # compatibility with API change in 2.5
                    textStorage = EditViewController.graphicView().textStorage(
                    )
                LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object)
                RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object)
                if LeftChar != 0 and RightChar != 0:
                    selection = textStorage.selectedRange()
                    if selection.length < 2:
                        selection.length = 2
                        if selection.location > 0:
                            selection.location -= 1

                    NewString = ""
                    if LeftChar < 0xffff and RightChar < 0xffff:
                        NewString = "%s%s" % (unichr(LeftChar),
                                              unichr(RightChar))
                    else:
                        print("Upper plane codes are not supported yet")

                    textStorage.replaceCharactersInRange_withString_(
                        selection, NewString)
                    selection.length = 0
                    selection.location += 1
                    textStorage.setSelectedRange_(selection)
            #self.w.preview.set(glyphs)
        except IndexError:
            pass

    # checking
    @objc.python_method
    def _hasSufficientWidth(self, g):
        # to ignore combining accents and the like
        if self.excludeZeroWidth:
            # also skips 1-unit wide glyphs which many use instead of 0
            if g.width < 2 or g._object.subCategory == "Nonspacing":
                return False
        return True

    @objc.python_method
    def _trimGlyphList(self, glyphList):
        newGlyphList = []
        for g in glyphList:
            if g.box is not None and self._hasSufficientWidth(g):
                newGlyphList.append(g)
        return newGlyphList

    def windowResized_(self, window):
        posSize = self.w.getPosSize()
        Height = posSize[3]
        if Height > self.closedWindowHeight and self.isResizing is False:
            print("set new Height", Height)
            NSUserDefaults.standardUserDefaults().setInteger_forKey_(
                Height, "ToucheWindowHeight")
            self.windowHeight = Height

    @objc.python_method
    def _resizeWindow(self, enlarge=True):
        posSize = self.w.getPosSize()
        if enlarge:
            self.w.results.show(True)
            self.w.outputList.show(True)
            targetHeight = self.windowHeight
            if targetHeight < 340:
                targetHeight = 340
        else:
            self.w.results.show(False)
            self.w.outputList.show(False)
            targetHeight = self.closedWindowHeight
        self.isResizing = True
        self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight))
        self.isResizing = False

    # ok let's do this
    @objc.python_method
    def checkFont(self, useSelection=False, excludeZeroWidth=True):
        f = CurrentFont()
        if f is not None:
            # initialize things
            self.w.options.progress.start()
            time0 = time.time()
            self.excludeZeroWidth = excludeZeroWidth
            self.f = f

            glyphNames = f.selection if useSelection else f.keys()
            glyphList = [f[x] for x in glyphNames]
            glyphList = self._trimGlyphList(glyphList)

            self.touchingPairs = Touche(f).findTouchingPairs(glyphList)

            # display output
            self.w.results.stats.set("%d glyphs checked" % len(glyphList))
            self.w.results.result.set("%d touching pairs found" %
                                      len(self.touchingPairs))
            self.w.results.show(True)

            outputList = [{
                "left glyph": g1,
                "right glyph": g2
            } for (g1, g2) in self.touchingPairs]
            self.w.outputList.set(outputList)
            if len(self.touchingPairs) > 0:
                self.w.outputList.setSelection([0])

            #self.w.preview.setFont(f)
            self.w.options.progress.stop()
            self._resizeWindow(enlarge=True)

            time1 = time.time()
            print('Touché: finished checking %d glyphs in %.2f seconds' %
                  (len(glyphList), time1 - time0))

        else:
            Message('Touché: Can’t find a font to check')
Example #21
0
class BroadNib(object):

    preview = False
    drawValues = False
    elementShape = SHAPE_OPTIONS[0]

    pluginWidth = 120
    pluginHeight = 300

    marginTop = 10
    marginRgt = 10
    marginBtm = 10
    marginLft = 10
    marginRow = 8

    netWidth = pluginWidth - marginLft - marginRgt

    def __init__(self):

        # init window
        self.win = FloatingWindow((self.pluginWidth, self.pluginHeight),
                                  "Broad Nib")

        # checkBox preview
        jumpingY = self.marginTop
        self.win.preview = CheckBox(
            (self.marginLft, jumpingY, self.netWidth, CheckBoxHeight),
            "Preview",
            value=self.preview,
            sizeStyle='small',
            callback=self.previewCallback)

        jumpingY += CheckBoxHeight
        self.win.shapeRadio = RadioGroup(
            (self.marginLft + 10, jumpingY, self.netWidth, 32),
            SHAPE_OPTIONS,
            sizeStyle='small',
            callback=self.shapeRadioCallback)
        self.win.shapeRadio.enable(False)
        self.win.shapeRadio.set(0)

        # checkBox draw values
        jumpingY += self.marginRow + self.win.shapeRadio.getPosSize()[3] - 3
        self.win.drawValues = CheckBox(
            (self.marginLft, jumpingY, self.netWidth, CheckBoxHeight),
            "Draw Values",
            value=self.drawValues,
            sizeStyle='small',
            callback=self.drawValuesCallback)

        # oval width
        jumpingY += self.marginRow + CheckBoxHeight
        self.win.widthCaption = TextBox(
            (self.marginLft, jumpingY + 3, self.netWidth * .5, TextBoxHeight),
            "Width:",
            sizeStyle='small')

        self.win.widthEdit = EditText(
            (self.marginLft + self.netWidth * .5, jumpingY, self.netWidth * .4,
             EditTextHeight),
            sizeStyle='small',
            callback=self.widthEditCallback)

        # oval height
        jumpingY += self.marginRow + EditTextHeight
        self.win.heightCaption = TextBox(
            (self.marginLft, jumpingY + 3, self.netWidth * .5, TextBoxHeight),
            "Height:",
            sizeStyle='small')

        self.win.heightEdit = EditText(
            (self.marginLft + self.netWidth * .5, jumpingY, self.netWidth * .4,
             EditTextHeight),
            sizeStyle='small',
            callback=self.heightEditCallback)

        # oval angle
        jumpingY += self.marginRow + EditTextHeight
        self.win.angleCaption = TextBox(
            (self.marginLft, jumpingY + 3, self.netWidth * .5, TextBoxHeight),
            "Angle:",
            sizeStyle='small')

        self.win.angleEdit = EditText(
            (self.marginLft + self.netWidth * .5, jumpingY, self.netWidth * .4,
             EditTextHeight),
            sizeStyle='small',
            callback=self.angleEditCallback)

        # add
        jumpingY += self.marginRow + EditTextHeight
        self.win.addToLib = SquareButton(
            (self.marginLft, jumpingY, self.netWidth, SquareButtonHeight),
            "Add to lib",
            sizeStyle='small',
            callback=self.addToLibCallback)

        # clear
        jumpingY += self.marginRow + EditTextHeight
        self.win.clearLib = SquareButton(
            (self.marginLft, jumpingY, self.netWidth, SquareButtonHeight),
            "Clear lib",
            sizeStyle='small',
            callback=self.clearLibCallback)

        jumpingY += self.marginRow + EditTextHeight
        self.win.expandToForeground = SquareButton(
            (self.marginLft, jumpingY, self.netWidth, SquareButtonHeight),
            "Expand",
            sizeStyle='small',
            callback=self.expandToForegroundCallback)

        # managing observers
        addObserver(self, "_drawBackground", "drawBackground")
        addObserver(self, "_drawBackground", "drawInactive")
        addObserver(self, "_drawBlack", "drawPreview")
        self.win.bind("close", self.closing)

        # adjust window
        jumpingY += self.marginRow + EditTextHeight
        self.win.setPosSize((200, 200, self.pluginWidth, jumpingY))

        # opening window
        self.win.open()

    def previewCallback(self, sender):
        self.preview = bool(sender.get())
        self.win.shapeRadio.enable(self.preview)
        UpdateCurrentGlyphView()

    def shapeRadioCallback(self, sender):
        self.elementShape = SHAPE_OPTIONS[sender.get()]
        UpdateCurrentGlyphView()

    def drawValuesCallback(self, sender):
        self.drawValues = bool(sender.get())
        UpdateCurrentGlyphView()

    def widthEditCallback(self, sender):
        try:
            self.nibWidth = int(sender.get())
        except ValueError:
            print traceback.format_exc()
            self.nibWidth = None
            self.win.widthEdit.set('')

    def heightEditCallback(self, sender):
        try:
            self.nibHeight = int(sender.get())
        except ValueError:
            print traceback.format_exc()
            self.nibHeight = None
            self.win.heightEdit.set('')

    def angleEditCallback(self, sender):
        try:
            self.nibAngle = int(sender.get())
        except ValueError:
            print traceback.format_exc()
            self.nibAngle = None
            self.win.angleEdit.set('')

    def addToLibCallback(self, sender):
        assert self.nibWidth is not None, 'nibWidth is None'
        assert self.nibHeight is not None, 'nibHeight is None'
        assert self.nibAngle is not None, 'nibAngle is None'

        if PLUGIN_KEY not in CurrentGlyph().lib:
            CurrentGlyph().lib[PLUGIN_KEY] = {}

        selectedIDs = collectIDsFromSelectedPoints(CurrentGlyph())
        for eachSelectedID in selectedIDs:
            CurrentGlyph().lib[PLUGIN_KEY][eachSelectedID] = {
                'width': self.nibWidth,
                'height': self.nibHeight,
                'angle': self.nibAngle
            }
        if version[0] == '2':
            CurrentGlyph().changed()
        else:
            CurrentGlyph().update()
        UpdateCurrentGlyphView()

    def clearLibCallback(self, sender):
        if PLUGIN_KEY in CurrentGlyph().lib:
            del CurrentGlyph().lib[PLUGIN_KEY]
        if version[0] == '2':
            CurrentGlyph().changed()
        else:
            CurrentGlyph().update()
        UpdateCurrentGlyphView()

    def expandToForegroundCallback(self, sender):
        self._drawElements(CurrentGlyph(), None, 2, 'foreground')

    def loadDataFromLib(self, glyph, ID):
        nibData = glyph.lib[PLUGIN_KEY][ID]
        self.win.widthEdit.set('%s' % nibData['width'])
        self.win.heightEdit.set('%s' % nibData['height'])
        self.win.angleEdit.set('%s' % nibData['angle'])
        self.nibWidth = nibData['width']
        self.nibHeight = nibData['height']
        self.nibAngle = nibData['angle']

    def _drawBackground(self, infoDict):
        assert self.elementShape in SHAPE_OPTIONS

        glyph = infoDict['glyph']
        currentTool = getActiveEventTool()
        view = currentTool.getNSView()
        textAttributes = {
            NSFontAttributeName: NSFont.systemFontOfSize_(bodySizeCaption),
            NSForegroundColorAttributeName: NSColor.blackColor()
        }

        # load data if point is selected
        if PLUGIN_KEY in glyph.lib:
            for eachContour in glyph:
                for eachPt in eachContour.points:
                    if eachPt.selected is True and eachPt.type != 'offCurve' and eachPt.naked(
                    ).uniqueID in glyph.lib[PLUGIN_KEY]:
                        self.loadDataFromLib(glyph, eachPt.naked().uniqueID)

        # draw interpolateValued ovals
        if self.preview is True and PLUGIN_KEY in glyph.lib:
            self._drawElements(glyph, SUB_COLOR, 4, 'canvas')

        # draw master ovals
        if self.preview is True and PLUGIN_KEY in glyph.lib:
            for eachContour in glyph:
                for eachSegment in eachContour:
                    ID = eachSegment.onCurve.naked().uniqueID
                    if ID in glyph.lib[PLUGIN_KEY]:
                        elementDict = glyph.lib[PLUGIN_KEY][ID]
                        save()
                        fill(*MASTER_COLOR)
                        translate(eachSegment.onCurve.x, eachSegment.onCurve.y)
                        rotate(elementDict['angle'])

                        if self.elementShape == 'Oval':
                            oval(-elementDict['width'] / 2.,
                                 -elementDict['height'] / 2.,
                                 elementDict['width'], elementDict['height'])
                        else:
                            rect(-elementDict['width'] / 2.,
                                 -elementDict['height'] / 2.,
                                 elementDict['width'], elementDict['height'])
                        restore()

        # draw values
        if self.drawValues is True and PLUGIN_KEY in glyph.lib:
            for eachContour in glyph:
                for eachSegment in eachContour:
                    ID = eachSegment.onCurve.naked().uniqueID
                    if ID in glyph.lib[PLUGIN_KEY]:
                        nibData = glyph.lib[PLUGIN_KEY][ID]
                        values = '%s: %s\n%s: %s\n%s: %s' % (
                            'width', nibData['width'], 'height',
                            nibData['height'], 'angle', nibData['angle'])
                        view._drawTextAtPoint(
                            values,
                            textAttributes,
                            (eachSegment.onCurve.x, eachSegment.onCurve.y),
                            yOffset=2.8 * bodySizeCaption)

    def _drawBlack(self, infoDict):
        glyph = infoDict['glyph']
        if self.preview is True and PLUGIN_KEY in glyph.lib:
            # background
            fill(*LIGHT_YELLOW)
            rect(-1000, -1000, 2000, 2000)

            # calligraphy
            self._drawElements(glyph, BLACK_COLOR, 4, 'canvas')

    def _drawElements(self, glyph, color, distance, mode):
        assert mode == 'canvas' or mode == 'foreground'
        assert self.elementShape in SHAPE_OPTIONS

        if mode == 'foreground':
            phantomGlyph = RGlyph()

        for eachContour in glyph:
            for indexSegment, eachSegment in enumerate(eachContour):
                if indexSegment != len(eachContour) - 1:
                    nextSegment = eachContour[indexSegment + 1]
                else:
                    if eachContour.open is True:
                        continue
                    else:
                        nextSegment = eachContour[0]

                pt1 = eachSegment.onCurve.x, eachSegment.onCurve.y
                pt4 = nextSegment.onCurve.x, nextSegment.onCurve.y

                if nextSegment.offCurve:
                    pt2 = nextSegment.offCurve[0].x, nextSegment.offCurve[0].y
                    pt3 = nextSegment.offCurve[1].x, nextSegment.offCurve[1].y
                else:
                    pt2 = pt1
                    pt3 = pt4
                pt1 = eachSegment.onCurve.x, eachSegment.onCurve.y
                pt4 = nextSegment.onCurve.x, nextSegment.onCurve.y

                if eachSegment.onCurve.naked().uniqueID in glyph.lib[PLUGIN_KEY] and \
                   nextSegment.onCurve.naked().uniqueID in glyph.lib[PLUGIN_KEY]:
                    startLib = glyph.lib[PLUGIN_KEY][
                        eachSegment.onCurve.naked().uniqueID]
                    endLib = glyph.lib[PLUGIN_KEY][
                        nextSegment.onCurve.naked().uniqueID]

                    bezPoints = collectsPointsOnBezierCurveWithFixedDistance(
                        pt1, pt2, pt3, pt4, distance)
                    for indexBezPt, eachBezPt in enumerate(bezPoints):
                        factor = indexBezPt / float(len(bezPoints))
                        width = lerp(startLib['width'], endLib['width'],
                                     factor)
                        height = lerp(startLib['height'], endLib['height'],
                                      factor)
                        angle = lerp(startLib['angle'], endLib['angle'],
                                     factor)

                        if mode == 'canvas':
                            save()
                            fill(*color)
                            translate(eachBezPt[0][0], eachBezPt[0][1])
                            rotate(angle)

                            if self.elementShape == 'Oval':
                                oval(-width / 2., -height / 2., width, height)
                            else:
                                rect(-width / 2., -height / 2., width, height)
                            restore()

                        else:
                            matrix = Identity
                            matrix = matrix.translate(eachBezPt[0][0],
                                                      eachBezPt[0][1])
                            matrix = matrix.rotate(math.radians(angle))

                            if self.elementShape == 'Oval':
                                robofabOval(phantomGlyph, 0, 0, width, height)
                            else:
                                robofabRect(phantomGlyph, 0, 0, width, height)
                            phantomGlyph[len(phantomGlyph) -
                                         1].transform(matrix)

        if mode == 'foreground':
            phantomGlyph.removeOverlap()
            flattenGlyph(phantomGlyph, 20)
            thresholdGlyph(phantomGlyph, 5)
            if version[0] == '2':
                glyph.getLayer('public.default',
                               clear=True).appendGlyph(phantomGlyph, (0, 0))
            else:
                glyph.getLayer('foreground',
                               clear=True).appendGlyph(phantomGlyph, (0, 0))

    def closing(self, sender):
        removeObserver(self, "drawBackground")
        removeObserver(self, "drawInactive")
        removeObserver(self, "drawPreview")