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]
Example #2
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()
Example #3
0
class ChooseExceptionWindow(BaseWindowController):
    lastEvent = None

    def __init__(self, options, callback):
        super(ChooseExceptionWindow, self).__init__()
        self.options = options
        self.callback = callback
        self.whichException = options[0]

        self.w = FloatingWindow((300, 120), 'Choose exception')

        self.w.optionsRadio = RadioGroup(
            (MARGIN, MARGIN, -MARGIN, len(options) * 20),
            options,
            callback=self.optionsCallback)
        self.w.optionsRadio.set(0)

        self.w.cancel = Button(
            (-(90 * 2 + MARGIN * 2),
             -(vanillaControlsSize['ButtonRegularHeight'] + MARGIN), 90,
             vanillaControlsSize['ButtonRegularHeight']),
            'Cancel',
            callback=self.cancelCallback)

        self.w.submit = Button(
            (-(90 + MARGIN),
             -(vanillaControlsSize['ButtonRegularHeight'] + MARGIN), 90,
             vanillaControlsSize['ButtonRegularHeight']),
            'Submit',
            callback=self.submitCallback)
        self.setUpBaseWindowBehavior()

    def set(self, exception):
        self.whichException = exception
        self.lastEvent = 'submit'

    def trigger(self):
        self.callback(self)

    def get(self):
        return self.whichException

    def enable(self, value):
        if value is True:
            self.w.center()
            self.w.show()
        else:
            self.w.hide()

    def close(self):
        self.whichException = None
        self.w.close()

    def setOptions(self, options):
        self.options = options

        optionsRepresentation = []
        for lft, rgt in self.options:
            row = []
            for eachSide in [lft, rgt]:
                if eachSide.startswith('@') is True:
                    groupRepr = encloseClassTitleInBrackets(eachSide)
                    row.append(groupRepr)
                else:
                    row.append(eachSide)
            optionsRepresentation.append(', '.join(row))

        if hasattr(self.w, 'optionsRadio') is True:
            delattr(self.w, 'optionsRadio')
        self.w.optionsRadio = RadioGroup(
            (MARGIN, MARGIN, -MARGIN, len(options) * 20),
            optionsRepresentation,
            callback=self.optionsCallback)

    def optionsCallback(self, sender):
        self.whichException = self.options[sender.get()]

    def cancelCallback(self, sender):
        self.lastEvent = 'cancel'
        self.whichException = None
        self.callback(self)
        self.w.hide()

    def submitCallback(self, sender):
        self.lastEvent = 'submit'
        self.callback(self)
        self.w.hide()
Example #4
0
class Script(object):
    def __init__(self):
        self.valuesPrefsKey = prefsKey + ".delta." + basename(
            Glyphs.font.filepath)
        # UI metrics
        spacing = 8
        height = 22
        # calculate window height
        minWinSize = (220,
                      120 + (len(Glyphs.font.masters) * (height + spacing)))
        # create UI window
        self.w = FloatingWindow(minWinSize,
                                "Adjust sidebearings",
                                minSize=minWinSize,
                                maxSize=(500, 500),
                                autosaveName=prefsKey + ".win")
        # layout UI controls
        y = 16
        self.w.label = TextBox((16, y, -16, height),
                               "Sidebearing delta adjustment:")
        y += height + spacing

        inputWidth = 64
        for master in Glyphs.font.masters:
            setattr(self.w, "deltaLabel%s" % master.id,
                    TextBox((16, y, -16 - inputWidth, height), master.name))
            setattr(self.w, "deltaInput%s" % master.id,
                    EditText((-16 - inputWidth, y, -16, height), "16"))
            # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id))
            y += height + spacing

        self.w.submitButton = Button((16, -16 - height, -16, height),
                                     "Adjust all sidebearings",
                                     callback=self.onSubmit)
        # finalize UI
        self.w.setDefaultButton(self.w.submitButton)
        self.loadPreferences()
        self.w.bind("close", self.savePreferences)

        # make sure window is large enough to show all masters
        x, y, w, h = self.w.getPosSize()
        if w < minWinSize[0] and h < minWinSize[1]:
            self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]),
                              animate=False)
        elif w < minWinSize[0]:
            self.w.setPosSize((x, y, minWinSize[0], h), animate=False)
        elif h < minWinSize[1]:
            self.w.setPosSize((x, y, w, minWinSize[1]), animate=False)

        self.w.open()
        self.w.makeKey()

    def getDeltaInputForMaster(self, masterId):
        return getattr(self.w, "deltaInput%s" % masterId)

    def loadPreferences(self):
        try:
            Glyphs.registerDefault(self.valuesPrefsKey, [])
            for t in Glyphs.defaults[self.valuesPrefsKey]:
                try:
                    masterId, value = t
                    self.getDeltaInputForMaster(masterId).set(value)
                except:
                    pass
        except:
            print("failed to load preferences")

    def savePreferences(self, sender):
        try:
            values = []
            for master in Glyphs.font.masters:
                values.append(
                    (master.id, self.getDeltaInputForMaster(master.id).get()))
            Glyphs.defaults[self.valuesPrefsKey] = values
        except:
            print("failed to save preferences")

    def onSubmit(self, sender):
        try:
            sender.enable(False)
            if performFontChanges(self.action1):
                self.w.close()
        except Exception, e:
            Glyphs.showMacroWindow()
            print("error: %s" % e)
        finally:
