class ColdtypeSerializer(BaseWindowController): def __init__(self): self.w = FloatingWindow((300, 40), "Coldtype Serializer", minSize=(123, 200)) self.w.globalToggle = CheckBox((10, 10, -10, 20), 'serialize?', value=True) self.output = Path("~/robofont-coldtype.json").expanduser().absolute() addObserver(self, "shouldDraw", "draw") self.setUpBaseWindowBehavior() self.w.open() def windowCloseCallback(self, sender): removeObserver(self, 'draw') super(ColdtypeSerializer, self).windowCloseCallback(sender) def shouldDraw(self, notification): if not self.w.globalToggle.get(): return glyph = notification['glyph'] data_out = {"name": glyph.name, "layers": {}} for g in glyph.layers: rp = RecordingPen() g.draw(rp) data_out["layers"][g.layer.name] = dict(value=rp.value, width=g.width) self.output.write_text(json.dumps(data_out)) print("> wrote glyph data", glyph.name)
class ColorThemesDialog: colorThemesFolder = os.path.join(os.getcwd(), 'themes') def __init__(self): self.w = FloatingWindow((246, 300), "CodeColors") x = y = p = 10 self.w.colorThemesList = List((x, y, -p, -p), sorted(self.colorThemes.keys()), selectionCallback=self.selectionCallback, allowsMultipleSelection=False, allowsEmptySelection=False) self.w.open() @property def colorThemes(self): return getColorThemes(self.colorThemesFolder) @property def colorTheme(self): i = self.w.colorThemesList.getSelection()[0] colorThemeName = sorted(self.colorThemes.keys())[i] return self.colorThemes[colorThemeName] def selectionCallback(self, sender): setColorTheme(self.colorTheme)
class highlightPointsOnMetrics(BaseWindowController): def __init__(self): self.w = FloatingWindow((135, 40), "", minSize=(123, 200)) # a checkbox to turn the tool on/off self.w.showPoints = CheckBox((10, 10, -10, 20), 'highlight points', value=True) # add an observer to the drawPreview event addObserver(self, "highlightPoints", "draw") # open window self.setUpBaseWindowBehavior() self.w.open() def windowCloseCallback(self, sender): # remove observer when window is closed removeObserver(self, 'draw') super(highlightPointsOnMetrics, self).windowCloseCallback(sender) def highlightPoints(self, info): # check if checkbox is selected if not self.w.showPoints.get(): return # get the current glyph glyph = info["glyph"] sc = info['scale'] # Anything more than 3 could be considered an intentional overshoot? rang = 3 asc = f.info.ascender xhe = f.info.xHeight cap = f.info.capHeight dsc = f.info.descender size = 8 * sc # draw highlight stroke(None) if glyph is not None: for c in glyph: for s in c: for p in s: if p.y == asc or p.y == xhe or p.y == cap or p.y == dsc or p.y == 0: pass else: if (asc + -rang <= p.y <= asc + rang) or ( xhe + -rang <= p.y <= xhe + rang) or ( cap + -rang <= p.y <= cap + rang) or ( dsc + -rang <= p.y <= dsc + rang) or (-rang <= p.y <= rang): # fill(1, 0, 0, .6) fill(None) stroke(1.0, 0.0, 0.0, .5) strokeWidth(7 * sc) oval(p.x - size / 2, p.y - size / 2, size, size)
class MoveGlyphWindow: '''Move the current glyph using sliders.''' def __init__(self, glyph): # if glyph is None, show a message if glyph is None: Message('no glyph selected', title='moveTool', informativeText='Please select one glyph first!') return # store the glyph and initial move values as object attributes self.glyph = glyph self.moveX = 0 # create a floating window self.w = FloatingWindow((200, 74), "move %s" % self.glyph.name) # add a slider for moving in the x axis self.w.sliderX = Slider((10, 10, -10, 22), value=0, maxValue=200, minValue=-200, callback=self.adjust) # open the window self.w.open() def adjust(self, sender): # get the current x and y values from the sliders valueX = self.w.sliderX.get() # calculate actual move distance x = self.moveX - valueX # move the glyph self.glyph.moveBy((x, y)) # update saved values self.moveX = valueX # print the current move distance print(self.moveX)
class ContrastController(object): def __init__(self): # base window self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT)) jumpingY = MARGIN_VER self.w.libChoice = PopUpButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), ['A', 'B', 'C'], callback=self.libChoiceCallback) jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER self.w.addButton = SquareButton( (MARGIN_HOR, jumpingY, NET_WIDTH * .6, SQUARE_BUTTON_HEIGHT), 'add stem', callback=self.addButtonCallback) self.w.checkBoxStar = CheckBoxStar( (MARGIN_HOR + MARGIN_VER + NET_WIDTH * .6, jumpingY, NET_WIDTH * .35, NET_WIDTH * .35), callback=self.checkBoxStarCallback) jumpingY += SQUARE_BUTTON_HEIGHT + MARGIN_VER self.w.removeButton = SquareButton( (MARGIN_HOR, jumpingY, NET_WIDTH * .6, SQUARE_BUTTON_HEIGHT), 'remove stem', callback=self.removeButtonCallback) # lit up! self.w.open() def libChoiceCallback(self, sender): print sender.get() def addButtonCallback(self, sender): print 'hit addButtonCallback' def removeButtonCallback(self, sender): print 'hit removeButtonCallback' def checkBoxStarCallback(self, sender): print sender.get()
class MoveGlyphWindow: '''Move the current glyph using sliders.''' def __init__(self, glyph): # if glyph is None, show a message # store the glyph and initial move values as object attributes self.w = FloatingWindow((200, 64), "move "+str(labelslider)) for labelslider in BuildLabelsList(f): # create a floating window # add a slider for moving in the x axis self.g = glyph self.moveX = 0 self.label= labelslider self.w.labelslider = Slider( (10, 10, -10, 22), value=0, maxValue=100, minValue=-100, callback=self.adjust) # open the window self.w.button = Button((15, -35, -15, 20), "Done") self.w.open() def adjust(self, sender): # get the current x and y values from the sliders valueX = self.w.labelslider.get() # calculate actual move distance x = self.moveX - valueX # move the glyph for contour in self.g: for point in counter.points: if point.labels is 'CROSSBAR': point.moveBy((x, 0)) # update saved values self.moveX = valueX # print the current move distance print(self.moveX)
class CornerController: def __init__(self): self.modifiedGlyph = None self.w = FloatingWindow((400, 170), 'Corner Tool') self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor()) self.modes = ['Break', 'Build','Pit'] self.objectTypes = {'Build':'Segment', 'Break':'Corner point', 'Pit':'Corner point'} self.parameters = { 'radius': VanillaSingleValueParameter('radius', 20, (-200, 200), numType='int'), 'roundness': VanillaSingleValueParameter('roundness', 1.25, (0, 4), numType='float'), 'depth': VanillaSingleValueParameter('depth', 30, (-100, 100), numType='int'), 'breadth': VanillaSingleValueParameter('breadth', 30, (0, 150), numType='int'), 'bottom': VanillaSingleValueParameter('bottom', 5, (0, 40), numType='int') } self.currentMode = 'Break' self.previewGlyph = None self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode) for i, mode in enumerate(self.modes): setattr(self.w, mode, Group((120, 15, -15, -15))) modeGroup = getattr(self.w, mode) modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u'>', callback=self.apply) modeGroup.infoBox = Box((0, 0, -50, 35)) modeGroup.info = TextBox((10, 8, -50, 20), 'No selection') if i > 0: modeGroup.show(False) self.w.Break.radius = ParameterSliderTextInput(self.parameters['radius'], (0, 60, -25, 25), title='Radius', callback=self.makePreviewGlyph) self.w.Break.roundness = ParameterSliderTextInput(self.parameters['roundness'], (0, 95, -25, 25), title='Roundness', callback=self.makePreviewGlyph) self.w.Pit.depth = ParameterSliderTextInput(self.parameters['depth'], (0, 50, -25, 25), title='Depth', callback=self.makePreviewGlyph) self.w.Pit.breadth = ParameterSliderTextInput(self.parameters['breadth'], (0, 80, -25, 25), title='Breadth', callback=self.makePreviewGlyph) self.w.Pit.bottom = ParameterSliderTextInput(self.parameters['bottom'], (0, 110, -25, 25), title='bottom', callback=self.makePreviewGlyph) addObserver(self, 'preview', 'draw') addObserver(self, 'preview', 'drawInactive') addObserver(self, 'makePreviewGlyph', 'mouseDown') addObserver(self, 'makePreviewGlyph', 'mouseDragged') addObserver(self, 'makePreviewGlyph', 'keyDown') addObserver(self, 'makePreviewGlyph', 'keyUp') addObserver(self, 'setControls', 'mouseUp') addObserver(self, 'setControls', 'selectAll') addObserver(self, 'setControls', 'deselectAll') addObserver(self, 'setControls', 'currentGlyphChanged') self.w.bind('close', self.windowClose) self.setControls() self.w.open() def changeMode(self, sender): index = sender.get() previousModeGroup = getattr(self.w, self.currentMode) previousModeGroup.show(False) self.currentMode = self.modes[index] modeGroup = getattr(self.w, self.currentMode) modeGroup.show(True) self.setControls() def setControls(self, notification=None): mode = self.currentMode selection = self.getSelection() modeGroup = getattr(self.w, mode) if not len(selection): modeGroup.apply.enable(False) modeGroup.info.set('No selection (%ss)'%(self.objectTypes[mode].lower())) elif len(selection): modeGroup.apply.enable(True) info = '%s valid %s'%(len(selection), self.objectTypes[mode].lower()) if len(selection) > 1: info += 's' modeGroup.info.set(info) self.makePreviewGlyph() def getSelection(self, notification=None): glyph = CurrentGlyph() if len(glyph.selection) == 0: return [] elif len(glyph.selection) > 0: iG = IntelGlyph(glyph) if self.currentMode == 'Build': selection = iG.getSelection(True) elif self.currentMode in ['Break', 'Pit']: selection = [point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi/18)] return selection def preview(self, notification): sc = notification['scale'] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor) def makePreviewGlyph(self, sender=None): if (sender is not None) and isinstance(sender, dict): if sender.has_key('notificationName') and sender['notificationName'] == 'mouseDragged': g = sender['glyph'] if not len(g.selection): return self.previewGlyph = self.makeCornerGlyph() UpdateCurrentGlyphView() def makeCornerGlyph(self, sender=None): mode = self.currentMode if mode == 'Build': cornerGlyph = self.buildCorners() elif mode == 'Break': cornerGlyph = self.breakCorners() elif mode == 'Pit': cornerGlyph = self.pitCorners() return cornerGlyph def buildCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) for contour in iG: segments = contour.collectSegments()['selection'] l = len(segments) lines, curves = self.checkComposition(segments) if l > 1 and lines and curves: segments = [segment for segment in segments if len(segment) == 4] elif l > 1 and lines and not curves: segments = segments[:1] + segments[-1:] for segment in reversed(segments): contour.buildCorner(segment) return iG def breakCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) radius = self.parameters['radius'].get() roundness = self.parameters['roundness'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.breakCorner(point, radius, velocity=roundness) contour.correctSmoothness() return iG def pitCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) depth = self.parameters['depth'].get() breadth = self.parameters['breadth'].get() bottom = self.parameters['bottom'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.pitCorner(point, depth, breadth, bottom) contour.removeOverlappingPoints() contour.correctSmoothness() return iG def apply(self, sender): targetGlyph = CurrentGlyph() modifiedGlyph = self.makeCornerGlyph() targetGlyph.prepareUndo('un.round') targetGlyph.clearContours() for p in targetGlyph.selection: p.selected = False pen = targetGlyph.getPointPen() modifiedGlyph.drawPoints(pen) targetGlyph.performUndo() targetGlyph.update() def checkComposition(self, segmentsList): lines = 0 curves = 0 for segment in segmentsList: if len(segment) == 2: lines += 1 elif len(segment) == 4: curves += 1 return lines, curves def windowClose(self, notification): removeObserver(self, 'draw') removeObserver(self, 'drawInactive') removeObserver(self, 'mouseUp') removeObserver(self, 'mouseDown') removeObserver(self, 'mouseDragged') removeObserver(self, 'keyDown') removeObserver(self, 'keyUp') removeObserver(self, 'selectAll') removeObserver(self, 'deselectAll') removeObserver(self, 'currentGlyphChanged')
class CornerController: def __init__(self): self.modifiedGlyph = None self.w = FloatingWindow((400, 170), 'Corner Tool') self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor()) self.modes = ['Break', 'Build','Pit'] self.objectTypes = {'Build':'Segment', 'Break':'Corner point', 'Pit':'Corner point'} self.parameters = { 'radius': VanillaSingleValueParameter('radius', 20, (-200, 200), numType='int'), 'roundness': VanillaSingleValueParameter('roundness', 1.25, (0, 4), numType='float'), 'depth': VanillaSingleValueParameter('depth', 30, (-100, 100), numType='int'), 'breadth': VanillaSingleValueParameter('breadth', 30, (0, 150), numType='int'), 'bottom': VanillaSingleValueParameter('bottom', 5, (0, 40), numType='int') } self.currentMode = 'Break' self.previewGlyph = None self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode) for i, mode in enumerate(self.modes): setattr(self.w, mode, Group((120, 15, -15, -15))) modeGroup = getattr(self.w, mode) modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u'>', callback=self.apply) modeGroup.infoBox = Box((0, 0, -50, 35)) modeGroup.info = TextBox((10, 8, -50, 20), 'No selection') if i > 0: modeGroup.show(False) self.w.Break.radius = ParameterSliderTextInput(self.parameters['radius'], (0, 60, -25, 25), title='Radius', callback=self.makePreviewGlyph) self.w.Break.roundness = ParameterSliderTextInput(self.parameters['roundness'], (0, 95, -25, 25), title='Roundness', callback=self.makePreviewGlyph) self.w.Pit.depth = ParameterSliderTextInput(self.parameters['depth'], (0, 50, -25, 25), title='Depth', callback=self.makePreviewGlyph) self.w.Pit.breadth = ParameterSliderTextInput(self.parameters['breadth'], (0, 80, -25, 25), title='Breadth', callback=self.makePreviewGlyph) self.w.Pit.bottom = ParameterSliderTextInput(self.parameters['bottom'], (0, 110, -25, 25), title='bottom', callback=self.makePreviewGlyph) addObserver(self, 'preview', 'draw') addObserver(self, 'preview', 'drawInactive') addObserver(self, 'previewSolid', 'drawPreview') addObserver(self, 'makePreviewGlyph', 'mouseDown') addObserver(self, 'makePreviewGlyph', 'mouseDragged') addObserver(self, 'makePreviewGlyph', 'keyDown') addObserver(self, 'makePreviewGlyph', 'keyUp') addObserver(self, 'setControls', 'mouseUp') addObserver(self, 'setControls', 'selectAll') addObserver(self, 'setControls', 'deselectAll') addObserver(self, 'setControls', 'currentGlyphChanged') self.w.bind('close', self.windowClose) self.setControls() self.w.open() def changeMode(self, sender): index = sender.get() previousModeGroup = getattr(self.w, self.currentMode) previousModeGroup.show(False) self.currentMode = self.modes[index] modeGroup = getattr(self.w, self.currentMode) modeGroup.show(True) self.setControls() def setControls(self, notification=None): mode = self.currentMode selection = self.getSelection() modeGroup = getattr(self.w, mode) if not len(selection): modeGroup.apply.enable(False) modeGroup.info.set('No selection (%ss)'%(self.objectTypes[mode].lower())) elif len(selection): modeGroup.apply.enable(True) info = '%s valid %s'%(len(selection), self.objectTypes[mode].lower()) if len(selection) > 1: info += 's' modeGroup.info.set(info) self.makePreviewGlyph() def getSelection(self, notification=None): glyph = CurrentGlyph() if len(glyph.selection) == 0: return [] elif len(glyph.selection) > 0: iG = IntelGlyph(glyph) if self.currentMode == 'Build': selection = iG.getSelection(True) elif self.currentMode in ['Break', 'Pit']: selection = [point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi/18)] return selection def preview(self, notification): sc = notification['scale'] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor) def previewSolid(self, notification): sc = notification['scale'] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, plain=True) def makePreviewGlyph(self, sender=None): if (sender is not None) and isinstance(sender, dict): if sender.has_key('notificationName') and sender['notificationName'] == 'mouseDragged': g = sender['glyph'] if not len(g.selection): return self.previewGlyph = self.makeCornerGlyph() UpdateCurrentGlyphView() def makeCornerGlyph(self, sender=None): mode = self.currentMode if mode == 'Build': cornerGlyph = self.buildCorners() elif mode == 'Break': cornerGlyph = self.breakCorners() elif mode == 'Pit': cornerGlyph = self.pitCorners() return cornerGlyph def buildCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) for contour in iG: segments = contour.collectSegments()['selection'] l = len(segments) lines, curves = self.checkComposition(segments) if l > 1 and lines and curves: segments = [segment for segment in segments if len(segment) == 4] elif l > 1 and lines and not curves: segments = segments[:1] + segments[-1:] for segment in reversed(segments): contour.buildCorner(segment) return iG def breakCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) radius = self.parameters['radius'].get() roundness = self.parameters['roundness'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.breakCorner(point, radius, velocity=roundness) contour.correctSmoothness() return iG def pitCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) depth = self.parameters['depth'].get() breadth = self.parameters['breadth'].get() bottom = self.parameters['bottom'].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.pitCorner(point, depth, breadth, bottom) contour.removeOverlappingPoints() contour.correctSmoothness() return iG def apply(self, sender): targetGlyph = CurrentGlyph() modifiedGlyph = self.makeCornerGlyph() targetGlyph.prepareUndo('un.round') targetGlyph.clearContours() for p in targetGlyph.selection: p.selected = False pen = targetGlyph.getPointPen() modifiedGlyph.drawPoints(pen) targetGlyph.performUndo() targetGlyph.update() def checkComposition(self, segmentsList): lines = 0 curves = 0 for segment in segmentsList: if len(segment) == 2: lines += 1 elif len(segment) == 4: curves += 1 return lines, curves def windowClose(self, notification): removeObserver(self, 'draw') removeObserver(self, 'drawInactive') removeObserver(self, 'drawPreview') removeObserver(self, 'mouseUp') removeObserver(self, 'mouseDown') removeObserver(self, 'mouseDragged') removeObserver(self, 'keyDown') removeObserver(self, 'keyUp') removeObserver(self, 'selectAll') removeObserver(self, 'deselectAll') removeObserver(self, 'currentGlyphChanged')
class OverlayUFOs(BaseWindowController): DEFAULTKEY = "com.fontbureau.overlayUFO" DEFAULTKEY_DRAW = "%s.draw" % DEFAULTKEY DEFAULTKEY_FILL = "%s.fill" % DEFAULTKEY DEFAULTKEY_FILLCOLOR = "%s.fillColor" % DEFAULTKEY DEFAULTKEY_STROKE = "%s.stroke" % DEFAULTKEY DEFAULTKEY_STROKECOLOR = "%s.strokeColor" % DEFAULTKEY DEFAULTKEY_POINTS = "%s.points" % DEFAULTKEY DEFAULTKEY_POINTSCOLOR = "%s.pointsColor" % DEFAULTKEY DEFAULTKEY_ALIGNMENT = "%s.alignment" % DEFAULTKEY DEFAULTKEY_KERNING = "%s.kerning" % DEFAULTKEY DEFAULTKEY_FLOATING = "%s.floating" % DEFAULTKEY FALLBACK_FILLCOLOR = NSColor.colorWithCalibratedRed_green_blue_alpha_(0, 0.3, 1, .1) FALLBACK_STROKECOLOR = NSColor.colorWithCalibratedRed_green_blue_alpha_(0, 0.3, 1, .5) FALLBACK_POINTSCOLOR = NSColor.colorWithCalibratedRed_green_blue_alpha_(0, 0.3, 1, .5) def getListDescriptor(self): return [ dict(title="Status", key="status", width=10, cell=SmallTextListCell(), editable=False), dict(title="Name", key="name", width=230, cell=SmallTextListCell(), editable=False), ] def __init__(self): # Preferences self._drawing = getExtensionDefault(self.DEFAULTKEY_DRAW, True) self._fill = getExtensionDefault(self.DEFAULTKEY_FILL, True) self._stroke = getExtensionDefault(self.DEFAULTKEY_STROKE, True) self._points = getExtensionDefault(self.DEFAULTKEY_POINTS, True) self._fillColor = getExtensionDefaultColor(self.DEFAULTKEY_FILLCOLOR, self.FALLBACK_FILLCOLOR) self._strokeColor = getExtensionDefaultColor(self.DEFAULTKEY_STROKECOLOR, self.FALLBACK_STROKECOLOR) self._pointsColor = getExtensionDefaultColor(self.DEFAULTKEY_POINTSCOLOR, self.FALLBACK_POINTSCOLOR) self._alignment = getExtensionDefault(self.DEFAULTKEY_ALIGNMENT, 0) self._kerning = getExtensionDefault(self.DEFAULTKEY_KERNING, 1) self._floating = getExtensionDefault(self.DEFAULTKEY_FLOATING, 1) # User preferences self._onCurvePointsSize = getDefault("glyphViewOncurvePointsSize") # typo, should be: OnCurve self._offCurvePointsSize = getDefault("glyphViewOffCurvePointsSize") self._strokeWidth = getDefault("glyphViewStrokeWidth") w, h = 400, 195 x = y = 10 self.initAllFonts() self.w = FloatingWindow((w, h), "Overlay UFOs") self.w.draw = CheckBox((x, y, 95, 18), "Draw", callback=self.drawCallback, value=self._drawing, sizeStyle="small") x += 60 self.w.fill = CheckBox((x, y, 95, 18), "Fill", callback=self.fillCallback, value=self._fill, sizeStyle="small") x += 40 self.w.fillColor = ColorWell((x, y, 45, 20), callback=self.fillColorCallback, color=self._fillColor) x += 60 self.w.stroke = CheckBox((x, y, 95, 18), "Stroke", callback=self.strokeCallback, value=self._stroke, sizeStyle="small") x += 60 self.w.strokeColor = ColorWell((x, y, 45, 20), callback=self.strokeColorCallback, color=self._strokeColor) x += 60 self.w.points = CheckBox((x, y, 95, 18), "Points", callback=self.pointsCallback, value=self._points, sizeStyle="small") x += 60 self.w.pointsColor = ColorWell((x, y, 45, 20), callback=self.pointsColorCallback, color=self._pointsColor) x, y = 10, 40 self.w.alignText = TextBox((x, y, 250, 15), "Alignment:", sizeStyle="small") y += 18 self.w.alignment = RadioGroup((x, y, 80, 55), ['Left', 'Center', 'Right'], isVertical=True, callback=self.alignmentCallback, sizeStyle="small") self.w.alignment.set(self._alignment) y += 62 self.w.kerning = CheckBox((x, y, 100, 10), "Show kerning", callback=self.kerningCallback, value=self._kerning, sizeStyle="mini") y += 18 self.w.floating = CheckBox((x, y, 100, 10), "Floating Window", callback=self.floatingCallback, value=self._floating, sizeStyle="mini") y += 25 self.w.resetDefaults = Button((x, y, 85, 14), "Reset settings", callback=self.resetSettingsCallback, sizeStyle="mini") x, y = 110, 40 self.w.fontList = List((x, y, 240, 55), self.getFontItems(), columnDescriptions=self.getListDescriptor(), showColumnTitles=False, selectionCallback=None, doubleClickCallback=self.fontListCallback, allowsMultipleSelection=True, allowsEmptySelection=True, drawVerticalLines=False, drawHorizontalLines=True, drawFocusRing=False, rowHeight=16 ) y += 55 self.w.hiddenFontList = List((x, y, 240, 55), self.getHiddenFontItems(), columnDescriptions=self.getListDescriptor(), showColumnTitles=False, selectionCallback=None, doubleClickCallback=self.hiddenFontListCallback, allowsMultipleSelection=True, allowsEmptySelection=True, drawVerticalLines=False, drawHorizontalLines=True, drawFocusRing=False, rowHeight=16 ) self._selectionChanging = False self.w.fontList.setSelection([]) # unselect y += 65 self.w.contextLeft = EditText((x, y, 90, 20), callback=self.contextCallback, continuous=True, placeholder="Left", sizeStyle="small") self.w.contextCurrent = EditText((x+95, y, 50, 20), callback=self.contextCallback, continuous=True, placeholder="?", sizeStyle="small") self.w.contextRight = EditText((x+150, y, 90, 20), callback=self.contextCallback, continuous=True, placeholder="Right", sizeStyle="small") x, y = 360, 100 self.w.addFonts = Button((x, y, 30, 20), "+", callback=self.addHiddenFontsCallback, sizeStyle="regular") y += 25 self.w.removeFonts = Button((x, y, 30, 20), unichr(8722), callback=self.removeHiddenFontsCallback, sizeStyle="regular") # Observers addObserver(self, "fontDidOpen", "fontDidOpen") addObserver(self, "fontWillClose", "fontWillClose") # fontDidClose? addObserver(self, "draw", "drawInactive") addObserver(self, "draw", "draw") # Prepare and open window self.setWindowLevel() self.setUpBaseWindowBehavior() self.w.open() def setWindowLevel(self): if self._floating: self.w._window.setLevel_(NSFloatingWindowLevel) else: self.w._window.setLevel_(NSNormalWindowLevel) def windowCloseCallback(self, sender): removeObserver(self, "fontDidOpen") removeObserver(self, "fontWillClose") removeObserver(self, "drawInactive") removeObserver(self, "draw") self.updateView() super(OverlayUFOs, self).windowCloseCallback(sender) def updateView(self): UpdateCurrentGlyphView() def initAllFonts(self): fonts = [] for font in AllFonts(): fonts.append({"font": font, "status": u"•"}) self.fonts = fonts self.hiddenFonts = [] def getFontName(self, font): if font.document(): name = font.document().displayName() else: name = font.fileName.split("/")[-1] return name def getHiddenFontItems(self): hiddenFonts = self.hiddenFonts hiddenFontItems = self.getItems(hiddenFonts) return hiddenFontItems def getFontItems(self): fonts = self.fonts fontItems = self.getItems(fonts) return fontItems def getItems(self, fonts): items = [] uniqueNames = {} for f in fonts: font = f["font"] status = f["status"] path = font.path name = self.getFontName(font) if not uniqueNames.has_key(name): uniqueNames[name] = [] uniqueNames[name].append(path) paths = uniqueNames[name] if len(paths) > 1: prefix = commonprefix(paths) if prefix: suffix = " ...%s" % path[len(prefix):] else: suffix = " %s" % path name += suffix items.append({"status": status, "name": name}) return items def getActiveFonts(self): fonts = self.fonts + self.hiddenFonts activeFonts = [] for font in fonts: if font["status"]: activeFonts.append(font["font"]) return activeFonts def getContexts(self): return self.w.contextLeft.get(), self.w.contextCurrent.get(), self.w.contextRight.get() def getAlignment(self): index = self._alignment if index == 0: return 'left' elif index == 1: return 'center' elif index == 2: return 'right' def getKernValue(self, nakedFont, leftGlyph, rightGlyph): if not leftGlyph or not rightGlyph: return 0 if not self._kerning: return 0 return nakedFont.flatKerning.get((leftGlyph.name, rightGlyph.name)) or 0 def draw(self, info): if not self._drawing: return glyph = info.get("glyph") drawingScale = info.get('scale') if glyph is None: return current = glyph.getParent() fonts = self.getActiveFonts() for font in fonts: nakedFont = font.naked() contextLeft, contextCurrent, contextRight = self.getContexts() contextLeft = splitText(contextLeft or '', nakedFont.unicodeData or '') contextLeft = [font[gname] for gname in contextLeft if gname in font.keys()] contextRight = splitText(contextRight or '', nakedFont.unicodeData or '') contextRight = [font[gname] for gname in contextRight if gname in font.keys()] contextCurrent = splitText(contextCurrent or '', nakedFont.unicodeData or '') if len(contextCurrent) > 0: contextCurrent = [font[gname] for gname in [contextCurrent[0]] if gname in font.keys()] if len(contextCurrent) > 0: sourceGlyph = contextCurrent[0] else: sourceGlyph = None elif glyph.name in font.keys(): sourceGlyph = font[glyph.name] contextCurrent = [sourceGlyph] else: sourceGlyph = None contextCurrent = [] save() self._fillColor.setFill() self._strokeColor.setStroke() scale(current.info.unitsPerEm/float(font.info.unitsPerEm)) # Draw left context previousGlyph = sourceGlyph contextLeft.reverse() totalWidth = 0 for i, cbGlyph in enumerate(contextLeft): kernValue = self.getKernValue(nakedFont, cbGlyph, previousGlyph) # right to left translate(-cbGlyph.width-kernValue, 0) totalWidth += cbGlyph.width + kernValue glyphBezierPath = cbGlyph.naked().getRepresentation("defconAppKit.NSBezierPath") if self._fill: glyphBezierPath.fill() if self._stroke: glyphBezierPath.setLineWidth_(self._strokeWidth*drawingScale) strokePixelPath(glyphBezierPath) previousGlyph = cbGlyph translate(totalWidth, 0) # Draw current context or current glyph if contextCurrent: previousGlyph = None alignment = self.getAlignment() if alignment == 'left': offset = 0 elif alignment == 'center': offset = (glyph.width - contextCurrent[0].width)/2 elif alignment == 'right': offset = glyph.width - contextCurrent[0].width totalWidth = offset translate(totalWidth, 0) for i, cbGlyph in enumerate(contextCurrent): #if cbGlyph == glyph: # continue # Don't show if is current glyph kernValue = self.getKernValue(nakedFont, previousGlyph, cbGlyph) translate(kernValue, 0) glyphBezierPath = cbGlyph.naked().getRepresentation("defconAppKit.NSBezierPath") if self._fill: glyphBezierPath.fill() if self._stroke: glyphBezierPath.setLineWidth_(self._strokeWidth*drawingScale) strokePixelPath(glyphBezierPath) if self._points: self.drawPoints(cbGlyph, info['scale']) translate(cbGlyph.width, 0) totalWidth += cbGlyph.width + kernValue previousGlyph = cbGlyph translate(-totalWidth, 0) # Draw right context translate(glyph.width) totalWidth = glyph.width for i, cbGlyph in enumerate(contextRight): kernValue = self.getKernValue(nakedFont, previousGlyph, cbGlyph) translate(kernValue, 0) glyphBezierPath = cbGlyph.naked().getRepresentation("defconAppKit.NSBezierPath") if self._fill: glyphBezierPath.fill() if self._stroke: glyphBezierPath.setLineWidth_(self._strokeWidth*drawingScale) strokePixelPath(glyphBezierPath) translate(cbGlyph.width, 0) totalWidth += cbGlyph.width + kernValue previousGlyph = cbGlyph restore() def drawPoints(self, glyph, scale): save() _onCurveSize = self._onCurvePointsSize * scale _offCurveSize = self._offCurvePointsSize * scale _strokeWidth = self._strokeWidth * scale self._pointsColor.set() path = NSBezierPath.bezierPath() offCurveHandlesPath = NSBezierPath.bezierPath() pointsData = glyph.getRepresentation("doodle.OutlineInformation") for point1, point2 in pointsData["bezierHandles"]: offCurveHandlesPath.moveToPoint_(point1) offCurveHandlesPath.lineToPoint_(point2) for point in pointsData.get("offCurvePoints"): (x, y) = point["point"] path.appendBezierPathWithOvalInRect_(NSMakeRect(x - _offCurveSize, y - _offCurveSize, _offCurveSize * 2, _offCurveSize * 2)) for point in pointsData.get("onCurvePoints"): (x, y) = point["point"] path.appendBezierPathWithRect_(NSMakeRect(x - _onCurveSize, y - _onCurveSize, _onCurveSize * 2, _onCurveSize * 2)) path.fill() offCurveHandlesPath.setLineWidth_(_strokeWidth) strokePixelPath(offCurveHandlesPath) restore() def fontDidOpen(self, event): font = event["font"] self.fonts.append({"font": font, "status": u"•"}) self.w.fontList.set(self.getFontItems()) self.updateView() def fontWillClose(self, event): # not always working... try from path or name? closingFont = event["font"] for i, font in enumerate(self.fonts): if font["font"] == closingFont: del self.fonts[i] item = self.w.fontList.get()[i] self.w.fontList.remove(item) break self.updateView() def fontListCallback(self, sender): fonts = self.fonts self.listCallback(sender, fonts) def hiddenFontListCallback(self, sender): fonts = self.hiddenFonts self.listCallback(sender, fonts) def listCallback(self, sender, fonts): for index in sender.getSelection(): item = sender.get()[index] font = fonts[index] if item["status"]: item["status"] = font["status"] = "" else: item["status"] = font["status"] = u"•" self.updateView() def contextCallback(self, sender): self.updateView() def drawCallback(self, sender): drawing = sender.get() setExtensionDefault(self.DEFAULTKEY_DRAW, drawing) self._drawing = drawing self.updateView() def fillCallback(self, sender): fill = sender.get() setExtensionDefault(self.DEFAULTKEY_FILL, fill) self._fill = fill self.updateView() def fillColorCallback(self, sender): fillColor = sender.get() setExtensionDefaultColor(self.DEFAULTKEY_FILLCOLOR, fillColor) self._fillColor = fillColor self.updateView() def strokeCallback(self, sender): stroke = sender.get() setExtensionDefault(self.DEFAULTKEY_STROKE, stroke) self._stroke = stroke self.updateView() def strokeColorCallback(self, sender): strokeColor = sender.get() setExtensionDefaultColor(self.DEFAULTKEY_STROKECOLOR, strokeColor) self._strokeColor = strokeColor self.updateView() def pointsCallback(self, sender): points = sender.get() setExtensionDefault(self.DEFAULTKEY_POINTS, points) self._points = points self.updateView() def pointsColorCallback(self, sender): pointsColor = sender.get() setExtensionDefaultColor(self.DEFAULTKEY_POINTSCOLOR, pointsColor) self._pointsColor = pointsColor self.updateView() def alignmentCallback(self, sender): alignment = sender.get() setExtensionDefault(self.DEFAULTKEY_ALIGNMENT, alignment) self._alignment = alignment self.updateView() def kerningCallback(self, sender): kerning = sender.get() setExtensionDefault(self.DEFAULTKEY_KERNING, kerning) self._kerning = kerning self.updateView() def floatingCallback(self, sender): floating = sender.get() setExtensionDefault(self.DEFAULTKEY_FLOATING, floating) self._floating = floating self.setWindowLevel() def resetSettingsCallback(self, sender): drawing = True fill = True fillColor = self.FALLBACK_FILLCOLOR stroke = True strokeColor = self.FALLBACK_STROKECOLOR points = True pointsColor = self.FALLBACK_POINTSCOLOR alignment = 0 kerning = True floating = True setExtensionDefault(self.DEFAULTKEY_DRAW, drawing) setExtensionDefault(self.DEFAULTKEY_FILL, fill) setExtensionDefaultColor(self.DEFAULTKEY_FILLCOLOR, fillColor) setExtensionDefault(self.DEFAULTKEY_STROKE, stroke) setExtensionDefaultColor(self.DEFAULTKEY_STROKECOLOR, strokeColor) setExtensionDefault(self.DEFAULTKEY_POINTS, points) setExtensionDefaultColor(self.DEFAULTKEY_POINTSCOLOR, pointsColor) setExtensionDefault(self.DEFAULTKEY_ALIGNMENT, alignment) setExtensionDefault(self.DEFAULTKEY_KERNING, kerning) setExtensionDefault(self.DEFAULTKEY_FLOATING, floating) self.w.draw.set(drawing) self.w.fill.set(fill) self.w.fillColor.set(fillColor) self.w.stroke.set(stroke) self.w.strokeColor.set(strokeColor) self.w.points.set(points) self.w.pointsColor.set(strokeColor) self.w.alignment.set(alignment) self.w.kerning.set(kerning) self.w.floating.set(floating) self._drawing = drawing self._fill = fill self._fillColor = fillColor self._stroke = stroke self._strokeColor = strokeColor self._points = points self._pointsColor = strokeColor self._alignment = alignment self._kerning = kerning self._floating = floating self.updateView() def addHiddenFontsCallback(self, sender): fonts = OpenFont(None, False) if fonts is None: return if not isinstance(fonts, list): # make sure it's a list fonts = [fonts] paths = [font["font"].path for font in self.hiddenFonts] for font in fonts: if font.path in paths: continue # already open self.hiddenFonts.append({"font": font, "status": u"•"}) self.w.hiddenFontList.set(self.getHiddenFontItems()) self.updateView() def removeHiddenFontsCallback(self, sender): hiddenFontList = self.w.hiddenFontList.get() selection = self.w.hiddenFontList.getSelection() for i in reversed(selection): del self.hiddenFonts[i] item = hiddenFontList[i] self.w.hiddenFontList.remove(item) self.updateView()
class ToucheTool(): def __init__(self): NSUserDefaults.standardUserDefaults().registerDefaults_({"ToucheWindowHeight":340}) self.windowHeight = NSUserDefaults.standardUserDefaults().integerForKey_("ToucheWindowHeight") self.minWindowHeight = 340 if self.windowHeight < self.minWindowHeight: self.windowHeight = self.minWindowHeight self.closedWindowHeight = 100 self.w = FloatingWindow((180, self.windowHeight), u'Touché!', minSize=(180,340), maxSize=(250,898)) self.w.bind("resize", self.windowResized) self.isResizing = False p = 10 w = 160 # options self.w.options = Group((0, 0, 180, 220)) buttons = { "checkSelBtn": {"text": "Check selected glyphs", "callback": self.checkSel, "y": p}, } for button, data in buttons.iteritems(): setattr(self.w.options, button, Button((p, data["y"], w - 22, 22), data["text"], callback=data["callback"], sizeStyle="small")) self.w.options.zeroCheck = CheckBox((p, 35, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small") self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16), sizeStyle="small") # results self.w.results = Group((0, 220, 180, -0)) self.w.results.show(False) textBoxes = {"stats": -34, "result": -18} for box, y in textBoxes.iteritems(): setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small")) # list and preview self.w.outputList = List((0,58,-0,-40), [{"left glyph": "", "right glyph": ""}], columnDescriptions=[{"title": "left glyph", "width": 90}, {"title": "right glyph"}], showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair) self.w.outputList._setColumnAutoresizing() self._resizeWindow(False) self.w.open() # callbacks def checkAll(self, sender=None): self.check(useSelection=False) def checkSel(self, sender=None): self.check(useSelection=True) def check(self, useSelection): self._resizeWindow(enlarge=False) self.checkFont(useSelection=useSelection, excludeZeroWidth=self.w.options.zeroCheck.get()) def showPair(self, sender=None): try: index = sender.getSelection()[0] glyphs = [self.f[gName] for gName in self.touchingPairs[index]] ActiveFont = self.f._font EditViewController = ActiveFont.currentTab if EditViewController is None: tabText = "/%s/%s" %(glyphs[0].name, glyphs[1].name) ActiveFont.newTab(tabText) else: textStorage = EditViewController.graphicView() if not hasattr(textStorage, "replaceCharactersInRange_withString_"): # compatibility with API change in 2.5 textStorage = EditViewController.graphicView().textStorage() LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object) RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object) if LeftChar != 0 and RightChar != 0: selection = textStorage.selectedRange() if selection.length < 2: selection.length = 2 if selection.location > 0: selection.location -= 1 NewString = "" if LeftChar < 0xffff and RightChar < 0xffff: NewString = u"%s%s" % (unichr(LeftChar), unichr(RightChar)) else: print "Upper plane codes are not supported yet" textStorage.replaceCharactersInRange_withString_(selection, NewString) selection.length = 0 selection.location += 1 textStorage.setSelectedRange_(selection) #self.w.preview.set(glyphs) except IndexError: pass # checking def _hasSufficientWidth(self, g): # to ignore combining accents and the like if self.excludeZeroWidth: # also skips 1-unit wide glyphs which many use instead of 0 if g.width < 2 or g._object.subCategory == "Nonspacing": return False return True def _trimGlyphList(self, glyphList): newGlyphList = [] for g in glyphList: if g.box is not None and self._hasSufficientWidth(g): newGlyphList.append(g) return newGlyphList def windowResized(self, window): posSize = self.w.getPosSize() Height = posSize[3] if Height > self.closedWindowHeight and self.isResizing is False: print "set new Height", Height NSUserDefaults.standardUserDefaults().setInteger_forKey_(Height, "ToucheWindowHeight") self.windowHeight = Height def _resizeWindow(self, enlarge=True): posSize = self.w.getPosSize() if enlarge: self.w.results.show(True) self.w.outputList.show(True) targetHeight = self.windowHeight if targetHeight < 340: targetHeight = 340 else: self.w.results.show(False) self.w.outputList.show(False) targetHeight = self.closedWindowHeight self.isResizing = True self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight)) self.isResizing = False # ok let's do this def checkFont(self, useSelection=False, excludeZeroWidth=True): f = CurrentFont() if f is not None: # initialize things self.w.options.progress.start() time0 = time.time() self.excludeZeroWidth = excludeZeroWidth self.f = f glyphNames = f.selection if useSelection else f.keys() glyphList = [f[x] for x in glyphNames] glyphList = self._trimGlyphList(glyphList) self.touchingPairs = Touche(f).findTouchingPairs(glyphList) # display output self.w.results.stats.set("%d glyphs checked" % len(glyphList)) self.w.results.result.set("%d touching pairs found" % len(self.touchingPairs)) self.w.results.show(True) outputList = [{"left glyph": g1, "right glyph": g2} for (g1, g2) in self.touchingPairs] self.w.outputList.set(outputList) if len(self.touchingPairs) > 0: self.w.outputList.setSelection([0]) #self.w.preview.setFont(f) self.w.options.progress.stop() self._resizeWindow(enlarge=True) time1 = time.time() print u'Touché: finished checking %d glyphs in %.2f seconds' % (len(glyphList), time1-time0) else: Message(u'Touché: Can’t find a font to check')
class MoveGlyphWindow: '''Move the current glyph using sliders.''' def __init__(self, glyph): # if glyph is None, show a message if glyph is None: Message('no glyph selected', title='moveTool', informativeText='Please select one glyph first!') return # store the glyph and initial move values as object attributes self.glyph = glyph self.moveXTRA = 0 # create a floating window self.w = FloatingWindow((200, 74), "move %s" % self.glyph.name) # add a slider for moving in the x axis self.w.sliderXTRA = Slider((10, 10, -10, 22), value=0, maxValue=200, minValue=0, callback=self.adjustXTRA) # self.w.sliderXOPQ = Slider( # (10, 10, -10, 22), # value=0, maxValue=200, minValue=-200, # callback=self.adjust) # open the window self.w.open() def adjustXTRA(self, sender): # crossbartop='CROSSBARTOP' # crossbarbottom='CROSSBARBOTTOM' # rightsideinsidelabel='RIGHTSIDEINSIDE' # rightsideoutsidelabel='RIGHTSIDEOUTSIDE' # leftsideinsidelabel='LEFTSIDEINSIDE' # leftsideoutsidelabel='LEFTSIDEOUTSIDE' rightstemlabel = 'RIGHTSTEM' # get the current x and y values from the sliders valueXTRA = self.w.sliderXTRA.get() # calculate actual move distance x = self.moveXTRA - valueXTRA # move the glyph for self.c in self.glyph: for self.p in self.c.points: if rightstemlabel in self.p.labels: self.glyph.prepareUndo() self.p.move((-x, 0)) self.glyph.rightMargin self.glyph.changed() self.glyph.performUndo() # self.glyph.moveBy((x, 0)) # update saved values self.moveXTRA = valueXTRA # print the current move distance print(self.moveXTRA)
class DemoWindowTool: def __init__(self): self.glyph = None # Typical RoboFont function self.updating = False pad = 10 leading = 32 y = pad w = 300 h = 400 buttonWidth = 100 buttonHeight = 48 self.w = FloatingWindow((100, 100, w, h), 'DemoWindowTool') self.w.doDraw = CheckBox((pad, y, 100, 24), 'Draw', callback=self.doDrawCallback) y += leading self.w.mySlider = Slider((pad, y, -pad, 24), minValue=0, maxValue=2000, value=0, tickMarkCount=10, callback=self.mySliderCallback) y = self.w.saveFont = Button((-buttonWidth - pad, -buttonHeight - pad, buttonWidth, buttonHeight), 'Save', callback=self.saveFontCallback) addObserver(self, "currentGlyphChanged", "currentGlyphChanged") # Subscribe to application event addObserver(self, 'draw', 'draw') addObserver(self, 'drawBackground', 'drawBackground') self.w.bind('close', self.windowCloseCallback) self.w.open() def windowCloseCallback(self, sender): removeObserver( self, "currentGlyphChanged") # Unsubscribing from application event removeObserver(self, 'draw') removeObserver(self, 'drawBackground') print('removeObserver currentGlyphChanged') def mySliderCallback(self, sender): if self.glyph is not None: self.glyph.width = sender.get() # same as self.w.mySlider self.updating = True self.glyph.changed() UpdateCurrentGlyphView() self.updating = False def doDrawCallback(self, sender): UpdateCurrentGlyphView() def saveFontCallback(self, sender): print('Saving') def draw(self, info): #print('Drawing' ,info['glyph']) if not self.updating: glyph = info['glyph'] self.w.mySlider.set(glyph.width) if self.w.doDraw.get(): fill(1, 0, 0, 0.5) rect(0, 0, glyph.width, 50) def drawBackground(self, info): #print('Drawing' ,info['glyph']) if not self.updating: glyph = info['glyph'] self.w.mySlider.set(glyph.width) if self.w.doDraw.get(): fill(0, 0, 1, 0.5) rect(0, 0, glyph.width, 50) #def glyphChanged(self, info): # print('Glyph changed', info['glyph']) def currentGlyphChanged(self, info): #if self.glyph is not None: # self.glyph.removeObserver(self, 'Glyph.Change') self.glyph = info['glyph']
class VariableController(object): def __init__(self, attributes, callback, document=None): self._callback = callback self._attributes = None self.w = FloatingWindow((250, 50)) self.buildUI(attributes) self.w.open() if document: self.w.assignToDocument(document) self.w.setTitle("Variables") def buildUI(self, attributes): if self._attributes == attributes: return self._attributes = attributes if hasattr(self.w, "ui"): del self.w.ui self.w.ui = ui = vanilla.Group((0, 0, -0, -0)) y = 10 labelSize = 100 gutter = 5 for attribute in self._attributes: uiElement = attribute["ui"] name = attribute["name"] args = dict(attribute.get("args", {})) height = 19 # adjust the height if a radioGroup is vertical if uiElement == "RadioGroup": if args.get("isVertical", True): height = height * len(args.get("titles", [""])) # create a label for every ui element except a checkbox if uiElement not in ("CheckBox", "Button"): # create the label view label = vanilla.TextBox((0, y + 2, labelSize - gutter, height), "%s:" % name, alignment="right", sizeStyle="small") # set the label view setattr(ui, "%sLabel" % name, label) else: args["title"] = name # check the provided args and add required keys if uiElement == "ColorWell": # a color well needs a color to be set # no size style if "color" not in args: args["color"] = AppKit.NSColor.blackColor() elif uiElement == "TextEditor": # different control height # no size style height = attribute.get("height", 75) else: # all other get a size style args["sizeStyle"] = "small" # create the control view attr = getattr(vanilla, uiElement)((labelSize, y, -10, height), callback=self.changed, **args) # set the control view setattr(ui, name, attr) y += height + 6 # resize the window according the provided ui elements self.w.resize(250, y) def changed(self, sender): self.documentWindowToFront() if self._callback: self._callback() def get(self): data = {} for attribute in self._attributes: if attribute["ui"] in ("Button", ): continue name = attribute["name"] data[name] = getattr(self.w.ui, name).get() return data def show(self): self.w.show() def documentWindowToFront(self, sender=None): self.w.makeKey()
class TestInstallBackup(object): def __init__(self): self.w = FloatingWindow( (120, 140), minSize=(100, 40), textured=True, ) self.w.ti_checkbox = CheckBox((10, 10, -10, 20), 'Test Install', value=True) self.w.otf_checkbox = CheckBox((10, 30, -10, 20), 'OTF Backup', value=True) self.w.button = SquareButton((10, 60, -10, -10), 'GO', callback=self.button_callback) self.w.open() def build_otf(self, font): # this is the full path to the UFO ufo_path = font.path # the time string added to the file name time_string = time.strftime('%Y_%m_%d at %H-%M-%S') # the path to the final OTF otf_path = ufo_path[:-4] + ' ' + time_string + '.otf' # do not overwrite existing file if os.path.exists(otf_path): print('This file already exists and will not be overwritten.') else: # generate the OTF font.generate( otf_path, 'otf', # checkOutlines removes overlaps checkOutlines=True, ) # if the output path does not exist, # the font could not be generated. if not os.path.exists(otf_path): print('uh - oh. There was a problem') print(otf_path) def button_callback(self, sender): # Collect the state of the checkboxes test_install_state = self.w.ti_checkbox.get() build_otf_state = self.w.otf_checkbox.get() # Fetch the font and see if there even is one font = CurrentFont() if font: # let's see if the checkbox to build a UFO is checked. if build_otf_state: self.build_otf(font) # if the test install checkbox is checked if test_install_state: font.testInstall() else: print('No UFO open.')
class DebuggerWindow(BaseWindowController): def __init__(self): self.w = FloatingWindow((123, 44), 'debug!') ms = MarginSelector() self.w.open() self.w.bind("close", ms.destroy)
class WurstSchreiber(object): def __init__(self): self.draw = False self.swap = True self.radius = getExtensionDefault( "%s.%s" % (WurstSchreiberDefaultKey, "radius"), 60) color = NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 0, 0, .5) colorValue = getExtensionDefaultColor( "%s.%s" % (WurstSchreiberDefaultKey, "color"), color) self.w = FloatingWindow((150, 170), "WurstSchreiber") x = 15 y = 15 self.w.preview = CheckBox( (x, y, -x, 20), "Preview", callback=self.previewChanged, value=True) y+=30 self.w.slider = SliderGroup( (x, y, -x, 22), 0, 100, self.radius, callback=self.sliderChanged) y+=35 self.w.color = ColorWell( (x, y, -x, 40), callback=self.colorChanged, color=colorValue) y+=55 self.w.button = Button( (x, y, -x, 20), "Trace!", callback=self.traceButton) addObserver(self, "drawWurst", "drawBackground") self.w.bind("close", self.closing) self.w.open() def closing(self, sender): removeObserver(self, "drawBackground") def previewChanged(self, sender): UpdateCurrentGlyphView() def sliderChanged(self, sender): self.radius = int(sender.get()) setExtensionDefault( "%s.%s" % (WurstSchreiberDefaultKey, "radius"), self.radius) UpdateCurrentGlyphView() def colorChanged(self, sender): setExtensionDefaultColor( "%s.%s" % (WurstSchreiberDefaultKey, "color"), sender.get()) UpdateCurrentGlyphView() def getColor(self): color = self.w.color.get() return color.getRed_green_blue_alpha_(None, None, None, None) def traceButton(self, sender): if self.w.preview.get(): self.draw = True UpdateCurrentGlyphView() def drawWurst(self, sender): if self.w.preview.get(): radius = self.radius draw = self.draw r,g,b,a = self.getColor() fill(r,g,b,a) glyph = CurrentGlyph() pen = WurstPen(None, radius, draw) glyph.draw(pen) if self.draw: glyph.prepareUndo("WurstTrace") if self.swap: glyph.getLayer("background").clear() glyph.swapToLayer("background") glyph.appendGlyph(pen.glyphcopy) self.draw = False self.w.preview.set(False) glyph.performUndo() glyph.update()
class RedArrowErrorFilter(): def __init__(self): self.errorList = [ 'Extremum', 'Mixed cubic and quadratic segments', 'Fractional Coordinates', 'Fractional transformation', 'Incorrect smooth connection', 'Empty segment', 'Vector on closepath', 'Collinear vectors', 'Semi-horizontal vector', 'Semi-vertical vector', 'Zero handle', ] self.errorList.insert(0, "select Red Arrow Error") self.heightOfTool = 360 self.widthOfTool = 200 self.w = FloatingWindow((self.widthOfTool, self.heightOfTool), "Red Arrow Error Filter") ### FloatingWindow #self.w.text = TextBox((10, 5, -10, 16), "...", sizeStyle='regular') self.w.select_test = PopUpButton((10, 10, -10, 16), self.errorList, sizeStyle='regular', callback=self.select_test) self.w.extremumToleranceText = TextBox((10, 35, -25, 18), "Extremum Tolerance", sizeStyle='small') self.w.extremumToleranceInput = EditText((160, 35, -10, 18), "2", sizeStyle='small') self.w.smooth_connection_max_distance_Text = TextBox((10, 57, -25, 18), "Smooth Connect max_dist", sizeStyle='small') self.w.smooth_connection_max_distance_Input = EditText((160, 55, -10, 18), "4", sizeStyle='small') self.w.collinear_vectors_max_distance_Text = TextBox((10, 77, -25, 18), "Collinear Vectors max_dist", sizeStyle='small') self.w.collinear_vectors_max_distance_Input = EditText((160, 75, -10, 18), "2", sizeStyle='small') self.w.report = EditText((10, 100, -10, -10), "...", sizeStyle='regular') self.w.report.enable(False) self.count = [] self.report = [] self.w.open() def select_test(self, sender): # options try: extremumTolerance = int(self.w.extremumToleranceInput.get()) except ValueError: self.w.extremumToleranceInput.set(2) extremumTolerance = 2 try: smooth_connection_max_distance_Input = int(self.w.smooth_connection_max_distance_Input.get()) except ValueError: self.w.smooth_connection_max_distance_Input.set(4) smooth_connection_max_distance_Input = 4 try: collinear_vectors_max_distance_Input = int(self.w.collinear_vectors_max_distance_Input.get()) except ValueError: self.w.collinear_vectors_max_distance_Input.set(2) collinear_vectors_max_distance_Input = 2 options = { "extremum_calculate_badness": True, "extremum_ignore_badness_below": extremumTolerance, "smooth_connection_max_distance": smooth_connection_max_distance_Input, "fractional_ignore_point_zero": True, "collinear_vectors_max_distance": collinear_vectors_max_distance_Input, } # a random number to change the mark color #randomNumber = random.random() # check if a font is open or not if CurrentFont(): font = CurrentFont() glyphnames = CurrentFont().keys() else: self.w.report.set("Open a Font") return # start the outline test selection = [] otp = OutlineTestPen(CurrentFont(), options) for n in glyphnames: otp.errors = [] g = font[n] g.drawPoints(otp) if otp.errors: for e in otp.errors: # convert error object to string errorString = str(e).split(" ")[0] # if the first part of error string = the first part of selection from PopUp if errorString == str(sender.getItems()[sender.get()]).split(" ")[0]: #g.mark = (1, randomNumber, 0.6, 1) selection.append(g.name) #print(e) font.selection = selection # output of glyphs with errors in UI result = dict((x,selection.count(x)) for x in set(selection)) formattedResult = ' '.join("%s=%r" % (key,val) for (key,val) in sorted(result.items())) self.w.report.set(formattedResult)
class AscenderDescenderCalculator(BaseWindowController): lowerExtreme = None higherExtreme = None is_hhea = False is_vhea = False is_osTwo = False is_usWin = False def __init__(self): super(AscenderDescenderCalculator, self).__init__() self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT), PLUGIN_TITLE) jumpingY = MARGIN_VER self.w.calcButton = SquareButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Measure Extremes', callback=self.calcButtonCallback) jumpingY += vanillaControlsSize[ 'ButtonRegularHeight'] * 1.5 + MARGIN_ROW self.w.topCaption = TextBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['TextBoxRegularHeight']), 'Top: None') jumpingY += vanillaControlsSize['TextBoxRegularHeight'] + MARGIN_ROW self.w.btmCaption = TextBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['TextBoxRegularHeight']), 'Bottom: None') jumpingY += vanillaControlsSize['TextBoxRegularHeight'] self.w.separationLine = HorizontalLine( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['TextBoxRegularHeight'])) jumpingY += vanillaControlsSize['TextBoxRegularHeight'] + MARGIN_ROW self.w.check_hhea = CheckBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight']), 'hhea table', value=self.is_hhea, callback=self.check_hheaCallback) jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW self.w.check_vhea = CheckBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight']), 'vhea table', value=self.is_vhea, callback=self.check_vheaCallback) jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW self.w.check_osTwo = CheckBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight']), 'OS/2 table', value=self.is_osTwo, callback=self.check_osTwoCallback) jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW self.w.check_usWin = CheckBox( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight']), 'usWin table', value=self.is_usWin, callback=self.check_usWinCallback) jumpingY += vanillaControlsSize['CheckBoxRegularHeight'] + MARGIN_ROW self.w.writeButton = SquareButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Write values into fonts', callback=self.writeButtonCallback) jumpingY += vanillaControlsSize[ 'ButtonRegularHeight'] * 1.5 + MARGIN_VER * 1.5 self.w.resize(PLUGIN_WIDTH, jumpingY) self.setUpBaseWindowBehavior() self.w.open() def calcButtonCallback(self, sender): self.lowerExtreme, self.higherExtreme = findFamilyExtremes(AllFonts()) self.updateCaptions() def updateCaptions(self): self.w.topCaption.set( 'Highest: {ex.y} ({ex.glyphName}, {ex.styleName})'.format( ex=self.higherExtreme)) self.w.btmCaption.set( 'Lowest: {ex.y} ({ex.glyphName}, {ex.styleName})'.format( ex=self.lowerExtreme)) def check_hheaCallback(self, sender): self.is_hhea = bool(sender.get()) def check_vheaCallback(self, sender): self.is_vhea = bool(sender.get()) def check_osTwoCallback(self, sender): self.is_osTwo = bool(sender.get()) def check_usWinCallback(self, sender): self.is_usWin = bool(sender.get()) def writeButtonCallback(self, sender): if self.lowerExtreme is not None and self.higherExtreme is not None: for eachFont in AllFonts(): if self.is_hhea is True: eachFont.info.openTypeHheaAscender = self.higherExtreme.y eachFont.info.openTypeHheaDescender = self.lowerExtreme.y if self.is_vhea is True: eachFont.info.openTypeVheaVertTypoAscender = self.higherExtreme.y eachFont.info.openTypeVheaVertTypoDescender = self.lowerExtreme.y if self.is_osTwo is True: eachFont.info.openTypeOS2TypoAscender = self.higherExtreme.y eachFont.info.openTypeOS2TypoDescender = self.lowerExtreme.y if self.is_usWin is True: eachFont.info.openTypeOS2WinAscent = self.higherExtreme.y eachFont.info.openTypeOS2WinDescent = self.lowerExtreme.y else: message('Calc Extremes first!')
class AdjustAnchors(BaseWindowController): def __init__(self): self.font = CurrentFont() self.glyph = CurrentGlyph() self.upm = self.font.info.unitsPerEm self.glyphPreviewCacheDict = {} # key: glyph name -- value: list containing assembled glyphs self.anchorsOnMarksDict = {} # key: anchor name -- value: list of mark glyph names self.anchorsOnBasesDict = {} # key: anchor name -- value: list of base glyph names self.marksDict = {} # key: mark glyph name -- value: anchor name (NOTE: It's expected that each mark glyph only has one type of anchor) self.fillAnchorsAndMarksDicts() self.glyphNamesList = [] # list of glyph names that will be displayed in the UI list self.selectedGlyphNamesList = [] # list of glyph names selected in the UI list self.extraGlyphsList = [] # list of the glyph objects that should be inserted before and after the accented glyphs self.Blue, self.Alpha = 1, 0.6 self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") addObserver(self, "_fontWillClose", "fontWillClose") addObserver(self, "_currentFontChanged", "fontResignCurrent") addObserver(self, "_currentGlyphChanged", "currentGlyphChanged") addObserver(self, "_drawFill", "draw") addObserver(self, "_drawFill", "drawInactive") addObserver(self, "_previewFill", "drawPreview") addObserver(self, "_drawGlyphs", "draw") # observer for the draw event addObserver(self, "_drawGlyphs", "drawInactive") # draw the glyphs when the glyph window is not in focus addObserver(self, "_drawGlyphs", "drawPreview") integerNumFormatter = NSNumberFormatter.alloc().init() integerNumFormatter.setAllowsFloats_(False) integerNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init() intPosMinZeroNumFormatter.setAllowsFloats_(False) intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0)) intPosMinOneNumFormatter = NSNumberFormatter.alloc().init() intPosMinOneNumFormatter.setAllowsFloats_(False) intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1)) self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize")) if not self.textSize: self.textSize = 150 self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight")) if not self.lineHeight: self.lineHeight = 200 self.extraSidebearings = getExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings")) if not self.extraSidebearings: self.extraSidebearings = [0, 0] self.extraGlyphs = getExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs")) if not self.extraGlyphs: self.extraGlyphs = '' posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize")) if not posSize: posSize = (100, 100, 1200, 400) self.calibrateMode = getExtensionDefault("%s.%s" % (extensionKey, "calibrateMode")) if not self.calibrateMode: self.calibrateMode = False calibrateModeStrings = getExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings")) if not calibrateModeStrings: calibrateModeStrings = { 'group1.baseInput': 'dotlessi o s', 'group1.markInput': 'dieresis circumflex macron breve caron', 'group2.baseInput': 'I O S', 'group2.markInput': 'dieresis.cap circumflex.cap macron.cap breve.cap caron.cap', 'group3.baseInput': 'I.sc O.sc S.sc', 'group3.markInput': 'dieresis circumflex macron breve caron', 'group4.baseInput': '', 'group4.markInput': '', } # -- Window -- self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400)) self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback) if roboFontVersion < '1.7': self.w.fontList.getNSTableView().sizeToFit() # use the full width of the column self.w.fontList.show(not self.calibrateMode) self.w.lineView = MultiLineView((210, 10, -10, -41), pointSize = self.textSize, lineHeight = self.lineHeight, displayOptions={"Beam" : False, "displayMode" : "Multi Line"} ) # -- Calibration Mode -- baseLabel = "Bases" markLabel = "Marks" width, height = 190, 140 self.cm = Group((0, 0, 0, 0)) # --- self.cm.group1 = Group((5, height*0, width, height-10)) self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group1.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group1.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group2 = Group((5, height*1, width, height-10)) self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group2.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group2.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group3 = Group((5, height*2, width, height-10)) self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group3.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group3.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group4 = Group((5, height*3, width, height-10)) self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group4.baseInput = EditText((0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group4.markInput = EditText((0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False) # --- view = DefconAppKitTopAnchoredNSView.alloc().init() view.addSubview_(self.cm.getNSView()) view.setFrame_(((0, 0), (width+10, height*4-23))) self.cm.setPosSize((0, 0, width+10, height*4-22)) self.w.scrollView = ScrollView((5, 10, width+10, -41), view, drawsBackground=False, hasHorizontalScroller=False) self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder) self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) # NSScrollElasticityNone self.w.scrollView.show(self.calibrateMode) # -- Footer -- self.w.calibrateModeCheck = CheckBox((10, -32, 200, -10), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode) self.w.textSizeLabel = TextBox((210, -30, 100, -10), "Text Size") self.w.textSize = EditText((270, -32, 35, -10), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter) self.w.lineHeightLabel = TextBox((320, -30, 100, -10), "Line Height") self.w.lineHeight = EditText((395, -32, 35, -10), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter) self.w.extraSidebearingsLabel = TextBox((446, -30, 180, -10), "Extra Sidebearings") self.w.extraSidebearingsChar = TextBox((602, -30, 20, -10), "&") self.w.extraSidebearingLeft = EditText((567, -32, 35, -10), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.extraSidebearingRight = EditText((614, -32, 35, -10), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.extraGlyphsLabel = TextBox((665, -30, 180, -10), "Extra Glyphs") self.w.extraGlyphs = EditText((749, -32, -10, -10), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False) # trigger the initial state and contents of the window self.extraGlyphsCallback() # this will call self.updateExtensionWindow() self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def calibrateModeCallback(self, sender): self.calibrateMode = not self.calibrateMode self.w.fontList.show(not sender.get()) self.w.scrollView.show(self.calibrateMode) self.updateExtensionWindow() def textSizeCallback(self, sender): try: # in case the user submits an empty field self.textSize = int(sender.get()) except: # reset to the previous value NSBeep() self.sender.set(self.textSize) self.w.lineView.setPointSize(self.textSize) def lineHeightCallback(self, sender): try: self.lineHeight = int(sender.get()) except: NSBeep() self.sender.set(self.lineHeight) self.w.lineView.setLineHeight(self.lineHeight) def extraSidebearingsCallback(self, sender): left = self.w.extraSidebearingLeft right = self.w.extraSidebearingRight try: self.extraSidebearings = [int(left.get()), int(right.get())] except: NSBeep() left.set(self.extraSidebearings[0]) right.set(self.extraSidebearings[1]) self.extraGlyphsCallback() # this will call self.updateExtensionWindow() def extraGlyphsCallback(self, *sender): del self.extraGlyphsList[:] # empty the list self.extraGlyphs = self.w.extraGlyphs.get() glyphNamesList = self.extraGlyphs.split() for gName in glyphNamesList: try: extraGlyph = self.font[gName] # must create a new glyph in order to be able to increase the sidebearings without modifying the font newGlyph = RGlyph() newGlyph.setParent(self.font) # must use deepAppend because the extra glyph may have components (which will cause problems to the MultiLineView) newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph) newGlyph.width = extraGlyph.width except RoboFontError: continue newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] self.extraGlyphsList.append(newGlyph) self.glyphPreviewCacheDict.clear() self.updateExtensionWindow() def windowClose(self, sender): self.font.naked().removeObserver(self, "Font.Changed") removeObserver(self, "fontWillClose") removeObserver(self, "fontResignCurrent") removeObserver(self, "currentGlyphChanged") removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") self.saveExtensionDefaults() def getCalibrateModeStrings(self): calibrateModeStringsDict = {} for i in range(1,5): group = getattr(self.cm, "group%d" % i) calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get() calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get() return calibrateModeStringsDict def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize()) setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize) setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight) setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings) setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs) setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode) setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings()) def _previewFill(self, info): self.Blue, self.Alpha = 0, 1 def _drawFill(self, info): self.Blue, self.Alpha = 1, 0.6 def _fontWillClose(self, info): """ Close the window when the last font is closed """ if len(AllFonts()) < 2: self.windowClose(self) self.w.close() def _currentFontChanged(self, info): self.font.naked().removeObserver(self, "Font.Changed") self.font = CurrentFont() self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def _currentGlyphChanged(self, info): self.updateExtensionWindow() def fontWasModified(self, info): OutputWindow().clear() self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def deepAppendGlyph(self, glyph, gToAppend, offset=(0,0)): if not gToAppend.components: glyph.appendGlyph(gToAppend, offset) else: for component in gToAppend.components: compGlyph = self.font[component.baseGlyph].copy() # handle component transformations componentTransformation = component.transformation # when undoing a paste anchor or a delete anchor action, RoboFont returns component.transformation as a list instead of a tuple if type(componentTransformation) is list: componentTransformation = tuple(componentTransformation) if componentTransformation != (1, 0, 0, 1, 0, 0): # if component is skewed and/or is shifted matrix = componentTransformation[0:4] if matrix != (1, 0, 0, 1): # if component is skewed transformObj = Identity.transform(matrix + (0, 0)) # ignore the original component's shifting values compGlyph.transform(transformObj) glyph.appendGlyph(compGlyph, map(sum, zip(component.offset, offset))) # add the two tuples of offset for contour in gToAppend: glyph.appendContour(contour, offset) # if the assembled glyph still has components, recursively remove and replace them 1-by-1 by the glyphs they reference if glyph.components: nestedComponent = glyph.components[-1] # start from the end glyph.removeComponent(nestedComponent) glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset) return glyph def updateCalibrateMode(self, *sender): glyphsList = [] newLine = self.w.lineView.createNewLineGlyph() # cycle thru the UI Groups and collect the strings for i in range(1,5): group = getattr(self.cm, "group%d" % i) baseGlyphsNamesList = group.baseInput.get().split() markGlyphsNamesList = group.markInput.get().split() # iterate thru the base+mark combinations for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList): newGlyph = RGlyph() newGlyph.setParent(self.font) # skip invalid glyph names try: baseGlyph = self.font[gBaseName] markGlyph = self.font[gMarkName] except RoboFontError: continue # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph)) # set the advanced width dfltSidebearings = self.upm * .05 # 5% of the UPM newGlyph.leftMargin = dfltSidebearings + self.extraSidebearings[0] newGlyph.rightMargin = dfltSidebearings + self.extraSidebearings[1] # append the assembled glyph to the list glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) # add line break, if both input fields have content if baseGlyphsNamesList and markGlyphsNamesList: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newLine) # update the contents of the MultiLineView self.w.lineView.set(glyphsList) def updateExtensionWindow(self): if self.calibrateMode: self.updateCalibrateMode() return if CurrentGlyph() is not None: # NOTE: CurrentGlyph() will return zero (its length), so "is not None" is necessary self.glyph = CurrentGlyph() self.glyphNamesList = self.makeGlyphNamesList(self.glyph) self.updateListView() currentGlyphName = self.glyph.name # base glyph + accent combinations preview # first check if there's a cached glyph if currentGlyphName in self.glyphPreviewCacheDict: self.w.lineView.set(self.glyphPreviewCacheDict[currentGlyphName]) # assemble the glyphs else: glyphsList = [] for glyphNameInUIList in self.glyphNamesList: newGlyph = RGlyph() newGlyph.setParent(self.font) # the glyph in the UI list is a mark if glyphNameInUIList in self.marksDict: markGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph)) # set the advanced width if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width newGlyph.leftMargin = self.upm * .05 # 5% of the UPM newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = self.glyph.width # the glyph in the UI list is a base else: baseGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph)) # set the advanced width if self.glyph.width < 10: # combining marks or other glyphs with a small advanced width newGlyph.leftMargin = self.upm * .05 newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = baseGlyph.width # pad the new glyph if it has too much overhang if newGlyph.leftMargin < self.upm * .15: newGlyph.leftMargin = self.upm * .05 if newGlyph.rightMargin < self.upm * .15: newGlyph.rightMargin = self.upm * .05 # add extra sidebearings newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] # one last check for making sure the new glyph can be displayed if not newGlyph.components: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) else: print "Combination with mark glyph %s can't be previewed because it contains component %s." % (glyphNameInUIList, newGlyph.components[0].baseGlyph) glyphsList.extend(self.extraGlyphsList) self.w.lineView.set(glyphsList) # add to the cache self.glyphPreviewCacheDict[currentGlyphName] = glyphsList else: self.w.lineView.set([]) def listSelectionCallback(self, sender): selectedGlyphNamesList = [] for index in sender.getSelection(): selectedGlyphNamesList.append(self.glyphNamesList[index]) self.selectedGlyphNamesList = selectedGlyphNamesList self.updateGlyphView() def updateGlyphView(self): # update the current glyph view UpdateCurrentGlyphView() def fillAnchorsAndMarksDicts(self): # reset all the dicts self.glyphPreviewCacheDict.clear() self.anchorsOnMarksDict.clear() self.anchorsOnBasesDict.clear() self.marksDict.clear() markGlyphsWithMoreThanOneAnchorTypeList = [] for glyphName in self.font.glyphOrder: glyphAnchorsList = self.font[glyphName].anchors for anchor in glyphAnchorsList: if anchor.name[0] == '_': anchorName = anchor.name[1:] # add to AnchorsOnMarks dictionary if anchorName not in self.anchorsOnMarksDict: self.anchorsOnMarksDict[anchorName] = [glyphName] else: tempList = self.anchorsOnMarksDict[anchorName] tempList.append(glyphName) self.anchorsOnMarksDict[anchorName] = tempList # add to Marks dictionary if glyphName not in self.marksDict: self.marksDict[glyphName] = anchorName else: if glyphName not in markGlyphsWithMoreThanOneAnchorTypeList: markGlyphsWithMoreThanOneAnchorTypeList.append(glyphName) else: anchorName = anchor.name # add to AnchorsOnBases dictionary if anchorName not in self.anchorsOnBasesDict: self.anchorsOnBasesDict[anchorName] = [glyphName] else: tempList = self.anchorsOnBasesDict[anchorName] tempList.append(glyphName) self.anchorsOnBasesDict[anchorName] = tempList if markGlyphsWithMoreThanOneAnchorTypeList: for glyphName in markGlyphsWithMoreThanOneAnchorTypeList: print "ERROR: Glyph %s has more than one type of anchor." % glyphName def makeGlyphNamesList(self, glyph): glyphNamesList = [] markGlyphIsAbleToBeBase = False if glyph is not None: # NOTE: "if glyph" will return zero (its length), so "is not None" is necessary # assemble the list for the UI list for anchor in glyph.anchors: anchorName = anchor.name if anchorName in self.anchorsOnMarksDict: glyphNamesList.extend(self.anchorsOnMarksDict[anchorName]) elif anchorName[1:] in self.anchorsOnBasesDict: # skips the leading underscore glyphNamesList.extend(self.anchorsOnBasesDict[anchorName[1:]]) # for mark glyphs, test if they're able to get other mark glyphs attached to them # this will (correctly) prevent the UI list from including glyph names that cannot be displayed with the current glyph if glyph.name in self.marksDict: for anchor in glyph.anchors: if anchor.name[0] != '_': # the current mark glyph has anchors that allow it to be a base for other marks markGlyphIsAbleToBeBase = True break # remove marks from the glyph list if the current mark glyph can't work as a base if not markGlyphIsAbleToBeBase: for glyphName in glyphNamesList[::-1]: # iterate from the end of the list if glyphName in self.marksDict: glyphNamesList.remove(glyphName) return glyphNamesList def updateListView(self): self.w.fontList.set(self.glyphNamesList) def getAnchorOffsets(self, canvasGlyph, glyphToDraw): # the current glyph is a mark if canvasGlyph.name in self.marksDict: # glyphToDraw is also a mark (mark-to-mark case) if glyphToDraw.name in self.marksDict: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] != '_': anchorName = anchor.name markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == '_'+ anchorName: baseAnchor = anchor break except UnboundLocalError: continue # glyphToDraw is not a mark else: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] == '_': anchorName = anchor.name[1:] markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == anchorName: baseAnchor = anchor break except UnboundLocalError: continue try: offsetX = markAnchor.x - baseAnchor.x offsetY = markAnchor.y - baseAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 # the current glyph is a base else: try: anchorName = self.marksDict[glyphToDraw.name] except KeyError: anchorName = None if anchorName: # pick the (base glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name == anchorName: baseAnchor = anchor break # pick the (mark glyph) anchor to draw on for anchor in glyphToDraw.anchors: if anchor.name == '_'+ anchorName: markAnchor = anchor break try: offsetX = baseAnchor.x - markAnchor.x offsetY = baseAnchor.y - markAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 return (offsetX, offsetY) def _drawGlyphs(self, info): """ draw stuff in the glyph window view """ translateBefore = (0, 0) for glyphName in self.selectedGlyphNamesList: glyphToDraw = self.font[glyphName] # determine the offset of the anchors offset = self.getAnchorOffsets(self.glyph, glyphToDraw) # set the offset of the drawing translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1]) # record the shift amounts (these are needed for resetting the drawing position when more than one mark is selected on the list) translateBefore = offset # set the fill & stroke fill(0, 0, self.Blue, self.Alpha) strokeWidth(None) # draw it mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font) glyphToDraw.draw(mojoPen) mojoPen.draw()
class GenerateWithOrder(object): """A simple extension to generate font with order from a FL Encoding file """ def __init__(self): self.glyphOrder = [] self.orderFileName = '' self.formats = ['otf', 'ttf', 'pfa'] self.format = 'otf' self.decompose = True self.overlap = True self.autohint = True self.release = True self.w = FloatingWindow((200,270), "Generate", minSize=(200,270),) self.w.getEncoding = Button((10, 10, 180, 20), 'Get .enc file', callback=self.getEncodingCallback) self.w.viewEncoding = Button((10, 75, 180, 20), 'View encoding', callback=self.viewEncodingCallback) self.w.line = HorizontalLine((12, 103, -12, 1)) self.w.formatLabel = TextBox((15, 117, 60, 20), "Format") self.w.formatChoice = PopUpButton((70, 115, 80, 20), self.formats, callback=self.formatCallback) self.w.decomposeCheck = CheckBox((20, 141, -10, 20), "Decompose", callback=self.decomposeCallback, value=self.decompose) self.w.overlapCheck = CheckBox((20, 161, -10, 20), "Remove Overlap", callback=self.overlapCallback, value=self.overlap) self.w.autohintCheck = CheckBox((20, 181, -10, 20), "Autohint", callback=self.autohintCallback, value=self.autohint) self.w.releaseCheck = CheckBox((20, 201, -10, 20), "Release Mode", callback=self.releaseCallback, value=self.release) self.w.generate = Button((10, 232, 180, 20), 'Generate Font', callback=self.generateCallback) self.w.decomposeCheck.enable(False) self.w.viewEncoding.enable(False) self.w.formatChoice.enable(False) self.w.overlapCheck.enable(False) self.w.autohintCheck.enable(False) self.w.releaseCheck.enable(False) self.w.generate.enable(False) self.d = Drawer((170, 400), self.w, preferredEdge="right") self.d.text = TextEditor((10, 10, -10, -10), readOnly=True) self.d.open() self.d.toggle() self.w.open() def process_enc(self, p): order = [] f = open(p) for line in f: if line.startswith(('#', '%')): pass else: l = line.split() if len(l[0]) != 0: for i in l: if not i.startswith(('#', '%', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) and len(i) != 0: order.append(i) f.close() self.glyphOrder = order def getEncodingCallback(self, sender): getFile(parentWindow=self.w, fileTypes=['enc', 'Enc'], resultCallback=self.processEncodingCallback) def viewEncodingCallback(self, sender): if not self.d.isOpen(): t = "\n".join(self.glyphOrder) self.d.text.set(t) self.d.toggle() def processEncodingCallback(self, sender): if sender[0] is not None: fn = os.path.split(sender[0])[1] self.process_enc(sender[0]) self.w.encodingTitle = TextBox((15, 34, 180, 17), "Encoding File:", alignment="left") self.w.encodingFileTitle = TextBox((15, 52, 180, 17), fn, alignment="left") self.w.viewEncoding.enable(True) self.w.formatChoice.enable(True) self.w.overlapCheck.enable(True) self.w.autohintCheck.enable(True) self.w.releaseCheck.enable(True) self.w.generate.enable(True) def formatCallback(self, sender): self.format = self.formats[sender.get()] if self.format == 'otf' or self.format == 'pfa': self.decompose = True self.w.decomposeCheck.enable(False) self.w.decomposeCheck.set(True) else: self.w.decomposeCheck.enable(True) def decomposeCallback(self, sender): if sender.get() == 0: self.decompose = False else: self.decompose = True def overlapCallback(self, sender): if sender.get() == 0: self.overlap = False else: self.overlap = True def autohintCallback(self, sender): if sender.get() == 0: self.autohint = False else: self.autohint = True def releaseCallback(self, sender): if sender.get() == 0: self.release = False else: self.release = True def generateCallback(self, sender): font = CurrentFont() d,f = os.path.split(font.path) f = f[:-3] + self.format putFile(messageText="Save Font", directory=d, fileName=f, parentWindow=self.w, resultCallback=self.processGenerateCallback) def processGenerateCallback(self, sender): path = sender font = CurrentFont() font.generate(path, self.format, decompose=self.decompose, autohint=self.autohint, releaseMode=self.release, glyphOrder=self.glyphOrder)
class MasterSelectorWindow(GeneralPlugin): @objc.python_method def settings(self): self.name = 'Master Selector' self.warningOnlyOneMaster = Glyphs.localize({ "en": "Your font has only one master.", "de": "Die Schrift hat nur einen Master.", "fr": "La police contient seulement un master.", "es": "La fuente tiene solamente un máster.", }) self.warningNoFontOpen = Glyphs.localize({ "en": "Please open a font first.", "de": "Bitte öffnen Sie erst eine Schrift.", "fr": "Veuillez ouvrir un fichier de police.", "es": "Por favor, primero abrir una fuente.", }) @objc.python_method def start(self): try: targetMenu = WINDOW_MENU newMenuItem = NSMenuItem(self.name, self.showWindow_) Glyphs.menu[targetMenu].append(newMenuItem) except: mainMenu = Glyphs.mainMenu() s = objc.selector(self.showWindow, signature='v@:@') newMenuItem = NSMenuItem.alloc( ).initWithTitle_action_keyEquivalent_(self.name, s, "") newMenuItem.setTarget_(self) mainMenu.itemWithTag_(5).submenu().addItem_(newMenuItem) def showWindow_(self, sender): if Glyphs.font is None: self.helperWindow(self.warningNoFontOpen) elif len(Glyphs.font.masters) < 2: self.helperWindow(self.warningOnlyOneMaster) else: mastersList = [] for m in Glyphs.font.masters: mastersList.append(m.name) currentMasterIndex = Glyphs.font.masterIndex self.windowWidth = 250 self.windowHeight = 25 * len(mastersList) + 23 + 30 self.w = FloatingWindow((self.windowWidth, self.windowHeight), self.name) self.w.radiomasters = RadioGroup( (10, 10, -10, 25 * len(mastersList)), mastersList, callback=self.changeMaster) self.w.slider = Slider((10, -35, -10, 23), tickMarkCount=len(mastersList), stopOnTickMarks=True, value=currentMasterIndex, minValue=0, maxValue=len(mastersList) - 1, sizeStyle="small", continuous=False, callback=self.changeMasterSlider) self.w.open() @objc.python_method def helperWindow(self, message): Message(title=self.name, message=message, OKButton=None) @objc.python_method def changeMaster(self, sender): currentChoice = self.w.radiomasters.get() try: Glyphs.font.parent.windowController().setMasterIndex_( currentChoice) self.w.slider.set(currentChoice) except: print('BROKEN') pass @objc.python_method def changeMasterSlider(self, sender): currentChoice = int(self.w.slider.get()) try: Glyphs.font.parent.windowController().setMasterIndex_( currentChoice) self.w.radiomasters.set(currentChoice) except: print('BROKEN') pass @objc.python_method def __file__(self): """Please leave this method unchanged""" return __file__
class Adhesiontext(BaseWindowController): def __init__(self): flushAlign = 76 firstRowY = 12 rowOffsetY = 30 firstCheckY = 135 checkOffsetY = 27 rightMarginX = -12 self.windowWidth = 410 self.windowHeightWithoutOptions = 45 self.windowHeightWithOptions = 280 self.scriptIsRTL = False windowPos = getExtensionDefault("%s.%s" % (extensionKey, "windowPos")) if not windowPos: windowPos = (100, 100) self.optionsVisible = getExtensionDefault("%s.%s" % (extensionKey, "optionsVisible")) if self.optionsVisible: optionsButtonSign = '-' windowHeight = self.windowHeightWithOptions else: self.optionsVisible = False # needs to be set because the first time the extension runs self.optionsVisible will be None optionsButtonSign = '+' windowHeight = self.windowHeightWithoutOptions self.chars = getExtensionDefault("%s.%s" % (extensionKey, "chars")) if not self.chars: self.chars = '' self.sliderValue = getExtensionDefault("%s.%s" % (extensionKey, "sliderValue")) if not self.sliderValue: self.sliderValue = 25 self.scriptsIndex = getExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex")) if not self.scriptsIndex: self.scriptsIndex = 0 self.langsIndex = getExtensionDefault("%s.%s" % (extensionKey, "langsIndex")) if not self.langsIndex: self.langsIndex = 0 self.w = FloatingWindow((windowPos[0], windowPos[1], self.windowWidth, windowHeight), "adhesiontext") # 1st row self.w.labelChars = TextBox((10, firstRowY, flushAlign, 20), "Characters:", alignment="right") self.w.chars = EditText((flushAlign +15, firstRowY -1, 199, 22), self.chars, callback=self.charsCallback) self.w.button = Button((300, firstRowY, 68, 20), "Get text", callback=self.buttonCallback) self.w.spinner = FixedSpinner((325, firstRowY, 20, 20), displayWhenStopped=False) self.w.optionsButton = SquareButton((378, firstRowY +1, 18, 18), optionsButtonSign, sizeStyle="small", callback=self.optionsCallback) # set the initial state of the button according to the content of the chars EditText if len(self.w.chars.get()): self.w.button.enable(True) else: self.w.button.enable(False) # keep track of the content of chars EditText self.previousChars = self.w.chars.get() # 2nd row self.w.labelWords = TextBox((10, firstRowY + rowOffsetY, flushAlign, 20), "Words:", alignment="right") self.w.wordCount = TextBox((flushAlign +12, firstRowY + rowOffsetY, 40, 20), alignment="left") self.w.slider = Slider((flushAlign +47, firstRowY + rowOffsetY +1, 165, 20), value=self.sliderValue, minValue=5, maxValue=200, callback=self.sliderCallback) # set the initial wordCount value according to the position of the slider self.w.wordCount.set(int(self.w.slider.get())) # 3rd row self.w.labelScripts = TextBox((10, firstRowY + rowOffsetY *2, flushAlign, 20), "Script:", alignment="right") self.w.scriptsPopup = PopUpButton((flushAlign +15, firstRowY + rowOffsetY *2, 150, 20), scriptsNameList, callback=self.scriptsCallback) self.w.scriptsPopup.set(self.scriptsIndex) # 4th row self.w.labelLangs = TextBox((10, firstRowY + rowOffsetY *3, flushAlign, 20), "Language:", alignment="right") self.w.langsPopup = PopUpButton((flushAlign +15, firstRowY + rowOffsetY *3, 150, 20), []) # set the initial list of languages according to the script value self.w.langsPopup.setItems(langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]]) self.w.langsPopup.set(self.langsIndex) self.punctCheck = getExtensionDefault("%s.%s" % (extensionKey, "punctCheck")) if not self.punctCheck: self.punctCheck = 0 self.figsCheck = getExtensionDefault("%s.%s" % (extensionKey, "figsCheck")) if not self.figsCheck: self.figsCheck = 0 self.figsPopup = getExtensionDefault("%s.%s" % (extensionKey, "figsPopup")) if not self.figsPopup: self.figsPopup = 0 self.trimCheck = getExtensionDefault("%s.%s" % (extensionKey, "trimCheck")) if not self.trimCheck: self.trimCheck = 0 self.caseCheck = getExtensionDefault("%s.%s" % (extensionKey, "caseCheck")) if not self.caseCheck: self.caseCheck = 0 self.casingCheck = getExtensionDefault("%s.%s" % (extensionKey, "casingCheck")) if not self.casingCheck: self.casingCheck = 0 self.casingPopup = getExtensionDefault("%s.%s" % (extensionKey, "casingPopup")) if not self.casingPopup: self.casingPopup = 0 # 1st checkbox self.w.punctCheck = CheckBox((flushAlign +15, firstCheckY, 130, 20), "Add punctuation") self.w.punctCheck.set(self.punctCheck) # 2nd checkbox self.w.figsCheck = CheckBox((flushAlign +15, firstCheckY + checkOffsetY, 120, 20), "Insert numbers", callback=self.figsCallback) self.w.figsCheck.set(self.figsCheck) self.w.figsPopup = PopUpButton((210, firstCheckY + checkOffsetY, 90, 20), figOptionsList) self.w.figsPopup.set(self.figsPopup) # enable or disable the figure options PopUp depending on the figures CheckBox if scriptsNameList[self.w.scriptsPopup.get()] in enableFigOptionList: self.w.figsPopup.show(True) if self.w.figsCheck.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) else: self.w.figsPopup.show(False) # 3rd checkbox self.w.trimCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *2, 120, 20), "Trim accents") self.w.trimCheck.set(self.trimCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableTrimCheckList: self.w.trimCheck.enable(True) else: self.w.trimCheck.enable(False) # 4th checkbox self.w.caseCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *3, 120, 20), "Ignore casing") self.w.caseCheck.set(self.caseCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList: self.w.caseCheck.enable(True) else: self.w.caseCheck.enable(False) # 5th checkbox self.w.casingCheck = CheckBoxPlus((flushAlign +15, firstCheckY + checkOffsetY *4, 115, 20), "Change casing", callback=self.casingCallback) self.w.casingCheck.set(self.casingCheck) if scriptsNameList[self.w.scriptsPopup.get()] in enableCaseCheckList: self.w.casingCheck.enable(True) else: self.w.casingCheck.enable(False) self.w.casingPopup = PopUpButton((210, firstCheckY + checkOffsetY *4, 90, 20), casingNameList) self.w.casingPopup.set(self.casingPopup) # enable or disable the casing PopUp depending on the casing CheckBox if self.w.casingCheck.get() and self.w.casingCheck.isEnable(): self.w.casingPopup.enable(True) else: self.w.casingPopup.enable(False) self.nsTextField = self.w.chars.getNSTextField() self.w.setDefaultButton(self.w.button) self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def windowClose(self, sender): self.saveExtensionDefaults() def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "windowPos"), self.w.getPosSize()[0:2]) setExtensionDefault("%s.%s" % (extensionKey, "optionsVisible"), self.optionsVisible) setExtensionDefault("%s.%s" % (extensionKey, "chars"), self.w.chars.get()) setExtensionDefault("%s.%s" % (extensionKey, "sliderValue"), int(self.w.slider.get())) setExtensionDefault("%s.%s" % (extensionKey, "scriptsIndex"), int(self.w.scriptsPopup.get())) setExtensionDefault("%s.%s" % (extensionKey, "langsIndex"), int(self.w.langsPopup.get())) setExtensionDefault("%s.%s" % (extensionKey, "punctCheck"), self.w.punctCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "figsCheck"), self.w.figsCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "figsPopup"), self.w.figsPopup.get()) setExtensionDefault("%s.%s" % (extensionKey, "trimCheck"), self.w.trimCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "caseCheck"), self.w.caseCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "casingCheck"), self.w.casingCheck.get()) setExtensionDefault("%s.%s" % (extensionKey, "casingPopup"), self.w.casingPopup.get()) def buttonCallback(self, sender): sender.enable(False) self.w.spinner.start() self.getText() self.w.spinner.stop() sender.enable(True) def optionsCallback(self, sender): sign = sender.getTitle() if sign == "+": sender.setTitle("-") self.w.resize(self.windowWidth, self.windowHeightWithOptions, animate=True) self.optionsVisible = True else: sender.setTitle("+") self.w.resize(self.windowWidth, self.windowHeightWithoutOptions, animate=True) self.optionsVisible = False def charsCallback(self, sender): charsContent = sender.get() if len(charsContent): self.w.button.enable(True) nsTextView = self.nsTextField.currentEditor() # NOTE: the field editor is only available when NSTextField is in editing mode. # when only one glyph is selected and copied, the contents of the clipboard are the glyph's XML # instead of its unicode character or its name; therefore, post-process the pasted content. if xmlHeader in charsContent: caretIndex = charsContent.index(xmlHeader) codepointString = re_glyphUnicode.search(charsContent) glyphName = re_glyphName.search(charsContent) if codepointString: replacement = unichr(eval('0x' + codepointString.group(1))) elif glyphName: replacement = '/' + glyphName.group(1) else: replacement = '' # replace the glyph's XML by its unicode character or its name self.w.chars.set(re_glyph.sub(replacement, charsContent)) # restore the location of the caret location = caretIndex + len(replacement) nsTextView.setSelectedRange_((location, 0)) # update the variable charsContent = sender.get() caretIndex = nsTextView.selectedRanges()[0].rangeValue().location # Limit the number of characters numeralWasFound = self.stringHasNumeral(charsContent) if len(charsContent) > maxChars or numeralWasFound: NSBeep() if numeralWasFound: self.showMessage("Sorry, numerals are not allowed.", "") else: self.showMessage("You've reached the maximum \rnumber of characters.", "The limit is %d." % maxChars) # restore the content of chars EditText to the previous string sender.set(self.previousChars) # restore the focus on the chars EditText and restore the location of the caret caretIndexAdjust = len(self.previousChars) - len(charsContent) self.w.getNSWindow().makeFirstResponder_(self.nsTextField) nsTextView.setSelectedRange_((caretIndex + caretIndexAdjust, 0)) # update the stored string self.previousChars = sender.get() else: self.w.button.enable(False) def sliderCallback(self, sender): self.w.wordCount.set(int(sender.get())) def scriptsCallback(self, sender): self.w.langsPopup.setItems(langsNameDict[scriptsNameList[sender.get()]]) # toggle RTL/LTR if scriptsNameList[sender.get()] in rightToLeftList: self.scriptIsRTL = True self.nsTextField.setBaseWritingDirection_(NSWritingDirectionRightToLeft) self.nsTextField.setAlignment_(NSRightTextAlignment) else: self.scriptIsRTL = False self.nsTextField.setBaseWritingDirection_(NSWritingDirectionLeftToRight) self.nsTextField.setAlignment_(NSLeftTextAlignment) # restore the focus on the chars EditText self.w.getNSWindow().makeFirstResponder_(self.nsTextField) # toggle figsPopup if scriptsNameList[sender.get()] in enableFigOptionList: self.w.figsPopup.show(True) if self.w.figsCheck.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) else: self.w.figsPopup.show(False) # toggle trimCheck if scriptsNameList[sender.get()] in enableTrimCheckList: self.w.trimCheck.enable(True) else: self.w.trimCheck.enable(False) # toggle caseCheck and casingCheck if scriptsNameList[sender.get()] in enableCaseCheckList: self.w.caseCheck.enable(True) self.w.casingCheck.enable(True) if self.w.casingCheck.get(): self.w.casingPopup.enable(True) else: self.w.caseCheck.enable(False) self.w.casingCheck.enable(False) self.w.casingPopup.enable(False) def figsCallback(self, sender): if sender.get(): self.w.figsPopup.enable(True) else: self.w.figsPopup.enable(False) def casingCallback(self, sender): if sender.get(): self.w.casingPopup.enable(True) else: self.w.casingPopup.enable(False) def stringHasNumeral(self, string): if re_numeral.search(string): return True return False def isConnected(self): try: urlopen(url, timeout=3) return True except URLError: pass return False def getText(self): if CurrentFont() is None: NSBeep() self.showMessage("Open a font first.", "") return if not self.isConnected(): NSBeep() self.showMessage("Required internet connection not found.", "") return values = {'chars' : self.w.chars.get().encode('utf-8'), 'script' : scriptsTagDict[scriptsNameList[self.w.scriptsPopup.get()]], 'tb' : langsTagDict[langsNameDict[scriptsNameList[self.w.scriptsPopup.get()]][self.w.langsPopup.get()]] } if self.w.punctCheck.get(): values['punct'] = True if self.w.figsCheck.get(): values['figs'] = True if self.w.figsPopup.isVisible(): figsOptTagsList = ["dflt", "locl"] values['figsOpt'] = figsOptTagsList[self.w.figsPopup.get()] if self.w.trimCheck.get() and self.w.trimCheck.isEnable(): values['trim'] = True if self.w.caseCheck.get() and self.w.caseCheck.isEnable(): values['case'] = True if self.w.casingCheck.get() and self.w.casingCheck.isEnable(): values['casing'] = casingNameList[self.w.casingPopup.get()].lower() data = urlencode(values) data = data.encode('utf-8') print(data) request = Request(url, data) response = urlopen(request) text = response.read() textU = unicode(text, 'utf-8') if (msgStr in textU): textU = textU.replace(msgStr, "") NSBeep() self.showMessage(textU, "") return elif (wrnStr in textU): resultIndex = textU.find(rsltStr) secmsgIndex = textU.find(sndStr) frstmsgU = textU[:secmsgIndex].replace(wrnStr, "") scndmsgU = textU[secmsgIndex:resultIndex].replace(sndStr, "") textU = textU[resultIndex:].replace(rsltStr, "") NSBeep() self.showMessage(frstmsgU, scndmsgU) textList = textU.split() trimmedText = ' '.join(textList[:int(self.w.slider.get())]) if CurrentSpaceCenter() is None: OpenSpaceCenter(CurrentFont(), newWindow=False) sp = CurrentSpaceCenter() print(trimmedText) sp.setRaw(trimmedText) # Toggle RTL-LTR try: sp.setLeftToRight(not self.scriptIsRTL) sp.setInputWritingDirection('Right to Left' if self.scriptIsRTL else 'Left to Right') except AttributeError: pass return
class GlyphFax(object): def __init__(self): '''Initialize the dialog.''' x = y = padding = 10 buttonHeight = 20 windowWidth = 400 rows = 4.5 self.w = FloatingWindow( (windowWidth, buttonHeight * rows + padding * (rows)), "Glyph Fax Machine") self.fonts = {} # self.w.textBox = TextBox((x, y, -padding, buttonHeight), "Glyphs to Copy") # y += buttonHeight self.w.editText = EditText( (x, y, -padding, buttonHeight * 2 + padding), placeholder="Space-separated list of glyphs to copy") y += buttonHeight * 2 + padding * 2 # self.w.overwriteGlyphsCheckBox = CheckBox((x, y, -padding, buttonHeight), "Overwrite Glyphs", value=False, callback=self.overwriteGlyphsOptions) # y += buttonHeight + padding self.w.overwriteNormalWidthGlyphsCheckBox = CheckBox( (x, y, -padding, buttonHeight), "Overwrite 600w Glyphs", value=False, sizeStyle="small") # self.w.overwriteNormalWidthGlyphsCheckBox.show(False) self.w.overwriteAdjustedWidthGlyphsCheckBox = CheckBox( (windowWidth * 0.4, y, -padding, buttonHeight), "Overwrite non-600w Glyphs", value=False, sizeStyle="small") # self.w.overwriteAdjustedWidthGlyphsCheckBox.show(False) self.w.colorWell = ColorWell( (windowWidth * 0.85, y, -padding, buttonHeight), color=NSColor.orangeColor()) y += buttonHeight + padding self.w.sans2mono = Button( (x, y, windowWidth / 3 - padding / 2, buttonHeight), "Sans → Mono", callback=self.sans2monoCallback) self.w.mono2sans = Button( (windowWidth / 3 + padding, y, -padding, buttonHeight), "Mono → Sans", callback=self.mono2SansCallback) self.w.open() def copyGlyph(self, glyphName, fontToCopyFrom, fontToSendTo): print( f"copying glyph /{glyphName} from \n\t{fontToCopyFrom} \n\tto \n\t{fontToSendTo}" ) glyphCopyName = glyphName copyGlyph = True if glyphName not in fontToSendTo: fontToSendTo.newGlyph(glyphName) else: overwrite600 = self.w.overwriteNormalWidthGlyphsCheckBox.get() overwriteNon600 = self.w.overwriteAdjustedWidthGlyphsCheckBox.get() # default case: overwrite is false (overwrite 600 is true, overwrite non600 is false) if overwrite600 == False and overwriteNon600 == False: # glyphCopyName = glyphName + '.copy' # fontToSendTo.newGlyph(glyphCopyName) copyGlyph = False elif overwrite600 == True and overwriteNon600 == True: fontToSendTo[glyphCopyName].clear() elif overwrite600 == False and overwriteNon600 == True: if round(fontToSendTo[glyphCopyName].width, -1) == 600: # glyphCopyName = glyphName + '.copy' # fontToSendTo.newGlyph(glyphCopyName) copyGlyph = False else: fontToSendTo[glyphCopyName].clear() elif overwrite600 == True and overwriteNon600 == False: if round(fontToSendTo[glyphCopyName].width, -1) == 600: fontToSendTo[glyphCopyName].clear() else: # glyphCopyName = glyphName + '.copy' # fontToSendTo.newGlyph(glyphCopyName) copyGlyph = False if copyGlyph: glyphToCopy = fontToCopyFrom[glyphName] layerGlyph = fontToSendTo[glyphCopyName].getLayer("foreground") # get the point pen of the layer glyph pen = layerGlyph.getPointPen() # draw the points of the imported glyph into the layered glyph glyphToCopy.drawPoints(pen) if len(layerGlyph.components) == 1: parentGlyphWidth = fontToSendTo[ layerGlyph.components[0].baseGlyph].width print(type(parentGlyphWidth)) layerGlyph.width = parentGlyphWidth print("copied glyph now has width of its component parent:", str(parentGlyphWidth)) else: layerGlyph.width = glyphToCopy.width layerGlyph.markColor = (self.w.colorWell.get().redComponent(), self.w.colorWell.get().greenComponent(), self.w.colorWell.get().blueComponent(), self.w.colorWell.get().alphaComponent()) for anchor in glyphToCopy.anchors: layerGlyph.appendAnchor(anchor.name, (anchor.x, anchor.y)) def glyphsToCopy(self, sender): if self.w.editText.get() == "": Message('no glyphs listed', title='Glyph Fax Machine', informativeText='Please list glyphs to send copies of!') return return self.w.editText.get().split(" ") # default: dlig & ss01–ss12 off def getFontsCueCopying(self, proportionToCopyFrom, glyphsToCopy): files = getFile("Select UFOs to copy glyphs between", allowsMultipleSelection=True, fileTypes=["ufo"]) for path in files: f = OpenFont(path, showInterface=True) variation = f.info.styleName.replace('Mono ', '').replace('Sans ', '') if variation not in self.fonts.keys(): self.fonts[variation] = [] if self.fonts[variation] == []: self.fonts[variation].append(f) elif self.fonts[variation] != []: # if 'Mono' already in list, put 'Sans' second if proportionToCopyFrom in self.fonts[variation][ 0].info.styleName: self.fonts[variation].append(f) # else put 'Mono' first else: self.fonts[variation] = [f] + self.fonts[variation] for variation in self.fonts.keys(): print('\n', variation, '\n') for glyphName in glyphsToCopy: self.copyGlyph(glyphName, self.fonts[variation][0], self.fonts[variation][1]) self.w.close() ## ss01–ss12 on def mono2SansCallback(self, sender): '''Copy glyphs from Mono to Sans masters.''' glyphsToCopy = self.glyphsToCopy(sender) if glyphsToCopy is not None: self.getFontsCueCopying('Mono', glyphsToCopy) def sans2monoCallback(self, sender): '''Copy glyphs from Sans to Mono masters.''' glyphsToCopy = self.glyphsToCopy(sender) if glyphsToCopy is not None: self.getFontsCueCopying('Sans', glyphsToCopy)
class spacingObserver(object): def __init__(self): self.enableGroupSpacing = False self.popupOpen = False addObserver(self, 'glyphEditCallback', 'spaceCenterKeyDown') addObserver(self, 'glyphEditedCallback', 'spaceCenterKeyUp') addObserver(self, 'spaceCenterOpenCallback', 'spaceCenterDidOpen') addObserver(self, 'fontOpenCallback', 'fontDidOpen') self.previousMargins = {'left': 0, 'right': 0} def processMetricsGroups(self, baseGlyph=None): for groupName in self.metricsGroups: if (baseGlyph is None) and len(self.font.groups[groupName]) > 0: baseGlyph = self.font.groups[groupName][0] self.previousMargins['left'] = self.font[baseGlyph].angledLeftMargin self.previousMargins['right'] = self.font[baseGlyph].angledRightMargin if (metricsPrefix in groupName) and (baseGlyph in self.font.groups[groupName]): if (leftIndicator in groupName) and (self.previousMargins['left'] != self.font[baseGlyph].angledLeftMargin): self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Left') elif (rightIndicator in groupName) and (self.previousMargins['right'] != self.font[baseGlyph].angledRightMargin): self.setGroupSpacing(baseGlyph, self.font.groups[groupName], 'Right') def setGroupSpacing(self, baseGlyphName, group, side): for glyphName in group: baseGlyph = self.font[baseGlyphName] targetGlyph = self.font[glyphName] if glyphName is not baseGlyphName: if (len(targetGlyph.components) > 0) and (side == 'Left'): for component in targetGlyph.components: if component.baseGlyph in group: component.move((self.previousMargins['left']-baseGlyph.angledLeftMargin, 0)) self.setSidebearing(baseGlyph, targetGlyph, side) elif glyphName is baseGlyphName: if (len(baseGlyph.components) > 0) and (side == 'Left'): for component in baseGlyph.components: if component.baseGlyph in group: component.move((self.previousMargins['left']-baseGlyph.angledLeftMargin, 0)) targetGlyph.update() def setSidebearing(self, baseGlyph, targetGlyph, side): baseMargin = getattr(baseGlyph, 'angled' + side + 'Margin') targetMargin = getattr(targetGlyph, 'angled' + side + 'Margin') if targetMargin != baseMargin: setattr(targetGlyph, 'angled' + side + 'Margin', baseMargin) def getMetricsGroups(self, notification=None): self.font = CurrentFont() if self.font is not None: self.metricsGroups = [group for group in self.font.groups.keys() if metricsPrefix in group and leftIndicator in group or rightIndicator in group] if (notification is not None) and (self.enableGroupSpacing == True): self.processMetricsGroups() def enableGroupSpacingCallback(self, sender): self.enableGroupSpacing = sender.get() def glyphEditCallback(self, notification): edGlyph = notification['glyph'] self.previousMargins = {'width': edGlyph.width, 'left': edGlyph.angledLeftMargin, 'right': edGlyph.angledRightMargin} def glyphEditedCallback(self, notification): if self.enableGroupSpacing == True: edGlyph = notification['glyph'] if self.font != CurrentFont(): self.getMetricsGroups() self.processMetricsGroups(edGlyph.name) def spaceCenterOpenCallback(self, notification): if (not self.popupOpen) and (len(self.metricsGroups) > 0): self.w = FloatingWindow((160, 36), 'Group Spacing') self.w.activateGroups = CheckBox((9, -27, 151, 18), "Activate Group spacing", value=self.enableGroupSpacing, callback=self.enableGroupSpacingCallback, sizeStyle="small") self.w.bind('close', self.windowCloseCallback) self.w.open() self.popupOpen = True def windowCloseCallback(self, notification): self.popupOpen = False def fontOpenCallback(self, notification): font = notification['font'] font.groups.addObserver(self, 'getMetricsGroups', 'Groups.Changed') self.getMetricsGroups(notification)
class SidebearingsLinker(BaseWindowController): allFonts = [] servantSubscriptions = [] masterSubscriptions = [] displayedSubscriptions = [] currentRow = None selectedFont = None def __init__(self, willOpen=True): super(SidebearingsLinker, self).__init__() # collecting fonts self.allFonts = AllFonts() if self.allFonts != []: self.selectedFont = self.allFonts[0] # interface self.w = FloatingWindow((PLUGIN_WIDTH, PLUGIN_HEIGHT), PLUGIN_TITLE) jumpingY = MARGIN_VER self.w.fontPopUp = PopUpButton( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), getNamesFrom(self.allFonts), callback=self.fontPopUpCallback) jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_ROW self.w.canvas = CanvasGroup( (MARGIN_HOR, jumpingY, NET_WIDTH, CANVAS_HEIGHT), delegate=self) jumpingY += CANVAS_HEIGHT + MARGIN_ROW linksColumnDescriptions = [{ "title": "left", 'key': 'lft', 'width': LIST_WIDE_COL }, { "title": "active", "cell": CheckBoxListCell(), 'key': 'lftActive', 'width': LIST_NARROW_COL }, { "title": "glyph", 'key': 'servant', 'width': LIST_WIDE_COL, "editable": False }, { "title": "active", "cell": CheckBoxListCell(), 'key': 'rgtActive', 'width': LIST_NARROW_COL }, { "title": "right", 'key': 'rgt', 'width': LIST_WIDE_COL }] if self.selectedFont is not None: links = loadLinksFromFont(self.selectedFont) else: links = [] self.w.linksList = List( (MARGIN_HOR, jumpingY, NET_WIDTH, 200), links, showColumnTitles=False, allowsMultipleSelection=False, drawVerticalLines=True, columnDescriptions=linksColumnDescriptions, selectionCallback=self.selectionLinksListCallback, editCallback=self.editLinksListCallback) if self.selectedFont is not None: self.w.linksList.setSelection([0]) self.currentRow = self.w.linksList[0] self.matchDisplayedSubscriptions() jumpingY += self.w.linksList.getPosSize()[3] + MARGIN_ROW buttonWidth = (NET_WIDTH - MARGIN_HOR) / 2 self.w.linkAllButton = SquareButton( (MARGIN_HOR, jumpingY, buttonWidth, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Link All', callback=self.linkAllButtonCallback) self.w.unlockAllButton = SquareButton( (MARGIN_HOR * 2 + buttonWidth, jumpingY, buttonWidth, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Unlink All', callback=self.unlockAllButtonCallback) jumpingY += vanillaControlsSize[ 'ButtonRegularHeight'] * 1.5 + MARGIN_ROW self.w.separationLineOne = HorizontalLine( (MARGIN_HOR, jumpingY, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness'])) jumpingY += MARGIN_ROW self.w.pushIntoFontButton = SquareButton( (MARGIN_HOR, jumpingY, buttonWidth, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Push into font', callback=self.pushIntoFontButtonCallback) self.w.pushIntoFontButton.enable(False) self.w.clearLibsButton = SquareButton( (MARGIN_HOR * 2 + buttonWidth, jumpingY, buttonWidth, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Clear Libs', callback=self.clearLibsButtonCallback) jumpingY += vanillaControlsSize[ 'ButtonRegularHeight'] * 1.5 + MARGIN_ROW self.w.loadFromTable = SquareButton( (MARGIN_HOR, jumpingY, buttonWidth, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Load from table', callback=self.loadFromTableCallback) self.w.loadFromTable.enable(True) self.w.exportTable = SquareButton( (MARGIN_HOR * 2 + buttonWidth, jumpingY, buttonWidth, vanillaControlsSize['ButtonRegularHeight'] * 1.5), 'Export table', callback=self.exportTableCallback) self.w.exportTable.enable(True) jumpingY += vanillaControlsSize[ 'ButtonRegularHeight'] * 1.5 + MARGIN_VER * 2 self.w.resize(PLUGIN_WIDTH, jumpingY) self.setUpBaseWindowBehavior() if self.selectedFont is not None: self.matchSubscriptions() result = askYesNo('Warning', 'Do you want to align servants to masters?') if bool(result) is True: self._alignServantsToMasters() addObserver(self, "drawOnGlyphCanvas", "draw") addObserver(self, "drawOnGlyphCanvas", "drawInactive") addObserver(self, 'fontDidOpenCallback', 'fontDidOpen') addObserver(self, 'fontDidCloseCallback', 'fontDidClose') if willOpen is True: self.w.open() # drawing callbacks def drawOnGlyphCanvas(self, infoDict): glyphOnCanvas = infoDict['glyph'] scalingFactor = infoDict['scale'] bodySize = .25 horizontalOffset = 80 if PLUGIN_LIB_NAME in glyphOnCanvas.lib: thisLib = glyphOnCanvas.lib[PLUGIN_LIB_NAME] else: return None lftGlyph = None if thisLib['lft'] != '': lftGlyph = self.selectedFont[thisLib['lft']] rgtGlyph = None if thisLib['rgt'] != '': rgtGlyph = self.selectedFont[thisLib['rgt']] try: dt.fill(*GRAY) if lftGlyph is not None: dt.save() dt.translate(-lftGlyph.width * bodySize - horizontalOffset, -self.selectedFont.info.unitsPerEm * bodySize) # glyph dt.scale(bodySize) dt.drawGlyph(lftGlyph) # lock if thisLib['lftActive'] is True: txt = u'🔒' else: txt = u'🔓' dt.fontSize(300) txtWdt, txtHgt = dt.textSize(txt) dt.text(txt, (-txtWdt, 0)) dt.restore() if rgtGlyph is not None: dt.save() dt.translate(glyphOnCanvas.width + horizontalOffset, -self.selectedFont.info.unitsPerEm * bodySize) dt.scale(bodySize) dt.drawGlyph(rgtGlyph) # lock if thisLib['rgtActive'] is True: txt = u'🔒' else: txt = u'🔓' dt.fontSize(300) dt.text(txt, (rgtGlyph.width, 0)) dt.restore() except Exception as error: print(error) def draw(self): try: if self.selectedFont is not None and self.currentRow is not None: scalingFactor = CANVAS_HEIGHT / ( self.selectedFont.info.unitsPerEm + UPM_MARGIN) if self.currentRow['lft'] in self.selectedFont: lftGlyph = self.selectedFont[self.currentRow['lft']] if lftGlyph is not None: drawReferenceGlyph(aGlyph=lftGlyph, scalingFactor=scalingFactor, startingX=NET_WIDTH * (1 / 6), left=True, right=False) drawLock(closed=self.currentRow['lftActive'], startingX=NET_WIDTH * (2 / 6), glyphQuota=self.selectedFont.info.xHeight, scalingFactor=scalingFactor) if self.currentRow['servant'] in self.selectedFont: servantGlyph = self.selectedFont[ self.currentRow['servant']] if servantGlyph is not None: drawReferenceGlyph(aGlyph=servantGlyph, scalingFactor=scalingFactor, startingX=NET_WIDTH * (3 / 6), left=True, right=True) if self.currentRow['rgt'] in self.selectedFont: rgtGlyph = self.selectedFont[self.currentRow['rgt']] if rgtGlyph is not None: drawReferenceGlyph(aGlyph=rgtGlyph, scalingFactor=scalingFactor, startingX=NET_WIDTH * (5 / 6), right=True) drawLock(closed=self.currentRow['rgtActive'], startingX=NET_WIDTH * (4 / 6), glyphQuota=self.selectedFont.info.xHeight, scalingFactor=scalingFactor) except Exception as error: print(error) # observers def unsubscribeGlyphs(self): for eachGlyph in self.servantSubscriptions: eachGlyph.removeObserver(self, "Glyph.WidthChanged") self.servantSubscriptions = list() for eachGlyph in self.masterSubscriptions: eachGlyph.removeObserver(self, "Glyph.WidthChanged") self.masterSubscriptions = list() def unsubscribeDisplayedGlyphs(self): for eachGlyph in self.displayedSubscriptions: eachGlyph.removeObserver(self, "Glyph.Changed") self.displayedSubscriptions = list() def matchDisplayedSubscriptions(self): self.unsubscribeDisplayedGlyphs() if self.currentRow['lft'] != '': lftGlyph = self.selectedFont[self.currentRow['lft']] if lftGlyph not in self.displayedSubscriptions: lftGlyph.addObserver(self, 'displayedGlyphChanged', 'Glyph.Changed') self.displayedSubscriptions.append(lftGlyph) if self.currentRow['rgt'] != '': rgtGlyph = self.selectedFont[self.currentRow['rgt']] if rgtGlyph not in self.displayedSubscriptions: rgtGlyph.addObserver(self, 'displayedGlyphChanged', 'Glyph.Changed') self.displayedSubscriptions.append(rgtGlyph) if self.currentRow['servant'] != '': servantGlyph = self.selectedFont[self.currentRow['servant']] if servantGlyph not in self.displayedSubscriptions: servantGlyph.addObserver(self, 'displayedGlyphChanged', 'Glyph.Changed') self.displayedSubscriptions.append(servantGlyph) def matchSubscriptions(self): self.unsubscribeGlyphs() for servantGlyph in self.selectedFont: if PLUGIN_LIB_NAME in servantGlyph.lib: thisLib = servantGlyph.lib[PLUGIN_LIB_NAME] if (thisLib['lft'] != '' and thisLib['lftActive'] is True) or ( thisLib['rgt'] != '' and thisLib['rgtActive'] is True): if servantGlyph not in self.servantSubscriptions: servantGlyph.addObserver(self, "servantGlyphChanged", "Glyph.WidthChanged") self.servantSubscriptions.append(servantGlyph) # servants if thisLib['lftActive'] is True and thisLib['lft'] != '': lftMaster = self.selectedFont[thisLib['lft']] if lftMaster not in self.masterSubscriptions: lftMaster.addObserver(self, "masterGlyphChanged", "Glyph.WidthChanged") self.masterSubscriptions.append(lftMaster) if thisLib['rgtActive'] is True and thisLib['rgt'] != '': rgtMaster = self.selectedFont[thisLib['rgt']] if rgtMaster not in self.masterSubscriptions: rgtMaster.addObserver(self, "masterGlyphChanged", "Glyph.WidthChanged") self.masterSubscriptions.append(rgtMaster) def servantGlyphChanged(self, notification): glyph = notification.object warningMessage = 'The glyph <{servantName}> is linked to <{masterName}>, do you want to broke the link?' if PLUGIN_LIB_NAME in glyph.lib: thisLib = glyph.lib[PLUGIN_LIB_NAME] if thisLib['lftActive'] is True: lftMaster = self.selectedFont[thisLib['lft']] if glyph.leftMargin != lftMaster.leftMargin: result = askYesNo( 'Warning', warningMessage.format(servantName=glyph.name, masterName=lftMaster.name)) if bool(result) is False: glyph.leftMargin = lftMaster.leftMargin thisLib['lftActive'] = True else: thisLib['lftActive'] = False if thisLib['rgtActive'] is True: rgtMaster = self.selectedFont[thisLib['rgt']] if glyph.rightMargin != rgtMaster.rightMargin: result = askYesNo( 'Warning', warningMessage.format(servantName=glyph.name, masterName=rgtMaster.name)) if bool(result) is False: glyph.rightMargin = rgtMaster.rightMargin thisLib['rgtActive'] = True else: thisLib['rgtActive'] = False links = loadLinksFromFont(self.selectedFont) self.w.linksList.set(links) self.w.canvas.update() def masterGlyphChanged(self, notification): masterGlyph = notification.object for eachGlyph in self.selectedFont: if PLUGIN_LIB_NAME in eachGlyph.lib: thisLib = eachGlyph.lib[PLUGIN_LIB_NAME] if thisLib['lft'] == masterGlyph.name and thisLib[ 'lftActive'] is True: eachGlyph.leftMargin = masterGlyph.leftMargin if thisLib['rgt'] == masterGlyph.name and thisLib[ 'rgtActive'] is True: eachGlyph.rightMargin = masterGlyph.rightMargin self.w.canvas.update() def displayedGlyphChanged(self, notification): self.w.canvas.update() # callbacks def fontDidOpenCallback(self, notification): self.allFonts = AllFonts() if self.selectedFont is not None: previousFontName = self.w.fontPopUp.getItems()[ self.w.fontPopUp.get()] else: self.selectedFont = self.allFonts[0] previousFontName = None newNames = getNamesFrom(self.allFonts) self.w.fontPopUp.setItems(newNames) if previousFontName is not None: self.w.fontPopUp.set(newNames.index(previousFontName)) links = loadLinksFromFont(self.selectedFont) self.w.linksList.set(links) self.w.linksList.setSelection([0]) self.currentRow = self.w.linksList[self.w.linksList.getSelection()[0]] self.matchSubscriptions() self.matchDisplayedSubscriptions() def fontDidCloseCallback(self, notification): self.allFonts = AllFonts() if self.selectedFont is None and self.allFonts != []: self.selectedFont = self.allFonts[0] links = loadLinksFromFont(self.selectedFont) self.w.linksList.set(links) currentFontName = self.w.fontPopUp.getItems()[self.w.fontPopUp.get()] newNames = getNamesFrom(self.allFonts) self.w.fontPopUp.setItems(newNames) if self.allFonts != []: if currentName in newNames: self.w.fontPopUp.set(newNames.index(currentName)) else: self.w.fontPopUp.set( newNames.index(os.path.basename(self.selectedFont))) def fontPopUpCallback(self, sender): if self._compareLibsToList() is False: result = askYesNo( 'Some changes were not pushed into font, would you like to do it now? Otherwise the changes will be lost' ) if bool(result) is True: self.pushIntoFontButtonCallback(sender=None) self.selectedFont = self.allFonts[sender.get()] links = loadLinksFromFont(self.selectedFont) self.w.linksList.set(links) def selectionLinksListCallback(self, sender): if sender.getSelection() == []: sender.setSelection([0]) self.currentRow = sender[sender.getSelection()[0]] self.matchDisplayedSubscriptions() self.w.canvas.update() def editLinksListCallback(self, sender): self.w.canvas.update() self.w.pushIntoFontButton.enable(not self._compareLibsToList()) def linkAllButtonCallback(self, sender): for eachRow in self.w.linksList: if eachRow['lft'] != '': eachRow['lftActive'] = True if eachRow['rgt'] != '': eachRow['rgtActive'] = True def unlockAllButtonCallback(self, sender): for eachRow in self.w.linksList: if eachRow['lft'] is not None: eachRow['lftActive'] = False if eachRow['rgt'] is not None: eachRow['rgtActive'] = False def pushIntoFontButtonCallback(self, sender): for eachRow in self.w.linksList: eachGlyph = self.selectedFont[eachRow['servant']] newLib = { 'lft': eachRow['lft'], 'lftActive': bool(eachRow['lftActive']), 'rgt': eachRow['rgt'], 'rgtActive': bool(eachRow['rgtActive']) } if newLib['lft'] == '' and newLib['rgt'] == '': if PLUGIN_LIB_NAME in eachGlyph.lib: del eachGlyph.lib[PLUGIN_LIB_NAME] else: eachGlyph.lib[PLUGIN_LIB_NAME] = newLib self.matchSubscriptions() self._alignServantsToMasters() self.w.pushIntoFontButton.enable(False) def clearLibsButtonCallback(self, sender): for eachGlyph in self.selectedFont: if PLUGIN_LIB_NAME in eachGlyph.lib: del eachGlyph.lib[PLUGIN_LIB_NAME] selectionIndex = self.w.linksList.getSelection() links = loadLinksFromFont(self.selectedFont) self.w.linksList.set(links) self.w.linksList.setSelection(selectionIndex) def loadFromTableCallback(self, sender): loadingPath = getFile("Select table with linked sidebearings") if loadingPath is None: return None with open(loadingPath[0], 'r') as linksTable: rawTable = [item for item in linksTable.readlines()] changedItems = [] toBeLinksList = list(self.selectedFont.glyphOrder) for indexRow, eachRow in enumerate(rawTable): lft, lftActive, servant, rgtActive, rgt = [ item.strip() for item in eachRow.split('\t') ] servantResult = self._isGlyphNameAllowed(servant) lftResult = self._isGlyphNameAllowed(lft) rgtResult = self._isGlyphNameAllowed(rgt) if all([servantResult, lftResult, rgtResult]) is False: message( 'Line {} contains a mistake'.format(indexRow + 1), 'One or more glyphs [lft:<{}> servant:<{}> rgt:<{}>] are not allowed in this font' .format(lft, servant, rgt)) return None if servant in toBeLinksList: servantIndex = toBeLinksList.index(servant) toBeLinksList[servantIndex] = { 'lft': lft, 'lftActive': True if lftActive == 'True' else False, 'servant': servant, 'rgt': rgt, 'rgtActive': True if rgtActive == 'True' else False } changedItems.append(servantIndex) for eachUnchangedIndex in [ ii for ii in range(len(toBeLinksList)) if ii not in changedItems ]: toBeLinksList[eachUnchangedIndex] = { 'lft': '', 'lftActive': False, 'servant': toBeLinksList[eachUnchangedIndex], 'rgt': '', 'rgtActive': False } self.w.linksList.set(toBeLinksList) self._compareLibsToList() def exportTableCallback(self, sender): savingPath = putFile("") if savingPath is None: return None with open(savingPath, 'w') as linksTable: for eachRow in self.w.linksList: if eachRow['lft'] != '' or eachRow['rgt'] != '': linksTable.write( '{lft}\t{lftActive}\t{servant}\t{rgtActive}\t{rgt}\n'. format(**eachRow)) def windowCloseCallback(self, sender): self.unsubscribeGlyphs() self.unsubscribeDisplayedGlyphs() removeObserver(self, 'fontDidOpen') removeObserver(self, 'fontDidClose') removeObserver(self, "draw") removeObserver(self, "drawInactive") super(SidebearingsLinker, self).windowCloseCallback(sender) # private methods def _isGlyphNameAllowed(self, glyphName): if glyphName == '': return True elif glyphName not in self.selectedFont: return False else: return True def _compareLibsToList(self): inFont = loadLinksFromFont(self.selectedFont) return inFont == [item for item in self.w.linksList] def _alignServantsToMasters(self): for eachGlyph in self.selectedFont: if PLUGIN_LIB_NAME in eachGlyph.lib: thisLib = eachGlyph.lib[PLUGIN_LIB_NAME] if thisLib['lftActive'] is True and thisLib['lft'] != '': lftMaster = self.selectedFont[thisLib['lft']] eachGlyph.leftMargin = lftMaster.leftMargin if thisLib['rgtActive'] is True and thisLib['rgt'] != '': rgtMaster = self.selectedFont[thisLib['rgt']] eachGlyph.rightMargin = rgtMaster.rightMargin
class AdjustAnchors(BaseWindowController): def __init__(self): self.font = CurrentFont() self.glyph = CurrentGlyph() self.upm = self.font.info.unitsPerEm self.rf3 = int(roboFontVersion.split(".")[0]) >= 3 if self.rf3: self.layer = CurrentLayer() # key: glyph name -- value: list containing assembled glyphs self.glyphPreviewCacheDict = {} # key: anchor name -- value: list of mark glyph names self.anchorsOnMarksDict = {} # key: anchor name -- value: list of base glyph names self.anchorsOnBasesDict = {} self.CXTanchorsOnBasesDict = {} # key: mark glyph name -- value: anchor name # NOTE: It's expected that each mark glyph only has one type of anchor self.marksDict = {} self.fillAnchorsAndMarksDicts() # list of glyph names that will be displayed in the UI list self.glyphNamesList = [] # list of glyph names selected in the UI list self.selectedGlyphNamesList = [] # list of the glyph objects that should be inserted # before and after the accented glyphs self.extraGlyphsList = [] self.Blue, self.Alpha = 1, 0.6 self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") addObserver(self, "_fontWillClose", "fontWillClose") addObserver(self, "_currentFontChanged", "fontResignCurrent") addObserver(self, "_currentGlyphChanged", "currentGlyphChanged") addObserver(self, "_drawFill", "draw") addObserver(self, "_drawFill", "drawInactive") addObserver(self, "_previewFill", "drawPreview") # observer for the draw event addObserver(self, "_drawGlyphs", "draw") # draw the glyphs when the glyph window is not in focus addObserver(self, "_drawGlyphs", "drawInactive") addObserver(self, "_drawGlyphs", "drawPreview") integerNumFormatter = NSNumberFormatter.alloc().init() integerNumFormatter.setAllowsFloats_(False) integerNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter = NSNumberFormatter.alloc().init() intPosMinZeroNumFormatter.setAllowsFloats_(False) intPosMinZeroNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinZeroNumFormatter.setMinimum_(NSNumber.numberWithInt_(0)) intPosMinOneNumFormatter = NSNumberFormatter.alloc().init() intPosMinOneNumFormatter.setAllowsFloats_(False) intPosMinOneNumFormatter.setGeneratesDecimalNumbers_(False) intPosMinOneNumFormatter.setMinimum_(NSNumber.numberWithInt_(1)) self.textSize = getExtensionDefault("%s.%s" % (extensionKey, "textSize")) if not self.textSize: self.textSize = 150 self.lineHeight = getExtensionDefault("%s.%s" % (extensionKey, "lineHeight")) if not self.lineHeight: self.lineHeight = 200 self.extraSidebearings = getExtensionDefault( "%s.%s" % (extensionKey, "extraSidebearings")) if not self.extraSidebearings: self.extraSidebearings = [0, 0] self.extraGlyphs = getExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs")) if not self.extraGlyphs: self.extraGlyphs = '' posSize = getExtensionDefault("%s.%s" % (extensionKey, "posSize")) if not posSize: posSize = (100, 100, 1200, 400) self.calibrateMode = getExtensionDefault( "%s.%s" % (extensionKey, "calibrateMode")) if not self.calibrateMode: self.calibrateMode = False calibrateModeStrings = getExtensionDefault( "%s.%s" % (extensionKey, "calibrateModeStrings")) if not calibrateModeStrings: calibrateModeStrings = { 'group1.baseInput': 'dotlessi o s', 'group1.markInput': 'dieresis circumflex macron breve caron', 'group2.baseInput': 'I O S', 'group2.markInput': 'dieresis.cap circumflex.cap macron.cap ' 'breve.cap caron.cap', 'group3.baseInput': 'I.sc O.sc S.sc', 'group3.markInput': 'dieresis circumflex macron breve caron', 'group4.baseInput': '', 'group4.markInput': '', } # -- Window -- self.w = FloatingWindow(posSize, extensionName, minSize=(500, 400)) self.w.fontList = List((10, 10, 190, -41), self.glyphNamesList, selectionCallback=self.listSelectionCallback) if roboFontVersion < '1.7': # use the full width of the column self.w.fontList.getNSTableView().sizeToFit() self.w.fontList.show(not self.calibrateMode) self.w.lineView = MultiLineView((210, 10, -10, -41), pointSize=self.textSize, lineHeight=self.lineHeight, displayOptions={ "Beam": False, "displayMode": "Multi Line" }) self.w.lineView.setFont(self.font) # -- Calibration Mode -- baseLabel = "Bases" markLabel = "Marks" width, height = 190, 140 self.cm = Group((0, 0, 0, 0)) # --- self.cm.group1 = Group((5, height * 0, width, height - 10)) self.cm.group1.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group1.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group1.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group1.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group1.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group1.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group2 = Group((5, height * 1, width, height - 10)) self.cm.group2.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group2.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group2.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group2.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group2.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group2.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group3 = Group((5, height * 2, width, height - 10)) self.cm.group3.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group3.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group3.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group3.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group3.markInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group3.divider = HorizontalLine((0, -1, -0, 1)) # --- self.cm.group4 = Group((5, height * 3, width, height - 10)) self.cm.group4.baseLabel = TextBox((0, 0, width, 20), baseLabel) self.cm.group4.baseInput = EditText( (0, 21, width, 22), calibrateModeStrings['group4.baseInput'], callback=self.updateCalibrateMode, continuous=False) self.cm.group4.markLabel = TextBox((0, 50, width, 20), markLabel) self.cm.group4.markInput = EditText( (0, 71, width, 44), calibrateModeStrings['group4.markInput'], callback=self.updateCalibrateMode, continuous=False) # --- view = DefconAppKitTopAnchoredNSView.alloc().init() view.addSubview_(self.cm.getNSView()) view.setFrame_(((0, 0), (width + 10, height * 4 - 23))) self.cm.setPosSize((0, 0, width + 10, height * 4 - 22)) self.w.scrollView = ScrollView((5, 10, width + 10, -41), view, drawsBackground=False, hasHorizontalScroller=False) self.w.scrollView.getNSScrollView().setBorderType_(NSNoBorder) # NSScrollElasticityNone self.w.scrollView.getNSScrollView().setVerticalScrollElasticity_(1) self.w.scrollView.show(self.calibrateMode) # -- Footer -- self.w.footer = Group((10, -32, -10, -10)) self.w.footer.calibrateModeCheck = CheckBox( (0, 0, 200, -0), "Calibration Mode", callback=self.calibrateModeCallback, value=self.calibrateMode) self.w.footer.textSizeLabel = TextBox((200, 2, 100, -0), "Text Size") self.w.footer.textSize = EditText((260, 0, 35, -0), self.textSize, callback=self.textSizeCallback, continuous=False, formatter=intPosMinOneNumFormatter) self.w.footer.lineHeightLabel = TextBox((310, 2, 100, -0), "Line Height") self.w.footer.lineHeight = EditText((385, 0, 35, -0), self.lineHeight, callback=self.lineHeightCallback, continuous=False, formatter=integerNumFormatter) self.w.footer.extraSidebearingsLabel = TextBox((436, 2, 180, -0), "Extra Sidebearings") self.w.footer.extraSidebearingsChar = TextBox((592, 2, 20, -0), "&") self.w.footer.extraSidebearingLeft = EditText( (557, 0, 35, -0), self.extraSidebearings[0], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.footer.extraSidebearingRight = EditText( (604, 0, 35, -0), self.extraSidebearings[1], callback=self.extraSidebearingsCallback, continuous=False, formatter=intPosMinZeroNumFormatter) self.w.footer.extraGlyphsLabel = TextBox((655, 2, 180, -0), "Extra Glyphs") self.w.footer.extraGlyphs = EditText((739, 0, -0, -0), self.extraGlyphs, callback=self.extraGlyphsCallback, continuous=False) # trigger the initial state and contents of the window self.extraGlyphsCallback() # calls self.updateExtensionWindow() self.w.bind("close", self.windowClose) self.w.open() self.w.makeKey() def calibrateModeCallback(self, sender): self.calibrateMode = not self.calibrateMode self.w.fontList.show(not sender.get()) self.w.scrollView.show(self.calibrateMode) self.updateExtensionWindow() def textSizeCallback(self, sender): try: # in case the user submits an empty field self.textSize = int(sender.get()) except Exception: # reset to the previous value NSBeep() self.sender.set(self.textSize) self.w.lineView.setPointSize(self.textSize) def lineHeightCallback(self, sender): try: self.lineHeight = int(sender.get()) except Exception: NSBeep() self.sender.set(self.lineHeight) self.w.lineView.setLineHeight(self.lineHeight) def extraSidebearingsCallback(self, sender): left = self.w.footer.extraSidebearingLeft right = self.w.footer.extraSidebearingRight try: self.extraSidebearings = [int(left.get()), int(right.get())] except Exception: NSBeep() left.set(self.extraSidebearings[0]) right.set(self.extraSidebearings[1]) self.extraGlyphsCallback() # calls self.updateExtensionWindow() def extraGlyphsCallback(self, *sender): del self.extraGlyphsList[:] # empty the list self.extraGlyphs = self.w.footer.extraGlyphs.get() glyphNamesList = self.extraGlyphs.split() for gName in glyphNamesList: try: extraGlyph = self.font[gName] # must create a new glyph in order to be able to # increase the sidebearings without modifying the font newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # must use deepAppend because the extra glyph may have # components (which will cause problems to the MultiLineView) newGlyph = self.deepAppendGlyph(newGlyph, extraGlyph) newGlyph.width = extraGlyph.width except Exception: continue newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] self.extraGlyphsList.append(newGlyph) self.glyphPreviewCacheDict.clear() self.updateExtensionWindow() def windowClose(self, sender): self.font.naked().removeObserver(self, "Font.Changed") removeObserver(self, "fontWillClose") removeObserver(self, "fontResignCurrent") removeObserver(self, "currentGlyphChanged") removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") self.saveExtensionDefaults() def getCalibrateModeStrings(self): calibrateModeStringsDict = {} for i in range(1, 5): group = getattr(self.cm, "group%d" % i) calibrateModeStringsDict["group%d.baseInput" % i] = group.baseInput.get() calibrateModeStringsDict["group%d.markInput" % i] = group.markInput.get() return calibrateModeStringsDict def saveExtensionDefaults(self): setExtensionDefault("%s.%s" % (extensionKey, "posSize"), self.w.getPosSize()) setExtensionDefault("%s.%s" % (extensionKey, "textSize"), self.textSize) setExtensionDefault("%s.%s" % (extensionKey, "lineHeight"), self.lineHeight) setExtensionDefault("%s.%s" % (extensionKey, "extraSidebearings"), self.extraSidebearings) setExtensionDefault("%s.%s" % (extensionKey, "extraGlyphs"), self.extraGlyphs) setExtensionDefault("%s.%s" % (extensionKey, "calibrateMode"), self.calibrateMode) setExtensionDefault("%s.%s" % (extensionKey, "calibrateModeStrings"), self.getCalibrateModeStrings()) def _previewFill(self, info): self.Blue, self.Alpha = 0, 1 def _drawFill(self, info): self.Blue, self.Alpha = 1, 0.6 def _fontWillClose(self, info): """ Close the window when the last font is closed """ if len(AllFonts()) < 2: self.windowClose(self) self.w.close() def _currentFontChanged(self, info): self.font.naked().removeObserver(self, "Font.Changed") self.font = CurrentFont() self.font.naked().addObserver(self, "fontWasModified", "Font.Changed") self.w.lineView.setFont(self.font) self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def _currentGlyphChanged(self, info): self.updateExtensionWindow() def fontWasModified(self, info): OutputWindow().clear() self.fillAnchorsAndMarksDicts() del self.glyphNamesList[:] del self.selectedGlyphNamesList[:] self.updateExtensionWindow() def deepAppendGlyph(self, glyph, gToAppend, offset=(0, 0)): if not gToAppend.components: glyph.appendGlyph(gToAppend, offset) else: for component in gToAppend.components: # avoid traceback in the case where the selected glyph is # referencing a component whose glyph is not in the font if component.baseGlyph not in self.font.keys(): print("WARNING: %s is referencing a glyph named %s, which " "does not exist in the font." % (self.font.selection[0], component.baseGlyph)) continue compGlyph = self.font[component.baseGlyph].copy() # handle component transformations componentTransformation = component.transformation # when undoing a paste anchor or a delete anchor action, # RoboFont returns component.transformation as a list instead # of a tuple if type(componentTransformation) is list: componentTransformation = tuple(componentTransformation) # if component is skewed and/or is shifted if componentTransformation != (1, 0, 0, 1, 0, 0): matrix = componentTransformation[0:4] if matrix != (1, 0, 0, 1): # if component is skewed # ignore the original component's shifting values transformObj = Identity.transform(matrix + (0, 0)) compGlyph.transform(transformObj) # add the two tuples of offset glyph.appendGlyph( compGlyph, tuple(map(sum, zip(component.offset, offset)))) for contour in gToAppend: glyph.appendContour(contour, offset) # if the assembled glyph still has components, recursively # remove and replace them 1-by-1 by the glyphs they reference if glyph.components: nestedComponent = glyph.components[-1] # start from the end glyph.removeComponent(nestedComponent) glyph = self.deepAppendGlyph(glyph, self.font[nestedComponent.baseGlyph], nestedComponent.offset) return glyph def updateCalibrateMode(self, *sender): glyphsList = [] newLine = self.w.lineView.createNewLineGlyph() # cycle thru the UI Groups and collect the strings for i in range(1, 5): group = getattr(self.cm, "group%d" % i) baseGlyphsNamesList = group.baseInput.get().split() markGlyphsNamesList = group.markInput.get().split() # iterate thru the base+mark combinations for gBaseName, gMarkName in product(baseGlyphsNamesList, markGlyphsNamesList): newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # skip invalid glyph names try: baseGlyph = self.font[gBaseName] markGlyph = self.font[gMarkName] except Exception: continue # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, markGlyph, self.getAnchorOffsets(baseGlyph, markGlyph)) # set the advanced width dfltSidebearings = self.upm * .05 # 5% of UPM newGlyph.leftMargin = (dfltSidebearings + self.extraSidebearings[0]) newGlyph.rightMargin = (dfltSidebearings + self.extraSidebearings[1]) # append the assembled glyph to the list glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) # add line break, if both input fields have content if baseGlyphsNamesList and markGlyphsNamesList: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newLine) # update the contents of the MultiLineView self.w.lineView.set(glyphsList) def updateExtensionWindow(self): if self.calibrateMode: self.updateCalibrateMode() return # NOTE: CurrentGlyph() will return zero (its length), # so "is not None" is necessary if CurrentGlyph() is not None: self.glyph = CurrentGlyph() self.glyphNamesList = self.makeGlyphNamesList(self.glyph) self.updateListView() currentGlyphName = self.glyph.name # base glyph + accent combinations preview # first check if there's a cached glyph if currentGlyphName in self.glyphPreviewCacheDict: self.w.lineView.set( self.glyphPreviewCacheDict[currentGlyphName]) # assemble the glyphs else: glyphsList = [] for glyphNameInUIList in self.glyphNamesList: # trim the contextual portion of the UI glyph name # and keep track of it if CONTEXTUAL_ANCHOR_TAG in glyphNameInUIList: cxtTagIndex = glyphNameInUIList.find( CONTEXTUAL_ANCHOR_TAG) glyphNameCXTportion = glyphNameInUIList[cxtTagIndex:] # this line must be last! glyphNameInUIList = glyphNameInUIList[:cxtTagIndex] else: glyphNameCXTportion = '' newGlyph = RGlyph() if self.rf3: newGlyph.layer = self.layer else: newGlyph.font = newGlyph.getParent() # the glyph in the UI list is a mark if glyphNameInUIList in self.marksDict: markGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, self.glyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, markGlyph, self.getAnchorOffsets(self.glyph, markGlyph, glyphNameCXTportion)) # set the advanced width # combining marks or other glyphs with # a small advanced width if self.glyph.width < 10: newGlyph.leftMargin = self.upm * .05 # 5% of UPM newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = self.glyph.width # the glyph in the UI list is a base else: baseGlyph = self.font[glyphNameInUIList] # append base glyph newGlyph = self.deepAppendGlyph(newGlyph, baseGlyph) # append mark glyph newGlyph = self.deepAppendGlyph( newGlyph, self.glyph, self.getAnchorOffsets(baseGlyph, self.glyph)) # set the advanced width # combining marks or other glyphs with # a small advanced width if self.glyph.width < 10: newGlyph.leftMargin = self.upm * .05 newGlyph.rightMargin = newGlyph.leftMargin else: newGlyph.width = baseGlyph.width # pad the new glyph if it has too much overhang if newGlyph.leftMargin < self.upm * .15: newGlyph.leftMargin = self.upm * .05 if newGlyph.rightMargin < self.upm * .15: newGlyph.rightMargin = self.upm * .05 # add extra sidebearings newGlyph.leftMargin += self.extraSidebearings[0] newGlyph.rightMargin += self.extraSidebearings[1] # one last check for making sure the new glyph # can be displayed if not newGlyph.components: glyphsList.extend(self.extraGlyphsList) glyphsList.append(newGlyph) else: print("Combination with mark glyph %s can't be " "previewed because it contains component %s." % (glyphNameInUIList + glyphNameCXTportion, newGlyph.components[0].baseGlyph)) glyphsList.extend(self.extraGlyphsList) self.w.lineView.set(glyphsList) # add to the cache self.glyphPreviewCacheDict[currentGlyphName] = glyphsList else: self.w.lineView.set([]) def listSelectionCallback(self, sender): selectedGlyphNamesList = [] for index in sender.getSelection(): selectedGlyphNamesList.append(self.glyphNamesList[index]) self.selectedGlyphNamesList = selectedGlyphNamesList self.updateGlyphView() def updateGlyphView(self): UpdateCurrentGlyphView() def fillAnchorsAndMarksDicts(self): # reset all the dicts self.glyphPreviewCacheDict.clear() self.anchorsOnMarksDict.clear() self.anchorsOnBasesDict.clear() self.CXTanchorsOnBasesDict.clear() self.marksDict.clear() markGlyphsWithMoreThanOneAnchorTypeList = [] for glyphName in self.font.glyphOrder: glyphAnchorsList = self.font[glyphName].anchors for anchor in glyphAnchorsList: if anchor.name[0] == '_': anchorName = anchor.name[1:] # add to AnchorsOnMarks dictionary if anchorName not in self.anchorsOnMarksDict: self.anchorsOnMarksDict[anchorName] = [glyphName] else: tempList = self.anchorsOnMarksDict[anchorName] tempList.append(glyphName) self.anchorsOnMarksDict[anchorName] = tempList # add to Marks dictionary if glyphName not in self.marksDict: self.marksDict[glyphName] = anchorName else: if (glyphName not in markGlyphsWithMoreThanOneAnchorTypeList): markGlyphsWithMoreThanOneAnchorTypeList.append( glyphName) else: anchorName = anchor.name if CONTEXTUAL_ANCHOR_TAG in anchorName: # add to AnchorsOnBases dictionary if anchorName not in self.CXTanchorsOnBasesDict: self.CXTanchorsOnBasesDict[anchorName] = [ glyphName ] else: tempList = self.CXTanchorsOnBasesDict[anchorName] tempList.append(glyphName) self.CXTanchorsOnBasesDict[anchorName] = tempList else: # add to AnchorsOnBases dictionary if anchorName not in self.anchorsOnBasesDict: self.anchorsOnBasesDict[anchorName] = [glyphName] else: tempList = self.anchorsOnBasesDict[anchorName] tempList.append(glyphName) self.anchorsOnBasesDict[anchorName] = tempList if markGlyphsWithMoreThanOneAnchorTypeList: for glyphName in markGlyphsWithMoreThanOneAnchorTypeList: print("ERROR: Glyph %s has more than one type of anchor." % glyphName) def makeGlyphNamesList(self, glyph): glyphNamesList = [] markGlyphIsAbleToBeBase = False # NOTE: "if glyph" will return zero (its length), # so "is not None" is necessary if glyph is not None: # assemble the list for the UI list for anchor in glyph.anchors: anchorName = anchor.name # the glyph selected is a base if anchorName in self.anchorsOnMarksDict: glyphNamesList.extend(self.anchorsOnMarksDict[anchorName]) # the glyph selected is a mark # skips the leading underscore elif anchorName[1:] in self.anchorsOnBasesDict: glyphNamesList.extend( self.anchorsOnBasesDict[anchorName[1:]]) # the glyph selected is a base elif anchorName[0] != '_' and (anchorName in self.CXTanchorsOnBasesDict): cxtTagIndex = anchorName.find(CONTEXTUAL_ANCHOR_TAG) anchorNameNOTCXTportion = anchorName[:cxtTagIndex] anchorNameCXTportion = anchorName[cxtTagIndex:] # XXX here only the first mark glyph that has an anchor of # the kind 'anchorNameNOTCXTportion' is considered. # This is probably harmless, but... glyphName = '%s%s' % ( self.anchorsOnMarksDict[anchorNameNOTCXTportion][0], anchorNameCXTportion) glyphNamesList.append(glyphName) # for mark glyphs, test if they're able to get # other mark glyphs attached to them. # this will (correctly) prevent the UI list from including # glyph names that cannot be displayed with the current glyph if glyph.name in self.marksDict: for anchor in glyph.anchors: # the current mark glyph has anchors that # allow it to be a base for other marks if anchor.name[0] != '_': markGlyphIsAbleToBeBase = True break # remove marks from the glyph list if the # current mark glyph can't work as a base if not markGlyphIsAbleToBeBase: # iterate from the end of the list for glyphName in glyphNamesList[::-1]: if glyphName in self.marksDict: glyphNamesList.remove(glyphName) glyphNamesList.sort() return glyphNamesList def updateListView(self): self.w.fontList.set(self.glyphNamesList) def getAnchorOffsets(self, canvasGlyph, glyphToDraw, anchorNameCXTportion=''): # the current glyph is a mark if canvasGlyph.name in self.marksDict: # glyphToDraw is also a mark (mark-to-mark case) if glyphToDraw.name in self.marksDict: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] != '_': anchorName = anchor.name markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == '_' + anchorName: baseAnchor = anchor break except UnboundLocalError: continue # glyphToDraw is not a mark else: # pick the (mark glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name[0] == '_': anchorName = anchor.name[1:] markAnchor = anchor break # pick the (base glyph) anchor to draw on for anchor in glyphToDraw.anchors: try: if anchor.name == anchorName: baseAnchor = anchor break except UnboundLocalError: continue try: offsetX = markAnchor.x - baseAnchor.x offsetY = markAnchor.y - baseAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 # the current glyph is a base else: try: anchorName = self.marksDict[glyphToDraw.name] except KeyError: anchorName = None if anchorName: # pick the (base glyph) anchor to draw on for anchor in canvasGlyph.anchors: if anchor.name == anchorName + anchorNameCXTportion: baseAnchor = anchor break # pick the (mark glyph) anchor to draw on for anchor in glyphToDraw.anchors: if anchor.name == '_' + anchorName: markAnchor = anchor break try: offsetX = baseAnchor.x - markAnchor.x offsetY = baseAnchor.y - markAnchor.y except UnboundLocalError: offsetX = 0 offsetY = 0 return (offsetX, offsetY) def _drawGlyphs(self, info): """ draw stuff in the glyph window view """ translateBefore = (0, 0) for glyphName in self.selectedGlyphNamesList: # trim the contextual portion of the UI glyph name # and keep track of it if CONTEXTUAL_ANCHOR_TAG in glyphName: cxtTagIndex = glyphName.find(CONTEXTUAL_ANCHOR_TAG) glyphNameCXTportion = glyphName[cxtTagIndex:] glyphName = glyphName[:cxtTagIndex] # this line must be last! else: glyphNameCXTportion = '' glyphToDraw = self.font[glyphName] # determine the offset of the anchors offset = self.getAnchorOffsets(self.glyph, glyphToDraw, glyphNameCXTportion) # set the offset of the drawing translate(offset[0] - translateBefore[0], offset[1] - translateBefore[1]) # record the shift amounts (these are needed for resetting the # drawing position when more than one mark is selected on the list) translateBefore = offset # set the fill & stroke fill(0, 0, self.Blue, self.Alpha) strokeWidth(None) # draw it mojoPen = MojoDrawingToolsPen(glyphToDraw, self.font) glyphToDraw.draw(mojoPen) mojoPen.draw()
class Labeler(object): labelCltrIndex = 0 def __init__(self): # init window self.w = FloatingWindow((PLUGIN_WIDTH, 300), 'labeler.py') self.jumpingY = MARGIN_VER self.w.labelCtrl_0 = LabelCtrl((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['EditTextRegularHeight']), index=self.labelCltrIndex, callback=self.labelCtrlsCallback) self.jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER self.w.separation = HorizontalLine((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness'])) self.w.clearButton = SquareButton((MARGIN_HOR, self.jumpingY+vanillaControlsSize['HorizontalLineThickness'] + MARGIN_VER, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight']), 'Clear Glyph Labels', sizeStyle='small', callback=self.clearButtonCallback) # resize window self._computeWindowHeight() self.w.resize(PLUGIN_WIDTH, self.windowHeight) # open window self.w.open() def _computeWindowHeight(self): self.windowHeight = self.jumpingY + vanillaControlsSize['ButtonRegularHeight'] + vanillaControlsSize['HorizontalLineThickness'] + MARGIN_VER*2 def _updateCtrlsPos(self): self.w.separation.setPosSize((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness'])) self.w.clearButton.setPosSize((MARGIN_HOR, self.jumpingY+vanillaControlsSize['HorizontalLineThickness'] + MARGIN_VER, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight'])) def labelCtrlsCallback(self, sender): lastAction, labelName = sender.get() if lastAction == 'attach': attachLabelToSelectedPoints(labelName) elif lastAction == 'add': self.labelCltrIndex += 1 labelCtrl = LabelCtrl((MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['EditTextRegularHeight']), index=self.labelCltrIndex, callback=self.labelCtrlsCallback) setattr(self.w, 'labelCtrl_{:d}'.format(labelCtrl.index), labelCtrl) self.jumpingY += vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER self._computeWindowHeight() self._updateCtrlsPos() self.w.resize(PLUGIN_WIDTH, self.windowHeight) elif lastAction == 'subtract': delattr(self.w, 'labelCtrl_{:d}'.format(labelCtrl.index)) self.jumpingY -= vanillaControlsSize['EditTextRegularHeight'] + MARGIN_VER self.w.resize(PLUGIN_WIDTH, self.windowHeight) # else: # None def clearButtonCallback(self, sender): myGlyph = CurrentGlyph() for eachContour in myGlyph: for eachPt in eachContour.points: if eachPt.name is not None: eachPt.name = None myGlyph.update()
class Rotator(BaseWindowController): _title = 'Rotator' _width = 180 _frame = 8 _height = 249 _row = 24 _padding = 16 _gutter = 8 _lineHeight = 20 _color = NSColor.colorWithCalibratedRed_green_blue_alpha_( 0.0, 0.5, 1.0, .8) _columns = 3 _col_width = (_width - ((_columns - 1) * _gutter)) / _columns _col_0 = _frame _col_1 = _frame + _col_width + _gutter _col_2 = _frame + 2 * _col_width + 2 * _gutter _col_3 = _frame + 3 * _col_width + 3 * _gutter xValue = getExtensionDefault('%s.%s' % (rotatorDefaults, 'x'), 0) yValue = getExtensionDefault('%s.%s' % (rotatorDefaults, 'y'), 0) steps = getExtensionDefault('%s.%s' % (rotatorDefaults, 'steps'), 12) lock = getExtensionDefault('%s.%s' % (rotatorDefaults, 'lock'), False) rounding = getExtensionDefault('%s.%s' % (rotatorDefaults, 'round'), False) angle = 360.0 / steps def __init__(self): self.w = FloatingWindow((self._width + 2 * self._frame, self._height), self._title) # ---------- # text boxes # ---------- textBoxY = self._padding self.w.steps_label = TextBox( (self._col_0, textBoxY, self._col_width, self._lineHeight), 'Steps', alignment='right') if rfVersion >= 3.4: self.w.steps_text = NumberEditText( (self._col_1, textBoxY - 2, self._col_width, self._lineHeight), self.steps, callback=self.angleCallback, allowFloat=False, allowNegative=False, allowEmpty=False, minimum=1, decimals=0, continuous=True) else: self.w.steps_text = EditText( (self._col_1, textBoxY - 2, self._col_width, self._lineHeight), self.steps, callback=self.angleCallback, continuous=True) textBoxY += (self._row) self.w.xValue_label = TextBox( (self._col_0, textBoxY, self._col_width, self._lineHeight), 'x', alignment='right') if rfVersion >= 3.4: self.w.xValue_text = NumberEditText( (self._col_1, textBoxY - 2, self._col_width, self._lineHeight), self.xValue, callback=self.xCallback, allowFloat=True, decimals=0) else: self.w.xValue_text = EditText( (self._col_1, textBoxY - 2, self._col_width, self._lineHeight), self.xValue, callback=self.xCallback) textBoxY += (self._row) self.w.yValue_label = TextBox( (self._col_0, textBoxY, self._col_width, self._lineHeight), 'y', alignment='right') if rfVersion >= 3.4: self.w.yValue_text = NumberEditText( (self._col_1, textBoxY - 2, self._col_width, self._lineHeight), self.yValue, callback=self.yCallback, allowFloat=True, decimals=0) else: self.w.yValue_text = EditText( (self._col_1, textBoxY - 2, self._col_width, self._lineHeight), self.yValue, callback=self.yCallback) textBoxY += (self._row) self.w.angle_label = TextBox( (self._col_0, textBoxY, self._col_width, self._lineHeight), 'Angle', alignment='right') self.w.angleResult = TextBox( (self._col_1, textBoxY, self._col_width, self._lineHeight), u'%s°' % self.niceAngleString(self.angle)) textBoxY += (self._row) textBoxY += (self._row * .25) self.w.line = HorizontalLine( (self._gutter, textBoxY, -self._gutter, 0.5)) textBoxY += (self._row * .25) self.w.lock_checkbox = CheckBox( (self._col_1 - 25, textBoxY, -self._gutter, self._lineHeight), 'Lock Center', value=self.lock, callback=self.lockCallback) textBoxY += (self._row) self.w.rounding_checkbox = CheckBox( (self._col_1 - 25, textBoxY, -self._gutter, self._lineHeight), 'Round Result', value=self.rounding, callback=self.roundingCallback) textBoxY += (self._row) # ------- # buttons # ------- self.w.color = ColorWell( (self._col_0, textBoxY, -self._gutter, 2 * self._lineHeight), color=getExtensionDefaultColor( '%s.%s' % (rotatorDefaults, 'color'), self._color), callback=self.colorCallback) textBoxY += (self._row) self.w.buttonRotate = Button( (self._col_0, -30, -self._gutter, self._lineHeight), 'Rotate', callback=self.rotateCallback) self.setUpBaseWindowBehavior() addObserver(self, 'updateOrigin', 'mouseDragged') addObserver(self, 'drawRotationPreview', 'drawBackground') addObserver(self, 'drawSolidPreview', 'drawPreview') self.w.setDefaultButton(self.w.buttonRotate) self.w.open() def drawRotationPreview(self, info): # draw preview glyph outline = self.getRotatedGlyph() pen = CocoaPen(None) self.w.color.get().set() outline.draw(pen) pen.path.setLineWidth_(info['scale'] * .5) pen.path.stroke() # draw crosshair ch_pen = CocoaPen(None) center_x = self.xValue center_y = self.yValue strokeColor = NSColor.redColor() strokeColor.set() ch_pen.moveTo((center_x - 10, center_y)) ch_pen.lineTo((center_x + 10, center_y)) ch_pen.endPath() ch_pen.moveTo((center_x, center_y - 10)) ch_pen.lineTo((center_x, center_y + 10)) ch_pen.endPath() ch_pen.path.setLineWidth_(info['scale']) ch_pen.path.stroke() def drawSolidPreview(self, info): outline = self.getRotatedGlyph() pen = CocoaPen(None) outline.draw(pen) defaultPreviewColor = getDefault('glyphViewPreviewFillColor') fillColor = NSColor.colorWithCalibratedRed_green_blue_alpha_( *defaultPreviewColor) fillColor.set() pen.path.fill() def xCallback(self, sender): xValue = sender.get() try: self.xValue = int(xValue) except ValueError: xValue = self.xValue self.w.xValue_text.set(xValue) UpdateCurrentGlyphView() def yCallback(self, sender): yValue = sender.get() try: self.yValue = int(yValue) except ValueError: yValue = self.yValue self.w.yValue_text.set(yValue) UpdateCurrentGlyphView() def lockCallback(self, sender): self.lock = not self.lock self.saveDefaults() def roundingCallback(self, sender): self.rounding = not self.rounding self.saveDefaults() def angleCallback(self, sender): try: stepValue = float(sender.get()) stepValue = int(round(stepValue)) except ValueError: stepValue = self.steps self.w.steps_text.set(self.steps) self.steps = stepValue if abs(stepValue) < 2: self.angle = 90.0 elif stepValue == 0: self.angle = 0 else: self.angle = 360 / stepValue self.w.angleResult.set(u'%s°' % self.niceAngleString(self.angle)) UpdateCurrentGlyphView() def niceAngleString(self, angle): angleResultString = u'%.2f' % angle if angleResultString.endswith('.00'): angleResultString = angleResultString[0:-3] return angleResultString def colorCallback(self, sender): setExtensionDefaultColor('%s.%s' % (rotatorDefaults, 'color'), sender.get()) UpdateCurrentGlyphView() def windowCloseCallback(self, sender): removeObserver(self, 'mouseUp') removeObserver(self, 'drawBackground') removeObserver(self, 'drawPreview') UpdateCurrentGlyphView() self.saveDefaults() def updateOrigin(self, info): if not self.lock: self.xValue, self.yValue = int(round(info['point'].x)), int( round(info['point'].y)) self.w.xValue_text.set(self.xValue) self.w.yValue_text.set(self.yValue) def saveDefaults(self): setExtensionDefault('%s.%s' % (rotatorDefaults, 'x'), self.xValue) setExtensionDefault('%s.%s' % (rotatorDefaults, 'y'), self.yValue) setExtensionDefault('%s.%s' % (rotatorDefaults, 'steps'), self.steps) setExtensionDefault('%s.%s' % (rotatorDefaults, 'lock'), self.lock) setExtensionDefault('%s.%s' % (rotatorDefaults, 'round'), self.rounding) def rotateCallback(self, sender): glyph = CurrentGlyph() glyph.prepareUndo('Rotator') rotatedGlyph = self.getRotatedGlyph() glyph.appendGlyph(rotatedGlyph) glyph.performUndo() self.saveDefaults() glyph.changed() def getRotatedGlyph(self): glyph = CurrentGlyph() x = int(self.w.xValue_text.get()) y = int(self.w.yValue_text.get()) steps = self.steps angle = self.angle center = (x, y) rotation_result_glyph = RGlyph() rotation_step_glyph = RGlyph() pen = rotation_step_glyph.getPointPen() contourList = [] for idx, contour in enumerate(glyph): if contour.selected: contourList.append(idx) # if nothing is selected, the whole glyph will be rotated. if len(contourList) == 0: for idx, contour in enumerate(glyph): contourList.append(idx) for contour in contourList: glyph[contour].drawPoints(pen) # don't draw the original shape again stepCount = steps - 1 if steps < 2: # solution/hack for 1-step rotation stepCount = 1 angle = 90 for i in range(stepCount): rotation_step_glyph.rotateBy(angle, center) rotation_result_glyph.appendGlyph(rotation_step_glyph) if self.rounding: rotation_result_glyph.round() return rotation_result_glyph
class Script(object): def __init__(self): self.valuesPrefsKey = prefsKey + ".delta." + basename( Glyphs.font.filepath) # UI metrics spacing = 8 height = 22 # calculate window height minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing))) # create UI window self.w = FloatingWindow(minWinSize, "Adjust sidebearings", minSize=minWinSize, maxSize=(500, 500), autosaveName=prefsKey + ".win") # layout UI controls y = 16 self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:") y += height + spacing inputWidth = 64 for master in Glyphs.font.masters: setattr(self.w, "deltaLabel%s" % master.id, TextBox((16, y, -16 - inputWidth, height), master.name)) setattr(self.w, "deltaInput%s" % master.id, EditText((-16 - inputWidth, y, -16, height), "16")) # print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id)) y += height + spacing self.w.submitButton = Button((16, -16 - height, -16, height), "Adjust all sidebearings", callback=self.onSubmit) # finalize UI self.w.setDefaultButton(self.w.submitButton) self.loadPreferences() self.w.bind("close", self.savePreferences) # make sure window is large enough to show all masters x, y, w, h = self.w.getPosSize() if w < minWinSize[0] and h < minWinSize[1]: self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False) elif w < minWinSize[0]: self.w.setPosSize((x, y, minWinSize[0], h), animate=False) elif h < minWinSize[1]: self.w.setPosSize((x, y, w, minWinSize[1]), animate=False) self.w.open() self.w.makeKey() def getDeltaInputForMaster(self, masterId): return getattr(self.w, "deltaInput%s" % masterId) def loadPreferences(self): try: Glyphs.registerDefault(self.valuesPrefsKey, []) for t in Glyphs.defaults[self.valuesPrefsKey]: try: masterId, value = t self.getDeltaInputForMaster(masterId).set(value) except: pass except: print("failed to load preferences") def savePreferences(self, sender): try: values = [] for master in Glyphs.font.masters: values.append( (master.id, self.getDeltaInputForMaster(master.id).get())) Glyphs.defaults[self.valuesPrefsKey] = values except: print("failed to save preferences") def onSubmit(self, sender): try: sender.enable(False) if performFontChanges(self.action1): self.w.close() except Exception, e: Glyphs.showMacroWindow() print("error: %s" % e) finally:
class CornerController: def __init__(self): self.modifiedGlyph = None self.w = FloatingWindow((400, 170), "Corner Tool") self.w.getNSWindow().setBackgroundColor_(NSColor.whiteColor()) self.modes = ["Break", "Build", "Pit"] self.objectTypes = {"Build": "Segment", "Break": "Corner point", "Pit": "Corner point"} self.parameters = { "radius": VanillaSingleValueParameter("radius", 20, (-200, 200), numType="int"), "roundness": VanillaSingleValueParameter("roundness", 1.25, (0, 4), numType="float"), "depth": VanillaSingleValueParameter("depth", 30, (-100, 100), numType="int"), "breadth": VanillaSingleValueParameter("breadth", 30, (0, 150), numType="int"), "bottom": VanillaSingleValueParameter("bottom", 5, (0, 40), numType="int"), } self.currentMode = "Break" self.previewGlyph = None self.w.modes = RadioGroup((15, 15, 70, -15), self.modes, callback=self.changeMode) for i, mode in enumerate(self.modes): setattr(self.w, mode, Group((120, 15, -15, -15))) modeGroup = getattr(self.w, mode) modeGroup.apply = GradientButton((-35, 0, -0, -0), title=u">", callback=self.apply) modeGroup.infoBox = Box((0, 0, -50, 35)) modeGroup.info = TextBox((10, 8, -50, 20), "No selection") if i > 0: modeGroup.show(False) self.w.Break.radius = ParameterSliderTextInput( self.parameters["radius"], (0, 60, -25, 25), title="Radius", callback=self.makePreviewGlyph ) self.w.Break.roundness = ParameterSliderTextInput( self.parameters["roundness"], (0, 95, -25, 25), title="Roundness", callback=self.makePreviewGlyph ) self.w.Pit.depth = ParameterSliderTextInput( self.parameters["depth"], (0, 50, -25, 25), title="Depth", callback=self.makePreviewGlyph ) self.w.Pit.breadth = ParameterSliderTextInput( self.parameters["breadth"], (0, 80, -25, 25), title="Breadth", callback=self.makePreviewGlyph ) self.w.Pit.bottom = ParameterSliderTextInput( self.parameters["bottom"], (0, 110, -25, 25), title="bottom", callback=self.makePreviewGlyph ) addObserver(self, "preview", "draw") addObserver(self, "preview", "drawInactive") addObserver(self, "previewSolid", "drawPreview") addObserver(self, "makePreviewGlyph", "mouseDown") addObserver(self, "makePreviewGlyph", "mouseDragged") addObserver(self, "makePreviewGlyph", "keyDown") addObserver(self, "makePreviewGlyph", "keyUp") addObserver(self, "setControls", "mouseUp") addObserver(self, "setControls", "selectAll") addObserver(self, "setControls", "deselectAll") addObserver(self, "setControls", "currentGlyphChanged") self.w.bind("close", self.windowClose) self.setControls() self.w.open() def changeMode(self, sender): index = sender.get() previousModeGroup = getattr(self.w, self.currentMode) previousModeGroup.show(False) self.currentMode = self.modes[index] modeGroup = getattr(self.w, self.currentMode) modeGroup.show(True) self.setControls() def setControls(self, notification=None): mode = self.currentMode selection = self.getSelection() modeGroup = getattr(self.w, mode) if not len(selection): modeGroup.apply.enable(False) modeGroup.info.set("No selection (%ss)" % (self.objectTypes[mode].lower())) elif len(selection): modeGroup.apply.enable(True) info = "%s valid %s" % (len(selection), self.objectTypes[mode].lower()) if len(selection) > 1: info += "s" modeGroup.info.set(info) self.makePreviewGlyph() def getSelection(self, notification=None): glyph = CurrentGlyph() if len(glyph.selection) == 0: return [] elif len(glyph.selection) > 0: iG = IntelGlyph(glyph) if self.currentMode == "Build": selection = iG.getSelection(True) elif self.currentMode in ["Break", "Pit"]: selection = [ point for point in iG.getSelection() if (point.segmentType is not None) and (abs(point.turn()) > pi / 18) ] return selection def preview(self, notification): sc = notification["scale"] if self.previewGlyph is not None: self.previewGlyph.drawPreview( sc, styleFill=True, showNodes=False, strokeWidth=2, fillColor=cornerOutlineSoftColor, strokeColor=cornerOutlineStrongColor, ) def previewSolid(self, notification): sc = notification["scale"] if self.previewGlyph is not None: self.previewGlyph.drawPreview(sc, plain=True) def makePreviewGlyph(self, sender=None): if (sender is not None) and isinstance(sender, dict): if sender.has_key("notificationName") and sender["notificationName"] == "mouseDragged": g = sender["glyph"] if not len(g.selection): return self.previewGlyph = self.makeCornerGlyph() UpdateCurrentGlyphView() def makeCornerGlyph(self, sender=None): mode = self.currentMode if mode == "Build": cornerGlyph = self.buildCorners() elif mode == "Break": cornerGlyph = self.breakCorners() elif mode == "Pit": cornerGlyph = self.pitCorners() return cornerGlyph def buildCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) for contour in iG: segments = contour.collectSegments()["selection"] l = len(segments) lines, curves = self.checkComposition(segments) if l > 1 and lines and curves: segments = [segment for segment in segments if len(segment) == 4] elif l > 1 and lines and not curves: segments = segments[:1] + segments[-1:] for segment in reversed(segments): contour.buildCorner(segment) return iG def breakCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) radius = self.parameters["radius"].get() roundness = self.parameters["roundness"].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.breakCorner(point, radius, velocity=roundness) contour.correctSmoothness() return iG def pitCorners(self): g = CurrentGlyph() iG = IntelGlyph(g) depth = self.parameters["depth"].get() breadth = self.parameters["breadth"].get() bottom = self.parameters["bottom"].get() for contour in iG: selection = contour.getSelection() for point in selection: contour.pitCorner(point, depth, breadth, bottom) contour.removeOverlappingPoints() contour.correctSmoothness() return iG def apply(self, sender): targetGlyph = CurrentGlyph() modifiedGlyph = self.makeCornerGlyph() targetGlyph.prepareUndo("un.round") targetGlyph.clearContours() for p in targetGlyph.selection: p.selected = False pen = targetGlyph.getPointPen() modifiedGlyph.drawPoints(pen) targetGlyph.performUndo() targetGlyph.update() def checkComposition(self, segmentsList): lines = 0 curves = 0 for segment in segmentsList: if len(segment) == 2: lines += 1 elif len(segment) == 4: curves += 1 return lines, curves def windowClose(self, notification): removeObserver(self, "draw") removeObserver(self, "drawInactive") removeObserver(self, "drawPreview") removeObserver(self, "mouseUp") removeObserver(self, "mouseDown") removeObserver(self, "mouseDragged") removeObserver(self, "keyDown") removeObserver(self, "keyUp") removeObserver(self, "selectAll") removeObserver(self, "deselectAll") removeObserver(self, "currentGlyphChanged")
class ItalicBowtie(Subscriber, WindowController): DEFAULTKEY = 'com.fontbureau.italicBowtie' DEFAULTKEY_REFERENCE = DEFAULTKEY + '.drawReferenceGlyph' italicSlantOffsetKey = 'com.typemytype.robofont.italicSlantOffset' debug = True def build(self): glyphEditor = self.getGlyphEditor() self.container = glyphEditor.extensionContainer( identifier="com.roboFont.ItalicBowtie.background", location="background", clear=True) self.crossHeightLayer = self.container.appendLineSublayer( strokeWidth=1, strokeColor=(.2, .1, .5, .5), strokeDash=(2, )) fillColor = (.2, .1, .5, .05) strokeColor = (.2, .1, .5, .5) strokeWidth = 0.5 self.leftBowtieLayer = self.container.appendPathSublayer( fillColor=fillColor, strokeColor=strokeColor, strokeWidth=strokeWidth) self.rightBowtieLayer = self.container.appendPathSublayer( fillColor=fillColor, strokeColor=strokeColor, strokeWidth=strokeWidth) self.w = FloatingWindow((325, 250), "Italic Bowtie") self.populateWindow() def started(self): self.w.open() def destroy(self): self.container.clearSublayers() def glyphEditorGlyphDidChange(self, info): self.updateBowtie() def glyphEditorDidSetGlyph(self, info): self.updateBowtie() def populateWindow(self): y = 10 x = 10 self.w.italicAngleLabel = TextBox((x, y + 4, 100, 22), 'Italic Angle', sizeStyle="small") x += 100 self.w.italicAngle = EditText((x, y, 40, 22), '', sizeStyle="small", callback=self.calcItalicCallback) y += 30 x = 10 self.w.crossHeightLabel = TextBox((x, y + 4, 95, 22), 'Cross Height', sizeStyle="small") x += 100 self.w.crossHeight = EditText((x, y, 40, 22), '', sizeStyle="small", callback=self.calcItalicCallback) x += 50 self.w.crossHeightSetUC = Button((x, y, 65, 22), 'Mid UC', sizeStyle="small", callback=self.calcItalicCallback) x += 75 self.w.crossHeightSetLC = Button((x, y, 65, 22), 'Mid LC', sizeStyle="small", callback=self.calcItalicCallback) y += 30 x = 10 self.w.italicSlantOffsetLabel = TextBox((x, y + 4, 100, 22), 'Italic Slant Offset', sizeStyle="small") x += 100 self.w.italicSlantOffset = EditText((x, y, 40, 22), '', sizeStyle="small", callback=self.calcItalicCallback) x += 60 y += 30 x = 10 self.w.refresh = Button((x, y, 140, 22), u'Values from Current', callback=self.refresh, sizeStyle="small") y += 30 self.w.fontSelection = RadioGroup((x, y, 120, 35), ['Current Font', 'All Fonts'], sizeStyle="small") self.w.fontSelection.set(0) x += 160 self.w.glyphSelection = RadioGroup( (x, y, 120, 55), ['Current Glyph', 'Selected Glyphs', 'All Glyphs'], sizeStyle="small") self.w.glyphSelection.set(0) y += 60 x = 10 self.w.setInFont = Button((x, y, 140, 22), 'Set Font Italic Values', sizeStyle="small", callback=self.setInFontCallback) x += 160 self.w.italicize = Button((x, y, 140, 22), 'Italicize Glyphs', sizeStyle="small", callback=self.italicizeCallback) y += 25 self.w.makeReferenceLayer = CheckBox( (x, y, 145, 22), 'Make Reference Layer', value=getExtensionDefault(self.DEFAULTKEY_REFERENCE, False), sizeStyle="small", callback=self.makeReferenceLayerCallback) x = 10 self.refresh() if self.getItalicAngle() == 0 and CurrentFont() is not None: self.setCrossHeight((CurrentFont().info.capHeight or 0) / 2) def makeReferenceLayerCallback(self, sender): setExtensionDefault(self.DEFAULTKEY_REFERENCE, sender.get()) def italicizeCallback(self, sender=None): italicAngle = self.getItalicAngle() italicSlantOffset = self.getItalicSlantOffset() if self.w.fontSelection.get() == 0: if CurrentFont() is not None: fonts = [CurrentFont()] else: fonts = [] else: fonts = AllFonts() if self.w.glyphSelection.get() == 0 and CurrentGlyph() is not None: glyphs = [CurrentGlyph()] elif self.w.glyphSelection.get() == 1: glyphs = [] for f in fonts: for gname in CurrentFont().selection: if gname in f: glyphs.append(f[gname]) else: glyphs = [] for f in fonts: for g in f: glyphs.append(g.name) for glyph in glyphs: italicize(glyph, italicAngle, offset=italicSlantOffset, shouldMakeReferenceLayer=self.w.makeReferenceLayer.get()) def refresh(self, sender=None): f = CurrentFont() if f: italicSlantOffset = f.lib.get(self.italicSlantOffsetKey) or 0 italicAngle = f.info.italicAngle or 0 crossHeight = calcCrossHeight(italicAngle=italicAngle, italicSlantOffset=italicSlantOffset) self.setItalicSlantOffset(italicSlantOffset) self.setItalicAngle(italicAngle) self.setCrossHeight(crossHeight) else: self.setItalicSlantOffset(0) self.setItalicAngle(0) self.setCrossHeight(0) self.updateBowtie() def setInFontCallback(self, sender): if self.w.fontSelection.get() == 0: if CurrentFont() is not None: fonts = [CurrentFont()] else: fonts = [] else: fonts = AllFonts() for f in fonts: with f.undo('italicBowtie'): f.info.italicAngle = self.getItalicAngle() f.lib[self.italicSlantOffsetKey] = self.getItalicSlantOffset() try: window = CurrentGlyphWindow() window.setGlyph(CurrentGlyph().naked()) except Exception: print(self.DEFAULTKEY, 'error resetting window, please refresh it') self.updateBowtie() def drawItalicBowtie(self, layer, italicAngle=0, crossHeight=0, italicSlantOffset=0, ascender=0, descender=0, xoffset=0): """ Draw an italic Bowtie. """ topBowtie = ascender topBowtieOffset = calcItalicOffset(topBowtie, italicAngle) bottomBowtie = descender bottomBowtieOffset = calcItalicOffset(bottomBowtie, italicAngle) pen = layer.getPen() pen.moveTo((xoffset, descender)) pen.lineTo( (xoffset + bottomBowtieOffset + italicSlantOffset, descender)) pen.lineTo((xoffset + topBowtieOffset + italicSlantOffset, ascender)) pen.lineTo((xoffset, ascender)) pen.closePath() def calcItalicCallback(self, sender): italicAngle = self.getItalicAngle() italicSlantOffset = self.getItalicSlantOffset() if sender == self.w.crossHeightSetUC and CurrentFont() is not None: crossHeight = (CurrentFont().info.capHeight or 0) / 2.0 sender = self.w.crossHeight elif sender == self.w.crossHeightSetLC and CurrentFont() is not None: crossHeight = (CurrentFont().info.xHeight or 0) / 2.0 sender = self.w.crossHeight else: crossHeight = self.getCrossHeight() if sender == self.w.italicAngle or sender == self.w.italicSlantOffset: self.setCrossHeight( calcCrossHeight(italicAngle=italicAngle, italicSlantOffset=italicSlantOffset)) elif sender == self.w.crossHeight: self.setItalicSlantOffset( calcItalicSlantOffset(italicAngle=italicAngle, crossHeight=crossHeight)) self.setCrossHeight(crossHeight) self.updateBowtie() def updateBowtie(self): glyph = CurrentGlyph() italicAngle = self.getItalicAngle() italicSlantOffset = self.getItalicSlantOffset() crossHeight = self.getCrossHeight() ascender = glyph.font.info.ascender descender = glyph.font.info.descender italicSlantOffsetOffset = italicSlantOffset for xoffset, layer in [(0, self.leftBowtieLayer), (glyph.width, self.rightBowtieLayer)]: self.drawItalicBowtie(layer=layer, italicAngle=italicAngle, crossHeight=crossHeight, ascender=ascender, descender=descender, italicSlantOffset=italicSlantOffsetOffset, xoffset=xoffset) # crossheight self.crossHeightLayer.setStartPoint((0, crossHeight)) self.crossHeightLayer.setEndPoint((glyph.width, crossHeight)) ################################ ################################ ################################ def getItalicAngle(self): a = self.w.italicAngle.get() try: return float(a) except ValueError: return 0 def getItalicSlantOffset(self): a = self.w.italicSlantOffset.get() try: return float(a) except ValueError: return 0 def getCrossHeight(self): a = self.w.crossHeight.get() try: return float(a) except ValueError: print('error', a) return 0 def setItalicAngle(self, italicAngle): self.w.italicAngle.set(f'{italicAngle}') def setItalicSlantOffset(self, italicSlantOffset): self.w.italicSlantOffset.set(f'{italicSlantOffset}') def setCrossHeight(self, crossHeight): self.w.crossHeight.set(f'{crossHeight}')
class FontNanny(object): def __init__(self, font): self.w = FloatingWindow((185, 370), "Font Nanny") y = 5 self.w.info = TextBox((10, y, 180, 14), text="Glyph Checks (all Glyphs):", sizeStyle="small") y += 20 self.w.check1 = SquareButton((10, y, 145, 20), "Unicode values", callback=self.perform, sizeStyle="small") self.w.color1 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 0, 0, 1, 0.3)) self.w.color1.enable(0) y += 20 self.w.check2 = SquareButton((10, y, 145, 20), "Contour Count", callback=self.perform, sizeStyle="small") self.w.color2 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 5, 0, 0, 0.4)) self.w.color2.enable(0) y += 25 self.w.info2 = TextBox((10, y, 180, 14), text="Outline Checks (all Glyphs):", sizeStyle="small") y += 20 self.w.check3 = SquareButton((10, y, 145, 20), "Stray Points", callback=self.perform, sizeStyle="small") self.w.color3 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .7, .0, .7, .9)) self.w.color3.enable(0) y += 20 self.w.check4 = SquareButton((10, y, 145, 20), "Small Contours", callback=self.perform, sizeStyle="small") self.w.color4 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 0, .8, 0, 0.9)) self.w.color4.enable(0) y += 20 self.w.check5 = SquareButton((10, y, 145, 20), "Open Contours", callback=self.perform, sizeStyle="small") self.w.color5 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .4, .4, .4, 0.8)) self.w.color5.enable(0) y += 20 self.w.check6 = SquareButton((10, y, 145, 20), "Duplicate Contours", callback=self.perform, sizeStyle="small") self.w.color6 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, 0, 0, 0.6)) self.w.color6.enable(0) y += 20 self.w.check7 = SquareButton((10, y, 145, 20), "Extreme points", callback=self.perform, sizeStyle="small") self.w.color7 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 0, .9, 0, 0.6)) self.w.color7.enable(0) y += 20 self.w.check8 = SquareButton((10, y, 145, 20), "Unnecessary Points", callback=self.perform, sizeStyle="small") self.w.color8 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, .0, 1, 0.8)) self.w.color8.enable(0) y += 20 self.w.check9 = SquareButton((10, y, 145, 20), "Unnecessary Handles", callback=self.perform, sizeStyle="small") self.w.color9 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .4, .0, .0, .3)) self.w.color9.enable(0) y += 20 self.w.check10 = SquareButton((10, y, 145, 20), "Overlapping Points", callback=self.perform, sizeStyle="small") self.w.color10 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .0, .0, .6, .3)) self.w.color10.enable(0) y += 20 self.w.check11 = SquareButton((10, y, 145, 20), "Points near vert. Metrics", callback=self.perform, sizeStyle="small") self.w.color11 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, 1, .0, .2)) self.w.color11.enable(0) y += 20 self.w.check12 = SquareButton((10, y, 145, 20), "Complex Curves", callback=self.perform, sizeStyle="small") self.w.color12 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 1, 0, 1)) self.w.color12.enable(0) y += 20 self.w.check13 = SquareButton((10, y, 145, 20), "Crossed handles", callback=self.perform, sizeStyle="small") self.w.color13 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( 1, 0, .4, .2)) self.w.color13.enable(0) y += 20 self.w.check14 = SquareButton((10, y, 145, 20), "Straight Lines", callback=self.perform, sizeStyle="small") self.w.color14 = ColorWell( (155, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( .0, .6, .0, .3)) self.w.color14.enable(0) y += 30 self.w.horizontalLine = HorizontalLine((10, y - 5, 165, 1)) self.w.clearGlyphMarksButton = SquareButton( (10, y, 90, 20), "Clear all marks", callback=self.clearGlyphMarks, sizeStyle="small") self.w.colorClearGlyphMarks = ColorWell( (100, y, 20, 20), color=NSColor.colorWithCalibratedRed_green_blue_alpha_(1, 1, 1, 1)) self.w.colorClearGlyphMarks.enable(0) self.w.closeWin = SquareButton((120, y, -10, 20), "x) close", callback=self.CloseWindow, sizeStyle="small") self.w.open() def CloseWindow(self, sender): self.w.close() def clearGlyphMarks(self, sender): font = CurrentFont() for g in font: if g.mark == (0, 0, 1, 0.3) or (5, 0, 0, 0.4) or ( .7, .0, .7, .9 ) or (0, .8, 0, 0.9) or (.4, .4, .4, 0.8) or (1, 0, 0, 0.6) or ( 0, .9, 0, 0.6) or (1, .0, 1, 0.8) or (.4, .0, .0, .3) or ( .0, .0, .6, .3) or (1, 1, .0, .2) or (1, 1, 0, 1) or ( 1, 0, .4, .2) or (.0, .6, .0, .3): g.mark = None def perform(self, sender): senderInput = sender.getTitle() f = CurrentFont() tickCount = len(CurrentFont()) progressBar = ProgressBar(title=senderInput, ticks=tickCount, label="checking all the glyphs...") tick = 0 if senderInput == "Open Contours": for glyph in f: self.testForOpenContours(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Extreme points": for glyph in f: self.testForExtremePoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Straight Lines": for glyph in f: self.testForStraightLines(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Crossed handles": for glyph in f: self.testForCrossedHandles(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Unicode values": for glyph in f: self.testUnicodeValue(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Contour Count": for glyph in f: self.testContourCount(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Duplicate Contours": for glyph in f: self.testDuplicateContours(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Small Contours": for glyph in f: self.testForSmallContours(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Complex Curves": for glyph in f: self.testForComplexCurves(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Unnecessary Points": for glyph in f: self.testForUnnecessaryPoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Overlapping Points": for glyph in f: self.testForOverlappingPoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Unnecessary Handles": for glyph in f: self.testForUnnecessaryHandles(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Stray Points": for glyph in f: self.testForStrayPoints(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() if senderInput == "Points near vert. Metrics": for glyph in f: self.testForPointsNearVerticalMetrics(glyph) tick = tick + 1 progressBar.tick(tick) progressBar.close() def testUnicodeValue(self, glyph): """ A Unicode value should appear only once per font. """ report = [] font = glyph.getParent() uni = glyph.unicode name = glyph.name # test against AGL expectedUni = AGL2UV.get(name) if expectedUni != uni: report.append("Incorrect Unicode value?: %s." % name) glyph.mark = (0, 0, 1, 0.3) # look for duplicates if uni is not None: duplicates = [] for name in sorted(font.keys()): if name == glyph.name: continue other = font[name] if other.unicode == uni: duplicates.append(name) glyph.mark = (0, 0, 1, 0.3) report.append("The Unicode for this glyph is also used by: %s" % " ".join(duplicates)) # Glyph Construction def testContourCount(self, glyph): """ There shouldn't be too many overlapping contours. """ report = [] count = len(glyph) test = glyph.copy() test.removeOverlap() if count - len(test) > 2: report.append( "This glyph has a unusally high number of overlapping contours." ) glyph.mark = (5, 0, 0, 0.4) return report def testDuplicateContours(self, glyph): """ Contours shouldn't be duplicated on each other. """ contours = {} for index, contour in enumerate(glyph): contour = contour.copy() contour.autoStartSegment() pen = DigestPointPen() contour.drawPoints(pen) digest = pen.getDigest() if digest not in contours: contours[digest] = [] contours[digest].append(index) duplicateContours = [] for digest, indexes in contours.items(): if len(indexes) > 1: duplicateContours.append(indexes[0]) glyph.mark = (1, 0, 0, 0.6) # Contours def testForSmallContours(self, glyph): """ Contours should not have an area less than or equal to 4 units. """ smallContours = {} for index, contour in enumerate(glyph): box = contour.box if not box: continue xMin, yMin, xMax, yMax = box w = xMax - xMin h = yMin - yMax area = abs(w * h) if area <= 4: smallContours[index] = contour.box glyph.mark = (0, .8, 0, 0.9) def testForOpenContours(self, glyph): """ Contours should be closed. """ openContours = {} for index, contour in enumerate(glyph): if not contour.open: continue start = contour[0].onCurve start = (start.x, start.y) end = contour[-1].onCurve end = (end.x, end.y) if start != end: openContours[index] = (start, end) glyph.mark = (.4, .4, .4, 0.8) def testForExtremePoints(self, glyph): """ Points should be at the extrema. """ pointsAtExtrema = {} for index, contour in enumerate(glyph): dummy = glyph.copy() dummy.clear() dummy.appendContour(contour) dummy.extremePoints() testPoints = _getOnCurves(dummy[0]) points = _getOnCurves(contour) if points != testPoints: pointsAtExtrema[index] = testPoints - points glyph.mark = (0, .9, 0, 0.6) def testForComplexCurves(self, glyph): """ S curves are suspicious. """ impliedS = {} for index, contour in enumerate(glyph): prev = _unwrapPoint(contour[-1].onCurve) for segment in contour: if segment.type == "curve": pt0 = prev pt1, pt2 = [_unwrapPoint(p) for p in segment.offCurve] pt3 = _unwrapPoint(segment.onCurve) line1 = (pt0, pt3) line2 = (pt1, pt2) if index not in impliedS: impliedS[index] = [] if _intersectLines(line1, line2): impliedS[index].append((prev, pt1, pt2, pt3)) glyph.mark = (1, 1, 0, 1) prev = _unwrapPoint(segment.onCurve) def testForCrossedHandles(self, glyph): """ Handles shouldn't intersect. """ crossedHandles = {} for index, contour in enumerate(glyph): pt0 = _unwrapPoint(contour[-1].onCurve) for segment in contour: pt3 = _unwrapPoint(segment.onCurve) if segment.type == "curve": pt1, pt2 = [_unwrapPoint(p) for p in segment.offCurve] # direct intersection direct = _intersectLines((pt0, pt1), (pt2, pt3)) if direct: if index not in crossedHandles: crossedHandles[index] = [] crossedHandles[index].append( dict(points=(pt0, pt1, pt2, pt3), intersection=direct)) glyph.mark = (1, 0, .4, .2) # indirect intersection else: while 1: # bcp1 = ray, bcp2 = segment angle = _calcAngle(pt0, pt1) if angle in (0, 180.0): t1 = (pt0[0] + 1000, pt0[1]) t2 = (pt0[0] - 1000, pt0[1]) else: yOffset = _getAngleOffset(angle, 1000) t1 = (pt0[0] + 1000, pt0[1] + yOffset) t2 = (pt0[0] - 1000, pt0[1] - yOffset) indirect = _intersectLines((t1, t2), (pt2, pt3)) if indirect: if index not in crossedHandles: crossedHandles[index] = [] crossedHandles[index].append( dict(points=(pt0, indirect, pt2, pt3), intersection=indirect)) break # bcp1 = segment, bcp2 = ray angle = _calcAngle(pt3, pt2) if angle in (90.0, 270.0): t1 = (pt3[0], pt3[1] + 1000) t2 = (pt3[0], pt3[1] - 1000) else: yOffset = _getAngleOffset(angle, 1000) t1 = (pt3[0] + 1000, pt3[1] + yOffset) t2 = (pt3[0] - 1000, pt3[1] - yOffset) indirect = _intersectLines((t1, t2), (pt0, pt1)) if indirect: if index not in crossedHandles: crossedHandles[index] = [] crossedHandles[index].append( dict(points=(pt0, pt1, indirect, pt3), intersection=indirect)) glyph.mark = (1, 0, .4, .2) break break pt0 = pt3 def testForUnnecessaryPoints(self, glyph): """ Consecutive segments shouldn't have the same angle. """ unnecessaryPoints = {} for index, contour in enumerate(glyph): for segmentIndex, segment in enumerate(contour): if segment.type == "line": prevSegment = contour[segmentIndex - 1] nextSegment = contour[(segmentIndex + 1) % len(contour)] if nextSegment.type == "line": thisAngle = _calcAngle(prevSegment.onCurve, segment.onCurve) nextAngle = _calcAngle(segment.onCurve, nextSegment.onCurve) if thisAngle == nextAngle: if index not in unnecessaryPoints: unnecessaryPoints[index] = [] unnecessaryPoints[index].append( _unwrapPoint(segment.onCurve)) glyph.mark = (1, .0, 1, 0.8) def testForOverlappingPoints(self, glyph): """ Consequtive points should not overlap. """ overlappingPoints = {} for index, contour in enumerate(glyph): if len(contour) == 1: continue prev = _unwrapPoint(contour[-1].onCurve) for segment in contour: point = _unwrapPoint(segment.onCurve) if point == prev: if index not in overlappingPoints: overlappingPoints[index] = set() overlappingPoints[index].add(point) glyph.mark = (.0, .0, .6, .3) prev = point # Segments def testForUnnecessaryHandles(self, glyph): """ Handles shouldn't be used if they aren't doing anything. """ unnecessaryHandles = {} for index, contour in enumerate(glyph): prevPoint = contour[-1].onCurve for segment in contour: if segment.type == "curve": pt0 = prevPoint pt1, pt2 = segment.offCurve pt3 = segment.onCurve lineAngle = _calcAngle(pt0, pt3, 0) bcpAngle1 = bcpAngle2 = None if (pt0.x, pt0.y) != (pt1.x, pt1.y): bcpAngle1 = _calcAngle(pt0, pt1, 0) if (pt2.x, pt2.y) != (pt3.x, pt3.y): bcpAngle2 = _calcAngle(pt2, pt3, 0) if bcpAngle1 == lineAngle and bcpAngle2 == lineAngle: if index not in unnecessaryHandles: unnecessaryHandles[index] = [] unnecessaryHandles[index].append( (_unwrapPoint(pt1), _unwrapPoint(pt2))) glyph.mark = (.4, .0, .0, .3) prevPoint = segment.onCurve def testForStraightLines(self, glyph): """ Lines shouldn't be just shy of vertical or horizontal. """ straightLines = {} for index, contour in enumerate(glyph): prev = _unwrapPoint(contour[-1].onCurve) for segment in contour: point = _unwrapPoint(segment.onCurve) if segment.type == "line": x = abs(prev[0] - point[0]) y = abs(prev[1] - point[1]) if x > 0 and x <= 5: if index not in straightLines: straightLines[index] = set() straightLines[index].add((prev, point)) glyph.mark = (.0, .6, .0, .3) if y > 0 and y <= 5: if index not in straightLines: straightLines[index] = set() straightLines[index].add((prev, point)) glyph.mark = (.0, .6, .0, .3) prev = point # Points def testForStrayPoints(self, glyph): """ There should be no stray points. """ strayPoints = {} for index, contour in enumerate(glyph): if len(contour) == 1: pt = contour[0].onCurve pt = (pt.x, pt.y) strayPoints[index] = pt glyph.mark = (.7, .0, .7, .9) def testForPointsNearVerticalMetrics(self, glyph): """ Points shouldn't be just off a vertical metric. """ font = glyph.getParent() verticalMetrics = {0: set()} for attr in "descender xHeight capHeight ascender".split(" "): value = getattr(font.info, attr) verticalMetrics[value] = set() for contour in glyph: for segment in contour: pt = _unwrapPoint(segment.onCurve) y = pt[1] for v in verticalMetrics: d = abs(v - y) if d != 0 and d <= 5: verticalMetrics[v].add(pt) glyph.mark = (1, 1, .0, .2) for verticalMetric, points in verticalMetrics.items(): if not points: del verticalMetrics[verticalMetric]
class VFB2UFO(BaseWindowController): inputOptions = ['Single File', 'Multiple files from a Folder'] chosenMode = inputOptions[0] conversionOptions = ["From VFB to UFO", "From UFO to VFB"] suffix2conversionOption = { "From VFB to UFO": '.vfb', "From UFO to VFB": '.ufo' } chosenSuffix = suffix2conversionOption[conversionOptions[0]] def __init__(self): super(VFB2UFO, self).__init__() self.w = FloatingWindow((PLUGIN_WIDTH, 2), PLUGIN_TITLE) self.jumpingY = MARGIN_VER # options button self.w.optionsPopUp = PopUpButton( (MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['PopUpButtonRegularHeight']), self.inputOptions, callback=self.optionsPopUpCallback) self.jumpingY += vanillaControlsSize[ 'PopUpButtonRegularHeight'] + MARGIN_HOR # suffix option self.w.suffixRadio = RadioGroup( (MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight'] * 2), ["From VFB to UFO", "From UFO to VFB"], callback=self.suffixRadioCallback) self.w.suffixRadio.set(0) self.w.suffixRadio.enable(False) self.jumpingY += vanillaControlsSize[ 'ButtonRegularHeight'] * 2 + MARGIN_HOR # convert button self.w.convertButton = Button( (MARGIN_HOR, self.jumpingY, NET_WIDTH, vanillaControlsSize['ButtonRegularHeight']), 'Choose file and convert', callback=self.convertButtonCallback) self.jumpingY += vanillaControlsSize['ButtonRegularHeight'] + MARGIN_HOR self.w.resize(PLUGIN_WIDTH, self.jumpingY) self.setUpBaseWindowBehavior() self.w.open() def optionsPopUpCallback(self, sender): self.chosenMode = self.inputOptions[sender.get()] if self.chosenMode == 'Single File': self.w.suffixRadio.enable(False) self.w.convertButton.setTitle('Choose file and convert') else: self.w.suffixRadio.enable(True) self.w.convertButton.setTitle('Choose folder and convert') def suffixRadioCallback(self, sender): self.chosenSuffix = self.suffix2conversionOption[ self.conversionOptions[sender.get()]] def convertButtonCallback(self, sender): if self.chosenMode == 'Single File': inputPath = getFile('Choose the file to convert')[0] if inputPath.endswith('.vfb') or inputPath.endswith('.ufo'): executeCommand(['vfb2ufo', '-fo', inputPath], shell=True) else: message('input file path is not correct') else: inputFolder = getFolder('Choose a folder with files to convert')[0] if inputFolder: for eachPath in catchFilesAndFolders(inputFolder, self.chosenSuffix): executeCommand(['vfb2ufo', '-fo', eachPath], shell=True) else: message('input folder path is not correct')
class OverlayUFOs(Subscriber, WindowController): debug = DEBUG_MODE def build(self): self.fonts = AllFonts() self.w = FloatingWindow((400, 200), "Overlay UFOs", minSize=(400, 200)) self.populateWindow() self.w.open() def started(self): CurrentGlyphSubscriber.controller = self registerCurrentGlyphSubscriber(CurrentGlyphSubscriber) GlyphEditorSubscriber.controller = self registerGlyphEditorSubscriber(GlyphEditorSubscriber) FontListManager.controller = self registerRoboFontSubscriber(FontListManager) def destroy(self): CurrentGlyphSubscriber.controller = None unregisterCurrentGlyphSubscriber(CurrentGlyphSubscriber) GlyphEditorSubscriber.controller = None unregisterGlyphEditorSubscriber(GlyphEditorSubscriber) FontListManager.controller = None unregisterRoboFontSubscriber(FontListManager) if DEBUG_MODE: for eachFont in AllFonts(): eachFont.close() def _getFontLabel(self, path): if path is None: return None if not path: return "Untitled" name = path.split("/")[-1] status = SELECTED_SYMBOL return status, path, name def _getFontLabels(self): labels = {} fontPaths = [ f.path or f"{f.info.familyName} {f.info.styleName}" for f in self.fonts ] for path in fontPaths: if path: label = self._getFontLabel(path) name = label[-1] else: name = "Untitled" if name not in labels: labels[name] = [] labels[name].append(label) sortedLabels = [] for _, labelSet in sorted(labels.items()): # There is only a single font with this name if len(labelSet) == 1: sortedLabels.append(labelSet[0]) # Otherwise we'll have to construct new names to show the difference else: for status, path, name in sorted(labelSet): sortedLabels.append( (status, path, f'{name} "{Path(path).parent}"')) return sortedLabels def openedFontsDidChange(self, info): self.w.fontList.set(self._getFontItems()) def resetCallback(self, sender=None): """ Resets the view to the currently opened fonts. """ self.fonts = AllFonts() self.w.fontList.set(self._getFontItems()) def addCallback(self, sender=None): """ Open a font without UI and add it to the font list. """ f = OpenFont(None, showInterface=False) if f is None: return self.fonts.append(f) self.w.fontList.set(self._getFontItems()) def populateWindow(self): """ The UI """ self.fillColor = getExtensionDefault(DEFAULTKEY_FILLCOLOR, FALLBACK_FILLCOLOR) self.strokeColor = getExtensionDefault(DEFAULTKEY_STROKECOLOR, FALLBACK_STROKECOLOR) self.contextBefore = self.contextAfter = "" # Populating the view can only happen after the view is attached to the window, # or else the relative widths go wrong. self.w.add = Button((-40, 3, 30, 22), "+", callback=self.addCallback) self.w.reset = Button((-40, 30, 30, 22), chr(8634), callback=self.resetCallback) # Flag to see if the selection list click is in progress. We are resetting the selection # ourselves, using the list "buttons", but changing that selection will cause another # list update, that should be ignored. self._selectionChanging = False x = y = 4 self.w.fontList = List( (C2, y, 250, -65), self._getFontItems(), selectionCallback=self.fontListCallback, drawFocusRing=False, enableDelete=False, allowsMultipleSelection=False, allowsEmptySelection=True, drawHorizontalLines=True, showColumnTitles=False, columnDescriptions=self._getPathListDescriptor(), rowHeight=16, ) self.w.fill = CheckBox( (x, y, 60, 22), "Fill", sizeStyle=CONTROLS_SIZE_STYLE, value=True, callback=self.fillCallback, ) y += L - 3 self.w.stroke = CheckBox( (x, y, 60, 22), "Stroke", sizeStyle=CONTROLS_SIZE_STYLE, value=False, callback=self.strokeCallback, ) y += L defaultColor = getExtensionDefault(DEFAULTKEY_FILLCOLOR, FALLBACK_FILLCOLOR) self.w.color = ColorWell( (x, y, 60, 22), color=NSColor.colorWithCalibratedRed_green_blue_alpha_( *defaultColor), callback=self.colorCallback, ) y += LL + 5 self.w.alignText = TextBox((x, y, 90, 50), "Alignment", sizeStyle=CONTROLS_SIZE_STYLE) y += L self.w.align = RadioGroup( (x, y, 90, 50), ["Left", "Center", "Right"], isVertical=True, sizeStyle=CONTROLS_SIZE_STYLE, callback=self.alignCallback, ) self.w.align.set(0) self.w.viewCurrent = CheckBox( (C2, -60, 150, 22), "Always View Current", sizeStyle=CONTROLS_SIZE_STYLE, value=False, callback=self.viewCurrentCallback, ) self.w.contextBefore = GlyphNamesEditText( (C2, -30, 85, 20), callback=self.contextEditCallback, continuous=True, sizeStyle="small", placeholder="Left Context", ) self.w.contextBefore.title = "contextBefore" self.w.contextCurrent = GlyphNamesEditText( (C2 + 95, -30, 60, 20), callback=self.contextEditCallback, continuous=True, sizeStyle="small", ) self.w.contextCurrent.title = "contextCurrent" self.w.contextAfter = GlyphNamesEditText( (C2 + 165, -30, 85, 20), callback=self.contextEditCallback, continuous=True, sizeStyle="small", placeholder="Right Context", ) self.w.contextAfter.title = "contextAfter" def viewCurrentCallback(self, sender): postEvent(f"{DEFAULTKEY}.alwaysCurrentViewDidChange") def fontListCallback(self, sender): """ If there is a selection, toggle the status of these fonts """ # Avoid recursive loop because of changing font selection if not self._selectionChanging: for selectedIndex in sender.getSelection(): item = sender.get()[selectedIndex] if item["status"]: item["status"] = "" else: item["status"] = SELECTED_SYMBOL self._selectionChanging = True # Avoid recursive loop because of changing font selection sender.setSelection([]) self._selectionChanging = False postEvent(f"{DEFAULTKEY}.displayedFontsDidChange") def _getFontItems(self, update=False): """ Get all fonts in a way that can be set into a vanilla list """ itemsByName = {} if update: # if update flag is set, then keep the existing selected fonts for item in self.w.fontList.get(): if item["status"]: itemsByName[item["name"]] = item currentStatuses = {} if hasattr(self.w, "fontList"): for d in self.w.fontList.get(): currentStatuses[d["path"]] = d["status"] for status, path, uniqueName in self._getFontLabels(): if path in currentStatuses: status = currentStatuses[path] else: status = SELECTED_SYMBOL if uniqueName not in itemsByName.keys( ): # If it is not already there, add this to the list itemsByName[uniqueName] = dict(status=status, path=path, name=uniqueName) fontList = [] for _, item in sorted(itemsByName.items()): fontList.append(item) return fontList def _getPathListDescriptor(self): return [ dict(title="Status", key="status", cell=SmallTextListCell(editable=False), width=12, editable=False), dict(title="Name", key="name", width=300, cell=SmallTextListCell(editable=False), editable=False), dict(title="Path", key="path", width=0, editable=False), ] def _setSourceFonts(self): """ Set the font list from the current set of open fonts """ labels = [] currentSelection = [] for d in self.w.fontList.get(): if d["status"]: currentSelection.append(d["path"]) for status, path, name in self.fontListManager.getFontLabels(): if path in currentSelection: status = SELECTED_SYMBOL else: status = "" labels.append(dict(status=status, path=path, name=name)) self.w.fontList.set(labels) def colorCallback(self, sender): """ Change the color """ r, g, b, a = NSColorToRgba(sender.get()) self.fillColor = r, g, b, a self.strokeColor = r, g, b, 1 setExtensionDefault(DEFAULTKEY_FILLCOLOR, (r, g, b, a)) setExtensionDefault(DEFAULTKEY_STROKECOLOR, self.strokeColor) postEvent(f"{DEFAULTKEY}.colorDidChange") def fillCallback(self, sender): """ Change the fill status """ setExtensionDefault(DEFAULTKEY_FILL, sender.get()) postEvent(f"{DEFAULTKEY}.fillCheckBoxDidChange") def strokeCallback(self, sender): """ Change the stroke status """ setExtensionDefault(DEFAULTKEY_STROKE, sender.get()) postEvent(f"{DEFAULTKEY}.strokeCheckBoxDidChange") def alignCallback(self, sender): """ Change the alignment status """ postEvent(f"{DEFAULTKEY}.alignmentDidChange") def contextEditCallback(self, sender): postEvent(f"{DEFAULTKEY}.contextDidChange", position=sender.title)
class UnicodePicker(AppKit.NSWindowController): def __new__(cls): return cls.alloc().init() def __init__(self): self.searchResults = [] self.selectedChars = "" self.w = FloatingWindow((300, 400), "Unicode Picker", minSize=(250, 300), autosaveName="UnicodePicker") y = 15 self.w.searchField = EditText((20, y, -20, 25), placeholder="Search Unicode name or value", callback=self.searchTextChanged_) y += 40 columnDescriptions = [ dict(title="char", width=40, cell=makeTextCell(align="center", font=AppKit.NSFont.systemFontOfSize_(14))), dict(title="unicode", width=60, cell=makeTextCell(align="right")), dict(title="name"), ] self.w.unicodeList = List((0, y, 0, -100), [], columnDescriptions=columnDescriptions, rowHeight=18, selectionCallback=self.listSelectionChanged_, doubleClickCallback=self.listDoubleClickCallback_) y = -95 self.w.unicodeText = TextBox((20, y, -10, 55), "") self.w.unicodeText._nsObject.cell().setFont_(AppKit.NSFont.systemFontOfSize_(36)) self.w.unicodeText._nsObject.cell().setLineBreakMode_(AppKit.NSLineBreakByTruncatingMiddle) y += 55 self.w.copyButton = Button((20, y, 120, 25), "Copy", callback=self.copy_) self.w.copyButton.enable(False) self.w.open() self.w._window.setWindowController_(self) self.w._window.setBecomesKeyOnlyIfNeeded_(False) self.w._window.makeKeyWindow() def show(self): if self.w._window is None: # we have been closed, let's reconstruct self.__init__() else: self.w.show() def searchTextChanged_(self, sender): results = [] terms = sender.get().upper().split() if len(terms) == 1: m = _unicodePat.match(terms[0]) if m is not None: uni = int(m.group(4), 16) if uni < 0x110000: results = [uni] if terms: uniSets = [set(findPrefix(t)) for t in terms] foundSet = uniSets[0] for s in uniSets[1:]: foundSet &= s results += sorted(foundSet) self.searchResults = results self.w.unicodeList.set([]) self.appendResults_(500) def appendResults_(self, maxResults): start = len(self.w.unicodeList) unicodeItems = [dict(char=chr(uni), unicode=f"U+{uni:04X}", name=unicodedata.name(chr(uni), "")) for uni in self.searchResults[start:start+maxResults]] if len(self.searchResults) > start + maxResults: unicodeItems.append(dict(name="...more...")) self.w.unicodeList.extend(unicodeItems) def listSelectionChanged_(self, sender): sel = sender.getSelection() if sel and sender[max(sel)]["name"] == "...more...": del sender[len(sender) - 1] self.appendResults_(500) sender.setSelection(sel) chars = "".join(chr(self.searchResults[i]) for i in sel) self.w.copyButton.enable(bool(chars)) self.selectedChars = chars self.w.unicodeText.set(chars) def listDoubleClickCallback_(self, sender): app = AppKit.NSApp() w = app.mainWindow() fr = w.firstResponder() if fr is None or not isinstance(fr, AppKit.NSTextView): return fr.insertText_replacementRange_(self.selectedChars, fr.selectedRange()) def copy_(self, sender): p = AppKit.NSPasteboard.generalPasteboard() p.clearContents() p.declareTypes_owner_([AppKit.NSPasteboardTypeString], None) p.setString_forType_(self.selectedChars, AppKit.NSPasteboardTypeString)
class AccentedMaker(BaseWindowController): fontOptions = [] whichFont = None actions = ['Place Anchors', 'Build Accents'] whichAction = actions[0] whichGlyphList = None markEditedGlyphs = False markColor = glyphCollectionColors[glyphCollectionColors.keys()[0]] uppercaseAccents = False def __init__(self): super(AccentedMaker, self).__init__() self.initLogger() self.fontOptions = ['All Fonts', 'Current Font'] + AllFonts() self.whichFont = self.fontOptions[0] self.pluginHeight = PLUGIN_HEIGHT self.loadAccentedData() self.parseGlyphListsFromAccentedData() firstKey = self.glyphLists[self.whichAction].keys()[0] self.whichGlyphList = self.glyphLists[self.whichAction][firstKey] self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, self.pluginHeight), PLUGIN_TITLE) self.w.sharedCtrls = SharedCtrls( (MARGIN_HOR, MARGIN_VER, NET_WIDTH, 104), fontOptions=self.fontOptions, whichFont=self.whichFont, actions=self.actions, whichAction=self.whichAction, glyphLists=self.glyphLists, whichGlyphList=self.whichGlyphList, markColor=self.markColor, markEditedGlyphs=self.markEditedGlyphs, callback=self.sharedCtrlsCallback) self.w.separationLine = HorizontalLine( (MARGIN_HOR, self.w.sharedCtrls.getPosSize()[3] + MARGIN_ROW, NET_WIDTH, vanillaControlsSize['HorizontalLineThickness'])) dependantCtrlsHgt = MARGIN_VER + self.w.sharedCtrls.getPosSize( )[3] + MARGIN_ROW self.w.anchorsCtrls = AnchorsCtrls( (MARGIN_HOR, dependantCtrlsHgt, NET_WIDTH, 76), callbackAttrs=self.anchorsVarsCallback, placeCallback=self.anchorsPlaceCallback, deleteCallback=self.anchorsDeleteCallback) self.w.buildingCtrls = BuildingCtrls( (MARGIN_HOR, dependantCtrlsHgt, NET_WIDTH, 50), self.uppercaseAccents, callbackAttrs=self.buildingVarsCallback, callbackCheck=self.checkAccentedCallback, callbackBuild=self.buildAccentedCallback) self.w.buildingCtrls.show(False) addObserver(self, 'updateFontOptions', "newFontDidOpen") addObserver(self, 'updateFontOptions', "fontDidOpen") addObserver(self, 'updateFontOptions', "fontWillClose") self.w.bind("close", self.windowCloseCallback) self.setUpBaseWindowBehavior() self.adjustPluginHeight() self.w.open() def initLogger(self): # create a logger self.accentedLogger = logging.getLogger('accentedLogger') # create file handler which logs info messages fileBasedHandler = logging.FileHandler('accentedLettersMaker.log') fileBasedHandler.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 | %(levelname)s | line: %(lineno)d | %(funcName)s | %(message)s' ) fileBasedHandler.setFormatter(formatter) consoleHandler.setFormatter(formatter) # add the handlers to the logger self.accentedLogger.addHandler(fileBasedHandler) self.accentedLogger.addHandler(consoleHandler) # deal with font data def prepareFontsToAction(self): if self.whichFont == 'All Fonts': fontsToProcess = AllFonts() elif self.whichFont == 'Current Font': fontsToProcess = [CurrentFont()] else: fontsToProcess = [self.whichFont] return fontsToProcess def deleteAnchors(self): self.accentedLogger.info( START_FUNC.format(funcName=self.deleteAnchors.__name__)) fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: self.accentedLogger.info( START_FONT.format(familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) for eachGlyphName in self.whichGlyphList: eachGlyph = eachFont[eachGlyphName] if self.markEditedGlyphs is True: if version[0] == '2': eachGlyph.markColor = self.markColor else: eachGlyph.mark = self.markColor for eachAnchor in eachGlyph.anchors: eachGlyph.removeAnchor(eachAnchor) self.accentedLogger.info( REMOVE_ANCHOR.format(anchorName=self.anchorName, glyphName=eachGlyphName)) self.accentedLogger.info( END_FUNC.format(funcName=self.deleteAnchors.__name__)) def placeAnchors(self): assert self.anchorName is not None, '[WARNING] no anchor name provided' assert self.anchorHeight is not None, '[WARNING] no anchor height provided' self.accentedLogger.info( START_FUNC.format(funcName=self.placeAnchors.__name__)) fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: self.accentedLogger.info( START_FONT.format(familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) for eachGlyphName in self.whichGlyphList: if eachGlyphName in eachFont: eachGlyph = eachFont[eachGlyphName] if self.markEditedGlyphs is True: if version[0] == '2': eachGlyph.markColor = self.markColor else: eachGlyph.mark = self.markColor if selectAnchorByName(eachGlyph, self.anchorName): anchorToDel = selectAnchorByName( eachGlyph, self.anchorName) eachGlyph.removeAnchor(anchorToDel) if version[0] == '2': xMin, yMin, xMax, yMax = eachGlyph.bounds else: xMin, yMin, xMax, yMax = eachGlyph.box if eachFont.info.italicAngle: anchorAngle = radians(-eachFont.info.italicAngle) else: anchorAngle = radians(0) tangentOffset = tan(anchorAngle) * self.anchorHeight anchorX = ( eachGlyph.width - eachGlyph.angledLeftMargin - eachGlyph.angledRightMargin ) / 2 + eachGlyph.angledLeftMargin + tangentOffset eachGlyph.appendAnchor(self.anchorName, (anchorX, self.anchorHeight)) self.accentedLogger.info( APPEND_ANCHOR.format(anchorName=self.anchorName, anchorX=anchorX, anchorHeight=self.anchorHeight, glyphName=eachGlyphName)) else: self.accentedLogger.error( GLYPH_NOT_IN_FONT.format( glyphName=eachGlyphName, familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) self.accentedLogger.info( END_FUNC.format(funcName=self.placeAnchors.__name__)) def checkAccented(self, isPrinting=True): report = [] notReady = OrderedDict() fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: toSkip = [] report.append('Checking {} {}'.format(eachFont.info.familyName, eachFont.info.styleName)) for eachAccentedName, eachBaseName, eachAccentName, eachAnchorName in self.whichGlyphList: # base glyph if eachFont.has_key(eachBaseName) is False: report.append( BUILD_MISSING_GLYPH.format( glyphName=eachBaseName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) else: eachBaseGlyph = eachFont[eachBaseName] if not eachBaseGlyph.anchors: report.append( NO_ANCHORS.format(glyphName=eachBaseName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachBaseGlyph.markColor = ERROR_MARK_COLOR else: eachBaseGlyph.mark = ERROR_MARK_COLOR else: for eachAnchor in eachBaseGlyph.anchors: if eachAnchor.name == eachAnchorName: break else: report.append( BUILD_MISSING_ANCHOR.format( anchorName=eachAnchorName, glyphName=eachBaseName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachBaseGlyph.markColor = ERROR_MARK_COLOR else: eachBaseGlyph.mark = ERROR_MARK_COLOR # accent if eachFont.has_key(eachAccentName) is False: report.append( BUILD_MISSING_GLYPH.format( glyphName=eachAccentName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) else: eachAccentGlyph = eachFont[eachAccentName] if not eachAccentGlyph.anchors: report.append( NO_ANCHORS.format(glyphName=eachAccentName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachAccentGlyph.markColor = ERROR_MARK_COLOR else: eachAccentGlyph.mark = ERROR_MARK_COLOR else: for eachAnchor in eachAccentGlyph.anchors: if eachAnchor.name == '_{}'.format(eachAnchorName): break else: report.append( BUILD_MISSING_ANCHOR.format( anchorName=eachAnchorName, glyphName=eachAccentName, accentedName=eachAccentedName)) if eachAccentedName not in toSkip: toSkip.append(eachAccentedName) if version[0] == '2': eachAccentGlyph.markColor = ERROR_MARK_COLOR else: eachAccentGlyph.mark = ERROR_MARK_COLOR notReady['{} {}'.format(eachFont.info.familyName, eachFont.info.styleName)] = toSkip report.append('End checking {} {}'.format(eachFont.info.familyName, eachFont.info.styleName)) report.append('\n\n') if isPrinting is True: self.accentedLogger.error('\n'.join(report)) return notReady def buildAccented(self): notReady = self.checkAccented(isPrinting=False) self.accentedLogger.info( START_FUNC.format(funcName=self.buildAccented.__name__)) fontsToProcess = self.prepareFontsToAction() for eachFont in fontsToProcess: self.accentedLogger.info( START_FONT.format(familyName=eachFont.info.familyName, styleName=eachFont.info.styleName)) for eachAccentedName, eachBaseName, eachAccentName, eachAnchorName in self.whichGlyphList: if eachAccentedName in notReady['{} {}'.format( eachFont.info.familyName, eachFont.info.styleName)]: self.accentedLogger.error( NOT_READY.format(fontName='{} {}'.format( eachFont.info.familyName, eachFont.info.styleName), accentedName=eachAccentedName)) continue eachBaseGlyph = eachFont[eachBaseName] eachBaseAnchor = selectAnchorByName(eachBaseGlyph, eachAnchorName) eachAccentGlyph = eachFont[eachAccentName] eachAccentAnchor = selectAnchorByName( eachAccentGlyph, '_{}'.format(eachAnchorName)) if eachFont.has_key(eachAccentedName) is False: eachAccentedGlyph = eachFont.newGlyph(eachAccentedName) else: eachAccentedGlyph = eachFont[eachAccentedName] eachAccentedGlyph.clear() eachAccentedGlyph.width = eachBaseGlyph.width eachAccentedGlyph.appendComponent(eachBaseName, (0, 0), (1, 1)) accentOffsetX, accentOffsetY = eachBaseAnchor.x - eachAccentAnchor.x, eachBaseAnchor.y - eachAccentAnchor.y eachAccentedGlyph.appendComponent( eachAccentName, (accentOffsetX, accentOffsetY), (1, 1)) self.accentedLogger.info( BUILT_GLYPH.format(accentedName=eachAccentedName, baseName=eachBaseName, accentName=eachAccentName, anchorName=eachAnchorName)) if self.markEditedGlyphs is True: if version[0] == '2': eachAccentedGlyph.markColor = self.markColor else: eachAccentedGlyph.mark = self.markColor self.accentedLogger.info( END_FUNC.format(funcName=self.buildAccented.__name__)) # deal with table data def loadAccentedData(self): self.accentedData = [[cell.strip() for cell in row.split('\t')] for row in open(TABLE_PATH, 'r').readlines()] def parseGlyphListsFromAccentedData(self): assert self.accentedData is not None self.glyphLists = OrderedDict() # anchors self.glyphLists['Place Anchors'] = OrderedDict() accentsTop = [] _ = [ accentsTop.append(row[2]) for row in self.accentedData if row[3] == 'top' and row[2] not in accentsTop ] self.glyphLists['Place Anchors']['ACC TOP'] = accentsTop accentsBtm = [] _ = [ accentsBtm.append(row[2]) for row in self.accentedData if row[3] == 'bottom' and row[2] not in accentsBtm ] self.glyphLists['Place Anchors']['ACC BTM'] = accentsBtm accentsCaseTop = ['{}.case'.format(name) for name in accentsTop] self.glyphLists['Place Anchors']['ACC CASE TOP'] = accentsCaseTop accentsCaseBtm = ['{}.case'.format(name) for name in accentsBtm] self.glyphLists['Place Anchors']['ACC CASE BTM'] = accentsCaseBtm ucBaseTop = [] _ = [ ucBaseTop.append(row[1]) for row in self.accentedData if row[1][0].isupper() and row[3] == 'top' and row[1] not in ucBaseTop ] self.glyphLists['Place Anchors']['UC TOP'] = ucBaseTop ucBaseBtm = [] _ = [ ucBaseBtm.append(row[1]) for row in self.accentedData if row[1][0].isupper() and row[3] == 'bottom' and row[1] not in ucBaseBtm ] self.glyphLists['Place Anchors']['UC BTM'] = ucBaseBtm lcBaseTop = [] _ = [ lcBaseTop.append(row[1]) for row in self.accentedData if row[1][0].islower() and row[3] == 'top' and row[1] not in lcBaseTop ] self.glyphLists['Place Anchors']['LC TOP'] = lcBaseTop lcBaseBtm = [] _ = [ lcBaseBtm.append(row[1]) for row in self.accentedData if row[1][0].islower() and row[3] == 'bottom' and row[1] not in lcBaseBtm ] self.glyphLists['Place Anchors']['LC BTM'] = lcBaseBtm # build self.glyphLists['Build Accents'] = OrderedDict() self.glyphLists['Build Accents']['ALL'] = self.accentedData buildUC = [ row for row in self.accentedData if row[1][0].isupper() is True ] self.glyphLists['Build Accents']['UC'] = buildUC buildLC = [ row for row in self.accentedData if row[1][0].islower() is True ] self.glyphLists['Build Accents']['LC'] = buildLC # ui def adjustPluginHeight(self): if self.whichAction == 'Place Anchors': self.pluginHeight = MARGIN_VER + self.w.sharedCtrls.getPosSize( )[3] + MARGIN_ROW + self.w.anchorsCtrls.getPosSize( )[3] + MARGIN_VER else: self.pluginHeight = MARGIN_VER + self.w.sharedCtrls.getPosSize( )[3] + MARGIN_ROW + self.w.buildingCtrls.getPosSize( )[3] + MARGIN_VER lft, top, wdt, hgt = self.w.getPosSize() self.w.resize(wdt, self.pluginHeight) def switchDependantCtrl(self): if self.whichAction == 'Place Anchors': self.w.anchorsCtrls.show(True) self.w.buildingCtrls.show(False) else: self.w.anchorsCtrls.show(False) self.w.buildingCtrls.show(True) # observers def updateFontOptions(self, sender): self.fontOptions = ['All Fonts', 'Current Font'] + AllFonts() self.w.sharedCtrls.setFontOptions(self.fontOptions) # callbacks def sharedCtrlsCallback(self, sender): self.whichFont = sender.getWhichFont() self.whichAction = sender.getWhichAction() self.switchDependantCtrl() self.adjustPluginHeight() self.whichGlyphList = sender.getWhichGlyphList() self.markEditedGlyphs, self.markColor = sender.getMarkEditedGlyphs() def anchorsVarsCallback(self, sender): self.anchorHeight = sender.getHeight() self.anchorName = sender.getName() def anchorsPlaceCallback(self, sender): self.placeAnchors() def anchorsDeleteCallback(self, sender): self.deleteAnchors() def buildingVarsCallback(self, sender): self.uppercaseAccents = sender.getUppercaseAccents() def checkAccentedCallback(self, sender): self.checkAccented() def buildAccentedCallback(self, sender): self.buildAccented() def windowCloseCallback(self, sender): removeObserver(self, "newFontDidOpen") removeObserver(self, "fontDidOpen") removeObserver(self, "fontWillClose") super(AccentedMaker, self).windowCloseCallback(sender)
class GroupSpacingWindow(BaseWindowController): ''' A tool to enable group spacing in the Space Center. - works with the selected glyph in the Space Center - shows a preview of all other glyphs in the same spacing group - transfer margins from current glyph to all glyphs in the same spacing group - supports measurements using the current beam ''' def __init__(self): padding = 10 lineHeight = 20 buttonHeight = 20 width = 123 height = lineHeight * 6 + buttonHeight * 4 + padding * 9 self.w = FloatingWindow((width, height), title='spacing') x = y = padding self.w.makeGroupButton = Button((x, y, -padding, buttonHeight), 'make group', callback=self.makeGroupCallback, sizeStyle='small') y += buttonHeight + padding self.w.side = RadioGroup((x, y, -padding, lineHeight), ['left', 'right'], isVertical=False, callback=self.updateViewsCallback, sizeStyle='small') self.w.side.set(0) y += lineHeight + padding self.w.copySpacingButton = Button((x, y, -padding, buttonHeight), 'copy margin', callback=self.copySpacingCallback, sizeStyle='small') y += buttonHeight + padding self.w.useBeam = CheckBox((x, y, -padding, lineHeight), 'use beam', callback=self.useBeamCallback, sizeStyle='small') y += lineHeight self.w.allLayers = CheckBox((x, y, -padding, lineHeight), 'all layers', callback=self.updateViewsCallback, sizeStyle='small') y += lineHeight + padding self.w.opacityLabel = TextBox((x, y, -padding, lineHeight), 'opacity:', sizeStyle='small') y += lineHeight self.w.opacity = Slider((x, y, -padding, lineHeight), value=0.4, minValue=0.0, maxValue=0.9, callback=self.updateViewsCallback, sizeStyle='small') y += lineHeight + padding self.w.exportButton = Button((x, y, -padding, buttonHeight), 'export…', callback=self.exportCallback, sizeStyle='small') y += buttonHeight + padding self.w.importButton = Button((x, y, -padding, buttonHeight), 'import…', callback=self.importCallback, sizeStyle='small') y += buttonHeight + padding self.w.verbose = CheckBox((x, y, -padding, lineHeight), 'verbose', value=True, sizeStyle='small') self.setUpBaseWindowBehavior() addObserver(self, "drawGlyphsInGroup", "spaceCenterDraw") self.w.getNSWindow().setTitlebarAppearsTransparent_(True) self.w.open() # dynamic attrs. @property def side(self): '''The selected spacing group side.''' return ['left', 'right'][int(self.w.side.get())] @property def useBeam(self): '''Use or not the beam to measure margins. Value taken from the checkbox.''' return self.w.useBeam.get() @property def allLayers(self): '''Set margins in all layers. Value taken from the checkbox.''' return self.w.allLayers.get() @property def beam(self): '''The beam’s y position in the Space Center.''' sp = CurrentSpaceCenter() if not sp: return return sp.beam() @property def opacity(self): '''The opacity of the fill color of glyphs in group.''' return self.w.opacity.get() @property def verbose(self): '''Output action info to the console.''' return self.w.verbose.get() # --------- # callbacks # --------- def makeGroupCallback(self, sender): '''Make a new spacing group with the selected glyph.''' glyph = CurrentGlyph() if not glyph: return if glyph.font is None: return prefix = PREFIX_LEFTSIDE if self.side == 'left' else PREFIX_RIGHTSIDE groupName = prefix + glyph.name if not groupName in glyph.font.groups: glyph.font.groups[groupName] = [glyph.name] def copySpacingCallback(self, sender): '''Copy margin from current glyph to other glyphs in left/right spacing class.''' glyph = CurrentGlyph() if not glyph: return if glyph.bounds is None: return if glyph.font is None: return siblings = getSiblings(glyph, self.side) if not siblings: return beam = self.beam if self.useBeam else None copyMargins(glyph, siblings, self.side, beam=beam, allLayers=self.allLayers, verbose=self.verbose) def useBeamCallback(self, sender): '''Show/hide the beam according to checkbox selection.''' S = CurrentSpaceCenter() if not S: return value = sender.get() options = S.glyphLineView.getDisplayStates() options['Beam'] = value S.glyphLineView.setDisplayStates(options) def exportCallback(self, sender): '''Export spacing groups to .json file.''' font = CurrentFont() filePath = PutFile(message='export spacing groups', fileName='spacingGroups.json') exportSpacingGroups(font, filePath) def importCallback(self, sender): '''Import spacing groups from .json file.''' font = CurrentFont() filePath = GetFile(message='import spacing groups', fileTypes=['json']) importSpacingGroups(font, filePath) def windowCloseCallback(self, sender): '''Remove observers when closing window.''' super().windowCloseCallback(sender) removeObserver(self, "spaceCenterDraw") def updateViewsCallback(self, sender): '''Update the Space Center.''' S = CurrentSpaceCenter() if not S: return S.glyphLineView.refresh() # --------- # observers # --------- def drawGlyphsInGroup(self, notification): '''Display all glyphs belonging to the same spacing group in the background.''' glyph = notification['glyph'] font = glyph.font if font is None: return siblings = getSiblings(glyph, self.side) if not siblings: return if not notification['selected']: return S = CurrentSpaceCenter() if not S: return inverse = S.glyphLineView.getDisplayStates()['Inverse'] # hide solid color glyph R, G, B, A = getDefault( "spaceCenterBackgroundColor") if not inverse else getDefault( "spaceCenterGlyphColor") bounds = glyph.bounds if bounds: save() fill(R, G, B, A) stroke(R, G, B, A) drawGlyph(glyph) restore() # draw side indicator save() stroke(1, 0, 0) strokeWidth(10) xPos = 0 if self.side == 'left' else glyph.width yMin = font.info.descender yMax = yMin + font.info.unitsPerEm line((xPos, yMin), (xPos, yMax)) restore() # draw glyph and siblings R, G, B, A = getDefault( "spaceCenterGlyphColor") if not inverse else getDefault( "spaceCenterBackgroundColor") alpha = (1.0 / len(siblings) + self.opacity) / 2 stroke(None) for glyphName in siblings: if glyphName not in glyph.layer: continue g = font[glyphName].getLayer(glyph.layer.name) save() if self.side == 'right': dx = glyph.width - g.width translate(dx, 0) color = (R, G, B, 0.4) if glyphName == glyph.name else (R, G, B, alpha) fill(*color) drawGlyph(g) restore()
class ToucheTool(): def __init__(self): NSUserDefaults.standardUserDefaults().registerDefaults_( {"ToucheWindowHeight": 340}) self.windowHeight = NSUserDefaults.standardUserDefaults( ).integerForKey_("ToucheWindowHeight") self.minWindowHeight = 340 if self.windowHeight < self.minWindowHeight: self.windowHeight = self.minWindowHeight self.closedWindowHeight = 100 self.w = FloatingWindow((180, self.windowHeight), 'Touché!', minSize=(180, 340), maxSize=(250, 898)) self.w.bind("resize", self.windowResized_) self.isResizing = False p = 10 w = 160 # options self.w.options = Group((0, 0, 180, 220)) buttons = { "checkSelBtn": { "text": "Check selected glyphs", "callback": self.checkSel_, "y": p }, } for button, data in buttons.iteritems(): setattr( self.w.options, button, Button((p, data["y"], w - 22, 22), data["text"], callback=data["callback"], sizeStyle="small")) self.w.options.zeroCheck = CheckBox((p, 35, w, 20), "Ignore zero-width glyphs", value=True, sizeStyle="small") self.w.options.progress = ProgressSpinner((w - 8, 13, 16, 16), sizeStyle="small") # results self.w.results = Group((0, 220, 180, -0)) self.w.results.show(False) textBoxes = {"stats": -34, "result": -18} for box, y in textBoxes.iteritems(): setattr(self.w.results, box, TextBox((p, y, w, 14), "", sizeStyle="small")) # list and preview self.w.outputList = List((0, 58, -0, -40), [{ "left glyph": "", "right glyph": "" }], columnDescriptions=[{ "title": "left glyph", "width": 90 }, { "title": "right glyph" }], showColumnTitles=False, allowsMultipleSelection=False, enableDelete=False, selectionCallback=self.showPair_) self.w.outputList._setColumnAutoresizing() self._resizeWindow(False) self.w.open() # callbacks def checkAll_(self, sender=None): self.check_(useSelection=False) def checkSel_(self, sender=None): self.check_(useSelection=True) def check_(self, useSelection): self._resizeWindow(enlarge=False) self.checkFont(useSelection=useSelection, excludeZeroWidth=self.w.options.zeroCheck.get()) def showPair_(self, sender=None): try: index = sender.getSelection()[0] glyphs = [self.f[gName] for gName in self.touchingPairs[index]] ActiveFont = self.f._font EditViewController = ActiveFont.currentTab if EditViewController is None: tabText = "/%s/%s" % (glyphs[0].name, glyphs[1].name) ActiveFont.newTab(tabText) else: textStorage = EditViewController.graphicView() if not hasattr(textStorage, "replaceCharactersInRange_withString_" ): # compatibility with API change in 2.5 textStorage = EditViewController.graphicView().textStorage( ) LeftChar = ActiveFont.characterForGlyph_(glyphs[0]._object) RightChar = ActiveFont.characterForGlyph_(glyphs[1]._object) if LeftChar != 0 and RightChar != 0: selection = textStorage.selectedRange() if selection.length < 2: selection.length = 2 if selection.location > 0: selection.location -= 1 NewString = "" if LeftChar < 0xffff and RightChar < 0xffff: NewString = "%s%s" % (unichr(LeftChar), unichr(RightChar)) else: print("Upper plane codes are not supported yet") textStorage.replaceCharactersInRange_withString_( selection, NewString) selection.length = 0 selection.location += 1 textStorage.setSelectedRange_(selection) #self.w.preview.set(glyphs) except IndexError: pass # checking @objc.python_method def _hasSufficientWidth(self, g): # to ignore combining accents and the like if self.excludeZeroWidth: # also skips 1-unit wide glyphs which many use instead of 0 if g.width < 2 or g._object.subCategory == "Nonspacing": return False return True @objc.python_method def _trimGlyphList(self, glyphList): newGlyphList = [] for g in glyphList: if g.box is not None and self._hasSufficientWidth(g): newGlyphList.append(g) return newGlyphList def windowResized_(self, window): posSize = self.w.getPosSize() Height = posSize[3] if Height > self.closedWindowHeight and self.isResizing is False: print("set new Height", Height) NSUserDefaults.standardUserDefaults().setInteger_forKey_( Height, "ToucheWindowHeight") self.windowHeight = Height @objc.python_method def _resizeWindow(self, enlarge=True): posSize = self.w.getPosSize() if enlarge: self.w.results.show(True) self.w.outputList.show(True) targetHeight = self.windowHeight if targetHeight < 340: targetHeight = 340 else: self.w.results.show(False) self.w.outputList.show(False) targetHeight = self.closedWindowHeight self.isResizing = True self.w.setPosSize((posSize[0], posSize[1], posSize[2], targetHeight)) self.isResizing = False # ok let's do this @objc.python_method def checkFont(self, useSelection=False, excludeZeroWidth=True): f = CurrentFont() if f is not None: # initialize things self.w.options.progress.start() time0 = time.time() self.excludeZeroWidth = excludeZeroWidth self.f = f glyphNames = f.selection if useSelection else f.keys() glyphList = [f[x] for x in glyphNames] glyphList = self._trimGlyphList(glyphList) self.touchingPairs = Touche(f).findTouchingPairs(glyphList) # display output self.w.results.stats.set("%d glyphs checked" % len(glyphList)) self.w.results.result.set("%d touching pairs found" % len(self.touchingPairs)) self.w.results.show(True) outputList = [{ "left glyph": g1, "right glyph": g2 } for (g1, g2) in self.touchingPairs] self.w.outputList.set(outputList) if len(self.touchingPairs) > 0: self.w.outputList.setSelection([0]) #self.w.preview.setFont(f) self.w.options.progress.stop() self._resizeWindow(enlarge=True) time1 = time.time() print('Touché: finished checking %d glyphs in %.2f seconds' % (len(glyphList), time1 - time0)) else: Message('Touché: Can’t find a font to check')
class DrawingAssistant(BaseWindowController): """the aim of this plugin is to provide useful visualizations on glyph canvas""" # switches sqrActive = False bcpLengthActive = False gridActive = False offgridActive = False stemActive = False diagonalActive = False openedFonts = [] lftNeighborActive = False lftFontPath = None lftGlyphName = None cntNeighborActive = False cntFontPath = None cntGlyphName = None rgtNeighborActive = False rgtFontPath = None rgtGlyphName = None gridsDB = [] def __init__(self): super(DrawingAssistant, self).__init__() # collect currentGlyph (if available) self.currentGlyph = CurrentGlyph() # collect opened fonts if AllFonts(): self.collectOpenedFontPaths() # init fonts if self.openedFontPaths: self._initFontsAndGlyphs() # init views self.bcpController = BcpController( (MARGIN_HOR, 0, NET_WIDTH, vanillaControlsSize['CheckBoxRegularHeight'] * 2.5), sqrActive=self.sqrActive, bcpLengthActive=self.bcpLengthActive, callback=self.bcpControllerCallback) self.gridController = MultipleGridController( (MARGIN_HOR, 0, NET_WIDTH, 220), gridActive=self.gridActive, ctrlsAmount=5, activeCtrls=0, offgridActive=self.offgridActive, callback=self.gridControllerCallback) self.distancesController = DistancesController( (MARGIN_HOR, 0, NET_WIDTH, 160), stemActive=self.stemActive, diagonalActive=self.diagonalActive, currentGlyph=self.currentGlyph, callback=self.distancesControllerCallback) self.neighborsController = NeighborsController( (MARGIN_HOR, 0, NET_WIDTH, 94), openedFontPaths=self.openedFontPaths, lftNeighborActive=self.lftNeighborActive, lftFontPath=self.lftFontPath, lftGlyphName=self.lftGlyphName, cntNeighborActive=self.cntNeighborActive, cntFontPath=self.cntFontPath, cntGlyphName=self.cntGlyphName, rgtNeighborActive=self.rgtNeighborActive, rgtFontPath=self.rgtFontPath, rgtGlyphName=self.rgtGlyphName, callback=self.neighborsControllerCallback) # collect views accordionItems = [ { 'label': 'bcp controller', 'view': self.bcpController, 'size': self.bcpController.ctrlHeight, 'collapsed': False, 'canResize': False }, { 'label': 'grid controller', 'view': self.gridController, 'size': self.gridController.ctrlHeight, 'collapsed': False, 'canResize': False }, { 'label': 'distances controller', 'view': self.distancesController, 'size': self.distancesController.ctrlHeight, 'collapsed': False, 'canResize': False }, { 'label': 'neighbors controller', 'view': self.neighborsController, 'size': self.neighborsController.ctrlHeight, 'collapsed': False, 'canResize': False }, ] # init window with accordion obj totalHeight = self.bcpController.ctrlHeight + self.gridController.ctrlHeight + self.distancesController.ctrlHeight + self.neighborsController.ctrlHeight + 18 * 4 self.w = FloatingWindow((0, 0, PLUGIN_WIDTH, totalHeight), PLUGIN_TITLE, minSize=(PLUGIN_WIDTH, 200), maxSize=(PLUGIN_WIDTH, totalHeight + 10)) self.w.accordionView = AccordionView((0, 0, -0, -0), accordionItems) # add observers addObserver(self, "_draw", "draw") addObserver(self, "_draw", "drawInactive") addObserver(self, "_drawPreview", "drawPreview") addObserver(self, "_mouseDown", "mouseDown") addObserver(self, "_drawBackground", "drawBackground") addObserver(self, "_updateCurrentFont", "fontBecameCurrent") addObserver(self, '_updateCurrentGlyph', 'viewDidChangeGlyph') addObserver(self, "aFontIsOpening", "newFontDidOpen") addObserver(self, "aFontIsOpening", "fontDidOpen") addObserver(self, "aFontIsClosing", "fontWillClose") self.setUpBaseWindowBehavior() # open window self.w.open() # drawing callbacks def _mouseDown(self, infoDict): mouseDownPoint = infoDict['point'] mouseDownClickCount = infoDict['clickCount'] # double click if mouseDownClickCount == 2: if self.lftNeighborActive is True and self.lftGlyphName: lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName) if version[0] == '2': xMin, yMin, xMax, yMax = lftGlyph.bounds else: xMin, yMin, xMax, yMax = lftGlyph.box if xMin < (mouseDownPoint.x + lftGlyph.width ) < xMax and yMin < mouseDownPoint.y < yMax: OpenGlyphWindow(glyph=lftGlyph, newWindow=True) if self.rgtNeighborActive is True and self.rgtGlyphName: rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName) if version[0] == '2': xMin, yMin, xMax, yMax = rgtGlyph.bounds else: xMin, yMin, xMax, yMax = rgtGlyph.box if xMin < (mouseDownPoint.x - self.currentGlyph.width ) < xMax and yMin < mouseDownPoint.y < yMax: OpenGlyphWindow(glyph=rgtGlyph, newWindow=True) def _draw(self, infoDict): glyphOnCanvas = infoDict['glyph'] scalingFactor = infoDict['scale'] if self.lftGlyphName and self.lftNeighborActive is True: lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName) if self.rgtGlyphName and self.rgtNeighborActive is True: rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName) try: if self.lftGlyphName and self.lftNeighborActive is True: self._drawGlyphOutline(lftGlyph, scalingFactor, offset_X=-lftGlyph.width) if self.rgtGlyphName and self.rgtNeighborActive is True: self._drawGlyphOutline(rgtGlyph, scalingFactor, offset_X=glyphOnCanvas.width) if self.sqrActive is True and 2 > scalingFactor: self._drawSquarings(glyphOnCanvas, scalingFactor) if self.lftGlyphName and self.lftNeighborActive is True: self._drawSquarings(lftGlyph, scalingFactor, offset_X=-lftGlyph.width) if self.rgtGlyphName and self.rgtNeighborActive is True: self._drawSquarings(rgtGlyph, scalingFactor, offset_X=glyphOnCanvas.width) if self.bcpLengthActive is True and 2 > scalingFactor: self._drawBcpLenght(glyphOnCanvas, scalingFactor, 2) if self.lftGlyphName and self.lftNeighborActive is True: self._drawBcpLenght(lftGlyph, scalingFactor, offset_X=-lftGlyph.width) if self.rgtGlyphName and self.rgtNeighborActive is True: self._drawBcpLenght(rgtGlyph, scalingFactor, offset_X=glyphOnCanvas.width) except Exception as error: print traceback.format_exc() def _drawPreview(self, infoDict): glyphOnCanvas = infoDict['glyph'] scalingFactor = infoDict['scale'] if self.lftGlyphName and self.lftNeighborActive is True: lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName) if self.rgtGlyphName and self.rgtNeighborActive is True: rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName) try: if self.lftGlyphName and self.lftNeighborActive is True: self._drawGlyphBlack(lftGlyph, scalingFactor, offset_X=-lftGlyph.width) if self.rgtGlyphName and self.rgtNeighborActive is True: self._drawGlyphBlack(rgtGlyph, scalingFactor, offset_X=glyphOnCanvas.width) except Exception as error: print traceback.format_exc() def _drawBackground(self, infoDict): glyphOnCanvas = infoDict['glyph'] scalingFactor = infoDict['scale'] if self.lftGlyphName and self.lftNeighborActive is True: lftGlyph = chooseRightGlyph(self.lftFontPath, self.lftGlyphName) if self.rgtGlyphName and self.rgtNeighborActive is True: rgtGlyph = chooseRightGlyph(self.rgtFontPath, self.rgtGlyphName) try: if self.gridActive is True and 2 > scalingFactor: offsetFromOrigin = infoDict['view'].offset() visibleRect = infoDict['view'].visibleRect() frameOrigin = int(visibleRect.origin.x - offsetFromOrigin[0]), int( visibleRect.origin.y - offsetFromOrigin[1]) frameSize = int(visibleRect.size.width), int( visibleRect.size.height) self._drawGrids(frameOrigin, frameSize, glyphOnCanvas.getParent().info.italicAngle, scalingFactor) if self.diagonalActive is True and 1 > scalingFactor: self._drawDiagonals(glyphOnCanvas, scalingFactor) if self.lftGlyphName and self.lftNeighborActive is True: self._drawDiagonals(lftGlyph, scalingFactor, offset_X=-lftGlyph.width) if self.rgtGlyphName and self.rgtNeighborActive is True: self._drawDiagonals(rgtGlyph, scalingFactor, offset_X=glyphOnCanvas.width) if self.cntNeighborActive is True: cntGlyph = chooseRightGlyph(self.cntFontPath, self.cntGlyphName) self._drawCentralBackgroundGlyph(cntGlyph) if self.stemActive is True and 1.5 > scalingFactor: self._drawStems(glyphOnCanvas, scalingFactor) if self.lftGlyphName and self.lftNeighborActive is True: self._drawStems(lftGlyph, scalingFactor, offset_X=-lftGlyph.width) if self.rgtGlyphName and self.rgtNeighborActive is True: self._drawStems(rgtGlyph, scalingFactor, offset_X=glyphOnCanvas.width) if self.offgridActive is True: self._drawOffgridPoints(glyphOnCanvas, scalingFactor) except Exception as error: print traceback.format_exc() def _drawOffgridPoints(self, glyph, scalingFactor): dt.save() dt.fill(*RED_COLOR) dt.stroke(None) scaledRadius = OFFGRID_RADIUS * scalingFactor for eachContour in glyph: for eachBPT in eachContour.bPoints: if eachBPT.anchor[0] % 4 != 0 or eachBPT.anchor[1] % 4 != 0: dt.oval(eachBPT.anchor[0] - scaledRadius / 2., eachBPT.anchor[1] - scaledRadius / 2., scaledRadius, scaledRadius) if eachBPT.bcpIn != (0, 0): bcpInAbs = eachBPT.anchor[0] + eachBPT.bcpIn[ 0], eachBPT.anchor[1] + eachBPT.bcpIn[1] if bcpInAbs[0] % 4 != 0 or bcpInAbs[1] % 4 != 0: dt.oval(bcpInAbs[0] - scaledRadius / 2., bcpInAbs[1] - scaledRadius / 2., scaledRadius, scaledRadius) if eachBPT.bcpOut != (0, 0): bcpOutAbs = eachBPT.anchor[0] + eachBPT.bcpOut[ 0], eachBPT.anchor[1] + eachBPT.bcpOut[1] if bcpOutAbs[0] % 4 != 0 or bcpOutAbs[1] % 4 != 0: dt.oval(bcpOutAbs[0] - scaledRadius / 2., bcpOutAbs[1] - scaledRadius / 2., scaledRadius, scaledRadius) dt.restore() def _drawCentralBackgroundGlyph(self, glyph): dt.save() dt.fill(*LIGHT_GRAY_COLOR) dt.drawGlyph(glyph) dt.restore() def _drawGrids(self, frameOrigin, frameSize, italicAngle, scalingFactor): if not italicAngle: italicAngle = 0 for eachGridDescription in reversed(self.gridsDB): gridColor = eachGridDescription['color'] isHorizontal = eachGridDescription['horizontal'] isVertical = eachGridDescription['vertical'] gridStep = eachGridDescription['step'] if isHorizontal is False and isVertical is False: continue if not gridStep: continue dt.save() dt.skew(-italicAngle, 0) dt.stroke(*gridColor) dt.strokeWidth(.5 * scalingFactor) dt.fill(None) if isVertical is True: # to the right (from 0) extraSlant = int( ceil(tan(radians(italicAngle)) * frameOrigin[1])) for eachX in range(0, frameOrigin[0] + frameSize[0] + extraSlant, gridStep): dt.line((eachX, frameOrigin[1] - GRID_TOLERANCE), (eachX, frameOrigin[1] + frameSize[1] + GRID_TOLERANCE)) # to the left (from 0) extraSlant = int( ceil( tan(radians(italicAngle)) * (frameSize[1] + frameOrigin[1]))) for eachX in [ i for i in range(frameOrigin[0] + extraSlant, 0) if i % gridStep == 0 ]: dt.line((eachX, frameOrigin[1] - GRID_TOLERANCE), (eachX, frameOrigin[1] + frameSize[1] + GRID_TOLERANCE)) if isHorizontal is True: # to the top (from baseline) for eachY in range(0, frameOrigin[1] + frameSize[1], gridStep): dt.line((frameOrigin[0] - GRID_TOLERANCE, eachY), (frameOrigin[0] + frameSize[0] + GRID_TOLERANCE, eachY)) # to the bottom (from baseline) for eachY in [ i for i in range(frameOrigin[1], 0) if i % gridStep == 0 ]: dt.line((frameOrigin[0] - GRID_TOLERANCE, eachY), (frameOrigin[0] + frameSize[0] + GRID_TOLERANCE, eachY)) dt.restore() def _drawBcpLenght(self, glyph, scalingFactor, offset_X=0): for eachContour in glyph: for indexBPT, eachBPT in enumerate(eachContour.bPoints): if eachBPT.bcpOut != (0, 0): absBcpOut = eachBPT.anchor[0] + eachBPT.bcpOut[ 0], eachBPT.anchor[1] + eachBPT.bcpOut[1] bcpOutAngle = calcAngle(eachBPT.anchor, absBcpOut) bcpOutLenght = calcDistance(eachBPT.anchor, absBcpOut) captionBcpOut = u'→{:d}'.format(int(bcpOutLenght)) projOut_X = eachBPT.anchor[0] + cos( radians(bcpOutAngle)) * bcpOutLenght / 2. projOut_Y = eachBPT.anchor[1] + sin( radians(bcpOutAngle)) * bcpOutLenght / 2. textQualities(BODYSIZE_CAPTION * scalingFactor, weight='bold') textWidth, textHeight = dt.textSize(captionBcpOut) dt.save() dt.translate(projOut_X + offset_X, projOut_Y) dt.rotate(bcpOutAngle % 90) belowRect = (-textWidth / 2. - 1, -textHeight / 2. - 1, textWidth + 2, textHeight + 2, 1) dt.fill(0, 0, 0, .7) dt.roundedRect(*belowRect) textRect = (-textWidth / 2., -textHeight / 2., textWidth, textHeight) textQualities(BODYSIZE_CAPTION * scalingFactor, weight='bold', color=WHITE_COLOR) dt.textBox(captionBcpOut, textRect, align='center') dt.restore() if eachBPT.bcpIn != (0, 0): absBcpIn = eachBPT.anchor[0] + eachBPT.bcpIn[ 0], eachBPT.anchor[1] + eachBPT.bcpIn[1] bcpInAngle = calcAngle(eachBPT.anchor, absBcpIn) bcpInLenght = calcDistance(eachBPT.anchor, absBcpIn) captionBcpIn = u'→{:d}'.format(int(bcpInLenght)) projIn_X = eachBPT.anchor[0] + cos( radians(bcpInAngle)) * bcpInLenght / 2. projIn_Y = eachBPT.anchor[1] + sin( radians(bcpInAngle)) * bcpInLenght / 2. textQualities(BODYSIZE_CAPTION * scalingFactor, weight='bold') textWidth, textHeight = dt.textSize(captionBcpIn) dt.save() dt.translate(projIn_X + offset_X, projIn_Y) dt.rotate(bcpInAngle % 90) belowRect = (-textWidth / 2. - 1, -textHeight / 2. - 1, textWidth + 2, textHeight + 2, 1) dt.fill(0, 0, 0, .7) dt.roundedRect(*belowRect) textQualities(BODYSIZE_CAPTION * scalingFactor, weight='bold', color=WHITE_COLOR) textRect = (-textWidth / 2., -textHeight / 2., textWidth, textHeight) dt.textBox(captionBcpIn, textRect, align='center') dt.restore() def _drawSquarings(self, glyph, scalingFactor, offset_X=0): for eachContour in glyph: for indexBPT, eachBPT in enumerate(eachContour.bPoints): if eachBPT.bcpOut != (0, 0): nextBPT = eachContour.bPoints[(indexBPT + 1) % len(eachContour.bPoints)] absBcpOut = (eachBPT.anchor[0] + eachBPT.bcpOut[0], eachBPT.anchor[1] + eachBPT.bcpOut[1]) angleOut = calcAngle(eachBPT.anchor, absBcpOut) handleOutLen = calcDistance(eachBPT.anchor, absBcpOut) absBcpIn = (nextBPT.anchor[0] + nextBPT.bcpIn[0], nextBPT.anchor[1] + nextBPT.bcpIn[1]) angleIn = calcAngle(nextBPT.anchor, absBcpIn) nextHandleInLen = calcDistance(nextBPT.anchor, absBcpIn) handlesIntersection = intersectionBetweenSegments( eachBPT.anchor, absBcpOut, absBcpIn, nextBPT.anchor) if handlesIntersection is not None: maxOutLen = calcDistance(eachBPT.anchor, handlesIntersection) maxInLen = calcDistance(nextBPT.anchor, handlesIntersection) sqrOut = handleOutLen / maxOutLen sqrIn = nextHandleInLen / maxInLen projOut_X = eachBPT.anchor[0] + cos( radians(angleOut)) * handleOutLen projOut_Y = eachBPT.anchor[1] + sin( radians(angleOut)) * handleOutLen if angleOut != 0 and angleOut % 90 != 0: captionSqrOut = u'{:.0%}, {:d}°'.format( sqrOut, int(angleOut % 180)) else: captionSqrOut = '{:.0%}'.format(sqrOut) captionSqrOut = captionSqrOut.replace('0.', '') dt.save() dt.translate(projOut_X + offset_X, projOut_Y) textQualities(BODYSIZE_CAPTION * scalingFactor) textWidth, textHeight = dt.textSize(captionSqrOut) if angleOut == 90: # text above textRect = (-textWidth / 2., SQR_CAPTION_OFFSET * scalingFactor, textWidth, textHeight) elif angleOut == -90: # text below textRect = (-textWidth / 2., -textHeight - SQR_CAPTION_OFFSET * scalingFactor, textWidth, textHeight) elif -90 < angleOut < 90: # text on the right textRect = (SQR_CAPTION_OFFSET * scalingFactor, -textHeight / 2., textWidth, textHeight) else: # text on the left textRect = (-textWidth - SQR_CAPTION_OFFSET * scalingFactor, -textHeight / 2., textWidth, textHeight) dt.textBox(captionSqrOut, textRect, align='center') dt.restore() projIn_X = nextBPT.anchor[0] + cos( radians(angleIn)) * nextHandleInLen projIn_Y = nextBPT.anchor[1] + sin( radians(angleIn)) * nextHandleInLen if angleIn != 0 and angleIn % 90 != 0: captionSqrIn = u'{:.0%}, {:d}°'.format( sqrIn, int(angleIn % 180)) else: captionSqrIn = '{:.0%}'.format(sqrIn) captionSqrIn = captionSqrIn.replace('0.', '') dt.save() dt.translate(projIn_X + offset_X, projIn_Y) textQualities(BODYSIZE_CAPTION * scalingFactor) textWidth, textHeight = dt.textSize(captionSqrIn) if angleIn == 90: # text above textRect = (-textWidth / 2., SQR_CAPTION_OFFSET * scalingFactor, textWidth, textHeight) elif angleIn == -90: # text below textRect = (-textWidth / 2., -textHeight - SQR_CAPTION_OFFSET * scalingFactor, textWidth, textHeight) elif -90 < angleIn < 90: # text on the right textRect = (SQR_CAPTION_OFFSET * scalingFactor, -textHeight / 2., textWidth, textHeight) else: # text on the left textRect = (-textWidth - SQR_CAPTION_OFFSET * scalingFactor, -textHeight / 2., textWidth, textHeight) dt.textBox(captionSqrIn, textRect, align='center') dt.restore() def _drawGlyphOutline(self, glyph, scalingFactor, offset_X=0): dt.save() dt.translate(offset_X, 0) dt.fill(None) dt.strokeWidth(1 * scalingFactor) dt.stroke(*LIGHT_GRAY_COLOR) dt.drawGlyph(glyph) scaledRadius = BCP_RADIUS * scalingFactor for eachContour in glyph: for eachBPT in eachContour.bPoints: dt.stroke(None) dt.fill(*LIGHT_GRAY_COLOR) dt.rect(eachBPT.anchor[0] - scaledRadius / 2., eachBPT.anchor[1] - scaledRadius / 2., scaledRadius, scaledRadius) if eachBPT.bcpIn != (0, 0): dt.stroke(None) dt.fill(*LIGHT_GRAY_COLOR) dt.oval( eachBPT.anchor[0] + eachBPT.bcpIn[0] - scaledRadius / 2., eachBPT.anchor[1] + eachBPT.bcpIn[1] - scaledRadius / 2., scaledRadius, scaledRadius) dt.stroke(*LIGHT_GRAY_COLOR) dt.fill(None) dt.line((eachBPT.anchor[0], eachBPT.anchor[1]), (eachBPT.anchor[0] + eachBPT.bcpIn[0], eachBPT.anchor[1] + eachBPT.bcpIn[1])) if eachBPT.bcpOut != (0, 0): dt.stroke(None) dt.fill(*LIGHT_GRAY_COLOR) dt.oval( eachBPT.anchor[0] + eachBPT.bcpOut[0] - scaledRadius / 2., eachBPT.anchor[1] + eachBPT.bcpOut[1] - scaledRadius / 2., scaledRadius, scaledRadius) dt.stroke(*LIGHT_GRAY_COLOR) dt.fill(None) dt.line((eachBPT.anchor[0], eachBPT.anchor[1]), (eachBPT.anchor[0] + eachBPT.bcpOut[0], eachBPT.anchor[1] + eachBPT.bcpOut[1])) dt.restore() def _drawGlyphBlack(self, glyph, scalingFactor, offset_X=0): dt.save() dt.translate(offset_X, 0) dt.fill(*BLACK_COLOR) dt.stroke(None) dt.drawGlyph(glyph) dt.restore() def _drawStems(self, currentGlyph, scalingFactor, offset_X=0): if STEM_KEY not in currentGlyph.lib: return None stemData = calcStemsData(currentGlyph, STEM_KEY) for PTs, DIFFs, middlePoint in stemData: pt1, pt2 = PTs horDiff, verDiff = DIFFs dt.save() dt.translate(offset_X, 0) dt.stroke(*self.stemColor) dt.fill(None) dt.strokeWidth(1 * scalingFactor) dt.newPath() if horDiff > verDiff: # ver rightPt, leftPt = PTs if pt1.x > pt2.x: rightPt, leftPt = leftPt, rightPt dt.moveTo((leftPt.x, leftPt.y)) dt.curveTo((leftPt.x - horDiff / 2, leftPt.y), (rightPt.x + horDiff / 2, rightPt.y), (rightPt.x, rightPt.y)) else: # hor topPt, btmPt = PTs if pt2.y > pt1.y: btmPt, topPt = topPt, btmPt dt.moveTo((btmPt.x, btmPt.y)) dt.curveTo((btmPt.x, btmPt.y + verDiff / 2), (topPt.x, topPt.y - verDiff / 2), (topPt.x, topPt.y)) dt.drawPath() dt.restore() dt.save() dt.translate(offset_X, 0) textQualities(BODYSIZE_CAPTION * scalingFactor) dataToPlot = u'↑{:d}\n→{:d}'.format(int(verDiff), int(horDiff)) textWidth, textHeight = dt.textSize(dataToPlot) textRect = (middlePoint[0] - textWidth / 2., middlePoint[1] - textHeight / 2., textWidth, textHeight) dt.textBox(dataToPlot, textRect, align='center') dt.restore() def _drawDiagonals(self, currentGlyph, scalingFactor, offset_X=0): if DIAGONALS_KEY not in currentGlyph.lib: return None diagonalsData = calcDiagonalsData(currentGlyph, DIAGONALS_KEY) for ptsToDisplay, angle, distance in diagonalsData: pt1, pt2 = ptsToDisplay dt.save() dt.stroke(*self.diagonalColor) dt.fill(None) dt.strokeWidth(1 * scalingFactor) if 90 < angle <= 180 or -180 < angle < -90: direction = -1 adjustedAngle = angle + 180 + 90 else: direction = 1 adjustedAngle = angle + 90 diagonalPt1 = pt1[0] + cos(radians(adjustedAngle)) * ( (DIAGONAL_OFFSET - 1) * direction), pt1[1] + sin( radians(adjustedAngle)) * ( (DIAGONAL_OFFSET - 1) * direction) diagonalPt2 = pt2[0] + cos(radians(adjustedAngle)) * ( (DIAGONAL_OFFSET - 1) * direction), pt2[1] + sin( radians(adjustedAngle)) * ( (DIAGONAL_OFFSET - 1) * direction) offsetPt1 = pt1[0] + cos(radians( adjustedAngle)) * DIAGONAL_OFFSET * direction, pt1[1] + sin( radians(adjustedAngle)) * DIAGONAL_OFFSET * direction offsetPt2 = pt2[0] + cos(radians( adjustedAngle)) * DIAGONAL_OFFSET * direction, pt2[1] + sin( radians(adjustedAngle)) * DIAGONAL_OFFSET * direction dt.line((pt1), (offsetPt1)) dt.line((pt2), (offsetPt2)) dt.line((diagonalPt1), (diagonalPt2)) dt.restore() dt.save() textQualities(BODYSIZE_CAPTION * scalingFactor) offsetMidPoint = calcMidPoint(offsetPt1, offsetPt2) dt.translate(offsetMidPoint[0], offsetMidPoint[1]) if 90 < angle <= 180 or -180 < angle < -90: dt.rotate(angle + 180) textBoxY = -BODYSIZE_CAPTION * 1.2 * scalingFactor else: dt.rotate(angle) textBoxY = 0 dataToPlot = u'∡{:.1f} ↗{:d}'.format(angle % 180, int(distance)) textWidth, textHeight = dt.textSize(dataToPlot) dt.textBox(dataToPlot, (-textWidth / 2., textBoxY, textWidth, BODYSIZE_CAPTION * 1.2 * scalingFactor), align='center') dt.restore() # ui callback def collectOpenedFontPaths(self): self.openedFontPaths = [ f.path for f in AllFonts() if f.path is not None ] self.openedFontPaths.sort() def aFontIsClosing(self, infoDict): willCloseFont = infoDict['font'] self.openedFontPaths.remove(willCloseFont.path) for eachFontAttrName, eachGlyphAttrName in [ ('lftFontPath', 'lftGlyphName'), ('cntFontPath', 'cntGlyphName'), ('rgtFontPath', 'rgtGlyphName') ]: if getattr(self, eachFontAttrName) is willCloseFont: anotherFont = getOpenedFontFromPath(AllFonts(), self.openedFontPaths[0]) setattr(self, eachFontAttrName, anotherFont) self.pushFontAttr(eachFontAttrName) activeName = getattr(self, eachGlyphAttrName).name if anotherFont.has_key(activeName): setattr(self, eachGlyphAttrName, anotherFont[activeName]) else: setattr(self, eachGlyphAttrName, anotherFont[anotherFont.glyphOrder[0]]) self.pushGlyphAttr(eachGlyphAttrName) self.neighborsController.setFontData(self.openedFontPaths) def pushFontAttr(self, attrName): fontToBePushed = getattr(self, attrName) self.neighborsController.neighborsDB[attrName][1] = fontToBePushed setattr(self.neighborsController, '{}Controller.activeFont'.format(attrName[:3], fontToBePushed)) getattr(self.neighborsController, '{}Controller'.format(attrName[:3]).updateGlyphList()) def pushGlyphAttr(self, attrName): glyphToBePushed = getattr(self, attrName) self.neighborsController.neighborsDB[attrName][2] = glyphToBePushed setattr( self.neighborsController, '{}Controller.activeGlyph'.format(attrName[:3], glyphToBePushed)) getattr(self.neighborsController, '{}Controller'.format(attrName[:3]).updateGlyphList()) def aFontIsOpening(self, infoDict): originalState = list(self.openedFontPaths) self.openedFontPaths.append(infoDict['font'].path) self.openedFontPaths.sort() if originalState == []: self._initFontsAndGlyphs() for eachFontAttrName, eachGlyphAttrName in [ ('lftFontPath', 'lftGlyphName'), ('cntFontPath', 'cntGlyphName'), ('rgtFontPath', 'rgtGlyphName') ]: self.pushFontAttr(eachFontAttrName) self.pushGlyphAttr(eachGlyphAttrName) self.neighborsController.setFontData(self.openedFontPaths) def _initFontsAndGlyphs(self): for eachFontAttrName, eachGlyphAttrName in [ ('lftFontPath', 'lftGlyphName'), ('cntFontPath', 'cntGlyphName'), ('rgtFontPath', 'rgtGlyphName') ]: setattr(self, eachFontAttrName, CURRENT_FONT_REPR) setattr(self, eachGlyphAttrName, CURRENT_GLYPH_REPR) def _updateCurrentGlyph(self, infoDict): if infoDict['glyph']: # update distances self.currentGlyph = infoDict['glyph'] self.distancesController.setCurrentGlyph(self.currentGlyph) # update neighbors self.neighborsController.lftController.updateCurrentGlyph() self.neighborsController.cntController.updateCurrentGlyph() self.neighborsController.rgtController.updateCurrentGlyph() def _updateCurrentFont(self, infoDict): if infoDict['font']: self.currentGlyph = CurrentGlyph() self.distancesController.setCurrentGlyph(self.currentGlyph) # update neighbors self.neighborsController.lftController.updateCurrentGlyph() self.neighborsController.cntController.updateCurrentGlyph() self.neighborsController.rgtController.updateCurrentGlyph() # controls callbacks def windowCloseCallback(self, sender): removeObserver(self, 'draw') removeObserver(self, 'drawInactive') removeObserver(self, 'drawBackground') removeObserver(self, 'drawPreview') removeObserver(self, 'mouseDown') removeObserver(self, 'viewDidChangeGlyph') removeObserver(self, 'newFontDidOpen') removeObserver(self, 'fontWillClose') removeObserver(self, 'fontDidOpen') def bcpControllerCallback(self, sender): self.sqrActive = sender.getSqr() self.bcpLengthActive = sender.getBcpLength() UpdateCurrentGlyphView() def gridControllerCallback(self, sender): self.gridActive, self.gridsDB, self.offgridActive = sender.get() UpdateCurrentGlyphView() def distancesControllerCallback(self, sender): self.stemActive, self.stemColor, self.diagonalActive, self.diagonalColor = sender.get( ) UpdateCurrentGlyphView() def neighborsControllerCallback(self, sender): neighborsDB = sender.get() self.lftNeighborActive = neighborsDB['lft'][0] self.lftFontPath = neighborsDB['lft'][1] self.lftGlyphName = neighborsDB['lft'][2] self.cntNeighborActive = neighborsDB['cnt'][0] self.cntFontPath = neighborsDB['cnt'][1] self.cntGlyphName = neighborsDB['cnt'][2] self.rgtNeighborActive = neighborsDB['rgt'][0] self.rgtFontPath = neighborsDB['rgt'][1] self.rgtGlyphName = neighborsDB['rgt'][2] UpdateCurrentGlyphView()