def run(self, destDir, progress):
        paths = self.controller.get(["ufo"])

        report = Report()

        tempDir = os.path.join(destDir, "temp")
        if not os.path.exists(tempDir):
            os.makedirs(tempDir)
        tempExportPaths = self._generateCallback(tempDir, progress, report)

        progress.update("Merging Tables...")
        report.writeTitle("Merged Fonts:")
        report.newLine()

        tableNames = [item["tableName"] for item in self.tableList if item["add"]]

        for fontIndex, path in enumerate(paths):
            font = RFont(path, document=False, showInterface=False)
            binarySourcepath = font.lib.get("com.typemytype.robofont.binarySource")
            tempExportPath = tempExportPaths[fontIndex]
            if binarySourcepath:
                binaryIs
                Type = os.path.splitext(binarySourcepath)[1].lower() in [".ttf", ".otf"]
                tempIsOpenType = os.path.splitext(tempExportPath)[1].lower() in [".ttf", ".otf"]
                if binaryIsOpenType and tempIsOpenType:
                    if os.path.exists(binarySourcepath) and os.path.exists(tempExportPath):
                        binarySource = TTFont(binarySourcepath)
                        tempFont = TTFont(tempExportPath)
                        fileName = os.path.basename(tempExportPath)
                        if not self.controller.keepFileNames():
                            fileName = "%s-%s%s" % (font.info.familyName, font.info.styleName, os.path.splitext(tempExportPath)[1])
                        path = os.path.join(destDir, fileName)
                        report.writeTitle(os.path.basename(path), "'")
                        report.write("source: %s" % tempExportPath)
                        report.write("binary source: %s" % binarySourcepath)
                        report.newLine()
                        report.indent()
                        for table in tableNames:
                            if table in binarySource:
                                report.write("merge %s table" % table)
                                tempFont[table] = binarySource[table]
                        report.write("save to %s" % path)
                        tempFont.save(path)
                        report.dedent()
                        report.newLine()
                        tempFont.close()
                        binarySource.close()
            if not font.hasInterface():
                font.close()

        reportPath = os.path.join(destDir, "Binary Merge Report.txt")
        report.save(reportPath)

        if not getDefault("Batch.Debug", False):
            if os.path.exists(tempDir):
                shutil.rmtree(tempDir)
    def drawPoints(self, info):
    
        newPoints = self.getValues(None)
        if newPoints != None:
            glyph = CurrentGlyph()
        
            onCurveSize = getDefault("glyphViewOncurvePointsSize") * 5
            offCurveSize = getDefault("glyphViewOncurvePointsSize") * 3
            fillColor = tuple([i for i in getDefault("glyphViewCurvePointsFill")])
        
            upmScale = (glyph.font.info.unitsPerEm/1000) 
            scale = info["scale"]
            oPtSize = onCurveSize * scale
            fPtSize = offCurveSize * scale

            d.save()

            # thanks Erik!
            textLoc = newPoints[0][3][0] + math.cos(self.returnAngle(newPoints)) * (scale*.25*120) * 2, newPoints[0][3][1] + math.sin(self.returnAngle(newPoints)) * (scale*.25*120) * 2

            for b in newPoints:
                for a in b:
                    if a == newPoints[1][0] or a == newPoints[0][-1]:
                        d.oval(a[0]-oPtSize/2,a[1]-oPtSize/2, oPtSize, oPtSize) 
                    else:
                        d.oval(a[0]-fPtSize/2,a[1]-fPtSize/2, fPtSize, fPtSize) 
                    d.fill(fillColor[0],fillColor[1],fillColor[2],fillColor[3])
        
            d.stroke(fillColor[0],fillColor[1],fillColor[2],fillColor[3])
            d.strokeWidth(scale)
            d.line(newPoints[0][2],newPoints[1][1])
            d.restore()

            # https://robofont.com/documentation/building-tools/toolspace/observers/draw-info-text-in-glyph-view/?highlight=draw%20text

            glyphWindow = CurrentGlyphWindow()
            if not glyphWindow:
                return
            glyphView = glyphWindow.getGlyphView()
            textAttributes = {
                AppKit.NSFontAttributeName: AppKit.NSFont.userFixedPitchFontOfSize_(11),
            }
            glyphView.drawTextAtPoint(
            f'{round(abs(math.degrees(self.returnAngle(newPoints)))%180,4)}°\n{str(round(self.returnRatio(newPoints),4))}',
            textAttributes,
            textLoc,
            yOffset=0,
            drawBackground=True,
            centerX=True,
            centerY=True,
            roundBackground=False,)

            UpdateCurrentGlyphView()
Esempio n. 3
0
 def newThemeCallback(self, sender):
     if self.debug: print("newThemeCallback")
     # Callback from the "New" button
     # Read all of the current preferences into a new theme dictionary and update the Vanilla list
     theme = {}
     for key, name, dataType in THEMEKEYS:
         data = getDefault(key)
         data = dataType(data)
         # Save the data in the theme
         theme[key] = data
     # Give the theme a default name
     new = []
     name = "★ New Theme"
     for themes in self.themeNames:
         if name in themes:
             new.append(themes)
     length = len(new) + 1
     if name in self.themeNames:
         newName = name.replace("★ ", "")
         themeName = newName + " (%s)" % length
         theme["themeName"] = themeName
         theme["themeType"] = "User"  # User or Default
     else:
         theme["themeName"] = "New Theme"
         theme["themeType"] = "User"  # User or Default
     self.themes += [theme]
     self.rebuildThemeList()
     self.editName((len(self.themes) - 1), theme["themeName"])
