class Preview:
    def __init__(self):
        ## create a window
        self.w = Window((400, 400), "Preview", minSize=(100, 100))

        ## add a GlyphPreview to the window
        self.w.preview = GlyphPreview((0, 0, -0, -0))

        ## set the currentGlyph
        self.setGlyph(CurrentGlyph())

        ## add an observer when the glyph changed in the glyph view
        addObserver(self, "_currentGlyphChanged", "currentGlyphChanged")

        ## bind a windows close callback to this object
        self.w.bind("close", self.windowClose)
        ## open the window
        self.w.open()

    def _currentGlyphChanged(self, info):
        ## notification callback when the glyph changed in the glyph view
        ## just set the new glyph in the glyph preview
        self.setGlyph(CurrentGlyph())

    def setGlyph(self, glyph):
        ## setting the glyph in the glyph Preview
        self.w.preview.setGlyph(glyph)

    def windowClose(self, sender):
        ## remove the observer if the window closes
        removeObserver(self, "_currentGlyphChanged")
Пример #2
0
 def openWindow(self, document):
     u"""
     Controls and paper view for a document.
     """
     self.windowSize = (document.width, document.height)
     w = Window(self.windowSize, minSize=(1, 1), maxSize=self.windowSize, closable=False)
     self.setPaper(w, document)
     self.setStatusBar(w, document)
     w.bind("should close", self.windowShouldCloseCallback)
     w.bind("close", self.windowCloseCallback)
     w.open()
     return w
Пример #3
0
class DBFinalTool:
    def __init__(self):
        self.glyph = None # Typical RoboFont function
        self.updating = False
        
        pad = 10
        leading = 32
        y = pad
        w = 300
        h = 400
        buttonWidth = 100
        buttonHeight = 48
        
        self.w = Window((100, 100, w, h), 'Final tool window')
                    
        self.w.bind('close', self.windowCloseCallback)
        self.w.open()
        
    def windowCloseCallback(self, sender):
        print('removeObservers')
class PenBallWizardController(object):

    def __init__(self):
        self.filters = PenBallFiltersManager()
        self.filters.loadFiltersFromJSON('/'.join([LOCALPATH, JSONFILE]))
        self.glyphNames = []
        self.observedGlyphs = []
        self.cachedFont = RFont(showUI=False)
        self.currentFont = CurrentFont()
        filtersList = self.filters.keys()
        if len(filtersList) > 0:
            self.currentFilterName = filtersList[0]
        else:
            self.currentFilterName = None
        self.fill = True

        self.observers = [
            ('fontChanged', 'fontBecameCurrent'),
            ('fontChanged', 'fontDidOpen'),
            ('fontChanged', 'fontDidClose'),
        ]

        self.w = Window((100, 100, 800, 500), 'PenBall Wizard v{0}'.format(__version__), minSize=(500, 400))
        self.w.filtersPanel = Group((0, 0, 300, -0))
        self.w.filtersPanel.filtersList = List((0, 0, -0, -40), filtersList, selectionCallback=self.filterSelectionChanged, doubleClickCallback=self.filterEdit, allowsMultipleSelection=False, allowsEmptySelection=False, rowHeight=22)
        self.w.filtersPanel.controls = Group((0, -40, -0, 0))
        self.w.filtersPanel.addFilter = SquareButton((0, -40, 100, 40), 'Add filter', sizeStyle='small', callback=self.addFilter)
        self.w.filtersPanel.addFilterChain = SquareButton((100, -40, 100, 40), 'Add operations', sizeStyle='small', callback=self.addFilterChain)
        self.w.filtersPanel.removeFilter = SquareButton((-100, -40, 100, 40), 'Remove filter', sizeStyle='small', callback=self.removeFilter)
        self.w.textInput = EditText((300, 0, -90, 22), '', callback=self.stringInput)
        self.w.generate = SquareButton((-90, 0, 90, 22), 'Generate', callback=self.generateGlyphsToFont, sizeStyle='small')
        self.w.preview = MultiLineView((300, 22, -0, -0))
        self.w.switchFillStroke = SquareButton((-75, -40, 60, 25), 'Stroke', callback=self.switchFillStroke, sizeStyle='small')
        displayStates = self.w.preview.getDisplayStates()
        for key in ['Show Metrics','Upside Down','Stroke','Beam','Inverse','Water Fall','Multi Line']:
            displayStates[key] = False
        for key in ['Fill','Single Line']:
            displayStates[key] = True
        self.w.preview.setDisplayStates(displayStates)

        for callback, event in self.observers:
            addObserver(self, callback, event)

        self.updateControls()

        self.w.bind('close', self.end)
        self.launchWindow()
        self.w.open()

    def generateGlyphsToFont(self, sender):
        newFont = RFont(showUI=False)
        font = self.currentFont
        filterName = self.currentFilterName
        currentFilter = self.filters[filterName]
        if font is not None:
            glyphs = [font[glyphName] for glyphName in font.selection if glyphName in font]
            for glyph in glyphs:
                if len(glyph.components) > 0:
                    for comp in glyph.components:
                        baseGlyphName = comp.baseGlyph
                        baseGlyph = font[baseGlyphName]
                        baseFilteredGlyph = currentFilter(baseGlyph)
                        newFont.insertGlyph(baseFilteredGlyph, baseGlyphName)
                        newFont[baseGlyphName].unicode = baseFilteredGlyph.unicode
                filteredGlyph = currentFilter(glyph)
                if filteredGlyph is not None:
                    newFont.insertGlyph(filteredGlyph, glyph.name)
            newFont.showUI()

    def generateGlyphsToLayer(self, layerName):
        font = self.currentFont
        filterName = self.currentFilterName
        currentFilter = self.filters[filterName]
        if font is not None:
            glyphs = [font[glyphName] for glyphName in font.selection if glyphName in font]
            for glyph in glyphs:
                if len(glyph.components) == 0:
                    layerGlyph = glyph.getLayer(layerName)
                    filteredGlyph = currentFilter(glyph)
                    if filteredGlyph is not None:
                        layerGlyph.appendGlyph(filteredGlyph)

    def updateFiltersList(self, selectedIndex=0):
        filtersList = self.filters.keys()
        self.w.filtersPanel.filtersList.set(filtersList)
        self.w.filtersPanel.filtersList.setSelection([selectedIndex])

    def setArgumentValue(self, sender):
        value = sender.get()
        valueType = sender.type
        if valueType == 'bool':
            value = bool(value)
        key = sender.name
        if self.currentFilterName is not None:
            self.filters.setArgumentValue(self.currentFilterName, key, value)
            self.resetRepresentations()
        self.updatePreview()

    def resetRepresentations(self):
        font = self.currentFont
        self.cachedFont = RFont(showUI=False)
        if font is not None:
            for glyphName in self.glyphNames:
                if glyphName in font:
                    font[glyphName].naked().destroyAllRepresentations()

    def processGlyphs(self):
        font = self.currentFont
        if font is not None:
            sourceGlyphs = []
            for glyphName in self.glyphNames:
                if glyphName in font:
                    glyph = font[glyphName]
                    if glyph not in self.observedGlyphs:
                        glyph.addObserver(self, 'glyphChanged', 'Glyph.Changed')
                        self.observedGlyphs.append(glyph)
                    sourceGlyphs.append(glyph)
            filterName = self.currentFilterName
            filteredGlyphs = self.filterGlyphs(filterName, sourceGlyphs, self.cachedFont)
            return filteredGlyphs
        return []

    def filterGlyphs(self, filterName, glyphs, font):
        currentFilter = self.filters[filterName]
        filteredGlyphs = []
        for glyph in glyphs:
            if len(glyph.components) > 0:
                for comp in glyph.components:
                    baseGlyphName = comp.baseGlyph
                    baseGlyph = glyph.getParent()[baseGlyphName]
                    baseFilteredGlyph = currentFilter(baseGlyph)
                    if baseFilteredGlyph is not None:
                        font.insertGlyph(baseFilteredGlyph, baseGlyphName)
            filteredGlyph = currentFilter(glyph)
            if filteredGlyph is not None:
                font.insertGlyph(filteredGlyph, glyph.name)
                filteredGlyphs.append(font[glyph.name])
        return filteredGlyphs

    def updatePreview(self, notification=None):
        glyphs = self.processGlyphs()
        self.w.preview.setFont(self.cachedFont)
        self.w.preview.set(glyphs)

    def updateControls(self):
        if self.currentFilterName is not None:
            if hasattr(self.w.filtersPanel, 'controls'):
                delattr(self.w.filtersPanel, 'controls')
            currentFilter = self.filters[self.currentFilterName]
            self.w.filtersPanel.controls = Group((0, 0, 0, 0))
            hasSubfilters = self.filters.hasSubfilters(self.currentFilterName)

            if hasSubfilters:
                argumentBlocks = [(subfilterName, self.filters[subfilterName].arguments) for subfilterName, mode, source in currentFilter.subfilters]
            else:
                argumentBlocks = [(currentFilter.name, currentFilter.arguments)]

            gap = 5
            height = gap
            end = 0
            lineheight = 27
            top = 35
            boxes = 0

            for j, (filterName, arguments)  in enumerate(argumentBlocks):
                if len(arguments) > 0:
                    blockfilterName = '{0}{1}'.format(filterName, j)
                    setattr(self.w.filtersPanel.controls, blockfilterName, Box((5, 5, 0, 0)))
                    block = getattr(self.w.filtersPanel.controls, blockfilterName)
                    if hasSubfilters:
                        _, filterMode, filterSource = currentFilter.subfilters[j]
                        modes = '({0}) [{1}]'.format(filterMode, filterSource)
                        block.modes = TextBox((-150, 11, -8, 12), modes, alignment='right', sizeStyle='mini')
                    block.title = TextBox((8, 8, -150, 22), filterName.upper())
                    start = height
                    boxHeight = top

                    for i, (arg, value) in enumerate(arguments.items()):
                        valueType = None

                        if hasSubfilters:
                            argumentName = currentFilter.joinSubfilterArgumentName(filterName, arg, j)
                            if argumentName in currentFilter.arguments:
                                value = currentFilter.arguments[argumentName]
                            else:
                                value = self.filters[filterName].arguments[argumentName]
                                currentFilter.setArgumentValue(argumentName, value)
                        else:
                            argumentName = arg

                        limits = currentFilter.getLimits(argumentName)
                        if limits is None: limits = (0, 100)

                        if isinstance(value, bool):
                            setattr(block, arg, CheckBox((8, top + (i*lineheight), -8, 22), arg, value=value, callback=self.setArgumentValue, sizeStyle='small'))
                            valueType = 'bool'
                        elif isinstance(value, (str, unicode)):
                            setattr(block, arg, EditText((8, top + (i*lineheight), -8, 22), value, callback=self.setArgumentValue, sizeStyle='small'))
                        elif isinstance(value, (int, float)):
                            parameter = VanillaSingleValueParameter(arg, value, limits=limits)
                            setattr(block, arg, ParameterSliderTextInput(parameter, (8, top + (i*lineheight), -8, 22), title=arg, callback=self.setArgumentValue))

                        control = getattr(block, arg)
                        control.name = argumentName
                        control.type = valueType

                        boxHeight += lineheight

                    boxHeight += 12
                    block.setPosSize((5, start, -5, boxHeight))
                    height += boxHeight + gap

            height += 40

            self.w.filtersPanel.filtersList.setPosSize((0, 0, -0, -height))
            self.w.filtersPanel.controls.setPosSize((0, -height, -0, -45))

    def stringInput(self, sender):
        text = sender.get()
        if self.currentFont is not None:
            cmap = self.currentFont.getCharacterMapping()
            self.glyphNames = splitText(text, cmap)
        else:
            self.glyphNames = []
        self.updatePreview()

    def filterEdit(self, sender):
        filterName = self.currentFilterName
        if self.filters.hasSubfilters(filterName):
            self.buildFilterGroupSheet(filterName)
        else:
            self.buildFilterSheet(filterName)
        self.filterSheet.open()

    # def buildGenerationSheet(self, sender):
    #     self.generationSheet = Sheet((0, 0, 400, 350), self.w)
    #     self.generationSheet.title = TextBox((15, 15, -15, 22), u'Generate selected glyphs to:')

    def buildFilterSheet(self, filterName='', makeNew=False):
        sheetFields = {
            'file': '',
            'module': '',
            'filterObjectName': '',
            'limits': {},
            'arguments': {},
        }
        if filterName != '':
            filterDict = self.filters[filterName].getFilterDict()
            for key in filterDict:
                if key == "arguments":
                    entry = OrderedDict(filterDict[key])
                else:
                    entry = filterDict[key]
                sheetFields[key] = entry

        self.filterSheet = Sheet((0, 0, 400, 350), self.w)
        self.filterSheet.new = makeNew
        self.filterSheet.index = self.filters[filterName].index if not makeNew else -1
        applyTitle = 'Add Filter' if filterName == '' else 'Update Filter'
        self.filterSheet.apply = SquareButton((-115, -37, 100, 22), applyTitle, callback=self.processFilter, sizeStyle='small')
        self.filterSheet.cancel = SquareButton((-205, -37, 80, 22), 'Cancel', callback=self.closeFilterSheet, sizeStyle='small')

        y = 20
        self.filterSheet.nameTitle = TextBox((15, y, 100, 22), 'Filter Name')
        self.filterSheet.name = EditText((125, y, -15, 22), filterName)
        y += 22

        tabs = ['module','file']
        selectedTab = 0 if len(sheetFields['module']) >= len(sheetFields['file']) else 1
        filterObjectName = sheetFields['filterObjectName']

        y += 20
        self.filterSheet.importPath = Tabs((15, y, -15, 75), tabs)
        self.filterSheet.importPath.set(selectedTab)
        modulePathTab = self.filterSheet.importPath[0]
        filePathTab = self.filterSheet.importPath[1]

        modulePathTab.pathInput = EditText((10, 10, -10, -10), sheetFields['module'])
        filePathTab.pathInput = EditText((10, 10, -110, -10), sheetFields['file'])
        filePathTab.fileInput = SquareButton((-100, 10, 90, -10), u'Add File…', sizeStyle='small', callback=self.getFile)
        y += 75

        y += 10
        self.filterSheet.filterObjectTitle = TextBox((15, y, 100, 22), 'Filter Object (pen, function)')
        self.filterSheet.filterObject = EditText((125, y, -15, 22), filterObjectName)
        y += 22

        y += 20
        columns = [
            {'title': 'argument', 'width': 160, 'editable':True},
            {'title': 'value', 'width': 71, 'editable':True},
            {'title': 'min', 'width': 49, 'editable':True},
            {'title': 'max', 'width': 49, 'editable':True}
        ]

        arguments = sheetFields['arguments']
        limits = sheetFields['limits']

        argumentItems = []

        for key, value in arguments.items():
            if isinstance(value, bool):
                value = str(value)
            elif isinstance(value, float):
                value = round(value, 2)
            argItem = {
                'argument': key,
                'value': value
                }
            if key in limits:
                minimum, maximum = sheetFields['limits'][key]
                argItem['min'] = minimum
                argItem['max'] = maximum

            argumentItems.append(argItem)

        buttonSize = 20
        gutter = 7
        self.filterSheet.arguments = List((15 + buttonSize + gutter, y, -15, -52), argumentItems, columnDescriptions=columns, allowsMultipleSelection=False, allowsEmptySelection=False)
        self.filterSheet.addArgument = SquareButton((15, -52-(buttonSize*2)-gutter, buttonSize, buttonSize), '+', sizeStyle='small', callback=self.addArgument)
        self.filterSheet.removeArgument = SquareButton((15, -52-buttonSize, buttonSize, buttonSize), '-', sizeStyle='small', callback=self.removeArgument)
        if len(argumentItems) == 0:
            self.filterSheet.removeArgument.enable(False)

        if filterName == '':
            self.currentFilterName = ''

    def buildFilterGroupSheet(self, filterName='', makeNew=False):

        subfilters = self.filters[filterName].subfilters if filterName in self.filters else []
        subfilterItems = [{'filterName': subfilterName, 'mode': subfilterMode if subfilterMode is not None else '', 'source': source if source is not None else ''} for subfilterName, subfilterMode, source in subfilters]

        self.filterSheet = Sheet((0, 0, 400, 350), self.w)
        self.filterSheet.new = makeNew
        self.filterSheet.index = self.filters[filterName].index if not makeNew else -1
        applyTitle = 'Add Operation' if filterName == '' else 'Update Operation'
        self.filterSheet.apply = SquareButton((-145, -37, 130, 22), applyTitle, callback=self.processFilterGroup, sizeStyle='small')
        self.filterSheet.cancel = SquareButton((-210, -37, 60, 22), 'Cancel', callback=self.closeFilterSheet, sizeStyle='small')

        y = 20
        self.filterSheet.nameTitle = TextBox((15, y, 100, 22), 'Filter Name')
        self.filterSheet.name = EditText((125, y, -15, 22), filterName)
        y += 22

        columns = [
            {'title': 'filterName', 'editable': True, 'width': 140},
            {'title': 'mode', 'editable': True, 'width': 89},
            {'title': 'source', 'editable': True, 'width': 100}
        ]

        buttonSize = 20
        gutter = 7

        y += 20
        self.filterSheet.subfilters = List((15 + buttonSize + gutter, y, -15, -52), subfilterItems, columnDescriptions=columns, allowsMultipleSelection=False, allowsEmptySelection=False)
        self.filterSheet.addSubfilter = SquareButton((15, -52-(buttonSize*2)-gutter, buttonSize, buttonSize), '+', sizeStyle='small', callback=self.addSubfilter)
        self.filterSheet.removeSubfilter = SquareButton((15, -52-buttonSize, buttonSize, buttonSize), '-', sizeStyle='small', callback=self.removeSubfilter)
        if len(subfilters) == 0:
            self.filterSheet.removeSubfilter.enable(False)
        y += 75
        self.filterSheet.moveSubfilterUp = SquareButton((15, y, buttonSize, buttonSize), u'⇡', sizeStyle='small', callback=self.moveSubfilterUp)
        self.filterSheet.moveSubfilterDown = SquareButton((15, y + buttonSize + gutter, buttonSize, buttonSize), u'⇣', sizeStyle='small', callback=self.moveSubfilterDown)

        if filterName == '':
            self.currentFilterName = ''

    def addArgument(self, sender):
        argumentsList = self.filterSheet.arguments.get()
        argumentsList.append({'argument': 'rename me', 'value': 50, 'min': 0, 'max': 100})
        if len(argumentsList) > 0:
            self.filterSheet.removeArgument.enable(True)
        self.filterSheet.arguments.set(argumentsList)

    def removeArgument(self, sender):
        argumentsList = self.filterSheet.arguments.get()
        if len(argumentsList) == 0:
            self.filterSheet.removeArgument.enable(False)
        selection = self.filterSheet.arguments.getSelection()[0]
        argumentsList.pop(selection)
        self.filterSheet.arguments.set(argumentsList)

    def addSubfilter(self, sender):
        subfiltersList = self.filterSheet.subfilters.get()
        subfilterDict = {'filterName': '{enter filter name}', 'mode': '', 'source': ''}
        subfiltersList.append(subfilterDict)
        if len(subfiltersList) > 0:
            self.filterSheet.removeSubfilter.enable(True)
        self.filterSheet.subfilters.set(subfiltersList)

    def removeSubfilter(self, sender):
        subfiltersList = self.filterSheet.subfilters.get()
        if len(subfiltersList) == 0:
            self.filterSheet.removeSubfilter.enable(False)
        selection = self.filterSheet.subfilters.getSelection()[0]
        subfiltersList.pop(selection)
        self.filterSheet.subfilters.set(subfiltersList)

    def moveSubfilterUp(self, sender):
        subfiltersList = self.filterSheet.subfilters.get()
        nItems = len(subfiltersList)
        if nItems > 1:
            selection = self.filterSheet.subfilters.getSelection()[0]
            if selection > 0:
                itemToMove = subfiltersList.pop(selection)
                subfiltersList.insert(selection-1, itemToMove)
                self.filterSheet.subfilters.set(subfiltersList)

    def moveSubfilterDown(self, sender):
        subfiltersList = self.filterSheet.subfilters.get()
        nItems = len(subfiltersList)
        if nItems > 1:
            selection = self.filterSheet.subfilters.getSelection()[0]
            if selection < nItems-1:
                itemToMove = subfiltersList.pop(selection)
                subfiltersList.insert(selection+1, itemToMove)
                self.filterSheet.subfilters.set(subfiltersList)

    def getFile(self, sender):
        path = getFile(fileTypes=['py'], allowsMultipleSelection=False, resultCallback=self.loadFilePath, parentWindow=self.filterSheet)

    def loadFilePath(self, paths):
        path = paths[0]
        self.filterSheet.importPath[1].pathInput.set(path)

    def closeFilterSheet(self, sender):
        self.filterSheet.close()
        delattr(self, 'filterSheet')

    def processFilter(self, sender):
        argumentsList = self.filterSheet.arguments.get()
        filterName = self.filterSheet.name.get()
        index = self.filterSheet.index
        filterDict = {}

        if len(filterName) > 0:
            sourceIndex = self.filterSheet.importPath.get()
            mode = ['module','file'][sourceIndex]
            importString = self.filterSheet.importPath[sourceIndex].pathInput.get()

            if len(importString) > 0:
                filterDict[mode] = importString

                filterObjectName = self.filterSheet.filterObject.get()
                filterDict['filterObjectName'] = filterObjectName

                if len(filterObjectName) > 0:

                    for argItem in argumentsList:
                        if 'argument' in argItem:
                            key = argItem['argument']
                            if 'value' in argItem:
                                value = self.parseValue(argItem['value'])
                                if 'arguments' not in filterDict:
                                    filterDict['arguments'] = OrderedDict()
                                filterDict['arguments'][key] = value
                                if 'min' in argItem and 'max' in argItem and isinstance(value, (float, int)):
                                    try:
                                        mini, maxi = float(argItem['min']), float(argItem['max'])
                                        if 'limits' not in filterDict:
                                            filterDict['limits'] = {}
                                        filterDict['limits'][key] = (mini, maxi)
                                    except:
                                        pass

                    if filterName in self.filters:
                        self.filters.setFilter(filterName, filterDict)

                    elif self.filterSheet.new == False:
                        index = self.filterSheet.index
                        self.filters.changeFilterNameByIndex(index, filterName)
                        self.filters.setFilter(filterName, filterDict)

                    elif self.filterSheet.new == True:
                        self.filters.setFilter(filterName, filterDict)

                    self.closeFilterSheet(sender)
                    self.updateFiltersList(index)
                    self.updateControls()
                    self.resetRepresentations()
                    self.updatePreview()

    def processFilterGroup(self, sender):
        filterName = self.filterSheet.name.get()
        subfiltersList = self.filterSheet.subfilters.get()
        isNew = self.filterSheet.new
        index = self.filterSheet.index
        subfilters = []

        for item in subfiltersList:
            subfilterName = item['filterName'] if 'filterName' in item else ''
            mode = item['mode'] if 'mode' in item else None
            source = item['source'] if 'source' in item else None
            try:
                source = int(source)
            except:
                pass
            subfilters.append((subfilterName, mode, source))

        if filterName in self.filters:
            self.filters.updateFilterChain(filterName, subfilters)
        elif not isNew:
            self.filters.changeFilterNameByIndex(index, filterName)
            self.filters.updateFilterChain(filterName, subfilters)
        elif isNew:
            self.filters.setFilterChain(filterName, subfilters)

        self.closeFilterSheet(sender)
        self.updateFiltersList(index)
        self.updateControls()
        self.resetRepresentations()
        self.updatePreview()

    def addFilter(self, sender):
        self.buildFilterSheet(makeNew=True)
        self.filterSheet.open()

    def addFilterChain(self, sender):
        self.buildFilterGroupSheet(makeNew=True)
        self.filterSheet.open()

    def addExternalFilter(self, filterName, filterDict):
        self.filters.addFilter(filterName, filterDict)
        self.updateFiltersList()

    def removeFilter(self, sender):
        filterName = self.currentFilterName
        self.filters.removeFilter(filterName)
        self.updateFiltersList()

    def filterSelectionChanged(self, sender):
        selectedFilterName = self.getSelectedFilterName()
        self.cachedFont = RFont(showUI=False)
        self.currentFilterName = selectedFilterName
        self.updateControls()
        self.updatePreview()

    def getSelectedFilterName(self):
        filtersList = self.w.filtersPanel.filtersList
        filterNamesList = filtersList.get()
        if len(filterNamesList):
            selectedIndices = filtersList.getSelection()
            if len(selectedIndices):
                selection = filtersList.getSelection()[0]
                return filterNamesList[selection]
        return None

    def switchFillStroke(self, sender):
        self.fill = not self.fill
        displayStates = self.w.preview.getDisplayStates()
        if self.fill == True:
            sender.setTitle('Stroke')
            displayStates['Fill'] = True
            displayStates['Stroke'] = False
        elif self.fill == False:
            sender.setTitle('Fill')
            displayStates['Fill'] = False
            displayStates['Stroke'] = True
        self.w.preview.setDisplayStates(displayStates)

    def parseValue(self, value):
        if isinstance(value, bool):
            value = bool(value)
        elif isinstance(value, (str, unicode)) and value.lower() == 'true':
            value = True
        elif isinstance(value, (str, unicode)) and value.lower() == 'false':
            value = False
        elif value is not '' or value is not None:
            try:
                value = float(value)
            except:
                pass
        return value

    def fontChanged(self, notification):
        if 'font' in notification:
            self.releaseObservedGlyphs()
            self.stringInput(self.w.textInput)
            self.currentFont = notification['font']
            self.cachedFont = RFont(showUI=False)
            self.updatePreview()

    def releaseObservedGlyphs(self):
        for glyph in self.observedGlyphs:
            glyph.removeObserver(self, 'Glyph.Changed')
        self.observedGlyphs = []

    def glyphChanged(self, notification):
        glyph = notification.object
        glyph.destroyAllRepresentations()
        self.updatePreview()

    def launchWindow(self):
        postEvent("PenBallWizardSubscribeFilter", subscribeFilter=self.addExternalFilter)

    def end(self, notification):
        self.filters.saveFiltersToJSON('/'.join([LOCALPATH, JSONFILE]))
        self.releaseObservedGlyphs()
        for callback, event in self.observers:
            removeObserver(self, event)
