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")
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
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)
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))
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()
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')
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)
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()
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()