Example #1
0
class ToucheTool():
    def __init__(self):
        NSUserDefaults.standardUserDefaults().registerDefaults_(
            {"ToucheWindowHeight": 340})
        self.windowHeight = NSUserDefaults.standardUserDefaults(
        ).integerForKey_("ToucheWindowHeight")
        self.minWindowHeight = 340
        if self.windowHeight < self.minWindowHeight:
            self.windowHeight = self.minWindowHeight
        self.closedWindowHeight = 100
        self.w = FloatingWindow((180, self.windowHeight),
                                'Touché!',
                                minSize=(180, 340),
                                maxSize=(250, 898))
        self.w.bind("resize", self.windowResized_)
        self.isResizing = False
        p = 10
        w = 160

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

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

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

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

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

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

    # callbacks

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

      finally:
        g.endUndo()

    # end for g in font

    # sync layers that use metricsKey
    if len(syncLayers) > 0:
      print("Syncing LSB & RSB for %r layers..." % len(syncLayers))
      for layer in syncLayers:
        layer.syncMetrics()
    print("Done")
Example #5
0
class TestMetrics(BaseWindowController):

    testOptions = [
        'Complete report', 'Vertical alignments', 'Accented letters',
        'Interpunction', 'Figures', 'Dnom Inf Num Sup', 'Fractions',
        'Flipped Margins', 'LC ligatures', 'UC ligatures', 'LC extra',
        'UC extra'
    ]

    testOptionsAbbr = [
        'completeReport', 'verticalAlignments', 'accented', 'interpunction',
        'figures', 'dnomInfNumSup', 'fractions', 'flippedMargins', 'LCliga',
        'UCliga', 'LCextra', 'UCextra'
    ]

    testFunctions = {
        'Complete report': [
            checkVerticalExtremes, checkAccented, checkInterpunction,
            checkFigures, checkDnomNumrSubsSups, checkFractions,
            checkLCligatures, checkUCligatures, checkLCextra, checkUCextra,
            checkFlippedMargins
        ],
        'Vertical alignments': [checkVerticalExtremes],
        'Accented letters': [checkAccented],
        'Interpunction': [checkInterpunction],
        'Figures': [checkFigures],
        'Dnom Inf Num Sup': [checkDnomNumrSubsSups],
        'Fractions': [checkFractions],
        'Flipped Margins': [checkFlippedMargins],
        'LC ligatures': [checkLCligatures],
        'UC ligatures': [checkUCligatures],
        'LC extra': [checkLCextra],
        'UC extra': [checkUCextra]
    }

    chosenTest = testOptions[0]

    showMissingGlyph = False
    fontOptions = None
    chosenFont = None

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

        # load data
        self.fontOptions = AllFonts()
        if self.fontOptions:
            self.chosenFont = self.fontOptions[0]

        # init win
        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT),
                                "Metrics Tester")

        # target pop up
        jumpingY = MARGIN_TOP
        self.w.targetPopUp = PopUpButton(
            (MARGIN_LFT, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']), [
                 '{} {}'.format(font.info.familyName, font.info.styleName)
                 for font in self.fontOptions
             ],
            callback=self.targetPopUpCallback)

        # test choice
        jumpingY += MARGIN_HALFROW + vanillaControlsSize[
            'PopUpButtonRegularHeight']
        self.w.testChoice = PopUpButton(
            (MARGIN_LFT, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.testOptions,
            callback=self.testChoiceCallback)

        # separation line
        jumpingY += vanillaControlsSize[
            'PopUpButtonRegularHeight'] + MARGIN_HALFROW
        # self.w.separationLine = HorizontalLine((MARGIN_LFT, jumpingY, NET_WIDTH, 1))

        # jumpingY += RADIO_GROUP_HEIGHT + MARGIN_ROW
        self.w.missingGlyphCheck = CheckBox(
            (MARGIN_LFT, jumpingY, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight']),
            'Show missing glyphs',
            callback=self.missingGlyphCheckCallback)

        jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW
        self.w.runButton = Button((MARGIN_LFT, jumpingY, NET_WIDTH,
                                   vanillaControlsSize['ButtonRegularHeight']),
                                  "Run Tests",
                                  callback=self.runButtonCallback)

        # adjust height
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_BTM
        self.w.setPosSize((0, 0, PLUGIN_WIDTH, jumpingY))

        # observers
        addObserver(self, "updateFontList", "fontWillClose")
        addObserver(self, "updateFontList", "fontDidOpen")
        addObserver(self, "updateFontList", "newFontDidOpen")
        self.setUpBaseWindowBehavior()

        # open win
        self.w.open()

    def windowCloseCallback(self, sender):
        removeObserver(self, "fontWillClose")
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "newFontDidOpen")
        super(TestMetrics, self).windowCloseCallback(sender)

    def updateFontList(self, sender):
        self.fontOptions = AllFonts()
        self.w.targetPopUp.setItems([
            '{} {}'.format(font.info.familyName, font.info.styleName)
            for font in self.fontOptions
        ])

        if self.fontOptions:
            self.chosenFont = self.fontOptions[0]
        else:
            self.chosenFont = None

    def targetPopUpCallback(self, sender):
        self.chosenFont = self.fontOptions[sender.get()]
        assert isinstance(self.chosenFont,
                          RFont), '{0!r} is not a RFont instance'.format(
                              self.chosenFont)

    def testChoiceCallback(self, sender):
        self.chosenTest = self.testOptions[sender.get()]

    def missingGlyphCheckCallback(self, sender):
        self.showMissingGlyph = bool(sender.get())

    def runButtonCallback(self, sender):

        testsToRun = self.testFunctions[self.chosenTest]
        rightNow = datetime.now()

        fileNameProposal = '{}{:0>2d}{:0>2d}_{}_{}.txt'.format(
            rightNow.year, rightNow.month, rightNow.day,
            os.path.basename(self.chosenFont.path)[:-4],
            self.testOptionsAbbr[self.testOptions.index(self.chosenTest)])

        progressWindow = self.startProgress('Running Tests')
        with open(PutFile('Choose where to save the report', fileNameProposal),
                  'w') as reportFile:
            for eachFunc in testsToRun:
                errorLines, missingGlyphs = eachFunc(self.chosenFont)
                report = convertLinesToString(errorLines, missingGlyphs,
                                              self.showMissingGlyph)
                reportFile.write(report)
        progressWindow.close()
Example #6
0
class CopyAndMove(object):

    isOffsetAllowed = False
    verticalOffset = 0

    def __init__(self):
        self.transferList = NAME_2_GLYPHLIST[GLYPHLISTS_OPTIONS[0]]
        self.target = TARGET_OPTIONS[0]

        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, 400), PLUGIN_TITLE)
        jumpingY = MARGIN_VER

        # target fonts
        self.w.targetFontsPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            TARGET_OPTIONS,
            callback=self.targetFontsPopUpCallback)
        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER

        # transfer lists pop up
        self.w.glyphListPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            GLYPHLISTS_OPTIONS,
            callback=self.glyphListPopUpCallback)
        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER

        # offset caption
        self.w.offsetCaption = TextBox(
            (MARGIN_HOR, jumpingY + 2, NET_WIDTH * .22,
             vanillaControlsSize['TextBoxRegularHeight']), 'Offset:')

        # offset edit text
        self.w.offsetEdit = EditText(
            (MARGIN_HOR + NET_WIDTH * .25, jumpingY, NET_WIDTH * .35,
             vanillaControlsSize['EditTextRegularHeight']),
            continuous=False,
            callback=self.offsetEditCallback)
        self.w.offsetEdit.set('%d' % self.verticalOffset)
        jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER

        # clean button
        self.w.cleanButton = Button(
            (MARGIN_HOR, jumpingY, NET_WIDTH * .45,
             vanillaControlsSize['ButtonRegularHeight']),
            'Clean',
            callback=self.cleanButtonCallback)

        # run button
        self.w.runButton = Button(
            (MARGIN_HOR + NET_WIDTH * .55, jumpingY, NET_WIDTH * .45,
             vanillaControlsSize['ButtonRegularHeight']),
            'Run!',
            callback=self.runButtonCallback)
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_VER

        self.w.setPosSize((0, 0, PLUGIN_WIDTH, jumpingY))
        self.w.open()

    def targetFontsPopUpCallback(self, sender):
        self.target = TARGET_OPTIONS[sender.get()]

    def glyphListPopUpCallback(self, sender):
        if GLYPHLISTS_OPTIONS[sender.get()] in ACTIVE_OFFSET:
            self.w.offsetEdit.enable(True)
        else:
            self.w.offsetEdit.enable(False)
        self.transferList = NAME_2_GLYPHLIST[GLYPHLISTS_OPTIONS[sender.get()]]
        self.isOffsetAllowed = False

    def offsetEditCallback(self, sender):
        try:
            self.verticalOffset = int(sender.get())
        except ValueError:
            self.w.offsetEdit.set('%d' % self.verticalOffset)

    def cleanButtonCallback(self, sender):
        if self.target == 'All Fonts':
            fontsToProcess = AllFonts()
        else:
            fontsToProcess = [CurrentFont()]

        for eachFont in fontsToProcess:
            for eachTargetName, _ in self.transferList:
                if eachTargetName in eachFont:
                    eachFont[eachTargetName].clear()

    def runButtonCallback(self, sender):
        if self.target == 'All Fonts':
            fontsToProcess = AllFonts()
        else:
            fontsToProcess = [CurrentFont()]

        for eachFont in fontsToProcess:
            for eachRow in self.transferList:

                if len(eachRow) == 2:
                    eachTarget, eachSource = eachRow
                else:
                    eachTarget, eachSource = eachRow[0], eachRow[1:]

                if eachTarget not in eachFont:
                    targetGlyph = eachFont.newGlyph(eachTarget)
                else:
                    targetGlyph = eachFont[eachTarget]
                    targetGlyph.clear()

                if isinstance(eachSource, types.ListType) is False:
                    targetGlyph.width = eachFont[eachSource].width

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

                    tangentOffset = tan(anchorAngle) * self.verticalOffset
                    targetGlyph.appendComponent(
                        eachSource, (tangentOffset, self.verticalOffset))
                else:
                    offsetX = 0
                    for eachSourceGlyphName in eachSource:
                        targetGlyph.appendComponent(eachSourceGlyphName,
                                                    (offsetX, 0))
                        offsetX += eachFont[eachSourceGlyphName].width
                    targetGlyph.width = offsetX