Example #5
0
class 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)
class ProofGroupInspector:
    def __init__(self, proofGroup):
        """
        Initialize inspector with proofGroup data.
        proofGroup is a dictionary passed in by ProofDrawer().
        """
        self.proofGroup = proofGroup
        self.editedProofGroup = {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def cancelCB(self, sender):
        self.w.close()
Example #7
0
class Script( object ):
  def __init__(self):
    self.valuesPrefsKey = prefsKey + ".delta." + basename(Glyphs.font.filepath)
    # UI metrics
    spacing = 8
    height = 22
    # calculate window height
    minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing)))
    # create UI window
    self.w = FloatingWindow(
      minWinSize,
      "Adjust sidebearings",
      minSize=minWinSize,
      maxSize=(500, 500),
      autosaveName=prefsKey + ".win")
    # layout UI controls
    y = 16
    self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:")
    y += height + spacing

    inputWidth = 64
    for master in Glyphs.font.masters:
      setattr(self.w, "deltaLabel%s" % master.id,
        TextBox((16, y, -16-inputWidth, height), master.name))
      setattr(self.w, "deltaInput%s" % master.id,
        EditText((-16-inputWidth, y, -16, height), "16"))
      # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id))
      y += height + spacing

    self.w.submitButton = Button(
      (16, -16-height, -16, height),
      "Adjust all sidebearings",
      callback=self.onSubmit)
    # finalize UI
    self.w.setDefaultButton(self.w.submitButton)
    self.loadPreferences()
    self.w.bind("close", self.savePreferences)

    # make sure window is large enough to show all masters
    x, y, w, h = self.w.getPosSize()
    if w < minWinSize[0] and h < minWinSize[1]:
      self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False)
    elif w < minWinSize[0]:
      self.w.setPosSize((x, y, minWinSize[0], h), animate=False)
    elif h < minWinSize[1]:
      self.w.setPosSize((x, y, w, minWinSize[1]), animate=False)

    self.w.open()
    self.w.makeKey()


  def getDeltaInputForMaster(self, masterId):
    return getattr(self.w, "deltaInput%s" % masterId)


  def loadPreferences(self):
    try:
      Glyphs.registerDefault(self.valuesPrefsKey, [])
      for t in Glyphs.defaults[self.valuesPrefsKey]:
        try:
          masterId, value = t
          self.getDeltaInputForMaster(masterId).set(value)
        except:
          pass
    except:
      print("failed to load preferences")


  def savePreferences(self, sender):
    try:
      values = []
      for master in Glyphs.font.masters:
        values.append((master.id, self.getDeltaInputForMaster(master.id).get()))
      Glyphs.defaults[self.valuesPrefsKey] = values
    except:
      print("failed to save preferences")


  def onSubmit(self, sender):
    try:
      sender.enable(False)
      if performFontChanges(self.action1):
        self.w.close()
    except Exception(e):
      Glyphs.showMacroWindow()
      print("error: %s" % e)
    finally:
      sender.enable(True)


  def action1(self, font):
    print(font)

    deltas = {}  # keyed on master.id
    for master in Glyphs.font.masters:
      deltas[master.id] = int(self.getDeltaInputForMaster(master.id).get())
    syncLayers = set()  # layers that need syncMetrics() to be called

    # [debug] alterntive loop over a few select glyphs, for debugging.
    # debugGlyphNames = set([
    #   ".null",
    #   "A", "Adieresis", "Lambda",
    #   "Bhook",
    #   "C", "Chook",
    #   "endash",
    #   "space",
    # ])
    # for g in [g for g in font.glyphs if g.name in debugGlyphNames]:
    for g in font.glyphs:
      # print(">> %s  (%s)" % (g.name, g.productionName))
      if g.name in excludedGlyphNames:
        # glyph is exlcuded
        print("ignoring excluded glyph %r" % g.name)
        continue
      g.beginUndo()
      try:
        for master in font.masters:
          layer = g.layers[master.id]
          delta = deltas[master.id]
          if delta == 0:
            # no adjustment
            continue

          if layer.width == 0:
            # ignore glyphs with zero-width layers
            print("ignoring zero-width glyph", g.name)
            break

          if layer.isAligned:
            # ignore glyphs which are auto-aligned from components
            print("ignoring auto-aligned glyph", g.name)
            break

          if len(layer.paths) == 0 and len(layer.components) == 0:
            # adjust width instead of LSB & RSB of empty glyphs
            if layer.widthMetricsKey is None:
              layer.width = max(0, layer.width + (delta * 2))
            else:
              syncLayers.add(layer)
            continue

          # offset components by delta to counter-act effect of also applying delta
          # to the component glyphs.
          if layer.components is not None:
            for cn in layer.components:
              m = cn.transform
              #         auto-aligned       or offset x or offset y   or contains shapes
              if not cn.automaticAlignment or m[4] != 0 or m[5] != 0 or len(layer.paths) > 0:
                cn.transform = (
                  m[0], # x scale factor
                  m[1], # x skew factor
                  m[2], # y skew factor
                  m[3], # y scale factor
                  m[4] - delta, # x position (+ since transform is inverse)
                  m[5]     # y position
                )

          # Note:
          # layer metrics keys are expressions, e.g. "==H+10"
          # glyph metrics keys are other glyphs, e.g. U+0041

          if g.leftMetricsKey is None and layer.leftMetricsKey is None:
            layer.LSB = layer.LSB + delta
          else:
            syncLayers.add(layer)

          if g.rightMetricsKey is None and layer.rightMetricsKey is None:
            layer.RSB = layer.RSB + delta
          else:
            syncLayers.add(layer)

      finally:
        g.endUndo()

    # end for g in font

    # sync layers that use metricsKey
    if len(syncLayers) > 0:
      print("Syncing LSB & RSB for %r layers..." % len(syncLayers))
      for layer in syncLayers:
        layer.syncMetrics()
    print("Done")
class 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()