Пример #1
0
class ColdtypeSerializer(BaseWindowController):
    def __init__(self):
        self.w = FloatingWindow((300, 40),
                                "Coldtype Serializer",
                                minSize=(123, 200))
        self.w.globalToggle = CheckBox((10, 10, -10, 20),
                                       'serialize?',
                                       value=True)
        self.output = Path("~/robofont-coldtype.json").expanduser().absolute()
        addObserver(self, "shouldDraw", "draw")

        self.setUpBaseWindowBehavior()
        self.w.open()

    def windowCloseCallback(self, sender):
        removeObserver(self, 'draw')
        super(ColdtypeSerializer, self).windowCloseCallback(sender)

    def shouldDraw(self, notification):
        if not self.w.globalToggle.get():
            return
        glyph = notification['glyph']
        data_out = {"name": glyph.name, "layers": {}}
        for g in glyph.layers:
            rp = RecordingPen()
            g.draw(rp)
            data_out["layers"][g.layer.name] = dict(value=rp.value,
                                                    width=g.width)
        self.output.write_text(json.dumps(data_out))
        print("> wrote glyph data", glyph.name)
Пример #2
0
class ColorThemesDialog:

    colorThemesFolder = os.path.join(os.getcwd(), 'themes')

    def __init__(self):
        self.w = FloatingWindow((246, 300), "CodeColors")
        x = y = p = 10
        self.w.colorThemesList = List((x, y, -p, -p),
                                      sorted(self.colorThemes.keys()),
                                      selectionCallback=self.selectionCallback,
                                      allowsMultipleSelection=False,
                                      allowsEmptySelection=False)
        self.w.open()

    @property
    def colorThemes(self):
        return getColorThemes(self.colorThemesFolder)

    @property
    def colorTheme(self):
        i = self.w.colorThemesList.getSelection()[0]
        colorThemeName = sorted(self.colorThemes.keys())[i]
        return self.colorThemes[colorThemeName]

    def selectionCallback(self, sender):
        setColorTheme(self.colorTheme)
Пример #3
0
class highlightPointsOnMetrics(BaseWindowController):
    def __init__(self):
        self.w = FloatingWindow((135, 40), "", minSize=(123, 200))

        # a checkbox to turn the tool on/off
        self.w.showPoints = CheckBox((10, 10, -10, 20),
                                     'highlight points',
                                     value=True)

        # add an observer to the drawPreview event
        addObserver(self, "highlightPoints", "draw")

        # open window
        self.setUpBaseWindowBehavior()
        self.w.open()

    def windowCloseCallback(self, sender):
        # remove observer when window is closed
        removeObserver(self, 'draw')
        super(highlightPointsOnMetrics, self).windowCloseCallback(sender)

    def highlightPoints(self, info):
        # check if checkbox is selected
        if not self.w.showPoints.get():
            return

        # get the current glyph
        glyph = info["glyph"]
        sc = info['scale']

        # Anything more than 3 could be considered an intentional overshoot?
        rang = 3

        asc = f.info.ascender
        xhe = f.info.xHeight
        cap = f.info.capHeight
        dsc = f.info.descender
        size = 8 * sc
        # draw highlight
        stroke(None)
        if glyph is not None:
            for c in glyph:
                for s in c:
                    for p in s:
                        if p.y == asc or p.y == xhe or p.y == cap or p.y == dsc or p.y == 0:
                            pass
                        else:
                            if (asc + -rang <= p.y <= asc + rang) or (
                                    xhe + -rang <= p.y <= xhe + rang) or (
                                        cap + -rang <= p.y <= cap + rang) or (
                                            dsc + -rang <= p.y <= dsc +
                                            rang) or (-rang <= p.y <= rang):

                                # fill(1, 0, 0, .6)

                                fill(None)
                                stroke(1.0, 0.0, 0.0, .5)
                                strokeWidth(7 * sc)
                                oval(p.x - size / 2, p.y - size / 2, size,
                                     size)
Пример #4
0
class MoveGlyphWindow:
    '''Move the current glyph using sliders.'''
    def __init__(self, glyph):

        # if glyph is None, show a message
        if glyph is None:
            Message('no glyph selected',
                    title='moveTool',
                    informativeText='Please select one glyph first!')
            return

        # store the glyph and initial move values as object attributes
        self.glyph = glyph
        self.moveX = 0

        # create a floating window
        self.w = FloatingWindow((200, 74), "move %s" % self.glyph.name)

        # add a slider for moving in the x axis
        self.w.sliderX = Slider((10, 10, -10, 22),
                                value=0,
                                maxValue=200,
                                minValue=-200,
                                callback=self.adjust)

        # open the window
        self.w.open()

    def adjust(self, sender):

        # get the current x and y values from the sliders
        valueX = self.w.sliderX.get()

        # calculate actual move distance
        x = self.moveX - valueX

        # move the glyph
        self.glyph.moveBy((x, y))

        # update saved values
        self.moveX = valueX

        # print the current move distance
        print(self.moveX)
Пример #5
0
class ContrastController(object):
    def __init__(self):

        # base window
        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT))

        jumpingY = MARGIN_VER
        self.w.libChoice = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']), ['A', 'B', 'C'],
            callback=self.libChoiceCallback)

        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER
        self.w.addButton = SquareButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH * .6, SQUARE_BUTTON_HEIGHT),
            'add stem',
            callback=self.addButtonCallback)

        self.w.checkBoxStar = CheckBoxStar(
            (MARGIN_HOR + MARGIN_VER + NET_WIDTH * .6, jumpingY,
             NET_WIDTH * .35, NET_WIDTH * .35),
            callback=self.checkBoxStarCallback)

        jumpingY += SQUARE_BUTTON_HEIGHT + MARGIN_VER
        self.w.removeButton = SquareButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH * .6, SQUARE_BUTTON_HEIGHT),
            'remove stem',
            callback=self.removeButtonCallback)

        # lit up!
        self.w.open()

    def libChoiceCallback(self, sender):
        print sender.get()

    def addButtonCallback(self, sender):
        print 'hit addButtonCallback'

    def removeButtonCallback(self, sender):
        print 'hit removeButtonCallback'

    def checkBoxStarCallback(self, sender):
        print sender.get()
Пример #6
0
class MoveGlyphWindow:

    '''Move the current glyph using sliders.'''

    def __init__(self, glyph):

        # if glyph is None, show a message
        # store the glyph and initial move values as object attributes
        self.w = FloatingWindow((200, 64), "move "+str(labelslider))
        for labelslider in BuildLabelsList(f):
            # create a floating window
            # add a slider for moving in the x axis
            self.g = glyph
            self.moveX = 0
            self.label= labelslider
            self.w.labelslider = Slider(
                    (10, 10, -10, 22),
                    value=0, maxValue=100, minValue=-100,
                    callback=self.adjust)

            # open the window
            self.w.button = Button((15, -35, -15, 20), "Done")
            self.w.open()

    def adjust(self, sender):

        # get the current x and y values from the sliders
        valueX = self.w.labelslider.get()

        # calculate actual move distance
        x = self.moveX - valueX

        # move the glyph
        for contour in self.g:
            for point in counter.points:
                if point.labels is 'CROSSBAR':
                    point.moveBy((x, 0))

        # update saved values
        self.moveX = valueX

        # print the current move distance
        print(self.moveX)
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 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')
Пример #9
0
class OverlayUFOs(BaseWindowController):
    
    DEFAULTKEY = "com.fontbureau.overlayUFO"
    DEFAULTKEY_DRAW = "%s.draw" % DEFAULTKEY
    DEFAULTKEY_FILL = "%s.fill" % DEFAULTKEY
    DEFAULTKEY_FILLCOLOR = "%s.fillColor" % DEFAULTKEY
    DEFAULTKEY_STROKE = "%s.stroke" % DEFAULTKEY
    DEFAULTKEY_STROKECOLOR = "%s.strokeColor" % DEFAULTKEY
    DEFAULTKEY_POINTS = "%s.points" % DEFAULTKEY
    DEFAULTKEY_POINTSCOLOR = "%s.pointsColor" % DEFAULTKEY
    DEFAULTKEY_ALIGNMENT = "%s.alignment" % DEFAULTKEY
    DEFAULTKEY_KERNING = "%s.kerning" % DEFAULTKEY
    DEFAULTKEY_FLOATING = "%s.floating" % DEFAULTKEY
    
    FALLBACK_FILLCOLOR = NSColor.colorWithCalibratedRed_green_blue_alpha_(0, 0.3, 1, .1)
    FALLBACK_STROKECOLOR = NSColor.colorWithCalibratedRed_green_blue_alpha_(0, 0.3, 1, .5)
    FALLBACK_POINTSCOLOR = NSColor.colorWithCalibratedRed_green_blue_alpha_(0, 0.3, 1, .5)
    
    def getListDescriptor(self): 
        return [
            dict(title="Status", key="status", width=10, cell=SmallTextListCell(), editable=False), 
            dict(title="Name", key="name", width=230, cell=SmallTextListCell(), editable=False), 
        ]
    
    def __init__(self):
        
        # Preferences
        self._drawing = getExtensionDefault(self.DEFAULTKEY_DRAW, True)
        self._fill = getExtensionDefault(self.DEFAULTKEY_FILL, True)
        self._stroke = getExtensionDefault(self.DEFAULTKEY_STROKE, True)
        self._points = getExtensionDefault(self.DEFAULTKEY_POINTS, True)
        
        self._fillColor = getExtensionDefaultColor(self.DEFAULTKEY_FILLCOLOR, self.FALLBACK_FILLCOLOR)
        self._strokeColor = getExtensionDefaultColor(self.DEFAULTKEY_STROKECOLOR, self.FALLBACK_STROKECOLOR)
        self._pointsColor = getExtensionDefaultColor(self.DEFAULTKEY_POINTSCOLOR, self.FALLBACK_POINTSCOLOR)
        
        self._alignment = getExtensionDefault(self.DEFAULTKEY_ALIGNMENT, 0)
        self._kerning = getExtensionDefault(self.DEFAULTKEY_KERNING, 1)
        self._floating = getExtensionDefault(self.DEFAULTKEY_FLOATING, 1)
        
        # User preferences
        self._onCurvePointsSize = getDefault("glyphViewOncurvePointsSize") # typo, should be: OnCurve
        self._offCurvePointsSize = getDefault("glyphViewOffCurvePointsSize")
        self._strokeWidth = getDefault("glyphViewStrokeWidth")
        
        w, h = 400, 195
        x = y = 10
        
        self.initAllFonts()
        
        self.w = FloatingWindow((w, h), "Overlay UFOs")
        self.w.draw = CheckBox((x, y, 95, 18), "Draw", callback=self.drawCallback, value=self._drawing, sizeStyle="small")
        x += 60
        self.w.fill = CheckBox((x, y, 95, 18), "Fill", callback=self.fillCallback, value=self._fill, sizeStyle="small")
        x += 40
        self.w.fillColor = ColorWell((x, y, 45, 20), callback=self.fillColorCallback, color=self._fillColor)
        x += 60
        self.w.stroke = CheckBox((x, y, 95, 18), "Stroke", callback=self.strokeCallback, value=self._stroke, sizeStyle="small")
        x += 60
        self.w.strokeColor = ColorWell((x, y, 45, 20), callback=self.strokeColorCallback, color=self._strokeColor)
        x += 60
        self.w.points = CheckBox((x, y, 95, 18), "Points", callback=self.pointsCallback, value=self._points, sizeStyle="small")
        x += 60
        self.w.pointsColor = ColorWell((x, y, 45, 20), callback=self.pointsColorCallback, color=self._pointsColor)
        x, y = 10, 40
        self.w.alignText = TextBox((x, y, 250, 15), "Alignment:", sizeStyle="small")
        y += 18
        self.w.alignment = RadioGroup((x, y, 80, 55), ['Left', 'Center', 'Right'], isVertical=True, callback=self.alignmentCallback, sizeStyle="small")
        self.w.alignment.set(self._alignment)
        y += 62
        self.w.kerning = CheckBox((x, y, 100, 10), "Show kerning", callback=self.kerningCallback, value=self._kerning, sizeStyle="mini")
        y += 18
        self.w.floating = CheckBox((x, y, 100, 10), "Floating Window", callback=self.floatingCallback, value=self._floating, sizeStyle="mini")
        y += 25
        self.w.resetDefaults = Button((x, y, 85, 14), "Reset settings", callback=self.resetSettingsCallback, sizeStyle="mini")
        x, y = 110, 40
        self.w.fontList = List((x, y, 240, 55), self.getFontItems(), 
            columnDescriptions=self.getListDescriptor(), showColumnTitles=False,
            selectionCallback=None, doubleClickCallback=self.fontListCallback,
            allowsMultipleSelection=True, allowsEmptySelection=True,
            drawVerticalLines=False, drawHorizontalLines=True,
            drawFocusRing=False, rowHeight=16
        )
        y += 55
        self.w.hiddenFontList = List((x, y, 240, 55), self.getHiddenFontItems(), 
            columnDescriptions=self.getListDescriptor(), showColumnTitles=False,
            selectionCallback=None, doubleClickCallback=self.hiddenFontListCallback,
            allowsMultipleSelection=True, allowsEmptySelection=True,
            drawVerticalLines=False, drawHorizontalLines=True,
            drawFocusRing=False, rowHeight=16
        )
        self._selectionChanging = False
        self.w.fontList.setSelection([]) # unselect
        y += 65
        self.w.contextLeft = EditText((x, y, 90, 20), callback=self.contextCallback, continuous=True, placeholder="Left", sizeStyle="small")
        self.w.contextCurrent = EditText((x+95, y, 50, 20), callback=self.contextCallback, continuous=True, placeholder="?", sizeStyle="small")
        self.w.contextRight = EditText((x+150, y, 90, 20), callback=self.contextCallback, continuous=True, placeholder="Right", sizeStyle="small")
        x, y = 360, 100
        self.w.addFonts = Button((x, y, 30, 20), "+", callback=self.addHiddenFontsCallback, sizeStyle="regular")
        y += 25
        self.w.removeFonts = Button((x, y, 30, 20), unichr(8722), callback=self.removeHiddenFontsCallback, sizeStyle="regular")
                
        # Observers
        addObserver(self, "fontDidOpen", "fontDidOpen")
        addObserver(self, "fontWillClose", "fontWillClose") # fontDidClose?
        addObserver(self, "draw", "drawInactive")
        addObserver(self, "draw", "draw")
        
        # Prepare and open window
        self.setWindowLevel()
        self.setUpBaseWindowBehavior()
        self.w.open()
    
    def setWindowLevel(self):
        if self._floating:
            self.w._window.setLevel_(NSFloatingWindowLevel)
        else:
            self.w._window.setLevel_(NSNormalWindowLevel)
            
    def windowCloseCallback(self, sender):
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontWillClose")
        removeObserver(self, "drawInactive")
        removeObserver(self, "draw")
        self.updateView()
        super(OverlayUFOs, self).windowCloseCallback(sender)
    
    def updateView(self):
        UpdateCurrentGlyphView()
    
    def initAllFonts(self):
        fonts = []
        for font in AllFonts():
            fonts.append({"font": font, "status": u"•"})
        self.fonts = fonts
        self.hiddenFonts = []
            
    def getFontName(self, font):
        if font.document():
            name = font.document().displayName()
        else:
            name = font.fileName.split("/")[-1]
        return name
    
    def getHiddenFontItems(self):
        hiddenFonts = self.hiddenFonts
        hiddenFontItems = self.getItems(hiddenFonts)
        return hiddenFontItems
    
    def getFontItems(self):
        fonts = self.fonts
        fontItems = self.getItems(fonts)
        return fontItems
    
    def getItems(self, fonts):
        items = []
        uniqueNames = {}
        for f in fonts:
            font = f["font"]
            status = f["status"]
            path = font.path
            name = self.getFontName(font)
            if not uniqueNames.has_key(name):
                uniqueNames[name] = []
            uniqueNames[name].append(path)
            paths = uniqueNames[name]
            if len(paths) > 1:
                prefix = commonprefix(paths)
                if prefix:
                    suffix = " ...%s" % path[len(prefix):]
                else:
                    suffix = " %s" % path
                name += suffix
            items.append({"status": status, "name": name})
        return items
    
    def getActiveFonts(self):
        fonts = self.fonts + self.hiddenFonts
        activeFonts = []
        for font in fonts:
            if font["status"]:
                activeFonts.append(font["font"])
        return activeFonts
    
    def getContexts(self):
        return self.w.contextLeft.get(), self.w.contextCurrent.get(), self.w.contextRight.get()
    
    def getAlignment(self):
        index = self._alignment
        if index == 0:
            return 'left'
        elif index == 1:
            return 'center'
        elif index == 2:
            return 'right'
    
    def getKernValue(self, nakedFont, leftGlyph, rightGlyph):
        if not leftGlyph or not rightGlyph:
            return 0
        if not self._kerning:
            return 0
        return nakedFont.flatKerning.get((leftGlyph.name, rightGlyph.name)) or 0
    
    def draw(self, info):
        
        if not self._drawing:
            return
            
        glyph = info.get("glyph")
        drawingScale = info.get('scale')
        
        if glyph is None:
            return
            
        current = glyph.getParent()
        fonts = self.getActiveFonts()
        
        for font in fonts:
            
            nakedFont = font.naked()
            
            contextLeft, contextCurrent, contextRight = self.getContexts()
            contextLeft = splitText(contextLeft or '', nakedFont.unicodeData or '')
            contextLeft = [font[gname] for gname in contextLeft if gname in font.keys()]
            contextRight = splitText(contextRight or '', nakedFont.unicodeData or '')
            contextRight = [font[gname] for gname in contextRight if gname in font.keys()]
            contextCurrent = splitText(contextCurrent or '', nakedFont.unicodeData or '')
            if len(contextCurrent) > 0:
                contextCurrent = [font[gname] for gname in [contextCurrent[0]] if gname in font.keys()]
                if len(contextCurrent) > 0:
                    sourceGlyph = contextCurrent[0]
                else:
                    sourceGlyph = None
            elif glyph.name in font.keys():
                sourceGlyph = font[glyph.name]
                contextCurrent = [sourceGlyph]
            else:
                sourceGlyph = None
                contextCurrent = []
            
            save()
            
            self._fillColor.setFill()
            self._strokeColor.setStroke()
            scale(current.info.unitsPerEm/float(font.info.unitsPerEm))
            
            # Draw left context
            previousGlyph = sourceGlyph
            contextLeft.reverse()
            totalWidth = 0
            for i, cbGlyph in enumerate(contextLeft):
                kernValue = self.getKernValue(nakedFont, cbGlyph, previousGlyph) # right to left
                translate(-cbGlyph.width-kernValue, 0)
                totalWidth += cbGlyph.width + kernValue
                glyphBezierPath = cbGlyph.naked().getRepresentation("defconAppKit.NSBezierPath")
                if self._fill:
                    glyphBezierPath.fill()
                if self._stroke:
                    glyphBezierPath.setLineWidth_(self._strokeWidth*drawingScale)
                    strokePixelPath(glyphBezierPath)
                previousGlyph = cbGlyph
            translate(totalWidth, 0)
            
            # Draw current context or current glyph 
            if contextCurrent:
                previousGlyph = None
                alignment = self.getAlignment()
                if alignment == 'left':
                    offset = 0
                elif alignment == 'center':
                    offset = (glyph.width - contextCurrent[0].width)/2                                        
                elif alignment == 'right':
                    offset = glyph.width - contextCurrent[0].width                                     
                totalWidth = offset
                translate(totalWidth, 0)
            
            for i, cbGlyph in enumerate(contextCurrent):
                #if cbGlyph == glyph:
                #    continue # Don't show if is current glyph
                kernValue = self.getKernValue(nakedFont, previousGlyph, cbGlyph)
                translate(kernValue, 0)
                glyphBezierPath = cbGlyph.naked().getRepresentation("defconAppKit.NSBezierPath")
                if self._fill:
                    glyphBezierPath.fill()
                if self._stroke:
                    glyphBezierPath.setLineWidth_(self._strokeWidth*drawingScale)
                    strokePixelPath(glyphBezierPath)
                if self._points:
                    self.drawPoints(cbGlyph, info['scale'])
                translate(cbGlyph.width, 0)
                totalWidth += cbGlyph.width + kernValue
                previousGlyph = cbGlyph
            translate(-totalWidth, 0)
            
            # Draw right context
            translate(glyph.width)
            totalWidth = glyph.width
            for i, cbGlyph in enumerate(contextRight):
                kernValue = self.getKernValue(nakedFont, previousGlyph, cbGlyph)
                translate(kernValue, 0)
                glyphBezierPath = cbGlyph.naked().getRepresentation("defconAppKit.NSBezierPath")
                if self._fill:
                    glyphBezierPath.fill()
                if self._stroke:
                    glyphBezierPath.setLineWidth_(self._strokeWidth*drawingScale)
                    strokePixelPath(glyphBezierPath)
                translate(cbGlyph.width, 0)
                totalWidth += cbGlyph.width + kernValue
                previousGlyph = cbGlyph
                
            restore()
    
    def drawPoints(self, glyph, scale):
        save()
        _onCurveSize = self._onCurvePointsSize * scale
        _offCurveSize = self._offCurvePointsSize * scale
        _strokeWidth = self._strokeWidth * scale
        
        self._pointsColor.set()
        
        path = NSBezierPath.bezierPath()
        offCurveHandlesPath = NSBezierPath.bezierPath()
        pointsData = glyph.getRepresentation("doodle.OutlineInformation")
        
        for point1, point2 in pointsData["bezierHandles"]:
            offCurveHandlesPath.moveToPoint_(point1)
            offCurveHandlesPath.lineToPoint_(point2)

        for point in pointsData.get("offCurvePoints"):
            (x, y) = point["point"]
            path.appendBezierPathWithOvalInRect_(NSMakeRect(x - _offCurveSize, y - _offCurveSize, _offCurveSize * 2, _offCurveSize * 2))
            
        for point in pointsData.get("onCurvePoints"):
            (x, y) = point["point"]
            path.appendBezierPathWithRect_(NSMakeRect(x - _onCurveSize, y - _onCurveSize, _onCurveSize * 2, _onCurveSize * 2))
            
        path.fill()
        offCurveHandlesPath.setLineWidth_(_strokeWidth)
        strokePixelPath(offCurveHandlesPath)
        restore()

    def fontDidOpen(self, event):
        font = event["font"]
        self.fonts.append({"font": font, "status": u"•"})
        self.w.fontList.set(self.getFontItems())
        self.updateView()
    
    def fontWillClose(self, event):
        # not always working... try from path or name?
        closingFont = event["font"]
        for i, font in enumerate(self.fonts):
            if font["font"] == closingFont:
                del self.fonts[i]
                item = self.w.fontList.get()[i]
                self.w.fontList.remove(item)
                break
        self.updateView()
    
    def fontListCallback(self, sender):
        fonts = self.fonts
        self.listCallback(sender, fonts)

    def hiddenFontListCallback(self, sender):
        fonts = self.hiddenFonts
        self.listCallback(sender, fonts)
            
    def listCallback(self, sender, fonts):
        for index in sender.getSelection():
            item = sender.get()[index]
            font = fonts[index]
            if item["status"]:
                item["status"] = font["status"] = ""
            else:
                item["status"] = font["status"] = u"•"
        self.updateView()
    
    def contextCallback(self, sender):
        self.updateView()
    
    def drawCallback(self, sender):
        drawing = sender.get()
        setExtensionDefault(self.DEFAULTKEY_DRAW, drawing)
        self._drawing = drawing
        self.updateView()
    
    def fillCallback(self, sender):
        fill = sender.get()
        setExtensionDefault(self.DEFAULTKEY_FILL, fill)
        self._fill = fill
        self.updateView()
    
    def fillColorCallback(self, sender):
        fillColor = sender.get()
        setExtensionDefaultColor(self.DEFAULTKEY_FILLCOLOR, fillColor)
        self._fillColor = fillColor
        self.updateView()
    
    def strokeCallback(self, sender):
        stroke = sender.get()
        setExtensionDefault(self.DEFAULTKEY_STROKE, stroke)
        self._stroke = stroke
        self.updateView()
    
    def strokeColorCallback(self, sender):
        strokeColor = sender.get()
        setExtensionDefaultColor(self.DEFAULTKEY_STROKECOLOR, strokeColor)
        self._strokeColor = strokeColor
        self.updateView()
    
    def pointsCallback(self, sender):
        points = sender.get()
        setExtensionDefault(self.DEFAULTKEY_POINTS, points)
        self._points = points
        self.updateView()
    
    def pointsColorCallback(self, sender):
        pointsColor = sender.get()
        setExtensionDefaultColor(self.DEFAULTKEY_POINTSCOLOR, pointsColor)
        self._pointsColor = pointsColor
        self.updateView()
    
    def alignmentCallback(self, sender):
        alignment = sender.get()
        setExtensionDefault(self.DEFAULTKEY_ALIGNMENT, alignment)
        self._alignment = alignment
        self.updateView()
    
    def kerningCallback(self, sender):
        kerning = sender.get()
        setExtensionDefault(self.DEFAULTKEY_KERNING, kerning)
        self._kerning = kerning
        self.updateView()

    def floatingCallback(self, sender):
        floating = sender.get()
        setExtensionDefault(self.DEFAULTKEY_FLOATING, floating)
        self._floating = floating
        self.setWindowLevel()
        
    def resetSettingsCallback(self, sender):
        drawing = True
        fill = True
        fillColor = self.FALLBACK_FILLCOLOR
        stroke = True
        strokeColor = self.FALLBACK_STROKECOLOR
        points = True
        pointsColor = self.FALLBACK_POINTSCOLOR
        alignment = 0
        kerning = True
        floating = True
        
        setExtensionDefault(self.DEFAULTKEY_DRAW, drawing)
        setExtensionDefault(self.DEFAULTKEY_FILL, fill)
        setExtensionDefaultColor(self.DEFAULTKEY_FILLCOLOR, fillColor)
        setExtensionDefault(self.DEFAULTKEY_STROKE, stroke)
        setExtensionDefaultColor(self.DEFAULTKEY_STROKECOLOR, strokeColor)
        setExtensionDefault(self.DEFAULTKEY_POINTS, points)
        setExtensionDefaultColor(self.DEFAULTKEY_POINTSCOLOR, pointsColor)
        setExtensionDefault(self.DEFAULTKEY_ALIGNMENT, alignment)
        setExtensionDefault(self.DEFAULTKEY_KERNING, kerning)
        setExtensionDefault(self.DEFAULTKEY_FLOATING, floating)
        
        self.w.draw.set(drawing)
        self.w.fill.set(fill)
        self.w.fillColor.set(fillColor)
        self.w.stroke.set(stroke)
        self.w.strokeColor.set(strokeColor)
        self.w.points.set(points)
        self.w.pointsColor.set(strokeColor)
        self.w.alignment.set(alignment)
        self.w.kerning.set(kerning)
        self.w.floating.set(floating)
        
        self._drawing = drawing
        self._fill = fill
        self._fillColor = fillColor
        self._stroke = stroke
        self._strokeColor = strokeColor
        self._points = points
        self._pointsColor = strokeColor
        self._alignment = alignment
        self._kerning = kerning
        self._floating = floating
        
        self.updateView()

    def addHiddenFontsCallback(self, sender):
        fonts = OpenFont(None, False)
        if fonts is None:
            return
        if not isinstance(fonts, list): # make sure it's a list
            fonts = [fonts] 
        paths = [font["font"].path for font in self.hiddenFonts]
        for font in fonts:
            if font.path in paths:
                continue # already open
            self.hiddenFonts.append({"font": font, "status": u"•"})
        self.w.hiddenFontList.set(self.getHiddenFontItems())
        self.updateView()
    
    def removeHiddenFontsCallback(self, sender):
        hiddenFontList = self.w.hiddenFontList.get()
        selection = self.w.hiddenFontList.getSelection()
        for i in reversed(selection):
            del self.hiddenFonts[i]
            item = hiddenFontList[i]
            self.w.hiddenFontList.remove(item)
        self.updateView()