Esempio n. 4
0
 def makeBackupTheme(self):
     if self.debug: print("makeBackupTheme")
     # Make a backup of the current user prefs
     self.backupTheme = {}
     for key, name, dataType in THEMEKEYS:
         data = getDefault(key)
         data = dataType(data)
         self.backupTheme[key] = data
Esempio n. 5
0
 def drawSolidPreview(self, info):
     outline = self.getRotatedGlyph()
     pen = CocoaPen(None)
     outline.draw(pen)
     defaultPreviewColor = getDefault('glyphViewPreviewFillColor')
     fillColor = NSColor.colorWithCalibratedRed_green_blue_alpha_(
         *defaultPreviewColor)
     fillColor.set()
     pen.path.fill()
Esempio n. 6
0
    def __init__(self, parentWindow):

        data = getExtensionDefault(self.identifier, dict())
        updateWithDefaultValues(data, defaultOptions)
        data["debug"] = getDefault("Batch.Debug", False)

        width = 380
        height = 1000

        self.w = Sheet((width, height), parentWindow=parentWindow)

        y = 10
        self.w.threaded = CheckBox((10, y, -10, 22),
                                   "Threaded",
                                   value=data["threaded"])

        y += 30
        self.w.exportInFolders = CheckBox((10, y, -10, 22),
                                          "Export in Sub Folders",
                                          value=data["exportInFolders"])

        y += 30
        self.w.keepFileNames = CheckBox(
            (10, y, -10, 22),
            "Keep file names (otherwise use familyName-styleName)",
            value=data["keepFileNames"])

        y += 30
        self.w.debug = CheckBox((10, y, -10, 22), "Debug", value=data["debug"])

        y += 35
        self.w.saveButton = Button((-100, y, -10, 20),
                                   "Save settings",
                                   callback=self.saveCallback,
                                   sizeStyle="small")
        self.w.setDefaultButton(self.w.saveButton)

        self.w.closeButton = Button((-190, y, -110, 20),
                                    "Cancel",
                                    callback=self.closeCallback,
                                    sizeStyle="small")
        self.w.closeButton.bind(".", ["command"])
        self.w.closeButton.bind(chr(27), [])

        self.w.resetButton = Button((-280, y, -200, 20),
                                    "Reset",
                                    callback=self.resetCallback,
                                    sizeStyle="small")

        y += 30
        self.w.resize(width, y, False)

        self.w.open()
Esempio n. 7
0
    def setup(self):
        self._rin = None
        self._rout = None
        self.markerWidth = 12
        self.snapThreshold = .5

        self.dot_size = int(getDefault('glyphViewOffCurvePointsSize')) * 3
        self.snap_size = self.dot_size + 6
        self.font_size = int(getDefault('textFontSize')) + 2

        foregroundLayer = self.extensionContainer(
            identifier="com.letterror.angleRatioTool.fg", 
            location="foreground", 
            clear=True
            )

        self.outgoingLayer = foregroundLayer.appendPathSublayer(
            fillColor=None,
            strokeColor=self.outgoingColor
        )

        self.incomingLayer = foregroundLayer.appendPathSublayer(
            fillColor=None,
            strokeColor=self.incomingColor
        )

        self.captionTextLayer = foregroundLayer.appendTextLineSublayer(
           #position=(0, 0),
           #size=(400, 100),
           backgroundColor=self.outgoingColor,
           text="",
           fillColor=(1, 1, 1, 1),
           horizontalAlignment="center"
        )

        self.outgoingLayer.setVisible(True)
        self.incomingLayer.setVisible(True)
        self.captionTextLayer.setVisible(True)

        self.update()
    def spaceCenterDraw(self, notification):
        glyph = notification["glyph"]
        spaceCenter = notification["spaceCenter"]
        scale = notification["scale"]

        attrValues = self.getAttributes()
        outGlyph = self.getGlyph(glyph, *attrValues)

        inverse = spaceCenter.glyphLineView.getDisplayStates()['Inverse']
        foreground = tuple(getDefault('spaceCenterGlyphColor')) if not inverse else tuple(getDefault('spaceCenterBackgroundColor'))
        background = tuple(getDefault('spaceCenterBackgroundColor')) if not inverse else tuple(getDefault('spaceCenterGlyphColor')) 

        # cover current glyph
        drawingTools.fill(*background)
        drawingTools.stroke(*background)
        drawingTools.strokeWidth(2*scale)
        drawingTools.drawGlyph(glyph)
        drawingTools.stroke(None)

        # draw glyph preview
        drawingTools.fill(*foreground)
        drawingTools.drawGlyph(outGlyph)
    def resize(self, sender):
        SWM = getDefault("singleWindowMode")

        fw = CurrentFontWindow()
        fo = fw.fontOverview
        gc = fo.getGlyphCollection()
        v = gc.getGlyphCellView()

        # make cells small first
        starter = 10
        v.setCellSize_([starter, starter])
        # get the width of the whole window
        x, y, w, h = fw.window().getPosSize()
        # get the width of overall font overview
        fo_w = fo.getNSView().frameSize().width
        # get the width of the cell view
        vw, vh = v.frameSize().width, v.frameSize().height
        # get the width of the sets menu to the left of the font overview
        sets_w = fo_w - vw
        # get number of glyphs
        num_g = len(gc.getGlyphNames())

        cells_across = 1
        cw = int(vw / cells_across)
        while ((num_g) / cells_across) * cw > vh:
            cells_across += 1
            cw = ch = int(vw / cells_across)

        vw = cw * cells_across
        fo_total_w = vw + sets_w

        # set frame size in single window mode
        if SWM == 1:
            fw.editor.splitView.setDimension('fontOverview', fo_total_w)
            fw.editor.splitView.setDimension('glyphView', w - fo_total_w)
            fw.centerGlyphInView()
        # set frame size in multi-window mode
        else:
            windows = NSApp().orderedWindows()
            (x, y), (w, h) = windows[0].frame()
            x_diff = w - fo_total_w
            windows[0].setFrame_display_animate_(
                ((x + x_diff, y), (fo_total_w, h)), True, False)

        # change the cell size once and for all, update the slider to reflect the change
        v.setCellSize_([cw, ch])
        fo.views.sizeSlider.set(cw)
        # set this as the new default cell size (this happens when you use the native slide too)
        setDefault("fontCollectionViewGlyphSize", int(cw))
