class CopyKerning: def __init__(self): if AllFonts() is None: from vanilla.dialogs import message message("No fonts open.", "Open or create a font to copy data to and fro.") return self.sourceFontList = AllFonts() self.destinationFontList = AllFonts() self.source_font = self.sourceFontList[0] self.destination_fonts = None self.groups = None self.kerning = None ## create a window self.w = Window((400, 500), "Copy Groups and Kerning", minSize=(500, 600)) self.w.sourceTitle = TextBox((15, 20, 200, 20), "Source Font:") self.w.sourceFont = PopUpButton((15, 42, 340, 20), [f.info.familyName + ' ' + f.info.styleName for f in self.sourceFontList], callback=self.sourceCallback) self.w.desTitle = TextBox((15, 76, 200, 20), "Destination Fonts:") self.w.destinationFonts = FontList((15, 96, -15, -115), self.destinationFontList, selectionCallback=self.desCallback) self.w.copyButton = Button((-215, -40, 200, 20), 'Copy Groups & Kerning', callback=self.copyCallback) self.w.line = HorizontalLine((10, -60, -10, 1)) self._updateDest() ## open the window self.w.open() def _updateDest(self): des = list(self.sourceFontList) des.remove(self.source_font) self.w.destinationFonts.set(des) def copyKerning(self, groups, kerning, source_font, destination_fonts): kerning = source_font.kerning.asDict() groups = source_font.groups for font in destination_fonts: font.groups.update(groups) font.kerning.update(kerning) def sourceCallback(self, sender): self.source_font = self.sourceFontList[sender.get()] self._updateDest() def desCallback(self, sender): self.destination_fonts = [sender.get()[x] for x in sender.getSelection()] def copyCallback(self, sender): self.sheet = Sheet((300, 50), self.w) self.sheet.bar = ProgressBar((10, 20, -10, 10), isIndeterminate=True, sizeStyle="small") self.sheet.open() self.sheet.bar.start() self.copyKerning(self.groups, self.kerning, self.source_font, self.destination_fonts) self.sheet.bar.stop() self.sheet.close() del self.sheet self.w.close()
class convertMasterToBraceLayers(object): def __init__(self): self.master_names = [m.name for m in Glyphs.font.masters][1:] item_height = 24.0 w_width = 350.0 w_height = item_height * 5 margin = 10 next_y = margin col_1_width = w_width - (margin * 2) item_height = 24 self.w = Window((w_width, w_height), "Convert Master to Brace Layers") next_y = margin self.w.master_names_label = TextBox((margin, next_y + 2, col_1_width, item_height), "Master to Convert (Cannot be the first Master)", sizeStyle='regular') next_y += item_height self.w.master_names = PopUpButton((margin, next_y, col_1_width, item_height), self.master_names) next_y += item_height + margin selected_master_index = Glyphs.font.masters.index(Glyphs.font.selectedFontMaster) self.w.master_names.set(selected_master_index - 1) self.w.gobutton = Button((margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Add Brace Layers', callback=self.makeitso) self.w.setDefaultButton(self.w.gobutton) self.w.open() def makeitso(self, sender): self.w.close() Glyphs.font.disableUpdateInterface() selected_master_index = self.w.master_names.get() + 1 selected_master = Glyphs.font.masters[selected_master_index] layer_name = '{{{}}}'.format(', '.join([bytes(x) for x in selected_master.axes])) master_to_attach_to = Glyphs.font.masters[selected_master_index - 1] for g in Glyphs.font.glyphs: for l in g.layers: if l.associatedMasterId == selected_master.id: if l.isSpecialLayer: l.associatedMasterId = master_to_attach_to.id continue newL = copy.copy(l) newL.associatedMasterId = master_to_attach_to.id newL.name = layer_name g.layers.append(newL) del(Glyphs.font.masters[selected_master_index]) Glyphs.font.enableUpdateInterface() Glyphs.showMacroWindow()
class GlyphnameDialog( object): def __init__( self ): x = 10 y = 10 height = 20 button_width = 30 glyphname_width = 180 gap = 6 self.w = Window( ( x + button_width + gap + glyphname_width + gap + button_width + x, y + height + y ), "insert glyph" ) self.w.center() self.w.glyphname = EditText( ( x, y, glyphname_width, height ), '') x += glyphname_width + gap self.w.alignleft = Button( ( x, y, button_width, height ), LEFT, callback = self.buttonCallback ) x += button_width + gap self.w.alignright = Button( ( x, y, button_width, height ), RIGHT, callback = self.buttonCallback ) self.w.setDefaultButton( self.w.alignleft ) self.w.alignright.bind( "\x1b", [] ) self.w.open() def buttonCallback( self, sender ): alignment = sender.getTitle() glyphname = self.w.glyphname.get() if not glyphname: self.w.close() return if len( glyphname ) == 1: uni = ord(glyphname) g = font.glyphForUnicode_("%.4X" % uni) if g: glyphname = g.name other_glyph = font.glyphs[ glyphname ] if not other_glyph: for glyph in font.glyphs: if glyph.name.startswith( glyphname ): other_glyph = glyph print 'Using', glyph.name break else: print 'No matching glyph found.' self.w.close() return selected_glyphs = set( [ layer.parent for layer in font.selectedLayers ] ) for glyph in selected_glyphs: glyph.beginUndo() for layer in glyph.layers: # find other layer for other_layer in other_glyph.layers: if other_layer.name == layer.name: insert_paths( layer, other_layer, alignment ) break else: if active_layerId == layer.layerId: insert_paths( layer, other_glyph.layers[layer.associatedMasterId], alignment ) glyph.endUndo() self.w.close()
class GlyphnameDialog( object): def __init__( self ): hori_margin = 10 verti_margin = hori_margin button_width = 30 glyphname_width = 180 line_height = 20 gap = 9 dialog_height = line_height + gap + line_height + gap + line_height dialog_width = button_width + gap + glyphname_width + gap + button_width self.w = Window( ( hori_margin + dialog_width + hori_margin, verti_margin + dialog_height + verti_margin ), "insert glyph" ) self.w.center() x = hori_margin y = verti_margin # glyph name self.w.glyphname = EditText( ( x, y, glyphname_width, line_height ), '') self.w.glyphname.getNSTextField().setToolTip_( u'Enter the name of the glyph to be inserted. It is sufficient to enter the beginning of the glyph name, e.g. “deg” for “degree”.' ) # buttons x += glyphname_width + gap self.w.alignleft = Button( ( x, y, button_width, line_height ), LEFT, callback = self.buttonCallback ) self.w.alignleft.getNSButton().setToolTip_( 'Insert the other glyph left-aligned, i.e. at its original same position. Keyboard shortcut: Enter' ) x += button_width + gap self.w.alignright = Button( ( x, y, button_width, line_height ), RIGHT, callback = self.buttonCallback ) self.w.alignright.getNSButton().setToolTip_( 'Insert the other glyph right-aligned with respect to the advance widths. Keyboard shortcut: Esc' ) self.w.setDefaultButton( self.w.alignleft ) self.w.alignright.bind( "\x1b", [] ) # insert as component as_component_is_checked = True if Glyphs.defaults["com.FMX.InsertGlyphToBackground.AsCompoment"] is not None: as_component_is_checked = Glyphs.defaults["com.FMX.InsertGlyphToBackground.AsCompoment"] y += line_height + gap x = hori_margin self.w.as_component = CheckBox( ( x, y, dialog_width, line_height ), 'Insert as component', callback=None, value=as_component_is_checked ) self.w.as_component.getNSButton().setToolTip_( 'If checked, the other glyph is inserted to the background as a component. Otherwise, it is inserted as paths (even if the other glyph is made of components).' ) # clear current contents y += line_height + gap clear_contents_is_checked = True if Glyphs.defaults["com.FMX.InsertGlyphToBackground.ClearContents"] is not None: clear_contents_is_checked = Glyphs.defaults["com.FMX.InsertGlyphToBackground.ClearContents"] self.w.clear_contents = CheckBox( ( x, y, dialog_width, line_height ), 'Clear current contents', callback=None, value=clear_contents_is_checked ) self.w.clear_contents.getNSButton().setToolTip_( 'Check this to clear the background before inserting the other glyph. Uncheck to keep the current contents of the background.' ) self.w.open() def buttonCallback( self, sender ): alignment = sender.getTitle() glyphname = self.w.glyphname.get() as_component_is_checked = self.w.as_component.get() clear_contents_is_checked = self.w.clear_contents.get() if not glyphname: self.w.close() return if len( glyphname ) == 1: uni = ord(glyphname) g = font.glyphForUnicode_("%.4X" % uni) if g: glyphname = g.name other_glyph = font.glyphs[ glyphname ] if not other_glyph: for glyph in font.glyphs: if glyph.name.startswith( glyphname ): other_glyph = glyph break else: self.w.close() return selected_glyphs = set( [ layer.parent for layer in font.selectedLayers ] ) for glyph in selected_glyphs: glyph.beginUndo() for layer in glyph.layers: # find other layer for other_layer in other_glyph.layers: if other_layer.name == layer.name: insert_paths( layer, other_layer, alignment, as_component_is_checked, clear_contents_is_checked ) break else: if layer.isBraceLayer: # the corresponding brace layer was not found in other_glyph. # let’s interpolate it on-the-fly: other_glyph_copy = other_glyph.copy() other_glyph_copy.parent = font # ^ Glyphs needs the font’s master coordinates for the re-interpolation interpolatedLayer = GSLayer() interpolatedLayer.name = layer.name # ^ necessary for the re-interpolation other_glyph_copy.layers.append( interpolatedLayer ) interpolatedLayer.reinterpolate() insert_paths( layer, interpolatedLayer, alignment, as_component = False, clear_contents = clear_contents_is_checked ) elif active_layerId == layer.layerId: insert_paths( layer, other_glyph.layers[layer.associatedMasterId], alignment, as_component_is_checked, clear_contents_is_checked ) glyph.endUndo() Glyphs.defaults["com.FMX.InsertGlyphToBackground.AsCompoment"] = as_component_is_checked Glyphs.defaults["com.FMX.InsertGlyphToBackground.ClearContents"] = clear_contents_is_checked self.w.close()
class copyKerning(object): # TODO: Add class to different class name copying. # TODO: Allow left or right or both selection. # TODO: Allow open font selection. # TODO: Allow from/to layer selection. def __init__(self): item_height = 24.0 margin = 10 next_y = margin w_width = 400.0 w_height = item_height * 7 + margin col_1_width = w_width - (margin * 2) self.this_font = Glyphs.font try: self.other_font = [f for f in Glyphs.fonts if f != self.this_font][0] except IndexError: Glyphs.showNotification('Copy kerning for Class from Other Font:', 'There is only 1 file open!') raise self.other_fonts_classes = self.get_other_fonts_classes() self.w = Window((w_width, w_height), "Copy kerning for Class from Other Font") self.w.text_1 = TextBox( (margin, next_y, w_width, item_height), "Copy the kerning for this class to this font:", sizeStyle='small') next_y += item_height self.w.class_to_copy = PopUpButton( (margin, next_y, w_width - (margin * 2), item_height), self.other_fonts_classes, sizeStyle='regular') next_y += item_height + item_height self.w.copy_for_all = RadioGroup( (margin, next_y, w_width, item_height * 2), [ ' Copy only for the current Master', ' Copy for All masters', ]) self.w.copy_for_all.set(0) next_y += (item_height * 2) + margin self.w.gobutton = Button( (margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Copy', callback=self.makeitso) self.w.setDefaultButton(self.w.gobutton) self.w.center() self.w.open() def get_other_fonts_classes(self): all_kern_groups = set() for g in self.other_font.glyphs: if g.leftKerningGroup is not None: all_kern_groups.add(g.leftKerningGroup) if g.rightKerningGroup is not None: all_kern_groups.add(g.rightKerningGroup) return sorted(all_kern_groups) def get_other_master(self, this_master): try: other_master = [ m for m in self.other_font.masters if m.name == this_master.name ][0] return other_master except IndexError: pass return self.other_font.selectedFontMaster @staticmethod def glyph_for_id(font, some_name): try: return font.glyphForId_(some_name).name except AttributeError: return some_name def get_this_glyphname(self, some_name): try: return Glyphs.font.glyphForId_(some_name).name except AttributeError: return some_name def copy_left_kerning(self, this_master, class_name): mmk_class_name = '@MMK_L_{}'.format(class_name) for right, val in self.other_font.kerning[self.get_other_master( this_master).id][mmk_class_name].items(): if right.startswith('@MMK'): right_name = right else: right_name = self.other_font.glyphForId_(right).name self.this_font.setKerningForPair(this_master.id, mmk_class_name, right_name, val) def copy_right_kerning(self, this_master, class_name): # TODO: This. Like, it's not done. print( 'No left-side kerning was copied because the function hasn\'t been written yet.' ) pass def makeitso(self, sender): self.w.close() class_name = self.w.class_to_copy.getItem() print(bool(self.w.copy_for_all.get())) chosen_masters = self.this_font.masters if self.w.copy_for_all.get( ) else [self.this_font.selectedFontMaster] for m in chosen_masters: self.copy_left_kerning(m, class_name) self.copy_right_kerning(m, class_name)
class Dialogs(object): u""" New dialog creation, for dialog closing see Callbacks class. """ # Start. def openStartWindow(self): u""" Offers a 'New' and 'Open...' button. """ self.startDialog = Window(self.dialogSize, "Welcome", minSize=self.dialogSize, maxSize=self.dialogSize) self.startDialog.newText = TextBox((20, 60, 60, 30), "New") self.startDialog.newButton = Button((20, 100, 80, 20), "Create...", callback=self.new_) self.startDialog.openText = TextBox((160, 60, 60, 30), "Existing") self.startDialog.openButton = Button((160, 100, 60, 20), "Open...", callback=self.open_) self.startDialog.open() def closeStartDialog(self): u""" Closes and clears start dialog. """ if not self.startDialog is None: self.startDialog.close() self.startDialog = None # Document. def openOpenDocumentDialog(self): u""" Open document dialog. """ self.closeStartDialog() panel = NSOpenPanel.openPanel() panel.setCanChooseDirectories_(False) panel.setCanChooseFiles_(True) panel.setAllowsMultipleSelection_(False) panel.setAllowedFileTypes_(['wf']) if panel.runModal() == NSOKButton: added, document = self.model.openDocument(panel.filenames()[0]) self.initDocument(added, document) def openNewDialog(self): u""" Opens the document canvas if it doesn't exist yet. """ self.closeStartDialog() self.setDefaultDocumentValues() size = (400, 300) self.newDialog = w = Window(size, "New Document", minSize=size, maxSize=size) w.nameText = TextBox((20, 20, 180, 20), "Name") w.nameBox = EditText((200, 20, 180, 20), callback=self.newNameCallback) w.nameBox.set(self.documentValues['documentName']) w.sizeText = TextBox((20, 40, 180, 20), "Size") values = PaperSizes.getAllPaperSizes() w.sizeComboBox = ComboBox((200, 40, 180, 20), values, callback=self.newSizeCallback) w.sizeComboBox.set(self.defaultPaperSize) w.whText = TextBox((20, 60, 220, 20), u"w×h in ㎜") width, height = PaperSizes.getSize(self.defaultPaperSize, 'mm') w.width = EditText((200, 60, 80, 20), callback=self.newWidthCallback) w.width.set(width) w.x = TextBox((285, 60, 10, 20), u"×") w.dimensionBoxHeight = EditText((300, 60, 80, 20), callback=self.newHeightCallback) w.dimensionBoxHeight.set(height) w.okayButton = Button((240, 260, 60, 20), "Okay", callback=self.newOkayCallback) #w.okayButton.getNSButton().setEnabled_(False) w.cancelButton = Button((320, 260, 60, 20), "Cancel", callback=self.newCancelCallback) w.open() def closeOpenDialog(self): if not self.openDialog is None: self.openDialog.close() self.openDialog = None # Save dialog. def openSaveDialog(self): if not self.saveDialog is None: return self.saveDialog = Window(self.saveDialogSize, "Save", minSize=self.saveDialogSize, maxSize=self.saveDialogSize) self.saveDialog.saveText = TextBox((60, 20, 280, 60), "Save changes to the document %s?" % self.currentDocument.name) self.saveDialog.dontButton = Button((60, 70, 100, 20), "Don't Save", callback=self.saveDontCallback) self.saveDialog.cancelButton = Button((260, 70, 60, 20), "Cancel", callback=self.saveCloseCallback) self.saveDialog.doButton = Button((320, 70, 60, 20), "Save", callback=self.saveDoCallback) self.saveDialog.open() def closeSaveDialog(self): self.saveDocumentDialog.close() self.saveDocumentDialog = None # Close. def windowShouldCloseCallback(self, sender): window = self.getCurrentWindow() if sender == window: self.closeDocument_(sender)
class renameKerning(object): def __init__(self): item_height = 24.0 w_width = 300.0 w_height = item_height * 8 margin = 10 next_y = margin col_1_width = w_width - (margin * 2) item_height = 24 self.messages = [] self.interpolated_fonts = dict() self.current_glyph = None self.all_kern_groups = self.get_all_kern_groups() self.w = Window((w_width, w_height), "Rename Kern Groups") self.w.text_1 = TextBox((margin, next_y, w_width, item_height), "Rename:", sizeStyle='small') next_y += item_height self.w.nameFind = PopUpButton( (margin, next_y, w_width - (margin * 2), item_height), self.all_kern_groups, sizeStyle='regular') next_y += item_height + item_height self.w.text_2 = TextBox((margin, next_y, w_width, item_height), "To:", sizeStyle='small') next_y += item_height self.w.nameReplace = EditText( (margin, next_y, w_width - (margin * 2), item_height), "", sizeStyle='regular') next_y += item_height + margin self.w.gobutton = Button( (margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Rename', callback=self.makeitso) self.w.setDefaultButton(self.w.gobutton) self.w.center() self.w.open() def sbuttonCallback(self, sender): self.s.close() def get_all_kern_groups(self): all_kern_groups = set() for g in Glyphs.font.glyphs: if g.leftKerningGroup is not None: all_kern_groups.add('{} - LEFT'.format(g.leftKerningGroup)) if '{} - RIGHT'.format(g.leftKerningGroup) in all_kern_groups: all_kern_groups.add('{} - BOTH'.format(g.leftKerningGroup)) if g.rightKerningGroup is not None: all_kern_groups.add('{} - RIGHT'.format(g.rightKerningGroup)) if '{} - LEFT'.format(g.rightKerningGroup) in all_kern_groups: all_kern_groups.add('{} - BOTH'.format( g.rightKerningGroup)) return sorted(all_kern_groups) def makeitso(self, sender): self.w.close() nameReplace = self.w.nameReplace.get() nameFind = self.w.nameFind.getItem() group_name, _, side = nameFind.partition(' - ') for g in Glyphs.font.glyphs: if side in ['LEFT', 'BOTH']: if g.leftKerningGroup == group_name: g.leftKerningGroup = nameReplace if side in ['RIGHT', 'BOTH']: if g.rightKerningGroup == group_name: g.rightKerningGroup = nameReplace if '{} - {}'.format(nameReplace, side) in self.all_kern_groups: Glyphs.showNotification( 'Kern Group Renaming Success!', 'Note: the kern group named "{}" already exists so it will keep its existing kerning values.' .format(nameReplace)) return for m_id in Glyphs.font.kerning: for left_group in Glyphs.font.kerning[m_id]: if side in ['RIGHT', 'BOTH']: group_prefix = self.is_group(left_group, group_name) if group_prefix: for right_group, val in Glyphs.font.kerning[m_id][ left_group].items(): Glyphs.font.setKerningForPair( m_id, group_prefix + nameReplace, self.glyph_for_id(right_group), val) Glyphs.font.removeKerningForPair( m_id, left_group, self.glyph_for_id(right_group)) left_group = group_prefix + nameReplace if side in ['LEFT', 'BOTH']: for right_group, val in Glyphs.font.kerning[m_id][ left_group].items(): group_prefix = self.is_group(right_group, group_name) if group_prefix: # print(m_id, left_group, right_group, self.glyph_for_id(left_group), group_prefix + nameReplace, val) # with open('/Users/benjones/Desktop/test.txt', 'a') as f: # f.write('{}\n'.format([m_id, left_group, right_group, self.glyph_for_id(left_group), group_prefix + nameReplace, val])) Glyphs.font.setKerningForPair( m_id, self.glyph_for_id(left_group), group_prefix + nameReplace, val) Glyphs.font.removeKerningForPair( m_id, self.glyph_for_id(left_group), right_group) @staticmethod def is_group(group, group_name): found_group = re.match('^(@MMK_._){}$'.format(group_name), group) if found_group is None: return False return found_group.group(1) @staticmethod def glyph_for_id(some_name): try: return Glyphs.font.glyphForId_(some_name).name except AttributeError: return some_name
class GlyphnameDialog(object): def __init__(self): x = 10 y = 10 height = 20 button_width = 30 glyphname_width = 180 gap = 6 self.w = Window( (x + button_width + gap + glyphname_width + gap + button_width + x, y + height + y), "insert glyph") self.w.center() self.w.glyphname = EditText((x, y, glyphname_width, height), '') x += glyphname_width + gap self.w.alignleft = Button((x, y, button_width, height), LEFT, callback=self.buttonCallback) x += button_width + gap self.w.alignright = Button((x, y, button_width, height), RIGHT, callback=self.buttonCallback) self.w.setDefaultButton(self.w.alignleft) self.w.alignright.bind("\x1b", []) self.w.open() def buttonCallback(self, sender): title = sender.getTitle() glyphname = self.w.glyphname.get() if not glyphname: self.w.close() return if len(glyphname) == 1: uni = ord(glyphname) g = font.glyphForUnicode_("%.4X" % uni) if g: glyphname = g.name other_glyph = font.glyphs[glyphname] if not other_glyph: for glyph in font.glyphs: if glyph.name.startswith(glyphname): other_glyph = glyph break else: self.w.close() return for layer in font.selectedLayers: glyph = layer.parent glyph.beginUndo() # deselect all for path in layer.paths: for node in path.nodes: layer.removeObjectFromSelection_(node) # find other layer for other_layer in other_glyph.layers: if other_layer.name == layer.name: # insert paths for path in other_layer.copyDecomposedLayer().paths: if title == RIGHT: shift = layer.width - other_layer.width for node in path.nodes: node.x = node.x + shift layer.paths.append(path) # select path layer.paths[-1].selected = True break glyph.endUndo() self.w.close()
class makeDisplay(object): def __init__(self): self.verboten = { 'right': ['napostrophe', 'Omegadasiavaria'], 'left': ['ldot', 'Ldot', 'ldot.sc', 'sigmafinal'], 'both': ['*.tf', '*.tosf', '.notdef', 'NULL', 'CR'] } self.category = None self.messages = [] self.interpolated_fonts = dict() self.use_real = True self.use_selection = False self.ignore_red = False self.current_glyph = None self.leftside_kerning_groups = None self.rightside_kerning_groups = None self.all_kern_categories = self.get_all_kern_categories() self.categories_leftside = self.get_categorised_glyphs('left') self.categories_rightside = self.get_categorised_glyphs('right') item_height = 24.0 w_width = 300.0 w_height = item_height * (7 + len(self.all_kern_categories)) margin = 10 next_y = margin col_1_width = w_width - (margin * 2) item_height = 24 radio_height = item_height * len(self.all_kern_categories) self.w = Window((w_width, w_height), "Make Kerning Strings") self.w.text_1 = TextBox((margin, next_y, w_width, item_height), "Kern with:", sizeStyle='regular') next_y += item_height self.w.radioCategories = RadioGroup((margin, next_y, col_1_width, radio_height), self.all_kern_categories, sizeStyle='regular') self.w.radioCategories.set(0) next_y += radio_height + margin self.w.use_real = CheckBox((margin, next_y, col_1_width, item_height), "Use real words", value=True, sizeStyle='regular') next_y += item_height self.w.use_selected = CheckBox((margin, next_y, col_1_width, item_height), "Use the selected glyphs verbatum", value=False, sizeStyle='regular') next_y += item_height self.w.ignore_red = CheckBox((margin, next_y, col_1_width, item_height), "Ignore red marked glyphs", value=False, sizeStyle='regular') next_y += item_height + margin self.w.gobutton = Button((margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Make Strings', callback=self.makeitso) self.w.setDefaultButton(self.w.gobutton) self.w.center() self.w.open() # self.makeitso(None) def sbuttonCallback(self, sender): self.s.close() @staticmethod def has_smallcaps(): for g in Glyphs.font.glyphs: if g.subCategory == 'Smallcaps': return True return False def get_all_kern_categories(self): kcats = [ 'Uppercase', 'Lowercase', ] if self.has_smallcaps: kcats.append('Smallcaps') kcats += [ 'Quotes', 'Number', 'Punctuation', 'Other', ] return kcats def get_canonincal_kerning_glyph(self, layer, pair_side): g = layer.parent if self.use_selection: return g if pair_side == 'left': g = Glyphs.font.glyphs[layer.parent.rightKerningGroup] or layer.parent if pair_side == 'right': g = Glyphs.font.glyphs[layer.parent.leftKerningGroup] or layer.parent if g is None: g = layer.parent return g @staticmethod def make_list_unique(this_list): unique_list = [] for x in this_list: if x in unique_list or x is None: continue unique_list.append(x) return unique_list def get_categorised_glyphs(self, side): # cats = defaultdict(lambda: defaultdict(list)) cats = dict((k, defaultdict(list)) for k in self.all_kern_categories) for g in [x for x in Glyphs.font.glyphs if self.is_elligable(x)]: l = cats.get(g.category, cats.get(g.subCategory, cats['Other'])) l[g.script].append(self.get_canonincal_kerning_glyph(g.layers[0], side)) for cat in cats.keys(): for script in cats[cat].keys(): cats[cat][script] = self.make_list_unique(cats[cat][script]) return cats def get_string(self, left_g, right_g): string = None if self.category == 'Quotes': cat = left_g.subCategory if left_g.subCategory != 'Other' else left_g.category pattern = _kerningStrings.patterns.get(left_g.script, _kerningStrings.patterns.get('latin')).get(cat + '-Quotes', '') strings = [pattern.format(right=right_g.name, left=left_g.name, qL=quote_pair[0], qR=quote_pair[1]).replace(' /', '/') for quote_pair in _kerningStrings.quotations] string = ' '.join(strings) if not string and self.use_real: base_name_left, _, suffix_left = left_g.name.partition('.') base_name_right, _, suffix_right = right_g.name.partition('.') potentials = [ base_name_left + base_name_right, base_name_left + '/' + base_name_right, '/' + base_name_left + ' ' + base_name_right, '/' + base_name_left + '/' + base_name_right, ] for s in potentials: string = _kerningStrings.strings.get(s) if string: break print(s) if not string: pattern = self.get_pattern(left_g, right_g) string = pattern.format(right=right_g.name, left=left_g.name).replace(' /', '/') if not string: string = '/' + left_g.name + '/' + right_g.name return string def get_category_for_glyph(self, glyph): if glyph.category in self.all_kern_categories: return glyph.category if glyph.subCategory in self.all_kern_categories: return glyph.subCategory if glyph.subCategory == 'Currancy': return 'Number' return 'Other' def get_pattern(self, main_glyph, other_glyph): scripts_patterns = _kerningStrings.patterns.get(main_glyph.script, {}) # print(self.get_category_for_glyph(main_glyph)) # print(self.get_category_for_glyph(main_glyph) + '-' + self.get_category_for_glyph(other_glyph), self.all_kern_categories) pattern = scripts_patterns.get(self.get_category_for_glyph(main_glyph) + '-' + self.get_category_for_glyph(other_glyph), '') if self.category == 'Number': suffix = ''.join(main_glyph.name.partition('.')[1:]) else: suffix = '' try: pattern = pattern.format( suffix=suffix, left='{left}', right='{right}', ) except KeyError: pass return pattern def is_elligable(self, glyph, side='both'): if self.ignore_red and glyph.color == 0: return False if not glyph.export: return False for vgn in self.verboten[side]: if re.match(vgn.replace('.', '\\.').replace('*', '.*'), glyph.name): return False return True def makeitso(self, sender): try: self.w.close() except AttributeError: pass self.category = self.all_kern_categories[self.w.radioCategories.get()] self.use_real = self.w.use_real.get() self.use_selection = self.w.use_selected.get() self.ignore_red = self.w.ignore_red.get() all_strings = [] if self.category == 'Quotes': left_of_string_glyphs = self.make_list_unique([self.get_canonincal_kerning_glyph(sl, 'right') for sl in Glyphs.font.selectedLayers if self.is_elligable(sl.parent, 'right')]) right_of_string_glyphs = self.make_list_unique([self.get_canonincal_kerning_glyph(sl, 'left') for sl in Glyphs.font.selectedLayers if self.is_elligable(sl.parent, 'left')]) pairs = zip_longest(left_of_string_glyphs, right_of_string_glyphs) for p in pairs: gl, gr = p if gl is None: gl = gr if gr in left_of_string_glyphs else left_of_string_glyphs[0] if gr is None: gr = gl if gl in left_of_string_glyphs else right_of_string_glyphs[0] kerning_string = self.get_string(gl, gr) if kerning_string not in all_strings: all_strings.append(kerning_string) else: # Holds kerning key glyphs that have been seen already, to avoid duplicates processed_main_glyphs_left = OrderedDict() processed_main_glyphs_right = OrderedDict() # print([(k, self.categories_rightside[k].keys()) for k in self.categories_rightside.keys()]) for sl in Glyphs.font.selectedLayers: # Process the selected glyph on the left side main_g_left = self.get_canonincal_kerning_glyph(sl, 'left') pair_strings_left = [] if self.is_elligable(main_g_left, 'left'): if main_g_left.name not in processed_main_glyphs_left.keys(): processed_main_glyphs_left[main_g_left.name] = [sl.parent.name] try: if sl.parent.script: other_glyphs_rightside = self.categories_rightside[self.category].get(sl.parent.script, self.categories_rightside[self.category].get(None)) else: other_glyphs_rightside = self.categories_rightside[self.category].get(None, self.categories_rightside[self.category].get('latin')) except KeyError: other_glyphs_rightside = [] # print(self.category, self.categories_rightside.keys()) print(sl.parent.script, self.category, self.categories_rightside[self.category].keys()) for g in other_glyphs_rightside: if not self.is_elligable(g, 'right'): continue other_g = self.get_canonincal_kerning_glyph(g.layers[sl.associatedMasterId], 'right') kerning_string_left = self.get_string(main_g_left, other_g) if kerning_string_left not in pair_strings_left: pair_strings_left.append(kerning_string_left) else: processed_main_glyphs_left[main_g_left.name].append(sl.parent.name) if pair_strings_left: pair_strings_left.insert(0, main_g_left.name) # Process the selected glyph on the right side main_g_right = self.get_canonincal_kerning_glyph(sl, 'right') pair_strings_right = [] if self.is_elligable(main_g_right, 'right'): if main_g_right.name not in processed_main_glyphs_right.keys(): processed_main_glyphs_right[main_g_right.name] = [sl.parent.name] if self.category == 'Quotes': other_glyphs_leftside = [main_g_right] main_g_right = self.get_canonincal_kerning_glyph(sl, 'left') else: if sl.parent.script: other_glyphs_leftside = self.categories_leftside[self.category].get(sl.parent.script, self.categories_leftside[self.category].get(None, [])) else: other_glyphs_leftside = self.categories_leftside[self.category].get(None, self.categories_leftside[self.category].get('latin', [])) for g in other_glyphs_leftside: if not self.is_elligable(g, 'left'): continue other_g = self.get_canonincal_kerning_glyph(g.layers[sl.associatedMasterId], 'left') kerning_string_right = self.get_string(other_g, main_g_right) if kerning_string_right not in pair_strings_right: pair_strings_right.append(kerning_string_right) else: processed_main_glyphs_right[main_g_right.name].append(sl.parent.name) if pair_strings_right: pair_strings_right.insert(0, main_g_right.name) left_string = ' '.join(self.make_list_unique(pair_strings_left)) right_string = ' '.join(self.make_list_unique(pair_strings_right)) if all([left_string, right_string]): pair_strings = '\n'.join([left_string, right_string]) else: pair_strings = left_string or right_string # print(':', pair_strings, ':') if pair_strings: all_strings.append(pair_strings) Glyphs.font.newTab('\n\n'.join(all_strings)) Glyphs.font.currentTab.previewInstances = 'live' Glyphs.font.currentTab.scale = 0.065 Glyphs.font.currentTab.textCursor = 3 Glyphs.font.tool = 'TextTool'
class copyMetrics(object): def __init__(self): item_height = 24.0 w_width = 500.0 w_height = item_height * 12 margin = 10 next_y = margin col_1_width = w_width - (margin * 2) item_height = 24 self.get_prefs('copyMetricsFromFont.pref') self.available_layers = dict( ('{} - {}'.format(os.path.basename(font.filepath), master), master) for font in Glyphs.fonts for master in font.masters) self.w = Window((w_width, w_height), "Copy Metrics") self.w.hText_2 = TextBox((margin, next_y, col_1_width, item_height), "Copy metrics from this font/layer:", sizeStyle='regular') next_y += item_height self.w.available_layers = PopUpButton( (margin, next_y, col_1_width, item_height), sorted(self.available_layers.keys() )) # , callback=self.update_brace_value) next_y += item_height + margin metrics_options = [ 'Left sidebearing Only', 'Right sidebearing Only', 'Width Only (keep LSB)', 'Width Only (assign proportionally)', "Left sidebearing and Width", "Left sidebearing and Right sidebearing", 'Width and Right sidebearing', ] self.w.metrics_to_copy = RadioGroup( (margin, next_y, col_1_width, item_height * len(metrics_options)), metrics_options) self.w.metrics_to_copy.set(int(self.prefs.get('metrics_to_copy', 0))) next_y += item_height * len(metrics_options) + margin self.w.gobutton = Button( (margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Copy Metrics', callback=self.makeitso) next_y += item_height self.w.setDefaultButton(self.w.gobutton) self.w.open() def get_prefs(self, filename): self.pref_folder = os.path.expanduser( '~/Library/Application Support/Glyphs/Prefs') self.pref_filepath = os.path.join(self.pref_folder, filename) self.prefs = {} if os.path.exists(self.pref_filepath): with open(self.pref_filepath) as f: preflines = f.readlines() self.prefs = dict( line.split('\t') for line in preflines if line[0] != '#' and line.strip()) def set_prefs(self, **kwargs): try: if not os.path.exists(self.pref_folder): os.makedirs(self.pref_folder) pref_string = '\n'.join( ['\t'.join(str(b) for b in a) for a in kwargs.items()]) with open(self.pref_filepath, 'w') as f: f.write(pref_string) except AttributeError: print('The Preference filename has not been set.') def makeitso(self, sender): metrics_to_copy_mode = self.w.metrics_to_copy.get() self.set_prefs(metrics_to_copy=metrics_to_copy_mode) selected_layer_name = self.w.available_layers.getItem() source_master = self.available_layers[selected_layer_name] for l in Glyphs.font.selectedLayers: gname = l.parent.name corresponding_glyph = source_master.font.glyphs[gname] if corresponding_glyph is None: continue corresponding_layer = corresponding_glyph.layers[source_master.id] print(l.LSB, l.width, l.RSB) print(corresponding_layer.LSB, corresponding_layer.width, corresponding_layer.RSB) if metrics_to_copy_mode == 0: l.LSB = corresponding_layer.LSB elif metrics_to_copy_mode == 1: l.RSB = corresponding_layer.RSB elif metrics_to_copy_mode == 2: l.width = corresponding_layer.width elif metrics_to_copy_mode == 3: diff = (corresponding_layer.width - l.width) / 2 l.LSB += diff l.width = corresponding_layer.width elif metrics_to_copy_mode == 4: l.LSB = corresponding_layer.LSB l.width = corresponding_layer.width elif metrics_to_copy_mode == 5: l.LSB = corresponding_layer.LSB l.RSB = corresponding_layer.RSB elif metrics_to_copy_mode == 6: l.RSB = corresponding_layer.RSB l.width = corresponding_layer.width print(l.LSB, l.width, l.RSB) self.w.close()
class makePlist(object): def __init__(self): self.file_name = 'CustomFilter Project Glyph Sets.plist' folder_path = os.path.dirname(Glyphs.font.filepath) self.file_path = os.path.join(folder_path, self.file_name) self.charsets = OrderedDict() self.parse_plist() self.basic_xml = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.08"> <array> {charsets} </array> </plist>""" item_height = 24.0 w_width = 350.0 w_height = item_height * 7 margin = 10 next_y = margin col_1_width = w_width - (margin * 2) item_height = 24 self.w = Window((w_width, w_height), "Insert a font as brace layers") next_y = margin self.w.charset_name_label = TextBox((margin, next_y + 2, col_1_width, item_height), "Character Set Name:", sizeStyle='regular') next_y += item_height self.w.charset_name = EditText((margin, next_y, col_1_width, item_height), callback=self.check_extant_charsets) next_y += item_height + 2 self.w.extant_warning = TextBox((margin, next_y + 2, col_1_width, item_height), "The Character Set already exists and will be overwritten!", sizeStyle='small') self.w.extant_warning.show(False) next_y += (item_height * 0.7) + margin self.w.reopen = CheckBox((margin, next_y, col_1_width, item_height), "Close and Reopen current file", value=True) next_y += item_height + margin self.w.gobutton = Button((margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Make Character Set', callback=self.makeitso) self.w.setDefaultButton(self.w.gobutton) self.w.open() class charset: def __init__(self, xml_string=None): self.name = None self.glyph_names = None self.charset_template = """ \t<dict> \t\t<key>list</key> \t\t<array> \t\t\t{glyphnames} \t\t</array> \t\t<key>name</key> \t\t<string>{charset_name}</string> \t</dict>""" if xml_string: self.parse_xml_string(xml_string) def parse_xml_string(self, xml_string): xml_string = re.sub('<!--.+?-->', '', xml_string) try: self.name = re.search(r'<key>name<\/key>\s+<string>(.+)<\/string>', xml_string).group(1) glyph_names_string = re.search(r'<array>(.+?)<\/array>', xml_string, re.DOTALL).group(1) self.glyph_names = re.findall(r'<string>(.+?)<\/string>', glyph_names_string) except AttributeError: pass def make_xml_string(self): glyph_strings = '\n'.join(['\t\t\t<string>{gn}</string>'.format(gn=gn) for gn in self.glyph_names]) return self.charset_template.format(glyphnames=glyph_strings, charset_name=self.name) def parse_plist(self): if not os.path.exists(self.file_path): self.make_file() with open(self.file_path, 'r') as f: file_contents = f.read() all_charsets = re.findall(r'<dict>.*?<\/dict>', file_contents, re.DOTALL) for cs in all_charsets: csObj = self.charset(cs) self.charsets[csObj.name] = csObj def check_extant_charsets(self, sender): self.w.extant_warning.show(sender.get() in self.charsets.keys()) def make_file(self): with open(self.file_path, 'w') as f: f.write(self.basic_xml.format(charsets='')) def check_file(self): with open(self.file_path, 'r') as f: file_contents = f.read() return file_contents.startswith('<?xml version="1.0" encoding="UTF-8"?>') def replace_charset(self, charset_name, charset_entry): with open(self.file_path, 'r') as f: file_contents = f.read() all_char_sets = re.findall(r'<dict>.*?<\/dict>', file_contents, re.DOTALL) try: this_char_set = [x for x in all_char_sets if '<string>{}</string>'.format(charset_name) in x][0] except IndexError: return None file_contents.replace(this_char_set, charset_entry) with open(self.file_path, 'w') as f: f.write(file_contents) return True def append_charset(self, full_entry): with open(self.file_path, 'r') as f: file_contents = f.read() full_entry = full_entry + '\n</array>' file_contents = full_entry.join(file_contents.rsplit('</array>', 1)) with open(self.file_path, 'w') as f: f.write(file_contents) def makeitso(self, sender): self.w.close() charset_name = self.w.charset_name.get() if not charset_name: return if not os.path.exists(self.file_path): self.make_file() if not self.check_file(): self.make_file() glyph_names = [l.parent.name for l in Glyphs.font.selectedLayers] this_charset = self.charsets.get(charset_name) or self.charset() this_charset.name = charset_name this_charset.glyph_names = glyph_names self.charsets[charset_name] = this_charset all_charset_string = '\n'.join([cs.make_xml_string() for cs in self.charsets.values()]) full_text = self.basic_xml.format(charsets=all_charset_string) with open(self.file_path, 'w') as f: f.write(full_text) if self.w.reopen.get(): this_filepath = str(Glyphs.font.filepath) Glyphs.font.close() Glyphs.open(this_filepath)
from vanilla import Window, SquareButton, TextEditor import vanilla import mojo def onSubmit(sender): CurrentFont().info.note = window.textEditor.get() window.close() if CurrentFont() is None: vanilla.dialogs.message("there is no current font, operation aborted") else: window = Window((400, 400),"edit info.note", minSize=(100, 100)) window.textEditor = TextEditor(posSize=(0, 0, 400, 300)) noteContent = CurrentFont().info.note or "" window.textEditor.set(noteContent) window.updateNoteButton = SquareButton(posSize=(0, 350, 100, 50), title="update", callback=onSubmit) window.cancelButton = SquareButton(posSize=(110, 350, 100, 50), title="cancel", callback=lambda x: window.close()) window.open()
class GlyphnameDialog( object): def __init__( self ): x = 10 y = 10 height = 20 button_width = 30 glyphname_width = 180 gap = 6 self.w = Window( ( x + button_width + gap + glyphname_width + gap + button_width + x, y + height + y ), "insert glyph" ) self.w.center() self.w.glyphname = EditText( ( x, y, glyphname_width, height ), '') x += glyphname_width + gap self.w.alignleft = Button( ( x, y, button_width, height ), LEFT, callback = self.buttonCallback ) x += button_width + gap self.w.alignright = Button( ( x, y, button_width, height ), RIGHT, callback = self.buttonCallback ) self.w.setDefaultButton( self.w.alignleft ) self.w.alignright.bind( "\x1b", [] ) self.w.open() def buttonCallback( self, sender ): title = sender.getTitle() glyphname = self.w.glyphname.get() if not glyphname: self.w.close() return if len( glyphname ) == 1: uni = ord(glyphname) g = font.glyphForUnicode_("%.4X" % uni) if g: glyphname = g.name other_glyph = font.glyphs[ glyphname ] if not other_glyph: for glyph in font.glyphs: if glyph.name.startswith( glyphname ): other_glyph = glyph print 'Using', glyph.name break else: print 'No matching glyph found.' self.w.close() return for layer in font.selectedLayers: glyph = layer.parent glyph.beginUndo() # deselect all for path in layer.paths: for node in path.nodes: layer.removeObjectFromSelection_( node ) # find other layer for other_layer in other_glyph.layers: if other_layer.name == layer.name: # insert paths for path in other_layer.copyDecomposedLayer().paths: if title == RIGHT: shift = layer.width - other_layer.width for node in path.nodes: node.x = node.x + shift layer.paths.append( path ) # select path layer.paths[-1].selected = True break glyph.endUndo() self.w.close()
class CopyGlyphs: def __init__(self): self.doMarkGlyphs = 0 self.doOverwrite = 1 self.sourceFontList = AllFonts() self.destinationFontList = AllFonts() self.source_font = self.sourceFontList[0] self.destination_fonts = None self.glyphs = None self.mark = NSColor.redColor() sl = [] for f in self.sourceFontList: if f.info.familyName != None: fn = f.info.familyName else: fn = "None" if f.info.styleName != None: fs = f.info.styleName else: fs = "None" sl.append(fn+" "+fs) ## create a window self.w = Window((700, 500), "Copy Glyphs", minSize=(700, 500)) self.w.sourceTitle = TextBox((15, 20, 200, 20), "Source Font:") self.w.sourceFont = PopUpButton((15, 42, -410, 20), sl, callback=self.sourceCallback) self.w.glyphs = GlyphCollectionView((16, 70, -410, -65), initialMode="list", enableDelete=False, allowDrag=False, selectionCallback=self.glyphCallback) self._sortGlyphs(self.source_font) self.w.desTitle = TextBox((-400, 20, 200, 20), "Destination Fonts:") self.w.destinationFonts = FontList((-400, 42, -15, -115), self.destinationFontList, selectionCallback=self.desCallback) self.w.overwrite = CheckBox((-395, -105, 130, 22), "Overwrite glyphs", callback=self.overwriteCallback, value=self.doOverwrite) self.w.markGlyphs = CheckBox((-395, -84, 100, 22), "Mark Glyphs", callback=self.markCallback, value=self.doMarkGlyphs) self.w.copyButton = Button((-115, -40, 100, 20), 'Copy Glyphs', callback=self.copyCallback) self.w.line = HorizontalLine((10, -50, -10, 1)) self._checkSelection() self._updateDest() ## open the window self.w.open() def _updateDest(self): des = list(self.sourceFontList) des.remove(self.source_font) self.w.destinationFonts.set(des) def _sortGlyphs(self, font): gs = font.keys() self.w.glyphs.set([font[x] for x in sorted(gs)]) def _altName(self, font, glyph): name = glyph + '.copy' count = 1 while name in font.keys(): name = name + str(count) count += 1 return name def _checkSelection(self): if self.glyphs == None or len(self.glyphs) == 0: if len(self.source_font.selection) != 0: self.glyphs = self.source_font.selection select = [] for i, g in enumerate(self.w.glyphs): if g.name in self.glyphs: select.append(i) print(select) self.w.glyphs.setSelection(select) print(self.glyphs) def copyGlyphs(self, glyphs, source_font, destination_fonts, overwrite, mark): for glyph in glyphs: for font in destination_fonts: if glyph in font.keys() and overwrite == 0: n = self._altName(font, glyph) else: n = glyph font.insertGlyph(source_font[glyph], name=n) if mark == 1: font[n].mark = NSColorToRgba(self.mark) def overwriteCallback(self, sender): self.doOverwrite = sender.get() def markCallback(self, sender): self.doMarkGlyphs = sender.get() if self.doMarkGlyphs == 1: self.w.colorWell = ColorWell((-265, -85, 100, 23), callback=self.colorCallback, color=self.mark) else: del self.w.colorWell def colorCallback(self, sender): self.mark = sender.get() def sourceCallback(self, sender): self.source_font = self.sourceFontList[sender.get()] self._sortGlyphs(self.source_font) self._checkSelection() self._updateDest() def glyphCallback(self, sender): self.glyphs = [self.w.glyphs[x].name for x in sender.getSelection()] def desCallback(self, sender): self.destination_fonts = [sender.get()[x] for x in sender.getSelection()] def copyCallback(self, sender): self.sheet = Sheet((300, 50), self.w) self.sheet.bar = ProgressBar((10, 20, -10, 10), isIndeterminate=True, sizeStyle="small") self.sheet.open() self.sheet.bar.start() self.copyGlyphs(self.glyphs, self.source_font, self.destination_fonts, self.doOverwrite, self.doMarkGlyphs) self.sheet.bar.stop() self.sheet.close() del self.sheet self.w.close()
class GlyphnameDialog(object): def __init__(self): x = 10 y = 10 height = 20 button_width = 30 glyphname_width = 180 gap = 6 self.w = Window( (x + button_width + gap + glyphname_width + gap + button_width + x, y + height + y), "insert glyph") self.w.center() self.w.glyphname = EditText((x, y, glyphname_width, height), '') x += glyphname_width + gap self.w.alignleft = Button((x, y, button_width, height), LEFT, callback=self.buttonCallback) x += button_width + gap self.w.alignright = Button((x, y, button_width, height), RIGHT, callback=self.buttonCallback) self.w.setDefaultButton(self.w.alignleft) self.w.alignright.bind("\x1b", []) self.w.open() def buttonCallback(self, sender): alignment = sender.getTitle() glyphname = self.w.glyphname.get() if not glyphname: self.w.close() return if len(glyphname) == 1: uni = ord(glyphname) g = font.glyphForUnicode_("%.4X" % uni) if g: glyphname = g.name other_glyph = font.glyphs[glyphname] if not other_glyph: for glyph in font.glyphs: if glyph.name.startswith(glyphname): other_glyph = glyph print 'Using', glyph.name break else: print 'No matching glyph found.' self.w.close() return selected_glyphs = set([layer.parent for layer in font.selectedLayers]) for glyph in selected_glyphs: glyph.beginUndo() for layer in glyph.layers: # find other layer for other_layer in other_glyph.layers: if other_layer.name == layer.name: insert_paths(layer, other_layer, alignment) break else: if active_layerId == layer.layerId: insert_paths( layer, other_glyph.layers[layer.associatedMasterId], alignment) glyph.endUndo() self.w.close()
class replaceNamedComponent: def __init__(self): item_height = 24.0 margin = 10 w_width = 350.0 w_height = (item_height * 5) + margin next_y = margin col_1_width = w_width - (margin * 2) item_height = 24 self.get_prefs('SlantComponentPositions.pref') self.w = Window((w_width, w_height), "Slant Angle") self.w.slant_angle_text = TextBox( (margin, next_y + 2, col_1_width, item_height), "Slant Angle:", sizeStyle='regular') next_y += item_height self.w.slant_angle = EditText( (margin, next_y, col_1_width, item_height), self.prefs.get('slant_angle', '')) next_y += item_height + margin self.w.slant_all_layers = CheckBox( (margin, next_y, col_1_width, item_height), "Slant All Layers", value=int(self.prefs.get('slant_all_layers')), sizeStyle='regular') next_y += item_height + margin self.w.makeitso = Button( (w_width / 4.0, next_y, col_1_width / 2.0, item_height), 'Slant Components', callback=self.makeitso) self.w.setDefaultButton(self.w.makeitso) self.w.open() self.w.center() def get_prefs(self, filename): self.pref_folder = os.path.expanduser( '~/Library/Application Support/Glyphs/Prefs') self.pref_filepath = os.path.join(self.pref_folder, filename) self.prefs = {} if os.path.exists(self.pref_filepath): with open(self.pref_filepath) as f: preflines = f.readlines() self.prefs = dict( line.split('\t') for line in preflines if line[0] != '#' and line.strip()) def set_prefs(self, **kwargs): try: if not os.path.exists(self.pref_folder): os.makedirs(self.pref_folder) pref_string = '\n'.join( ['\t'.join(str(b) for b in a) for a in kwargs.items()]) with open(self.pref_filepath, 'w') as f: f.write(pref_string) except AttributeError: print('The Preference filename has not been set.') def get_reference_point(self, selection, layer=None): return self.get_center_of_selection(selection) # return NSPoint(self.get_obj_center(layer).x, Glyphs.font.selectedFontMaster.xHeight / 2) def get_obj_center(self, obj): return NSPoint(obj.bounds.origin.x + (obj.bounds.size.width / 2), obj.bounds.origin.y + (obj.bounds.size.height / 2)) def get_center_of_selection(self, selection): all_points = [] for obj in selection: try: all_points.append(self.get_obj_center(obj)) except AttributeError: all_points.append(obj.position) all_xs = [x.x for x in all_points] all_ys = [x.x for x in all_points] x_average = sum(all_xs) / len(all_xs) y_average = sum(all_ys) / len(all_ys) return NSPoint(int(x_average), int(y_average)) def makeitso(self, sender): self.w.close() slant_all_layers = self.w.slant_all_layers.get() # print(slant_all_layers, type(slant_all_layers)) try: slant_angle = float(self.w.slant_angle.get()) except TypeError: Glyphs.showNotification('Slant Component Positions', 'The slant angle was not a number!') return self.set_prefs(slant_angle=slant_angle, slant_all_layers=slant_all_layers) if not Glyphs.font.selectedLayers: return for sl in Glyphs.font.selectedLayers: if slant_all_layers: layers = [l for l in sl.parent.layers] else: layers = [sl] for l in layers: comps = [c for c in l.components if c.selected ] or [c for c in l.components] if not comps: Glyphs.showNotification( 'Slant Component Positions', 'You haven\'t selected any Components!') return reference_point = self.get_reference_point(comps, l) for c in comps: x_shift = math.tan(math.radians(slant_angle)) * ( self.get_obj_center(c).y - reference_point.y) c.position = NSPoint(c.position.x + x_shift, c.position.y)
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 CopyGlyphs: def __init__(self): self.doMarkGlyphs = 0 self.doOverwrite = 1 self.sourceFontList = AllFonts() self.destinationFontList = AllFonts() self.source_font = self.sourceFontList[0] self.destination_fonts = None self.glyphs = None self.mark = NSColor.redColor() ## create a window self.w = Window((700, 500), "Copy Glyphs", minSize=(700, 500)) self.w.sourceTitle = TextBox((15, 20, 200, 20), "Source Font:") self.w.sourceFont = PopUpButton((15, 42, -410, 20), [f.info.familyName + ' ' + f.info.styleName for f in self.sourceFontList], callback=self.sourceCallback) self.w.glyphs = GlyphCollectionView((16, 70, -410, -65), initialMode="list", enableDelete=False, allowDrag=False, selectionCallback=self.glyphCallback) self._sortGlyphs(self.source_font) self.w.desTitle = TextBox((-400, 20, 200, 20), "Destination Fonts:") self.w.destinationFonts = FontList((-400, 42, -15, -115), self.destinationFontList, selectionCallback=self.desCallback) self.w.overwrite = CheckBox((-395, -105, 130, 22), "Overwrite glyphs", callback=self.overwriteCallback, value=self.doOverwrite) self.w.markGlyphs = CheckBox((-395, -84, 100, 22), "Mark Glyphs", callback=self.markCallback, value=self.doMarkGlyphs) self.w.copyButton = Button((-115, -40, 100, 20), 'Copy Glyphs', callback=self.copyCallback) self.w.line = HorizontalLine((10, -50, -10, 1)) self._updateDest() ## open the window self.w.open() def _updateDest(self): des = list(self.sourceFontList) des.remove(self.source_font) self.w.destinationFonts.set(des) def _sortGlyphs(self, font): gs = font.keys() gs.sort() self.w.glyphs.set([font[x] for x in gs]) def _altName(self, font, glyph): name = glyph + '.copy' count = 1 while name in font.keys(): name = name + str(count) count += 1 return name def copyGlyphs(self, glyphs, source_font, destination_fonts, overwrite, mark): for glyph in glyphs: for font in destination_fonts: if glyph in font.keys() and overwrite == 0: n = self._altName(font, glyph) else: n = glyph font.insertGlyph(source_font[glyph], name=n) if mark == 1: font[n].mark = NSColorToRgba(self.mark) def overwriteCallback(self, sender): self.doOverwrite = sender.get() def markCallback(self, sender): self.doMarkGlyphs = sender.get() if self.doMarkGlyphs == 1: self.w.colorWell = ColorWell((-265, -85, 100, 23), callback=self.colorCallback, color=self.mark) else: del self.w.colorWell def colorCallback(self, sender): self.mark = sender.get() def sourceCallback(self, sender): self.source_font = self.sourceFontList[sender.get()] self._sortGlyphs(self.source_font) self._updateDest() def glyphCallback(self, sender): self.glyphs = [self.w.glyphs[x].name for x in sender.getSelection()] def desCallback(self, sender): self.destination_fonts = [sender.get()[x] for x in sender.getSelection()] def copyCallback(self, sender): self.sheet = Sheet((300, 50), self.w) self.sheet.bar = ProgressBar((10, 20, -10, 10), isIndeterminate=True, sizeStyle="small") self.sheet.open() self.sheet.bar.start() self.copyGlyphs(self.glyphs, self.source_font, self.destination_fonts, self.doOverwrite, self.doMarkGlyphs) self.sheet.bar.stop() self.sheet.close() del self.sheet self.w.close()
def userChecksInfoNote(font, fontState): window = Window((400, 400),"edit info.note", minSize=(100, 100)) window.textEditor = TextEditor(posSize=(0, 0, 400, 300)) noteContent = font.info.note or "" window.textEditor.set(noteContent) def handleCommit(s): editorContent = window.textEditor.get() window.close() commitInfoNote(editorContent, font, fontState) window.updateNoteButton = SquareButton(posSize=(0, 350, 100, 50), title="commit", callback=handleCommit) window.cancelButton = SquareButton(posSize=(110, 350, 130, 50), title="cancel operation", callback=lambda x: window.close()) window.open()
class makeItSo(object): def __init__(self): self.w = Window((550, 140), "Replace named Layer") self.w.editText = EditText((10, 15, -10, 22), placeholder="Layer Name", text='{170}') self.w.correct_path_direction = CheckBox((10, 50, -10, 18), "Correct Path Direction", value=True, sizeStyle='small') self.w.sync_metrics = CheckBox((210, 50, -10, 18), "Sync Metrics", value=True, sizeStyle='small') self.w.add_if_missing = CheckBox((10, 70, -10, 18), "Add layer if missing", value=True, sizeStyle='small') self.w.copybutton = Button((10, 100, -10, 17), "Replace layer", callback=self.buttonCallback) self.w.open() def buttonCallback(self, sender): self.w.close() target_font = Glyphs.font source_font = Glyphs.fonts[1] target_font.disableUpdateInterface() target_layer_name = self.w.editText.get() for source_glyph in source_font.glyphs: target_glyph = target_font.glyphs[source_glyph.name] if target_glyph is None: continue source_layer = source_glyph.layers[0] newL = copy.copy(source_layer) extant = True try: target_layer = [x for x in target_glyph.layers if x.name == target_layer_name][0] newL.associatedMasterId = target_layer.associatedMasterId except IndexError: if not self.w.add_if_missing.get(): continue source_weightValue = source_font.instances[0].weightValue mid = target_font.masters[0].id for m in target_font.masters: if m.weightValue > source_weightValue: break mid = m.id newL.associatedMasterId = mid extant = False newL.name = target_layer_name for c in newL.components: c.automaticAlignment = False if target_font.glyphs[c.componentName] is None: newLayerPaths = source_layer.copyDecomposedLayer() newL.paths = newLayerPaths.paths newL.components = [] break if self.w.correct_path_direction: newL.correctPathDirection() if extant: target_glyph.layers[target_layer.layerId] = newL else: target_glyph.layers.append(newL) if self.w.sync_metrics and extant: target_glyph.layers[target_layer.layerId].syncMetrics() target_font.enableUpdateInterface()
class makeDisplay(object): def __init__(self): self.all_layer_combos = self.get_all_layer_combos() self.instance_values = self.get_instance_values() item_height = 24.0 w_width = 300.0 w_height = item_height * 10 margin = 10 next_y = margin col_1_width = w_width - (margin * 2) col_2_width = (w_width / 2) - (margin * 1.5) item_height = 24 radio_height = 20 * 2 self.get_prefs('addBraceLayers.pref') self.w = Window((w_width, w_height), "Add Layers") self.w.text_1 = TextBox((margin, next_y, col_1_width, item_height), "Layer Combinations:", sizeStyle='regular') next_y += item_height self.w.parent_layers = PopUpButton( (margin, next_y, col_1_width, item_height), self.all_layer_combos, sizeStyle='regular') self.set_all_layer_combos() next_y += item_height + margin self.w.brace_or_bracket = RadioGroup( (margin, next_y, col_1_width, radio_height), ['Bracket Layer [X]', 'Brace Layer {X}'], sizeStyle='regular') self.w.brace_or_bracket.set(int(self.prefs.get('brace_or_bracket', 0))) next_y += radio_height + margin self.w.text_2 = TextBox((margin, next_y, col_1_width, item_height), "Layer Value:", sizeStyle='regular') next_y += item_height self.w.layer_value = EditText( (margin, next_y, col_2_width, item_height), '', sizeStyle='small', placeholder='e.g. 700') self.w.instance_value_popup = PopUpButton( (margin + margin + col_2_width, next_y, col_2_width, item_height), self.instance_values, sizeStyle='regular', callback=self.changeinstance_value) next_y += item_height + margin if self.prefs.get('layer_value') is not None: self.w.layer_value.set(self.prefs.get('layer_value')) self.w.gobutton = Button( (margin + (col_1_width / 4), next_y, col_1_width / 2, item_height), 'Add Layers', callback=self.makeitso) self.w.setDefaultButton(self.w.gobutton) self.w.center() self.w.open() # self.makeitso(None) def get_prefs(self, filename): self.pref_folder = os.path.expanduser( '~/Library/Application Support/Glyphs/Prefs') self.pref_filepath = os.path.join(self.pref_folder, filename) self.prefs = {} if os.path.exists(self.pref_filepath): with open(self.pref_filepath) as f: preflines = f.readlines() self.prefs = dict( line.split('\t') for line in preflines if line[0] != '#' and line.strip()) def set_prefs(self, **kwargs): try: if not os.path.exists(self.pref_folder): os.makedirs(self.pref_folder) pref_string = '\n'.join( ['\t'.join(str(b) for b in a) for a in kwargs.items()]) with open(self.pref_filepath, 'w') as f: f.write(pref_string) except AttributeError: print('The Preference filename has not been set.') def get_all_layer_combos(self): master_combos = OrderedDict() for mi, m in enumerate(Glyphs.font.masters): try: next_m = Glyphs.font.masters[mi + 1] if not next_m: break combo_tup = (m, next_m) master_combos['{}-{}'.format( *[x.name for x in combo_tup])] = combo_tup except IndexError: break return master_combos def set_all_layer_combos(self): selection_index = Glyphs.font.masterIndex if selection_index >= len(self.all_layer_combos): selection_index = len(self.all_layer_combos) - 1 self.w.parent_layers.set(selection_index) def get_instance_values(self): return [''] + [ ', '.join([str(x) for x in list(i.axes)]) for i in Glyphs.font.instances ] def changeinstance_value(self, sender): self.w.layer_value.set(sender.getItem()) def makeitso(self, sender): try: self.w.close() except AttributeError: pass parent_layers = self.w.parent_layers.getItem() brace_or_bracket = self.w.brace_or_bracket.get() layer_value = float(self.w.layer_value.get().strip()) if int(layer_value) - layer_value == 0: layer_value = int(layer_value) self.set_prefs( parent_layers=parent_layers, brace_or_bracket=brace_or_bracket, layer_value=layer_value, ) layer_name_template = '{master_name} {{{layer_value}}}' if brace_or_bracket else '{master_name} [{layer_value}]' master_names = parent_layers.split('-') masters = [m for m in Glyphs.font.masters if m.name in master_names] for sl in Glyphs.font.selectedLayers: g = sl.parent for m in masters: newL = copy.copy(g.layers[m.id]) newL.layerId = None newL.associatedMasterId = m.id newL.name = layer_name_template.format( master_name=m.name, layer_value=layer_value, ) g.layers.append(newL)