Пример #10
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')
Пример #11
0
class MoveGlyphWindow:
    '''Move the current glyph using sliders.'''
    def __init__(self, glyph):

        # if glyph is None, show a message
        if glyph is None:
            Message('no glyph selected',
                    title='moveTool',
                    informativeText='Please select one glyph first!')
            return

        # store the glyph and initial move values as object attributes
        self.glyph = glyph
        self.moveXTRA = 0

        # create a floating window
        self.w = FloatingWindow((200, 74), "move %s" % self.glyph.name)

        # add a slider for moving in the x axis
        self.w.sliderXTRA = Slider((10, 10, -10, 22),
                                   value=0,
                                   maxValue=200,
                                   minValue=0,
                                   callback=self.adjustXTRA)

        # self.w.sliderXOPQ = Slider(
        #         (10, 10, -10, 22),
        #         value=0, maxValue=200, minValue=-200,
        #         callback=self.adjust)

        # open the window

        self.w.open()

    def adjustXTRA(self, sender):

        # crossbartop='CROSSBARTOP'
        # crossbarbottom='CROSSBARBOTTOM'
        # rightsideinsidelabel='RIGHTSIDEINSIDE'
        # rightsideoutsidelabel='RIGHTSIDEOUTSIDE'
        # leftsideinsidelabel='LEFTSIDEINSIDE'
        # leftsideoutsidelabel='LEFTSIDEOUTSIDE'
        rightstemlabel = 'RIGHTSTEM'

        # get the current x and y values from the sliders
        valueXTRA = self.w.sliderXTRA.get()

        # calculate actual move distance
        x = self.moveXTRA - valueXTRA

        # move the glyph

        for self.c in self.glyph:
            for self.p in self.c.points:
                if rightstemlabel in self.p.labels:
                    self.glyph.prepareUndo()
                    self.p.move((-x, 0))
                    self.glyph.rightMargin
                    self.glyph.changed()
                    self.glyph.performUndo()
        # self.glyph.moveBy((x, 0))

        # update saved values
        self.moveXTRA = valueXTRA

        # print the current move distance
        print(self.moveXTRA)
Пример #12
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']
Пример #13
0
class VariableController(object):
    def __init__(self, attributes, callback, document=None):
        self._callback = callback
        self._attributes = None
        self.w = FloatingWindow((250, 50))
        self.buildUI(attributes)
        self.w.open()
        if document:
            self.w.assignToDocument(document)
        self.w.setTitle("Variables")

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

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

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

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

    def documentWindowToFront(self, sender=None):
        self.w.makeKey()
class TestInstallBackup(object):
    def __init__(self):
        self.w = FloatingWindow(
            (120, 140),
            minSize=(100, 40),
            textured=True,
        )
        self.w.ti_checkbox = CheckBox((10, 10, -10, 20),
                                      'Test Install',
                                      value=True)
        self.w.otf_checkbox = CheckBox((10, 30, -10, 20),
                                       'OTF Backup',
                                       value=True)
        self.w.button = SquareButton((10, 60, -10, -10),
                                     'GO',
                                     callback=self.button_callback)

        self.w.open()

    def build_otf(self, font):
        # this is the full path to the UFO
        ufo_path = font.path
        # the time string added to the file name
        time_string = time.strftime('%Y_%m_%d at %H-%M-%S')
        # the path to the final OTF
        otf_path = ufo_path[:-4] + ' ' + time_string + '.otf'

        # do not overwrite existing file
        if os.path.exists(otf_path):
            print('This file already exists and will not be overwritten.')
        else:
            # generate the OTF
            font.generate(
                otf_path,
                'otf',
                # checkOutlines removes overlaps
                checkOutlines=True,
            )
        # if the output path does not exist,
        # the font could not be generated.
        if not os.path.exists(otf_path):
            print('uh - oh. There was a problem')

        print(otf_path)

    def button_callback(self, sender):
        # Collect the state of the checkboxes
        test_install_state = self.w.ti_checkbox.get()
        build_otf_state = self.w.otf_checkbox.get()

        # Fetch the font and see if there even is one
        font = CurrentFont()
        if font:
            # let's see if the checkbox to build a UFO is checked.
            if build_otf_state:
                self.build_otf(font)
            # if the test install checkbox is checked
            if test_install_state:
                font.testInstall()
        else:
            print('No UFO open.')
Пример #15
0
 class DebuggerWindow(BaseWindowController):
     def __init__(self):
         self.w = FloatingWindow((123, 44), 'debug!')
         ms = MarginSelector()
         self.w.open()
         self.w.bind("close", ms.destroy)
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()
Пример #17
0
class RedArrowErrorFilter():

    def __init__(self):
        self.errorList = [
            'Extremum',
            'Mixed cubic and quadratic segments',
            'Fractional Coordinates',
            'Fractional transformation',
            'Incorrect smooth connection',
            'Empty segment',
            'Vector on closepath',
            'Collinear vectors',
            'Semi-horizontal vector',
            'Semi-vertical vector',
            'Zero handle',
        ]
        self.errorList.insert(0, "select Red Arrow Error")
        self.heightOfTool = 360
        self.widthOfTool = 200 
        self.w = FloatingWindow((self.widthOfTool, self.heightOfTool), "Red Arrow Error Filter") ### FloatingWindow
        #self.w.text = TextBox((10, 5, -10, 16), "...", sizeStyle='regular')       
        self.w.select_test = PopUpButton((10, 10, -10, 16), self.errorList, sizeStyle='regular', callback=self.select_test)
        self.w.extremumToleranceText = TextBox((10, 35, -25, 18), "Extremum Tolerance", sizeStyle='small')       
        self.w.extremumToleranceInput = EditText((160, 35, -10, 18), "2", sizeStyle='small')       

        self.w.smooth_connection_max_distance_Text = TextBox((10, 57, -25, 18), "Smooth Connect max_dist", sizeStyle='small')       
        self.w.smooth_connection_max_distance_Input = EditText((160, 55, -10, 18), "4", sizeStyle='small')

        self.w.collinear_vectors_max_distance_Text = TextBox((10, 77, -25, 18), "Collinear Vectors max_dist", sizeStyle='small')       
        self.w.collinear_vectors_max_distance_Input = EditText((160, 75, -10, 18), "2", sizeStyle='small')        
        
        self.w.report = EditText((10, 100, -10, -10), "...", sizeStyle='regular')       
        self.w.report.enable(False)

        self.count = []
        self.report = []
        self.w.open()


    def select_test(self, sender):
        
        # options
        try: 
            extremumTolerance = int(self.w.extremumToleranceInput.get())
        except ValueError:
            self.w.extremumToleranceInput.set(2)
            extremumTolerance = 2
        try: 
            smooth_connection_max_distance_Input = int(self.w.smooth_connection_max_distance_Input.get())
        except ValueError:
            self.w.smooth_connection_max_distance_Input.set(4)
            smooth_connection_max_distance_Input = 4     
        try: 
            collinear_vectors_max_distance_Input = int(self.w.collinear_vectors_max_distance_Input.get())
        except ValueError:
            self.w.collinear_vectors_max_distance_Input.set(2)
            collinear_vectors_max_distance_Input = 2 
        
        options = {
        "extremum_calculate_badness": True,
        "extremum_ignore_badness_below": extremumTolerance,
        "smooth_connection_max_distance": smooth_connection_max_distance_Input,
        "fractional_ignore_point_zero": True,
        "collinear_vectors_max_distance": collinear_vectors_max_distance_Input,
        }


        # a random number to change the mark color
        #randomNumber = random.random()
        # check if a font is open or not
        if CurrentFont():
            font = CurrentFont()
            glyphnames = CurrentFont().keys()
        else:
            self.w.report.set("Open a Font")
            return
        # start the outline test
        selection = []
        otp = OutlineTestPen(CurrentFont(), options)
        for n in glyphnames:
            otp.errors = []
            g = font[n]
            g.drawPoints(otp)
            if otp.errors:
                for e in otp.errors:
                    # convert error object to string
                    errorString = str(e).split(" ")[0]
                    # if the first part of error string = the first part of selection from PopUp                    
                    if errorString == str(sender.getItems()[sender.get()]).split(" ")[0]:
                        #g.mark = (1, randomNumber, 0.6, 1)
                        selection.append(g.name)
                        #print(e)
        font.selection = selection
        # output of glyphs with errors in UI
        result = dict((x,selection.count(x)) for x in set(selection))
        formattedResult = '  '.join("%s=%r" % (key,val) for (key,val) in sorted(result.items()))
        self.w.report.set(formattedResult)
Пример #18
0
class AscenderDescenderCalculator(BaseWindowController):

    lowerExtreme = None
    higherExtreme = None

    is_hhea = False
    is_vhea = False
    is_osTwo = False
    is_usWin = False

    def __init__(self):
        super(AscenderDescenderCalculator, self).__init__()
        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT),
                                PLUGIN_TITLE)

        jumpingY = MARGIN_VER
        self.w.calcButton = SquareButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Measure Extremes',
            callback=self.calcButtonCallback)

        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_ROW
        self.w.topCaption = TextBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['TextBoxRegularHeight']), 'Top: None')

        jumpingY += vanillaControlsSize['TextBoxRegularHeight'] + MARGIN_ROW
        self.w.btmCaption = TextBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['TextBoxRegularHeight']), 'Bottom: None')

        jumpingY += vanillaControlsSize['TextBoxRegularHeight']
        self.w.separationLine = HorizontalLine(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['TextBoxRegularHeight']))

        jumpingY += vanillaControlsSize['TextBoxRegularHeight'] + MARGIN_ROW
        self.w.check_hhea = CheckBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight']),
            'hhea table',
            value=self.is_hhea,
            callback=self.check_hheaCallback)

        jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW
        self.w.check_vhea = CheckBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight']),
            'vhea table',
            value=self.is_vhea,
            callback=self.check_vheaCallback)

        jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW
        self.w.check_osTwo = CheckBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight']),
            'OS/2 table',
            value=self.is_osTwo,
            callback=self.check_osTwoCallback)

        jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW
        self.w.check_usWin = CheckBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight']),
            'usWin table',
            value=self.is_usWin,
            callback=self.check_usWinCallback)

        jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW
        self.w.writeButton = SquareButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Write values into fonts',
            callback=self.writeButtonCallback)

        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_VER * 1.5
        self.w.resize(PLUGIN_WIDTH, jumpingY)
        self.setUpBaseWindowBehavior()
        self.w.open()

    def calcButtonCallback(self, sender):
        self.lowerExtreme, self.higherExtreme = findFamilyExtremes(AllFonts())
        self.updateCaptions()

    def updateCaptions(self):
        self.w.topCaption.set(
            'Highest: {ex.y} ({ex.glyphName}, {ex.styleName})'.format(
                ex=self.higherExtreme))
        self.w.btmCaption.set(
            'Lowest: {ex.y} ({ex.glyphName}, {ex.styleName})'.format(
                ex=self.lowerExtreme))

    def check_hheaCallback(self, sender):
        self.is_hhea = bool(sender.get())

    def check_vheaCallback(self, sender):
        self.is_vhea = bool(sender.get())

    def check_osTwoCallback(self, sender):
        self.is_osTwo = bool(sender.get())

    def check_usWinCallback(self, sender):
        self.is_usWin = bool(sender.get())

    def writeButtonCallback(self, sender):

        if self.lowerExtreme is not None and self.higherExtreme is not None:

            for eachFont in AllFonts():
                if self.is_hhea is True:
                    eachFont.info.openTypeHheaAscender = self.higherExtreme.y
                    eachFont.info.openTypeHheaDescender = self.lowerExtreme.y

                if self.is_vhea is True:
                    eachFont.info.openTypeVheaVertTypoAscender = self.higherExtreme.y
                    eachFont.info.openTypeVheaVertTypoDescender = self.lowerExtreme.y

                if self.is_osTwo is True:
                    eachFont.info.openTypeOS2TypoAscender = self.higherExtreme.y
                    eachFont.info.openTypeOS2TypoDescender = self.lowerExtreme.y

                if self.is_usWin is True:
                    eachFont.info.openTypeOS2WinAscent = self.higherExtreme.y
                    eachFont.info.openTypeOS2WinDescent = self.lowerExtreme.y

        else:
            message('Calc Extremes first!')