Esempio n. 10
0
    def generateVariationFont(self,
                              destPath,
                              autohint=False,
                              fitToExtremes=False,
                              releaseMode=True,
                              glyphOrder=None,
                              report=None):
        """
        Generate a variation font based on a desingSpace.
        """
        if report is None:
            report = Report()
        self.generateReport = report
        self.compileSettingAutohint = autohint
        self.compileSettingReleaseMode = releaseMode
        self.compileGlyphOrder = glyphOrder

        self.loadFonts()
        self.loadLocations()
        self.loadMasters()

        self._generatedFiles = set()

        self.makeMasterGlyphsCompatible()
        self.decomposedMixedGlyphs()
        self.makeMasterGlyphsQuadractic()
        self.makeMasterKerningCompatible()
        self.makeMasterOnDefaultLocation()
        self.makeLayerMaster()
        try:
            self._generateVariationFont(destPath)
        except Exception:
            import traceback
            result = traceback.format_exc()
            print(result)
        finally:
            if not getDefault("Batch.Debug", False):
                # remove generated files
                for path in self._generatedFiles:
                    if os.path.exists(path):
                        if os.path.isdir(path):
                            shutil.rmtree(path)
                        else:
                            os.remove(path)
        return report
Esempio n. 11
0
 def drawActionPreview(self, data):
     if not self.previewObjects:
         return
     pen = CocoaPen(glyphSet=self.glyph.layer)
     for obj in self.previewObjects:
         if isinstance(obj, BaseBPoint):
             pass
         elif isinstance(obj, BaseContour):
             obj.draw(pen)
         elif isinstance(obj, BaseComponent):
             obj.draw(pen)
     pixel = 1.0 / data["scale"]
     r, g, b, a = getDefault("glyphViewEchoStrokeColor")
     with bot.savedState():
         bot.fill(None)
         bot.stroke(r, g, b, a)
         bot.strokeWidth(pixel)
         bot.drawPath(pen.path)
Esempio n. 12
0
 def makeLayerMaster(self):
     """
     If there a layer name in a source description add it as seperate master in the source description.
     """
     for sourceDescriptor in self.sources:
         if sourceDescriptor.layerName is not None:
             path, ext = os.path.splitext(sourceDescriptor.path)
             sourceDescriptor.path = "%s-%s%s" % (
                 path, sourceDescriptor.layerName, ext)
             sourceDescriptor.styleName = "%s %s" % (
                 sourceDescriptor.styleName, sourceDescriptor.layerName)
             sourceDescriptor.filename = None
             sourceDescriptor.layerName = None
             if getDefault("Batch.Debug", False):
                 masterFont = self.fonts[sourceDescriptor.name]
                 layerPath = os.path.join(os.path.dirname(self.path),
                                          sourceDescriptor.path)
                 masterFont.save(layerPath)
    def generateVariationFont(self, destPath, autohint=False, fitToExtremes=False, releaseMode=True, glyphOrder=None, report=None):
        """
        Generate a variation font based on a desingSpace.
        """
        if report is None:
            report = Report()
        self.generateReport = report
        self.compileSettingAutohint = autohint
        self.compileSettingReleaseMode = releaseMode
        self.compileGlyphOrder = glyphOrder

        self.loadFonts()
        self.loadLocations()
        self.loadMasters()

        self._generatedFiles = set()

        self.makeMasterGlyphsCompatible()
        self.decomposedMixedGlyphs()
        self.makeMasterGlyphsQuadractic()
        self.makeMasterKerningCompatible()
        self.makeMasterOnDefaultLocation()
        self.makeLayerMaster()
        try:
            self._generateVariationFont(destPath)
        except Exception:
            import traceback
            result = traceback.format_exc()
            print(result)
        finally:
            if not getDefault("Batch.Debug", False):
                # remove generated files
                for path in self._generatedFiles:
                    if os.path.exists(path):
                        if os.path.isdir(path):
                            shutil.rmtree(path)
                        else:
                            os.remove(path)
        return report
