def transform(shiftX=0.0, shiftY=0.0, rotate=0.0, skew=0.0, scale=1.0): """ Returns an NSAffineTransform object for transforming layers. Apply an NSAffineTransform t object like this: Layer.transform_checkForSelection_doComponents_(t,False,True) Access its transformation matrix like this: tMatrix = t.transformStruct() # returns the 6-float tuple Apply the matrix tuple like this: Layer.applyTransform(tMatrix) Component.applyTransform(tMatrix) Path.applyTransform(tMatrix) Chain multiple NSAffineTransform objects t1, t2 like this: t1.appendTransform_(t2) """ myTransform = NSAffineTransform.transform() if rotate: myTransform.rotateByDegrees_(rotate) if scale != 1.0: myTransform.scaleBy_(scale) if not (shiftX == 0.0 and shiftY == 0.0): myTransform.translateXBy_yBy_(shiftX, shiftY) if skew: skewStruct = NSAffineTransformStruct() skewStruct.m11 = 1.0 skewStruct.m22 = 1.0 skewStruct.m21 = math.tan(math.radians(skew)) skewTransform = NSAffineTransform.transform() skewTransform.setTransformStruct_(skewStruct) myTransform.appendTransform_(skewTransform) return myTransform
def create_transform(x): new_t = NSAffineTransform.transform() new_t.translateXBy_yBy_(128, 171) new_t.scaleXBy_yBy_((random_scale_x - 1.0) * x + 1.0, (random_scale_y - 1.0) * x + 1.0) new_t.rotateByDegrees_(360 * x) new_t.translateXBy_yBy_(-128, -171) return new_t
def skew(a, b=None): # Skew the art board by "a", "b", if "b" is not set the art board will be skew with "a" = "b" Transform = NSAffineTransform.alloc().init() if b is None: b = a Transform.shearXBy_yBy_(a, b) Transform.concat()
def scale(x, y = None): # Scale the art board by "x", "y", if "y" is not set the art board will be scaled proportionally. Transform = NSAffineTransform.alloc().init() if y is None: y = x Transform.scaleXBy_yBy_(x, y) Transform.concat()
def scale(x, y=None): # Scale the art board by "x", "y", if "y" is not set the art board will be scaled proportionally. Transform = NSAffineTransform.alloc().init() if y is None: y = x Transform.scaleXBy_yBy_(x, y) Transform.concat()
def skew(a, b = None): # Skew the art board by "a", "b", if "b" is not set the art board will be skew with "a" = "b" Transform = NSAffineTransform.alloc().init() if b is None: b = a Transform.shearXBy_yBy_(a, b) Transform.concat()
def RotatePath(self, path, angle): """Rotates a path by an angle in degrees""" transform = NSAffineTransform.transform() transform.rotateByDegrees_(angle) for node in path.nodes: node.position = transform.transformPoint_( NSMakePoint(node.x, node.y))
def transformComponent(myComponent, myTransform): compTransform = NSAffineTransform.transform() compTransform.setTransformStruct_(myComponent.transform) compTransform.appendTransform_(myTransform) t = compTransform.transformStruct() tNew = (t.m11, t.m12, t.m21, t.m22, t.tX, t.tY) myComponent.transform = tNew return myComponent
def transformFromMatrix(matrix): """ Returns an NSAffineTransform based on the matrix supplied. Matrix needs to be a tuple of 6 floats. """ transformation = NSAffineTransform.transform() transformation.setTransformStruct_(matrix) return transformation
def DrawGlyph(f, glyph, PSCommands, xoffset, yoffset, ratio, fillcolour, strokecolour, strokewidth, dashed): if not PSCommands: layer = glyph.layers[0] p = layer.drawBezierPath transform = NSAffineTransform.new() transform.translateXBy_yBy_(xoffset * mm, yoffset * mm) transform.scaleBy_(ratio) p.transformUsingAffineTransform_(transform) else: p = NSBezierPath.bezierPath() for command in PSCommands: if command[0] == 'moveTo': try: p.close() except: pass x = xoffset * mm + command[1][0] * ratio y = yoffset * mm + command[1][1] * ratio p.moveToPoint_((x, y)) #print "('moveTo', (%s, %s))," % (command[1][0], command[1][1]) if command[0] == 'lineTo': x = xoffset * mm + command[1][0] * ratio y = yoffset * mm + command[1][1] * ratio p.lineToPoint_((x, y)) #print "('lineTo', (%s, %s))," % (command[1][0], command[1][1]) if command[0] == 'curveTo': points = [] for point in command[1:]: points.append((xoffset * mm + point[0] * ratio, yoffset * mm + point[1] * ratio)) p.curveToPoint_controlPoint1_controlPoint2_( points[0], points[1], points[2]) p.closePath() if fillcolour: NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_( fillcolour[0], fillcolour[1], fillcolour[2], fillcolour[3], 1).set() p.fill() if strokecolour: NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_( strokecolour[0], strokecolour[1], strokecolour[2], strokecolour[3], 1).set() if dashed: p.setLineDash_count_phase_(dashed, 2, 0.0) p.setLineWidth_(strokewidth) p.stroke()
def transform(self, shiftX=0.0, shiftY=0.0, rotate=0.0, skew=0.0, scale=1.0): myTransform = NSAffineTransform.transform() if rotate: myTransform.rotateByDegrees_(rotate) if scale != 1.0: myTransform.scaleBy_(scale) if not (shiftX == 0.0 and shiftY == 0.0): myTransform.translateXBy_yBy_(shiftX, shiftY) if skew: skewStruct = NSAffineTransformStruct() skewStruct.m11 = 1.0 skewStruct.m22 = 1.0 skewStruct.m21 = math.tan(math.radians(skew)) skewTransform = NSAffineTransform.transform() skewTransform.setTransformStruct_(skewStruct) myTransform.appendTransform_(skewTransform) return myTransform
def rotateByDegrees_atPoint_(self, angle, point): """ Rotate the coordinatespace ``angle`` degrees around ``point``. """ self.rotateByDegrees_(angle) tf = NSAffineTransform.transform() tf.rotateByDegrees_(-angle) oldPt = tf.transformPoint_(point) oldPt.x -= point.x oldPt.y -= point.y self.translateXBy_yBy_(oldPt.x, oldPt.y)
def rotation_transform(self, rotationCenter, rotationDegrees, direction): try: rotationX = rotationCenter.x rotationY = rotationCenter.y rotation = rotationDegrees * direction RotationTransform = NSAffineTransform.transform() RotationTransform.translateXBy_yBy_(rotationX, rotationY) RotationTransform.rotateByDegrees_(rotation) RotationTransform.translateXBy_yBy_(-rotationX, -rotationY) return RotationTransform except Exception as e: import traceback print(traceback.format_exc())
def rotationTransform(self, rotationCenter, rotationDegrees, rotationDirection): try: rotationX = rotationCenter.x rotationY = rotationCenter.y # rotation = rotationDegrees * rotationDirection RotationTransform = NSAffineTransform.transform() RotationTransform.translateXBy_yBy_(rotationX, rotationY) RotationTransform.rotateByDegrees_(rotation) RotationTransform.translateXBy_yBy_(-rotationX, -rotationY) return RotationTransform except Exception as e: self.logToConsole("rotationTransform: %s" % e)
def DrawGlyph(f, glyph, PSCommands, xoffset, yoffset, ratio, fillcolour, strokecolour, strokewidth, dashed): if not PSCommands: layer = glyph.layers[0] p = layer.drawBezierPath transform = NSAffineTransform.new() transform.translateXBy_yBy_(xoffset*mm, yoffset*mm) transform.scaleBy_(ratio) p.transformUsingAffineTransform_(transform) else: p = NSBezierPath.bezierPath() for command in PSCommands: if command[0] == 'moveTo': try: p.close() except: pass x = xoffset*mm + command[1][0] * ratio y = yoffset*mm + command[1][1] * ratio p.moveToPoint_((x, y)) #print "('moveTo', (%s, %s))," % (command[1][0], command[1][1]) if command[0] == 'lineTo': x = xoffset*mm + command[1][0] * ratio y = yoffset*mm + command[1][1] * ratio p.lineToPoint_((x, y)) #print "('lineTo', (%s, %s))," % (command[1][0], command[1][1]) if command[0] == 'curveTo': points = [] for point in command[1:]: points.append( (xoffset*mm + point[0] * ratio, yoffset*mm + point[1] * ratio) ) p.curveToPoint_controlPoint1_controlPoint2_(points[0], points[1], points[2]) p.closePath() if fillcolour: NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(fillcolour[0], fillcolour[1], fillcolour[2], fillcolour[3], 1).set() p.fill() if strokecolour: NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(strokecolour[0], strokecolour[1], strokecolour[2], strokecolour[3], 1).set() if dashed: p.setLineDash_count_phase_(dashed, 2, 0.0) p.setLineWidth_(strokewidth) p.stroke()
def drawCjkGuide(self, layer): '''Draw the CJK guide (汉字参考线).''' self.initCjkGuideGlyph() # TODO: color color = NSColor.systemOrangeColor().colorWithAlphaComponent_(0.1) color.set() cjkGuideLayer = Glyphs.font.glyphs[CJK_GUIDE_GLYPH].layers[0] trans = NSAffineTransform.transform() if self.cjkGuideScalingState: # TODO: currently only xScale is necessary # cjkGuideMaster = cjkGuideLayer.associatedFontMaster() # cjkGuideDescender = cjkGuideMaster.descender # cjkGuideAscender = cjkGuideMaster.ascender # cjkGuideHeight = cjkGuideAscender - cjkGuideDescender # master = layer.associatedFontMaster() # descender = master.descender # ascender = master.ascender # height = ascender - descender xScale = layer.width / cjkGuideLayer.width # yScale = height / cjkGuideHeight # trans.translateXBy_yBy_(0, cjkGuideDescender) # trans.scaleXBy_yBy_(xScale, yScale) # trans.translateXBy_yBy_(0, -descender) trans.scaleXBy_yBy_(xScale, 1) if cjkGuideLayer.bezierPath is not None: path = cjkGuideLayer.bezierPath.copy() path.transformUsingAffineTransform_(trans) path.fill()
def __GSComponent_get_scale(self): """ Return the scale components of the transformation.""" (xx, xy, yx, yy, dx, dy) = self.transformStruct() return xx, yy def __GSComponent_set_scale(self, (xScale, yScale)): """ Set the scale component of the transformation. Note: setting this value effectively makes the xy and yx values meaningless. We're assuming that if you're setting the xy and yx values, you will use the transformation attribute rather than the scale and offset attributes. """ print self Transform = NSAffineTransform.transform() Transform.setTransformStruct_(self.transformStruct()) Transform.scaleXBy_yBy_(xScale, yScale) self.setTransformStruct_(Transform.transformStruct()) GSComponent.scale = property(__GSComponent_get_scale, __GSComponent_set_scale, doc="the scale of the component") GSComponent.transformation = property( lambda self: self.transformStruct(), lambda self, value: self.setTransformStruct_(value)) def __GSComponent_move_(self, (x, y)):
def rotate(angle): # Rotate the art board by an angle. Transform = NSAffineTransform.alloc().init() Transform.rotateByDegrees(angle) Transform.concat()
offsetFilter.offsetLayer_offsetX_offsetY_makeStroke_autoStroke_position_metrics_error_shadow_capStyleStart_capStyleEnd_keepCompatibleOutlines_( layer, h / 2, v / 2, # horizontal and vertical offset True, # if True, creates a stroke False, # if True, distorts resulting shape to vertical metrics 0.5, # stroke distribution to the left and right, 0.5 = middle None, None, None, 0, 0, True) layer.correctPathDirection() rotate_transform = NSAffineTransform() rotate_transform.rotate(45, ((layer.width / 2), (layer.master.capHeight / 2))) layer.transform(rotate_transform) if not font.glyphs['divide']: glyph = GSGlyph('divide') font.glyphs.append(glyph) for i, layer in enumerate(glyph.layers): minusLayerBounds = font.glyphs['minus'].layers[i].bounds layer.width = zero_width[i] divide_height = minusLayerBounds.size.height divide_bottom = minusLayerBounds.origin.y dotaccentBounds = font.glyphs['dotaccentcomb'].layers[i].bounds dot_accent_x = dotaccentBounds.origin.x dot_accent_width = dotaccentBounds.size.width
def translate(x, y): # Translate the art board pane to "x", "y" Transform = NSAffineTransform.alloc().init() Transform.translateXBy_yBy_(x, y) Transform.concat()
def Main( self, sender ): font.disableUpdateInterface() try: layers = font.selectedLayers angle = int(self.w.angle.get()) distance = int(self.w.distance.get()) for layer in layers: almostExtremes = [] # Find the tangent nodes if angle % 90: # if not a right angle # Move the origin of the angle to a more natural position for this task tangentAngle = 90 - angle newPaths = [] for path in layer.paths: # Create an empty path newPath = GSPath() for segment in path.segments: # Create a path from the segment and duplicate it # so we can compare with the original later on originalSegment = GSPath() if Glyphs.versionNumber < 3.0: for index, point in enumerate(segment): newNode = GSNode() newNode.position = point.x, point.y if index in [1,2] and len(segment) == 4: newNode.type = "offcurve" elif index == 1 and len(segment) == 2: newNode.type = "line" else: newNode.type = "curve" originalSegment.addNode_( newNode ) else: for index in range(segment.count()): newNode = GSNode() point = segment.pointAtIndex_(index) newNode.position = point.x, point.y if index in [1,2] and segment.count() == 4: newNode.type = "offcurve" elif index == 1 and segment.count() == 2: newNode.type = "line" else: newNode.type = "curve" originalSegment.addNode_( newNode ) fakeSegment = originalSegment.copy() fakeSegment.nodes[0].type = "line" # Rotate the segment and add points to the extremes self.RotatePath( fakeSegment, tangentAngle ) fakeSegment.addExtremes_( True ) closestNode = None middleTangent = 1 # If the segment has 7 nodes, an extreme point was added if len( fakeSegment ) == 7: # Get the tangent angle of the middle node middleNode = fakeSegment.nodes[3] middleTangent = round( fakeSegment.tangentAngleAtNode_direction_( middleNode, 5 ) ) elif len( fakeSegment ) == 4: boundsLowX = fakeSegment.bounds.origin.x boundsHighX = boundsLowX + fakeSegment.bounds.size.width nodeList = list(fakeSegment.nodes) startNode = nodeList[0] endNode = nodeList[-1] errorMargin = 0.01 if boundsLowX < startNode.position.x - errorMargin: if boundsLowX < endNode.position.x - errorMargin: if startNode.position.x < endNode.position.x: closestNode = startNode else: closestNode = endNode elif boundsHighX > startNode.position.x + errorMargin: if boundsHighX > endNode.position.x + errorMargin: if startNode.position.x > endNode.position.x: closestNode = startNode else: closestNode = endNode # Rotate the segment back self.RotatePath( fakeSegment, -tangentAngle ) if closestNode: almostExtremes.append( closestNode.position ) # If the new diagonal extremes are perpendicular to our angle, # restore the original segment if middleTangent % 180 == 0: # check if horizontal fakeSegment = originalSegment # Add the nodes to the new path, skipping the first node # because the last and first ones repeat on adjacent segments for node in fakeSegment.nodes[1:]: newPath.addNode_( node ) # Close the path (if originally closed) and store it newPath.closed = True if path.closed else False newPaths.append( newPath ) else: # if right angle newPaths = layer.paths # Iterate the new paths, which are stored separately # and were not appended to the layer yet for path in newPaths: # Duplicate the tangent nodes (for extrusion) # Get all oncurve nodes onCurveNodes = [ node for node in path.nodes if node.type != "offcurve" ] # Create a list for our "diagonal extremes" diagonalExtremes = [] # Make the angle positive tangentAngle = angle while tangentAngle < 0: tangentAngle += 360 # Get the angle value from 0° to 180° tangentAngle = tangentAngle % 180 for node in onCurveNodes: errorMargin = 1.5 # in degrees if node.position in almostExtremes: diagonalExtremes.append( node ) elif node.smooth == True: # smooth node # For smooth nodes, check if their tangent angles match ours. # If true, adds the node to our list of diagonal extremes. # An error margin is considered. minTangentAngle = tangentAngle - errorMargin maxTangentAngle = tangentAngle + errorMargin nextNodeTan = round( path.tangentAngleAtNode_direction_( node, 1 ) ) if nextNodeTan < 0: nextNodeTan += 180 if nextNodeTan > minTangentAngle and nextNodeTan < maxTangentAngle: diagonalExtremes.append( node ) else: # corner node # For non-smooth angles, check if our tangent falls outside # the angle of the corner. If true, this means this particular # node will produce a line when extruded. nextNodeAngle = path.tangentAngleAtNode_direction_( node, 1 ) prevNodeAngle = path.tangentAngleAtNode_direction_( node, -1 ) # Subtract the tangent angle from the angles of the corner # then uses sine to check if the angle falls below or above # the horizontal axis. Only add the node if the angle is # completely above or below the line. nextNodeHorizontal = nextNodeAngle - tangentAngle prevNodeHorizontal = prevNodeAngle - tangentAngle if math.sin( math.radians(nextNodeHorizontal) ) < 0: if math.sin( math.radians(prevNodeHorizontal) ) < 0: diagonalExtremes.append( node ) if math.sin( math.radians(nextNodeHorizontal) ) > 0: if math.sin( math.radians(prevNodeHorizontal) ) > 0: diagonalExtremes.append( node ) # Duplicate the diagonal extremes and returns an updated list of nodes duplicateExtremes = [] for node in diagonalExtremes: newNode = GSNode() newNode.type = "line" newNode.smooth = False newNode.position = node.position node.smooth = False path.insertNode_atIndex_( newNode, node.index+1 ) duplicateExtremes.append( newNode ) allExtremes = [] for i in range( len(diagonalExtremes) ): allExtremes.append( diagonalExtremes[i] ) allExtremes.append( duplicateExtremes[i] ) # Selects the diagonal extreme nodes for node in diagonalExtremes: layer.selection.append( node ) # Move the nodes if distance != 0: # Calculate the deltaX and deltaY deltaX = math.cos( math.radians( angle ) ) * distance deltaY = math.sin( math.radians( angle ) ) * distance # # Build a list containing all duplicate nodes # allExtremes = [] # for node in path.nodes: # if node.position == node.nextNode.position: # allExtremes.extend( [ node, node.nextNode] ) # print(allExtremes) # Check if the start point should move or not fixedStartPoint = True startNode = path.nodes[-1] # If the start node is one of the diagonal extremes, use # the angle we get after subtracting the tangentAngle from # the secondNodeAngle to determine if the start should be fixed # or not. It should move if it sits below the horizontal axis. if startNode in allExtremes: secondNodeAngle = path.tangentAngleAtNode_direction_( path.nodes[0], 1 ) secondNodeHorizontal = secondNodeAngle - tangentAngle if math.sin( math.radians( secondNodeHorizontal ) ) < 0: fixedStartPoint = False # If the start node is not a diagonal extreme, duplicate the # path and move it by 1 unit in the direction of the extrusion. else: # Get the NSBezierPath and move it offsetPath = path.bezierPath translate = NSAffineTransform.transform() translate.translateXBy_yBy_( math.copysign( 1, deltaX ), math.copysign( 1, deltaY ) ) offsetPath.transformUsingAffineTransform_( translate ) startPoint = startNode.position # On counterclockwise (filled) paths, the start node should # move if the start node falls INSIDE the transformed path if path.direction == -1: if offsetPath.containsPoint_( startPoint ): fixedStartPoint = False # On clockwise paths, the start node should move if the # start node falls OUTSIDE the transformed path elif path.direction == 1: if not offsetPath.containsPoint_( startPoint ): fixedStartPoint = False # If the start point should move, rearrange # the list containing the diagonal extremes if fixedStartPoint == False: if path.nodes[-1] not in diagonalExtremes: if diagonalExtremes[-1].index > diagonalExtremes[0].index: lastNode = diagonalExtremes.pop(-1) diagonalExtremes.insert( 0, lastNode ) # Only move if the number diagonal extremes is even if len(diagonalExtremes) % 2 == 0: n = 0 tupleList = [] for i in range( len(diagonalExtremes)//2 ): tupleList.append( (diagonalExtremes[n], diagonalExtremes[n+1]) ) n += 2 for pair in tupleList: if pair[0].index > pair[1].index: selection = path.nodes[ pair[0].index +1 : ] selection.extend( path.nodes[ : pair[1].index +1 ] ) else: selection = path.nodes[ pair[0].index +1 : pair[1].index +1 ] layer.selection = selection # Finaly move a node for node in layer.selection: pos = node.position pos.x = pos.x + deltaX pos.y = pos.y + deltaY node.position = pos else: print("Could not find all extremes for glyph", layer.parent.name) # Replace all paths with the new ones if newPaths: if Glyphs.versionNumber < 3.0: layer.paths = newPaths else: layer.shapes = newPaths layer.roundCoordinates() layer.selection = None except Exception as e: raise(e) finally: font.enableUpdateInterface()
def rotate(angle): # Rotate the art board by an angle. Transform = NSAffineTransform.alloc().init() Transform.rotateByDegrees_(angle) Transform.concat()
GSComponent.offset = property(lambda self: self.position) def __GSComponent_get_scale(self): """ Return the scale components of the transformation.""" (xx, xy, yx, yy, dx, dy) = self.transformStruct() return xx, yy def __GSComponent_set_scale(self, (xScale, yScale)): """ Set the scale component of the transformation. Note: setting this value effectively makes the xy and yx values meaningless. We're assuming that if you're setting the xy and yx values, you will use the transformation attribute rather than the scale and offset attributes. """ print self Transform = NSAffineTransform.transform() Transform.setTransformStruct_(self.transformStruct()) Transform.scaleXBy_yBy_(xScale, yScale) self.setTransformStruct_(Transform.transformStruct()) GSComponent.scale = property(__GSComponent_get_scale, __GSComponent_set_scale, doc="the scale of the component") GSComponent.transformation = property(lambda self: self.transformStruct(), lambda self, value: self.setTransformStruct_(value)) def __GSComponent_move_(self, (x, y)): """Move the component""" (xx, xy, yx, yy, dx, dy) = self.transformStruct() self.setTransformStruct_((xx, xy, yx, yy, dx+x, dy+y)) GSComponent.move = __GSComponent_move_