Пример #19
0
class AdjustAnchors(BaseWindowController):

	def __init__(self):
		self.font = CurrentFont()
		self.glyph = CurrentGlyph()
		self.upm = self.font.info.unitsPerEm
		self.glyphPreviewCacheDict = {} # key: glyph name -- value: list containing assembled glyphs
		self.anchorsOnMarksDict = {} # key: anchor name -- value: list of mark glyph names
		self.anchorsOnBasesDict = {} # key: anchor name -- value: list of base glyph names
		self.marksDict = {} # key: mark glyph name -- value: anchor name (NOTE: It's expected that each mark glyph only has one type of anchor)
		self.fillAnchorsAndMarksDicts()
		self.glyphNamesList = [] # list of glyph names that will be displayed in the UI list
		self.selectedGlyphNamesList = [] # list of glyph names selected in the UI list
		self.extraGlyphsList = [] # list of the glyph objects that should be inserted before and after the accented glyphs

		self.Blue, self.Alpha = 1, 0.6

		self.font.naked().addObserver(self, "fontWasModified", "Font.Changed")
		addObserver(self, "_fontWillClose", "fontWillClose")
		addObserver(self, "_currentFontChanged", "fontResignCurrent")
		addObserver(self, "_currentGlyphChanged", "currentGlyphChanged")
		addObserver(self, "_drawFill", "draw")
		addObserver(self, "_drawFill", "drawInactive")
		addObserver(self, "_previewFill", "drawPreview")
		addObserver(self, "_drawGlyphs", "draw") # observer for the draw event
		addObserver(self, "_drawGlyphs", "drawInactive") # draw the glyphs when the glyph window is not in focus
		addObserver(self, "_drawGlyphs", "drawPreview")

		integerNumFormatter = NSNumberFormatter.alloc().init()
		integerNumFormatter.setAllowsFloats_(False)
		integerNumFormatter.setGeneratesDecimalNumbers_(False)

		intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init()
		intPosMinZeroNumFormatter.setAllowsFloats_(False)
		intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False)
		intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0))

		intPosMinOneNumFormatter = NSNumberFormatter.alloc().init()
		intPosMinOneNumFormatter.setAllowsFloats_(False)
		intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False)
		intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1))

		self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize"))
		if not self.textSize:
			self.textSize = 150

		self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight"))
		if not self.lineHeight:
			self.lineHeight = 200

		self.extraSidebearings = getExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"))
		if not self.extraSidebearings:
			self.extraSidebearings = [0, 0]

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

		posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize"))
		if not posSize:
			posSize = (100, 100, 1200, 400)

		self.calibrateMode = getExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"))
		if not self.calibrateMode:
			self.calibrateMode = False

		calibrateModeStrings = getExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"))
		if not calibrateModeStrings:
			calibrateModeStrings = {
				'group1.baseInput': 'dotlessi o s',
				'group1.markInput': 'dieresis circumflex macron breve caron',
				'group2.baseInput': 'I O S',
				'group2.markInput': 'dieresis.cap circumflex.cap macron.cap breve.cap caron.cap',
				'group3.baseInput': 'I.sc O.sc S.sc',
				'group3.markInput': 'dieresis circumflex macron breve caron',
				'group4.baseInput': '',
				'group4.markInput': '',
			}

		# -- Window --
		self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400))
		self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback)
		if roboFontVersion < '1.7':
			self.w.fontList.getNSTableView().sizeToFit() # use the full width of the column
		self.w.fontList.show(not self.calibrateMode)
		self.w.lineView = MultiLineView((210, 10, -10, -41),
							pointSize = self.textSize,
							lineHeight = self.lineHeight,
							displayOptions={"Beam" : False, "displayMode" : "Multi Line"}
							)

		# -- Calibration Mode --
		baseLabel = "Bases"
		markLabel = "Marks"
		width, height = 190, 140
		self.cm = Group((0, 0, 0, 0))
		# ---
		self.cm.group1 = Group((5, height*0, width, height-10))
		self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel)
		self.cm.group1.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel)
		self.cm.group1.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group1.divider = HorizontalLine((0, -1, -0, 1))
		# ---
		self.cm.group2 = Group((5, height*1, width, height-10))
		self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel)
		self.cm.group2.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel)
		self.cm.group2.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group2.divider = HorizontalLine((0, -1, -0, 1))
		# ---
		self.cm.group3 = Group((5, height*2, width, height-10))
		self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel)
		self.cm.group3.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel)
		self.cm.group3.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group3.divider = HorizontalLine((0, -1, -0, 1))
		# ---
		self.cm.group4 = Group((5, height*3, width, height-10))
		self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel)
		self.cm.group4.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False)
		self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel)
		self.cm.group4.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False)
		# ---
		view = DefconAppKitTopAnchoredNSView.alloc().init()
		view.addSubview_(self.cm.getNSView())
		view.setFrame_(((0, 0), (width+10, height*4-23)))
		self.cm.setPosSize((0, 0, width+10, height*4-22))
		self.w.scrollView = ScrollView((5, 10, width+10, -41), view, drawsBackground=False, hasHorizontalScroller=False)
		self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder)
		self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) # NSScrollElasticityNone
		self.w.scrollView.show(self.calibrateMode)

		# -- Footer --
		self.w.calibrateModeCheck = CheckBox((10, -32, 200, -10), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode)
		self.w.textSizeLabel = TextBox((210, -30, 100, -10), "Text Size")
		self.w.textSize = EditText((270, -32, 35, -10), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter)
		self.w.lineHeightLabel = TextBox((320, -30, 100, -10), "Line Height")
		self.w.lineHeight = EditText((395, -32, 35, -10), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter)
		self.w.extraSidebearingsLabel = TextBox((446, -30, 180, -10), "Extra Sidebearings")
		self.w.extraSidebearingsChar  = TextBox((602, -30, 20, -10), "&")
		self.w.extraSidebearingLeft  = EditText((567, -32, 35, -10), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter)
		self.w.extraSidebearingRight = EditText((614, -32, 35, -10), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter)
		self.w.extraGlyphsLabel = TextBox((665, -30, 180, -10), "Extra Glyphs")
		self.w.extraGlyphs = EditText((749, -32, -10, -10), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False)

		# trigger the initial state and contents of the window
		self.extraGlyphsCallback() # this will call self.updateExtensionWindow()

		self.w.bind("close", self.windowClose)
		self.w.open()
		self.w.makeKey()


	def calibrateModeCallback(self, sender):
		self.calibrateMode = not self.calibrateMode
		self.w.fontList.show(not sender.get())
		self.w.scrollView.show(self.calibrateMode)
		self.updateExtensionWindow()


	def textSizeCallback(self, sender):
		try: # in case the user submits an empty field
			self.textSize = int(sender.get())
		except: # reset to the previous value
			NSBeep()
			self.sender.set(self.textSize)
		self.w.lineView.setPointSize(self.textSize)


	def lineHeightCallback(self, sender):
		try:
			self.lineHeight = int(sender.get())
		except:
			NSBeep()
			self.sender.set(self.lineHeight)
		self.w.lineView.setLineHeight(self.lineHeight)


	def extraSidebearingsCallback(self, sender):
		left = self.w.extraSidebearingLeft
		right = self.w.extraSidebearingRight
		try:
			self.extraSidebearings = [int(left.get()), int(right.get())]
		except:
			NSBeep()
			left.set(self.extraSidebearings[0])
			right.set(self.extraSidebearings[1])
		self.extraGlyphsCallback() # this will call self.updateExtensionWindow()


	def extraGlyphsCallback(self, *sender):
		del self.extraGlyphsList[:] # empty the list
		self.extraGlyphs = self.w.extraGlyphs.get()
		glyphNamesList = self.extraGlyphs.split()
		for gName in glyphNamesList:
			try:
				extraGlyph = self.font[gName]
				# must create a new glyph in order to be able to increase the sidebearings without modifying the font
				newGlyph = RGlyph()
				newGlyph.setParent(self.font)
				# must use deepAppend because the extra glyph may have components (which will cause problems to the MultiLineView)
				newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph)
				newGlyph.width = extraGlyph.width
			except RoboFontError:
				continue
			newGlyph.leftMargin += self.extraSidebearings[0]
			newGlyph.rightMargin += self.extraSidebearings[1]
			self.extraGlyphsList.append(newGlyph)
		self.glyphPreviewCacheDict.clear()
		self.updateExtensionWindow()


	def windowClose(self, sender):
		self.font.naked().removeObserver(self, "Font.Changed")
		removeObserver(self, "fontWillClose")
		removeObserver(self, "fontResignCurrent")
		removeObserver(self, "currentGlyphChanged")
		removeObserver(self, "draw")
		removeObserver(self, "drawInactive")
		removeObserver(self, "drawPreview")
		self.saveExtensionDefaults()


	def getCalibrateModeStrings(self):
		calibrateModeStringsDict = {}
		for i in range(1,5):
			group = getattr(self.cm, "group%d" % i)
			calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get()
			calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get()
		return calibrateModeStringsDict


	def saveExtensionDefaults(self):
		setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize())
		setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize)
		setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight)
		setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings)
		setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs)
		setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode)
		setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings())


	def _previewFill(self, info):
		self.Blue, self.Alpha = 0, 1


	def _drawFill(self, info):
		self.Blue, self.Alpha = 1, 0.6


	def _fontWillClose(self, info):
		"""
			Close the window when the last font is closed
		"""
		if len(AllFonts()) < 2:
			self.windowClose(self)
			self.w.close()


	def _currentFontChanged(self, info):
		self.font.naked().removeObserver(self, "Font.Changed")
		self.font = CurrentFont()
		self.font.naked().addObserver(self, "fontWasModified", "Font.Changed")
		self.fillAnchorsAndMarksDicts()
		del self.glyphNamesList[:]
		del self.selectedGlyphNamesList[:]
		self.updateExtensionWindow()


	def _currentGlyphChanged(self, info):
		self.updateExtensionWindow()


	def fontWasModified(self, info):
		OutputWindow().clear()
		self.fillAnchorsAndMarksDicts()
		del self.glyphNamesList[:]
		del self.selectedGlyphNamesList[:]
		self.updateExtensionWindow()


	def deepAppendGlyph(self, glyph, gToAppend, offset=(0,0)):
		if not gToAppend.components:
			glyph.appendGlyph(gToAppend, offset)
		else:
			for component in gToAppend.components:
				compGlyph = self.font[component.baseGlyph].copy()

				# handle component transformations
				componentTransformation = component.transformation
				# when undoing a paste anchor or a delete anchor action, RoboFont returns component.transformation as a list instead of a tuple
				if type(componentTransformation) is list:
					componentTransformation = tuple(componentTransformation)
				if componentTransformation != (1, 0, 0, 1, 0, 0): # if component is skewed and/or is shifted
					matrix = componentTransformation[0:4]
					if matrix != (1, 0, 0, 1): # if component is skewed
						transformObj = Identity.transform(matrix + (0, 0)) # ignore the original component's shifting values
						compGlyph.transform(transformObj)

				glyph.appendGlyph(compGlyph, map(sum, zip(component.offset, offset))) # add the two tuples of offset
			for contour in gToAppend:
				glyph.appendContour(contour, offset)

		# if the assembled glyph still has components, recursively remove and replace them 1-by-1 by the glyphs they reference
		if glyph.components:
			nestedComponent = glyph.components[-1] # start from the end
			glyph.removeComponent(nestedComponent)
			glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset)

		return glyph


	def updateCalibrateMode(self, *sender):
		glyphsList = []
		newLine = self.w.lineView.createNewLineGlyph()

		# cycle thru the UI Groups and collect the strings
		for i in range(1,5):
			group = getattr(self.cm, "group%d" % i)
			baseGlyphsNamesList = group.baseInput.get().split()
			markGlyphsNamesList = group.markInput.get().split()

			# iterate thru the base+mark combinations
			for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList):
				newGlyph = RGlyph()
				newGlyph.setParent(self.font)
				# skip invalid glyph names
				try:
					baseGlyph = self.font[gBaseName]
					markGlyph = self.font[gMarkName]
				except RoboFontError:
					continue
				# append base glyph
				newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph)
				# append mark glyph
				newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph))
				# set the advanced width
				dfltSidebearings = self.upm * .05 # 5% of the UPM
				newGlyph.leftMargin = dfltSidebearings + self.extraSidebearings[0]
				newGlyph.rightMargin = dfltSidebearings + self.extraSidebearings[1]
				# append the assembled glyph to the list
				glyphsList.extend(self.extraGlyphsList)
				glyphsList.append(newGlyph)

			# add line break, if both input fields have content
			if baseGlyphsNamesList and markGlyphsNamesList:
				glyphsList.extend(self.extraGlyphsList)
				glyphsList.append(newLine)

		# update the contents of the MultiLineView
		self.w.lineView.set(glyphsList)


	def updateExtensionWindow(self):
		if self.calibrateMode:
			self.updateCalibrateMode()
			return

		if CurrentGlyph() is not None: # NOTE: CurrentGlyph() will return zero (its length), so "is not None" is necessary
			self.glyph = CurrentGlyph()
			self.glyphNamesList = self.makeGlyphNamesList(self.glyph)
			self.updateListView()
			currentGlyphName = self.glyph.name

			# base glyph + accent combinations preview
			# first check if there's a cached glyph
			if currentGlyphName in self.glyphPreviewCacheDict:
				self.w.lineView.set(self.glyphPreviewCacheDict[currentGlyphName])

			# assemble the glyphs
			else:
				glyphsList = []
				for glyphNameInUIList in self.glyphNamesList:
					newGlyph = RGlyph()
					newGlyph.setParent(self.font)

					# the glyph in the UI list is a mark
					if glyphNameInUIList in self.marksDict:
						markGlyph = self.font[glyphNameInUIList]

						# append base glyph
						newGlyph = self.deepAppendGlyph(newGlyph, self.glyph)
						# append mark glyph
						newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph))

						# set the advanced width
						if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width
							newGlyph.leftMargin = self.upm * .05 # 5% of the UPM
							newGlyph.rightMargin = newGlyph.leftMargin
						else:
							newGlyph.width = self.glyph.width

					# the glyph in the UI list is a base
					else:
						baseGlyph = self.font[glyphNameInUIList]

						# append base glyph
						newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph)
						# append mark glyph
						newGlyph = self.deepAppendGlyph(newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph))

						# set the advanced width
						if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width
							newGlyph.leftMargin = self.upm * .05
							newGlyph.rightMargin = newGlyph.leftMargin
						else:
							newGlyph.width = baseGlyph.width

					# pad the new glyph if it has too much overhang
					if newGlyph.leftMargin < self.upm * .15:
						newGlyph.leftMargin = self.upm * .05
					if newGlyph.rightMargin < self.upm * .15:
						newGlyph.rightMargin = self.upm * .05

					# add extra sidebearings
						newGlyph.leftMargin += self.extraSidebearings[0]
						newGlyph.rightMargin += self.extraSidebearings[1]

					# one last check for making sure the new glyph can be displayed
					if not newGlyph.components:
						glyphsList.extend(self.extraGlyphsList)
						glyphsList.append(newGlyph)
					else:
						print "Combination with mark glyph %s can't be previewed because it contains component %s." % (glyphNameInUIList, newGlyph.components[0].baseGlyph)

				glyphsList.extend(self.extraGlyphsList)
				self.w.lineView.set(glyphsList)

				# add to the cache
				self.glyphPreviewCacheDict[currentGlyphName] = glyphsList
		else:
			self.w.lineView.set([])


	def listSelectionCallback(self, sender):
		selectedGlyphNamesList = []
		for index in sender.getSelection():
			selectedGlyphNamesList.append(self.glyphNamesList[index])
		self.selectedGlyphNamesList = selectedGlyphNamesList
		self.updateGlyphView()


	def updateGlyphView(self):
		# update the current glyph view
		UpdateCurrentGlyphView()


	def fillAnchorsAndMarksDicts(self):
		# reset all the dicts
		self.glyphPreviewCacheDict.clear()
		self.anchorsOnMarksDict.clear()
		self.anchorsOnBasesDict.clear()
		self.marksDict.clear()
		markGlyphsWithMoreThanOneAnchorTypeList = []

		for glyphName in self.font.glyphOrder:
			glyphAnchorsList = self.font[glyphName].anchors
			for anchor in glyphAnchorsList:
				if anchor.name[0] == '_':
					anchorName = anchor.name[1:]
					# add to AnchorsOnMarks dictionary
					if anchorName not in self.anchorsOnMarksDict:
						self.anchorsOnMarksDict[anchorName] = [glyphName]
					else:
						tempList = self.anchorsOnMarksDict[anchorName]
						tempList.append(glyphName)
						self.anchorsOnMarksDict[anchorName] = tempList
					# add to Marks dictionary
					if glyphName not in self.marksDict:
						self.marksDict[glyphName] = anchorName
					else:
						if glyphName not in markGlyphsWithMoreThanOneAnchorTypeList:
							markGlyphsWithMoreThanOneAnchorTypeList.append(glyphName)
				else:
					anchorName = anchor.name
					# add to AnchorsOnBases dictionary
					if anchorName not in self.anchorsOnBasesDict:
						self.anchorsOnBasesDict[anchorName] = [glyphName]
					else:
						tempList = self.anchorsOnBasesDict[anchorName]
						tempList.append(glyphName)
						self.anchorsOnBasesDict[anchorName] = tempList

		if markGlyphsWithMoreThanOneAnchorTypeList:
			for glyphName in markGlyphsWithMoreThanOneAnchorTypeList:
				print "ERROR: Glyph %s has more than one type of anchor." % glyphName


	def makeGlyphNamesList(self, glyph):
		glyphNamesList = []
		markGlyphIsAbleToBeBase = False
		if glyph is not None: # NOTE: "if glyph" will return zero (its length), so "is not None" is necessary
			# assemble the list for the UI list
			for anchor in glyph.anchors:
				anchorName = anchor.name
				if anchorName in self.anchorsOnMarksDict:
					glyphNamesList.extend(self.anchorsOnMarksDict[anchorName])
				elif anchorName[1:] in self.anchorsOnBasesDict: # skips the leading underscore
					glyphNamesList.extend(self.anchorsOnBasesDict[anchorName[1:]])

			# for mark glyphs, test if they're able to get other mark glyphs attached to them
			# this will (correctly) prevent the UI list from including glyph names that cannot be displayed with the current glyph
			if glyph.name in self.marksDict:
				for anchor in glyph.anchors:
					if anchor.name[0] != '_': # the current mark glyph has anchors that allow it to be a base for other marks
						markGlyphIsAbleToBeBase = True
						break
				# remove marks from the glyph list if the current mark glyph can't work as a base
				if not markGlyphIsAbleToBeBase:
					for glyphName in glyphNamesList[::-1]: # iterate from the end of the list
						if glyphName in self.marksDict:
							glyphNamesList.remove(glyphName)

		return glyphNamesList


	def updateListView(self):
		self.w.fontList.set(self.glyphNamesList)


	def getAnchorOffsets(self, canvasGlyph, glyphToDraw):
		# the current glyph is a mark
		if canvasGlyph.name in self.marksDict:
			# glyphToDraw is also a mark (mark-to-mark case)
			if glyphToDraw.name in self.marksDict:
				# pick the (mark glyph) anchor to draw on
				for anchor in canvasGlyph.anchors:
					if anchor.name[0] != '_':
						anchorName = anchor.name
						markAnchor = anchor
						break
				# pick the (base glyph) anchor to draw on
				for anchor in glyphToDraw.anchors:
					try:
						if anchor.name == '_'+ anchorName:
							baseAnchor = anchor
							break
					except UnboundLocalError:
						continue
			# glyphToDraw is not a mark
			else:
				# pick the (mark glyph) anchor to draw on
				for anchor in canvasGlyph.anchors:
					if anchor.name[0] == '_':
						anchorName = anchor.name[1:]
						markAnchor = anchor
						break
				# pick the (base glyph) anchor to draw on
				for anchor in glyphToDraw.anchors:
					try:
						if anchor.name == anchorName:
							baseAnchor = anchor
							break
					except UnboundLocalError:
						continue

			try:
				offsetX = markAnchor.x - baseAnchor.x
				offsetY = markAnchor.y - baseAnchor.y
			except UnboundLocalError:
				offsetX = 0
				offsetY = 0

		# the current glyph is a base
		else:
			try:
				anchorName = self.marksDict[glyphToDraw.name]
			except KeyError:
				anchorName = None

			if anchorName:
				# pick the (base glyph) anchor to draw on
				for anchor in canvasGlyph.anchors:
					if anchor.name == anchorName:
						baseAnchor = anchor
						break
				# pick the (mark glyph) anchor to draw on
				for anchor in glyphToDraw.anchors:
					if anchor.name == '_'+ anchorName:
						markAnchor = anchor
						break

			try:
				offsetX = baseAnchor.x - markAnchor.x
				offsetY = baseAnchor.y - markAnchor.y
			except UnboundLocalError:
				offsetX = 0
				offsetY = 0

		return (offsetX, offsetY)


	def _drawGlyphs(self, info):
		""" draw stuff in the glyph window view """
		translateBefore = (0, 0)

		for glyphName in self.selectedGlyphNamesList:
			glyphToDraw = self.font[glyphName]

			# determine the offset of the anchors
			offset = self.getAnchorOffsets(self.glyph, glyphToDraw)

			# set the offset of the drawing
			translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1])

			# record the shift amounts (these are needed for resetting the drawing position when more than one mark is selected on the list)
			translateBefore = offset

			# set the fill & stroke
			fill(0, 0, self.Blue, self.Alpha)
			strokeWidth(None)

			# draw it
			mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font)
			glyphToDraw.draw(mojoPen)
			mojoPen.draw()