Esempio n. 14
0
    def makeMasterGlyphsCompatible(self):
        """
        Update all masters with missing glyphs.
        All Masters must have the same glyphs.
        """
        self.generateReport.writeTitle("Making master glyphs compatible", "'")
        self.generateReport.indent()
        # collect all possible glyph names
        glyphNames = []
        for master in self.masters.values():
            glyphNames.extend(master.keys())
        glyphNames = set(glyphNames)
        # get the default master
        defaultMaster = self.masters[self.default.name]
        # loop over all glyphName
        for glyphName in glyphNames:
            # first check if the default master has this glyph
            if glyphName not in defaultMaster:
                # the default does not have the glyph
                # build a repair mutator to generate a glyph.
                glyphItems = []
                for sourceDescriptor in self.sources:
                    master = self.masters[sourceDescriptor.name]
                    if glyphName in sourceDescriptor.mutedGlyphNames:
                        continue
                    if glyphName in master:
                        sourceGlyph = self.mathGlyphClass(master[glyphName])
                        sourceGlyphLocation = self.locations[
                            sourceDescriptor.name]
                        glyphItems.append((sourceGlyphLocation, sourceGlyph))
                # Note: this needs to be a mutatormath mutator, not a varlib.model.
                # A varlib model can't work without in the missing default.
                # Filling in the default is a bit of a hack: it will make the font work,
                # but it is a bit of a guess.
                _, mutator = buildMutator(glyphItems)
                # use the repair mutator to generate an instance at the default location
                result = mutator.makeInstance(
                    Location(self.newDefaultLocation()))
                # round if necessary
                if self.roundGeometry:
                    result.round()
                self.generateReport.write(
                    "Adding missing glyph '%s' in the default master '%s %s (%s)'"
                    % (glyphName, defaultMaster.font.info.familyName,
                       defaultMaster.font.info.styleName, defaultMaster.name))
                # add the glyph to the default master
                defaultMaster.newGlyph(glyphName)
                glyph = defaultMaster[glyphName]
                result.extractGlyph(glyph, onlyGeometry=True)

            glyphs = []
            # fill all masters with missing glyphs
            # and collect all glyphs from all masters
            # to send them to optimize contour data
            for sourceDescriptor in self.sources:
                master = self.masters[sourceDescriptor.name]
                hasGlyph = False
                if glyphName in master:
                    # Glyph is present in the master.
                    # This checks for points, components and so on.
                    if not checkGlyphIsEmpty(master[glyphName],
                                             allowWhiteSpace=True):
                        glyphs.append(master[glyphName])
                        hasGlyph = True
                if not hasGlyph:
                    # Get the varlibmodel to generate a filler glyph.
                    # These is probably a support with just a few glyphs.
                    try:
                        self.useVarlib = True
                        mutator = self.getGlyphMutator(glyphName)
                        if mutator is None:
                            self.useVarlib = False
                            mutator = self.getGlyphMutator(glyphName)
                            self.useVarlib = True
                        # generate an instance
                        result = mutator.makeInstance(
                            Location(sourceDescriptor.location))
                    except Exception as e:
                        print("Problem in %s" % glyphName)
                        print("\n".join(self.problems))
                        raise e
                    # round if necessary
                    if self.roundGeometry:
                        result.round()
                    self.generateReport.write(
                        "Adding missing glyph '%s' in master '%s %s (%s)'" %
                        (glyphName, master.font.info.familyName,
                         master.font.info.styleName, master.name))
                    # add the glyph to the master
                    master.newGlyph(glyphName)
                    result.extractGlyph(master[glyphName], onlyGeometry=True)
                    glyphs.append(master[glyphName])
            # optimize glyph contour data from all masters
            self.makeGlyphOutlinesCompatible(glyphs)
        if getDefault("Batch.Debug", False):
            for k, m in self.masters.items():
                tempPath = os.path.join(
                    os.path.dirname(m.font.path),
                    "%s_%s" % (k, os.path.basename(m.font.path)))
                m.font.save(tempPath)

        if self.compileGlyphOrder is None:
            self.compileGlyphOrder = defaultMaster.font.lib.get(
                "public.glyphOrder", [])
            for glyphName in sorted(glyphNames):
                if glyphName not in self.compileGlyphOrder:
                    self.compileGlyphOrder.append(glyphName)

        self.generateReport.dedent()
        self.generateReport.newLine()
except ImportError:
    hasMojo = False

try:
    CurrentFont
except NameError:

    class CurrentFont(dict):
        def save(self, path=None):
            pass


f = CurrentFont()

if hasMojo:
    glyphViewRoundValues = getDefault("glyphViewRoundValues")
    setDefault("glyphViewRoundValues", 0)

for g in f:
    g.leftMargin = 0
    g.rightMargin = 0
    n = g.naked()
    d = g.getLayer("union")
    d.clear()
    d.appendGlyph(g)
    d.removeOverlap(round=0)

    if len(g) > 1:
        for method in "xor", "difference", "intersection":
            d = g.getLayer(method)
            d.clear()
except ImportError:
    hasMojo = False

try:
    CurrentFont
except NameError:
    class CurrentFont(dict):

        def save(self, path=None):
            pass


f = CurrentFont()

if hasMojo:
    glyphViewRoundValues = getDefault("glyphViewRoundValues")
    setDefault("glyphViewRoundValues", 0)


for g in f:
    g.leftMargin = 0
    g.rightMargin = 0
    n = g.naked()
    d = g.getLayer("union")
    d.clear()
    d.appendGlyph(g)
    d.removeOverlap(round=0)

    if len(g) > 1:
        for method in "xor", "difference", "intersection":
            d = g.getLayer(method)