Пример #5
0
class DBFinalTool:
    def __init__(self):
        self.glyph = None  # Typical RoboFont function
        self.updating = False

        pad = 10
        leading = 32
        y = pad
        w = 500
        h = 500
        c1 = 150
        c2 = 200
        bh = 24  # Button height
        leading = bh + pad / 2

        self.w = Window((100, 100, w, h), 'Final tool window', minSize=(w, h))
        self.w.fontList = List((pad, pad, c2, -w / 2), [],
                               doubleClickCallback=self.openFontCallback,
                               selectionCallback=self.update)

        y = pad
        self.w.fixTabWidths = Button((-c1 - pad, y, c1, bh),
                                     'Fix tab widths',
                                     callback=self.fixTabWidthsCallback)
        self.w.fixTabWidths.enable(False)

        y += leading
        self.w.fixNegativeWidths = Button(
            (-c1 - pad, y, c1, bh),
            'Fix negative widths',
            callback=self.fixNegativeWidthsCallback)
        self.w.fixNegativeWidths.enable(False)

        y += leading
        self.w.fixMissingComponentsWidths = Button(
            (-c1 - pad, y, c1, bh),
            'Fix missing components',
            callback=self.fixMissingComponentCallback)
        self.w.fixMissingComponentsWidths.enable(False)

        self.w.selectFolder = Button((-c1 - pad, -pad - bh, c1, bh),
                                     'Select fonts',
                                     callback=self.selectFontFolder)

        self.w.report = EditText((pad, -w / 2 + pad, -pad, -pad),
                                 readOnly=False)

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

        self.dirPath = self.selectFontFolder()

    def update(self, sender):
        """Do some UI status update work"""
        enable = len(self.w.fontList.getSelection()) > 0
        self.w.fixTabWidths.enable(enable)
        self.w.fixMissingComponentsWidths.enable(enable)
        self.w.fixNegativeWidths.enable(enable)

    def openFontCallback(self, sender):
        selectedFonts = []
        for index in self.w.fontList.getSelection():
            selectedFonts.append(self.w.fontList.get()[index])
        # Do something here with the fonts after double click
        # Open in RoboFont of Glyphs
        cmd = 'open'
        for selectedFont in selectedFonts:
            cmd += ' ' + self.dirPath + selectedFont
        #self.report(cmd)
        os.system(cmd)

    def selectFontFolder(self, sender=None):
        result = getFolder(messageText='Select fonts folder',
                           title='Select',
                           allowsMultipleSelection=False)
        if result:
            path = result[0]
            fontNames = []
            for filePath in os.listdir(path):
                if filePath.startswith('.') or not filePath.endswith('.ufo'):
                    continue
                fontNames.append(filePath)
            self.w.fontList.set(fontNames)
            return path + '/'
        return None

    def fixTabWidthsCallback(self, sender):
        self.clearReport()
        for index in self.w.fontList.getSelection():
            fontFile = self.w.fontList.get()[index]
            result = fixTabWidths(self.dirPath + fontFile)
            self.report('\n'.join(result))
            #self.report(self.dirPath )
            self.report('Done %s' % fontFile)

    def fixNegativeWidthsCallback(self, sender):
        self.clearReport()
        for index in self.w.fontList.getSelection():
            fontFile = self.w.fontList.get()[index]
            try:
                result = fixNegativeWidths(self.dirPath + fontFile)
                self.report('\n'.join(result))
            except:
                f = open(self.dirPath + 'error.txt', 'w')
                traceback.print_exc(file=f)
                f.close()
            self.report('Done %s\n' % fontFile)

    def fixMissingComponentCallback(self, sender):
        self.clearReport()
        for index in self.w.fontList.getSelection():
            fontFile = self.w.fontList.get()[index]
            result = fixMissingComponent(self.dirPath + fontFile)
            self.report('\n'.join(result))
            #self.report(self.dirPath )
            self.report('Done %s\n' % fontFile)

    def windowCloseCallback(self, sender):
        self.report('removeObservers')

    def clearReport(self):
        self.w.report.set('')

    def report(self, report):
        s = self.w.report.get()
        if s:
            s += '\n'
        self.w.report.set(s + str(report))