class GenerateWithOrder(object):
    """A simple extension to generate font with order from a FL Encoding file """
    def __init__(self):
        self.glyphOrder = []
        self.orderFileName = ''
        self.formats = ['otf', 'ttf', 'pfa']
        self.format = 'otf'
        self.decompose = True
        self.overlap = True
        self.autohint = True
        self.release = True
        
        self.w = FloatingWindow((200,270), "Generate", minSize=(200,270),)
        self.w.getEncoding = Button((10, 10, 180, 20), 'Get .enc file', callback=self.getEncodingCallback)
        self.w.viewEncoding = Button((10, 75, 180, 20), 'View encoding', callback=self.viewEncodingCallback)
        self.w.line = HorizontalLine((12, 103, -12, 1))
        
        self.w.formatLabel    = TextBox((15, 117, 60, 20), "Format")
        self.w.formatChoice   = PopUpButton((70, 115, 80, 20), self.formats, callback=self.formatCallback)
        self.w.decomposeCheck = CheckBox((20, 141, -10, 20), "Decompose", callback=self.decomposeCallback, value=self.decompose)
        self.w.overlapCheck   = CheckBox((20, 161, -10, 20), "Remove Overlap", callback=self.overlapCallback, value=self.overlap)
        self.w.autohintCheck  = CheckBox((20, 181, -10, 20), "Autohint", callback=self.autohintCallback, value=self.autohint)
        self.w.releaseCheck   = CheckBox((20, 201, -10, 20), "Release Mode", callback=self.releaseCallback, value=self.release)
        self.w.generate = Button((10, 232, 180, 20), 'Generate Font', callback=self.generateCallback)
    
        self.w.decomposeCheck.enable(False)
        
        self.w.viewEncoding.enable(False)
        self.w.formatChoice.enable(False)
        self.w.overlapCheck.enable(False)
        self.w.autohintCheck.enable(False)
        self.w.releaseCheck.enable(False)
        self.w.generate.enable(False)
        
        self.d = Drawer((170, 400), self.w, preferredEdge="right")
        self.d.text = TextEditor((10, 10, -10, -10), readOnly=True)
        
        self.d.open()
        self.d.toggle()
        self.w.open()

    def process_enc(self, p):
        order = []
        
        f = open(p)
        for line in f:
            if line.startswith(('#', '%')):
                pass
            else:
                l = line.split()
                if len(l[0]) != 0:
                    for i in l:
                        if not i.startswith(('#', '%', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) and len(i) != 0:
                            order.append(i)
        f.close()
        
        self.glyphOrder = order

                
    def getEncodingCallback(self, sender):
        getFile(parentWindow=self.w, fileTypes=['enc', 'Enc'], resultCallback=self.processEncodingCallback)

    def viewEncodingCallback(self, sender):
        if not self.d.isOpen():
            t = "\n".join(self.glyphOrder)
            self.d.text.set(t)
        self.d.toggle()

    def processEncodingCallback(self, sender):
        if sender[0] is not None:
            fn = os.path.split(sender[0])[1]
            self.process_enc(sender[0])
            self.w.encodingTitle = TextBox((15, 34, 180, 17), "Encoding File:", alignment="left")
            self.w.encodingFileTitle = TextBox((15, 52, 180, 17), fn, alignment="left")
            
            self.w.viewEncoding.enable(True)
            self.w.formatChoice.enable(True)
            self.w.overlapCheck.enable(True)
            self.w.autohintCheck.enable(True)
            self.w.releaseCheck.enable(True)
            self.w.generate.enable(True)
            
    def formatCallback(self, sender):
        self.format = self.formats[sender.get()]
        if self.format == 'otf' or self.format == 'pfa':
            self.decompose = True
            self.w.decomposeCheck.enable(False)
            self.w.decomposeCheck.set(True)
        else:
            self.w.decomposeCheck.enable(True)

    def decomposeCallback(self, sender):
        if sender.get() == 0:
            self.decompose = False
        else:
            self.decompose = True

    def overlapCallback(self, sender):
        if sender.get() == 0:
            self.overlap = False
        else:
            self.overlap = True

    def autohintCallback(self, sender):
        if sender.get() == 0:
            self.autohint = False
        else:
            self.autohint = True

    def releaseCallback(self, sender):
        if sender.get() == 0:
            self.release = False
        else:
            self.release = True
            
    def generateCallback(self, sender):
        font = CurrentFont()
        d,f = os.path.split(font.path)
        f = f[:-3] + self.format
        putFile(messageText="Save Font", directory=d, fileName=f, parentWindow=self.w, resultCallback=self.processGenerateCallback)
        
    def processGenerateCallback(self, sender):
        path = sender
        font = CurrentFont()
        font.generate(path, self.format, decompose=self.decompose, autohint=self.autohint, releaseMode=self.release, glyphOrder=self.glyphOrder)
Пример #21
0
class MasterSelectorWindow(GeneralPlugin):
    @objc.python_method
    def settings(self):
        self.name = 'Master Selector'
        self.warningOnlyOneMaster = Glyphs.localize({
            "en":
            "Your font has only one master.",
            "de":
            "Die Schrift hat nur einen Master.",
            "fr":
            "La police contient seulement un master.",
            "es":
            "La fuente tiene solamente un máster.",
        })
        self.warningNoFontOpen = Glyphs.localize({
            "en":
            "Please open a font first.",
            "de":
            "Bitte öffnen Sie erst eine Schrift.",
            "fr":
            "Veuillez ouvrir un fichier de police.",
            "es":
            "Por favor, primero abrir una fuente.",
        })

    @objc.python_method
    def start(self):
        try:
            targetMenu = WINDOW_MENU
            newMenuItem = NSMenuItem(self.name, self.showWindow_)
            Glyphs.menu[targetMenu].append(newMenuItem)
        except:
            mainMenu = Glyphs.mainMenu()
            s = objc.selector(self.showWindow, signature='v@:@')
            newMenuItem = NSMenuItem.alloc(
            ).initWithTitle_action_keyEquivalent_(self.name, s, "")
            newMenuItem.setTarget_(self)
            mainMenu.itemWithTag_(5).submenu().addItem_(newMenuItem)

    def showWindow_(self, sender):
        if Glyphs.font is None:
            self.helperWindow(self.warningNoFontOpen)
        elif len(Glyphs.font.masters) < 2:
            self.helperWindow(self.warningOnlyOneMaster)
        else:
            mastersList = []
            for m in Glyphs.font.masters:
                mastersList.append(m.name)
            currentMasterIndex = Glyphs.font.masterIndex

            self.windowWidth = 250
            self.windowHeight = 25 * len(mastersList) + 23 + 30
            self.w = FloatingWindow((self.windowWidth, self.windowHeight),
                                    self.name)
            self.w.radiomasters = RadioGroup(
                (10, 10, -10, 25 * len(mastersList)),
                mastersList,
                callback=self.changeMaster)
            self.w.slider = Slider((10, -35, -10, 23),
                                   tickMarkCount=len(mastersList),
                                   stopOnTickMarks=True,
                                   value=currentMasterIndex,
                                   minValue=0,
                                   maxValue=len(mastersList) - 1,
                                   sizeStyle="small",
                                   continuous=False,
                                   callback=self.changeMasterSlider)

            self.w.open()

    @objc.python_method
    def helperWindow(self, message):
        Message(title=self.name, message=message, OKButton=None)

    @objc.python_method
    def changeMaster(self, sender):
        currentChoice = self.w.radiomasters.get()
        try:
            Glyphs.font.parent.windowController().setMasterIndex_(
                currentChoice)
            self.w.slider.set(currentChoice)
        except:
            print('BROKEN')
            pass

    @objc.python_method
    def changeMasterSlider(self, sender):
        currentChoice = int(self.w.slider.get())
        try:
            Glyphs.font.parent.windowController().setMasterIndex_(
                currentChoice)
            self.w.radiomasters.set(currentChoice)
        except:
            print('BROKEN')
            pass

    @objc.python_method
    def __file__(self):
        """Please leave this method unchanged"""
        return __file__
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
Пример #23
0
class GlyphFax(object):
    def __init__(self):
        '''Initialize the dialog.'''
        x = y = padding = 10
        buttonHeight = 20
        windowWidth = 400

        rows = 4.5

        self.w = FloatingWindow(
            (windowWidth, buttonHeight * rows + padding * (rows)),
            "Glyph Fax Machine")

        self.fonts = {}

        # self.w.textBox = TextBox((x, y, -padding, buttonHeight), "Glyphs to Copy")

        # y += buttonHeight

        self.w.editText = EditText(
            (x, y, -padding, buttonHeight * 2 + padding),
            placeholder="Space-separated list of glyphs to copy")

        y += buttonHeight * 2 + padding * 2

        # self.w.overwriteGlyphsCheckBox = CheckBox((x, y, -padding, buttonHeight), "Overwrite Glyphs", value=False, callback=self.overwriteGlyphsOptions)

        # y += buttonHeight + padding

        self.w.overwriteNormalWidthGlyphsCheckBox = CheckBox(
            (x, y, -padding, buttonHeight),
            "Overwrite 600w Glyphs",
            value=False,
            sizeStyle="small")
        # self.w.overwriteNormalWidthGlyphsCheckBox.show(False)

        self.w.overwriteAdjustedWidthGlyphsCheckBox = CheckBox(
            (windowWidth * 0.4, y, -padding, buttonHeight),
            "Overwrite non-600w Glyphs",
            value=False,
            sizeStyle="small")
        # self.w.overwriteAdjustedWidthGlyphsCheckBox.show(False)
        self.w.colorWell = ColorWell(
            (windowWidth * 0.85, y, -padding, buttonHeight),
            color=NSColor.orangeColor())

        y += buttonHeight + padding

        self.w.sans2mono = Button(
            (x, y, windowWidth / 3 - padding / 2, buttonHeight),
            "Sans → Mono",
            callback=self.sans2monoCallback)

        self.w.mono2sans = Button(
            (windowWidth / 3 + padding, y, -padding, buttonHeight),
            "Mono → Sans",
            callback=self.mono2SansCallback)

        self.w.open()

    def copyGlyph(self, glyphName, fontToCopyFrom, fontToSendTo):

        print(
            f"copying glyph /{glyphName} from \n\t{fontToCopyFrom} \n\tto \n\t{fontToSendTo}"
        )

        glyphCopyName = glyphName
        copyGlyph = True

        if glyphName not in fontToSendTo:
            fontToSendTo.newGlyph(glyphName)
        else:
            overwrite600 = self.w.overwriteNormalWidthGlyphsCheckBox.get()
            overwriteNon600 = self.w.overwriteAdjustedWidthGlyphsCheckBox.get()

            # default case: overwrite is false (overwrite 600 is true, overwrite non600 is false)
            if overwrite600 == False and overwriteNon600 == False:
                # glyphCopyName = glyphName + '.copy'
                # fontToSendTo.newGlyph(glyphCopyName)

                copyGlyph = False

            elif overwrite600 == True and overwriteNon600 == True:
                fontToSendTo[glyphCopyName].clear()

            elif overwrite600 == False and overwriteNon600 == True:
                if round(fontToSendTo[glyphCopyName].width, -1) == 600:
                    # glyphCopyName = glyphName + '.copy'
                    # fontToSendTo.newGlyph(glyphCopyName)

                    copyGlyph = False
                else:
                    fontToSendTo[glyphCopyName].clear()

            elif overwrite600 == True and overwriteNon600 == False:
                if round(fontToSendTo[glyphCopyName].width, -1) == 600:
                    fontToSendTo[glyphCopyName].clear()
                else:
                    # glyphCopyName = glyphName + '.copy'
                    # fontToSendTo.newGlyph(glyphCopyName)

                    copyGlyph = False

        if copyGlyph:
            glyphToCopy = fontToCopyFrom[glyphName]
            layerGlyph = fontToSendTo[glyphCopyName].getLayer("foreground")

            # get the point pen of the layer glyph
            pen = layerGlyph.getPointPen()
            # draw the points of the imported glyph into the layered glyph
            glyphToCopy.drawPoints(pen)

            if len(layerGlyph.components) == 1:
                parentGlyphWidth = fontToSendTo[
                    layerGlyph.components[0].baseGlyph].width
                print(type(parentGlyphWidth))
                layerGlyph.width = parentGlyphWidth

                print("copied glyph now has width of its component parent:",
                      str(parentGlyphWidth))
            else:
                layerGlyph.width = glyphToCopy.width

            layerGlyph.markColor = (self.w.colorWell.get().redComponent(),
                                    self.w.colorWell.get().greenComponent(),
                                    self.w.colorWell.get().blueComponent(),
                                    self.w.colorWell.get().alphaComponent())

            for anchor in glyphToCopy.anchors:
                layerGlyph.appendAnchor(anchor.name, (anchor.x, anchor.y))

    def glyphsToCopy(self, sender):
        if self.w.editText.get() == "":
            Message('no glyphs listed',
                    title='Glyph Fax Machine',
                    informativeText='Please list glyphs to send copies of!')
            return

        return self.w.editText.get().split(" ")

    # default: dlig & ss01–ss12 off

    def getFontsCueCopying(self, proportionToCopyFrom, glyphsToCopy):
        files = getFile("Select UFOs to copy glyphs between",
                        allowsMultipleSelection=True,
                        fileTypes=["ufo"])

        for path in files:
            f = OpenFont(path, showInterface=True)
            variation = f.info.styleName.replace('Mono ',
                                                 '').replace('Sans ', '')
            if variation not in self.fonts.keys():
                self.fonts[variation] = []

            if self.fonts[variation] == []:
                self.fonts[variation].append(f)
            elif self.fonts[variation] != []:
                # if 'Mono' already in list, put 'Sans' second
                if proportionToCopyFrom in self.fonts[variation][
                        0].info.styleName:
                    self.fonts[variation].append(f)
                # else put 'Mono' first
                else:
                    self.fonts[variation] = [f] + self.fonts[variation]

        for variation in self.fonts.keys():
            print('\n', variation, '\n')
            for glyphName in glyphsToCopy:
                self.copyGlyph(glyphName, self.fonts[variation][0],
                               self.fonts[variation][1])

        self.w.close()

    ## ss01–ss12 on

    def mono2SansCallback(self, sender):
        '''Copy glyphs from Mono to Sans masters.'''

        glyphsToCopy = self.glyphsToCopy(sender)

        if glyphsToCopy is not None:
            self.getFontsCueCopying('Mono', glyphsToCopy)

    def sans2monoCallback(self, sender):
        '''Copy glyphs from Sans to Mono masters.'''

        glyphsToCopy = self.glyphsToCopy(sender)

        if glyphsToCopy is not None:
            self.getFontsCueCopying('Sans', glyphsToCopy)
Пример #24
0
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)
Пример #25
0
class SidebearingsLinker(BaseWindowController):

    allFonts = []

    servantSubscriptions = []
    masterSubscriptions = []
    displayedSubscriptions = []

    currentRow = None
    selectedFont = None

    def __init__(self, willOpen=True):
        super(SidebearingsLinker, self).__init__()

        # collecting fonts
        self.allFonts = AllFonts()
        if self.allFonts != []:
            self.selectedFont = self.allFonts[0]

        # interface
        self.w = FloatingWindow((PLUGIN_WIDTH, PLUGIN_HEIGHT), PLUGIN_TITLE)

        jumpingY = MARGIN_VER
        self.w.fontPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            getNamesFrom(self.allFonts),
            callback=self.fontPopUpCallback)

        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_ROW
        self.w.canvas = CanvasGroup(
            (MARGIN_HOR, jumpingY, NET_WIDTH, CANVAS_HEIGHT), delegate=self)

        jumpingY += CANVAS_HEIGHT + MARGIN_ROW
        linksColumnDescriptions = [{
            "title": "left",
            'key': 'lft',
            'width': LIST_WIDE_COL
        }, {
            "title": "active",
            "cell": CheckBoxListCell(),
            'key': 'lftActive',
            'width': LIST_NARROW_COL
        }, {
            "title": "glyph",
            'key': 'servant',
            'width': LIST_WIDE_COL,
            "editable": False
        }, {
            "title": "active",
            "cell": CheckBoxListCell(),
            'key': 'rgtActive',
            'width': LIST_NARROW_COL
        }, {
            "title": "right",
            'key': 'rgt',
            'width': LIST_WIDE_COL
        }]

        if self.selectedFont is not None:
            links = loadLinksFromFont(self.selectedFont)
        else:
            links = []

        self.w.linksList = List(
            (MARGIN_HOR, jumpingY, NET_WIDTH, 200),
            links,
            showColumnTitles=False,
            allowsMultipleSelection=False,
            drawVerticalLines=True,
            columnDescriptions=linksColumnDescriptions,
            selectionCallback=self.selectionLinksListCallback,
            editCallback=self.editLinksListCallback)

        if self.selectedFont is not None:
            self.w.linksList.setSelection([0])
            self.currentRow = self.w.linksList[0]
            self.matchDisplayedSubscriptions()

        jumpingY += self.w.linksList.getPosSize()[3] + MARGIN_ROW
        buttonWidth = (NET_WIDTH - MARGIN_HOR) / 2
        self.w.linkAllButton = SquareButton(
            (MARGIN_HOR, jumpingY, buttonWidth,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Link All',
            callback=self.linkAllButtonCallback)

        self.w.unlockAllButton = SquareButton(
            (MARGIN_HOR * 2 + buttonWidth, jumpingY, buttonWidth,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Unlink All',
            callback=self.unlockAllButtonCallback)

        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_ROW
        self.w.separationLineOne = HorizontalLine(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['HorizontalLineThickness']))

        jumpingY += MARGIN_ROW
        self.w.pushIntoFontButton = SquareButton(
            (MARGIN_HOR, jumpingY, buttonWidth,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Push into font',
            callback=self.pushIntoFontButtonCallback)
        self.w.pushIntoFontButton.enable(False)

        self.w.clearLibsButton = SquareButton(
            (MARGIN_HOR * 2 + buttonWidth, jumpingY, buttonWidth,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Clear Libs',
            callback=self.clearLibsButtonCallback)

        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_ROW
        self.w.loadFromTable = SquareButton(
            (MARGIN_HOR, jumpingY, buttonWidth,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Load from table',
            callback=self.loadFromTableCallback)
        self.w.loadFromTable.enable(True)

        self.w.exportTable = SquareButton(
            (MARGIN_HOR * 2 + buttonWidth, jumpingY, buttonWidth,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Export table',
            callback=self.exportTableCallback)
        self.w.exportTable.enable(True)

        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_VER * 2
        self.w.resize(PLUGIN_WIDTH, jumpingY)

        self.setUpBaseWindowBehavior()

        if self.selectedFont is not None:
            self.matchSubscriptions()
            result = askYesNo('Warning',
                              'Do you want to align servants to masters?')
            if bool(result) is True:
                self._alignServantsToMasters()

        addObserver(self, "drawOnGlyphCanvas", "draw")
        addObserver(self, "drawOnGlyphCanvas", "drawInactive")
        addObserver(self, 'fontDidOpenCallback', 'fontDidOpen')
        addObserver(self, 'fontDidCloseCallback', 'fontDidClose')
        if willOpen is True:
            self.w.open()

    # drawing callbacks
    def drawOnGlyphCanvas(self, infoDict):
        glyphOnCanvas = infoDict['glyph']
        scalingFactor = infoDict['scale']
        bodySize = .25
        horizontalOffset = 80

        if PLUGIN_LIB_NAME in glyphOnCanvas.lib:
            thisLib = glyphOnCanvas.lib[PLUGIN_LIB_NAME]
        else:
            return None

        lftGlyph = None
        if thisLib['lft'] != '':
            lftGlyph = self.selectedFont[thisLib['lft']]

        rgtGlyph = None
        if thisLib['rgt'] != '':
            rgtGlyph = self.selectedFont[thisLib['rgt']]

        try:
            dt.fill(*GRAY)

            if lftGlyph is not None:
                dt.save()
                dt.translate(-lftGlyph.width * bodySize - horizontalOffset,
                             -self.selectedFont.info.unitsPerEm * bodySize)

                # glyph
                dt.scale(bodySize)
                dt.drawGlyph(lftGlyph)

                # lock
                if thisLib['lftActive'] is True:
                    txt = u'🔒'
                else:
                    txt = u'🔓'
                dt.fontSize(300)
                txtWdt, txtHgt = dt.textSize(txt)
                dt.text(txt, (-txtWdt, 0))
                dt.restore()

            if rgtGlyph is not None:
                dt.save()
                dt.translate(glyphOnCanvas.width + horizontalOffset,
                             -self.selectedFont.info.unitsPerEm * bodySize)
                dt.scale(bodySize)
                dt.drawGlyph(rgtGlyph)

                # lock
                if thisLib['rgtActive'] is True:
                    txt = u'🔒'
                else:
                    txt = u'🔓'
                dt.fontSize(300)
                dt.text(txt, (rgtGlyph.width, 0))

                dt.restore()

        except Exception as error:
            print(error)

    def draw(self):
        try:
            if self.selectedFont is not None and self.currentRow is not None:
                scalingFactor = CANVAS_HEIGHT / (
                    self.selectedFont.info.unitsPerEm + UPM_MARGIN)

                if self.currentRow['lft'] in self.selectedFont:
                    lftGlyph = self.selectedFont[self.currentRow['lft']]
                    if lftGlyph is not None:
                        drawReferenceGlyph(aGlyph=lftGlyph,
                                           scalingFactor=scalingFactor,
                                           startingX=NET_WIDTH * (1 / 6),
                                           left=True,
                                           right=False)

                    drawLock(closed=self.currentRow['lftActive'],
                             startingX=NET_WIDTH * (2 / 6),
                             glyphQuota=self.selectedFont.info.xHeight,
                             scalingFactor=scalingFactor)

                if self.currentRow['servant'] in self.selectedFont:
                    servantGlyph = self.selectedFont[
                        self.currentRow['servant']]
                    if servantGlyph is not None:
                        drawReferenceGlyph(aGlyph=servantGlyph,
                                           scalingFactor=scalingFactor,
                                           startingX=NET_WIDTH * (3 / 6),
                                           left=True,
                                           right=True)

                if self.currentRow['rgt'] in self.selectedFont:
                    rgtGlyph = self.selectedFont[self.currentRow['rgt']]
                    if rgtGlyph is not None:
                        drawReferenceGlyph(aGlyph=rgtGlyph,
                                           scalingFactor=scalingFactor,
                                           startingX=NET_WIDTH * (5 / 6),
                                           right=True)

                    drawLock(closed=self.currentRow['rgtActive'],
                             startingX=NET_WIDTH * (4 / 6),
                             glyphQuota=self.selectedFont.info.xHeight,
                             scalingFactor=scalingFactor)

        except Exception as error:
            print(error)

    # observers
    def unsubscribeGlyphs(self):
        for eachGlyph in self.servantSubscriptions:
            eachGlyph.removeObserver(self, "Glyph.WidthChanged")
        self.servantSubscriptions = list()
        for eachGlyph in self.masterSubscriptions:
            eachGlyph.removeObserver(self, "Glyph.WidthChanged")
        self.masterSubscriptions = list()

    def unsubscribeDisplayedGlyphs(self):
        for eachGlyph in self.displayedSubscriptions:
            eachGlyph.removeObserver(self, "Glyph.Changed")
        self.displayedSubscriptions = list()

    def matchDisplayedSubscriptions(self):
        self.unsubscribeDisplayedGlyphs()

        if self.currentRow['lft'] != '':
            lftGlyph = self.selectedFont[self.currentRow['lft']]
            if lftGlyph not in self.displayedSubscriptions:
                lftGlyph.addObserver(self, 'displayedGlyphChanged',
                                     'Glyph.Changed')
                self.displayedSubscriptions.append(lftGlyph)

        if self.currentRow['rgt'] != '':
            rgtGlyph = self.selectedFont[self.currentRow['rgt']]
            if rgtGlyph not in self.displayedSubscriptions:
                rgtGlyph.addObserver(self, 'displayedGlyphChanged',
                                     'Glyph.Changed')
                self.displayedSubscriptions.append(rgtGlyph)

        if self.currentRow['servant'] != '':
            servantGlyph = self.selectedFont[self.currentRow['servant']]
            if servantGlyph not in self.displayedSubscriptions:
                servantGlyph.addObserver(self, 'displayedGlyphChanged',
                                         'Glyph.Changed')
                self.displayedSubscriptions.append(servantGlyph)

    def matchSubscriptions(self):
        self.unsubscribeGlyphs()

        for servantGlyph in self.selectedFont:
            if PLUGIN_LIB_NAME in servantGlyph.lib:
                thisLib = servantGlyph.lib[PLUGIN_LIB_NAME]

                if (thisLib['lft'] != '' and thisLib['lftActive'] is True) or (
                        thisLib['rgt'] != '' and thisLib['rgtActive'] is True):
                    if servantGlyph not in self.servantSubscriptions:
                        servantGlyph.addObserver(self, "servantGlyphChanged",
                                                 "Glyph.WidthChanged")
                        self.servantSubscriptions.append(servantGlyph)

                    # servants
                    if thisLib['lftActive'] is True and thisLib['lft'] != '':
                        lftMaster = self.selectedFont[thisLib['lft']]
                        if lftMaster not in self.masterSubscriptions:
                            lftMaster.addObserver(self, "masterGlyphChanged",
                                                  "Glyph.WidthChanged")
                            self.masterSubscriptions.append(lftMaster)

                    if thisLib['rgtActive'] is True and thisLib['rgt'] != '':
                        rgtMaster = self.selectedFont[thisLib['rgt']]
                        if rgtMaster not in self.masterSubscriptions:
                            rgtMaster.addObserver(self, "masterGlyphChanged",
                                                  "Glyph.WidthChanged")
                            self.masterSubscriptions.append(rgtMaster)

    def servantGlyphChanged(self, notification):
        glyph = notification.object
        warningMessage = 'The glyph <{servantName}> is linked to <{masterName}>, do you want to broke the link?'

        if PLUGIN_LIB_NAME in glyph.lib:
            thisLib = glyph.lib[PLUGIN_LIB_NAME]

            if thisLib['lftActive'] is True:
                lftMaster = self.selectedFont[thisLib['lft']]
                if glyph.leftMargin != lftMaster.leftMargin:
                    result = askYesNo(
                        'Warning',
                        warningMessage.format(servantName=glyph.name,
                                              masterName=lftMaster.name))
                    if bool(result) is False:
                        glyph.leftMargin = lftMaster.leftMargin
                        thisLib['lftActive'] = True
                    else:
                        thisLib['lftActive'] = False

            if thisLib['rgtActive'] is True:
                rgtMaster = self.selectedFont[thisLib['rgt']]
                if glyph.rightMargin != rgtMaster.rightMargin:
                    result = askYesNo(
                        'Warning',
                        warningMessage.format(servantName=glyph.name,
                                              masterName=rgtMaster.name))
                    if bool(result) is False:
                        glyph.rightMargin = rgtMaster.rightMargin
                        thisLib['rgtActive'] = True
                    else:
                        thisLib['rgtActive'] = False

            links = loadLinksFromFont(self.selectedFont)
            self.w.linksList.set(links)
        self.w.canvas.update()

    def masterGlyphChanged(self, notification):
        masterGlyph = notification.object
        for eachGlyph in self.selectedFont:
            if PLUGIN_LIB_NAME in eachGlyph.lib:
                thisLib = eachGlyph.lib[PLUGIN_LIB_NAME]
                if thisLib['lft'] == masterGlyph.name and thisLib[
                        'lftActive'] is True:
                    eachGlyph.leftMargin = masterGlyph.leftMargin
                if thisLib['rgt'] == masterGlyph.name and thisLib[
                        'rgtActive'] is True:
                    eachGlyph.rightMargin = masterGlyph.rightMargin
        self.w.canvas.update()

    def displayedGlyphChanged(self, notification):
        self.w.canvas.update()

    # callbacks
    def fontDidOpenCallback(self, notification):
        self.allFonts = AllFonts()

        if self.selectedFont is not None:
            previousFontName = self.w.fontPopUp.getItems()[
                self.w.fontPopUp.get()]
        else:
            self.selectedFont = self.allFonts[0]
            previousFontName = None

        newNames = getNamesFrom(self.allFonts)
        self.w.fontPopUp.setItems(newNames)

        if previousFontName is not None:
            self.w.fontPopUp.set(newNames.index(previousFontName))

        links = loadLinksFromFont(self.selectedFont)
        self.w.linksList.set(links)
        self.w.linksList.setSelection([0])
        self.currentRow = self.w.linksList[self.w.linksList.getSelection()[0]]
        self.matchSubscriptions()
        self.matchDisplayedSubscriptions()

    def fontDidCloseCallback(self, notification):
        self.allFonts = AllFonts()

        if self.selectedFont is None and self.allFonts != []:
            self.selectedFont = self.allFonts[0]
            links = loadLinksFromFont(self.selectedFont)
            self.w.linksList.set(links)

        currentFontName = self.w.fontPopUp.getItems()[self.w.fontPopUp.get()]
        newNames = getNamesFrom(self.allFonts)
        self.w.fontPopUp.setItems(newNames)

        if self.allFonts != []:
            if currentName in newNames:
                self.w.fontPopUp.set(newNames.index(currentName))
            else:
                self.w.fontPopUp.set(
                    newNames.index(os.path.basename(self.selectedFont)))

    def fontPopUpCallback(self, sender):
        if self._compareLibsToList() is False:
            result = askYesNo(
                'Some changes were not pushed into font, would you like to do it now? Otherwise the changes will be lost'
            )
            if bool(result) is True:
                self.pushIntoFontButtonCallback(sender=None)
        self.selectedFont = self.allFonts[sender.get()]
        links = loadLinksFromFont(self.selectedFont)
        self.w.linksList.set(links)

    def selectionLinksListCallback(self, sender):
        if sender.getSelection() == []:
            sender.setSelection([0])
        self.currentRow = sender[sender.getSelection()[0]]
        self.matchDisplayedSubscriptions()
        self.w.canvas.update()

    def editLinksListCallback(self, sender):
        self.w.canvas.update()
        self.w.pushIntoFontButton.enable(not self._compareLibsToList())

    def linkAllButtonCallback(self, sender):
        for eachRow in self.w.linksList:
            if eachRow['lft'] != '':
                eachRow['lftActive'] = True
            if eachRow['rgt'] != '':
                eachRow['rgtActive'] = True

    def unlockAllButtonCallback(self, sender):
        for eachRow in self.w.linksList:
            if eachRow['lft'] is not None:
                eachRow['lftActive'] = False
            if eachRow['rgt'] is not None:
                eachRow['rgtActive'] = False

    def pushIntoFontButtonCallback(self, sender):
        for eachRow in self.w.linksList:
            eachGlyph = self.selectedFont[eachRow['servant']]
            newLib = {
                'lft': eachRow['lft'],
                'lftActive': bool(eachRow['lftActive']),
                'rgt': eachRow['rgt'],
                'rgtActive': bool(eachRow['rgtActive'])
            }

            if newLib['lft'] == '' and newLib['rgt'] == '':
                if PLUGIN_LIB_NAME in eachGlyph.lib:
                    del eachGlyph.lib[PLUGIN_LIB_NAME]
            else:
                eachGlyph.lib[PLUGIN_LIB_NAME] = newLib

        self.matchSubscriptions()
        self._alignServantsToMasters()
        self.w.pushIntoFontButton.enable(False)

    def clearLibsButtonCallback(self, sender):
        for eachGlyph in self.selectedFont:
            if PLUGIN_LIB_NAME in eachGlyph.lib:
                del eachGlyph.lib[PLUGIN_LIB_NAME]
        selectionIndex = self.w.linksList.getSelection()
        links = loadLinksFromFont(self.selectedFont)
        self.w.linksList.set(links)
        self.w.linksList.setSelection(selectionIndex)

    def loadFromTableCallback(self, sender):
        loadingPath = getFile("Select table with linked sidebearings")
        if loadingPath is None:
            return None

        with open(loadingPath[0], 'r') as linksTable:
            rawTable = [item for item in linksTable.readlines()]

        changedItems = []
        toBeLinksList = list(self.selectedFont.glyphOrder)
        for indexRow, eachRow in enumerate(rawTable):
            lft, lftActive, servant, rgtActive, rgt = [
                item.strip() for item in eachRow.split('\t')
            ]
            servantResult = self._isGlyphNameAllowed(servant)
            lftResult = self._isGlyphNameAllowed(lft)
            rgtResult = self._isGlyphNameAllowed(rgt)

            if all([servantResult, lftResult, rgtResult]) is False:
                message(
                    'Line {} contains a mistake'.format(indexRow + 1),
                    'One or more glyphs [lft:<{}> servant:<{}> rgt:<{}>] are not allowed in this font'
                    .format(lft, servant, rgt))
                return None

            if servant in toBeLinksList:
                servantIndex = toBeLinksList.index(servant)
                toBeLinksList[servantIndex] = {
                    'lft': lft,
                    'lftActive': True if lftActive == 'True' else False,
                    'servant': servant,
                    'rgt': rgt,
                    'rgtActive': True if rgtActive == 'True' else False
                }
                changedItems.append(servantIndex)

        for eachUnchangedIndex in [
                ii for ii in range(len(toBeLinksList))
                if ii not in changedItems
        ]:
            toBeLinksList[eachUnchangedIndex] = {
                'lft': '',
                'lftActive': False,
                'servant': toBeLinksList[eachUnchangedIndex],
                'rgt': '',
                'rgtActive': False
            }

        self.w.linksList.set(toBeLinksList)
        self._compareLibsToList()

    def exportTableCallback(self, sender):
        savingPath = putFile("")
        if savingPath is None:
            return None

        with open(savingPath, 'w') as linksTable:
            for eachRow in self.w.linksList:
                if eachRow['lft'] != '' or eachRow['rgt'] != '':
                    linksTable.write(
                        '{lft}\t{lftActive}\t{servant}\t{rgtActive}\t{rgt}\n'.
                        format(**eachRow))

    def windowCloseCallback(self, sender):
        self.unsubscribeGlyphs()
        self.unsubscribeDisplayedGlyphs()
        removeObserver(self, 'fontDidOpen')
        removeObserver(self, 'fontDidClose')
        removeObserver(self, "draw")
        removeObserver(self, "drawInactive")
        super(SidebearingsLinker, self).windowCloseCallback(sender)

    # private methods
    def _isGlyphNameAllowed(self, glyphName):
        if glyphName == '':
            return True
        elif glyphName not in self.selectedFont:
            return False
        else:
            return True

    def _compareLibsToList(self):
        inFont = loadLinksFromFont(self.selectedFont)
        return inFont == [item for item in self.w.linksList]

    def _alignServantsToMasters(self):
        for eachGlyph in self.selectedFont:
            if PLUGIN_LIB_NAME in eachGlyph.lib:
                thisLib = eachGlyph.lib[PLUGIN_LIB_NAME]

                if thisLib['lftActive'] is True and thisLib['lft'] != '':
                    lftMaster = self.selectedFont[thisLib['lft']]
                    eachGlyph.leftMargin = lftMaster.leftMargin

                if thisLib['rgtActive'] is True and thisLib['rgt'] != '':
                    rgtMaster = self.selectedFont[thisLib['rgt']]
                    eachGlyph.rightMargin = rgtMaster.rightMargin
Пример #26
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()
Пример #27
0
class Labeler(object):

    labelCltrIndex = 0

    def __init__(self):

        # init window
        self.w = FloatingWindow((PLUGIN_WIDTH, 300), 'labeler.py')

        self.jumpingY = MARGIN_VER
        self.w.labelCtrl_0 = LabelCtrl((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['EditTextRegularHeight']),
                                       index=self.labelCltrIndex,
                                       callback=self.labelCtrlsCallback)
        self.jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER

        self.w.separation = HorizontalLine((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness']))
        self.w.clearButton = SquareButton((MARGIN_HOR, self.jumpingY+vanillaControlsSize['HorizontalLineThickness'] + MARGIN_VER, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight']),
                                          'Clear Glyph Labels',
                                          sizeStyle='small',
                                          callback=self.clearButtonCallback)

        # resize window
        self._computeWindowHeight()
        self.w.resize(PLUGIN_WIDTH, self.windowHeight)

        # open window
        self.w.open()

    def _computeWindowHeight(self):
        self.windowHeight = self.jumpingY + vanillaControlsSize['ButtonRegularHeight'] + vanillaControlsSize['HorizontalLineThickness'] + MARGIN_VER*2

    def _updateCtrlsPos(self):
        self.w.separation.setPosSize((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness']))
        self.w.clearButton.setPosSize((MARGIN_HOR, self.jumpingY+vanillaControlsSize['HorizontalLineThickness'] + MARGIN_VER, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight']))

    def labelCtrlsCallback(self, sender):
        lastAction, labelName = sender.get()

        if lastAction == 'attach':
            attachLabelToSelectedPoints(labelName)

        elif lastAction == 'add':
            self.labelCltrIndex += 1
            labelCtrl = LabelCtrl((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['EditTextRegularHeight']),
                                  index=self.labelCltrIndex,
                                  callback=self.labelCtrlsCallback)
            setattr(self.w, 'labelCtrl_{:d}'.format(labelCtrl.index), labelCtrl)

            self.jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER
            self._computeWindowHeight()
            self._updateCtrlsPos()
            self.w.resize(PLUGIN_WIDTH, self.windowHeight)

        elif lastAction == 'subtract':
            delattr(self.w, 'labelCtrl_{:d}'.format(labelCtrl.index))
            self.jumpingY -= vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER
            self.w.resize(PLUGIN_WIDTH, self.windowHeight)

        # else: # None


    def clearButtonCallback(self, sender):
        myGlyph = CurrentGlyph()
        for eachContour in myGlyph:
            for eachPt in eachContour.points:
                if eachPt.name is not None:
                    eachPt.name = None
        myGlyph.update()
Пример #28
0
class Rotator(BaseWindowController):

    _title = 'Rotator'
    _width = 180
    _frame = 8
    _height = 249
    _row = 24
    _padding = 16
    _gutter = 8
    _lineHeight = 20
    _color = NSColor.colorWithCalibratedRed_green_blue_alpha_(
        0.0, 0.5, 1.0, .8)

    _columns = 3
    _col_width = (_width - ((_columns - 1) * _gutter)) / _columns
    _col_0 = _frame
    _col_1 = _frame + _col_width + _gutter
    _col_2 = _frame + 2 * _col_width + 2 * _gutter
    _col_3 = _frame + 3 * _col_width + 3 * _gutter

    xValue = getExtensionDefault('%s.%s' % (rotatorDefaults, 'x'), 0)
    yValue = getExtensionDefault('%s.%s' % (rotatorDefaults, 'y'), 0)
    steps = getExtensionDefault('%s.%s' % (rotatorDefaults, 'steps'), 12)
    lock = getExtensionDefault('%s.%s' % (rotatorDefaults, 'lock'), False)
    rounding = getExtensionDefault('%s.%s' % (rotatorDefaults, 'round'), False)
    angle = 360.0 / steps

    def __init__(self):

        self.w = FloatingWindow((self._width + 2 * self._frame, self._height),
                                self._title)

        # ----------
        # text boxes
        # ----------

        textBoxY = self._padding

        self.w.steps_label = TextBox(
            (self._col_0, textBoxY, self._col_width, self._lineHeight),
            'Steps',
            alignment='right')
        if rfVersion >= 3.4:
            self.w.steps_text = NumberEditText(
                (self._col_1, textBoxY - 2, self._col_width, self._lineHeight),
                self.steps,
                callback=self.angleCallback,
                allowFloat=False,
                allowNegative=False,
                allowEmpty=False,
                minimum=1,
                decimals=0,
                continuous=True)
        else:
            self.w.steps_text = EditText(
                (self._col_1, textBoxY - 2, self._col_width, self._lineHeight),
                self.steps,
                callback=self.angleCallback,
                continuous=True)

        textBoxY += (self._row)

        self.w.xValue_label = TextBox(
            (self._col_0, textBoxY, self._col_width, self._lineHeight),
            'x',
            alignment='right')

        if rfVersion >= 3.4:
            self.w.xValue_text = NumberEditText(
                (self._col_1, textBoxY - 2, self._col_width, self._lineHeight),
                self.xValue,
                callback=self.xCallback,
                allowFloat=True,
                decimals=0)
        else:
            self.w.xValue_text = EditText(
                (self._col_1, textBoxY - 2, self._col_width, self._lineHeight),
                self.xValue,
                callback=self.xCallback)

        textBoxY += (self._row)

        self.w.yValue_label = TextBox(
            (self._col_0, textBoxY, self._col_width, self._lineHeight),
            'y',
            alignment='right')

        if rfVersion >= 3.4:
            self.w.yValue_text = NumberEditText(
                (self._col_1, textBoxY - 2, self._col_width, self._lineHeight),
                self.yValue,
                callback=self.yCallback,
                allowFloat=True,
                decimals=0)
        else:
            self.w.yValue_text = EditText(
                (self._col_1, textBoxY - 2, self._col_width, self._lineHeight),
                self.yValue,
                callback=self.yCallback)
        textBoxY += (self._row)

        self.w.angle_label = TextBox(
            (self._col_0, textBoxY, self._col_width, self._lineHeight),
            'Angle',
            alignment='right')
        self.w.angleResult = TextBox(
            (self._col_1, textBoxY, self._col_width, self._lineHeight),
            u'%s°' % self.niceAngleString(self.angle))
        textBoxY += (self._row)

        textBoxY += (self._row * .25)
        self.w.line = HorizontalLine(
            (self._gutter, textBoxY, -self._gutter, 0.5))
        textBoxY += (self._row * .25)

        self.w.lock_checkbox = CheckBox(
            (self._col_1 - 25, textBoxY, -self._gutter, self._lineHeight),
            'Lock Center',
            value=self.lock,
            callback=self.lockCallback)
        textBoxY += (self._row)

        self.w.rounding_checkbox = CheckBox(
            (self._col_1 - 25, textBoxY, -self._gutter, self._lineHeight),
            'Round Result',
            value=self.rounding,
            callback=self.roundingCallback)
        textBoxY += (self._row)

        # -------
        # buttons
        # -------

        self.w.color = ColorWell(
            (self._col_0, textBoxY, -self._gutter, 2 * self._lineHeight),
            color=getExtensionDefaultColor(
                '%s.%s' % (rotatorDefaults, 'color'), self._color),
            callback=self.colorCallback)
        textBoxY += (self._row)

        self.w.buttonRotate = Button(
            (self._col_0, -30, -self._gutter, self._lineHeight),
            'Rotate',
            callback=self.rotateCallback)

        self.setUpBaseWindowBehavior()
        addObserver(self, 'updateOrigin', 'mouseDragged')
        addObserver(self, 'drawRotationPreview', 'drawBackground')
        addObserver(self, 'drawSolidPreview', 'drawPreview')
        self.w.setDefaultButton(self.w.buttonRotate)
        self.w.open()

    def drawRotationPreview(self, info):
        # draw preview glyph
        outline = self.getRotatedGlyph()
        pen = CocoaPen(None)
        self.w.color.get().set()
        outline.draw(pen)
        pen.path.setLineWidth_(info['scale'] * .5)
        pen.path.stroke()

        # draw crosshair
        ch_pen = CocoaPen(None)
        center_x = self.xValue
        center_y = self.yValue
        strokeColor = NSColor.redColor()
        strokeColor.set()
        ch_pen.moveTo((center_x - 10, center_y))
        ch_pen.lineTo((center_x + 10, center_y))
        ch_pen.endPath()
        ch_pen.moveTo((center_x, center_y - 10))
        ch_pen.lineTo((center_x, center_y + 10))
        ch_pen.endPath()
        ch_pen.path.setLineWidth_(info['scale'])
        ch_pen.path.stroke()

    def drawSolidPreview(self, info):
        outline = self.getRotatedGlyph()
        pen = CocoaPen(None)
        outline.draw(pen)
        defaultPreviewColor = getDefault('glyphViewPreviewFillColor')
        fillColor = NSColor.colorWithCalibratedRed_green_blue_alpha_(
            *defaultPreviewColor)
        fillColor.set()
        pen.path.fill()

    def xCallback(self, sender):
        xValue = sender.get()
        try:
            self.xValue = int(xValue)
        except ValueError:
            xValue = self.xValue
            self.w.xValue_text.set(xValue)

        UpdateCurrentGlyphView()

    def yCallback(self, sender):
        yValue = sender.get()
        try:
            self.yValue = int(yValue)
        except ValueError:
            yValue = self.yValue
            self.w.yValue_text.set(yValue)

        UpdateCurrentGlyphView()

    def lockCallback(self, sender):
        self.lock = not self.lock
        self.saveDefaults()

    def roundingCallback(self, sender):
        self.rounding = not self.rounding
        self.saveDefaults()

    def angleCallback(self, sender):
        try:
            stepValue = float(sender.get())
            stepValue = int(round(stepValue))
        except ValueError:
            stepValue = self.steps
            self.w.steps_text.set(self.steps)

        self.steps = stepValue

        if abs(stepValue) < 2:
            self.angle = 90.0

        elif stepValue == 0:
            self.angle = 0

        else:
            self.angle = 360 / stepValue

        self.w.angleResult.set(u'%s°' % self.niceAngleString(self.angle))
        UpdateCurrentGlyphView()

    def niceAngleString(self, angle):
        angleResultString = u'%.2f' % angle
        if angleResultString.endswith('.00'):
            angleResultString = angleResultString[0:-3]
        return angleResultString

    def colorCallback(self, sender):
        setExtensionDefaultColor('%s.%s' % (rotatorDefaults, 'color'),
                                 sender.get())
        UpdateCurrentGlyphView()

    def windowCloseCallback(self, sender):
        removeObserver(self, 'mouseUp')
        removeObserver(self, 'drawBackground')
        removeObserver(self, 'drawPreview')
        UpdateCurrentGlyphView()
        self.saveDefaults()

    def updateOrigin(self, info):
        if not self.lock:
            self.xValue, self.yValue = int(round(info['point'].x)), int(
                round(info['point'].y))
            self.w.xValue_text.set(self.xValue)
            self.w.yValue_text.set(self.yValue)

    def saveDefaults(self):
        setExtensionDefault('%s.%s' % (rotatorDefaults, 'x'), self.xValue)
        setExtensionDefault('%s.%s' % (rotatorDefaults, 'y'), self.yValue)
        setExtensionDefault('%s.%s' % (rotatorDefaults, 'steps'), self.steps)
        setExtensionDefault('%s.%s' % (rotatorDefaults, 'lock'), self.lock)
        setExtensionDefault('%s.%s' % (rotatorDefaults, 'round'),
                            self.rounding)

    def rotateCallback(self, sender):
        glyph = CurrentGlyph()
        glyph.prepareUndo('Rotator')
        rotatedGlyph = self.getRotatedGlyph()

        glyph.appendGlyph(rotatedGlyph)
        glyph.performUndo()
        self.saveDefaults()
        glyph.changed()

    def getRotatedGlyph(self):
        glyph = CurrentGlyph()
        x = int(self.w.xValue_text.get())
        y = int(self.w.yValue_text.get())

        steps = self.steps
        angle = self.angle

        center = (x, y)
        rotation_result_glyph = RGlyph()
        rotation_step_glyph = RGlyph()
        pen = rotation_step_glyph.getPointPen()

        contourList = []
        for idx, contour in enumerate(glyph):
            if contour.selected:
                contourList.append(idx)

        # if nothing is selected, the whole glyph will be rotated.
        if len(contourList) == 0:
            for idx, contour in enumerate(glyph):
                contourList.append(idx)

        for contour in contourList:
            glyph[contour].drawPoints(pen)

        # don't draw the original shape again
        stepCount = steps - 1

        if steps < 2:  # solution/hack for 1-step rotation
            stepCount = 1
            angle = 90

        for i in range(stepCount):
            rotation_step_glyph.rotateBy(angle, center)
            rotation_result_glyph.appendGlyph(rotation_step_glyph)

        if self.rounding:
            rotation_result_glyph.round()

        return rotation_result_glyph
Пример #29
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:
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")
Пример #31
0
class ItalicBowtie(Subscriber, WindowController):

    DEFAULTKEY = 'com.fontbureau.italicBowtie'
    DEFAULTKEY_REFERENCE = DEFAULTKEY + '.drawReferenceGlyph'
    italicSlantOffsetKey = 'com.typemytype.robofont.italicSlantOffset'

    debug = True

    def build(self):
        glyphEditor = self.getGlyphEditor()
        self.container = glyphEditor.extensionContainer(
            identifier="com.roboFont.ItalicBowtie.background",
            location="background",
            clear=True)

        self.crossHeightLayer = self.container.appendLineSublayer(
            strokeWidth=1, strokeColor=(.2, .1, .5, .5), strokeDash=(2, ))

        fillColor = (.2, .1, .5, .05)
        strokeColor = (.2, .1, .5, .5)
        strokeWidth = 0.5
        self.leftBowtieLayer = self.container.appendPathSublayer(
            fillColor=fillColor,
            strokeColor=strokeColor,
            strokeWidth=strokeWidth)
        self.rightBowtieLayer = self.container.appendPathSublayer(
            fillColor=fillColor,
            strokeColor=strokeColor,
            strokeWidth=strokeWidth)

        self.w = FloatingWindow((325, 250), "Italic Bowtie")
        self.populateWindow()

    def started(self):
        self.w.open()

    def destroy(self):
        self.container.clearSublayers()

    def glyphEditorGlyphDidChange(self, info):
        self.updateBowtie()

    def glyphEditorDidSetGlyph(self, info):
        self.updateBowtie()

    def populateWindow(self):
        y = 10
        x = 10
        self.w.italicAngleLabel = TextBox((x, y + 4, 100, 22),
                                          'Italic Angle',
                                          sizeStyle="small")
        x += 100
        self.w.italicAngle = EditText((x, y, 40, 22),
                                      '',
                                      sizeStyle="small",
                                      callback=self.calcItalicCallback)

        y += 30
        x = 10
        self.w.crossHeightLabel = TextBox((x, y + 4, 95, 22),
                                          'Cross Height',
                                          sizeStyle="small")
        x += 100
        self.w.crossHeight = EditText((x, y, 40, 22),
                                      '',
                                      sizeStyle="small",
                                      callback=self.calcItalicCallback)
        x += 50
        self.w.crossHeightSetUC = Button((x, y, 65, 22),
                                         'Mid UC',
                                         sizeStyle="small",
                                         callback=self.calcItalicCallback)
        x += 75
        self.w.crossHeightSetLC = Button((x, y, 65, 22),
                                         'Mid LC',
                                         sizeStyle="small",
                                         callback=self.calcItalicCallback)

        y += 30
        x = 10
        self.w.italicSlantOffsetLabel = TextBox((x, y + 4, 100, 22),
                                                'Italic Slant Offset',
                                                sizeStyle="small")
        x += 100
        self.w.italicSlantOffset = EditText((x, y, 40, 22),
                                            '',
                                            sizeStyle="small",
                                            callback=self.calcItalicCallback)
        x += 60

        y += 30
        x = 10
        self.w.refresh = Button((x, y, 140, 22),
                                u'Values from Current',
                                callback=self.refresh,
                                sizeStyle="small")

        y += 30

        self.w.fontSelection = RadioGroup((x, y, 120, 35),
                                          ['Current Font', 'All Fonts'],
                                          sizeStyle="small")
        self.w.fontSelection.set(0)

        x += 160
        self.w.glyphSelection = RadioGroup(
            (x, y, 120, 55),
            ['Current Glyph', 'Selected Glyphs', 'All Glyphs'],
            sizeStyle="small")
        self.w.glyphSelection.set(0)
        y += 60
        x = 10
        self.w.setInFont = Button((x, y, 140, 22),
                                  'Set Font Italic Values',
                                  sizeStyle="small",
                                  callback=self.setInFontCallback)
        x += 160
        self.w.italicize = Button((x, y, 140, 22),
                                  'Italicize Glyphs',
                                  sizeStyle="small",
                                  callback=self.italicizeCallback)
        y += 25
        self.w.makeReferenceLayer = CheckBox(
            (x, y, 145, 22),
            'Make Reference Layer',
            value=getExtensionDefault(self.DEFAULTKEY_REFERENCE, False),
            sizeStyle="small",
            callback=self.makeReferenceLayerCallback)
        x = 10

        self.refresh()
        if self.getItalicAngle() == 0 and CurrentFont() is not None:
            self.setCrossHeight((CurrentFont().info.capHeight or 0) / 2)

    def makeReferenceLayerCallback(self, sender):
        setExtensionDefault(self.DEFAULTKEY_REFERENCE, sender.get())

    def italicizeCallback(self, sender=None):
        italicAngle = self.getItalicAngle()
        italicSlantOffset = self.getItalicSlantOffset()
        if self.w.fontSelection.get() == 0:
            if CurrentFont() is not None:
                fonts = [CurrentFont()]
            else:
                fonts = []
        else:
            fonts = AllFonts()

        if self.w.glyphSelection.get() == 0 and CurrentGlyph() is not None:
            glyphs = [CurrentGlyph()]
        elif self.w.glyphSelection.get() == 1:
            glyphs = []
            for f in fonts:
                for gname in CurrentFont().selection:
                    if gname in f:
                        glyphs.append(f[gname])
        else:
            glyphs = []
            for f in fonts:
                for g in f:
                    glyphs.append(g.name)

        for glyph in glyphs:
            italicize(glyph,
                      italicAngle,
                      offset=italicSlantOffset,
                      shouldMakeReferenceLayer=self.w.makeReferenceLayer.get())

    def refresh(self, sender=None):
        f = CurrentFont()
        if f:
            italicSlantOffset = f.lib.get(self.italicSlantOffsetKey) or 0
            italicAngle = f.info.italicAngle or 0
            crossHeight = calcCrossHeight(italicAngle=italicAngle,
                                          italicSlantOffset=italicSlantOffset)
            self.setItalicSlantOffset(italicSlantOffset)
            self.setItalicAngle(italicAngle)
            self.setCrossHeight(crossHeight)
        else:
            self.setItalicSlantOffset(0)
            self.setItalicAngle(0)
            self.setCrossHeight(0)
        self.updateBowtie()

    def setInFontCallback(self, sender):
        if self.w.fontSelection.get() == 0:
            if CurrentFont() is not None:
                fonts = [CurrentFont()]
            else:
                fonts = []
        else:
            fonts = AllFonts()
        for f in fonts:
            with f.undo('italicBowtie'):
                f.info.italicAngle = self.getItalicAngle()
                f.lib[self.italicSlantOffsetKey] = self.getItalicSlantOffset()
        try:
            window = CurrentGlyphWindow()
            window.setGlyph(CurrentGlyph().naked())
        except Exception:
            print(self.DEFAULTKEY, 'error resetting window, please refresh it')
        self.updateBowtie()

    def drawItalicBowtie(self,
                         layer,
                         italicAngle=0,
                         crossHeight=0,
                         italicSlantOffset=0,
                         ascender=0,
                         descender=0,
                         xoffset=0):
        """
        Draw an italic Bowtie.
        """
        topBowtie = ascender
        topBowtieOffset = calcItalicOffset(topBowtie, italicAngle)
        bottomBowtie = descender
        bottomBowtieOffset = calcItalicOffset(bottomBowtie, italicAngle)

        pen = layer.getPen()
        pen.moveTo((xoffset, descender))
        pen.lineTo(
            (xoffset + bottomBowtieOffset + italicSlantOffset, descender))
        pen.lineTo((xoffset + topBowtieOffset + italicSlantOffset, ascender))
        pen.lineTo((xoffset, ascender))
        pen.closePath()

    def calcItalicCallback(self, sender):
        italicAngle = self.getItalicAngle()
        italicSlantOffset = self.getItalicSlantOffset()

        if sender == self.w.crossHeightSetUC and CurrentFont() is not None:
            crossHeight = (CurrentFont().info.capHeight or 0) / 2.0
            sender = self.w.crossHeight
        elif sender == self.w.crossHeightSetLC and CurrentFont() is not None:
            crossHeight = (CurrentFont().info.xHeight or 0) / 2.0
            sender = self.w.crossHeight
        else:
            crossHeight = self.getCrossHeight()
        if sender == self.w.italicAngle or sender == self.w.italicSlantOffset:
            self.setCrossHeight(
                calcCrossHeight(italicAngle=italicAngle,
                                italicSlantOffset=italicSlantOffset))
        elif sender == self.w.crossHeight:
            self.setItalicSlantOffset(
                calcItalicSlantOffset(italicAngle=italicAngle,
                                      crossHeight=crossHeight))
            self.setCrossHeight(crossHeight)
        self.updateBowtie()

    def updateBowtie(self):
        glyph = CurrentGlyph()
        italicAngle = self.getItalicAngle()
        italicSlantOffset = self.getItalicSlantOffset()
        crossHeight = self.getCrossHeight()
        ascender = glyph.font.info.ascender
        descender = glyph.font.info.descender
        italicSlantOffsetOffset = italicSlantOffset
        for xoffset, layer in [(0, self.leftBowtieLayer),
                               (glyph.width, self.rightBowtieLayer)]:
            self.drawItalicBowtie(layer=layer,
                                  italicAngle=italicAngle,
                                  crossHeight=crossHeight,
                                  ascender=ascender,
                                  descender=descender,
                                  italicSlantOffset=italicSlantOffsetOffset,
                                  xoffset=xoffset)
        # crossheight
        self.crossHeightLayer.setStartPoint((0, crossHeight))
        self.crossHeightLayer.setEndPoint((glyph.width, crossHeight))

    ################################
    ################################
    ################################

    def getItalicAngle(self):
        a = self.w.italicAngle.get()
        try:
            return float(a)
        except ValueError:
            return 0

    def getItalicSlantOffset(self):
        a = self.w.italicSlantOffset.get()
        try:
            return float(a)
        except ValueError:
            return 0

    def getCrossHeight(self):
        a = self.w.crossHeight.get()
        try:
            return float(a)
        except ValueError:
            print('error', a)
            return 0

    def setItalicAngle(self, italicAngle):
        self.w.italicAngle.set(f'{italicAngle}')

    def setItalicSlantOffset(self, italicSlantOffset):
        self.w.italicSlantOffset.set(f'{italicSlantOffset}')

    def setCrossHeight(self, crossHeight):
        self.w.crossHeight.set(f'{crossHeight}')
Пример #32
0
class FontNanny(object):
    def __init__(self, font):

        self.w = FloatingWindow((185, 370), "Font Nanny")
        y = 5
        self.w.info = TextBox((10, y, 180, 14),
                              text="Glyph Checks (all Glyphs):",
                              sizeStyle="small")
        y += 20
        self.w.check1 = SquareButton((10, y, 145, 20),
                                     "Unicode values",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color1 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                0, 0, 1, 0.3))
        self.w.color1.enable(0)
        y += 20
        self.w.check2 = SquareButton((10, y, 145, 20),
                                     "Contour Count",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color2 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                5, 0, 0, 0.4))
        self.w.color2.enable(0)
        y += 25
        self.w.info2 = TextBox((10, y, 180, 14),
                               text="Outline Checks (all Glyphs):",
                               sizeStyle="small")
        y += 20
        self.w.check3 = SquareButton((10, y, 145, 20),
                                     "Stray Points",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color3 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                .7, .0, .7, .9))
        self.w.color3.enable(0)
        y += 20
        self.w.check4 = SquareButton((10, y, 145, 20),
                                     "Small Contours",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color4 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                0, .8, 0, 0.9))
        self.w.color4.enable(0)
        y += 20
        self.w.check5 = SquareButton((10, y, 145, 20),
                                     "Open Contours",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color5 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                .4, .4, .4, 0.8))
        self.w.color5.enable(0)
        y += 20
        self.w.check6 = SquareButton((10, y, 145, 20),
                                     "Duplicate Contours",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color6 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                1, 0, 0, 0.6))
        self.w.color6.enable(0)
        y += 20
        self.w.check7 = SquareButton((10, y, 145, 20),
                                     "Extreme points",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color7 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                0, .9, 0, 0.6))
        self.w.color7.enable(0)
        y += 20
        self.w.check8 = SquareButton((10, y, 145, 20),
                                     "Unnecessary Points",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color8 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                1, .0, 1, 0.8))
        self.w.color8.enable(0)
        y += 20
        self.w.check9 = SquareButton((10, y, 145, 20),
                                     "Unnecessary Handles",
                                     callback=self.perform,
                                     sizeStyle="small")
        self.w.color9 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                .4, .0, .0, .3))
        self.w.color9.enable(0)
        y += 20
        self.w.check10 = SquareButton((10, y, 145, 20),
                                      "Overlapping Points",
                                      callback=self.perform,
                                      sizeStyle="small")
        self.w.color10 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                .0, .0, .6, .3))
        self.w.color10.enable(0)
        y += 20
        self.w.check11 = SquareButton((10, y, 145, 20),
                                      "Points near vert. Metrics",
                                      callback=self.perform,
                                      sizeStyle="small")
        self.w.color11 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                1, 1, .0, .2))
        self.w.color11.enable(0)
        y += 20
        self.w.check12 = SquareButton((10, y, 145, 20),
                                      "Complex Curves",
                                      callback=self.perform,
                                      sizeStyle="small")
        self.w.color12 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 1, 0, 1))
        self.w.color12.enable(0)
        y += 20
        self.w.check13 = SquareButton((10, y, 145, 20),
                                      "Crossed handles",
                                      callback=self.perform,
                                      sizeStyle="small")
        self.w.color13 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                1, 0, .4, .2))
        self.w.color13.enable(0)
        y += 20
        self.w.check14 = SquareButton((10, y, 145, 20),
                                      "Straight Lines",
                                      callback=self.perform,
                                      sizeStyle="small")
        self.w.color14 = ColorWell(
            (155, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                .0, .6, .0, .3))
        self.w.color14.enable(0)
        y += 30
        self.w.horizontalLine = HorizontalLine((10, y - 5, 165, 1))
        self.w.clearGlyphMarksButton = SquareButton(
            (10, y, 90, 20),
            "Clear all marks",
            callback=self.clearGlyphMarks,
            sizeStyle="small")
        self.w.colorClearGlyphMarks = ColorWell(
            (100, y, 20, 20),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 1, 1, 1))
        self.w.colorClearGlyphMarks.enable(0)
        self.w.closeWin = SquareButton((120, y, -10, 20),
                                       "x) close",
                                       callback=self.CloseWindow,
                                       sizeStyle="small")
        self.w.open()

    def CloseWindow(self, sender):
        self.w.close()

    def clearGlyphMarks(self, sender):
        font = CurrentFont()
        for g in font:
            if g.mark == (0, 0, 1, 0.3) or (5, 0, 0, 0.4) or (
                    .7, .0, .7, .9
            ) or (0, .8, 0, 0.9) or (.4, .4, .4, 0.8) or (1, 0, 0, 0.6) or (
                    0, .9, 0, 0.6) or (1, .0, 1, 0.8) or (.4, .0, .0, .3) or (
                        .0, .0, .6, .3) or (1, 1, .0, .2) or (1, 1, 0, 1) or (
                            1, 0, .4, .2) or (.0, .6, .0, .3):
                g.mark = None

    def perform(self, sender):
        senderInput = sender.getTitle()
        f = CurrentFont()
        tickCount = len(CurrentFont())
        progressBar = ProgressBar(title=senderInput,
                                  ticks=tickCount,
                                  label="checking all the glyphs...")
        tick = 0

        if senderInput == "Open Contours":
            for glyph in f:
                self.testForOpenContours(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Extreme points":
            for glyph in f:
                self.testForExtremePoints(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Straight Lines":
            for glyph in f:
                self.testForStraightLines(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Crossed handles":
            for glyph in f:
                self.testForCrossedHandles(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Unicode values":
            for glyph in f:
                self.testUnicodeValue(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Contour Count":
            for glyph in f:
                self.testContourCount(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Duplicate Contours":
            for glyph in f:
                self.testDuplicateContours(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Small Contours":
            for glyph in f:
                self.testForSmallContours(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Complex Curves":
            for glyph in f:
                self.testForComplexCurves(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Unnecessary Points":
            for glyph in f:
                self.testForUnnecessaryPoints(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Overlapping Points":
            for glyph in f:
                self.testForOverlappingPoints(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Unnecessary Handles":
            for glyph in f:
                self.testForUnnecessaryHandles(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Stray Points":
            for glyph in f:
                self.testForStrayPoints(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()
        if senderInput == "Points near vert. Metrics":
            for glyph in f:
                self.testForPointsNearVerticalMetrics(glyph)
                tick = tick + 1
                progressBar.tick(tick)
            progressBar.close()

    def testUnicodeValue(self, glyph):
        """
        A Unicode value should appear only once per font.
        """
        report = []
        font = glyph.getParent()
        uni = glyph.unicode
        name = glyph.name
        # test against AGL
        expectedUni = AGL2UV.get(name)
        if expectedUni != uni:
            report.append("Incorrect Unicode value?: %s." % name)
            glyph.mark = (0, 0, 1, 0.3)
        # look for duplicates
        if uni is not None:
            duplicates = []
            for name in sorted(font.keys()):
                if name == glyph.name:
                    continue
                other = font[name]
                if other.unicode == uni:
                    duplicates.append(name)
                    glyph.mark = (0, 0, 1, 0.3)
            report.append("The Unicode for this glyph is also used by: %s" %
                          " ".join(duplicates))

    # Glyph Construction

    def testContourCount(self, glyph):
        """
        There shouldn't be too many overlapping contours.
        """
        report = []
        count = len(glyph)
        test = glyph.copy()
        test.removeOverlap()
        if count - len(test) > 2:
            report.append(
                "This glyph has a unusally high number of overlapping contours."
            )
            glyph.mark = (5, 0, 0, 0.4)
        return report

    def testDuplicateContours(self, glyph):
        """
        Contours shouldn't be duplicated on each other.
        """
        contours = {}
        for index, contour in enumerate(glyph):
            contour = contour.copy()
            contour.autoStartSegment()
            pen = DigestPointPen()
            contour.drawPoints(pen)
            digest = pen.getDigest()
            if digest not in contours:
                contours[digest] = []
            contours[digest].append(index)
        duplicateContours = []
        for digest, indexes in contours.items():
            if len(indexes) > 1:
                duplicateContours.append(indexes[0])
                glyph.mark = (1, 0, 0, 0.6)

    # Contours

    def testForSmallContours(self, glyph):
        """
        Contours should not have an area less than or equal to 4 units.
        """
        smallContours = {}
        for index, contour in enumerate(glyph):
            box = contour.box
            if not box:
                continue
            xMin, yMin, xMax, yMax = box
            w = xMax - xMin
            h = yMin - yMax
            area = abs(w * h)
            if area <= 4:
                smallContours[index] = contour.box
                glyph.mark = (0, .8, 0, 0.9)

    def testForOpenContours(self, glyph):
        """
        Contours should be closed.
        """
        openContours = {}
        for index, contour in enumerate(glyph):
            if not contour.open:
                continue
            start = contour[0].onCurve
            start = (start.x, start.y)
            end = contour[-1].onCurve
            end = (end.x, end.y)
            if start != end:
                openContours[index] = (start, end)
            glyph.mark = (.4, .4, .4, 0.8)

    def testForExtremePoints(self, glyph):
        """
        Points should be at the extrema.
        """
        pointsAtExtrema = {}
        for index, contour in enumerate(glyph):
            dummy = glyph.copy()
            dummy.clear()
            dummy.appendContour(contour)
            dummy.extremePoints()
            testPoints = _getOnCurves(dummy[0])
            points = _getOnCurves(contour)
            if points != testPoints:
                pointsAtExtrema[index] = testPoints - points
                glyph.mark = (0, .9, 0, 0.6)

    def testForComplexCurves(self, glyph):
        """
        S curves are suspicious.
        """
        impliedS = {}
        for index, contour in enumerate(glyph):
            prev = _unwrapPoint(contour[-1].onCurve)
            for segment in contour:
                if segment.type == "curve":
                    pt0 = prev
                    pt1, pt2 = [_unwrapPoint(p) for p in segment.offCurve]
                    pt3 = _unwrapPoint(segment.onCurve)
                    line1 = (pt0, pt3)
                    line2 = (pt1, pt2)
                    if index not in impliedS:
                        impliedS[index] = []
                    if _intersectLines(line1, line2):
                        impliedS[index].append((prev, pt1, pt2, pt3))
                        glyph.mark = (1, 1, 0, 1)
                prev = _unwrapPoint(segment.onCurve)

    def testForCrossedHandles(self, glyph):
        """
        Handles shouldn't intersect.
        """
        crossedHandles = {}
        for index, contour in enumerate(glyph):
            pt0 = _unwrapPoint(contour[-1].onCurve)
            for segment in contour:
                pt3 = _unwrapPoint(segment.onCurve)
                if segment.type == "curve":
                    pt1, pt2 = [_unwrapPoint(p) for p in segment.offCurve]
                    # direct intersection
                    direct = _intersectLines((pt0, pt1), (pt2, pt3))
                    if direct:
                        if index not in crossedHandles:
                            crossedHandles[index] = []
                        crossedHandles[index].append(
                            dict(points=(pt0, pt1, pt2, pt3),
                                 intersection=direct))
                        glyph.mark = (1, 0, .4, .2)
                    # indirect intersection
                    else:
                        while 1:
                            # bcp1 = ray, bcp2 = segment
                            angle = _calcAngle(pt0, pt1)
                            if angle in (0, 180.0):
                                t1 = (pt0[0] + 1000, pt0[1])
                                t2 = (pt0[0] - 1000, pt0[1])
                            else:
                                yOffset = _getAngleOffset(angle, 1000)
                                t1 = (pt0[0] + 1000, pt0[1] + yOffset)
                                t2 = (pt0[0] - 1000, pt0[1] - yOffset)
                            indirect = _intersectLines((t1, t2), (pt2, pt3))
                            if indirect:
                                if index not in crossedHandles:
                                    crossedHandles[index] = []
                                crossedHandles[index].append(
                                    dict(points=(pt0, indirect, pt2, pt3),
                                         intersection=indirect))
                                break
                            # bcp1 = segment, bcp2 = ray
                            angle = _calcAngle(pt3, pt2)
                            if angle in (90.0, 270.0):
                                t1 = (pt3[0], pt3[1] + 1000)
                                t2 = (pt3[0], pt3[1] - 1000)
                            else:
                                yOffset = _getAngleOffset(angle, 1000)
                                t1 = (pt3[0] + 1000, pt3[1] + yOffset)
                                t2 = (pt3[0] - 1000, pt3[1] - yOffset)
                            indirect = _intersectLines((t1, t2), (pt0, pt1))
                            if indirect:
                                if index not in crossedHandles:
                                    crossedHandles[index] = []
                                crossedHandles[index].append(
                                    dict(points=(pt0, pt1, indirect, pt3),
                                         intersection=indirect))
                                glyph.mark = (1, 0, .4, .2)
                                break
                            break
                pt0 = pt3

    def testForUnnecessaryPoints(self, glyph):
        """
        Consecutive segments shouldn't have the same angle.
        """
        unnecessaryPoints = {}
        for index, contour in enumerate(glyph):
            for segmentIndex, segment in enumerate(contour):
                if segment.type == "line":
                    prevSegment = contour[segmentIndex - 1]
                    nextSegment = contour[(segmentIndex + 1) % len(contour)]
                    if nextSegment.type == "line":
                        thisAngle = _calcAngle(prevSegment.onCurve,
                                               segment.onCurve)
                        nextAngle = _calcAngle(segment.onCurve,
                                               nextSegment.onCurve)
                        if thisAngle == nextAngle:
                            if index not in unnecessaryPoints:
                                unnecessaryPoints[index] = []
                            unnecessaryPoints[index].append(
                                _unwrapPoint(segment.onCurve))
                            glyph.mark = (1, .0, 1, 0.8)

    def testForOverlappingPoints(self, glyph):
        """
        Consequtive points should not overlap.
        """
        overlappingPoints = {}
        for index, contour in enumerate(glyph):
            if len(contour) == 1:
                continue
            prev = _unwrapPoint(contour[-1].onCurve)
            for segment in contour:
                point = _unwrapPoint(segment.onCurve)
                if point == prev:
                    if index not in overlappingPoints:
                        overlappingPoints[index] = set()
                    overlappingPoints[index].add(point)
                    glyph.mark = (.0, .0, .6, .3)
                prev = point

    # Segments

    def testForUnnecessaryHandles(self, glyph):
        """
        Handles shouldn't be used if they aren't doing anything.
        """
        unnecessaryHandles = {}
        for index, contour in enumerate(glyph):
            prevPoint = contour[-1].onCurve
            for segment in contour:
                if segment.type == "curve":
                    pt0 = prevPoint
                    pt1, pt2 = segment.offCurve
                    pt3 = segment.onCurve
                    lineAngle = _calcAngle(pt0, pt3, 0)
                    bcpAngle1 = bcpAngle2 = None
                    if (pt0.x, pt0.y) != (pt1.x, pt1.y):
                        bcpAngle1 = _calcAngle(pt0, pt1, 0)
                    if (pt2.x, pt2.y) != (pt3.x, pt3.y):
                        bcpAngle2 = _calcAngle(pt2, pt3, 0)
                    if bcpAngle1 == lineAngle and bcpAngle2 == lineAngle:
                        if index not in unnecessaryHandles:
                            unnecessaryHandles[index] = []
                        unnecessaryHandles[index].append(
                            (_unwrapPoint(pt1), _unwrapPoint(pt2)))
                        glyph.mark = (.4, .0, .0, .3)
                prevPoint = segment.onCurve

    def testForStraightLines(self, glyph):
        """
        Lines shouldn't be just shy of vertical or horizontal.
        """
        straightLines = {}
        for index, contour in enumerate(glyph):
            prev = _unwrapPoint(contour[-1].onCurve)
            for segment in contour:
                point = _unwrapPoint(segment.onCurve)
                if segment.type == "line":
                    x = abs(prev[0] - point[0])
                    y = abs(prev[1] - point[1])
                    if x > 0 and x <= 5:
                        if index not in straightLines:
                            straightLines[index] = set()
                        straightLines[index].add((prev, point))
                        glyph.mark = (.0, .6, .0, .3)
                    if y > 0 and y <= 5:
                        if index not in straightLines:
                            straightLines[index] = set()
                        straightLines[index].add((prev, point))
                        glyph.mark = (.0, .6, .0, .3)
                prev = point

    # Points

    def testForStrayPoints(self, glyph):
        """
        There should be no stray points.
        """
        strayPoints = {}
        for index, contour in enumerate(glyph):
            if len(contour) == 1:
                pt = contour[0].onCurve
                pt = (pt.x, pt.y)
                strayPoints[index] = pt
                glyph.mark = (.7, .0, .7, .9)

    def testForPointsNearVerticalMetrics(self, glyph):
        """
        Points shouldn't be just off a vertical metric.
        """
        font = glyph.getParent()
        verticalMetrics = {0: set()}
        for attr in "descender xHeight capHeight ascender".split(" "):
            value = getattr(font.info, attr)
            verticalMetrics[value] = set()
        for contour in glyph:
            for segment in contour:
                pt = _unwrapPoint(segment.onCurve)
                y = pt[1]
                for v in verticalMetrics:
                    d = abs(v - y)
                    if d != 0 and d <= 5:
                        verticalMetrics[v].add(pt)
                        glyph.mark = (1, 1, .0, .2)
        for verticalMetric, points in verticalMetrics.items():
            if not points:
                del verticalMetrics[verticalMetric]
Пример #33
0
class VFB2UFO(BaseWindowController):

    inputOptions = ['Single File', 'Multiple files from a Folder']
    chosenMode = inputOptions[0]

    conversionOptions = ["From VFB to UFO", "From UFO to VFB"]
    suffix2conversionOption = {
        "From VFB to UFO": '.vfb',
        "From UFO to VFB": '.ufo'
    }
    chosenSuffix = suffix2conversionOption[conversionOptions[0]]

    def __init__(self):
        super(VFB2UFO, self).__init__()
        self.w = FloatingWindow((PLUGIN_WIDTH, 2), PLUGIN_TITLE)
        self.jumpingY = MARGIN_VER

        # options button
        self.w.optionsPopUp = PopUpButton(
            (MARGIN_HOR, self.jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.inputOptions,
            callback=self.optionsPopUpCallback)
        self.jumpingY += vanillaControlsSize[
            'PopUpButtonRegularHeight'] + MARGIN_HOR

        # suffix option
        self.w.suffixRadio = RadioGroup(
            (MARGIN_HOR, self.jumpingY, NET_WIDTH,
             vanillaControlsSize['ButtonRegularHeight'] * 2),
            ["From VFB to UFO", "From UFO to VFB"],
            callback=self.suffixRadioCallback)
        self.w.suffixRadio.set(0)
        self.w.suffixRadio.enable(False)
        self.jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 2 + MARGIN_HOR

        # convert button
        self.w.convertButton = Button(
            (MARGIN_HOR, self.jumpingY, NET_WIDTH,
             vanillaControlsSize['ButtonRegularHeight']),
            'Choose file and convert',
            callback=self.convertButtonCallback)
        self.jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_HOR

        self.w.resize(PLUGIN_WIDTH, self.jumpingY)
        self.setUpBaseWindowBehavior()
        self.w.open()

    def optionsPopUpCallback(self, sender):
        self.chosenMode = self.inputOptions[sender.get()]
        if self.chosenMode == 'Single File':
            self.w.suffixRadio.enable(False)
            self.w.convertButton.setTitle('Choose file and convert')
        else:
            self.w.suffixRadio.enable(True)
            self.w.convertButton.setTitle('Choose folder and convert')

    def suffixRadioCallback(self, sender):
        self.chosenSuffix = self.suffix2conversionOption[
            self.conversionOptions[sender.get()]]

    def convertButtonCallback(self, sender):
        if self.chosenMode == 'Single File':
            inputPath = getFile('Choose the file to convert')[0]
            if inputPath.endswith('.vfb') or inputPath.endswith('.ufo'):
                executeCommand(['vfb2ufo', '-fo', inputPath], shell=True)
            else:
                message('input file path is not correct')
        else:
            inputFolder = getFolder('Choose a folder with files to convert')[0]
            if inputFolder:
                for eachPath in catchFilesAndFolders(inputFolder,
                                                     self.chosenSuffix):
                    executeCommand(['vfb2ufo', '-fo', eachPath], shell=True)
            else:
                message('input folder path is not correct')
Пример #34
0
class OverlayUFOs(Subscriber, WindowController):

    debug = DEBUG_MODE

    def build(self):
        self.fonts = AllFonts()
        self.w = FloatingWindow((400, 200), "Overlay UFOs", minSize=(400, 200))
        self.populateWindow()
        self.w.open()

    def started(self):
        CurrentGlyphSubscriber.controller = self
        registerCurrentGlyphSubscriber(CurrentGlyphSubscriber)

        GlyphEditorSubscriber.controller = self
        registerGlyphEditorSubscriber(GlyphEditorSubscriber)

        FontListManager.controller = self
        registerRoboFontSubscriber(FontListManager)

    def destroy(self):
        CurrentGlyphSubscriber.controller = None
        unregisterCurrentGlyphSubscriber(CurrentGlyphSubscriber)

        GlyphEditorSubscriber.controller = None
        unregisterGlyphEditorSubscriber(GlyphEditorSubscriber)

        FontListManager.controller = None
        unregisterRoboFontSubscriber(FontListManager)

        if DEBUG_MODE:
            for eachFont in AllFonts():
                eachFont.close()

    def _getFontLabel(self, path):
        if path is None:
            return None
        if not path:
            return "Untitled"
        name = path.split("/")[-1]
        status = SELECTED_SYMBOL
        return status, path, name

    def _getFontLabels(self):
        labels = {}
        fontPaths = [
            f.path or f"{f.info.familyName} {f.info.styleName}"
            for f in self.fonts
        ]
        for path in fontPaths:
            if path:
                label = self._getFontLabel(path)
                name = label[-1]
            else:
                name = "Untitled"
            if name not in labels:
                labels[name] = []
            labels[name].append(label)
        sortedLabels = []
        for _, labelSet in sorted(labels.items()):
            # There is only a single font with this name
            if len(labelSet) == 1:
                sortedLabels.append(labelSet[0])
            # Otherwise we'll have to construct new names to show the difference
            else:
                for status, path, name in sorted(labelSet):
                    sortedLabels.append(
                        (status, path, f'{name} "{Path(path).parent}"'))
        return sortedLabels

    def openedFontsDidChange(self, info):
        self.w.fontList.set(self._getFontItems())

    def resetCallback(self, sender=None):
        """
        Resets the view to the currently opened fonts.

        """
        self.fonts = AllFonts()
        self.w.fontList.set(self._getFontItems())

    def addCallback(self, sender=None):
        """
        Open a font without UI and add it to the font list.

        """
        f = OpenFont(None, showInterface=False)
        if f is None:
            return
        self.fonts.append(f)
        self.w.fontList.set(self._getFontItems())

    def populateWindow(self):
        """
        The UI

        """
        self.fillColor = getExtensionDefault(DEFAULTKEY_FILLCOLOR,
                                             FALLBACK_FILLCOLOR)
        self.strokeColor = getExtensionDefault(DEFAULTKEY_STROKECOLOR,
                                               FALLBACK_STROKECOLOR)
        self.contextBefore = self.contextAfter = ""

        # Populating the view can only happen after the view is attached to the window,
        # or else the relative widths go wrong.
        self.w.add = Button((-40, 3, 30, 22), "+", callback=self.addCallback)
        self.w.reset = Button((-40, 30, 30, 22),
                              chr(8634),
                              callback=self.resetCallback)

        # Flag to see if the selection list click is in progress. We are resetting the selection
        # ourselves, using the list "buttons", but changing that selection will cause another
        # list update, that should be ignored.
        self._selectionChanging = False

        x = y = 4

        self.w.fontList = List(
            (C2, y, 250, -65),
            self._getFontItems(),
            selectionCallback=self.fontListCallback,
            drawFocusRing=False,
            enableDelete=False,
            allowsMultipleSelection=False,
            allowsEmptySelection=True,
            drawHorizontalLines=True,
            showColumnTitles=False,
            columnDescriptions=self._getPathListDescriptor(),
            rowHeight=16,
        )

        self.w.fill = CheckBox(
            (x, y, 60, 22),
            "Fill",
            sizeStyle=CONTROLS_SIZE_STYLE,
            value=True,
            callback=self.fillCallback,
        )
        y += L - 3
        self.w.stroke = CheckBox(
            (x, y, 60, 22),
            "Stroke",
            sizeStyle=CONTROLS_SIZE_STYLE,
            value=False,
            callback=self.strokeCallback,
        )
        y += L
        defaultColor = getExtensionDefault(DEFAULTKEY_FILLCOLOR,
                                           FALLBACK_FILLCOLOR)
        self.w.color = ColorWell(
            (x, y, 60, 22),
            color=NSColor.colorWithCalibratedRed_green_blue_alpha_(
                *defaultColor),
            callback=self.colorCallback,
        )
        y += LL + 5
        self.w.alignText = TextBox((x, y, 90, 50),
                                   "Alignment",
                                   sizeStyle=CONTROLS_SIZE_STYLE)
        y += L
        self.w.align = RadioGroup(
            (x, y, 90, 50),
            ["Left", "Center", "Right"],
            isVertical=True,
            sizeStyle=CONTROLS_SIZE_STYLE,
            callback=self.alignCallback,
        )
        self.w.align.set(0)

        self.w.viewCurrent = CheckBox(
            (C2, -60, 150, 22),
            "Always View Current",
            sizeStyle=CONTROLS_SIZE_STYLE,
            value=False,
            callback=self.viewCurrentCallback,
        )

        self.w.contextBefore = GlyphNamesEditText(
            (C2, -30, 85, 20),
            callback=self.contextEditCallback,
            continuous=True,
            sizeStyle="small",
            placeholder="Left Context",
        )
        self.w.contextBefore.title = "contextBefore"

        self.w.contextCurrent = GlyphNamesEditText(
            (C2 + 95, -30, 60, 20),
            callback=self.contextEditCallback,
            continuous=True,
            sizeStyle="small",
        )
        self.w.contextCurrent.title = "contextCurrent"

        self.w.contextAfter = GlyphNamesEditText(
            (C2 + 165, -30, 85, 20),
            callback=self.contextEditCallback,
            continuous=True,
            sizeStyle="small",
            placeholder="Right Context",
        )
        self.w.contextAfter.title = "contextAfter"

    def viewCurrentCallback(self, sender):
        postEvent(f"{DEFAULTKEY}.alwaysCurrentViewDidChange")

    def fontListCallback(self, sender):
        """
        If there is a selection, toggle the status of these fonts

        """
        # Avoid recursive loop because of changing font selection
        if not self._selectionChanging:
            for selectedIndex in sender.getSelection():
                item = sender.get()[selectedIndex]
                if item["status"]:
                    item["status"] = ""
                else:
                    item["status"] = SELECTED_SYMBOL
            self._selectionChanging = True
            # Avoid recursive loop because of changing font selection
            sender.setSelection([])
            self._selectionChanging = False
        postEvent(f"{DEFAULTKEY}.displayedFontsDidChange")

    def _getFontItems(self, update=False):
        """
        Get all fonts in a way that can be set into a vanilla list

        """
        itemsByName = {}
        if update:  # if update flag is set, then keep the existing selected fonts
            for item in self.w.fontList.get():
                if item["status"]:
                    itemsByName[item["name"]] = item
        currentStatuses = {}
        if hasattr(self.w, "fontList"):
            for d in self.w.fontList.get():
                currentStatuses[d["path"]] = d["status"]

        for status, path, uniqueName in self._getFontLabels():
            if path in currentStatuses:
                status = currentStatuses[path]
            else:
                status = SELECTED_SYMBOL

            if uniqueName not in itemsByName.keys(
            ):  # If it is not already there, add this to the list
                itemsByName[uniqueName] = dict(status=status,
                                               path=path,
                                               name=uniqueName)
        fontList = []
        for _, item in sorted(itemsByName.items()):
            fontList.append(item)
        return fontList

    def _getPathListDescriptor(self):
        return [
            dict(title="Status",
                 key="status",
                 cell=SmallTextListCell(editable=False),
                 width=12,
                 editable=False),
            dict(title="Name",
                 key="name",
                 width=300,
                 cell=SmallTextListCell(editable=False),
                 editable=False),
            dict(title="Path", key="path", width=0, editable=False),
        ]

    def _setSourceFonts(self):
        """
        Set the font list from the current set of open fonts

        """
        labels = []
        currentSelection = []
        for d in self.w.fontList.get():
            if d["status"]:
                currentSelection.append(d["path"])
        for status, path, name in self.fontListManager.getFontLabels():
            if path in currentSelection:
                status = SELECTED_SYMBOL
            else:
                status = ""
            labels.append(dict(status=status, path=path, name=name))
        self.w.fontList.set(labels)

    def colorCallback(self, sender):
        """
        Change the color

        """
        r, g, b, a = NSColorToRgba(sender.get())
        self.fillColor = r, g, b, a
        self.strokeColor = r, g, b, 1
        setExtensionDefault(DEFAULTKEY_FILLCOLOR, (r, g, b, a))
        setExtensionDefault(DEFAULTKEY_STROKECOLOR, self.strokeColor)
        postEvent(f"{DEFAULTKEY}.colorDidChange")

    def fillCallback(self, sender):
        """
        Change the fill status

        """
        setExtensionDefault(DEFAULTKEY_FILL, sender.get())
        postEvent(f"{DEFAULTKEY}.fillCheckBoxDidChange")

    def strokeCallback(self, sender):
        """
        Change the stroke status

        """
        setExtensionDefault(DEFAULTKEY_STROKE, sender.get())
        postEvent(f"{DEFAULTKEY}.strokeCheckBoxDidChange")

    def alignCallback(self, sender):
        """
        Change the alignment status

        """
        postEvent(f"{DEFAULTKEY}.alignmentDidChange")

    def contextEditCallback(self, sender):
        postEvent(f"{DEFAULTKEY}.contextDidChange", position=sender.title)
Пример #35
0
class UnicodePicker(AppKit.NSWindowController):

    def __new__(cls):
        return cls.alloc().init()

    def __init__(self):
        self.searchResults = []
        self.selectedChars = ""

        self.w = FloatingWindow((300, 400), "Unicode Picker", minSize=(250, 300),
                                autosaveName="UnicodePicker")
        y = 15
        self.w.searchField = EditText((20, y, -20, 25),
                                      placeholder="Search Unicode name or value",
                                      callback=self.searchTextChanged_)

        y += 40
        columnDescriptions = [
            dict(title="char", width=40,
                 cell=makeTextCell(align="center", font=AppKit.NSFont.systemFontOfSize_(14))),
            dict(title="unicode", width=60, cell=makeTextCell(align="right")),
            dict(title="name"),
        ]
        self.w.unicodeList = List((0, y, 0, -100), [], columnDescriptions=columnDescriptions,
                                  rowHeight=18,
                                  selectionCallback=self.listSelectionChanged_,
                                  doubleClickCallback=self.listDoubleClickCallback_)

        y = -95
        self.w.unicodeText = TextBox((20, y, -10, 55), "")
        self.w.unicodeText._nsObject.cell().setFont_(AppKit.NSFont.systemFontOfSize_(36))
        self.w.unicodeText._nsObject.cell().setLineBreakMode_(AppKit.NSLineBreakByTruncatingMiddle)
        y += 55
        self.w.copyButton = Button((20, y, 120, 25), "Copy", callback=self.copy_)
        self.w.copyButton.enable(False)

        self.w.open()
        self.w._window.setWindowController_(self)
        self.w._window.setBecomesKeyOnlyIfNeeded_(False)
        self.w._window.makeKeyWindow()

    def show(self):
        if self.w._window is None:
            # we have been closed, let's reconstruct
            self.__init__()
        else:
            self.w.show()

    def searchTextChanged_(self, sender):
        results = []
        terms = sender.get().upper().split()
        if len(terms) == 1:
            m = _unicodePat.match(terms[0])
            if m is not None:
                uni = int(m.group(4), 16)
                if uni < 0x110000:
                    results = [uni]
        if terms:
            uniSets = [set(findPrefix(t)) for t in terms]
            foundSet = uniSets[0]
            for s in uniSets[1:]:
                foundSet &= s
            results += sorted(foundSet)

        self.searchResults = results
        self.w.unicodeList.set([])
        self.appendResults_(500)

    def appendResults_(self, maxResults):
        start = len(self.w.unicodeList)
        unicodeItems = [dict(char=chr(uni),
                             unicode=f"U+{uni:04X}",
                             name=unicodedata.name(chr(uni), ""))
                        for uni in self.searchResults[start:start+maxResults]]
        if len(self.searchResults) > start + maxResults:
            unicodeItems.append(dict(name="...more..."))
        self.w.unicodeList.extend(unicodeItems)

    def listSelectionChanged_(self, sender):
        sel = sender.getSelection()
        if sel and sender[max(sel)]["name"] == "...more...":
            del sender[len(sender) - 1]
            self.appendResults_(500)
            sender.setSelection(sel)
        chars = "".join(chr(self.searchResults[i]) for i in sel)
        self.w.copyButton.enable(bool(chars))
        self.selectedChars = chars
        self.w.unicodeText.set(chars)

    def listDoubleClickCallback_(self, sender):
        app = AppKit.NSApp()
        w = app.mainWindow()
        fr = w.firstResponder()
        if fr is None or not isinstance(fr, AppKit.NSTextView):
            return
        fr.insertText_replacementRange_(self.selectedChars, fr.selectedRange())

    def copy_(self, sender):
        p = AppKit.NSPasteboard.generalPasteboard()
        p.clearContents()
        p.declareTypes_owner_([AppKit.NSPasteboardTypeString], None)
        p.setString_forType_(self.selectedChars, AppKit.NSPasteboardTypeString)
Пример #36
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)
Пример #37
0
class GroupSpacingWindow(BaseWindowController):
    '''
    A tool to enable group spacing in the Space Center.

    - works with the selected glyph in the Space Center
    - shows a preview of all other glyphs in the same spacing group
    - transfer margins from current glyph to all glyphs in the same spacing group
    - supports measurements using the current beam

    '''
    def __init__(self):
        padding = 10
        lineHeight = 20
        buttonHeight = 20
        width = 123
        height = lineHeight * 6 + buttonHeight * 4 + padding * 9

        self.w = FloatingWindow((width, height), title='spacing')

        x = y = padding
        self.w.makeGroupButton = Button((x, y, -padding, buttonHeight),
                                        'make group',
                                        callback=self.makeGroupCallback,
                                        sizeStyle='small')

        y += buttonHeight + padding
        self.w.side = RadioGroup((x, y, -padding, lineHeight),
                                 ['left', 'right'],
                                 isVertical=False,
                                 callback=self.updateViewsCallback,
                                 sizeStyle='small')
        self.w.side.set(0)

        y += lineHeight + padding
        self.w.copySpacingButton = Button((x, y, -padding, buttonHeight),
                                          'copy margin',
                                          callback=self.copySpacingCallback,
                                          sizeStyle='small')

        y += buttonHeight + padding
        self.w.useBeam = CheckBox((x, y, -padding, lineHeight),
                                  'use beam',
                                  callback=self.useBeamCallback,
                                  sizeStyle='small')

        y += lineHeight
        self.w.allLayers = CheckBox((x, y, -padding, lineHeight),
                                    'all layers',
                                    callback=self.updateViewsCallback,
                                    sizeStyle='small')

        y += lineHeight + padding
        self.w.opacityLabel = TextBox((x, y, -padding, lineHeight),
                                      'opacity:',
                                      sizeStyle='small')

        y += lineHeight
        self.w.opacity = Slider((x, y, -padding, lineHeight),
                                value=0.4,
                                minValue=0.0,
                                maxValue=0.9,
                                callback=self.updateViewsCallback,
                                sizeStyle='small')

        y += lineHeight + padding
        self.w.exportButton = Button((x, y, -padding, buttonHeight),
                                     'export…',
                                     callback=self.exportCallback,
                                     sizeStyle='small')

        y += buttonHeight + padding
        self.w.importButton = Button((x, y, -padding, buttonHeight),
                                     'import…',
                                     callback=self.importCallback,
                                     sizeStyle='small')

        y += buttonHeight + padding
        self.w.verbose = CheckBox((x, y, -padding, lineHeight),
                                  'verbose',
                                  value=True,
                                  sizeStyle='small')

        self.setUpBaseWindowBehavior()

        addObserver(self, "drawGlyphsInGroup", "spaceCenterDraw")

        self.w.getNSWindow().setTitlebarAppearsTransparent_(True)
        self.w.open()

    # dynamic attrs.

    @property
    def side(self):
        '''The selected spacing group side.'''
        return ['left', 'right'][int(self.w.side.get())]

    @property
    def useBeam(self):
        '''Use or not the beam to measure margins. Value taken from the checkbox.'''
        return self.w.useBeam.get()

    @property
    def allLayers(self):
        '''Set margins in all layers. Value taken from the checkbox.'''
        return self.w.allLayers.get()

    @property
    def beam(self):
        '''The beam’s y position in the Space Center.'''
        sp = CurrentSpaceCenter()
        if not sp:
            return
        return sp.beam()

    @property
    def opacity(self):
        '''The opacity of the fill color of glyphs in group.'''
        return self.w.opacity.get()

    @property
    def verbose(self):
        '''Output action info to the console.'''
        return self.w.verbose.get()

    # ---------
    # callbacks
    # ---------

    def makeGroupCallback(self, sender):
        '''Make a new spacing group with the selected glyph.'''

        glyph = CurrentGlyph()
        if not glyph:
            return

        if glyph.font is None:
            return

        prefix = PREFIX_LEFTSIDE if self.side == 'left' else PREFIX_RIGHTSIDE
        groupName = prefix + glyph.name
        if not groupName in glyph.font.groups:
            glyph.font.groups[groupName] = [glyph.name]

    def copySpacingCallback(self, sender):
        '''Copy margin from current glyph to other glyphs in left/right spacing class.'''

        glyph = CurrentGlyph()

        if not glyph:
            return

        if glyph.bounds is None:
            return

        if glyph.font is None:
            return

        siblings = getSiblings(glyph, self.side)

        if not siblings:
            return

        beam = self.beam if self.useBeam else None
        copyMargins(glyph,
                    siblings,
                    self.side,
                    beam=beam,
                    allLayers=self.allLayers,
                    verbose=self.verbose)

    def useBeamCallback(self, sender):
        '''Show/hide the beam according to checkbox selection.'''
        S = CurrentSpaceCenter()
        if not S:
            return
        value = sender.get()
        options = S.glyphLineView.getDisplayStates()
        options['Beam'] = value
        S.glyphLineView.setDisplayStates(options)

    def exportCallback(self, sender):
        '''Export spacing groups to .json file.'''
        font = CurrentFont()
        filePath = PutFile(message='export spacing groups',
                           fileName='spacingGroups.json')
        exportSpacingGroups(font, filePath)

    def importCallback(self, sender):
        '''Import spacing groups from .json file.'''
        font = CurrentFont()
        filePath = GetFile(message='import spacing groups', fileTypes=['json'])
        importSpacingGroups(font, filePath)

    def windowCloseCallback(self, sender):
        '''Remove observers when closing window.'''
        super().windowCloseCallback(sender)
        removeObserver(self, "spaceCenterDraw")

    def updateViewsCallback(self, sender):
        '''Update the Space Center.'''
        S = CurrentSpaceCenter()
        if not S:
            return
        S.glyphLineView.refresh()

    # ---------
    # observers
    # ---------

    def drawGlyphsInGroup(self, notification):
        '''Display all glyphs belonging to the same spacing group in the background.'''

        glyph = notification['glyph']

        font = glyph.font
        if font is None:
            return

        siblings = getSiblings(glyph, self.side)
        if not siblings:
            return

        if not notification['selected']:
            return

        S = CurrentSpaceCenter()
        if not S:
            return

        inverse = S.glyphLineView.getDisplayStates()['Inverse']

        # hide solid color glyph
        R, G, B, A = getDefault(
            "spaceCenterBackgroundColor") if not inverse else getDefault(
                "spaceCenterGlyphColor")
        bounds = glyph.bounds
        if bounds:
            save()
            fill(R, G, B, A)
            stroke(R, G, B, A)
            drawGlyph(glyph)
            restore()

        # draw side indicator
        save()
        stroke(1, 0, 0)
        strokeWidth(10)
        xPos = 0 if self.side == 'left' else glyph.width
        yMin = font.info.descender
        yMax = yMin + font.info.unitsPerEm
        line((xPos, yMin), (xPos, yMax))
        restore()

        # draw glyph and siblings
        R, G, B, A = getDefault(
            "spaceCenterGlyphColor") if not inverse else getDefault(
                "spaceCenterBackgroundColor")
        alpha = (1.0 / len(siblings) + self.opacity) / 2
        stroke(None)
        for glyphName in siblings:
            if glyphName not in glyph.layer:
                continue
            g = font[glyphName].getLayer(glyph.layer.name)
            save()
            if self.side == 'right':
                dx = glyph.width - g.width
                translate(dx, 0)
            color = (R, G, B, 0.4) if glyphName == glyph.name else (R, G, B,
                                                                    alpha)
            fill(*color)
            drawGlyph(g)
            restore()
Пример #38
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')
Пример #39
0
class DrawingAssistant(BaseWindowController):
    """the aim of this plugin is to provide useful visualizations on glyph canvas"""

    # switches
    sqrActive = False
    bcpLengthActive = False
    gridActive = False
    offgridActive = False
    stemActive = False
    diagonalActive = False

    openedFonts = []

    lftNeighborActive = False
    lftFontPath = None
    lftGlyphName = None
    cntNeighborActive = False
    cntFontPath = None
    cntGlyphName = None
    rgtNeighborActive = False
    rgtFontPath = None
    rgtGlyphName = None

    gridsDB = []

    def __init__(self):
        super(DrawingAssistant, self).__init__()

        # collect currentGlyph (if available)
        self.currentGlyph = CurrentGlyph()

        # collect opened fonts
        if AllFonts():
            self.collectOpenedFontPaths()

        # init fonts
        if self.openedFontPaths:
            self._initFontsAndGlyphs()

        # init views
        self.bcpController = BcpController(
            (MARGIN_HOR, 0, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight'] * 2.5),
            sqrActive=self.sqrActive,
            bcpLengthActive=self.bcpLengthActive,
            callback=self.bcpControllerCallback)

        self.gridController = MultipleGridController(
            (MARGIN_HOR, 0, NET_WIDTH, 220),
            gridActive=self.gridActive,
            ctrlsAmount=5,
            activeCtrls=0,
            offgridActive=self.offgridActive,
            callback=self.gridControllerCallback)

        self.distancesController = DistancesController(
            (MARGIN_HOR, 0, NET_WIDTH, 160),
            stemActive=self.stemActive,
            diagonalActive=self.diagonalActive,
            currentGlyph=self.currentGlyph,
            callback=self.distancesControllerCallback)

        self.neighborsController = NeighborsController(
            (MARGIN_HOR, 0, NET_WIDTH, 94),
            openedFontPaths=self.openedFontPaths,
            lftNeighborActive=self.lftNeighborActive,
            lftFontPath=self.lftFontPath,
            lftGlyphName=self.lftGlyphName,
            cntNeighborActive=self.cntNeighborActive,
            cntFontPath=self.cntFontPath,
            cntGlyphName=self.cntGlyphName,
            rgtNeighborActive=self.rgtNeighborActive,
            rgtFontPath=self.rgtFontPath,
            rgtGlyphName=self.rgtGlyphName,
            callback=self.neighborsControllerCallback)

        # collect views
        accordionItems = [
            {
                'label': 'bcp controller',
                'view': self.bcpController,
                'size': self.bcpController.ctrlHeight,
                'collapsed': False,
                'canResize': False
            },
            {
                'label': 'grid controller',
                'view': self.gridController,
                'size': self.gridController.ctrlHeight,
                'collapsed': False,
                'canResize': False
            },
            {
                'label': 'distances controller',
                'view': self.distancesController,
                'size': self.distancesController.ctrlHeight,
                'collapsed': False,
                'canResize': False
            },
            {
                'label': 'neighbors controller',
                'view': self.neighborsController,
                'size': self.neighborsController.ctrlHeight,
                'collapsed': False,
                'canResize': False
            },
        ]

        # init window with accordion obj
        totalHeight = self.bcpController.ctrlHeight + self.gridController.ctrlHeight + self.distancesController.ctrlHeight + self.neighborsController.ctrlHeight + 18 * 4
        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, totalHeight),
                                PLUGIN_TITLE,
                                minSize=(PLUGIN_WIDTH, 200),
                                maxSize=(PLUGIN_WIDTH, totalHeight + 10))
        self.w.accordionView = AccordionView((0, 0, -0, -0), accordionItems)

        # add observers
        addObserver(self, "_draw", "draw")
        addObserver(self, "_draw", "drawInactive")
        addObserver(self, "_drawPreview", "drawPreview")
        addObserver(self, "_mouseDown", "mouseDown")
        addObserver(self, "_drawBackground", "drawBackground")
        addObserver(self, "_updateCurrentFont", "fontBecameCurrent")
        addObserver(self, '_updateCurrentGlyph', 'viewDidChangeGlyph')
        addObserver(self, "aFontIsOpening", "newFontDidOpen")
        addObserver(self, "aFontIsOpening", "fontDidOpen")
        addObserver(self, "aFontIsClosing", "fontWillClose")
        self.setUpBaseWindowBehavior()

        # open window
        self.w.open()

    # drawing callbacks
    def _mouseDown(self, infoDict):
        mouseDownPoint = infoDict['point']
        mouseDownClickCount = infoDict['clickCount']

        # double click
        if mouseDownClickCount == 2:
            if self.lftNeighborActive is True and self.lftGlyphName:
                lftGlyph = chooseRightGlyph(self.lftFontPath,
                                            self.lftGlyphName)
                if version[0] == '2':
                    xMin, yMin, xMax, yMax = lftGlyph.bounds
                else:
                    xMin, yMin, xMax, yMax = lftGlyph.box
                if xMin < (mouseDownPoint.x + lftGlyph.width
                           ) < xMax and yMin < mouseDownPoint.y < yMax:
                    OpenGlyphWindow(glyph=lftGlyph, newWindow=True)

            if self.rgtNeighborActive is True and self.rgtGlyphName:
                rgtGlyph = chooseRightGlyph(self.rgtFontPath,
                                            self.rgtGlyphName)
                if version[0] == '2':
                    xMin, yMin, xMax, yMax = rgtGlyph.bounds
                else:
                    xMin, yMin, xMax, yMax = rgtGlyph.box
                if xMin < (mouseDownPoint.x - self.currentGlyph.width
                           ) < xMax and yMin < mouseDownPoint.y < yMax:
                    OpenGlyphWindow(glyph=rgtGlyph, newWindow=True)

    def _draw(self, infoDict):
        glyphOnCanvas = infoDict['glyph']
        scalingFactor = infoDict['scale']

        if self.lftGlyphName and self.lftNeighborActive is True:
            lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName)
        if self.rgtGlyphName and self.rgtNeighborActive is True:
            rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName)

        try:
            if self.lftGlyphName and self.lftNeighborActive is True:
                self._drawGlyphOutline(lftGlyph,
                                       scalingFactor,
                                       offset_X=-lftGlyph.width)
            if self.rgtGlyphName and self.rgtNeighborActive is True:
                self._drawGlyphOutline(rgtGlyph,
                                       scalingFactor,
                                       offset_X=glyphOnCanvas.width)

            if self.sqrActive is True and 2 > scalingFactor:
                self._drawSquarings(glyphOnCanvas, scalingFactor)
                if self.lftGlyphName and self.lftNeighborActive is True:
                    self._drawSquarings(lftGlyph,
                                        scalingFactor,
                                        offset_X=-lftGlyph.width)
                if self.rgtGlyphName and self.rgtNeighborActive is True:
                    self._drawSquarings(rgtGlyph,
                                        scalingFactor,
                                        offset_X=glyphOnCanvas.width)

            if self.bcpLengthActive is True and 2 > scalingFactor:
                self._drawBcpLenght(glyphOnCanvas, scalingFactor, 2)
                if self.lftGlyphName and self.lftNeighborActive is True:
                    self._drawBcpLenght(lftGlyph,
                                        scalingFactor,
                                        offset_X=-lftGlyph.width)
                if self.rgtGlyphName and self.rgtNeighborActive is True:
                    self._drawBcpLenght(rgtGlyph,
                                        scalingFactor,
                                        offset_X=glyphOnCanvas.width)

        except Exception as error:
            print traceback.format_exc()

    def _drawPreview(self, infoDict):
        glyphOnCanvas = infoDict['glyph']
        scalingFactor = infoDict['scale']

        if self.lftGlyphName and self.lftNeighborActive is True:
            lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName)
        if self.rgtGlyphName and self.rgtNeighborActive is True:
            rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName)

        try:
            if self.lftGlyphName and self.lftNeighborActive is True:
                self._drawGlyphBlack(lftGlyph,
                                     scalingFactor,
                                     offset_X=-lftGlyph.width)
            if self.rgtGlyphName and self.rgtNeighborActive is True:
                self._drawGlyphBlack(rgtGlyph,
                                     scalingFactor,
                                     offset_X=glyphOnCanvas.width)
        except Exception as error:
            print traceback.format_exc()

    def _drawBackground(self, infoDict):
        glyphOnCanvas = infoDict['glyph']
        scalingFactor = infoDict['scale']

        if self.lftGlyphName and self.lftNeighborActive is True:
            lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName)
        if self.rgtGlyphName and self.rgtNeighborActive is True:
            rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName)

        try:
            if self.gridActive is True and 2 > scalingFactor:
                offsetFromOrigin = infoDict['view'].offset()
                visibleRect = infoDict['view'].visibleRect()
                frameOrigin = int(visibleRect.origin.x -
                                  offsetFromOrigin[0]), int(
                                      visibleRect.origin.y -
                                      offsetFromOrigin[1])
                frameSize = int(visibleRect.size.width), int(
                    visibleRect.size.height)
                self._drawGrids(frameOrigin, frameSize,
                                glyphOnCanvas.getParent().info.italicAngle,
                                scalingFactor)

            if self.diagonalActive is True and 1 > scalingFactor:
                self._drawDiagonals(glyphOnCanvas, scalingFactor)
                if self.lftGlyphName and self.lftNeighborActive is True:
                    self._drawDiagonals(lftGlyph,
                                        scalingFactor,
                                        offset_X=-lftGlyph.width)
                if self.rgtGlyphName and self.rgtNeighborActive is True:
                    self._drawDiagonals(rgtGlyph,
                                        scalingFactor,
                                        offset_X=glyphOnCanvas.width)

            if self.cntNeighborActive is True:
                cntGlyph = chooseRightGlyph(self.cntFontPath,
                                            self.cntGlyphName)
                self._drawCentralBackgroundGlyph(cntGlyph)

            if self.stemActive is True and 1.5 > scalingFactor:
                self._drawStems(glyphOnCanvas, scalingFactor)
                if self.lftGlyphName and self.lftNeighborActive is True:
                    self._drawStems(lftGlyph,
                                    scalingFactor,
                                    offset_X=-lftGlyph.width)
                if self.rgtGlyphName and self.rgtNeighborActive is True:
                    self._drawStems(rgtGlyph,
                                    scalingFactor,
                                    offset_X=glyphOnCanvas.width)

            if self.offgridActive is True:
                self._drawOffgridPoints(glyphOnCanvas, scalingFactor)

        except Exception as error:
            print traceback.format_exc()

    def _drawOffgridPoints(self, glyph, scalingFactor):
        dt.save()
        dt.fill(*RED_COLOR)
        dt.stroke(None)

        scaledRadius = OFFGRID_RADIUS * scalingFactor
        for eachContour in glyph:
            for eachBPT in eachContour.bPoints:
                if eachBPT.anchor[0] % 4 != 0 or eachBPT.anchor[1] % 4 != 0:
                    dt.oval(eachBPT.anchor[0] - scaledRadius / 2.,
                            eachBPT.anchor[1] - scaledRadius / 2.,
                            scaledRadius, scaledRadius)

                if eachBPT.bcpIn != (0, 0):
                    bcpInAbs = eachBPT.anchor[0] + eachBPT.bcpIn[
                        0], eachBPT.anchor[1] + eachBPT.bcpIn[1]
                    if bcpInAbs[0] % 4 != 0 or bcpInAbs[1] % 4 != 0:
                        dt.oval(bcpInAbs[0] - scaledRadius / 2.,
                                bcpInAbs[1] - scaledRadius / 2., scaledRadius,
                                scaledRadius)

                if eachBPT.bcpOut != (0, 0):
                    bcpOutAbs = eachBPT.anchor[0] + eachBPT.bcpOut[
                        0], eachBPT.anchor[1] + eachBPT.bcpOut[1]
                    if bcpOutAbs[0] % 4 != 0 or bcpOutAbs[1] % 4 != 0:
                        dt.oval(bcpOutAbs[0] - scaledRadius / 2.,
                                bcpOutAbs[1] - scaledRadius / 2., scaledRadius,
                                scaledRadius)
        dt.restore()

    def _drawCentralBackgroundGlyph(self, glyph):
        dt.save()
        dt.fill(*LIGHT_GRAY_COLOR)
        dt.drawGlyph(glyph)
        dt.restore()

    def _drawGrids(self, frameOrigin, frameSize, italicAngle, scalingFactor):
        if not italicAngle:
            italicAngle = 0

        for eachGridDescription in reversed(self.gridsDB):
            gridColor = eachGridDescription['color']
            isHorizontal = eachGridDescription['horizontal']
            isVertical = eachGridDescription['vertical']
            gridStep = eachGridDescription['step']

            if isHorizontal is False and isVertical is False:
                continue
            if not gridStep:
                continue

            dt.save()
            dt.skew(-italicAngle, 0)
            dt.stroke(*gridColor)
            dt.strokeWidth(.5 * scalingFactor)
            dt.fill(None)

            if isVertical is True:
                # to the right (from 0)
                extraSlant = int(
                    ceil(tan(radians(italicAngle)) * frameOrigin[1]))
                for eachX in range(0,
                                   frameOrigin[0] + frameSize[0] + extraSlant,
                                   gridStep):
                    dt.line((eachX, frameOrigin[1] - GRID_TOLERANCE),
                            (eachX,
                             frameOrigin[1] + frameSize[1] + GRID_TOLERANCE))

                # to the left (from 0)
                extraSlant = int(
                    ceil(
                        tan(radians(italicAngle)) *
                        (frameSize[1] + frameOrigin[1])))
                for eachX in [
                        i for i in range(frameOrigin[0] + extraSlant, 0)
                        if i % gridStep == 0
                ]:
                    dt.line((eachX, frameOrigin[1] - GRID_TOLERANCE),
                            (eachX,
                             frameOrigin[1] + frameSize[1] + GRID_TOLERANCE))

            if isHorizontal is True:
                # to the top (from baseline)
                for eachY in range(0, frameOrigin[1] + frameSize[1], gridStep):
                    dt.line((frameOrigin[0] - GRID_TOLERANCE, eachY),
                            (frameOrigin[0] + frameSize[0] + GRID_TOLERANCE,
                             eachY))

                # to the bottom (from baseline)
                for eachY in [
                        i for i in range(frameOrigin[1], 0)
                        if i % gridStep == 0
                ]:
                    dt.line((frameOrigin[0] - GRID_TOLERANCE, eachY),
                            (frameOrigin[0] + frameSize[0] + GRID_TOLERANCE,
                             eachY))

            dt.restore()

    def _drawBcpLenght(self, glyph, scalingFactor, offset_X=0):
        for eachContour in glyph:
            for indexBPT, eachBPT in enumerate(eachContour.bPoints):
                if eachBPT.bcpOut != (0, 0):
                    absBcpOut = eachBPT.anchor[0] + eachBPT.bcpOut[
                        0], eachBPT.anchor[1] + eachBPT.bcpOut[1]
                    bcpOutAngle = calcAngle(eachBPT.anchor, absBcpOut)
                    bcpOutLenght = calcDistance(eachBPT.anchor, absBcpOut)
                    captionBcpOut = u'→{:d}'.format(int(bcpOutLenght))
                    projOut_X = eachBPT.anchor[0] + cos(
                        radians(bcpOutAngle)) * bcpOutLenght / 2.
                    projOut_Y = eachBPT.anchor[1] + sin(
                        radians(bcpOutAngle)) * bcpOutLenght / 2.

                    textQualities(BODYSIZE_CAPTION * scalingFactor,
                                  weight='bold')
                    textWidth, textHeight = dt.textSize(captionBcpOut)

                    dt.save()
                    dt.translate(projOut_X + offset_X, projOut_Y)
                    dt.rotate(bcpOutAngle % 90)
                    belowRect = (-textWidth / 2. - 1, -textHeight / 2. - 1,
                                 textWidth + 2, textHeight + 2, 1)
                    dt.fill(0, 0, 0, .7)
                    dt.roundedRect(*belowRect)

                    textRect = (-textWidth / 2., -textHeight / 2., textWidth,
                                textHeight)
                    textQualities(BODYSIZE_CAPTION * scalingFactor,
                                  weight='bold',
                                  color=WHITE_COLOR)
                    dt.textBox(captionBcpOut, textRect, align='center')
                    dt.restore()

                if eachBPT.bcpIn != (0, 0):
                    absBcpIn = eachBPT.anchor[0] + eachBPT.bcpIn[
                        0], eachBPT.anchor[1] + eachBPT.bcpIn[1]
                    bcpInAngle = calcAngle(eachBPT.anchor, absBcpIn)
                    bcpInLenght = calcDistance(eachBPT.anchor, absBcpIn)
                    captionBcpIn = u'→{:d}'.format(int(bcpInLenght))

                    projIn_X = eachBPT.anchor[0] + cos(
                        radians(bcpInAngle)) * bcpInLenght / 2.
                    projIn_Y = eachBPT.anchor[1] + sin(
                        radians(bcpInAngle)) * bcpInLenght / 2.

                    textQualities(BODYSIZE_CAPTION * scalingFactor,
                                  weight='bold')
                    textWidth, textHeight = dt.textSize(captionBcpIn)

                    dt.save()
                    dt.translate(projIn_X + offset_X, projIn_Y)
                    dt.rotate(bcpInAngle % 90)

                    belowRect = (-textWidth / 2. - 1, -textHeight / 2. - 1,
                                 textWidth + 2, textHeight + 2, 1)
                    dt.fill(0, 0, 0, .7)
                    dt.roundedRect(*belowRect)

                    textQualities(BODYSIZE_CAPTION * scalingFactor,
                                  weight='bold',
                                  color=WHITE_COLOR)
                    textRect = (-textWidth / 2., -textHeight / 2., textWidth,
                                textHeight)
                    dt.textBox(captionBcpIn, textRect, align='center')
                    dt.restore()

    def _drawSquarings(self, glyph, scalingFactor, offset_X=0):
        for eachContour in glyph:
            for indexBPT, eachBPT in enumerate(eachContour.bPoints):

                if eachBPT.bcpOut != (0, 0):
                    nextBPT = eachContour.bPoints[(indexBPT + 1) %
                                                  len(eachContour.bPoints)]

                    absBcpOut = (eachBPT.anchor[0] + eachBPT.bcpOut[0],
                                 eachBPT.anchor[1] + eachBPT.bcpOut[1])
                    angleOut = calcAngle(eachBPT.anchor, absBcpOut)
                    handleOutLen = calcDistance(eachBPT.anchor, absBcpOut)

                    absBcpIn = (nextBPT.anchor[0] + nextBPT.bcpIn[0],
                                nextBPT.anchor[1] + nextBPT.bcpIn[1])
                    angleIn = calcAngle(nextBPT.anchor, absBcpIn)
                    nextHandleInLen = calcDistance(nextBPT.anchor, absBcpIn)

                    handlesIntersection = intersectionBetweenSegments(
                        eachBPT.anchor, absBcpOut, absBcpIn, nextBPT.anchor)

                    if handlesIntersection is not None:
                        maxOutLen = calcDistance(eachBPT.anchor,
                                                 handlesIntersection)
                        maxInLen = calcDistance(nextBPT.anchor,
                                                handlesIntersection)

                        sqrOut = handleOutLen / maxOutLen
                        sqrIn = nextHandleInLen / maxInLen

                        projOut_X = eachBPT.anchor[0] + cos(
                            radians(angleOut)) * handleOutLen
                        projOut_Y = eachBPT.anchor[1] + sin(
                            radians(angleOut)) * handleOutLen
                        if angleOut != 0 and angleOut % 90 != 0:
                            captionSqrOut = u'{:.0%}, {:d}°'.format(
                                sqrOut, int(angleOut % 180))
                        else:
                            captionSqrOut = '{:.0%}'.format(sqrOut)
                        captionSqrOut = captionSqrOut.replace('0.', '')

                        dt.save()
                        dt.translate(projOut_X + offset_X, projOut_Y)
                        textQualities(BODYSIZE_CAPTION * scalingFactor)
                        textWidth, textHeight = dt.textSize(captionSqrOut)
                        if angleOut == 90:  # text above
                            textRect = (-textWidth / 2.,
                                        SQR_CAPTION_OFFSET * scalingFactor,
                                        textWidth, textHeight)
                        elif angleOut == -90:  # text below
                            textRect = (-textWidth / 2., -textHeight -
                                        SQR_CAPTION_OFFSET * scalingFactor,
                                        textWidth, textHeight)
                        elif -90 < angleOut < 90:  # text on the right
                            textRect = (SQR_CAPTION_OFFSET * scalingFactor,
                                        -textHeight / 2., textWidth,
                                        textHeight)
                        else:  # text on the left
                            textRect = (-textWidth -
                                        SQR_CAPTION_OFFSET * scalingFactor,
                                        -textHeight / 2., textWidth,
                                        textHeight)
                        dt.textBox(captionSqrOut, textRect, align='center')
                        dt.restore()

                        projIn_X = nextBPT.anchor[0] + cos(
                            radians(angleIn)) * nextHandleInLen
                        projIn_Y = nextBPT.anchor[1] + sin(
                            radians(angleIn)) * nextHandleInLen
                        if angleIn != 0 and angleIn % 90 != 0:
                            captionSqrIn = u'{:.0%}, {:d}°'.format(
                                sqrIn, int(angleIn % 180))
                        else:
                            captionSqrIn = '{:.0%}'.format(sqrIn)
                        captionSqrIn = captionSqrIn.replace('0.', '')

                        dt.save()
                        dt.translate(projIn_X + offset_X, projIn_Y)
                        textQualities(BODYSIZE_CAPTION * scalingFactor)
                        textWidth, textHeight = dt.textSize(captionSqrIn)
                        if angleIn == 90:  # text above
                            textRect = (-textWidth / 2.,
                                        SQR_CAPTION_OFFSET * scalingFactor,
                                        textWidth, textHeight)
                        elif angleIn == -90:  # text below
                            textRect = (-textWidth / 2., -textHeight -
                                        SQR_CAPTION_OFFSET * scalingFactor,
                                        textWidth, textHeight)
                        elif -90 < angleIn < 90:  # text on the right
                            textRect = (SQR_CAPTION_OFFSET * scalingFactor,
                                        -textHeight / 2., textWidth,
                                        textHeight)
                        else:  # text on the left
                            textRect = (-textWidth -
                                        SQR_CAPTION_OFFSET * scalingFactor,
                                        -textHeight / 2., textWidth,
                                        textHeight)
                        dt.textBox(captionSqrIn, textRect, align='center')
                        dt.restore()

    def _drawGlyphOutline(self, glyph, scalingFactor, offset_X=0):
        dt.save()
        dt.translate(offset_X, 0)

        dt.fill(None)
        dt.strokeWidth(1 * scalingFactor)
        dt.stroke(*LIGHT_GRAY_COLOR)
        dt.drawGlyph(glyph)

        scaledRadius = BCP_RADIUS * scalingFactor

        for eachContour in glyph:
            for eachBPT in eachContour.bPoints:
                dt.stroke(None)
                dt.fill(*LIGHT_GRAY_COLOR)
                dt.rect(eachBPT.anchor[0] - scaledRadius / 2.,
                        eachBPT.anchor[1] - scaledRadius / 2., scaledRadius,
                        scaledRadius)

                if eachBPT.bcpIn != (0, 0):
                    dt.stroke(None)
                    dt.fill(*LIGHT_GRAY_COLOR)
                    dt.oval(
                        eachBPT.anchor[0] + eachBPT.bcpIn[0] -
                        scaledRadius / 2., eachBPT.anchor[1] +
                        eachBPT.bcpIn[1] - scaledRadius / 2., scaledRadius,
                        scaledRadius)

                    dt.stroke(*LIGHT_GRAY_COLOR)
                    dt.fill(None)
                    dt.line((eachBPT.anchor[0], eachBPT.anchor[1]),
                            (eachBPT.anchor[0] + eachBPT.bcpIn[0],
                             eachBPT.anchor[1] + eachBPT.bcpIn[1]))

                if eachBPT.bcpOut != (0, 0):
                    dt.stroke(None)
                    dt.fill(*LIGHT_GRAY_COLOR)
                    dt.oval(
                        eachBPT.anchor[0] + eachBPT.bcpOut[0] -
                        scaledRadius / 2., eachBPT.anchor[1] +
                        eachBPT.bcpOut[1] - scaledRadius / 2., scaledRadius,
                        scaledRadius)

                    dt.stroke(*LIGHT_GRAY_COLOR)
                    dt.fill(None)
                    dt.line((eachBPT.anchor[0], eachBPT.anchor[1]),
                            (eachBPT.anchor[0] + eachBPT.bcpOut[0],
                             eachBPT.anchor[1] + eachBPT.bcpOut[1]))

        dt.restore()

    def _drawGlyphBlack(self, glyph, scalingFactor, offset_X=0):
        dt.save()
        dt.translate(offset_X, 0)

        dt.fill(*BLACK_COLOR)
        dt.stroke(None)
        dt.drawGlyph(glyph)

        dt.restore()

    def _drawStems(self, currentGlyph, scalingFactor, offset_X=0):
        if STEM_KEY not in currentGlyph.lib:
            return None

        stemData = calcStemsData(currentGlyph, STEM_KEY)
        for PTs, DIFFs, middlePoint in stemData:
            pt1, pt2 = PTs
            horDiff, verDiff = DIFFs

            dt.save()
            dt.translate(offset_X, 0)
            dt.stroke(*self.stemColor)
            dt.fill(None)
            dt.strokeWidth(1 * scalingFactor)

            dt.newPath()
            if horDiff > verDiff:  # ver
                rightPt, leftPt = PTs
                if pt1.x > pt2.x:
                    rightPt, leftPt = leftPt, rightPt
                dt.moveTo((leftPt.x, leftPt.y))
                dt.curveTo((leftPt.x - horDiff / 2, leftPt.y),
                           (rightPt.x + horDiff / 2, rightPt.y),
                           (rightPt.x, rightPt.y))

            else:  # hor
                topPt, btmPt = PTs
                if pt2.y > pt1.y:
                    btmPt, topPt = topPt, btmPt
                dt.moveTo((btmPt.x, btmPt.y))
                dt.curveTo((btmPt.x, btmPt.y + verDiff / 2),
                           (topPt.x, topPt.y - verDiff / 2),
                           (topPt.x, topPt.y))
            dt.drawPath()
            dt.restore()

            dt.save()
            dt.translate(offset_X, 0)
            textQualities(BODYSIZE_CAPTION * scalingFactor)
            dataToPlot = u'↑{:d}\n→{:d}'.format(int(verDiff), int(horDiff))
            textWidth, textHeight = dt.textSize(dataToPlot)
            textRect = (middlePoint[0] - textWidth / 2.,
                        middlePoint[1] - textHeight / 2., textWidth,
                        textHeight)
            dt.textBox(dataToPlot, textRect, align='center')
            dt.restore()

    def _drawDiagonals(self, currentGlyph, scalingFactor, offset_X=0):
        if DIAGONALS_KEY not in currentGlyph.lib:
            return None

        diagonalsData = calcDiagonalsData(currentGlyph, DIAGONALS_KEY)
        for ptsToDisplay, angle, distance in diagonalsData:
            pt1, pt2 = ptsToDisplay

            dt.save()
            dt.stroke(*self.diagonalColor)
            dt.fill(None)
            dt.strokeWidth(1 * scalingFactor)

            if 90 < angle <= 180 or -180 < angle < -90:
                direction = -1
                adjustedAngle = angle + 180 + 90
            else:
                direction = 1
                adjustedAngle = angle + 90

            diagonalPt1 = pt1[0] + cos(radians(adjustedAngle)) * (
                (DIAGONAL_OFFSET - 1) * direction), pt1[1] + sin(
                    radians(adjustedAngle)) * (
                        (DIAGONAL_OFFSET - 1) * direction)
            diagonalPt2 = pt2[0] + cos(radians(adjustedAngle)) * (
                (DIAGONAL_OFFSET - 1) * direction), pt2[1] + sin(
                    radians(adjustedAngle)) * (
                        (DIAGONAL_OFFSET - 1) * direction)
            offsetPt1 = pt1[0] + cos(radians(
                adjustedAngle)) * DIAGONAL_OFFSET * direction, pt1[1] + sin(
                    radians(adjustedAngle)) * DIAGONAL_OFFSET * direction
            offsetPt2 = pt2[0] + cos(radians(
                adjustedAngle)) * DIAGONAL_OFFSET * direction, pt2[1] + sin(
                    radians(adjustedAngle)) * DIAGONAL_OFFSET * direction

            dt.line((pt1), (offsetPt1))
            dt.line((pt2), (offsetPt2))
            dt.line((diagonalPt1), (diagonalPt2))
            dt.restore()

            dt.save()
            textQualities(BODYSIZE_CAPTION * scalingFactor)
            offsetMidPoint = calcMidPoint(offsetPt1, offsetPt2)
            dt.translate(offsetMidPoint[0], offsetMidPoint[1])

            if 90 < angle <= 180 or -180 < angle < -90:
                dt.rotate(angle + 180)
                textBoxY = -BODYSIZE_CAPTION * 1.2 * scalingFactor
            else:
                dt.rotate(angle)
                textBoxY = 0

            dataToPlot = u'∡{:.1f} ↗{:d}'.format(angle % 180, int(distance))
            textWidth, textHeight = dt.textSize(dataToPlot)
            dt.textBox(dataToPlot, (-textWidth / 2., textBoxY, textWidth,
                                    BODYSIZE_CAPTION * 1.2 * scalingFactor),
                       align='center')
            dt.restore()

    # ui callback
    def collectOpenedFontPaths(self):
        self.openedFontPaths = [
            f.path for f in AllFonts() if f.path is not None
        ]
        self.openedFontPaths.sort()

    def aFontIsClosing(self, infoDict):
        willCloseFont = infoDict['font']
        self.openedFontPaths.remove(willCloseFont.path)

        for eachFontAttrName, eachGlyphAttrName in [
            ('lftFontPath', 'lftGlyphName'), ('cntFontPath', 'cntGlyphName'),
            ('rgtFontPath', 'rgtGlyphName')
        ]:
            if getattr(self, eachFontAttrName) is willCloseFont:
                anotherFont = getOpenedFontFromPath(AllFonts(),
                                                    self.openedFontPaths[0])
                setattr(self, eachFontAttrName, anotherFont)
                self.pushFontAttr(eachFontAttrName)

                activeName = getattr(self, eachGlyphAttrName).name
                if anotherFont.has_key(activeName):
                    setattr(self, eachGlyphAttrName, anotherFont[activeName])
                else:
                    setattr(self, eachGlyphAttrName,
                            anotherFont[anotherFont.glyphOrder[0]])
                self.pushGlyphAttr(eachGlyphAttrName)

        self.neighborsController.setFontData(self.openedFontPaths)

    def pushFontAttr(self, attrName):
        fontToBePushed = getattr(self, attrName)
        self.neighborsController.neighborsDB[attrName][1] = fontToBePushed
        setattr(self.neighborsController,
                '{}Controller.activeFont'.format(attrName[:3], fontToBePushed))
        getattr(self.neighborsController,
                '{}Controller'.format(attrName[:3]).updateGlyphList())

    def pushGlyphAttr(self, attrName):
        glyphToBePushed = getattr(self, attrName)
        self.neighborsController.neighborsDB[attrName][2] = glyphToBePushed
        setattr(
            self.neighborsController,
            '{}Controller.activeGlyph'.format(attrName[:3], glyphToBePushed))
        getattr(self.neighborsController,
                '{}Controller'.format(attrName[:3]).updateGlyphList())

    def aFontIsOpening(self, infoDict):
        originalState = list(self.openedFontPaths)
        self.openedFontPaths.append(infoDict['font'].path)
        self.openedFontPaths.sort()
        if originalState == []:
            self._initFontsAndGlyphs()
            for eachFontAttrName, eachGlyphAttrName in [
                ('lftFontPath', 'lftGlyphName'), ('cntFontPath',
                                                  'cntGlyphName'),
                ('rgtFontPath', 'rgtGlyphName')
            ]:
                self.pushFontAttr(eachFontAttrName)
                self.pushGlyphAttr(eachGlyphAttrName)
        self.neighborsController.setFontData(self.openedFontPaths)

    def _initFontsAndGlyphs(self):
        for eachFontAttrName, eachGlyphAttrName in [
            ('lftFontPath', 'lftGlyphName'), ('cntFontPath', 'cntGlyphName'),
            ('rgtFontPath', 'rgtGlyphName')
        ]:
            setattr(self, eachFontAttrName, CURRENT_FONT_REPR)
            setattr(self, eachGlyphAttrName, CURRENT_GLYPH_REPR)

    def _updateCurrentGlyph(self, infoDict):
        if infoDict['glyph']:
            # update distances
            self.currentGlyph = infoDict['glyph']
            self.distancesController.setCurrentGlyph(self.currentGlyph)
            # update neighbors
            self.neighborsController.lftController.updateCurrentGlyph()
            self.neighborsController.cntController.updateCurrentGlyph()
            self.neighborsController.rgtController.updateCurrentGlyph()

    def _updateCurrentFont(self, infoDict):
        if infoDict['font']:
            self.currentGlyph = CurrentGlyph()
            self.distancesController.setCurrentGlyph(self.currentGlyph)
            # update neighbors
            self.neighborsController.lftController.updateCurrentGlyph()
            self.neighborsController.cntController.updateCurrentGlyph()
            self.neighborsController.rgtController.updateCurrentGlyph()

    # controls callbacks
    def windowCloseCallback(self, sender):
        removeObserver(self, 'draw')
        removeObserver(self, 'drawInactive')
        removeObserver(self, 'drawBackground')
        removeObserver(self, 'drawPreview')
        removeObserver(self, 'mouseDown')
        removeObserver(self, 'viewDidChangeGlyph')
        removeObserver(self, 'newFontDidOpen')
        removeObserver(self, 'fontWillClose')
        removeObserver(self, 'fontDidOpen')

    def bcpControllerCallback(self, sender):
        self.sqrActive = sender.getSqr()
        self.bcpLengthActive = sender.getBcpLength()
        UpdateCurrentGlyphView()

    def gridControllerCallback(self, sender):
        self.gridActive, self.gridsDB, self.offgridActive = sender.get()
        UpdateCurrentGlyphView()

    def distancesControllerCallback(self, sender):
        self.stemActive, self.stemColor, self.diagonalActive, self.diagonalColor = sender.get(
        )
        UpdateCurrentGlyphView()

    def neighborsControllerCallback(self, sender):
        neighborsDB = sender.get()
        self.lftNeighborActive = neighborsDB['lft'][0]
        self.lftFontPath = neighborsDB['lft'][1]
        self.lftGlyphName = neighborsDB['lft'][2]
        self.cntNeighborActive = neighborsDB['cnt'][0]
        self.cntFontPath = neighborsDB['cnt'][1]
        self.cntGlyphName = neighborsDB['cnt'][2]
        self.rgtNeighborActive = neighborsDB['rgt'][0]
        self.rgtFontPath = neighborsDB['rgt'][1]
        self.rgtGlyphName = neighborsDB['rgt'][2]
        UpdateCurrentGlyphView()