def getColor():
    curColor = getDefault("glyphViewLocalGuidesColor")
    curColor = map(lambda x: x - 0.001 if x > 0.5 else x + 0.001, curColor)
    return tuple(curColor)
Esempio n. 18
0
    def drawGlyphsInGroup(self, notification):
        '''Display all glyphs belonging to the same spacing group in the background.'''

        glyph = notification['glyph']

        font = glyph.font
        if font is None:
            return

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

        if not notification['selected']:
            return

        S = CurrentSpaceCenter()
        if not S:
            return

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

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

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

        # draw glyph and siblings
        R, G, B, A = getDefault(
            "spaceCenterGlyphColor") if not inverse else getDefault(
                "spaceCenterBackgroundColor")
        alpha = (1.0 / len(siblings) + self.opacity) / 2
        stroke(None)
        for glyphName in siblings:
            if glyphName not in glyph.layer:
                continue
            g = font[glyphName].getLayer(glyph.layer.name)
            save()
            if self.side == 'right':
                dx = glyph.width - g.width
                translate(dx, 0)
            color = (R, G, B, 0.4) if glyphName == glyph.name else (R, G, B,
                                                                    alpha)
            fill(*color)
            drawGlyph(g)
            restore()
Esempio n. 19
0
    def _generateVariationFont(self, outPutPath):
        """
        Generate a variation font.
        """
        dirname = os.path.dirname(outPutPath)
        # fontCompiler settings
        options = FontCompilerOptions()
        options.saveFDKPartsNextToUFO = getDefault("saveFDKPartsNextToUFO")
        options.shouldDecomposeWithCheckOutlines = False
        options.generateCheckComponentMatrix = True
        options.defaultDrawingSegmentType = "qcurve"
        options.format = "ttf"
        options.decompose = False
        options.checkOutlines = False
        options.autohint = self.compileSettingAutohint
        options.releaseMode = self.compileSettingReleaseMode
        options.glyphOrder = self.compileGlyphOrder
        options.useMacRoman = False
        options.fdk = CurrentFDK()
        options.generateFeaturesWithFontTools = True

        self.generateReport.newLine()
        self.generateReport.writeTitle("Generate TTF", "'")
        self.generateReport.indent()
        # map all master ufo paths to generated binaries
        masterBinaryPaths = VarLibMasterFinder()
        masterCount = 0
        for sourceDescriptor in self.sources:
            master = self.masters[sourceDescriptor.name]
            # get the output path
            outputPath = os.path.join(
                dirname, "temp_%02d_%s-%s-%s.ttf" %
                (masterCount, master.font.info.familyName,
                 master.font.info.styleName, master.name))
            masterBinaryPaths[sourceDescriptor.path] = outputPath
            self._generatedFiles.add(outputPath)
            masterCount += 1
            # set the output path
            options.outputPath = outputPath
            options.layerName = master.name
            try:
                # generate the font
                result = generateFont(master.font, options=options)
                if getDefault("Batch.Debug", False):
                    tempSavePath = os.path.join(
                        dirname, "temp_%s-%s-%s.ufo" %
                        (master.font.info.familyName,
                         master.font.info.styleName, master.name))
                    font = master.font
                    font.save(tempSavePath)
                    if font.layers.defaultLayer.name != master.name:
                        tempFont = defcon.Font(tempSavePath)
                        tempFont.layers.defaultLayer = tempFont.layers[
                            master.name]
                        tempFont.save()
            except Exception:
                import traceback
                result = traceback.format_exc()
                print(result)
            self.generateReport.newLine()
            self.generateReport.write(
                "Generate %s %s (%s)" %
                (master.font.info.familyName, master.font.info.styleName,
                 master.name))
            self.generateReport.indent()
            self.generateReport.write(result)
            self.generateReport.dedent()
        self.generateReport.dedent()
        # optimize the design space for varlib
        designSpacePath = os.path.join(os.path.dirname(self.path),
                                       "temp_%s" % os.path.basename(self.path))
        self.write(designSpacePath)
        self._generatedFiles.add(designSpacePath)
        try:
            # let varLib build the variation font
            varFont, _, _ = varLib.build(designSpacePath,
                                         master_finder=masterBinaryPaths)
            # save the variation font
            varFont.save(outPutPath)
        except Exception:
            if getDefault("Batch.Debug", False):
                print("masterBinaryPaths:", masterBinaryPaths)
            import traceback
            result = traceback.format_exc()
            print(result)
