def buildCircledGlyph(thisGlyph, circleName, scaleFactors, minDistanceBetweenTwoLayers=90.0, suffix=None): isBlack = "black" in circleName.lower() thisFont = thisGlyph.font() thisGlyph.widthMetricsKey = None # "=%i" % thisFont.upm ) thisGlyph.leftMetricsKey = "=40" thisGlyph.rightMetricsKey = "=|" for i, thisMaster in enumerate(thisFont.masters): figureHeight = None scaleFactor = scaleFactors[i] if isBlack: scaleFactor = max(0.6, scaleFactor) circleGlyph = thisFont.glyphs[circleName] circleLayer = circleGlyph.layers[thisMaster.id] circleScaleFactor = thisFont.upm * 0.92 / max( thisFont.upm * 0.66, circleLayer.bounds.size.width) # prepare layer thisLayer = thisGlyph.layers[thisMaster.id] thisLayer.clear() # add circle: assumedCenter = NSPoint(thisFont.upm * 0.5, thisFont.upm * 0.3) # hardcoded circleComponent = GSComponent(circleName) thisLayer.components.append(circleComponent) # scale circle: circleScale = transform(scale=circleScaleFactor).transformStruct() circleComponent.applyTransform(circleScale) # move circle: circleBounds = thisLayer.components[0].bounds circleCenter = centerOfRect(circleBounds) xShift = assumedCenter.x - circleCenter.x yShift = assumedCenter.y - circleCenter.y circleShift = transform(shiftX=xShift, shiftY=yShift).transformStruct() circleComponent.applyTransform(circleShift) # update metrics: thisLayer.updateMetrics() thisLayer.syncMetrics() # find number and letter components to add: suffixlessName = thisGlyph.name if "." in suffixlessName: suffixlessName = thisGlyph.name[:thisGlyph.name.find(".")] componentNames = suffixlessName.split("_") # add one component in the center: if componentNames: advance = 0 for j, compName in enumerate(componentNames): lfName = "%s.lf" % compName osfName = "%s.osf" % compName namesToCheck = [compName] extraSuffixes = (".osf", ".lf") for extraSuffix in extraSuffixes: namesToCheck.insert(0, compName + extraSuffix) if suffix: for existingName in namesToCheck[:]: namesToCheck.insert(0, existingName + suffix) for nameToCheck in namesToCheck: if thisFont.glyphs[nameToCheck]: compName = nameToCheck break innerComponent = GSComponent(compName) innerComponent.automaticAlignment = False thisLayer.components.append(innerComponent) innerComponent.position = NSPoint(advance, 0.0) if j > 0: innerComponent.disableAlignment = True placeComponentsAtDistance( thisLayer, thisLayer.components[-2], thisLayer.components[-1], # same as innerComponent distance=minDistanceBetweenTwoLayers) originalLayerWidth = thisFont.glyphs[compName].layers[ thisMaster.id].width advance += originalLayerWidth collectedBounds = [c.bounds for c in thisLayer.components[1:]] compCenter = centerOfRect(combinedBounds(collectedBounds)) centerAnchor = thisLayer.anchorForName_traverseComponents_( "#center", True) if centerAnchor: circleCenter = centerAnchor.position else: circleCenter = centerOfRect(circleComponent.bounds) # scale and move it in place: shift = transform(shiftX=-compCenter.x, shiftY=-compCenter.y).transformStruct() scaleToFit = transform(scale=scaleFactor * circleScaleFactor).transformStruct() backshift = transform(shiftX=circleCenter.x, shiftY=circleCenter.y).transformStruct() compensateStroke = [] for innerComponent in thisLayer.components[1:]: # optically shift so top anchor is in center: originalLayer = topAnchor = innerComponent.component.layers[ thisMaster.id] topAnchor = originalLayer.anchors["top"] if topAnchor: anchorCenter = topAnchor.x boundsCenter = centerOfRect(originalLayer.bounds).x opticalCorrection = boundsCenter - anchorCenter if opticalCorrection != 0.0: threshold = 35.0 if abs(opticalCorrection) > threshold: posNeg = opticalCorrection / abs(opticalCorrection) rest = abs(opticalCorrection) - threshold opticalCorrection = posNeg * (threshold + rest * 1 / rest**0.3) print("--", opticalCorrection) opticalShift = transform( shiftX=opticalCorrection).transformStruct() innerComponent.applyTransform(opticalShift) innerComponent.applyTransform(shift) innerComponent.applyTransform(scaleToFit) innerComponent.applyTransform(backshift) # move components closer to center: #move = 15.0 #hOffset = circleCenter.x - centerOfRect(innerComponent.bounds).x #if abs(hOffset) > move: # hOffset = (hOffset/abs(hOffset))*move #if hOffset != 0.0: # moveCloser = transform( shiftX=hOffset ).transformStruct() # innerComponent.applyTransform( moveCloser ) # compensatory shift: if thisGlyph.name in ("two_zero.circled", "one_nine.circled", "one_zero.circled"): compensate = transform(shiftX=10.0).transformStruct() innerComponent.applyTransform(compensate) if innerComponent.component.glyphInfo.category == "Number": if figureHeight == None: figureHeight = innerComponent.position.y else: innerComponent.position.y = figureHeight compensateStroke.append(innerComponent) # make slightly bolder: isNumber = False for i in range(len(compensateStroke))[::-1]: componentToDecompose = compensateStroke[i] if componentToDecompose.component.category == "Number": isNumber = True thisLayer.decomposeComponent_(componentToDecompose) offsetLayer(thisLayer, 4.0) #4.0 if isNumber else 3.0 ) if thisLayer.paths and isBlack: thisLayer.removeOverlap() for thisPath in thisLayer.paths: # set first node (make compatible again after remove overlap): lowestY = thisPath.bounds.origin.y lowestNodes = [n for n in thisPath.nodes if n.y <= lowestY] if len(lowestNodes) == 0: lowestNode = sorted(lowestNodes, key=lambda node: node.y)[0] elif len(lowestNodes) == 1: lowestNode = lowestNodes[0] elif len(lowestNodes) > 1: lowestNode = sorted(lowestNodes, key=lambda node: node.x)[0] while lowestNode.type == GSOFFCURVE: lowestNode = lowestNode.nextNode thisPath.makeNodeFirst_(lowestNode) # reverse (white on black): thisPath.reverse() thisLayer.anchors = None for thisComp in thisLayer.components: if thisComp.componentName == circleName: thisComp.locked = True
def BatchInsertAnchorMain(self, sender): try: # update settings to the latest user input: if not self.SavePreferences(self): print( "Note: 'Batch Insert Anchor' could not write preferences.") thisFont = Glyphs.font # frontmost font print("Batch Insert Anchor Report for %s" % thisFont.familyName) print(thisFont.filepath) print() anchorName = Glyphs.defaults[ "com.mekkablue.BatchInsertAnchor.anchorName"] xPos = Glyphs.defaults["com.mekkablue.BatchInsertAnchor.xPos"] yPos = Glyphs.defaults["com.mekkablue.BatchInsertAnchor.yPos"] replaceExisting = Glyphs.defaults[ "com.mekkablue.BatchInsertAnchor.replaceExisting"] selectedLayers = thisFont.selectedLayers selectedGlyphNames = list( set([l.parent.name for l in selectedLayers if l.parent])) print("Inserting anchor '%s' at %s and %s in %i glyphs...\n" % ( anchorName, xPositions[xPos], yPositions[yPos], len(selectedGlyphNames), )) for glyphName in selectedGlyphNames: print("🔠 %s" % glyphName) glyph = thisFont.glyphs[glyphName] if not glyph: print("⛔️ Error: could not find glyph %s. Skipping.\n" % glyphName) else: for thisLayer in glyph.layers: if replaceExisting or not thisLayer.anchors[anchorName]: if xPos == 0: x = thisLayer.width // 2 elif xPos == 1: # "LSB" x = 0.0 elif xPos == 2: # "RSB" x = thisLayer.width elif xPos == 3: # "BBox Left Edge" x = thisLayer.bounds.origin.x elif xPos == 4: # "BBox Horizontal Center" x = thisLayer.bounds.origin.x + thisLayer.bounds.size.width // 2 elif xPos == 5: # "BBox Right Edge" x = thisLayer.bounds.origin.x + thisLayer.bounds.size.width if yPos == 0: # "Baseline" y = 0.0 if yPos == 1: # "x-Height" y = thisLayer.master.xHeight if yPos == 2: # "Smallcap Height" y = thisLayer.master.customParameters[ "smallCapHeight"] if not y: # Fallback if not set: y = thisLayer.master.xHeight if yPos == 3: # "Cap Height" y = thisLayer.master.capHeight if yPos == 4: # "Ascender" y = thisLayer.master.ascender if yPos == 5: # "Shoulder Height" y = thisLayer.master.customParameters[ "shoulderHeight"] if not y: # Fallback if not set: y = thisLayer.master.capHeight if yPos == 6: # "Descender" y = thisLayer.master.descender if yPos == 7: # "BBox Top" y = thisLayer.bounds.origin.y + thisLayer.bounds.size.height if yPos == 8: # "BBox Center" y = thisLayer.bounds.origin.y + thisLayer.bounds.size.height // 2 if yPos == 9: # "BBox Bottom" y = thisLayer.bounds.origin.y anchor = GSAnchor() anchor.name = anchorName anchor.position = NSPoint(x, y) thisLayer.anchors.append(anchor) except Exception as e: # brings macro window to front and reports error: Glyphs.showMacroWindow() print("Batch Insert Anchor Error: %s" % e) import traceback print(traceback.format_exc())
def circleInsideRect(rect): """Returns a GSPath for a circle inscribed into the NSRect rect.""" MAGICNUMBER = 4.0 * (2.0**0.5 - 1.0) / 3.0 x = rect.origin.x y = rect.origin.y height = rect.size.height width = rect.size.width fullHeight = y + height fullWidth = x + width halfHeight = y + 0.5 * height halfWidth = x + 0.5 * width horHandle = width * 0.5 * MAGICNUMBER * 1.05 verHandle = height * 0.5 * MAGICNUMBER * 1.05 segments = ((NSPoint(x, halfHeight - verHandle), NSPoint(halfWidth - horHandle, y), NSPoint(halfWidth, y)), (NSPoint(halfWidth + horHandle, y), NSPoint(fullWidth, halfHeight - verHandle), NSPoint(fullWidth, halfHeight)), (NSPoint(fullWidth, halfHeight + verHandle), NSPoint(halfWidth + horHandle, fullHeight), NSPoint(halfWidth, fullHeight)), (NSPoint(halfWidth - horHandle, fullHeight), NSPoint(x, halfHeight + verHandle), NSPoint(x, halfHeight))) circlePath = GSPath() for thisSegment in segments: for i in range(3): nodeType = (GSOFFCURVE, GSOFFCURVE, GSCURVE)[i] nodePos = thisSegment[i] newNode = GSNode() newNode.position = nodePos newNode.type = nodeType newNode.connection = GSSMOOTH circlePath.nodes.append(newNode) print(circlePath) for n in circlePath.nodes: print(" ", n) circlePath.closed = True return circlePath
def interpolatedPosition(self, foregroundPosition, foregroundFactor, backgroundPosition, backgroundFactor): interpolatedX = foregroundPosition.x * foregroundFactor + backgroundPosition.x * backgroundFactor interpolatedY = foregroundPosition.y * foregroundFactor + backgroundPosition.y * backgroundFactor interpolatedPosition = NSPoint(interpolatedX, interpolatedY) return interpolatedPosition
def lerp(t, a, b): return NSValue.valueWithPoint_( NSPoint(int((1 - t) * a.x + t * b.x), int((1 - t) * a.y + t * b.y)))
def realignLayer(self, thisLayer, shouldRealign=False, shouldReport=False, shouldVerbose=False): moveForward = NSPoint(1, 0) moveBackward = NSPoint(-1, 0) noModifier = NSNumber.numberWithUnsignedInteger_(0) layerCount = 0 if thisLayer: for thisPath in thisLayer.paths: oldPathCoordinates = [n.position for n in thisPath.nodes] for i, thisNode in enumerate(thisPath.nodes): if thisNode.type == GSOFFCURVE: # oldPosition = NSPoint(thisNode.position.x, thisNode.position.y) oncurve = None if thisNode.prevNode.type != GSOFFCURVE: oncurve = thisNode.prevNode opposingPoint = oncurve.prevNode elif thisNode.nextNode.type != GSOFFCURVE: oncurve = thisNode.nextNode opposingPoint = oncurve.nextNode handleStraight = (oncurve.x - thisNode.x) * ( oncurve.y - thisNode.y) == 0.0 if oncurve and oncurve.smooth and not handleStraight: # thisNode = angled handle, straighten it thisPath.setSmooth_withCenterPoint_oppositePoint_( thisNode, oncurve.position, opposingPoint.position, ) elif oncurve and opposingPoint and oncurve.smooth and handleStraight and opposingPoint.type == GSOFFCURVE: # thisNode = straight handle: align opposite handle thisPath.setSmooth_withCenterPoint_oppositePoint_( opposingPoint, oncurve.position, thisNode.position, ) else: selectedNode = NSMutableArray.arrayWithObject_( thisNode) thisLayer.setSelection_(selectedNode) self.Tool.moveSelectionLayer_shadowLayer_withPoint_withModifier_( thisLayer, thisLayer, moveForward, noModifier) self.Tool.moveSelectionLayer_shadowLayer_withPoint_withModifier_( thisLayer, thisLayer, moveBackward, noModifier) # TODO: # recode with GSPath.setSmooth_withCenterNode_oppositeNode_() for i, coordinate in enumerate(oldPathCoordinates): if thisPath.nodes[i].position != coordinate: layerCount += 1 # put handle back if not desired by user: if not shouldRealign: thisPath.nodes[i].position = coordinate thisLayer.setSelection_(()) if shouldReport and shouldVerbose: if layerCount: if shouldRealign: print(u" ⚠️ Realigned %i handle%s." % (layerCount, "" if layerCount == 1 else "s")) else: print(u" ❌ %i handle%s are unaligned." % (layerCount, "" if layerCount == 1 else "s")) else: print(u" ✅ All BCPs OK.") return layerCount
def interruptwait(): """ If waituntil() has been called, this will interrupt the waiting process so it can check whether it should stop waiting. """ evt = NSEvent.otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_(NSApplicationDefined, NSPoint(), NSApplicationDefined, 0, 1, None, LIGHTBLUE_NOTIFY_ID, 0, 0) NSApplication.sharedApplication().postEvent_atStart_(evt, True)
def smallFigureBuilderMain( self, sender ): try: if not self.SavePreferences( self ): print("Note: 'Build Small Figures' could not write preferences.") thisFont = Glyphs.font # frontmost font # report in macro window: Glyphs.clearLog() print("Build Small Figures, report for: %s" % thisFont.familyName) if thisFont.filepath: print(thisFont.filepath) print() # parse user entries and preferences: default = Glyphs.defaults["com.mekkablue.smallFigureBuilder.default"].strip() derive = Glyphs.defaults["com.mekkablue.smallFigureBuilder.derive"] currentMasterOnly = Glyphs.defaults["com.mekkablue.smallFigureBuilder.currentMasterOnly"] decomposeDefaultFigures = Glyphs.defaults["com.mekkablue.smallFigureBuilder.decomposeDefaultFigures"] figures = ("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine") offsets={} for suffixPair in [pair.split(":") for pair in derive.split(",")]: suffix = suffixPair[0].strip() value = float(suffixPair[1].strip()) offsets[suffix] = value createdGlyphCount = 0 updatedGlyphCount = 0 # go through 1, 2, 3, 4, 5... for i,fig in enumerate(figures): # determine default glyph: defaultGlyphName = "%s%s" % (fig,default) defaultGlyph = thisFont.glyphs[defaultGlyphName] if not defaultGlyph: print("\nNot found: %s" % defaultGlyphName) else: print("\n%s Deriving from %s:" % ( "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣"[i*3:i*3+3], # it is actually three unicodes defaultGlyphName, )) # decompose if necessary: if decomposeDefaultFigures: print(" - decomposing %s" % defaultGlyphName) if currentMasterOnly: mID = thisFont.selectedFontMaster.id else: mID = None self.decomposeComponents(defaultGlyph) # step through derivative suffixes: for deriveSuffix in offsets: # create or overwrite derived glyph: deriveGlyphName = "%s%s" % (fig,deriveSuffix) deriveGlyph = thisFont.glyphs[deriveGlyphName] if not deriveGlyph: deriveGlyph = GSGlyph(deriveGlyphName) thisFont.glyphs.append(deriveGlyph) print(" - creating new glyph %s" % deriveGlyphName) createdGlyphCount += 1 else: print(" - overwriting glyph %s" % deriveGlyphName) updatedGlyphCount += 1 # copy glyph attributes: deriveGlyph.leftKerningGroup = defaultGlyph.leftKerningGroup deriveGlyph.rightKerningGroup = defaultGlyph.rightKerningGroup # reset category & subcategory: deriveGlyph.updateGlyphInfo() # add component on each master layer: for thisMaster in thisFont.masters: isCurrentMaster = thisMaster is thisFont.selectedFontMaster if isCurrentMaster or not currentMasterOnly: mID = thisMaster.id offset = offsets[deriveSuffix] offsetPos = NSPoint(0,offset) if thisMaster.italicAngle != 0.0: offsetPos = italicize(offsetPos,italicAngle=thisMaster.italicAngle) defaultComponent = GSComponent(defaultGlyphName,offsetPos) deriveLayer = deriveGlyph.layers[mID] deriveLayer.clear() try: # GLYPHS 3: deriveLayer.shapes.append(defaultComponent) except: # GLYPHS 2: deriveLayer.components.append(defaultComponent) # open a new tab if requested if Glyphs.defaults["com.mekkablue.smallFigureBuilder.openTab"]: tabText = "" for suffix in [default] + sorted(offsets.keys()): escapedFigureNames = ["/%s%s"%(fig,suffix) for fig in figures] tabText += "".join(escapedFigureNames) tabText += "\n" tabText = tabText.strip() if thisFont.currentTab and Glyphs.defaults["com.mekkablue.smallFigureBuilder.reuseTab"]: # reuses current tab: thisFont.currentTab.text = tabText else: # opens new Edit tab: thisFont.newTab( tabText ) # Floating notification: Glyphs.showNotification( u"%s: small figures built" % (thisFont.familyName), u"%i glyph%s created, %i glyph%s updated. Detailed info in Macro Window." % ( createdGlyphCount, "" if createdGlyphCount==1 else "s", updatedGlyphCount, "" if updatedGlyphCount==1 else "s", ), ) except Exception as e: # brings macro window to front and reports error: Glyphs.showMacroWindow() print("Build Small Figures Error: %s" % e) import traceback print(traceback.format_exc())
def drawTopOrBottom(self, bbox, defaultColor, zones, top, xHeight, italicAngle): try: bboxOrigin = bbox.origin bboxSize = bbox.size left = bboxOrigin.x right = left + bboxSize.width middle = left + bboxSize.width / 2.0 position = bboxOrigin.y surplus = 30.0 scale = self.getScale() numberDistance = 25.0 / scale lineDistance = 10.0 / scale # adjust values for top/bottom: if top: position += bboxSize.height numberDistance -= 10.0 / scale else: numberDistance *= -1 lineDistance *= -1 # adjust values for italic angle: if italicAngle != 0.0: offset = (position - xHeight * 0.5) * math.tan( italicAngle * math.pi / 180.0) left += offset right += offset middle += offset # draw it red if it is not inside a zone: drawColor = NSColor.redColor() for thisZone in zones: zoneBegin = thisZone[0] zoneEnd = zoneBegin + thisZone[1] zoneBottom = min((zoneBegin, zoneEnd)) zoneTop = max((zoneBegin, zoneEnd)) if position <= zoneTop and position >= zoneBottom: drawColor = defaultColor # set line attributes: drawColor.set() storedLineWidth = NSBezierPath.defaultLineWidth() NSBezierPath.setDefaultLineWidth_(1.0 / scale) # draw horizontal line on canvas: leftPoint = NSPoint(left - surplus, position) rightPoint = NSPoint(right + surplus, position) NSBezierPath.strokeLineFromPoint_toPoint_(leftPoint, rightPoint) # draw vertical line on canvas: startPoint = NSPoint(middle, position) endPoint = NSPoint(middle, position + lineDistance) NSBezierPath.strokeLineFromPoint_toPoint_(startPoint, endPoint) # restore default line width: NSBezierPath.setDefaultLineWidth_(storedLineWidth) # draw number on canvas: self.drawTextAtPoint("%.1f" % position, NSPoint(middle, position + numberDistance), fontColor=drawColor) except Exception as e: self.logToConsole("drawBottom: %s" % str(e))
#MenuTitle: Cut Cap # -*- coding: utf-8 -*- __doc__ = """ Cut the terminal of strokes with a `_cap.cup` glyph. """ from Foundation import NSPoint CAP_SIZE = 100.0 CAP_NAME = '_cap.cup' for path in Glyphs.font.selectedLayers[0].paths: selected_nodes = [i for i in path.nodes if i.selected] if len(selected_nodes) == 2: (node0, node1) = tuple(selected_nodes) scale = abs(node0.y - node1.y) / CAP_SIZE cap = GSHint() cap.type = CAP cap.name = CAP_NAME cap.scale = NSPoint(scale, scale) (cap.originNode, cap.targetNode) = (node0, node1) Glyphs.font.selectedLayers[0].hints.append(cap)
def copyAnchor(thisLayer, anchorName, anchorX, anchorY): newAnchor = GSAnchor.alloc().init() newAnchor.name = anchorName thisLayer.addAnchor_(newAnchor) newPosition = NSPoint(anchorX, anchorY) newAnchor.setPosition_(newPosition)
pivotalY=0.0 ): # don't change x to y for horizontal / vertical DIRECTION x = thisPoint.x yOffset = thisPoint.y - pivotalY # calculate vertical offset italicAngle = radians(italicAngle) # convert to radians tangens = tan(italicAngle) # math.tan needs radians horizontalDeviance = tangens * yOffset # vertical distance from pivotal point x += horizontalDeviance # x of point that is yOffset from pivotal point return NSPoint(int(x), thisPoint.y) # move on curves for node in selection: if node.nextNode.type != 'offcurve' and node.nextNode.nextNode.type == 'offcurve': # oncurve node.nextNode.x = node.x node.nextNode.y = node.y node.nextNode.smooth = False # offcurve node.nextNode.nextNode.x = italicize( NSPoint(node.x, node.nextNode.nextNode.y), italicAngle, node.y)[0] elif node.prevNode.type != 'offcurve' and node.prevNode.prevNode.type == 'offcurve': # oncurve node.prevNode.x = node.x node.prevNode.y = node.y node.prevNode.smooth = False # offcurve node.prevNode.prevNode.x = italicize( NSPoint(node.x, node.prevNode.prevNode.y), italicAngle, node.y)[0]
def set_content_size(self, size): ns = self._ns_view self.size = NSScrollView.\ frameSizeForContentSize_hasHorizontalScroller_hasVerticalScroller_borderType_( size, ns.hasHorizontalScroller(), ns.hasVerticalScroller(), ns.borderType()) def get_scroll_offset(self): ns_clip_view = self._ns_view.contentView() x, y = ns_clip_view.bounds().origin return x, y def set_scroll_offset(self, (x, y)): ns_view = self._ns_view ns_clip_view = ns_view.contentView() new_pt = ns_clip_view.constrainScrollPoint_(NSPoint(x, y)) ns_clip_view.scrollToPoint_(new_pt) ns_view.reflectScrolledClipView_(ns_clip_view) def get_line_scroll_amount(self): ns_view = self._ns_view x = ns_view.horizontalLineScroll() y = ns_view.verticalLineScroll() return x, y def set_line_scroll_amount(self, (x, y)): ns_view = self._ns_view ns_view.setHorizontalLineScroll_(x) ns_view.setVerticalLineScroll_(y) ns_view.setHorizontalPageScroll_(x) ns_view.setVerticalPageScroll_(y)
def middleBetweenTwoPoints(p1, p2): x = (p1.x + p2.x) * 0.5 y = (p1.y + p2.y) * 0.5 return NSPoint(x, y)
def insertAnchorsMain(self, sender): try: # update settings to the latest user input: if not self.SavePreferences(self): print("Note: 'Quote Manager' could not write preferences.") Glyphs.clearLog() Font = Glyphs.font # frontmost font # query suffix dotSuffix = self.getDotSuffix() # report: self.reportFont() print( "Inserting cursive attachment anchors in single quotes, and auto-aligning double quotes%s." % (" with suffix '%s'" % dotSuffix if dotSuffix else "")) defaultSingle, defaultDouble = self.defaultQuotes(dotSuffix) if defaultSingle and not Font.glyphs[defaultSingle]: self.reportMissingGlyph(defaultSingle) elif defaultDouble and not Font.glyphs[defaultDouble]: self.reportMissingGlyph(defaultDouble) else: for singleName in names: doubleName = names[singleName] if dotSuffix: doubleName += dotSuffix singleName += dotSuffix if singleName == "quotesingle" and Glyphs.defaults[ "com.mekkablue.QuoteManager.excludeDumbQuotes"]: print(u"\n⚠️ Skipping %s/%s" % (singleName, doubleName)) else: print("\n%s/%s:" % (singleName, doubleName)) g = Font.glyphs[singleName] # single quote glyph gg = Font.glyphs[doubleName] # double quote glyph if not g: self.reportMissingGlyph(singleName) elif not gg: self.reportMissingGlyph(doubleName) else: for master in Font.masters: mID = master.id gl = g.layers[mID] # single quote layer ggl = gg.layers[mID] # double quote layer # check if a default quote has been determined by the user: if defaultSingle: referenceGlyph = Font.glyphs[defaultDouble] referenceLayer = referenceGlyph.layers[mID] else: referenceGlyph = gg referenceLayer = ggl # layer for measuring, depends on user input # measure referenceLayer: xPos = [ c.position.x for c in referenceLayer.components ] if xPos and len(xPos) == 2: # add anchors in single quote: print(xPos[1] - xPos[0], master.name) dist = abs(xPos[1] - xPos[0]) for aName in ("entry", "exit"): if aName == "exit": x = dist else: x = 0 newAnchor = GSAnchor( "#%s" % aName, NSPoint(x, 0)) gl.anchors.append(newAnchor) print( u" ✅ %s: Added #exit and #entry anchors." % g.name) # auto align components for comp in ggl.components: comp.automaticAlignment = True # update metrics: ggl.updateMetrics() ggl.syncMetrics() print( u" ✅ %s: Auto-aligned components." % gg.name) else: print( u" ⚠️ WARNING: No components in %s, layer '%s'. Cannot add anchors." % (referenceLayer.parent.name, referenceLayer.name)) self.openTabIfRequested() Font.updateInterface() Font.currentTab.redraw() except Exception as e: # brings macro window to front and reports error: Glyphs.showMacroWindow() print("Quote Manager Error: %s" % e) import traceback print(traceback.format_exc())
def MoveCallback(self, sender): Font = Glyphs.font selectedLayers = Font.selectedLayers selectedMaster = Font.selectedFontMaster italicAngle = selectedMaster.italicAngle anchor_index = self.w.anchor_name.get() anchor_name = self.w.anchor_name.getItems()[anchor_index] horizontal_index = Glyphs.defaults[ "com.mekkablue.AnchorMover2.hTarget"] horizontal_change = self.prefAsFloat( "com.mekkablue.AnchorMover2.hChange") vertical_index = Glyphs.defaults["com.mekkablue.AnchorMover2.vTarget"] vertical_change = self.prefAsFloat( "com.mekkablue.AnchorMover2.vChange") # Keep inquiries to the objects to a minimum: selectedAscender = selectedMaster.ascender selectedCapheight = selectedMaster.capHeight selectedXheight = selectedMaster.xHeight selectedDescender = selectedMaster.descender # respecting italic angle respectItalic = Glyphs.defaults["com.mekkablue.AnchorMover2.italic"] if italicAngle: italicCorrection = italicSkew(0.0, selectedXheight / 2.0, italicAngle) print("italicCorrection", italicCorrection) else: italicCorrection = 0.0 evalCodeH = listHorizontal[horizontal_index][1] evalCodeV = listVertical[vertical_index][1] if not selectedLayers: print("No glyphs selected.") Message(title="No Glyphs Selected", message="Could not move anchors. No glyphs were selected.", OKButton=None) else: print("Processing %i glyphs..." % (len(selectedLayers))) Font.disableUpdateInterface() for originalLayer in selectedLayers: if originalLayer.name != None: if len(originalLayer.anchors) > 0: thisGlyph = originalLayer.parent # create a layer copy that can be slanted backwards if necessary copyLayer = originalLayer.copyDecomposedLayer() thisGlyph.beginUndo() # not working? try: if italicAngle and respectItalic: # slant the layer copy backwards for mPath in copyLayer.paths: for mNode in mPath.nodes: mNode.x = italicSkew( mNode.x, mNode.y, -italicAngle) + italicCorrection for mAnchor in copyLayer.anchors: mAnchor.x = italicSkew( mAnchor.x, mAnchor.y, -italicAngle) + italicCorrection for copyAnchor in copyLayer.anchors: if copyAnchor.name == anchor_name: old_anchor_x = copyAnchor.x old_anchor_y = copyAnchor.y xMove = eval(evalCodeH) + horizontal_change yMove = eval(evalCodeV) + vertical_change # Ignore moves relative to bbox if there are no paths: if not copyLayer.paths: if "bounds" in evalCodeH: xMove = old_anchor_x if "bounds" in evalCodeV: yMove = old_anchor_y # Only move if the calculated position differs from the original one: if [int(old_anchor_x), int(old_anchor_y) ] != [int(xMove), int(yMove)]: if italicAngle and respectItalic: # skew back xMove = italicSkew( xMove, yMove, italicAngle) - italicCorrection old_anchor_x = italicSkew( old_anchor_x, old_anchor_y, italicAngle) - italicCorrection originalAnchor = [ a for a in originalLayer.anchors if a.name == anchor_name ][0] originalAnchor.position = NSPoint( xMove, yMove) print( "Moved %s anchor from %i, %i to %i, %i in %s." % (anchor_name, old_anchor_x, old_anchor_y, xMove, yMove, thisGlyph.name)) else: print( "Keeping %s anchor at %i, %i in %s." % (anchor_name, old_anchor_x, old_anchor_y, thisGlyph.name)) except Exception as e: print("ERROR: Failed to move anchor in %s." % thisGlyph.name) print(e) finally: thisGlyph.endUndo() Font.enableUpdateInterface() print("Done.")
height = path.bounds.size.height move_to = (ascender_height / 2) - (height / 2) move_by = bottom - move_to move_by *= -1 print(move_by) if move_by == 0: print("Path is already centered") else: print("Centering path, moving by %s" % move_by) path.applyTransform(( 1.0, # x scale factor 0.0, # x skew factor 0.0, # y skew factor 1.0, # y scale factor 0.0, # x position move_by # y position )) if selected_anchors: for anchor in selected_anchors: layer = font.selectedLayers[0] height = layer.master.ascender y = height / 2 x = anchor.position.x print("Moving anchor %s to (%s,%s)" % (anchor.name, x, y)) anchor.position = NSPoint(x, y) if not selected_paths and not selected_anchors: print("You haven't selected anything to center.")
def interpolatePointPos(p1, p2, factor): factor = factor % 1.0 x = p1.x * factor + p2.x * (1.0 - factor) y = p1.y * factor + p2.y * (1.0 - factor) return NSPoint(x, y)
def RewireFireMain(self, sender): try: Glyphs.clearLog() # update settings to the latest user input: if not self.SavePreferences(self): print("Note: 'Rewire Fire' could not write preferences.") thisFont = Glyphs.font # frontmost font print("Rewire Fire Report for %s" % thisFont.familyName) print(thisFont.filepath) duplicateCount = 0 affectedLayers = [] for thisGlyph in thisFont.glyphs: if thisGlyph.export or Glyphs.defaults[ "com.mekkablue.RewireFire.includeNonExporting"]: for thisLayer in thisGlyph.layers: if thisLayer.isMasterLayer or thisLayer.isSpecialLayer: thisLayer.selection = None allCoordinates = [] duplicateCoordinates = [] for thisPath in thisLayer.paths: for thisNode in thisPath.nodes: if thisNode.type != OFFCURVE: if thisNode.position in allCoordinates: thisNode.selected = True duplicateCoordinates.append( thisNode.position) duplicateCount += 1 if Glyphs.defaults[ "com.mekkablue.RewireFire.setFireToNode"]: thisNode.name = self.nodeMarker else: allCoordinates.append( thisNode.position) if thisNode.name == self.nodeMarker: thisNode.name = None if duplicateCoordinates: print() print(u"%s, layer: '%s'" % (thisGlyph.name, thisLayer.name)) for dupe in duplicateCoordinates: print(u" %s x %.1f, y %.1f" % (self.nodeMarker, dupe.x, dupe.y)) if Glyphs.defaults[ "com.mekkablue.RewireFire.markWithCircle"]: coords = set([(p.x, p.y) for p in duplicateCoordinates ]) for dupeCoord in coords: x, y = dupeCoord self.circleInLayerAtPosition( thisLayer, NSPoint(x, y)) affectedLayers.append(thisLayer) print( "\nFound a total of %i duplicate coordinates. (Triplets count as two.)" % duplicateCount) if not affectedLayers: Message( title="No Duplicates Found", message=u"Could not find any duplicate coordinates in %s." % thisFont.familyName, OKButton=u"😇 Cool") elif Glyphs.defaults[ "com.mekkablue.RewireFire.openTabWithAffectedLayers"]: # opens new Edit tab: newTab = thisFont.newTab() newTab.layers = affectedLayers else: Glyphs.showMacroWindow() self.w.close() # delete if you want window to stay open except Exception as e: # brings macro window to front and reports error: Glyphs.showMacroWindow() print("Rewire Fire Error: %s" % e) import traceback print(traceback.format_exc())
def bezierWithPoints(A, B, C, D, t): x, y = bezier(A.x, A.y, B.x, B.y, C.x, C.y, D.x, D.y, t) return NSPoint(x, y)
def RealignStackingAnchorsMain(self, sender): try: Glyphs.clearLog() # clears macro window log # update settings to the latest user input: if not self.SavePreferences(self): print( "Note: 'Realign Stacking Anchors in Combining Accents' could not write preferences." ) whichAnchorPairs = Glyphs.defaults[ "com.mekkablue.RealignStackingAnchors.whichAnchorPairs"] anchorPairs = [a.strip() for a in whichAnchorPairs.split(",")] allGlyphs = Glyphs.defaults[ "com.mekkablue.RealignStackingAnchors.allGlyphs"] limitToCombiningMarks = Glyphs.defaults[ "com.mekkablue.RealignStackingAnchors.limitToCombiningMarks"] includeNonExporting = Glyphs.defaults[ "com.mekkablue.RealignStackingAnchors.includeNonExporting"] thisFont = Glyphs.font # frontmost font print("Realign Stacking Anchors, Report for %s" % thisFont.familyName) if thisFont.filepath: print(thisFont.filepath) print() if not allGlyphs: if includeNonExporting: glyphs = [l.parent for l in thisFont.selectedLayers] else: glyphs = [ l.parent for l in thisFont.selectedLayers if l.parent.export ] else: if includeNonExporting: glyphs = thisFont.glyphs else: glyphs = [g for g in thisFont.glyphs if g.export] if limitToCombiningMarks: glyphs = [ g for g in glyphs if g.category == "Mark" and g.subCategory == "Nonspacing" ] print("Processing %i glyph%s..." % ( len(glyphs), "" if len(glyphs) == 1 else "s", )) movedAnchorCount = 0 for thisGlyph in glyphs: print("\n%s:" % thisGlyph.name) for thisLayer in thisGlyph.layers: if thisLayer.isMasterLayer or thisLayer.isSpecialLayer: # Nomenclature: # top: default anchor # _top: underscore Anchor for defaultAnchorName in anchorPairs: # sanitize user entry: # get the default anchor name by getting rid of leading underscores while defaultAnchorName[0] == "_" and len( anchorName) > 1: defaultAnchorName = defaultAnchorName[1:] # derive underscore anchor from default anchor underscoreAnchorName = "_%s" % defaultAnchorName underscoreAnchor = thisLayer.anchors[ underscoreAnchorName] # only proceed if there are both anchors: if not underscoreAnchor: print(" ⚠️ No anchor ‘%s’ found" % underscoreAnchorName) else: defaultAnchor = thisLayer.anchors[ defaultAnchorName] if not defaultAnchor: print(" ⚠️ No anchor ‘%s’ found" % defaultAnchorName) else: # record original position: oldPosition = defaultAnchor.position # determine italic angle and move the anchor accordingly: italicAngle = thisLayer.associatedFontMaster( ).italicAngle straightPosition = NSPoint( underscoreAnchor.position.x, defaultAnchor.position.y) if italicAngle: defaultAnchor.position = italicize( straightPosition, italicAngle, underscoreAnchor.position.y) else: defaultAnchor.position = straightPosition # compare new position to original position, and report if moved: if defaultAnchor.position != oldPosition: print(" ↔️ Moved %s on layer '%s'" % (defaultAnchorName, thisLayer.name)) movedAnchorCount += 1 else: print(" ✅ Anchor %s on layer '%s'" % (defaultAnchorName, thisLayer.name)) self.w.close() # delete if you want window to stay open # wrap up and report: report = "Moved %i anchor%s in %i glyph%s." % ( movedAnchorCount, "" if movedAnchorCount == 1 else "s", len(glyphs), "" if len(glyphs) == 1 else "s", ) print("\n%s\nDone." % report) Message( title="Realigned Anchors", message="%s Detailed report in Macro Window." % report, OKButton=None, ) except Exception as e: # brings macro window to front and reports error: Glyphs.showMacroWindow() print("Realign Stacking Anchors in Combining Accents Error: %s" % e) import traceback print(traceback.format_exc())
circlePath.closed = True return circlePath if not thisFont.glyphs[".notdef"]: if thisFont.glyphs["question"]: sourceMasterID = thisFont.masters[len(thisFont.masters) - 1].id sourceLayer = thisFont.glyphs["question"].layers[sourceMasterID] if sourceLayer: # Build .notdef from question mark and circle: questionmarkLayer = sourceLayer.copyDecomposedLayer() scaleLayerByFactor(questionmarkLayer, 0.8) qOrigin = questionmarkLayer.bounds.origin qWidth = questionmarkLayer.bounds.size.width qHeight = questionmarkLayer.bounds.size.height qCenter = NSPoint(qOrigin.x + 0.5 * qWidth, qOrigin.y + 0.5 * qHeight) side = max((qWidth, qHeight)) * 1.5 circleRect = NSRect( NSPoint(qCenter.x - 0.5 * side, qCenter.y - 0.5 * side), NSSize(side, side)) circle = circleInsideRect(circleRect) questionmarkLayer.paths.append(circle) questionmarkLayer.correctPathDirection() # Create glyph: notdefName = ".notdef" notdefGlyph = GSGlyph() notdefGlyph.name = notdefName thisFont.glyphs.append(notdefGlyph) if thisFont.glyphs[notdefName]: print("%s added successfully" % notdefName)
""" import GlyphsApp from Foundation import NSPoint import math font = Glyphs.font allSelectedGlyphs = [l.parent for l in font.selectedLayers] def angle(angle, height, yPos): offset = math.tan(math.radians(angle)) * height / 2 shift = math.tan(math.radians(angle)) * yPos - offset return shift for thisGlyph in allSelectedGlyphs: for thisLayer in thisGlyph.layers: #Set variables masterID = thisLayer.associatedMasterId thisMasterXheight = font.masters[masterID].xHeight thisMasterAngle = thisLayer.italicAngle width = thisLayer.width for thisAnchor in thisLayer.anchors: posY = thisLayer.anchors[thisAnchor.name].position.y centerOfLayer = width / 2 + angle(thisMasterAngle, thisMasterXheight, posY) thisLayer.anchors[thisAnchor.name].position = NSPoint( centerOfLayer, posY)
def moveHandle(a,b,intersection,bPercentage): x = a.x + (intersection.x-a.x) * bPercentage y = a.y + (intersection.y-a.y) * bPercentage return NSPoint(x,y)
def get_center(self, layer): center = NSPoint(layer.bounds.origin.x + layer.bounds.size.width / 2, layer.bounds.origin.y + layer.bounds.size.height / 2) return center
def smallFigureBuilderMain(self, sender): try: # brings macro window to front and clears its log: Glyphs.clearLog() thisFont = Glyphs.font # frontmost font default = Glyphs.defaults[ "com.mekkablue.smallFigureBuilder.default"].strip() derive = Glyphs.defaults["com.mekkablue.smallFigureBuilder.derive"] figures = ("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine") offsets = {} for suffixPair in [pair.split(":") for pair in derive.split(",")]: suffix = suffixPair[0].strip() value = float(suffixPair[1].strip()) offsets[suffix] = value # go through for fig in figures: # determine default glyph: defaultGlyphName = "%s%s" % (fig, default) defaultGlyph = thisFont.glyphs[defaultGlyphName] if not defaultGlyph: print "\nNot found: %s" % defaultGlyphName else: print "\nDeriving from %s:" % defaultGlyphName for deriveSuffix in offsets: # create or overwrite derived glyph: deriveGlyphName = "%s%s" % (fig, deriveSuffix) deriveGlyph = thisFont.glyphs[deriveGlyphName] if not deriveGlyph: deriveGlyph = GSGlyph(deriveGlyphName) thisFont.glyphs.append(deriveGlyph) print " - creating new glyph %s" % deriveGlyphName else: print " - overwriting glyph %s" % deriveGlyphName # copy glyph attributes: deriveGlyph.leftKerningGroup = defaultGlyph.leftKerningGroup deriveGlyph.rightKerningGroup = defaultGlyph.rightKerningGroup # reset category & subcategory: deriveGlyph.updateGlyphInfo() # add component on each master layer: for thisMaster in thisFont.masters: isCurrentMaster = thisMaster is thisFont.selectedFontMaster if isCurrentMaster or not Glyphs.defaults[ "com.mekkablue.smallFigureBuilder.currentMasterOnly"]: mID = thisMaster.id offset = offsets[deriveSuffix] offsetPos = NSPoint(0, offset) if thisMaster.italicAngle != 0.0: offsetPos = italicize( offsetPos, italicAngle=thisMaster.italicAngle) defaultComponent = GSComponent( defaultGlyphName, offsetPos) deriveLayer = deriveGlyph.layers[mID] deriveLayer.clear() deriveLayer.components.append(defaultComponent) if not self.SavePreferences(self): print "Note: 'Build Small Figures' could not write preferences." # self.w.close() # delete if you want window to stay open except Exception, e: # brings macro window to front and reports error: Glyphs.showMacroWindow() print "Build Small Figures Error: %s" % e import traceback print traceback.format_exc()
class Window(GWindow): # _ns_window PyGUI_NSWindow # _ns_style_mask int def __init__(self, style='standard', zoomable=None, **kwds): # We ignore zoomable, since it's the same as resizable. self._style = style options = dict(_default_options_for_style[style]) for option in ['movable', 'closable', 'hidable', 'resizable']: if option in kwds: options[option] = kwds.pop(option) self._ns_style_mask = self._ns_window_style_mask(**options) if style == 'fullscreen': ns_rect = NSScreen.mainScreen().frame() else: ns_rect = NSRect(NSPoint(0, 0), NSSize(self._default_width, self._default_height)) ns_window = PyGUI_NSWindow.alloc() ns_window.initWithContentRect_styleMask_backing_defer_( ns_rect, self._ns_style_mask, AppKit.NSBackingStoreBuffered, True) ns_content = PyGUI_NS_ContentView.alloc() ns_content.initWithFrame_(NSRect(NSPoint(0, 0), NSSize(0, 0))) ns_content.pygui_component = self ns_window.setContentView_(ns_content) ns_window.setAcceptsMouseMovedEvents_(True) ns_window.setDelegate_(ns_window) ns_window.pygui_component = self self._ns_window = ns_window GWindow.__init__(self, style=style, closable=options['closable'], _ns_view=ns_window.contentView(), _ns_responder=ns_window, _ns_set_autoresizing_mask=False, **kwds) def _ns_window_style_mask(self, movable, closable, hidable, resizable): if movable or closable or hidable or resizable: mask = AppKit.NSTitledWindowMask if closable: mask |= AppKit.NSClosableWindowMask if hidable: mask |= AppKit.NSMiniaturizableWindowMask if resizable: mask |= AppKit.NSResizableWindowMask else: mask = AppKit.NSBorderlessWindowMask return mask def destroy(self): #print "Window.destroy:", self ### self.hide() app = application() if app._ns_key_window is self: app._ns_key_window = None GWindow.destroy(self) # We can't drop all references to the NSWindow yet, because this method # can be called from its windowShouldClose: method, and allowing an # NSWindow to be released while executing one of its own methods seems # to be a very bad idea (Cocoa hangs). So we hide the NSWindow and store # a reference to it in a global. It will be released the next time a # window is closed and the global is re-used. global _ns_zombie_window _ns_zombie_window = self._ns_window self._ns_window.pygui_component = None #self._ns_window = None def get_bounds(self): ns_window = self._ns_window ns_frame = ns_window.frame() (l, y), (w, h) = ns_window.contentRectForFrameRect_styleMask_( ns_frame, self._ns_style_mask) b = Globals.ns_screen_height - y result = (l, b - h, l + w, b) return result def set_bounds(self, (l, t, r, b)): y = Globals.ns_screen_height - b ns_rect = NSRect(NSPoint(l, y), NSSize(r - l, b - t)) ns_window = self._ns_window ns_frame = ns_window.frameRectForContentRect_styleMask_( ns_rect, self._ns_style_mask) ns_window.setFrame_display_(ns_frame, False)
def buildCirclePart(thisFont, glyphName, isBlack=False): partCircle = (((353.0, 0.0), ((152.0, 0.0), (0.0, 150.0), (0.0, 348.0)), ((0.0, 549.0), (152.0, 700.0), (353.0, 700.0)), ((556.0, 700.0), (708.0, 549.0), (708.0, 348.0)), ((708.0, 149.0), (556.0, 0.0), (353.0, 0.0))), ) thisGlyph = thisFont.glyphs[glyphName] if not thisGlyph: thisGlyph = GSGlyph() thisGlyph.name = glyphName thisFont.glyphs.append(thisGlyph) thisGlyph.leftMetricsKey = "=40" thisGlyph.rightMetricsKey = "=|" print("Generated %s" % glyphName) thisGlyph.export = False # draw in every layer: for thisLayer in thisGlyph.layers: # make sure it is empty: thisLayer.clear() # draw outer circle: for thisPath in partCircle: pen = thisLayer.getPen() pen.moveTo(thisPath[0]) for thisSegment in thisPath[1:]: if len(thisSegment) == 2: # lineto pen.lineTo(thisSegment) elif len(thisSegment) == 3: # curveto pen.curveTo(thisSegment[0], thisSegment[1], thisSegment[2]) else: print( "%s: Path drawing error. Could not process this segment:\n" % (glyphName, thisSegment)) pen.closePath() pen.endPath() # scale: refHeight = thisFont.upm - 80 actualHeight = thisLayer.bounds.size.height scaleFactor = refHeight / actualHeight thisLayer.applyTransform( transform(scale=scaleFactor).transformStruct()) # shift to align with capHeight: refY = thisLayer.associatedFontMaster().capHeight * 0.5 actualY = thisLayer.bounds.origin.y + thisLayer.bounds.size.height * 0.5 shift = refY - actualY thisLayer.applyTransform(transform(shiftY=shift).transformStruct()) if not isBlack: # inner circle, scaled down: currentHeight = thisLayer.bounds.size.height outerCircle = thisLayer.paths[0] innerCircle = outerCircle.copy() thisLayer.paths.append(innerCircle) # scale down inner circle: stemSize = 50.0 hstems = thisLayer.associatedFontMaster().horizontalStems vstems = thisLayer.associatedFontMaster().verticalStems if hstems and vstems: stemSize = (hstems[0] + vstems[0]) * 0.25 maximumStemSize = currentHeight * 0.28 stemSize = min(maximumStemSize, stemSize) smallerBy = stemSize * 2 * 1.06 newHeight = currentHeight - smallerBy scaleFactor = newHeight / currentHeight scale = transform(scale=scaleFactor).transformStruct() centerX = innerCircle.bounds.origin.x + innerCircle.bounds.size.width * 0.5 centerY = innerCircle.bounds.origin.y + innerCircle.bounds.size.height * 0.5 shift = transform(shiftX=-centerX, shiftY=-centerY).transformStruct() shiftBack = transform(shiftX=centerX, shiftY=centerY).transformStruct() innerCircle.applyTransform(shift) innerCircle.applyTransform(scale) innerCircle.applyTransform(shiftBack) # tidy up paths and set width: thisLayer.correctPathDirection() thisLayer.cleanUpPaths() thisLayer.updateMetrics() thisLayer.syncMetrics() # add anchor: centerX = thisLayer.bounds.origin.x + thisLayer.bounds.size.width * 0.5 centerY = thisLayer.bounds.origin.y + thisLayer.bounds.size.height * 0.5 centerAnchor = GSAnchor() centerAnchor.name = "#center" centerAnchor.position = NSPoint(centerX, centerY) thisLayer.anchors.append(centerAnchor)
def rotate(self, sender): # update settings to the latest user input: if not self.SavePreferences(self): print("Note: 'Rotate Around Anchor' could not write preferences.") selectedLayers = Glyphs.currentDocument.selectedLayers() originatingButton = sender.getTitle() if "ccw" in originatingButton: rotationDirection = 1 else: rotationDirection = -1 if "+" in originatingButton: repeatCount = int(Glyphs.defaults[ "com.mekkablue.rotateAroundAnchor.stepAndRepeat_times"]) else: repeatCount = 0 rotationDegrees = float( Glyphs.defaults["com.mekkablue.rotateAroundAnchor.rotate_degrees"]) rotationCenter = NSPoint( int(Glyphs.defaults["com.mekkablue.rotateAroundAnchor.anchor_x"]), int(Glyphs.defaults["com.mekkablue.rotateAroundAnchor.anchor_y"]), ) if len(selectedLayers) == 1: selectionCounts = True else: selectionCounts = False for thisLayer in selectedLayers: # rotate individually selected nodes and components try: thisGlyph = thisLayer.parent selectionCounts = selectionCounts and bool( thisLayer.selection) # True only if both are True RotationTransform = self.rotationTransform( rotationCenter, rotationDegrees, rotationDirection) print("rotationCenter, rotationDegrees, rotationDirection:", rotationCenter, rotationDegrees, rotationDirection) RotationTransformMatrix = RotationTransform.transformStruct() thisGlyph.beginUndo() if repeatCount == 0: # simple rotation for thisThing in selection: thisLayer.transform_checkForSelection_doComponents_( RotationTransform, selectionCounts, True) else: # step and repeat paths and components newPaths, newComps = [], [] for i in range(repeatCount): for thisPath in thisLayer.paths: if thisPath.selected or not selectionCounts: rotatedPath = thisPath.copy() for j in range(i + 1): rotatedPath.applyTransform( RotationTransformMatrix) newPaths.append(rotatedPath) for thisComp in thisLayer.components: if thisComp.selected or not selectionCounts: rotatedComp = thisComp.copy() for j in range(i + 1): rotatedComp.applyTransform( RotationTransformMatrix) newComps.append(rotatedComp) for newPath in newPaths: thisLayer.paths.append(newPath) for newComp in newComps: thisLayer.components.append(newComp) thisGlyph.endUndo() except Exception as e: import traceback print(traceback.format_exc())