Пример #6
0
class MM2SpaceCenter:
    def activateModule(self):
        addObserver(self, "MMPairChangedObserver",
                    "MetricsMachine.currentPairChanged")
        print('MM2SpaceCenter activated')

    def deactivateModule(self, sender):
        removeObserver(self, "MetricsMachine.currentPairChanged")
        print('MM2SpaceCenter deactivated')

    def __init__(self, ):
        self.font = metricsMachine.CurrentFont()
        self.pair = metricsMachine.GetCurrentPair()
        #self.wordlistPath = wordlistPath

        leftMargin = 10
        topMargin = 5
        yPos = 0
        lineHeight = 20

        yPos += topMargin

        self.messageText = 'MM2SpaceCenter activated 😎'

        self.wordCount = 20
        self.minLength = 3
        self.maxLength = 15

        self.activateModule()
        self.w = Window((250, 155), "MM2SpaceCenter")

        self.w.myTextBox = TextBox((leftMargin, yPos, -10, 17),
                                   self.messageText,
                                   sizeStyle="regular")

        yPos += (lineHeight * 1.2)

        topLineFields = {
            "wordCount": [0 + leftMargin, self.wordCount, 20],
            #"minLength": [108+leftMargin, self.minLength, 3],
            #"maxLength": [145+leftMargin, self.maxLength, 10],
        }
        topLineLabels = {
            "wcText": [31 + leftMargin, 78, 'words', 'left'],

            #"wcText": [31+leftMargin, 78, 'words with', 'left'],

            # "lenTextTwo": [133+leftMargin, 10, u'–', 'center'],
            #"lenTextThree": [176+leftMargin, -0, 'letters', 'left'],
        }

        # for label, values in topLineFields.items():
        #     setattr(self.w, label, EditText((values[0], 0+yPos, 28, 22), text=values[1], placeholder=str(values[2])))

        self.w.wordCount = EditText((0 + leftMargin, 0 + yPos, 28, 22),
                                    text=self.wordCount,
                                    placeholder=self.wordCount,
                                    callback=self.wordCountCallback)

        for label, values in topLineLabels.items():
            setattr(
                self.w, label,
                TextBox((values[0], 3 + yPos, values[1], 22),
                        text=values[2],
                        alignment=values[3]))

        yPos += lineHeight * 1.3

        self.loadDictionaries()

        # language selection
        languageOptions = list(self.languageNames)

        self.w.source = PopUpButton((leftMargin, yPos, 85, 20), [],
                                    sizeStyle="small",
                                    callback=self.changeSourceCallback)
        self.w.source.setItems(languageOptions)

        self.w.source.set(4)  #default to English for now
        self.source = None
        self.source = self.w.source.get(
        )  #get value, to use for other functions

        yPos += lineHeight * 1.2

        checkBoxSize = 18
        self.w.listOutput = CheckBox(
            (leftMargin, yPos, checkBoxSize, checkBoxSize),
            "",
            sizeStyle="small",
            callback=self.sortedCallback)
        self.w.listLabel = TextBox(
            (checkBoxSize + 5, yPos + 2, -leftMargin, checkBoxSize),
            "Output as list sorted by width",
            sizeStyle="small")

        yPos += lineHeight * 1.2

        checkBoxSize = 18
        self.w.openCloseContext = CheckBox(
            (leftMargin, yPos, checkBoxSize, checkBoxSize),
            "",
            sizeStyle="small",
            callback=self.sortedCallback)
        self.w.openCloseContextLabel = TextBox(
            (checkBoxSize + 5, yPos + 2, -leftMargin, checkBoxSize),
            "Show open+close context {n}",
            sizeStyle="small")

        yPos += lineHeight * 1.2

        self.w.mirroredPair = CheckBox(
            (leftMargin, yPos, checkBoxSize, checkBoxSize),
            "",
            sizeStyle="small",
            callback=self.sortedCallback)
        self.w.mirroredPairLabel = TextBox(
            (checkBoxSize + 5, yPos + 2, -leftMargin, checkBoxSize),
            "Show mirrored pair (LRL)",
            sizeStyle="small")

        self.sorted = self.w.listOutput.get()

        self.w.bind("close", self.deactivateModule)
        self.w.open()

    def sortedCallback(self, sender):
        self.sorted = self.w.listOutput.get()
        self.wordsForMMPair()

    def wordCountCallback(self, sender):
        #print ('old', self.wordCount)

        self.wordCount = self.w.wordCount.get() or 1

        #update space center
        self.wordsForMMPair()

    #from word-o-mat
    def loadDictionaries(self):
        """Load the available wordlists and read their contents."""
        self.dictWords = {}
        self.allWords = []
        self.outputWords = []

        self.textfiles = [
            'catalan', 'czech', 'danish', 'dutch', 'ukacd', 'finnish',
            'french', 'german', 'hungarian', 'icelandic', 'italian', 'latin',
            'norwegian', 'polish', 'slovak', 'spanish', 'vietnamese'
        ]
        self.languageNames = [
            'Catalan', 'Czech', 'Danish', 'Dutch', 'English', 'Finnish',
            'French', 'German', 'Hungarian', 'Icelandic', 'Italian', 'Latin',
            'Norwegian', 'Polish', 'Slovak', 'Spanish', 'Vietnamese syllables'
        ]

        #self.source = getExtensionDefault("com.cjtype.MM2SpaceCenter.source", 4)

        bundle = ExtensionBundle("MM2SpaceCenter")
        contentLimit = '*****'  # If word list file contains a header, start looking for content after this delimiter

        # read included textfiles
        for textfile in self.textfiles:
            path = bundle.getResourceFilePath(textfile)
            #print (path)
            with codecs.open(path, mode="r", encoding="utf-8") as fo:
                lines = fo.read()

            self.dictWords[textfile] = lines.splitlines(
            )  # this assumes no whitespace has to be stripped

            # strip header
            try:
                contentStart = self.dictWords[textfile].index(contentLimit) + 1
                self.dictWords[textfile] = self.dictWords[textfile][
                    contentStart:]
            except ValueError:
                pass

        # read user dictionary
        with open('/usr/share/dict/words', 'r') as userFile:
            lines = userFile.read()
        self.dictWords["user"] = lines.splitlines()

        #print ('load dicts')

    def changeSourceCallback(self, sender):
        """On changing source/wordlist, check if a custom word list should be loaded."""
        customIndex = len(self.textfiles) + 2
        if sender.get() == customIndex:  # Custom word list
            try:
                filePath = getFile(
                    title="Load custom word list",
                    messageText=
                    "Select a text file with words on separate lines",
                    fileTypes=["txt"])[0]
            except TypeError:
                filePath = None
                self.customWords = []
                print("Input of custom word list canceled, using default")
            if filePath is not None:
                with codecs.open(filePath, mode="r", encoding="utf-8") as fo:
                    lines = fo.read()
                # self.customWords = lines.splitlines()
                self.customWords = []
                for line in lines.splitlines():
                    w = line.strip()  # strip whitespace from beginning/end
                    self.customWords.append(w)

        self.source = self.w.source.get()

        #update space center
        self.wordsForMMPair()

        #print ('source changed')

    def sortWordsByWidth(self, wordlist):
        """Sort output word list by width."""
        f = self.font
        wordWidths = []

        for word in wordlist:
            unitCount = 0
            for char in word:
                try:
                    glyphWidth = f[char].width
                except:
                    try:
                        gname = self.glyphNamesForValues[char]
                        glyphWidth = f[gname].width
                    except:
                        glyphWidth = 0
                unitCount += glyphWidth
            # add kerning
            for i in range(len(word) - 1):
                pair = list(word[i:i + 2])
                unitCount += int(self.findKerning(pair))
            wordWidths.append(unitCount)

        wordWidths_sorted, wordlist_sorted = zip(*sorted(
            zip(wordWidths, wordlist)))  # thanks, stackoverflow
        return wordlist_sorted

    def findKerning(self, chars):
        """Helper function to find kerning between two given glyphs.
        This assumes MetricsMachine style group names."""

        markers = ["@MMK_L_", "@MMK_R_"]
        keys = [c for c in chars]

        for i in range(2):
            allGroups = self.font.groups.findGlyph(chars[i])
            if len(allGroups) > 0:
                for g in allGroups:
                    if markers[i] in g:
                        keys[i] = g
                        continue

        key = (keys[0], keys[1])
        if self.font.kerning.has_key(key):
            return self.font.kerning[key]
        else:
            return 0

    def MMPairChangedObserver(self, sender):
        #add code here for when myObserver is triggered
        currentPair = sender["pair"]
        if currentPair == self.pair:
            return

        self.pair = currentPair

        #print ('current MM pair changed', self.pair)
        self.wordsForMMPair()

        #pass

    # def getMetricsMachineController(self):
    #     # Iterate through ALL OBJECTS IN PYTHON!
    #     import gc
    #     for obj in gc.get_objects():
    #         if hasattr(obj, "__class__"):
    #             # Does this one have a familiar name? Cool. Assume that we have what we are looking for.
    #             if obj.__class__.__name__ == "MetricsMachineController":
    #                 return obj

    def setSpaceCenter(self, font, text):
        currentSC = CurrentSpaceCenter()
        if currentSC is None:
            print('opening space center, click back into MM window')
            OpenSpaceCenter(font, newWindow=False)
            currentSC = CurrentSpaceCenter()
        currentSC.setRaw(text)

    def randomly(self, seq):
        shuffled = list(seq)
        random.shuffle(shuffled)
        return iter(shuffled)

    def gname2char(self, f, gname):
        uni = f[gname].unicodes[0]
        char = chr(uni)
        return char

    def checkForUnencodedGname(self, font, gname):
        glyphIsEncoded = False

        escapeList = ['slash', 'backslash']

        if (not font[gname].unicodes) or (gname in escapeList):
            scString = '/' + gname + ' '
        else:
            scString = self.gname2char(font, gname)
            glyphIsEncoded = True

        return (scString, glyphIsEncoded)

    def getPairstring(self, pair):

        left, self.leftEncoded = self.checkForUnencodedGname(
            self.font, pair[0])
        right, self.rightEncoded = self.checkForUnencodedGname(
            self.font, pair[1])

        pairstring = left + right

        return pairstring

    #convert char gnames to chars to find words in dict
    def pair2char(self, pair):

        debug = False

        try:
            #print ('pair =', pair)
            left = self.gname2char(CurrentFont(), pair[0])
            right = self.gname2char(CurrentFont(), pair[1])
            pair_char = (left, right)
            return pair_char
        except:
            if debug == True:
                print("couldn't convert pair to chars")
            return pair

    def lcString(self, pairstring):
        string = 'non' + pairstring + 'nono' + pairstring + 'oo'
        return string

    def ucString(self, pairstring):
        string = 'HH' + pairstring + 'HOHO' + pairstring + 'OO'
        return string

    openClosePairs = {

        # initial/final punctuation (from https://www.compart.com/en/unicode/category/Pi and https://www.compart.com/en/unicode/category/Pf)
        "‚": "‘",
        "„": "“",
        "„": "”",
        "‘": "’",
        "‛": "’",
        "“": "”",
        "‟": "”",
        "‹": "›",
        "›": "‹",
        "«": "»",
        "»": "«",
        "⸂": "⸃",
        "⸄": "⸅",
        "⸉": "⸊",
        "⸌": "⸍",
        "⸜": "⸝",
        "⸠": "⸡",
        "”": "”",
        "’": "’",

        # Miscellaneous but common open/close pairs
        "'": "'",
        '"': '"',
        "¡": "!",
        "¿": "?",
        "←": "→",
        "→": "←",

        # opening/closing punctuation (from https://www.compart.com/en/unicode/category/Ps & https://www.compart.com/en/unicode/category/Pe)
        "(": ")",
        "[": "]",
        "{": "}",
        "༺": "༻",
        "༼": "༽",
        "᚛": "᚜",
        "‚": "‘",
        "„": "“",
        "⁅": "⁆",
        "⁽": "⁾",
        "₍": "₎",
        "⌈": "⌉",
        "⌊": "⌋",
        "〈": "〉",
        "❨": "❩",
        "❪": "❫",
        "❬": "❭",
        "❮": "❯",
        "❰": "❱",
        "❲": "❳",
        "❴": "❵",
        "⟅": "⟆",
        "⟦": "⟧",
        "⟨": "⟩",
        "⟪": "⟫",
        "⟬": "⟭",
        "⟮": "⟯",
        "⦃": "⦄",
        "⦅": "⦆",
        "⦇": "⦈",
        "⦉": "⦊",
        "⦋": "⦌",
        "⦍": "⦎",
        "⦏": "⦐",
        "⦑": "⦒",
        "⦓": "⦔",
        "⦕": "⦖",
        "⦗": "⦘",
        "⧘": "⧙",
        "⧚": "⧛",
        "⧼": "⧽",
        "⸢": "⸣",
        "⸤": "⸥",
        "⸦": "⸧",
        "⸨": "⸩",
        "〈": "〉",
        "《": "》",
        "「": "」",
        "『": "』",
        "【": "】",
        "〔": "〕",
        "〖": "〗",
        "〘": "〙",
        "〚": "〛",
        "〝": "〞",
        "⹂": "〟",
        "﴿": "﴾",
        "︗": "︘",
        "︵": "︶",
        "︷": "︸",
        "︹": "︺",
        "︻": "︼",
        "︽": "︾",
        "︿": "﹀",
        "﹁": "﹂",
        "﹃": "﹄",
        "﹇": "﹈",
        "﹙": "﹚",
        "﹛": "﹜",
        "﹝": "﹞",
        "(": ")",
        "[": "]",
        "{": "}",
        "⦅": "⦆",
        "「": "」",
    }

    def openCloseContext(self, pair):
        if self.w.openCloseContext.get() == True:

            # get unicodes to make sure we don’t show pairs that don’t exist in the font
            # TODO? may be better to move outside this function, if running it each time is slow. BUT it would have to listen for the CurrentFont to change.
            unicodesInFont = [
                u for glyph in CurrentFont() for u in glyph.unicodes
            ]

            left, self.leftEncoded = self.checkForUnencodedGname(
                self.font, pair[0])
            right, self.rightEncoded = self.checkForUnencodedGname(
                self.font, pair[1])

            openCloseString = ""

            for openClose in self.openClosePairs.items():
                # if both sides of pair are in an open+close pair, just add them
                if openClose[0] == left and openClose[1] == right:
                    openCloseString += left + right + " "
                # if the left is in an openClose pair and its companion is in the font, add them
                if openClose[0] == left and ord(
                        openClose[1]) in unicodesInFont:
                    openCloseString += left + right + self.openClosePairs[
                        left] + " "
                # if the right is in an openClose pair and its companion is in the font, add them
                if openClose[1] == right and ord(
                        openClose[0]) in unicodesInFont:
                    openCloseString += openClose[0] + left + right + " "
                else:
                    continue

            return openCloseString
        else:
            return ""

    # make mirrored pair to judge symmetry of kerns
    def pairMirrored(self, pair):
        if self.w.mirroredPair.get() == True:
            left, self.leftEncoded = self.checkForUnencodedGname(
                self.font, pair[0])
            right, self.rightEncoded = self.checkForUnencodedGname(
                self.font, pair[1])
            return left + right + left + " "
        else:
            return ""

    def wordsForMMPair(self, ):

        self.mixedCase = False

        ### temp comment out to check speed
        self.source = self.w.source.get()
        wordsAll = self.dictWords[self.textfiles[self.source]]

        #default values are hard coded for now
        #self.wordCount = self.getIntegerValue(self.w.wordCount)

        #v = self.getIntegerValue(self.w.wordCount)

        wordCountValue = int(self.wordCount)

        #print(v)

        #print ('self.wordCount', self.wordCount)

        #currently allows any word lenght, this could be customized later

        text = ''
        textList = []

        # try getting pairstring once in order to check if encoded
        pairstring = self.getPairstring(self.pair)

        #convert MM tuple into search pair to check uc, lc, mixed case. Maybe need a different var name here?
        pair2char = ''.join(self.pair2char(self.pair))

        #check Encoding

        #print (pairstring)

        #default value
        makeUpper = False

        if pair2char.isupper():
            #print (pairstring, 'upper')
            makeUpper = True
            #make lower for searching
            searchString = pair2char.lower()

        else:
            #print(pairstring, 'not upper')
            makeUpper = False
            searchString = pair2char
            pass

        #check for mixed case
        if self.pair2char(self.pair)[0].isupper():
            if self.pair2char(self.pair)[1].islower():
                if (self.leftEncoded == True) and (self.rightEncoded == True):
                    self.mixedCase = True

        try:
            currentSC = CurrentSpaceCenter()
            previousText = currentSC.getRaw()
        except:
            previousText = ''
            pass

        count = 0

        #self.usePhrases = False

        # more results for mixed case if we include lc words and capitalize
        if self.mixedCase == True:
            for word in self.randomly(wordsAll):

                # first look for words that are already mixed case
                if searchString in word:
                    #avoid duplicates
                    if not word in textList:

                        #print (word)
                        textList.append(word)
                        count += 1

                #then try capitalizing lowercase words
                if (searchString.lower() in word[:2]):
                    word = word.capitalize()
                    #avoid duplicates
                    if not word in textList:

                        #print (word)
                        textList.append(word)
                        count += 1

                #stop when you get enough results
                if count >= wordCountValue:
                    #print (text)

                    break

            pass

        else:
            for word in self.randomly(wordsAll):
                if searchString in word:

                    #avoid duplicates
                    if not word in textList:

                        #print (word)
                        textList.append(word)
                        count += 1

                #stop when you get enough results
                if count >= wordCountValue:
                    #print (text)

                    break

        if makeUpper == True:
            #make text upper again
            textList = list(text.upper() for text in textList)

        if not len(textList) == 0:
            #see if box is checked
            self.sorted = self.w.listOutput.get()

            #self.sorted = False
            if self.sorted == True:
                sortedText = self.sortWordsByWidth(textList)

                textList = sortedText

                joinString = "\\n"
                text = joinString.join([str(word) for word in textList])

                if self.w.mirroredPair.get(
                ) == True:  #if "start with mirrored pair" is checked, add this to text
                    text = self.pairMirrored(self.pair) + joinString + text
                if self.w.openCloseContext.get(
                ) == True:  # if "show open+close" is checked, add this to text
                    text = self.openCloseContext(self.pair) + text

            else:
                text = ' '.join([str(word) for word in textList])
                if self.w.mirroredPair.get(
                ) == True:  #if "start with mirrored pair" is checked, add this to text
                    text = self.pairMirrored(self.pair) + text
                if self.w.openCloseContext.get(
                ) == True:  # if "show open+close" is checked, add this to text
                    text = self.openCloseContext(self.pair) + text

        # if no words are found, show spacing string and previous text
        if len(text) == 0:

            #do i need to get pairstring again or can I used the previous one?
            #pairstring = self.getPairstring(self.pair)

            previousText = '\\n no words for pair ' + pairstring

            self.messageText = '😞 no words found: ' + pairstring
            self.w.myTextBox.set(self.messageText)

            if makeUpper == True:
                text = self.ucString(pairstring) + previousText
                if self.w.mirroredPair.get(
                ) == True:  #if "start with mirrored pair" is checked, add this to text
                    text = self.pairMirrored(self.pair) + text
                if self.w.openCloseContext.get(
                ) == True:  # if "show open+close" is checked, add this to text
                    text = self.openCloseContext(self.pair) + text

            else:
                text = self.lcString(pairstring) + previousText
                if self.w.mirroredPair.get(
                ) == True:  #if "start with mirrored pair" is checked, add this to text
                    text = self.pairMirrored(self.pair) + text
                if self.w.openCloseContext.get(
                ) == True:  # if "show open+close" is checked, add this to text
                    text = self.openCloseContext(self.pair) + text

            text = text.lstrip()  #remove whitespace
            self.setSpaceCenter(self.font, text)

        else:
            #set space center if words are found
            #not sure why there's always a /slash in from of the first word, added ' '+ to avoid losing the first word

            text = text.lstrip()  #remove whitespace

            self.setSpaceCenter(self.font, text)

            self.messageText = '😎 words found: ' + pairstring
            self.w.myTextBox.set(self.messageText)