Example #7
0
class GridFitter(object):

    bcpHandlesFit = False

    def __init__(self):
        self.fontTarget = FONT_TARGET_OPTIONS[0]
        self.glyphTarget = GLYPH_TARGET_OPTIONS[0]
        self.gridSize = 4

        self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, 400), PLUGIN_TITLE)
        jumpingY = MARGIN_VER

        # font target
        self.w.fontTargetPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            FONT_TARGET_OPTIONS,
            callback=self.fontTargetPopUpCallback)
        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER

        # glyph target
        self.w.glyphTargetPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            GLYPH_TARGET_OPTIONS,
            callback=self.glyphTargetPopUpCallback)
        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER

        # grid size captions
        self.w.gridSizeCaption = TextBox(
            (MARGIN_HOR, jumpingY + 2, NET_WIDTH * .32,
             vanillaControlsSize['TextBoxRegularHeight']), 'Grid Size:')

        # grid size edit
        self.w.gridSizeEdit = EditText(
            (MARGIN_HOR + NET_WIDTH * .32, jumpingY, NET_WIDTH * .3,
             vanillaControlsSize['EditTextRegularHeight']),
            text='{:d}'.format(self.gridSize),
            callback=self.gridSizeEditCallback)
        jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER

        self.w.moveBcpHandlesCheck = CheckBox(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['CheckBoxRegularHeight']),
            'move bcp handles',
            value=self.bcpHandlesFit,
            callback=self.moveBcpHandlesCheckCallback)
        jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_VER

        # fit button
        self.w.fitButton = Button((MARGIN_HOR, jumpingY, NET_WIDTH,
                                   vanillaControlsSize['ButtonRegularHeight']),
                                  'Fit!',
                                  callback=self.fitButtonCallback)
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_VER

        self.w.setPosSize((0, 0, PLUGIN_WIDTH, jumpingY))
        self.w.open()

    def fontTargetPopUpCallback(self, sender):
        self.fontTarget = FONT_TARGET_OPTIONS[sender.get()]

    def glyphTargetPopUpCallback(self, sender):
        self.glyphTarget = GLYPH_TARGET_OPTIONS[sender.get()]

    def gridSizeEditCallback(self, sender):
        try:
            self.gridSize = int(sender.get())
        except ValueError:
            self.w.gridSizeEdit.set('{:d}'.format(self.gridSize))

    def moveBcpHandlesCheckCallback(self, sender):
        self.bcpHandlesFit = bool(sender.get())

    def fitButtonCallback(self, sender):
        if self.fontTarget == 'All Fonts':
            fontsToProcess = AllFonts()
        else:
            fontsToProcess = [CurrentFont()]

        for eachFont in fontsToProcess:
            if self.glyphTarget == 'All Glyphs':
                glyphNamesToProcess = eachFont.glyphOrder
            elif self.glyphTarget == 'Selection':
                glyphNamesToProcess = CurrentFont().selection
            else:
                glyphNamesToProcess = [CurrentGlyph().name]

            for eachName in glyphNamesToProcess:
                eachGlyph = eachFont[eachName]
                gridFit(eachGlyph,
                        self.gridSize,
                        bcpHandlesFit=self.bcpHandlesFit)
Example #8
0
class BroadNib(object):

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

    pluginWidth = 120
    pluginHeight = 300

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

    netWidth = pluginWidth - marginLft - marginRgt

    def __init__(self):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # opening window
        self.win.open()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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