class TRSmartCorner(QtGui.QVBoxLayout): # - Split/Break contour def __init__(self, parentWidget): super(TRSmartCorner, self).__init__() self.upper_widget = parentWidget # -- Init self.active_font = pFont() self.builder = None self.font_masters = self.active_font.masters() self.empty_preset = lambda row: OrderedDict([(row, OrderedDict([('Preset', 'Preset %s' %row)] + [(master, '0') for master in self.font_masters]))]) self.table_dict = self.empty_preset(0) self.last_preset = 0 # -- Widgets self.lay_head = QtGui.QGridLayout() self.edt_glyphName = QtGui.QLineEdit() self.edt_glyphName.setPlaceholderText('Glyph name') # -- Buttons self.btn_getBuilder = QtGui.QPushButton('Set &Builder') self.btn_findBuilder = QtGui.QPushButton('&From Font') self.btn_addPreset = QtGui.QPushButton('Add') self.btn_delPreset = QtGui.QPushButton('Remove') self.btn_resetPreset = QtGui.QPushButton('Reset') self.btn_loadPreset = QtGui.QPushButton('&Load Presets') self.btn_savePreset = QtGui.QPushButton('&Save Presets') self.btn_apply_smartCorner = QtGui.QPushButton('&Apply Smart Corner') self.btn_remove_smartCorner = QtGui.QPushButton('R&emove Smart Corner') self.btn_remove_presetCorner = QtGui.QPushButton('&Find and Remove') self.btn_apply_smartCorner.setToolTip('Apply Smart Corner preset on SELECTED nodes.') self.btn_remove_smartCorner.setToolTip('Remove Smart Corner on SELECTED nodes.') self.btn_remove_presetCorner.setToolTip('Find and remove all Smart Corners that equal the currently selected preset.') self.btn_apply_round = QtGui.QPushButton('&Round') self.btn_apply_mitre = QtGui.QPushButton('&Mitre') self.btn_apply_overlap = QtGui.QPushButton('&Overlap') self.btn_apply_trap = QtGui.QPushButton('&Trap') self.btn_rebuild = QtGui.QPushButton('Rebuild corner') self.btn_getBuilder.setMinimumWidth(70) self.btn_findBuilder.setMinimumWidth(70) self.btn_apply_round.setMinimumWidth(70) self.btn_apply_mitre.setMinimumWidth(70) self.btn_apply_overlap.setMinimumWidth(70) self.btn_apply_trap.setMinimumWidth(70) self.btn_rebuild.setMinimumWidth(70) self.btn_addPreset.setMinimumWidth(70) self.btn_delPreset.setMinimumWidth(70) self.btn_loadPreset.setMinimumWidth(140) self.btn_savePreset.setMinimumWidth(140) self.btn_apply_smartCorner.setMinimumWidth(140) self.btn_remove_smartCorner.setMinimumWidth(140) self.btn_remove_presetCorner.setMinimumWidth(140) self.btn_getBuilder.setCheckable(True) self.btn_getBuilder.setChecked(False) self.btn_findBuilder.setEnabled(False) self.btn_apply_round.setEnabled(False) self.btn_getBuilder.clicked.connect(lambda: self.getBuilder()) self.btn_addPreset.clicked.connect(lambda: self.preset_modify(False)) self.btn_delPreset.clicked.connect(lambda: self.preset_modify(True)) self.btn_resetPreset.clicked.connect(lambda: self.preset_reset()) self.btn_loadPreset.clicked.connect(lambda: self.preset_load()) self.btn_savePreset.clicked.connect(lambda: self.preset_save()) self.btn_apply_smartCorner.clicked.connect(lambda: self.apply_SmartCorner(False)) self.btn_remove_smartCorner.clicked.connect(lambda: self.apply_SmartCorner(True)) self.btn_remove_presetCorner.clicked.connect(lambda: self.remove_SmartCorner()) #self.btn_apply_round.clicked.connect(lambda: self.apply_round()) self.btn_apply_mitre.clicked.connect(lambda: self.apply_mitre(False)) self.btn_apply_overlap.clicked.connect(lambda: self.apply_mitre(True)) self.btn_apply_trap.clicked.connect(lambda: self.apply_trap()) self.btn_rebuild.clicked.connect(lambda: self.rebuild()) # -- Preset Table self.tab_presets = TRTableView(None) self.preset_reset() # -- Build Layout self.lay_head.addWidget(QtGui.QLabel('Value Presets:'), 0,0,1,8) self.lay_head.addWidget(self.btn_loadPreset, 1,0,1,4) self.lay_head.addWidget(self.btn_savePreset, 1,4,1,4) self.lay_head.addWidget(self.btn_addPreset, 2,0,1,2) self.lay_head.addWidget(self.btn_delPreset, 2,2,1,2) self.lay_head.addWidget(self.btn_resetPreset, 2,4,1,4) self.lay_head.addWidget(self.tab_presets, 3,0,5,8) self.lay_head.addWidget(QtGui.QLabel('Corner Actions:'),10, 0, 1, 8) self.lay_head.addWidget(self.btn_apply_round, 11, 0, 1, 2) self.lay_head.addWidget(self.btn_apply_mitre, 11, 2, 1, 2) self.lay_head.addWidget(self.btn_apply_overlap, 11, 4, 1, 2) self.lay_head.addWidget(self.btn_apply_trap, 11, 6, 1, 2) self.lay_head.addWidget(self.btn_rebuild, 12, 0, 1, 8) self.lay_head.addWidget(QtGui.QLabel('Smart Corner:'), 13,0,1,8) self.lay_head.addWidget(QtGui.QLabel('Builder: '), 14,0,1,1) self.lay_head.addWidget(self.edt_glyphName, 14,1,1,3) self.lay_head.addWidget(self.btn_getBuilder, 14,4,1,2) self.lay_head.addWidget(self.btn_findBuilder, 14,6,1,2) self.lay_head.addWidget(self.btn_remove_smartCorner, 15,0,1,4) self.lay_head.addWidget(self.btn_remove_presetCorner, 15,4,1,4) self.lay_head.addWidget(self.btn_apply_smartCorner, 16,0,1,8) self.addLayout(self.lay_head) # - Presets management ------------------------------------------------ def preset_reset(self): self.builder = None self.active_font = pFont() self.font_masters = self.active_font.masters() self.table_dict = self.empty_preset(0) self.tab_presets.clear() self.tab_presets.setTable(self.table_dict, sortData=(False, False)) self.tab_presets.horizontalHeader().setStretchLastSection(False) self.tab_presets.verticalHeader().hide() #self.tab_presets.resizeColumnsToContents() def preset_modify(self, delete=False): table_rawList = self.tab_presets.getTable(raw=True) if delete: for selection in self.tab_presets.selectionModel().selectedIndexes: table_rawList.pop(selection.row()) print selection.row() new_entry = OrderedDict() for key, data in table_rawList: new_entry[key] = OrderedDict(data) if not delete: new_entry[len(table_rawList)] = self.empty_preset(len(table_rawList)).items()[0][1] self.tab_presets.setTable(new_entry, sortData=(False, False)) def preset_load(self): fontPath = os.path.split(self.active_font.fg.path)[0] fname = QtGui.QFileDialog.getOpenFileName(self.upper_widget, 'Load presets from file', fontPath, 'TypeRig JSON (*.json)') if fname != None: with open(fname, 'r') as importFile: imported_data = json.load(importFile) # - Convert Data new_data = OrderedDict() for key, data in imported_data: new_data[key] = OrderedDict(data) self.tab_presets.setTable(new_data, sortData=(False, False)) print 'LOAD:\t Font:%s; Presets loaded from: %s.' %(self.active_font.name, fname) def preset_save(self): fontPath = os.path.split(self.active_font.fg.path)[0] fname = QtGui.QFileDialog.getSaveFileName(self.upper_widget, 'Save presets to file', fontPath, 'TypeRig JSON (*.json)') if fname != None: with open(fname, 'w') as exportFile: json.dump(self.tab_presets.getTable(raw=True), exportFile) print 'SAVE:\t Font:%s; Presets saved to: %s.' %(self.active_font.name, fname) def getPreset(self): table_raw = self.tab_presets.getTable(raw=True) ''' try: active_preset_index = self.tab_presets.selectionModel().selectedIndexes[0].row() except IndexError: active_preset_index = None ''' active_preset_index = self.tab_presets.selectionModel().selectedIndexes[0].row() if active_preset_index is None: active_preset_index = self.last_preset else: self.last_preset = active_preset_index return dict(table_raw[active_preset_index][1][1:]) # - Basic Corner ------------------------------------------------ def apply_mitre(self, doKnot=False): # - Init process_glyphs = getProcessGlyphs(pMode) active_preset = self.getPreset() # - Process if len(process_glyphs): for glyph in process_glyphs: if glyph is not None: wLayers = glyph._prepareLayers(pLayers) for layer in reversed(wLayers): if layer in active_preset.keys(): selection = glyph.selectedNodes(layer, filterOn=True, extend=eNode, deep=True) for node in reversed(selection): if not doKnot: node.cornerMitre(float(active_preset[layer])) else: node.cornerMitre(-float(active_preset[layer]), True) action = 'Mitre Corner' if not doKnot else 'Overlap Corner' glyph.update() glyph.updateObject(glyph.fl, '%s @ %s.' %(action, '; '.join(active_preset.keys()))) def apply_trap(self): # - Init process_glyphs = getProcessGlyphs(pMode) active_preset = self.getPreset() # - Process if len(process_glyphs): for glyph in process_glyphs: if glyph is not None: wLayers = glyph._prepareLayers(pLayers) for layer in reversed(wLayers): if layer in active_preset.keys(): selection = glyph.selectedNodes(layer, filterOn=True, extend=eNode, deep=True) for node in reversed(selection): preset_values = tuple([float(item.strip()) for item in active_preset[layer].split(',')]) node.cornerTrapInc(*preset_values) glyph.update() glyph.updateObject(glyph.fl, '%s @ %s.' %('Ink Trap', '; '.join(active_preset.keys()))) def rebuild(self): # - Init process_glyphs = getProcessGlyphs(pMode) # - Process if len(process_glyphs): for glyph in process_glyphs: if glyph is not None: wLayers = glyph._prepareLayers(pLayers) for layer in wLayers: selection = glyph.selectedNodes(layer, filterOn=True, extend=eNode, deep=True) if len(selection) > 1: node_first = selection[0] node_last = selection[-1] line_in = node_first.getPrevLine() if node_first.getPrevOn(False) not in selection else node_first.getNextLine() line_out = node_last.getNextLine() if node_last.getNextOn(False) not in selection else node_last.getPrevLine() crossing = line_in & line_out node_first.smartReloc(*crossing) node_first.parent.removeNodesBetween(node_first.fl, node_last.getNextOn()) glyph.update() glyph.updateObject(glyph.fl, 'Rebuild corner: %s nodes reduced; At layers: %s' %(len(selection), '; '.join(wLayers))) # - Smart Corner ------------------------------------------------ def getBuilder(self): if self.btn_getBuilder.isChecked(): if len(self.edt_glyphName.text): builder_glyph = self.active_font.glyph(self.edt_glyphName.text) else: builder_glyph = eGlyph() self.edt_glyphName.setText(builder_glyph.name) if builder_glyph is not None: temp_builder = builder_glyph.getBuilders() if len(temp_builder.keys()) and filter_name in temp_builder.keys(): self.builder = temp_builder[filter_name][0] self.btn_getBuilder.setText('Release') else: self.builder = None self.edt_glyphName.clear() self.btn_getBuilder.setText('Set Builder') def process_setFilter(self, glyph, shape, layer, builder, suffix='.old'): new_container = fl6.flShape() new_container.shapeData.name = shape.shapeData.name if len(shape.shapeData.name): shape.shapeData.name += suffix #!!!! TODO: transformation copy and delete new_container.include(shape, glyph.layer(layer)) new_container.shapeBuilder = builder.clone() new_container.update() glyph.layer(layer).addShape(new_container) #glyph.layer(layer).update() def process_smartCorner(self, glyph, preset): wLayers = glyph._prepareLayers(pLayers) nodes_at_shapes = {} # - Build selection for work_layer in wLayers: if work_layer in preset.keys(): # - Init selection_deep = [(item[0], item[2]) for item in glyph.selectedAtShapes(layer=work_layer, index=False, deep=True)] selection_shallow = [(item[0], item[2]) for item in glyph.selectedAtShapes(layer=work_layer, index=False, deep=False)] selection = selection_deep + selection_shallow # - Build note to shape reference nodes_at_shapes[work_layer] = [(shape, [node for shape, node in list(nodes)]) for shape, nodes in groupby(selection, key=itemgetter(0))] # - Process glyph for work_layer in wLayers: if work_layer in nodes_at_shapes.keys(): # - Build filter if not present for work_shape, node_list in nodes_at_shapes[work_layer]: if len(glyph.containers(work_layer)): for container in glyph.containers(work_layer): if work_shape not in container.includesList: self.process_setFilter(glyph, work_shape, work_layer, self.builder) else: self.process_setFilter(glyph, work_shape, work_layer, self.builder) # - Process the nodes process_nodes = [pNode(node) for shape, node_list in nodes_at_shapes[work_layer] for node in node_list] for work_node in process_nodes: angle_value = preset[work_layer] if 'DEL' not in angle_value.upper(): work_node.setSmartAngle(float(angle_value)) else: work_node.delSmartAngle() #glyph.update() #glyph.updateObject(glyph.fl, 'DONE:\t Glyph: %s; Filter: Smart corner; Parameters: %s' %(glyph.name, preset)) def apply_SmartCorner(self, remove=False): # NOTE: apply and remove here apply only to soelected nodes. if self.builder is not None: # - Init process_glyphs = getProcessGlyphs(pMode) active_preset = self.getPreset() if remove: # Build a special preset that deletes active_preset = {key:'DEL' for key in active_preset.keys()} # - Process if len(process_glyphs): for work_glyph in process_glyphs: if work_glyph is not None: self.process_smartCorner(work_glyph, active_preset) self.update_glyphs(process_glyphs, True) print 'DONE:\t Filter: Smart Corner; Glyphs: %s' %'; '.join([g.name for g in process_glyphs]) else: print 'ERROR:\t Please specify a Glyph with suitable Shape Builder (Smart corner) first!' def remove_SmartCorner(self): # Finds active preset in glyphs smart corners and removes them # - Init process_glyphs = getProcessGlyphs(pMode) active_preset = self.getPreset() # - Process if len(process_glyphs): for work_glyph in process_glyphs: if work_glyph is not None: # - Init wLayers = work_glyph._prepareLayers(pLayers) smart_corners, target_corners = [], [] # - Get all smart nodes/corners for layer in wLayers: for builder in work_glyph.getBuilders(layer)[filter_name]: smart_corners += builder.getSmartNodes() if len(smart_corners): for node in smart_corners: wNode = eNode(node) if wNode.getSmartAngleRadius() == float(active_preset[layer]): wNode.delSmartAngle() self.update_glyphs(process_glyphs, True) print 'DONE:\t Filter: Remove Smart Corner; Glyphs: %s' %'; '.join([g.name for g in process_glyphs]) def update_glyphs(self, glyphs, complete=False): for glyph in glyphs: glyph.update() if not complete: # Partial update - contour only for contour in glyph.contours(): contour.changed() else: # Full update - with undo snapshot glyph.updateObject(glyph.fl, verbose=False)
class WFontMetrics(QtGui.QWidget): def __init__(self, parentWidget): super(WFontMetrics, self).__init__() # - Init self.grid = QtGui.QGridLayout() self.upperWidget = parentWidget self.activeFont = pFont() self.metricData = { layer: self.activeFont.fontMetrics().asDict(layer) for layer in self.activeFont.masters() } # - Interface self.btn_apply = QtGui.QPushButton('Apply Changes') self.btn_reset = QtGui.QPushButton('Reset') self.btn_open = QtGui.QPushButton('Open') self.btn_save = QtGui.QPushButton('Save') self.btn_apply.clicked.connect(self.applyChanges) self.btn_reset.clicked.connect(self.resetChanges) self.btn_save.clicked.connect(self.exportMetrics) self.btn_open.clicked.connect(self.importMetrics) self.tab_fontMetrics = TRTableView(self.metricData) # - Build lbl_name = QtGui.QLabel('Font Metrics (All Masters)') lbl_name.setMaximumHeight(20) self.grid.addWidget(lbl_name, 0, 0, 1, 24) self.grid.addWidget(self.tab_fontMetrics, 1, 0, 5, 21) self.grid.addWidget(self.btn_save, 1, 21, 1, 3) self.grid.addWidget(self.btn_open, 2, 21, 1, 3) self.grid.addWidget(self.btn_reset, 4, 21, 1, 3) self.grid.addWidget(self.btn_apply, 5, 21, 1, 3) for i in range(1, 6): self.grid.setRowStretch(i, 2) self.setLayout(self.grid) def applyChanges(self): oldMetricData = self.activeFont.fontMetrics() newMetricData = self.tab_fontMetrics.getTable() for layer, metrics in newMetricData.iteritems(): oldMetricData.fromDict(metrics, layer) self.activeFont.fl.update() self.activeFont.updateObject( self.activeFont.fl, 'Font:%s; Font Metrics Updated!.' % self.activeFont.name) def resetChanges(self): self.tab_fontMetrics.setTable(self.metricData, True) print 'DONE:\t Font:%s; Font Metrics realoaded.' % self.activeFont.name def exportMetrics(self): fontPath = os.path.split(self.activeFont.fg.path)[0] fname = QtGui.QFileDialog.getSaveFileName(self.upperWidget, 'Save Font Metrics to file', fontPath, '*.json') if fname != None: with open(fname, 'w') as exportFile: json.dump(self.metricData, exportFile) print 'SAVE:\t Font:%s; Font Metrics saved to %s.' % ( self.activeFont.name, fname) def importMetrics(self): fontPath = os.path.split(self.activeFont.fg.path)[0] fname = QtGui.QFileDialog.getOpenFileName( self.upperWidget, 'Open Metric Expressions from file', fontPath) if fname != None: with open(fname, 'r') as importFile: loadedData = json.load(importFile) self.tab_fontMetrics.setTable(loadedData) print 'LOAD:\t Font:%s; Font Metrics loaded from %s.' % ( self.activeFont.name, fname) print 'NOTE:\t Use < Apply > to apply loaded metrics to active Font!'