class GlyphMetricsUI(object):
    def __init__(self):
        # debug
        windowname = 'Debug Glyph Metrics UI'
        windows = [w for w in NSApp().orderedWindows() if w.isVisible()]
        for window in windows:
            print(window.title())
            if window.title() == windowname:
                window.close()
        if debug == True:
            self.debug = Window((333, 33), windowname)
            self.debug.bind('close', self.windowSideuiClose)
            self.debug.open()
        # setup
        self.showButtons = False
        self.sbui = None
        # add interface
        addObserver(self, 'addInterface', 'glyphWindowWillOpen')
        addObserver(self, 'updateSelfWindow', 'currentGlyphChanged')
        # toggle visibility of tool ui
        addObserver(self, 'showSideUIonDraw', 'draw')
        addObserver(self, 'hideSideUIonPreview', 'drawPreview')
        # remove UI and window manager when glyph window closes
        # addObserver(self, "observerGlyphWindowWillClose", "glyphWindowWillClose")
        # subscribe to glyph changes
        addObserver(self, "viewDidChangeGlyph", "viewDidChangeGlyph")

    # def observerGlyphWindowWillClose(self, notification):
    #     self.window = notification['window']
    #     self.cleanup(self.window)
    #     del windowViewManger[notification['window']]
    #     del lll[notification['window']]
    #     del ccc[notification['window']]
    #     del rrr[notification['window']]

    def windowSideuiClose(self, sender):
        self.window.removeGlyphEditorSubview(self.sbui)
        removeObserver(self, 'glyphWindowWillOpen')
        removeObserver(self, 'glyphWindowWillClose')
        removeObserver(self, 'glyphWindowWillClose')
        removeObserver(self, 'currentGlyphChanged')
        removeObserver(self, 'draw')
        removeObserver(self, 'drawPreview')
        removeObserver(self, 'viewDidChangeGlyph')

    def cleanup(self, w):
        try:
            w.removeGlyphEditorSubview(self.sbui)
        except:
            return

    def addInterface(self, notification):
        self.window = notification['window']
        # self.cleanup(self.window)

        # CONTAINER
        xywh = (margin, -55, -margin, height)
        self.sbui = CanvasGroup(xywh, delegate=CanvasStuff(self.window))

        # LEFT
        x, y, w, h = xywh = (0, -height, dfltLwidth, height)
        this = self.sbui.L = Group(xywh)

        # text input
        xywh = (x, y + 3, width * 1.5, height * .75)
        this.Ltext = EditText(xywh,
                              placeholder='angledLeftMargin',
                              sizeStyle='mini',
                              continuous=False,
                              callback=self.setSB)
        # quick mod buttons
        xywh = (x + width * 1.5 + (gap * 1), y, width, height)
        this.Lminus = Button(xywh,
                             iconminus,
                             sizeStyle='mini',
                             callback=self.setLminus)
        this.Lminus.getNSButton().setToolTip_('Adjust LSB -' + str(unit))
        xywh = (x + width * 2.5 + (gap * 2), y, width, height)
        this.Lplus = Button(xywh,
                            iconplus,
                            sizeStyle='mini',
                            callback=self.setLplus)
        this.Lplus.getNSButton().setToolTip_('Adjust LSB +' + str(unit))
        xywh = (x + width * 3.5 + (gap * 3), y, width, height)
        this.Lround = Button(xywh,
                             iconround,
                             sizeStyle='mini',
                             callback=self.setLround)
        this.Lround.getNSButton().setToolTip_('Round LSB to ' + str(unit))
        xywh = (x + width * 4.5 + (gap * 4), y, width, height)
        this.Lright = Button(xywh,
                             iconcopyR,
                             sizeStyle='mini',
                             callback=self.setLright)
        this.Lright.getNSButton().setToolTip_('Copy Right Value')
        # stylize
        this.Ltext.getNSTextField().setBezeled_(False)
        this.Ltext.getNSTextField().setBackgroundColor_(NSColor.clearColor())
        self.flatButt(this.Lminus)
        self.flatButt(this.Lplus)
        self.flatButt(this.Lround)
        self.flatButt(this.Lright)

        # RIGHT
        x, y, w, h = xywh = (-dfltRwidth, y, dfltRwidth, h)
        this = self.sbui.R = Group(xywh)
        # text input
        xywh = (-x - width * 1.5, y + 3, width * 1.5, height * .75)
        this.Rtext = EditText(xywh,
                              placeholder='angledRightMargin',
                              sizeStyle='mini',
                              continuous=False,
                              callback=self.setSB)
        # quick mod buttons
        xywh = (-x - width * 5.5 - (gap * 4), y, width, height)
        this.Rleft = Button(xywh,
                            iconcopyL,
                            sizeStyle='mini',
                            callback=self.setRleft)
        this.Rleft.getNSButton().setToolTip_('Copy Left Value')
        xywh = (-x - width * 4.5 - (gap * 3), y, width, height)
        this.Rround = Button(xywh,
                             iconround,
                             sizeStyle='mini',
                             callback=self.setRround)
        this.Rround.getNSButton().setToolTip_('Round RSB to ' + str(unit))
        xywh = (-x - width * 3.5 - (gap * 2), y, width, height)
        this.Rminus = Button(xywh,
                             iconminus,
                             sizeStyle='mini',
                             callback=self.setRminus)
        this.Rminus.getNSButton().setToolTip_('Adjust RSB -' + str(unit))
        xywh = (-x - width * 2.5 - (gap * 1), y, width, height)
        this.Rplus = Button(xywh,
                            iconplus,
                            sizeStyle='mini',
                            callback=self.setRplus)
        this.Rplus.getNSButton().setToolTip_('Adjust RSB +' + str(unit))
        # stylize
        this.Rtext.getNSTextField().setBezeled_(False)
        this.Rtext.getNSTextField().setBackgroundColor_(NSColor.clearColor())
        this.Rtext.getNSTextField().setAlignment_(NSTextAlignmentRight)
        self.flatButt(this.Rminus)
        self.flatButt(this.Rplus)
        self.flatButt(this.Rround)
        self.flatButt(this.Rleft)

        # CENTER
        winX, winY, winW, winH = self.window.getVisibleRect()
        winW = winW - margin * 5
        x, y, w, h = xywh = ((winW / 2) - (dfltCwidth / 2), y, dfltCwidth, h)
        this = self.sbui.C = Group(xywh)
        x = 0

        # text input
        c = (dfltCwidth / 2)
        xywh = (c - (width * .75), y + 3, width * 1.5, height * .75)
        this.Ctext = EditText(xywh,
                              placeholder='width',
                              sizeStyle='mini',
                              continuous=False,
                              callback=self.setSB)
        # quick mod buttons
        xywh = (c - (width * .75) - width * 2 - (gap * 2), y, width, height)
        this.Ccenter = Button(xywh,
                              iconcenter,
                              sizeStyle='mini',
                              callback=self.setCcenter)
        this.Ccenter.getNSButton().setToolTip_('Center on Width')
        xywh = (c - (width * .75) - width - (gap * 1), y, width, height)
        this.Cround = Button(xywh,
                             iconround,
                             sizeStyle='mini',
                             callback=self.setCround)
        this.Cround.getNSButton().setToolTip_('Round Width to ' + str(unit))
        xywh = (c + (width * .75) + (gap * 1), y, width, height)
        this.Cminus = Button(xywh,
                             iconminus,
                             sizeStyle='mini',
                             callback=self.setCminus)
        this.Cminus.getNSButton().setToolTip_('Adjust Width -' + str(2 * unit))
        xywh = (c + (width * .75) + width + (gap * 2), y, width, height)
        this.Cplus = Button(xywh,
                            iconplus,
                            sizeStyle='mini',
                            callback=self.setCplus)
        this.Cplus.getNSButton().setToolTip_('Adjust Width +' + str(2 * unit))
        # stylize
        this.Ctext.getNSTextField().setBezeled_(False)
        this.Ctext.getNSTextField().setBackgroundColor_(NSColor.clearColor())
        this.Ctext.getNSTextField().setAlignment_(NSTextAlignmentCenter)
        self.flatButt(this.Cminus)
        self.flatButt(this.Cplus)
        self.flatButt(this.Cround)
        self.flatButt(this.Ccenter)

        # hide
        self.sbui.L.Lminus.show(False)
        self.sbui.L.Lround.show(False)
        self.sbui.L.Lplus.show(False)
        self.sbui.L.Lright.show(False)
        self.sbui.R.Rminus.show(False)
        self.sbui.R.Rround.show(False)
        self.sbui.R.Rplus.show(False)
        self.sbui.R.Rleft.show(False)
        self.sbui.C.Cminus.show(False)
        self.sbui.C.Cround.show(False)
        self.sbui.C.Ccenter.show(False)
        self.sbui.C.Cplus.show(False)

        # make it real
        self.sbWatcherInitialize()
        self.window.addGlyphEditorSubview(self.sbui)
        self.updateValues()
        self.buildMatchBase()
        windowViewManger[self.window] = self.sbui

    def updateSelfWindow(self, notification):
        self.window = CurrentGlyphWindow()
        self.buildMatchBase()
        self.updateValues()

    def showSideUIonDraw(self, notification):
        self.sbWatcher()
        sbui = windowViewManger.get(self.window)
        if sbui is not None:
            sbui.show(True)

    def hideSideUIonPreview(self, notification):
        sbui = windowViewManger.get(self.window)
        if sbui is not None:
            sbui.show(False)

    def updateValues(self, notification=None):
        try:
            g = self.window.getGlyph()
            f = g.font
            sbui = windowViewManger.get(self.window)
            sbui.L.Ltext.set(str(g.angledLeftMargin))
            sbui.R.Rtext.set(str(g.angledRightMargin))
            sbui.C.Ctext.set(str(g.width))
        except Exception as e:
            # if debug == True:
            #     print('Exception updateValues', e)
            return

    # hack sidebearings changed observer
    # used when things redraw
    def sbWatcherInitialize(self):
        g = self.window.getGlyph()
        f = g.font
        if g is not None:
            lll[self.window] = g.angledLeftMargin
            ccc[self.window] = g.width
            rrr[self.window] = g.angledRightMargin

    def sbWatcher(self):
        g = CurrentGlyph()
        if g is not None:
            f = g.font
            if lll[self.window] != None and ccc[self.window] != None and rrr[
                    self.window] != None:
                if lll[self.window] != g.angledLeftMargin or ccc[
                        self.window] != g.width or rrr[
                            self.window] != g.angledRightMargin:
                    lll[self.window] = g.angledLeftMargin
                    ccc[self.window] = g.width
                    rrr[self.window] = g.angledRightMargin
                    self.updateValues()
                    self.buildMatchBase()

    def setSB(self, sender):
        changeAttribute = sender.getPlaceholder()
        g = self.window.getGlyph()
        f = g.font
        v = sender.get()
        if is_number(v):
            if debug == True:
                print('value is a number')
            g.prepareUndo('Change ' + changeAttribute + ' SB')
            setattr(g, changeAttribute, float(v))
            g.performUndo()
            self.updateValues()
        elif v in f:
            if debug == True:
                print('value is a glyph')
            g.prepareUndo('Change ' + changeAttribute + ' SB')
            sb = getattr(f[v], changeAttribute)
            setattr(g, changeAttribute, sb)
            g.performUndo()
            self.updateValues()
        else:
            if debug == True:
                print('value is not a number or a glyph')
            return

    def setLminus(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('➖ Left SB')
        g.angledLeftMargin += -1 * unit
        g.performUndo()
        self.updateValues()

    def setLround(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Round Left SB')
        g.angledLeftMargin = int(unit *
                                 round(float(g.angledLeftMargin) / unit))
        g.performUndo()
        self.updateValues()

    def setLplus(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('➕ Left SB')
        g.angledLeftMargin += unit
        g.performUndo()
        self.updateValues()

    def setLright(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Copy Right SB')
        g.angledLeftMargin = g.angledRightMargin
        g.performUndo()
        self.updateValues()

    def setLmatch(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Match Left SB')
        f = g.font
        gmatch = sender.getTitle()
        if f[gmatch] is not None:
            g.angledLeftMargin = f[gmatch].angledLeftMargin
        g.performUndo()
        self.updateValues()

    def setRminus(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('➖ Right SB')
        g.angledRightMargin += -1 * unit
        g.performUndo()
        self.updateValues()

    def setRround(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Round Right SB')
        g.angledRightMargin = int(unit *
                                  round(float(g.angledRightMargin) / unit))
        g.performUndo()
        self.updateValues()

    def setRplus(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('➕ Right SB')
        g.angledRightMargin += unit
        g.performUndo()
        self.updateValues()

    def setRmatch(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Match Right SB')
        gmatch = sender.getTitle()
        f = g.font
        if f[gmatch] is not None:
            g.angledRightMargin = f[gmatch].angledRightMargin
        g.performUndo()
        self.updateValues()

    def setRleft(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Copy Left SB')
        g.angledRightMargin = g.angledLeftMargin
        g.performUndo()
        self.updateValues()

    def setCminus(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('➖ Width')
        # use whole units, not floats
        oldwidth = g.width
        leftby = unit
        g.angledLeftMargin += -1 * leftby
        g.width = oldwidth - unit * 2
        g.performUndo()
        self.updateValues()

    def setCround(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Round Width')
        g.width = int(unit * round(float(g.width) / unit))
        g.performUndo()
        self.updateValues()

    def setCcenter(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Center on Width')
        # use whole units, not floats
        padding = g.angledLeftMargin + g.angledRightMargin
        gwidth = g.width
        g.angledLeftMargin = int(padding / 2)
        g.width = gwidth
        g.performUndo()
        self.updateValues()

    def setCplus(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('➕ Width')
        # use whole units, not floats
        oldwidth = g.width
        leftby = unit
        g.angledLeftMargin += leftby
        g.width = oldwidth + unit * 2
        g.performUndo()
        self.updateValues()

    def setCmatch(self, sender):
        g = self.window.getGlyph()
        if g is None:
            return
        g.prepareUndo('Match Width')
        f = g.font
        gmatch = sender.getTitle()
        if f[gmatch] is not None:
            g.width = f[gmatch].width
        g.performUndo()
        self.updateValues()

    def flatButt(self, this, match=False):
        this = this.getNSButton()
        this.setBezelStyle_(buttstyle)
        this.setBordered_(False)
        this.setWantsLayer_(True)
        this.setBackgroundColor_(NSColor.whiteColor())
        if match == True:
            this.setBackgroundColor_(
                NSColor.colorWithCalibratedRed_green_blue_alpha_(
                    .9, 1, .85, 1))

    def buildMatchBase(self, notification=None):
        self.newheight = height
        try:
            g = self.window.getGlyph()
            f = g.font
            # remove old buttons
            for i in range(10):
                if hasattr(self.sbui.L, 'buttobj_%s' % i):
                    delattr(self.sbui.L, 'buttobj_%s' % i)
                    delattr(self.sbui.R, 'buttobj_%s' % i)
                    delattr(self.sbui.C, 'buttobj_%s' % i)

            # add button for each component
            self.uniquecomponents = []
            for c in g.components:
                if c.baseGlyph not in self.uniquecomponents:
                    self.uniquecomponents.append(c.baseGlyph)

            for i, c in enumerate(self.uniquecomponents):
                row = i + 1
                yy = -height * (row + 1) - 3

                xywh = (0, yy, width * 5.5 + (gap * 4), height)
                buttobj = Button(xywh,
                                 c,
                                 sizeStyle='mini',
                                 callback=self.setLmatch)
                setattr(self.sbui.L, 'buttobj_%s' % i, buttobj)
                this = getattr(self.sbui.L, 'buttobj_%s' % i)
                this.getNSButton().setAlignment_(NSTextAlignmentLeft)
                this.getNSButton().setToolTip_('Match LSB of ' + c)

                xywh = (-width * 5.5 - (gap * 4), yy, width * 5.5 + (gap * 4),
                        height)
                buttobj = Button(xywh,
                                 c,
                                 sizeStyle='mini',
                                 callback=self.setRmatch)
                setattr(self.sbui.R, 'buttobj_%s' % i, buttobj)
                this = getattr(self.sbui.R, 'buttobj_%s' % i)
                this.getNSButton().setAlignment_(NSTextAlignmentRight)
                this.getNSButton().setToolTip_('Match RSB of ' + c)

                xywh = ((dfltLwidth / 2) - (width * 2.75 + (gap * 2)), yy,
                        width * 5.5 + (gap * 4), height)
                buttobj = Button(xywh,
                                 c,
                                 sizeStyle='mini',
                                 callback=self.setCmatch)
                setattr(self.sbui.C, 'buttobj_%s' % i, buttobj)
                this = getattr(self.sbui.C, 'buttobj_%s' % i)
                this.getNSButton().setToolTip_('Match Width of ' + c)

            for i, c in enumerate(self.uniquecomponents):
                try:
                    this = getattr(self.sbui.L, 'buttobj_%s' % i)
                    # hide if hidden
                    if self.showButtons == False:
                        this.show(False)
                    # check if metrics match base glyphs
                    if int(f[c].angledLeftMargin) == int(g.angledLeftMargin):
                        self.flatButt(this, True)
                    else:
                        self.flatButt(this)

                    this = getattr(self.sbui.R, 'buttobj_%s' % i)
                    if self.showButtons == False:
                        this.show(False)
                    if int(f[c].angledRightMargin) == int(g.angledRightMargin):
                        self.flatButt(this, True)
                    else:
                        self.flatButt(this)

                    this = getattr(self.sbui.C, 'buttobj_%s' % i)
                    if self.showButtons == False:
                        this.show(False)
                    if f[c].width == g.width:
                        self.flatButt(this, True)
                    else:
                        self.flatButt(this)

                except Exception as e:
                    return

            # change height of canvas to fit buttons
            self.newheight = height * (len(self.uniquecomponents) + 2)
            newy = -55 - height * (len(self.uniquecomponents))
            self.sbui.setPosSize((margin, newy, -margin, self.newheight))
            self.sbui.L.setPosSize((0, 0, dfltLwidth, self.newheight))
            self.sbui.R.setPosSize(
                (-dfltRwidth, 0, dfltRwidth, self.newheight))
            winX, winY, winW, winH = self.window.getVisibleRect()
            winW = winW - margin * 5
            offsetcenter = (winW / 2) - (width * 2.25)
            self.sbui.C.setPosSize(
                (offsetcenter, 0, width * 5 + 12, self.newheight))

        except Exception as e:
            return

    #############################################
    # watch for glyph changes
    #############################################

    def viewDidChangeGlyph(self, notification):
        self.glyph = CurrentGlyph()
        self.unsubscribeGlyph()
        self.subscribeGlyph()
        self.glyphChanged()

    def subscribeGlyph(self):
        self.glyph.addObserver(self, 'glyphChanged', 'Glyph.Changed')

    def unsubscribeGlyph(self):
        if self.glyph is None:
            return
        self.glyph.removeObserver(self, 'Glyph.Changed')

    def glyphChanged(self, notification=None):
        self.updateValues()
Пример #8
0
class OCCToucheTool():
    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 = Window((180, self.windowHeight),
                        u'2ché!',
                        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")

        # Modification(Nic): prev / next buttons
        self.w.options.prevButton = Button((w + 22, 35, 10, 10),
                                           "Prev Button",
                                           callback=self.prevPair_)
        self.w.options.prevButton.bind("uparrow", [])

        self.w.options.nextButton = Button((w + 22, 35, 10, 10),
                                           "Next Button",
                                           callback=self.nextPair_)
        self.w.options.nextButton.bind("downarrow", [])

        # /Modification(Nic)

        # Modification(Nic): prev / next buttons

        self.w.options.decrementKerningHigh = Button(
            (w + 22, 35, 10, 10),
            "Decrement Kerning High",
            callback=self.decrementKerningByHigh_)
        self.w.options.decrementKerningHigh.bind("leftarrow", ["command"])

        self.w.options.decrementKerningLow = Button(
            (w + 22, 35, 10, 10),
            "Decrement Kerning Low",
            callback=self.decrementKerningByLow_)
        self.w.options.decrementKerningLow.bind("leftarrow", [])

        self.w.options.incrementKerningHigh = Button(
            (w + 22, 35, 10, 10),
            "Increment Kerning High",
            callback=self.incrementKerningByHigh_)
        self.w.options.incrementKerningHigh.bind("rightarrow", ["command"])

        self.w.options.incrementKerningLow = Button(
            (w + 22, 35, 10, 10),
            "Increment Kerning Low",
            callback=self.incrementKerningByLow_)
        self.w.options.incrementKerningLow.bind("rightarrow", [])

        # /Modification(Nic)

        # 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

    # Modification(Nic): Incrementing and Decrementing the Pair Index in the list.

    def prevPair_(self, sender=None):
        currentIndices = self.w.outputList.getSelection()
        prevIndices = map(lambda i: max(i - 1, 0), currentIndices)
        if (len(self.w.outputList) > 0):
            self.w.outputList.setSelection(prevIndices)

    def nextPair_(self, sender=None):
        currentIndices = self.w.outputList.getSelection()
        nextIndices = map(lambda i: min(i + 1,
                                        len(self.w.outputList) - 1),
                          currentIndices)
        if (len(self.w.outputList) > 0):
            self.w.outputList.setSelection(nextIndices)

    # /Modification(Nic)

    # Modification(Nic): Incrementing and Decrementing the Pair Index in the list.

    def decrementKerningByLow_(self, sender=None):
        print('dec by low')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementLow']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], -increment)

    def decrementKerningByHigh_(self, sender=None):
        print('dec by high')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementHigh']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], -increment)

    def incrementKerningByLow_(self, sender=None):
        print('inc by low')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementLow']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], increment)

    def incrementKerningByHigh_(self, sender=None):
        print('inc by high')
        currentIndices = self.w.outputList.getSelection()
        currentPair = self.w.outputList[currentIndices[0]]
        increment = Glyphs.intDefaults['GSKerningIncrementHigh']
        self.bumpKerningForPair(currentPair['left glyph'],
                                currentPair['right glyph'], increment)

    # /Modification(Nic)

    # Modification(Nic): Routine to increment/decrement kerning properly
    def bumpKerningForPair(self, left, right, increment):
        leftGlyph, rightGlyph = Glyphs.font[left], Glyphs.font[right]
        k = Glyphs.font.kerningForPair(Glyphs.font.selectedFontMaster.id,
                                       leftGlyph.rightKerningKey,
                                       rightGlyph.leftKerningKey)
        if k > 10000: k = 0  # Glyphs uses MAXINT to signal no kerning.
        Glyphs.font.setKerningForPair(Glyphs.font.selectedFontMaster.id,
                                      leftGlyph.rightKerningKey,
                                      rightGlyph.leftKerningKey, k + increment)

    # Modification(Nic)

    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 = OCCTouche(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'2ché: finished checking %d glyphs in %.2f seconds' % (
                len(glyphList), time1 - time0)

        else:
            Message(u'2ché: Can’t find a font to check')
Пример #9
0
class KerningController(BaseWindowController):
    """this is the main controller of TT kerning editor, it handles different controllers and dialogues with font data"""

    # these attributes take good care of undo/redo stack
    archive = []
    recordIndex = 0

    displayedWord = ''
    displayedPairs = []
    activePair = None

    canvasScalingFactor = CANVAS_SCALING_FACTOR_INIT

    fontsOrder = None
    navCursor_X = 0  # related to pairs
    navCursor_Y = 0  # related to active fonts

    isPreviewOn = False
    areVerticalLettersDrawn = True
    areGroupsShown = True
    areCollisionsShown = False
    isKerningDisplayActive = True
    isSidebearingsActive = True
    isCorrectionActive = True
    isMetricsActive = True
    isColorsActive = True
    prevGraphicsValues = None

    isSymmetricalEditingOn = False
    isSwappedEditingOn = False
    isVerticalAlignedEditingOn = False

    autoSave = True
    autoSaveSpan = 5  # mins

    kerningLogger = None

    def __init__(self):
        super(KerningController, self).__init__()
        # init time for the autosave
        self.initTime = datetime.now()
        # init logging
        self._initLogger()
        # init fonts
        if AllFonts() == []:
            message(
                'No fonts, no party!',
                'Please, open some fonts before starting the mighty MultiFont Kerning Controller'
            )
            return None

        self.w = Window((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT),
                        PLUGIN_TITLE,
                        minSize=(PLUGIN_WIDTH, PLUGIN_HEIGHT))
        self.w.bind('resize', self.mainWindowResize)

        # load opened fonts
        self.initFontsOrder()

        # exception window (will appear only if needed)
        self.exceptionWindow = ChooseExceptionWindow(
            ['group2glyph', 'glyph2group', 'glyph2glyph'],
            callback=self.exceptionWindowCallback)
        self.exceptionWindow.enable(False)

        # jump to line window (will appear only if invoked)
        self.jumpToLineWindow = JumpToLineWindow(
            callback=self.jumpToLineWindowCallback)
        self.jumpToLineWindow.enable(False)

        self.jumping_Y = MARGIN_VER
        self.jumping_X = MARGIN_HOR
        self.w.wordListController = WordListController(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN, 260),
            callback=self.wordListControllerCallback)
        self.displayedWord = self.w.wordListController.get()

        self.jumping_Y += self.w.wordListController.getPosSize()[3] + 4
        self.w.word_font_separationLine = HorizontalLine(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN,
             vanillaControlsSize['HorizontalLineThickness']))

        self.jumping_Y += MARGIN_VER
        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(
            self.fontsOrder) + MARGIN_HOR
        self.w.fontsOrderController = FontsOrderController(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN,
             fontsOrderControllerHeight),
            self.fontsOrder,
            callback=self.fontsOrderControllerCallback)
        self.jumping_Y += fontsOrderControllerHeight
        self.w.fonts_controller_separationLine = HorizontalLine(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN,
             vanillaControlsSize['HorizontalLineThickness']))

        self.jumping_Y += MARGIN_VER
        self.w.joystick = JoystickController(
            (self.jumping_X, self.jumping_Y, LEFT_COLUMN, 348),
            fontObj=self.fontsOrder[self.navCursor_Y],
            isSymmetricalEditingOn=self.isSymmetricalEditingOn,
            isSwappedEditingOn=self.isSwappedEditingOn,
            isVerticalAlignedEditingOn=self.isVerticalAlignedEditingOn,
            autoSave=self.autoSave,
            autoSaveSpan=self.autoSaveSpan,
            activePair=None,
            callback=self.joystickCallback)

        self.jumping_Y += self.w.joystick.getPosSize()[3] + MARGIN_VER
        self.w.graphicsManager = GraphicsManager(
            (self.jumping_X, -202, LEFT_COLUMN, 202),
            isKerningDisplayActive=self.isKerningDisplayActive,
            areVerticalLettersDrawn=self.areVerticalLettersDrawn,
            areGroupsShown=self.areGroupsShown,
            areCollisionsShown=self.areCollisionsShown,
            isSidebearingsActive=self.isSidebearingsActive,
            isCorrectionActive=self.isCorrectionActive,
            isMetricsActive=self.isMetricsActive,
            isColorsActive=self.isColorsActive,
            callback=self.graphicsManagerCallback)

        self.jumping_X += LEFT_COLUMN + MARGIN_COL * 2
        self.jumping_Y = MARGIN_VER

        self.w.displayedWordCaption = TextBox(
            (self.jumping_X, self.jumping_Y, 200,
             vanillaControlsSize['TextBoxRegularHeight']), self.displayedWord)

        self.w.scalingFactorController = FactorController(
            (-MARGIN_HOR - 72, self.jumping_Y, 72,
             vanillaControlsSize['TextBoxRegularHeight']),
            self.canvasScalingFactor,
            callback=self.scalingFactorControllerCallback)

        self.jumping_Y += self.w.displayedWordCaption.getPosSize(
        )[3] + MARGIN_COL
        self.initWordDisplays()
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())

        # observers!
        addObserver(self, 'openCloseFontCallback', "fontDidOpen")
        addObserver(self, 'openCloseFontCallback', "fontDidClose")
        self.setUpBaseWindowBehavior()
        self.w.open()

    def _initLogger(self):
        # create a logger
        self.kerningLogger = logging.getLogger('kerningLogger')
        self.kerningLogger.setLevel(logging.INFO)
        # create file handler which logs info messages
        fileHandle = logging.FileHandler('kerningLogger.log')
        fileHandle.setLevel(logging.INFO)
        # create console handler with a higher log level, only errors
        consoleHandler = logging.StreamHandler()
        consoleHandler.setLevel(logging.ERROR)
        # create formatter and add it to the handlers
        formatter = logging.Formatter(u'%(asctime)s – %(message)s')
        fileHandle.setFormatter(formatter)
        consoleHandler.setFormatter(formatter)
        # add the handlers to the logger
        self.kerningLogger.addHandler(fileHandle)
        self.kerningLogger.addHandler(consoleHandler)

    def windowCloseCallback(self, sender):
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontWillClose")
        self.exceptionWindow.close()
        super(KerningController, self).windowCloseCallback(sender)
        self.w.close()

    def openCloseFontCallback(self, sender):
        self.deleteWordDisplays()
        self.initFontsOrder()
        self.w.fontsOrderController.setFontsOrder(self.fontsOrder)
        self.initWordDisplays()

        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(
            self.fontsOrder) + MARGIN_HOR
        prevFontsOrderPos = self.w.fontsOrderController.getPosSize()
        self.w.fontsOrderController.setPosSize(
            (prevFontsOrderPos[0], prevFontsOrderPos[1], prevFontsOrderPos[2],
             fontsOrderControllerHeight))

        prevSepLinePos = self.w.fonts_controller_separationLine.getPosSize()
        self.w.fonts_controller_separationLine.setPosSize(
            (prevSepLinePos[0],
             prevFontsOrderPos[1] + fontsOrderControllerHeight,
             prevSepLinePos[2], prevSepLinePos[3]))

        prevJoystickPos = self.w.joystick.getPosSize()
        self.w.joystick.setPosSize(
            (prevJoystickPos[0],
             prevFontsOrderPos[1] + fontsOrderControllerHeight + MARGIN_VER,
             prevJoystickPos[2], prevJoystickPos[3]))

    def initFontsOrder(self):
        if self.fontsOrder is None:
            fontsOrder = [f for f in AllFonts() if f.path is not None]
            self.fontsOrder = sorted(fontsOrder,
                                     key=lambda f: os.path.basename(f.path))
        else:
            newFontsOrder = [f for f in AllFonts() if f in self.fontsOrder] + [
                f for f in AllFonts() if f not in self.fontsOrder
            ]
            self.fontsOrder = newFontsOrder

        for eachFont in self.fontsOrder:
            status, report = checkGroupConflicts(eachFont)
            if status is False:
                self.kerningLogger.error('groups conflict in {}'.format(
                    eachFont.path))
                self.kerningLogger.error(report)

    def deleteWordDisplays(self):
        for eachI in xrange(len(self.fontsOrder)):
            try:
                delattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
                self.jumping_Y = MARGIN_VER + vanillaControlsSize[
                    'TextBoxRegularHeight']
            except Exception as e:
                self.kerningLogger.error(traceback.format_exc())

    def initWordDisplays(self):
        windowWidth, windowHeight = self.w.getPosSize()[2], self.w.getPosSize(
        )[3]
        netTotalWindowHeight = windowHeight - MARGIN_COL - MARGIN_VER * 2 - vanillaControlsSize[
            'TextBoxRegularHeight'] - MARGIN_HOR * (len(self.fontsOrder) - 1)

        try:
            singleWindowHeight = netTotalWindowHeight / len(self.fontsOrder)
        except ZeroDivisionError:
            singleWindowHeight = 0

        rightColumnWidth = windowWidth - LEFT_COLUMN - MARGIN_COL

        self.jumping_Y = MARGIN_VER + vanillaControlsSize[
            'TextBoxRegularHeight'] + MARGIN_COL
        for eachI in xrange(len(self.fontsOrder)):

            if eachI == self.navCursor_Y:
                initPairIndex = self.navCursor_X
            else:
                initPairIndex = None

            try:
                wordCtrl = WordDisplay(
                    (self.jumping_X, self.jumping_Y, rightColumnWidth,
                     singleWindowHeight),
                    displayedWord=self.displayedWord,
                    canvasScalingFactor=self.canvasScalingFactor,
                    fontObj=self.fontsOrder[eachI],
                    isKerningDisplayActive=self.isKerningDisplayActive,
                    areVerticalLettersDrawn=self.areVerticalLettersDrawn,
                    areGroupsShown=self.areGroupsShown,
                    areCollisionsShown=self.areCollisionsShown,
                    isSidebearingsActive=self.isSidebearingsActive,
                    isCorrectionActive=self.isCorrectionActive,
                    isMetricsActive=self.isMetricsActive,
                    isColorsActive=self.isColorsActive,
                    isSymmetricalEditingOn=self.isSymmetricalEditingOn,
                    isSwappedEditingOn=self.isSwappedEditingOn,
                    indexPair=initPairIndex)

            except Exception:
                self.kerningLogger.error(traceback.format_exc())

            self.jumping_Y += singleWindowHeight + MARGIN_HOR
            setattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1), wordCtrl)

    def updateWordDisplays(self):
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            eachDisplay.setSymmetricalEditingMode(self.isSymmetricalEditingOn)
            eachDisplay.setSwappedEditingMode(self.isSwappedEditingOn)
            eachDisplay.setScalingFactor(self.canvasScalingFactor)
            eachDisplay.setGraphicsBooleans(
                self.isKerningDisplayActive, self.areVerticalLettersDrawn,
                self.areGroupsShown, self.areCollisionsShown,
                self.isSidebearingsActive, self.isCorrectionActive,
                self.isMetricsActive, self.isColorsActive)
            eachDisplay.wordCanvasGroup.update()

    def nextWord(self, isRecording=True):
        self.w.wordListController.nextWord()
        self.displayedWord = self.w.wordListController.get()
        self.isSwappedEditingOn = False
        self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateEditorAccordingToDiplayedWord()
        if isRecording is True:
            self.appendRecord('nextWord')

    def previousWord(self, isRecording=True):
        self.w.wordListController.previousWord()
        self.displayedWord = self.w.wordListController.get()
        self.isSwappedEditingOn = False
        self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateEditorAccordingToDiplayedWord()
        if isRecording is True:
            self.appendRecord('previousWord')

    def jumpToLine(self, lineIndex, isRecording=True):
        if isRecording is True:
            self.appendRecord(
                'jumpToLine',
                (self.w.wordListController.getActiveIndex(), lineIndex))

        self.w.wordListController.jumpToLine(lineIndex)
        self.displayedWord = self.w.wordListController.get()
        self.isSwappedEditingOn = False
        self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateEditorAccordingToDiplayedWord()

    def oneStepGroupSwitch(self, location):
        if self.isVerticalAlignedEditingOn is True:
            for eachI in xrange(len(self.fontsOrder)):
                eachDisplay = getattr(self.w,
                                      'wordCtrl_{:0>2d}'.format(eachI + 1))
                eachDisplay.switchGlyphFromGroup(location, self.navCursor_X)
        else:
            self.getActiveWordDisplay().switchGlyphFromGroup(
                location, self.navCursor_X)
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())

    def updateEditorAccordingToDiplayedWord(self):
        self.w.displayedWordCaption.set(self.displayedWord)
        if len(self.displayedWord) - 1 < (self.navCursor_X + 1):
            self.navCursor_X = len(self.displayedWord) - 2
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))

            if self.isVerticalAlignedEditingOn is False:
                if eachI == self.navCursor_Y:
                    eachDisplay.setActivePairIndex(self.navCursor_X)
            else:
                eachDisplay.setActivePairIndex(self.navCursor_X)

            eachDisplay.setDisplayedWord(self.displayedWord)
        self.updateWordDisplays()

        self.getActiveWordDisplay().setActivePairIndex(self.navCursor_X)
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())

    def getActiveWordDisplay(self):
        return getattr(self.w, 'wordCtrl_{:0>2d}'.format(self.navCursor_Y + 1))

    def setGraphicsManagerForPreviewMode(self):

        self.prevGraphicsValues = (self.isKerningDisplayActive,
                                   self.areVerticalLettersDrawn,
                                   self.areGroupsShown,
                                   self.areCollisionsShown,
                                   self.isSidebearingsActive,
                                   self.isCorrectionActive,
                                   self.isMetricsActive, self.isColorsActive)

        # set preview mode variables
        self.isKerningDisplayActive = True
        self.areVerticalLettersDrawn = False
        self.areGroupsShown = False
        self.areCollisionsShown = False
        self.isSidebearingsActive = False
        self.isCorrectionActive = False
        self.isMetricsActive = False
        self.isColorsActive = False
        self.w.graphicsManager.set(
            self.isKerningDisplayActive, self.areVerticalLettersDrawn,
            self.areGroupsShown, self.areCollisionsShown,
            self.isSidebearingsActive, self.isCorrectionActive,
            self.isMetricsActive, self.isColorsActive)

    def restoreGraphicsManagerValues(self):
        self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive = self.prevGraphicsValues
        self.prevGraphicsValues = None
        self.w.graphicsManager.set(
            self.isKerningDisplayActive, self.areVerticalLettersDrawn,
            self.areGroupsShown, self.areCollisionsShown,
            self.isSidebearingsActive, self.isCorrectionActive,
            self.isMetricsActive, self.isColorsActive)

    def switchPreviewAttribute(self, isRecording=True):
        if self.isPreviewOn is True:
            self.isPreviewOn = False
            self.restoreGraphicsManagerValues()
            self.w.graphicsManager.switchControls(True)
        else:
            self.isPreviewOn = True
            self.setGraphicsManagerForPreviewMode()
            self.w.graphicsManager.switchControls(False)
        self.updateWordDisplays()

        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('preview')

    def switchSolvedAttribute(self, isRecording=True):
        self.w.wordListController.switchActiveWordSolvedAttribute()
        if isRecording is True:
            self.appendRecord('solved')

    def switchSwappedEditing(self, isRecording=True):
        self.isSwappedEditingOn = not self.isSwappedEditingOn
        if self.isSwappedEditingOn is True and self.isSymmetricalEditingOn is True:
            self.isSymmetricalEditingOn = False
            self.w.joystick.setSymmetricalEditing(self.isSymmetricalEditingOn)
        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('swappedEditing')

    def switchSymmetricalEditing(self, isRecording=True):
        self.isSymmetricalEditingOn = not self.isSymmetricalEditingOn
        if self.isSwappedEditingOn is True and self.isSymmetricalEditingOn is True:
            self.isSwappedEditingOn = False
            self.w.joystick.setSwappedEditing(self.isSwappedEditingOn)
        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('symmetricalEditing')

    def switchVerticalAlignedEditing(self, isRecording=True):
        self.isVerticalAlignedEditingOn = not self.isVerticalAlignedEditingOn
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            if self.isVerticalAlignedEditingOn is True:
                eachDisplay.setActivePairIndex(self.navCursor_X)
            else:
                if eachI != self.navCursor_Y:
                    eachDisplay.setActivePairIndex(None)
        self.updateWordDisplays()
        if isRecording is True:
            self.appendRecord('verticalAlignedEditing')

    def exceptionTrigger(self):
        activeFont = self.fontsOrder[self.navCursor_Y]
        selectedPair = self.getActiveWordDisplay().getActivePair()
        correction, kerningReference, pairKind = getCorrection(
            selectedPair, activeFont)
        isException, doesExists, parentPair = isPairException(
            kerningReference, activeFont)

        # delete exception
        if isException:
            if self.isVerticalAlignedEditingOn is True:
                selectedFonts = self.fontsOrder
            else:
                selectedFonts = [self.fontsOrder[self.navCursor_Y]]

            for eachFont in selectedFonts:
                isException, doesExists, parentPair = isPairException(
                    kerningReference, eachFont)
                if isException:
                    deletePair(kerningReference, eachFont)
                    self.appendRecord('deletePair',
                                      (kerningReference, eachFont, correction))

        else:
            if not correction:
                # set standard pair to zero
                self.setPairCorrection(0)
                correction, kerningReference, pairKind = getCorrection(
                    selectedPair, activeFont)
                isException, doesExists, parentPair = isPairException(
                    kerningReference, activeFont)

            # trigger exception window
            exceptionOptions = possibleExceptions(selectedPair,
                                                  kerningReference, activeFont)
            if len(exceptionOptions) == 1:
                self.exceptionWindow.set(exceptionOptions[0])
                self.exceptionWindow.trigger()
            elif len(exceptionOptions) > 1:
                self.exceptionWindow.setOptions(exceptionOptions)
                self.exceptionWindow.enable(True)
            else:
                self.showMessage(
                    'no possible exceptions',
                    'kerning exceptions can be triggered only starting from class kerning corrections'
                )

        self.updateWordDisplays()

    # manipulate data
    def deletePair(self, isRecording=True):
        if self.autoSave is True:
            self.checkAutoSave()

        selectedPair = self.getActiveWordDisplay().getActivePair()
        if self.isVerticalAlignedEditingOn is True:
            selectedFonts = self.fontsOrder
        else:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]

        for eachFont in selectedFonts:
            previousAmount, kerningReference, pairKind = getCorrection(
                selectedPair, eachFont)
            deletePair(kerningReference, eachFont)
            if isRecording is True:
                self.appendRecord('deletePair',
                                  (kerningReference, eachFont, previousAmount))
            self.kerningLogger.info(
                DELETE_PAIR_LOG.format(leftGlyphName=selectedPair[0],
                                       rightGlyphName=selectedPair[1],
                                       familyName=eachFont.info.familyName,
                                       styleName=eachFont.info.styleName))

            if self.isSwappedEditingOn is True:
                swappedCorrectionKey = selectedPair[1], selectedPair[0]
                previousAmount, kerningReference, pairKind = getCorrection(
                    swappedCorrectionKey, eachFont)
                deletePair(kerningReference, eachFont)
                if isRecording is True:
                    self.appendRecord('deletePair', kerningReference, eachFont,
                                      previousAmount)
                self.kerningLogger.info(
                    DELETE_PAIR_LOG.format(leftGlyphName=kerningReference[0],
                                           rightGlyphName=kerningReference[1],
                                           familyName=eachFont.info.familyName,
                                           styleName=eachFont.info.styleName))

            if self.isSymmetricalEditingOn is True:
                symmetricalCorrectionKey = findSymmetricalPair(selectedPair)
                previousAmount, kerningReference, pairKind = getCorrection(
                    symmetricalCorrectionKey, eachFont)
                deletePair(kerningReference, eachFont)
                if isRecording is True:
                    self.appendRecord('deletePair', kerningReference, eachFont,
                                      previousAmount)
                self.kerningLogger.info(
                    DELETE_PAIR_LOG.format(leftGlyphName=kerningReference[0],
                                           rightGlyphName=kerningReference[1],
                                           familyName=eachFont.info.familyName,
                                           styleName=eachFont.info.styleName))

        self.updateWordDisplays()

    def setPairCorrection(self, amount, isRecording=True):
        if self.autoSave is True:
            self.checkAutoSave()

        selectedPair = self.getActiveWordDisplay().getActivePair()
        if self.isVerticalAlignedEditingOn is True:
            selectedFonts = self.fontsOrder
        else:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]

        for eachFont in selectedFonts:
            if isRecording is True:
                previousAmount = getCorrection(selectedPair, eachFont)[0]
                self.appendRecord('setCorrection',
                                  (selectedPair, eachFont, previousAmount))
            setCorrection(selectedPair, eachFont, amount)
            self.kerningLogger.info(
                SET_CORRECTION_LOG.format(leftGlyphName=selectedPair[0],
                                          rightGlyphName=selectedPair[1],
                                          familyName=eachFont.info.familyName,
                                          styleName=eachFont.info.styleName,
                                          amount=amount))

            if self.isSwappedEditingOn is True:
                swappedCorrectionKey = selectedPair[1], selectedPair[0]
                if isRecording is True:
                    previousAmount = getCorrection(swappedCorrectionKey,
                                                   eachFont)[0]
                    self.appendRecord(
                        'setCorrection',
                        (swappedCorrectionKey, eachFont, previousAmount))
                setCorrection(swappedCorrectionKey, eachFont, amount)
                self.kerningLogger.info(
                    SET_CORRECTION_LOG.format(
                        leftGlyphName=swappedCorrectionKey[0],
                        rightGlyphName=swappedCorrectionKey[1],
                        familyName=eachFont.info.familyName,
                        styleName=eachFont.info.styleName,
                        amount=amount))

            if self.isSymmetricalEditingOn is True:
                symmetricalCorrectionKey = findSymmetricalPair(selectedPair)
                if symmetricalCorrectionKey:
                    if isRecording is True:
                        previousAmount = getCorrection(
                            symmetricalCorrectionKey, eachFont)[0]
                        self.appendRecord('setCorrection',
                                          (symmetricalCorrectionKey, eachFont,
                                           previousAmount))
                    setCorrection(symmetricalCorrectionKey, eachFont, amount)
                    self.kerningLogger.info(
                        SET_CORRECTION_LOG.format(
                            leftGlyphName=symmetricalCorrectionKey[0],
                            rightGlyphName=symmetricalCorrectionKey[1],
                            familyName=eachFont.info.familyName,
                            styleName=eachFont.info.styleName,
                            amount=amount))

        self.updateWordDisplays()

    def modifyPairCorrection(self, amount, isRecording=True):
        if self.autoSave is True:
            self.checkAutoSave()

        selectedPair = self.getActiveWordDisplay().getActivePair()

        if self.isVerticalAlignedEditingOn is True:
            selectedFonts = self.fontsOrder
        else:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]

        for eachFont in selectedFonts:
            correction, correctionKey, pairKind = getCorrection(
                selectedPair, eachFont)
            correction = 0 if correction is None else correction
            if isRecording is True:
                previousAmount = getCorrection(selectedPair, eachFont)[0]
                self.appendRecord('setCorrection',
                                  (selectedPair, eachFont, previousAmount))
            setCorrection(selectedPair, eachFont, correction + amount)
            self.kerningLogger.info(
                SET_CORRECTION_LOG.format(leftGlyphName=selectedPair[0],
                                          rightGlyphName=selectedPair[1],
                                          familyName=eachFont.info.familyName,
                                          styleName=eachFont.info.styleName,
                                          amount=amount))

            if self.isSwappedEditingOn is True:
                swappedPair = selectedPair[1], selectedPair[0]
                if isRecording is True:
                    previousAmount = getCorrection(selectedPair, eachFont)[0]
                    self.appendRecord('setCorrection',
                                      (swappedPair, eachFont, previousAmount))
                setCorrection(swappedPair, eachFont, correction + amount)
                self.kerningLogger.info(
                    SET_CORRECTION_LOG.format(
                        leftGlyphName=swappedPair[0],
                        rightGlyphName=swappedPair[1],
                        familyName=eachFont.info.familyName,
                        styleName=eachFont.info.styleName,
                        amount=amount))

            if self.isSymmetricalEditingOn is True:
                symmetricalCorrectionKey = findSymmetricalPair(selectedPair)
                if symmetricalCorrectionKey:
                    if isRecording is True:
                        previousAmount = getCorrection(
                            symmetricalCorrectionKey, eachFont)[0]
                        self.appendRecord('setCorrection',
                                          (symmetricalCorrectionKey, eachFont,
                                           previousAmount))
                    setCorrection(symmetricalCorrectionKey, eachFont,
                                  correction + amount)
                    self.kerningLogger.info(
                        SET_CORRECTION_LOG.format(
                            leftGlyphName=symmetricalCorrectionKey[0],
                            rightGlyphName=symmetricalCorrectionKey[1],
                            familyName=eachFont.info.familyName,
                            styleName=eachFont.info.styleName,
                            amount=amount))

        self.w.joystick.updateCorrectionValue()
        self.updateWordDisplays()

    # cursor methods
    def cursorLeftRight(self, direction, isRecording=True):
        assert direction in ['left', 'right']

        if direction == 'left':
            step = -1
            if isRecording is True:
                self.appendRecord('cursorLeft')
        else:
            step = +1
            if isRecording is True:
                self.appendRecord('cursorRight')

        self.navCursor_X = (self.navCursor_X +
                            step) % (len(self.displayedWord) - 1)
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            if self.isVerticalAlignedEditingOn is False:
                if eachI == self.navCursor_Y:
                    eachDisplay.setActivePairIndex(self.navCursor_X)
            else:
                eachDisplay.setActivePairIndex(self.navCursor_X)
        self.w.joystick.setActivePair(
            self.getActiveWordDisplay().getActivePair())
        self.updateWordDisplays()

    def cursorUpDown(self, direction, isRecording=True):
        assert direction in ['up', 'down']
        if direction == 'up':
            step = -1
            if isRecording is True:
                self.appendRecord('cursorUp')
        else:
            step = +1
            if isRecording is True:
                self.appendRecord('cursorDown')

        if self.isVerticalAlignedEditingOn is False:
            self.getActiveWordDisplay().setActivePairIndex(None)  # old
            self.navCursor_Y = (self.navCursor_Y + step) % len(self.fontsOrder)
            self.getActiveWordDisplay().setActivePairIndex(
                self.navCursor_X)  # new
            self.w.joystick.setFontObj(self.fontsOrder[self.navCursor_Y])
            self.updateWordDisplays()

    ### callbacks
    def exceptionWindowCallback(self, sender):
        if self.isVerticalAlignedEditingOn is False:
            selectedFonts = [self.fontsOrder[self.navCursor_Y]]
        else:
            selectedFonts = self.fontsOrder
        selectedPair = self.getActiveWordDisplay().getActivePair()
        if sender.lastEvent == 'submit':
            for indexFont, eachFont in enumerate(selectedFonts):
                exceptionKey = sender.get()
                correction, kerningReference, pairKind = getCorrection(
                    selectedPair, eachFont)
                setRawCorrection(exceptionKey, eachFont, correction)
                self.appendRecord('createException',
                                  (exceptionKey, eachFont, correction))
                if indexFont == self.navCursor_Y:
                    self.w.joystick.updateCorrectionValue()
            self.updateWordDisplays()

    def jumpToLineWindowCallback(self, sender):
        if sender.get() is not None:
            self.jumpToLine(sender.get())
        self.jumpToLineWindow.enable(False)

    def mainWindowResize(self, mainWindow):
        windowWidth, windowHeight = mainWindow.getPosSize(
        )[2], mainWindow.getPosSize()[3]
        rightColumnWidth = windowWidth - LEFT_COLUMN

        # caption
        prevdisplayedWordCaptionSize = self.w.displayedWordCaption.getPosSize()
        self.w.displayedWordCaption.setPosSize(
            (prevdisplayedWordCaptionSize[0], prevdisplayedWordCaptionSize[1],
             rightColumnWidth, prevdisplayedWordCaptionSize[3]))

        # displayers
        initY = MARGIN_VER + vanillaControlsSize[
            'TextBoxRegularHeight'] + MARGIN_COL
        netTotalWindowHeight = windowHeight - initY - MARGIN_VER - MARGIN_HOR * (
            len(self.fontsOrder) - 1)

        try:
            singleWordDisplayHeight = netTotalWindowHeight / len(
                self.fontsOrder)
        except ZeroDivisionError:
            singleWordDisplayHeight = 0

        y = initY
        for eachI in xrange(len(self.fontsOrder)):
            eachDisplay = getattr(self.w, 'wordCtrl_{:0>2d}'.format(eachI + 1))
            eachDisplay.adjustSize(
                (self.jumping_X, y, rightColumnWidth, singleWordDisplayHeight))
            eachDisplay.setCtrlSize(rightColumnWidth, singleWordDisplayHeight)
            y += singleWordDisplayHeight + MARGIN_HOR

    def scalingFactorControllerCallback(self, sender):
        self.canvasScalingFactor = sender.getScalingFactor()
        self.updateWordDisplays()

    def wordListControllerCallback(self, sender):
        self.displayedWord = sender.get()
        self.updateEditorAccordingToDiplayedWord()

    def fontsOrderControllerCallback(self, sender):
        self.deleteWordDisplays()
        self.fontsOrder = []
        for indexFont, eachFont in enumerate(sender.getFontsOrder()):
            if sender.getIsDisplayedOrder()[indexFont] is True:
                self.fontsOrder.append(eachFont)
        self.initWordDisplays()

    def graphicsManagerCallback(self, sender):
        self.isKerningDisplayActive, self.areVerticalLettersDrawn, self.areGroupsShown, self.areCollisionsShown, self.isSidebearingsActive, self.isCorrectionActive, self.isMetricsActive, self.isColorsActive = sender.get(
        )
        self.updateWordDisplays()

    def joystickCallback(self, sender):
        joystickEvent = sender.getLastEvent()
        assert joystickEvent in JOYSTICK_EVENTS

        if joystickEvent == 'minusMajor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(-MAJOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'minusMinor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(-MINOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'plusMinor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(MINOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'plusMajor':
            if self.isKerningDisplayActive is True:
                self.modifyPairCorrection(MAJOR_STEP)
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)

        elif joystickEvent == 'preview':
            self.switchPreviewAttribute()

        elif joystickEvent == 'solved':
            self.switchSolvedAttribute()
            self.nextWord()

        elif joystickEvent == 'symmetricalEditing':
            self.switchSymmetricalEditing()

        elif joystickEvent == 'swappedEditing':
            self.switchSwappedEditing()

        elif joystickEvent == 'verticalAlignedEditing':
            self.switchVerticalAlignedEditing()

        elif joystickEvent == 'previousWord':
            self.previousWord()

        elif joystickEvent == 'cursorUp':
            self.cursorUpDown('up')

        elif joystickEvent == 'cursorLeft':
            self.cursorLeftRight('left')

        elif joystickEvent == 'cursorRight':
            self.cursorLeftRight('right')

        elif joystickEvent == 'cursorDown':
            self.cursorUpDown('down')

        elif joystickEvent == 'nextWord':
            self.nextWord()

        elif joystickEvent == 'deletePair':
            if self.isKerningDisplayActive is True:
                self.deletePair()
                self.w.joystick.setActivePair(
                    self.getActiveWordDisplay().getActivePair())

        elif joystickEvent == 'switchLftGlyph':
            self.oneStepGroupSwitch(location='left')

        elif joystickEvent == 'switchRgtGlyph':
            self.oneStepGroupSwitch(location='right')

        elif joystickEvent == 'keyboardEdit':
            if self.isKerningDisplayActive is True:
                correctionAmount = self.w.joystick.getKeyboardCorrection()
                if correctionAmount is None:
                    self.deletePair()
                else:
                    self.setPairCorrection(correctionAmount)
                self.updateWordDisplays()
            else:
                self.showMessage('Be aware!',
                                 KERNING_NOT_DISPLAYED_ERROR,
                                 callback=None)
                self.w.joystick.updateCorrectionValue()

        elif joystickEvent == 'jumpToLineTrigger':
            self.jumpToLineWindow.enable(True)

        elif joystickEvent == 'exceptionTrigger':
            self.exceptionTrigger()

        # from here on events are not archived in undo/redo stack
        elif joystickEvent == 'undo':
            self.undo()

        elif joystickEvent == 'redo':
            self.redo()

        elif joystickEvent == 'autoSave':
            self.autoSave, self.autoSaveSpan = self.w.joystick.getAutoSaveState(
            )

    # autosaving fonts
    def checkAutoSave(self):
        justNow = datetime.now()
        if (justNow - self.initTime).seconds > self.autoSaveSpan * 60:
            self.saveFontsOrder()
            self.initTime = datetime.now()

    def saveFontsOrder(self):
        for eachFont in self.fontsOrder:
            eachFont.save()
        self.kerningLogger.info("all fonts saved")

    # undo/redo stack
    def appendRecord(self, actionName, data=None):
        if self.recordIndex < 0:
            self.archive = self.archive[:self.recordIndex]
            self.recordIndex = 0
        if data is None:
            self.archive.append(actionName)
        else:
            self.archive.append((actionName, data))

    def undo(self):
        if abs(self.recordIndex) <= len(self.archive) - 1:
            self.recordIndex -= 1
            self.pullRecordFromArchive('undo')

    def redo(self):
        if self.recordIndex < 0:
            self.recordIndex += 1
            self.pullRecordFromArchive('redo')

    def pullRecordFromArchive(self, direction):
        """we miss these methods: switchLftGlyph, switchRgtGlyph"""

        assert direction in ['redo', 'undo']
        if direction == 'redo':
            record = self.archive[
                self.recordIndex -
                1]  # othwerwise we won't get back to the initial status
        else:
            record = self.archive[self.recordIndex]

        # these records, we can simply invert (they related to events in UI)
        if isinstance(record, types.StringType) is True:
            if record == 'nextWord':
                if direction == 'undo':
                    self.previousWord(isRecording=False)
                else:
                    self.nextWord(isRecording=False)
            elif record == 'previousWord':
                if direction == 'undo':
                    self.nextWord(isRecording=False)
                else:
                    self.previousWord(isRecording=False)
            elif record == 'preview':
                self.switchPreviewAttribute(isRecording=False)
            elif record == 'solved':
                self.switchSolvedAttribute(isRecording=False)
            elif record == 'swappedEditing':
                self.switchSwappedEditing(isRecording=False)
            elif record == 'symmetricalEditing':
                self.switchSymmetricalEditing(isRecording=False)
            elif record == 'verticalAlignedEditing':
                self.switchVerticalAlignedEditing(isRecording=False)

            elif record == 'cursorLeft':
                if direction == 'undo':
                    self.cursorLeftRight('right', isRecording=False)
                else:
                    self.cursorLeftRight('left', isRecording=False)

            elif record == 'cursorRight':
                if direction == 'undo':
                    self.cursorLeftRight('left', isRecording=False)
                else:
                    self.cursorLeftRight('right', isRecording=False)

            elif record == 'cursorUp':
                if direction == 'undo':
                    self.cursorUpDown('down', isRecording=False)
                else:
                    self.cursorUpDown('up', isRecording=False)

            elif record == 'cursorDown':
                if direction == 'undo':
                    self.cursorUpDown('up', isRecording=False)
                else:
                    self.cursorUpDown('down', isRecording=False)

        # these relate to data manipulation...
        else:
            recordTitle, data = record

            if recordTitle == 'setCorrection':
                pair, font, amount = data
                setCorrection(pair, font, amount)
                self.updateWordDisplays()

            elif recordTitle == 'createException':
                pair, font, amount = data
                if direction == 'undo':
                    deletePair(pair, font)
                else:
                    setRawCorrection(pair, font, amount)

            elif recordTitle == 'deletePair':
                pair, font, amount = data
                if direction == 'undo':
                    setRawCorrection(pair, font, amount)
                else:
                    deletePair(pair, font)

            elif recordTitle == 'jumpToLine':
                previousIndex, nextIndex = data
                if direction == 'undo':
                    self.jumpToLine(previousIndex)
                else:
                    self.jumpToLine(nextIndex, isRecording=False)
Пример #10
0
class VisualReporter(BaseWindowController):

    fontNames = []

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

        self.allFonts = AllFonts()
        self.sortAllFonts()
        self.collectFontNames()

        self.w = Window((0, 0, PLUGIN_WIDTH, 1), PLUGIN_TITLE)

        jumpingY = UI_MARGIN
        self.w.fontsPopUp = PopUpButton(
            (UI_MARGIN, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.fontNames,
            callback=None)

        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + UI_MARGIN
        self.w.reportButton = SquareButton(
            (UI_MARGIN, jumpingY, NET_WIDTH,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Generate PDF Report',
            callback=self.reportButtonCallback)
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] * 1.5 + UI_MARGIN

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

    def collectFontNames(self):
        self.fontNames = ['All Fonts'] + [
            os.path.basename(item.path) for item in self.allFonts
        ]

    def updateFontOptions(self, notification):
        self.allFonts = AllFonts()
        self.sortAllFonts()
        self.collectFontNames()
        previousFontName = self.w.fontsPopUp.get()
        self.w.fontsPopUp.setItems(self.fontNames)
        if previousFontName in self.fontNames:
            self.w.fontsPopUp.set(self.fontNames.index(previousFontName))

    def sortAllFonts(self):
        self.allFonts = sorted(
            self.allFonts, key=lambda x: STYLES_ORDER.index(x.info.styleName))

    def reportButtonCallback(self, sender):
        popUpIndex = self.w.fontsPopUp.get()
        if popUpIndex == 0:  # all fonts
            fontsToProcess = self.allFonts
        else:
            fontsToProcess = [self.allFonts[popUpIndex]]

        PDF_folder = getFolder('Choose where to save the reports')[0]
        templateFont = OpenFont(TEMPLATE_FONT_PATH, showUI=False)
        justNow = datetime.now()

        self._drawReport(
            referenceFont=templateFont,
            someFonts=fontsToProcess,
            glyphNames=templateFont.glyphOrder,
            reportPath=os.path.join(
                PDF_folder,
                '{:0>4d}{:0>2d}{:0>2d}_templateCompliant.pdf'.format(
                    justNow.year, justNow.month, justNow.day)),
            caption='Template Compliant')

        allGlyphNames = set()
        for eachFont in fontsToProcess:
            allGlyphNames = allGlyphNames | set(eachFont.glyphOrder)
        leftovers = allGlyphNames - set(templateFont.glyphOrder)

        self._drawReport(referenceFont=templateFont,
                         someFonts=fontsToProcess,
                         glyphNames=leftovers,
                         reportPath=os.path.join(
                             PDF_folder,
                             '{:0>4d}{:0>2d}{:0>2d}_extraTemplate.pdf'.format(
                                 justNow.year, justNow.month, justNow.day)),
                         caption='Extra Template')

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

    # drawing routines
    def _initPage(self, fontStyles):
        db.newPage('A3Landscape')
        db.fontSize(BODY_SIZE_TEXT)
        quota = db.height() - PDF_MARGIN
        self._drawHeader(quota, fontStyles)
        db.font('LucidaGrande')
        quota -= TAB_LINE_HEIGHT * 2
        return quota

    def _drawHeader(self, quota, fontStyles):
        fontTitles = {}

        colIndex = int(COLS['template'] / TAB_WIDTH)
        for eachFontStyle in fontStyles:
            colIndex += 1
            fontTitles[eachFontStyle] = colIndex * TAB_WIDTH

        headers = {k: v for (k, v) in COLS.items() + fontTitles.items()}
        db.font('LucidaGrande-Bold')
        for eachTitle, eachX in headers.items():
            db.text(eachTitle, (PDF_MARGIN + eachX, quota))

    def _drawReport(self, referenceFont, someFonts, glyphNames, reportPath,
                    caption):
        assert isinstance(reportPath, str) or isinstance(
            reportPath, unicode), 'this should be a string or unicode'
        assert isinstance(someFonts, list), 'this should be a list of RFont'

        prog = ProgressWindow(text='{}: drawing glyphs...'.format(caption),
                              tickCount=len(glyphNames))

        try:
            db.newDrawing()

            twoLinesFontStyles = [
                ff.info.styleName.replace(' ', '\n') for ff in someFonts
            ]
            quota = self._initPage(twoLinesFontStyles)
            for indexName, eachGlyphName in enumerate(glyphNames):
                db.save()
                db.translate(PDF_MARGIN, quota)

                # set name
                for eachSetName, eachGroup in SMART_SETS:
                    if eachGlyphName in eachGroup:
                        setName = eachSetName[3:].replace('.txt', '').replace(
                            '_', ' ')
                        break
                else:
                    setName = ''
                db.text(setName, (COLS['set name'], 0))

                # line number
                db.fill(*BLACK)
                db.text('{:0>4d}'.format(indexName), (COLS['line'], 0))

                # unicode hex
                if eachGlyphName in referenceFont and referenceFont[
                        eachGlyphName].unicode:
                    uniIntValue = referenceFont[eachGlyphName].unicode
                elif eachGlyphName in someFonts[0] and someFonts[0][
                        eachGlyphName].unicode:
                    uniIntValue = someFonts[0][eachGlyphName].unicode
                else:
                    uniIntValue = None

                if uniIntValue:
                    uniHexValue = 'U+{:04X}'.format(uniIntValue)
                    db.fill(*BLACK)
                    db.text(uniHexValue, (COLS['unicode'], 0))

                # os char
                if uniIntValue:
                    txt = db.FormattedString()
                    txt.fontSize(BODY_SIZE_GLYPH)
                    txt.fill(*GRAY)
                    txt += 'H'
                    txt.fill(*BLACK)
                    txt += unichr(uniIntValue)
                    txt.fill(*GRAY)
                    txt += 'p'
                    db.text(txt, (COLS['char'], 0))

                # glyphname
                db.fontSize(BODY_SIZE_TEXT)
                db.text(eachGlyphName, (COLS['glyph name'], 0))

                # glyphs
                db.translate(COLS['template'], 0)
                for eachFont in [referenceFont] + someFonts:
                    if eachGlyphName in eachFont:
                        eachGlyph = eachFont[eachGlyphName]
                        lftRefGL = eachFont['H']
                        rgtRefGL = eachFont['p']

                        db.save()
                        db.scale(BODY_SIZE_GLYPH / eachFont.info.unitsPerEm)
                        db.fill(*GRAY)
                        db.drawGlyph(lftRefGL)
                        db.translate(lftRefGL.width, 0)
                        db.fill(*BLACK)
                        db.drawGlyph(eachGlyph)
                        db.translate(eachGlyph.width, 0)
                        db.fill(*GRAY)
                        db.drawGlyph(rgtRefGL)
                        db.restore()
                    db.translate(TAB_WIDTH, 0)
                db.restore()
                prog.update()

                quota -= TAB_LINE_HEIGHT
                if quota <= PDF_MARGIN:
                    quota = self._initPage(twoLinesFontStyles)

            prog.setTickCount(value=None)
            prog.update(text='{}: saving PDF...'.format(caption))
            db.saveImage(reportPath)
            db.endDrawing()

        except Exception as error:
            prog.close()
            raise error

        prog.close()
Пример #11
0
class MultiFontMetricsWindow(BaseWindowController):

    # attrs
    textModeOptions = ['Loaded Strings', 'Typewriter']
    textMode = textModeOptions[0]

    selectedGlyph = None
    subscribedGlyph = None

    fontsDB = None
    unicodeMinimum = {}

    fontsOrder = None
    glyphNamesToDisplay = []

    showMetrics = False
    applyKerning = False
    leftToRight = True
    verticalMode = False

    multiLineOptions = {}

    bodySizeOptions = [
        10, 11, 12, 13, 18, 24, 30, 36, 48, 60, 72, 144, 216, 360
    ]
    lineHeightOptions = range(0, 301, 10)

    bodySize = bodySizeOptions[10]
    lineHeight = lineHeightOptions[0]

    fontsAmountOptions = range(1, 9)

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

        # ui vars
        originLeft = 0
        originRight = 0
        width = 956
        height = 640

        netWidth = width - MARGIN_LFT - MARGIN_RGT
        jumpingY = MARGIN_TOP

        # let's see if there are opened fonts (fontDB, ctrlFontsList, fontsOrder)
        self.loadFontsOrder()
        self.updateUnicodeMinimum()

        # defining plugin windows
        self.w = Window((originLeft, originRight, width, height),
                        "Multi Font Metrics Window",
                        minSize=(800, 400))
        self.w.bind('resize', self.mainWindowResize)

        switchButtonWdt = 140
        self.w.switchButton = PopUpButton(
            (MARGIN_LFT, jumpingY, switchButtonWdt,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.textModeOptions,
            sizeStyle='regular',
            callback=self.switchButtonCallback)

        # free text
        textCtrlX = MARGIN_LFT + MARGIN_COL + switchButtonWdt
        self.w.typewriterCtrl = Typewriter(
            (textCtrlX, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT),
             vanillaControlsSize['EditTextRegularHeight']),
            self.unicodeMinimum,
            callback=self.typewriterCtrlCallback)
        self.w.typewriterCtrl.show(False)

        # strings ctrls
        self.w.textStringsControls = TextStringsControls(
            (textCtrlX, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT),
             vanillaControlsSize['PopUpButtonRegularHeight'] + 1),
            self.unicodeMinimum,
            callback=self.textStringsControlsCallback)
        self.stringDisplayMode, self.glyphNamesToDisplay = self.w.textStringsControls.get(
        )

        # multi line
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_ROW

        self.calcSpacingMatrixHeight()
        self.multiLineOptions = {
            'Show Kerning': self.applyKerning,
            'Center': False,
            'Show Space Matrix': False,
            'Single Line': False,
            'displayMode': u'Multi Line',
            'Stroke': False,
            'xHeight Cut': False,
            'Upside Down': False,
            'Beam': False,
            'Water Fall': False,
            'Inverse': False,
            'Show Template Glyphs': True,
            'Multi Line': True,
            'Right to Left': False,
            'Show Metrics': self.showMetrics,
            'Show Control glyphs': True,
            'Fill': True,
            'Left to Right': self.leftToRight
        }
        self.w.lineView = MultiLineView(
            (MARGIN_LFT, jumpingY, -(RIGHT_COLUMN + MARGIN_COL + MARGIN_RGT),
             -MARGIN_BTM - MARGIN_HALFROW - self.spacingMatrixHeight),
            pointSize=self.bodySize,
            lineHeight=self.lineHeight,
            doubleClickCallback=self.lineViewDoubleClickCallback,
            selectionCallback=self.lineViewSelectionCallback,
            bordered=True,
            applyKerning=self.applyKerning,
            hasHorizontalScroller=False,
            hasVerticalScroller=True,
            displayOptions=self.multiLineOptions,
            updateUserDefaults=False,
            menuForEventCallback=None)

        # static options
        # body
        self.w.bodyCtrl = ComboBoxWithCaption(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['ComboBoxSmallHeight'] + 1),
            'Body Size:',
            self.bodySizeOptions,
            '{}'.format(self.bodySize),
            sizeStyle='small',
            callback=self.bodyCtrlCallback)

        # line height
        jumpingY += vanillaControlsSize['ComboBoxSmallHeight'] + int(
            MARGIN_HALFROW)
        self.w.lineHgtCtrl = ComboBoxWithCaption(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['ComboBoxSmallHeight'] + 1),
            'Line Height:',
            self.lineHeightOptions,
            '{}'.format(self.lineHeight),
            sizeStyle='small',
            callback=self.lineHgtCtrlCallback)

        # show metrics
        jumpingY += vanillaControlsSize['ComboBoxSmallHeight'] + MARGIN_ROW
        self.w.showMetricsCheck = CheckBox(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['CheckBoxSmallHeight']),
            "Show Metrics",
            value=self.showMetrics,
            sizeStyle='small',
            callback=self.showMetricsCheckCallback)

        # show kerning checkbox
        jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + MARGIN_ROW * .3
        self.w.applyKerningCheck = CheckBox(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['CheckBoxSmallHeight']),
            "Show Kerning",
            value=self.applyKerning,
            sizeStyle='small',
            callback=self.applyKerningCheckCallback)

        jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + MARGIN_ROW * .3
        self.w.leftToRightCheck = CheckBox(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             vanillaControlsSize['CheckBoxSmallHeight']),
            "Left to right",
            value=self.leftToRight,
            sizeStyle='small',
            callback=self.leftToRightCheckCallback)

        # separationLine
        jumpingY += vanillaControlsSize['CheckBoxSmallHeight'] + int(
            MARGIN_HALFROW)
        self.w.separationLineOne = HorizontalLine(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 1))

        jumpingY += int(MARGIN_HALFROW)
        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(self.fontsOrder) + 2
        self.w.fontsOrderController = FontsOrderController(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN,
             fontsOrderControllerHeight),
            self.fontsOrder,
            callback=self.fontsOrderControllerCallback)

        jumpingY += fontsOrderControllerHeight
        self.w.separationLineTwo = HorizontalLine(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 1))

        # joystick
        jumpingY += MARGIN_HALFROW
        self.w.joystick = SpacingJoystick(
            (-(RIGHT_COLUMN + MARGIN_RGT), jumpingY, RIGHT_COLUMN, 0),
            verticalMode=self.verticalMode,
            marginCallback=self.joystickMarginCallback,
            verticalCallback=self.joystickVerticalCallback)

        # edit metrics
        self.w.spacingMatrix = SpacingMatrix(
            (MARGIN_LFT, -(MARGIN_BTM + self.spacingMatrixHeight),
             self.w.getPosSize()[2] - MARGIN_LFT - MARGIN_RGT,
             self.spacingMatrixHeight),
            glyphNamesToDisplay=self.glyphNamesToDisplay,
            verticalMode=self.verticalMode,
            fontsOrder=self.fontsOrder,
            callback=self.spacingMatrixCallback)

        # add observer
        addObserver(self, 'newFontOpened', "newFontDidOpen")
        addObserver(self, 'openCloseFontCallback', "fontDidOpen")
        addObserver(self, 'openCloseFontCallback', "fontDidClose")

        # lit up!
        self.updateSubscriptions()
        self.updateLineView()
        self.setUpBaseWindowBehavior()
        self.w.open()

    # defcon observers (glyph obj)
    def updateSubscriptions(self):
        self.unsubscribeGlyphs()

        # subscribe
        self.subscribedGlyph = []
        for eachFont in self.fontsOrder:
            for eachGlyphName in self.glyphNamesToDisplay:
                if eachFont.has_key(eachGlyphName):
                    eachGlyph = eachFont[eachGlyphName]
                    if eachGlyph not in self.subscribedGlyph:  # you can't subscribe a glyph twice
                        eachGlyph.addObserver(self, "glyphChangedCallback",
                                              "Glyph.Changed")
                        self.subscribedGlyph.append(eachGlyph)

    def unsubscribeGlyphs(self):
        if self.subscribedGlyph:
            for eachGlyph in self.subscribedGlyph:
                eachGlyph.removeObserver(self, "Glyph.Changed")

    def glyphChangedCallback(self, notification):
        self.w.lineView.update()

    # observers funcs
    def mainWindowResize(self, mainWindow):
        windowWidth = mainWindow.getPosSize()[2]
        self.w.spacingMatrix.adjustSize(
            (windowWidth - MARGIN_LFT - MARGIN_RGT, self.spacingMatrixHeight))

    def openCloseFontCallback(self, sender):
        self.loadFontsOrder()
        self.w.fontsOrderController.setFontsOrder(self.fontsOrder)
        self.w.spacingMatrix.setFontsOrder(self.fontsOrder)
        self.updateUnicodeMinimum()
        self.updateSubscriptions()
        self.adjustSpacingMatrixHeight()
        self.w.spacingMatrix.update()
        self.adjustFontsOrderControllerHeight()
        self.updateLineView()

    def loadFontsOrder(self):
        if self.fontsOrder is None:
            fontsOrder = [f for f in AllFonts() if f.path is not None]
            self.fontsOrder = sorted(fontsOrder,
                                     key=lambda f: os.path.basename(f.path))
        else:
            newFontsOrder = [f for f in AllFonts() if f in self.fontsOrder] + [
                f for f in AllFonts() if f not in self.fontsOrder
            ]
            self.fontsOrder = newFontsOrder

    def newFontOpened(self, notification):
        message(
            'The MultiFont Metrics Window works only with saved font, please save the new font and re-open the plugin'
        )

    def calcSpacingMatrixHeight(self):
        self.spacingMatrixHeight = vanillaControlsSize[
            'EditTextSmallHeight'] + len(
                self.fontsOrder
            ) * vanillaControlsSize['EditTextSmallHeight'] * 2

    def spacingMatrixCallback(self, sender):
        self.w.lineView.update()

    # other funcs
    def adjustSpacingMatrixHeight(self):
        self.calcSpacingMatrixHeight()
        lineViewPosSize = self.w.lineView.getPosSize()
        self.w.lineView.setPosSize(
            (lineViewPosSize[0], lineViewPosSize[1], lineViewPosSize[2],
             -MARGIN_BTM - MARGIN_HALFROW - self.spacingMatrixHeight))
        spacingMatrixPosSize = self.w.spacingMatrix.getPosSize()
        self.w.spacingMatrix.setPosSize(
            (spacingMatrixPosSize[0], -MARGIN_BTM - self.spacingMatrixHeight,
             spacingMatrixPosSize[2], self.spacingMatrixHeight))

    def adjustFontsOrderControllerHeight(self):
        fontsOrderControllerHeight = FONT_ROW_HEIGHT * len(
            self.fontsOrder) + MARGIN_COL
        fontsOrderControllerPosSize = self.w.fontsOrderController.getPosSize()
        self.w.fontsOrderController.setPosSize(
            (fontsOrderControllerPosSize[0], fontsOrderControllerPosSize[1],
             fontsOrderControllerPosSize[2], fontsOrderControllerHeight))

    def windowCloseCallback(self, sender):
        self.unsubscribeGlyphs()
        removeObserver(self, "newFontDidOpen")
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontDidClose")
        super(MultiFontMetricsWindow, self).windowCloseCallback(sender)

    def updateUnicodeMinimum(self):
        # collect everything
        allUnicodeData = {}
        for eachFont in self.fontsOrder:
            for eachKey, eachValue in eachFont.naked().unicodeData.items():
                if eachKey not in allUnicodeData:
                    allUnicodeData[eachKey] = eachValue

        # filter
        self.unicodeMinimum = {}
        for eachKey, eachValue in allUnicodeData.items():
            for eachFont in self.fontsOrder:
                if eachKey not in eachFont.naked().unicodeData:
                    break
            if eachValue:
                self.unicodeMinimum[eachKey] = eachValue

    def updateLineView(self):
        try:
            displayedGlyphs = []
            if self.stringDisplayMode == 'Waterfall':
                for indexFont, eachFont in enumerate(self.fontsOrder):
                    assert isinstance(
                        eachFont,
                        RFont), 'object in self.fontsOrder not a RFont'
                    if indexFont != 0:
                        newLineGlyph = self.w.lineView.createNewLineGlyph()
                        displayedGlyphs.append(newLineGlyph)

                    for eachGlyphName in self.glyphNamesToDisplay:
                        if eachFont.has_key(eachGlyphName):
                            displayedGlyphs.append(eachFont[eachGlyphName])
                        elif eachGlyphName == '.newLine':
                            newLineGlyph = self.w.lineView.createNewLineGlyph()
                            displayedGlyphs.append(newLineGlyph)
                        else:
                            if eachFont.has_key('.notdef'):
                                displayedGlyphs.append(eachFont['.notdef'])
                            else:
                                displayedGlyphs.append(NOT_DEF_GLYPH)

            else:
                for eachGlyphName in self.glyphNamesToDisplay:
                    for indexFont, eachFont in enumerate(self.fontsOrder):
                        assert isinstance(
                            eachFont,
                            RFont), 'object in self.fontsOrder not a RFont'
                        if eachFont.has_key(eachGlyphName):
                            displayedGlyphs.append(eachFont[eachGlyphName])
                        else:
                            if eachFont.has_key('.notdef'):
                                displayedGlyphs.append(eachFont['.notdef'])
                            else:
                                displayedGlyphs.append(NOT_DEF_GLYPH)

            self.w.lineView.set(displayedGlyphs)

            # add kerning if needed
            if self.applyKerning is True:
                glyphRecords = self.w.lineView.contentView().getGlyphRecords()

                leftRecord = glyphRecords[0]
                rightRecord = glyphRecords[1]

                for indexRecords, eachGlyphRecord in enumerate(glyphRecords):
                    if indexRecords == len(
                            glyphRecords) and indexRecords == len(
                                0):  # avoid first and last
                        continue

                    leftRecord = glyphRecords[indexRecords - 1]
                    rightRecord = glyphRecords[indexRecords]
                    leftGlyph = leftRecord.glyph
                    rightGlyph = rightRecord.glyph

                    # if they come from the same font...
                    leftFont = leftGlyph.getParent()
                    rightFont = rightGlyph.getParent()
                    if leftFont is rightFont:
                        if (leftGlyph.name,
                                rightGlyph.name) in leftFont.flatKerning:
                            leftRecord.xAdvance += leftFont.flatKerning[(
                                leftGlyph.name, rightGlyph.name)]

            # manually refresh the line view ctrl, it should help
            self.w.lineView.update()

            # update spacing matrix
            self.w.spacingMatrix.canvas.update()

        except Exception, error:
            print traceback.format_exc()