class ShowGlyphPalette:
    bcColor = AppKit.NSColor.grayColor()
    fill = getDefault("glyphViewFillColor")
    attr = {
        AppKit.NSFontAttributeName:
        AppKit.NSFont.fontWithName_size_("Monaco", 8.0),
        AppKit.NSForegroundColorAttributeName: AppKit.NSColor.whiteColor()
    }
    key = "com.rafalbuchner.ShowGlyphPalette"

    def __init__(self):
        # self.debugW = vanilla.FloatingWindow((0,0,100,100))
        # self.debugW.bind("close",self.closingDebug)
        # self.debugW.open()
        self.isCursorAbove = False
        self.glyphList = []
        self.methodsToDraw = []
        self.methodsToDrawBackground = []
        self.itemsCallbacks = {
            'show related cluster': self.showGlyphsWithCurrentCB,
            'show related in back': self.showRelatedInBackCB
        }
        self.items = {
            'show related cluster': True,
            'show related in back': True
        }
        self.showOnCursorGlyphInSpaceCenterItem = {
            'show this glyph in SC': self.showThisGlyphInSC_CB
        }
        self.activeContextualOptions = []
        self.loadSettings()
        addObserver(self, "glyphAdditionContextualMenuItemsCB",
                    "glyphAdditionContextualMenuItems")
        addObserver(self, "currentGlyphChangedCB", "currentGlyphChanged")
        addObserver(self, "drawCB", "draw")
        addObserver(self, "drawBackgroundCB", "drawBackground")
        addObserver(self, "mouseMovedCB", "mouseMoved")
        addObserver(self, "mouseDownCB", "mouseDown")

    # def closingDebug(self, info):
    #     removeObserver(self, "glyphAdditionContextualMenuItems")
    #     removeObserver(self, "currentGlyphChanged")
    #     removeObserver(self, "draw")
    #     removeObserver(self, "drawBackground")
    #     removeObserver(self, "mouseMoved")
    #     removeObserver(self, "mouseDown")
    def showThisGlyphInSC_CB(self, sender):
        if CurrentSpaceCenter() is None:
            spaceCenter = OpenSpaceCenter(self.glyph.font)
            spaceCenter.set([self.glyphBelowName])
        else:
            scGlyphs = CurrentSpaceCenter().get()
            spaceCenter = OpenSpaceCenter(self.glyph.font)
            spaceCenter.set(scGlyphs + [self.glyphBelowName])

    def drawRelatedGlyphWindow(self, cursor, glyph, view):
        view._drawTextInRect(f"name:  {glyph.name}\nwidth: {glyph.width}",
                             self.attr,
                             cursor,
                             yOffset=10,
                             xOffset=10,
                             drawBackground=True,
                             position="left",
                             backgroundColor=self.bcColor)

    def mouseDownCB(self, point):
        if point['clickCount'] == 2:
            if self.isCursorAbove:
                CurrentGlyphWindow().setGlyphByName(self.glyphBelowName)

    def mouseMovedCB(self, info):

        self.cursor = (info['point'].x, info['point'].y)
        self.isCursorAbove = False
        self.activeContextualOptions = []
        if self.items['show related cluster']:
            for glyphRepr in self.glyphList:
                if glyphRepr.isInside(self.cursor):
                    self.view = info["view"]
                    self.glyphBelowName = glyphRepr.name
                    self.isCursorAbove = True
                    self.activeContextualOptions += list(
                        self.showOnCursorGlyphInSpaceCenterItem.items())

                    break
            UpdateCurrentGlyphView()

    def currentGlyphChangedCB(self, sender):
        self.glyph = CurrentGlyph()
        if self.glyph is None:
            return
        font = self.glyph.font
        self.glyphList = []
        self.clusterWidth = []
        for glyph in font:
            try:
                for component in glyph.components:
                    if component.baseGlyph == self.glyph.name:
                        # glyphRepr = GlyphRepr(font[glyph.name],origin=(self.glyph.width/2, self.glyph.bounds[-1]/2),fillColor=self.fill,shift=None,offset=font.info.ascender*1)
                        glyphRepr = GlyphRepr(font[glyph.name],
                                              origin=(0, 0),
                                              fillColor=self.fill,
                                              shift=None,
                                              offset=font.info.ascender * 1)

                        self.glyphList += [glyphRepr]

                        self.clusterWidth += [font[glyph.name].width]

            except:
                print(f"glyph <{glyph.name}> contains corrupted component")
        if len(self.glyphList) > 0:
            scale = 3 / len(self.glyphList)
            # for i, glyphRepr in enumerate(self.glyphList):
            #         glyphRepr.origin = (self.glyph.width/2-sum(self.clusterWidth)*scale/2+sum(self.clusterWidth[:i])*scale,
            #                             self.glyph.bounds[-1]/2)
            if scale > .4:
                scale = .4
            for glyph in self.glyphList:
                glyph.scale = scale

    @staticmethod
    def appendMethodToDrawingMethods(sender, method, drawingMethods):
        if sender.get() == 1:
            if method not in drawingMethods:
                drawingMethods.append(method)
                UpdateCurrentGlyphView()
        else:
            if method in drawingMethods:
                drawingMethods.remove(method)
                UpdateCurrentGlyphView()

    def showRelatedInBackCB(self, sender):
        ShowGlyphPalette.appendMethodToDrawingMethods(
            sender, self.showRelatedInBackDraw, self.methodsToDrawBackground)

    def showGlyphsWithCurrentCB(self, sender):
        ShowGlyphPalette.appendMethodToDrawingMethods(
            sender, self.showGlyphsWithCurrentDraw,
            self.methodsToDrawBackground)

    def showRelatedInBackDraw(self, scale):
        for glyphRepr in self.glyphList:
            glyph = self.glyph.font[glyphRepr.name]

            shift = (self.glyph.width - glyph.width) / 2
            glyphReprBack = GlyphRepr(glyph,
                                      origin=(shift, 0),
                                      fillColor=self.fill,
                                      shift=shift,
                                      offset=0)
            stroke(None)
            glyphReprBack.draw()

    def drawPreviewCB(self, scale):
        pass

    def showGlyphsWithCurrentDraw(self, scale):
        def _getGlyphWidth(glyphRepr):
            return glyphRepr.glyph.width

        if self.glyphList:

            # for i, glyphRepr in enumerate(self.glyphList):
            for i, glyphRepr in enumerate(self.glyphList):
                glyphRepr.origin = (
                    self.glyph.width / 2 -
                    sum(self.clusterWidth) * glyphRepr.scale / 2 +
                    sum(self.clusterWidth[:i]) * glyphRepr.scale,
                    self.glyph.bounds[-1] / 2)
                stroke(None)

                glyphRepr.draw()

    def drawCB(self, scale):
        if self.isCursorAbove:
            glyph = self.glyph.font[self.glyphBelowName]
            self.drawRelatedGlyphWindow(self.cursor, glyph, self.view)

        for method in self.methodsToDraw:
            method(scale)

    def drawBackgroundCB(self, scale):
        for method in self.methodsToDrawBackground:
            method(scale)

    def loadSettings(self):
        # loading settings
        zero_settings = self.items
        settings = getExtensionDefault(self.key, fallback=self.items)
        if settings:
            for name in settings:
                self.items[name] = settings[name]
            else:
                self.items = zero_settings
        else:
            self.items = zero_settings

    def savingSettings(self):
        setExtensionDefault(self.key, self.items)

    def glyphAdditionContextualMenuItemsCB(self, info):
        menuItems = info['additionContextualMenuItems']
        showGlyphPaletteMenuItems = self.activeContextualOptions
        tool = info["tool"]
        if isinstance(tool, EditingTool):

            for title in self.items:
                temp = title.replace(" ", "_")
                value = self.items[title]
                checkboxMenuItem = AppKit.NSMenuItem.alloc(
                ).initWithTitle_action_keyEquivalent_("regular", '', '')
                MenuTextAttributes = {
                    AppKit.NSFontAttributeName:
                    AppKit.NSFont.menuFontOfSize_(14)
                }
                t = AppKit.NSAttributedString.alloc(
                ).initWithString_attributes_(title, MenuTextAttributes)
                checkbox = vanilla.CheckBox((0, 0, 100, 22),
                                            t,
                                            value=value,
                                            callback=self.checkboxCallback)
                setattr(self, temp, checkbox)
                view = checkbox.getNSButton()
                view.setFrame_(((0, 0), (200, 30)))
                checkboxMenuItem.setView_(view)
                showGlyphPaletteMenuItems += [checkboxMenuItem]
                cb = self.itemsCallbacks.get(title)
                if cb is not None:
                    cb(checkbox)

            showGlyphPaletteMenuItems += [
                ("print out palette", self.printOutClusterCallback),
                ("open palette in SpaceCenter",
                 self.openClusterInSpaceCenterCallback),
                ("select palette in font", self.selectClusterCallback)
            ]
        menuItems += [("Glyph Palette", showGlyphPaletteMenuItems)]

    def selectClusterCallback(self, sender):
        self.glyph.font.selectedGlyphNames = [
            glyph.glyph.name for glyph in self.glyphList
        ]

    def openClusterInSpaceCenterCallback(self, sender):
        spaceCenter = OpenSpaceCenter(self.glyph.font)
        spaceCenter.set([glyph.glyph.name for glyph in self.glyphList])

    def printOutClusterCallback(self, sender):
        print([glyph.glyph.name for glyph in self.glyphList])

    def checkboxCallback(self, obj):
        value = False
        if obj.get() == 1:
            value = True
        self.items[obj.getTitle()] = value
        self.savingSettings()

        self.itemsCallbacks[obj.getTitle()](obj)
    def _generateVariationFont(self, outPutPath):
        """
        Generate a variation font.
        """
        dirname = os.path.dirname(outPutPath)
        # fontCompiler settings
        options = FontCompilerOptions()
        options.saveFDKPartsNextToUFO = getDefault("saveFDKPartsNextToUFO")
        options.shouldDecomposeWithCheckOutlines = False
        options.generateCheckComponentMatrix = True
        options.defaultDrawingSegmentType = "qcurve"
        options.format = "ttf"
        options.decompose = False
        options.checkOutlines = False
        options.autohint = self.compileSettingAutohint
        options.releaseMode = self.compileSettingReleaseMode
        options.glyphOrder = self.compileGlyphOrder
        options.useMacRoman = False
        options.fdk = CurrentFDK()
        options.generateFeaturesWithFontTools = False

        self.generateReport.newLine()
        self.generateReport.writeTitle("Generate TTF", "'")
        self.generateReport.indent()
        # map all master ufo paths to generated binaries
        masterBinaryPaths = VarLibMasterFinder()
        masterCount = 0
        for sourceDescriptor in self.sources:
            master = self.masters[sourceDescriptor.name]
            # get the output path
            outputPath = os.path.join(dirname, "temp_%02d_%s-%s-%s.ttf" % (masterCount, master.font.info.familyName, master.font.info.styleName, master.name))
            masterBinaryPaths[sourceDescriptor.path] = outputPath
            self._generatedFiles.add(outputPath)
            masterCount += 1
            # set the output path
            options.outputPath = outputPath
            options.layerName = master.name
            try:
                # generate the font
                result = generateFont(master.font, options=options)
                if getDefault("Batch.Debug", False):
                    tempSavePath = os.path.join(dirname, "temp_%s-%s-%s.ufo" % (master.font.info.familyName, master.font.info.styleName, master.name))
                    font = master.font
                    font.save(tempSavePath)
                    if font.layers.defaultLayer.name != master.name:
                        tempFont = defcon.Font(tempSavePath)
                        tempFont.layers.defaultLayer = tempFont.layers[master.name]
                        tempFont.save()
                        self._generatedFiles.add(tempSavePath)
            except Exception:
                import traceback
                result = traceback.format_exc()
                print(result)
            self.generateReport.newLine()
            self.generateReport.write("Generate %s %s (%s)" % (master.font.info.familyName, master.font.info.styleName, master.name))
            self.generateReport.indent()
            self.generateReport.write(result)
            self.generateReport.dedent()
        self.generateReport.dedent()
        # optimize the design space for varlib
        designSpacePath = os.path.join(os.path.dirname(self.path), "temp_%s" % os.path.basename(self.path))
        self.write(designSpacePath)
        self._generatedFiles.add(designSpacePath)
        try:
            # let varLib build the variation font
            varFont, _, _ = varLib.build(designSpacePath, master_finder=masterBinaryPaths)
            # save the variation font
            varFont.save(outPutPath)
        except Exception:
            import traceback
            result = traceback.format_exc()
            print(result)
    def makeMasterGlyphsCompatible(self):
        """
        Update all masters with missing glyphs.
        All Masters must have the same glyphs.
        """
        self.generateReport.writeTitle("Making master glyphs compatible", "'")
        self.generateReport.indent()
        # collect all possible glyph names
        glyphNames = []
        for master in self.masters.values():
            glyphNames.extend(master.keys())
        glyphNames = set(glyphNames)
        # get the default master
        defaultMaster = self.masters[self.default.name]
        # loop over all glyphName
        for glyphName in glyphNames:
            # first check if the default master has this glyph
            if glyphName not in defaultMaster:
                # the default does not have the glyph
                # build a repair mutator to generate a glyph.
                glyphItems = []
                for sourceDescriptor in self.sources:
                    master = self.masters[sourceDescriptor.name]
                    if glyphName in sourceDescriptor.mutedGlyphNames:
                        continue
                    if glyphName in master:
                        sourceGlyph = self.mathGlyphClass(master[glyphName])
                        sourceGlyphLocation = self.locations[sourceDescriptor.name]
                        glyphItems.append((sourceGlyphLocation, sourceGlyph))
                # Note: this needs to be a mutatormath mutator, not a varlib.model.
                # A varlib model can't work without in the missing default.
                # Filling in the default is a bit of a hack: it will make the font work,
                # but it is a bit of a guess.
                _, mutator = buildMutator(glyphItems)
                # use the repair mutator to generate an instance at the default location
                result = mutator.makeInstance(Location(self.defaultLoc))
                # round if necessary
                if self.roundGeometry:
                    result.round()
                self.generateReport.write("Adding missing glyph '%s' in the default master '%s %s (%s)'" % (glyphName, defaultMaster.font.info.familyName, defaultMaster.font.info.styleName, defaultMaster.name))
                # add the glyph to the default master
                defaultMaster.newGlyph(glyphName)
                glyph = defaultMaster[glyphName]
                result.extractGlyph(glyph, onlyGeometry=True)

            glyphs = []
            # fill all masters with missing glyphs
            # and collect all glyphs from all masters
            # to send them to optimize contour data
            for sourceDescriptor in self.sources:
                master = self.masters[sourceDescriptor.name]
                hasGlyph = False
                if glyphName in master:
                    # Glyph is present in the master.
                    # This checks for points, components and so on.
                    if not checkGlyphIsEmpty(master[glyphName], allowWhiteSpace=True):
                        glyphs.append(master[glyphName])
                        hasGlyph = True
                if not hasGlyph:
                    # Get the varlibmodel to generate a filler glyph.
                    # These is probably a support with just a few glyphs.
                    try:
                        self.useVarlib = True
                        mutator = self.getGlyphMutator(glyphName)
                        if mutator is None:
                            self.useVarlib = False
                            mutator = self.getGlyphMutator(glyphName)
                            self.useVarlib = True
                        # generate an instance
                        result = mutator.makeInstance(Location(sourceDescriptor.location))
                    except Exception as e:
                        print("Problem in %s" % glyphName)
                        print("\n".join(self.problems))
                        raise e
                    # round if necessary
                    if self.roundGeometry:
                        result.round()
                    self.generateReport.write("Adding missing glyph '%s' in master '%s %s (%s)'" % (glyphName, master.font.info.familyName, master.font.info.styleName, master.name))
                    # add the glyph to the master
                    master.newGlyph(glyphName)
                    result.extractGlyph(master[glyphName], onlyGeometry=True)
                    glyphs.append(master[glyphName])
            # optimize glyph contour data from all masters
            self.makeGlyphOutlinesCompatible(glyphs)
        if getDefault("Batch.Debug", False):
            for k, m in self.masters.items():
                tempPath = m.font.path.replace(".ufo", "_%s.ufo" % k)
                m.font.save(tempPath)

        if self.compileGlyphOrder is None:
            self.compileGlyphOrder = defaultMaster.font.lib.get("public.glyphOrder", [])
            for glyphName in sorted(glyphNames):
                if glyphName not in self.compileGlyphOrder:
                    self.compileGlyphOrder.append(glyphName)

        self.generateReport.dedent()
        self.generateReport.newLine()