Created
June 4, 2020 22:05
-
-
Save kieranb662/7174402b4078e90deaf222c726b1fa9d to your computer and use it in GitHub Desktop.
SVG -> iOS Bezier Curves
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import CoreGraphics | |
/// #SVGPath | |
/// A class that can take any svg path string and convert it to the | |
/// equivalent CGMutablePath | |
/// - Parameters | |
/// - string: The string of text representing an SVG path. | |
/// - Note | |
/// The string used should be the `d` attribute of the path element. | |
class SVGPath { | |
private let pathCommandRegex = "[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*" | |
private let commandNumbersRegex = "-?\\d*+(\\.\\d+)?" | |
var string: String | |
private var lastPoint: LastPoint? | |
var path: CGMutablePath | |
/// Helps keep track of the current position and if the last component | |
/// was a cubic or quadratic bezier to calculate inferred beziers. | |
private enum LastPoint { | |
case cubic(current: CGPoint, lastControl: CGPoint) | |
case quad(current: CGPoint, lastControl: CGPoint) | |
case other(current: CGPoint) | |
func getCurrent() -> CGPoint { | |
switch self { | |
case .cubic(let current, _): | |
return current | |
case .quad(let current, _): | |
return current | |
case .other(let current): | |
return current | |
} | |
} | |
func getControl() -> CGPoint? { | |
switch self { | |
case .cubic(_, let lastControl): | |
return lastControl | |
case .quad(_, let lastControl): | |
return lastControl | |
case .other(_): | |
break | |
} | |
return nil | |
} | |
} | |
init(path: String) { | |
self.string = path | |
self.path = CGMutablePath() | |
parse() | |
} | |
} | |
// MARK: Parsing Functions | |
extension SVGPath { | |
private func correctCommand(parsed command: String, amount: Int) -> [[CGFloat]] { | |
var nums = matches(for: commandNumbersRegex, in: command).filter({!$0.isEmpty}) | |
var set = [[CGFloat]]() | |
while (nums.count%amount == 0) { nums.append("0.0") } | |
for i in 1...Int(nums.count/amount) { | |
var values = [CGFloat]() | |
for n in 1..<amount+1 { | |
values.append(CGFloat(Double(nums[(i-1)*amount+(n-1)])!)) | |
} | |
set.append(values) | |
} | |
return set | |
} | |
private func correctLine(parsed command: String) -> [CGFloat] { | |
var nums = matches(for: commandNumbersRegex, in: command).filter({ !$0.isEmpty }) | |
if nums.count == 0 { | |
nums.append("0.0") | |
} | |
return nums.map({ CGFloat(Double($0)!) }) | |
} | |
private func parse() { | |
self.path = CGMutablePath() | |
let commands = matches(for: pathCommandRegex, in: string) | |
for command in commands { | |
if command.contains("M") { | |
let numbers = correctCommand(parsed: command, amount: 2) | |
moveTo(CGPoint(x: numbers[0][0], y: numbers[0][1])) | |
for i in 0...numbers.count-1 { | |
addLine(CGPoint(x: numbers[i][0], y: numbers[i][1])) | |
} | |
} else if command.contains("m") { | |
let numbers = correctCommand(parsed: command, amount: 2) | |
relMoveTo(CGPoint(x: numbers[0][0], y: numbers[0][1])) | |
for i in 0...numbers.count-1 { | |
addRelLine(CGPoint(x: numbers[i][0], y: numbers[i][1])) | |
} | |
} else if command.contains("L") { | |
let numbers = correctCommand(parsed: command, amount: 2) | |
for i in 0..<numbers.count { | |
addLine(CGPoint(x: numbers[i][0], y: numbers[i][1])) | |
} | |
} else if command.contains("l") { | |
let numbers = correctCommand(parsed: command, amount: 2) | |
for i in 0..<numbers.count { | |
addRelLine(CGPoint(x: numbers[i][0], y: numbers[i][1])) | |
} | |
} else if command.contains("H") { | |
let numbers = correctLine(parsed: command) | |
for num in numbers { | |
let last = lastPoint?.getCurrent() | |
addLine(CGPoint(x: num, y: last!.y)) | |
} | |
} else if command.contains("h") { | |
let numbers = correctLine(parsed: command) | |
for num in numbers { | |
addRelLine(CGPoint(x: num, y: 0)) | |
} | |
} else if command.contains("V") { | |
let numbers = correctLine(parsed: command) | |
for num in numbers { | |
let last = lastPoint?.getCurrent() | |
addLine(CGPoint(x: last!.x, y: num)) | |
} | |
} else if command.contains("v") { | |
let numbers = correctLine(parsed: command) | |
for num in numbers { | |
addRelLine(CGPoint(x: 0, y: num)) | |
} | |
} else if command.contains("C") { | |
let numbers = correctCommand(parsed: command, amount: 6) | |
for i in 0..<numbers.count { | |
let control1 = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let control2 = CGPoint(x: numbers[i][2], y: numbers[i][3]) | |
let point = CGPoint(x: numbers[i][4], y: numbers[i][5]) | |
addCubic(to: point, control1: control1, control2: control2) | |
} | |
} else if command.contains("c") { | |
let numbers = correctCommand(parsed: command, amount: 6) | |
for i in 0..<numbers.count { | |
let control1 = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let control2 = CGPoint(x: numbers[i][2], y: numbers[i][3]) | |
let point = CGPoint(x: numbers[i][4], y: numbers[i][5]) | |
addRelCubic(to: point, control1: control1, control2: control2) | |
} | |
} else if command.contains("S") { | |
let numbers = correctCommand(parsed: command, amount: 4) | |
for i in 0..<numbers.count { | |
let control = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let point = CGPoint(x: numbers[i][2], y: numbers[i][3]) | |
addInfCubic(to: point, control: control) | |
} | |
} else if command.contains("s") { | |
let numbers = correctCommand(parsed: command, amount: 4) | |
for i in 0..<numbers.count { | |
let control = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let point = CGPoint(x: numbers[i][2], y: numbers[i][3]) | |
addRelInfCubic(to: point, control: control) | |
} | |
} else if command.contains("Q") { | |
let numbers = correctCommand(parsed: command, amount: 4) | |
for i in 0..<numbers.count { | |
let control = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let point = CGPoint(x: numbers[i][2], y: numbers[i][3]) | |
addQuad(to: point, control: control) | |
} | |
} else if command.contains("q") { | |
let numbers = correctCommand(parsed: command, amount: 4) | |
for i in 0..<numbers.count { | |
let control = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let point = CGPoint(x: numbers[i][2], y: numbers[i][3]) | |
addRelQuad(to: point, control: control) | |
} | |
} else if command.contains("T") { | |
let numbers = correctCommand(parsed: command, amount: 2) | |
for i in 0..<numbers.count { | |
let point = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
addInfQuad(to: point) | |
} | |
} else if command.contains("t") { | |
let numbers = correctCommand(parsed: command, amount: 2) | |
for i in 0..<numbers.count { | |
let point = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
addRelInfQuad(to: point) | |
} | |
} else if command.contains("A") { | |
let numbers = correctCommand(parsed: command, amount: 7) | |
for i in 0..<numbers.count { | |
let point = CGPoint(x: numbers[i][5], y: numbers[i][6]) | |
let radii = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let xAngle = numbers[i][2] | |
let flagA = numbers[i][3] | |
let flagS = numbers[i][4] | |
let last = lastPoint?.getCurrent() | |
checkAndAdjustRadii(startPoint: last!, endPoint: point, radii: radii, xAngle: xAngle, flagA: flagA, flagS: flagS) | |
lastPoint = LastPoint.other(current: point) | |
} | |
} else if command.contains("a") { | |
let numbers = correctCommand(parsed: command, amount: 7) | |
for i in 0..<numbers.count { | |
let point = CGPoint(x: numbers[i][5], y: numbers[i][6]) | |
let radii = CGPoint(x: numbers[i][0], y: numbers[i][1]) | |
let xAngle = numbers[i][2] | |
let flagA = numbers[i][3] | |
let flagS = numbers[i][4] | |
let last = lastPoint?.getCurrent() | |
checkAndAdjustRadii(startPoint: last!, endPoint: point+last!, radii: radii, xAngle: xAngle, flagA: flagA, flagS: flagS) | |
lastPoint = LastPoint.other(current: point+last!) | |
} | |
} else if command.contains("z") || command.contains("Z") { | |
path.closeSubpath() | |
} | |
} | |
} | |
} | |
// MARK: Building Functions | |
extension SVGPath { | |
private func moveTo(_ point: CGPoint) { | |
path.move(to: point) | |
lastPoint = LastPoint.other(current: point) | |
} | |
private func relMoveTo(_ point: CGPoint) { | |
if let last = lastPoint?.getCurrent() { | |
let new = last + point | |
path.move(to: new) | |
lastPoint = LastPoint.other(current: new) | |
} else { | |
path.move(to: point) | |
lastPoint = LastPoint.other(current: point) | |
} | |
} | |
private func addLine(_ point: CGPoint) { | |
path.addLine(to: point) | |
lastPoint = LastPoint.other(current: point) | |
} | |
private func addRelLine(_ point: CGPoint) { | |
let new = (lastPoint?.getCurrent())! + point | |
path.addLine(to: new) | |
lastPoint = LastPoint.other(current: new) | |
} | |
private func addCubic(to point: CGPoint, control1: CGPoint, control2: CGPoint) { | |
path.addCurve(to: point, control1: control1, control2: control2) | |
lastPoint = LastPoint.cubic(current: point, lastControl: control2) | |
} | |
private func addRelCubic(to point: CGPoint, control1: CGPoint, control2: CGPoint) { | |
if let last = lastPoint?.getCurrent() { | |
path.addCurve(to: point + last, control1: control1 + last, control2: control2 + last) | |
lastPoint = LastPoint.cubic(current: point+last, lastControl: control2+last) | |
} | |
} | |
/// I think this is wrong | |
private func addInfCubic(to point: CGPoint, control: CGPoint) { | |
switch lastPoint { | |
case .cubic(current: let last, lastControl: let lastControl): | |
let relativeReflection = last - lastControl | |
let reflected = last + relativeReflection | |
path.addCurve(to: point, control1: reflected, control2: control) | |
lastPoint = LastPoint.cubic(current: point, lastControl: control) | |
case .quad(current: let last, lastControl: _) : | |
path.addCurve(to: point, control1: last, control2: control) | |
lastPoint = LastPoint.cubic(current: point, lastControl: control) | |
case .other(current: let last): | |
path.addCurve(to: point, control1: last, control2: control) | |
lastPoint = LastPoint.cubic(current: point, lastControl: control) | |
default: | |
break | |
} | |
} | |
private func addRelInfCubic(to point: CGPoint, control: CGPoint) { | |
switch lastPoint { | |
case .cubic(current: let last, lastControl: let lastControl): | |
let relativeReflection = last - lastControl | |
let reflected = last + relativeReflection | |
path.addCurve(to: point + last, control1: reflected, control2: control + last) | |
lastPoint = LastPoint.cubic(current: point + last, lastControl: control + last) | |
case .quad(current: let last, lastControl: _) : | |
path.addCurve(to: point + last, control1: last, control2: control + last) | |
lastPoint = LastPoint.cubic(current: point + last, lastControl: control + last) | |
case .other(current: let last): | |
path.addCurve(to: point + last, control1: last, control2: control + last) | |
lastPoint = LastPoint.cubic(current: point + last, lastControl: control + last) | |
default: | |
break | |
} | |
} | |
private func addQuad(to point: CGPoint, control: CGPoint) { | |
path.addQuadCurve(to: point, control: control) | |
lastPoint = LastPoint.quad(current: point, lastControl: control) | |
} | |
private func addRelQuad(to point: CGPoint, control: CGPoint) { | |
if let last = lastPoint?.getCurrent() { | |
path.addQuadCurve(to: point + last, control: control + last) | |
} | |
} | |
private func addInfQuad(to point: CGPoint) { | |
switch lastPoint { | |
case .cubic(current: _, lastControl: _): | |
path.addQuadCurve(to: point, control: point) | |
lastPoint = LastPoint.quad(current: point, lastControl: point) | |
case .quad(current: let last, lastControl: let lastControl) : | |
let relative = last - lastControl | |
let reflection = last + relative | |
path.addQuadCurve(to: point, control: reflection) | |
lastPoint = LastPoint.quad(current: point, lastControl: reflection) | |
case .other(current: _): | |
path.addQuadCurve(to: point, control: point) | |
lastPoint = LastPoint.quad(current: point, lastControl: point) | |
default: | |
break | |
} | |
} | |
private func addRelInfQuad(to point: CGPoint) { | |
switch lastPoint { | |
case .cubic(current: let last, lastControl: _): | |
path.addQuadCurve(to: point + last, control: point + last) | |
lastPoint = LastPoint.quad(current: point + last, lastControl: point + last) | |
case .quad(current: let last, lastControl: let lastControl) : | |
let relative = last - lastControl | |
let reflection = last + relative | |
path.addQuadCurve(to: point + last, control: reflection + last) | |
lastPoint = LastPoint.quad(current: point + last, lastControl: reflection + last) | |
case .other(current: let last): | |
path.addQuadCurve(to: point + last, control: point + last) | |
lastPoint = LastPoint.quad(current: point + last, lastControl: point + last) | |
default: | |
break | |
} | |
} | |
} | |
// MARK: Arc Conversion Functions | |
extension SVGPath { | |
/* IMPORTANT the function that gets called amongst all of these is "checkAndAdjustRadii" | |
it will append a Bezier that is either a straight line or an elliptical arc that | |
has been subdivided into cubic Bezier curves. */ | |
/// Function that converts a axially rotated elliptical arc in endpoint notation to center point notation. | |
private func convertEndPointToCenter(startPoint: CGPoint, endPoint: CGPoint, radii: CGPoint, xAngle: CGFloat, flagA: CGFloat, flagS: CGFloat) { | |
/* note in swift all angle values must be converted to radians */ | |
// convert flags into bools representing the values 0 for false and 1 for true | |
let flagABool = convertFlagToBool(flag: flagA) | |
let flagSBool = convertFlagToBool(flag: flagS) | |
// Break down the points into individual components | |
let x1 = startPoint.x | |
let y1 = startPoint.y | |
let x2 = endPoint.x | |
let y2 = endPoint.y | |
// Calculate midPoints | |
let midX = (x1 - x2)/2 | |
let midY = (y1 - y2)/2 | |
// Break down radii into component values, must be variables because radii could be adjust for no solution values | |
let rX = radii.x | |
let rY = radii.y | |
let rXSquared = rX*rX | |
let rYSquared = rY*rY | |
// calculate the intermediate values xPrime and yPrime | |
let xPrime = midX*cos(xAngle) + midY*sin(xAngle) | |
let yPrime = -midX*sin(xAngle) + midY*cos(xAngle) | |
let xPrimeSquared = xPrime*xPrime | |
let yPrimeSquared = yPrime*yPrime | |
// Compute intermediate values of the center points cXPrime, cYPrime. | |
let tP1 = rXSquared*rYSquared | |
let tP2 = rXSquared*yPrimeSquared + rYSquared*xPrimeSquared | |
// this prevents errors resulting from imaginary values EX sqrt(-1) == error | |
var innerRoot = (tP1/tP2) - 1 | |
if innerRoot < 0 { | |
innerRoot = 0 | |
} | |
// This is an intermediate value required for calculation cXPrime and cYPrime | |
var root = CGFloat(sqrt(Double(innerRoot))) | |
if flagABool == flagSBool { | |
root = -root | |
} | |
// intermediate values for calculating the center point | |
let cXPrime = root*(rX*yPrime/rY) | |
let cYPrime = -root*(rY*xPrime/rX) | |
// average values of the point components | |
let xAve = (x1+x2)/2 | |
let yAve = (y1+y2)/2 | |
// values of the center points of the ellipse | |
let cX = cXPrime*cos(xAngle) - cYPrime*sin(xAngle) + xAve | |
let cY = cXPrime*sin(xAngle) + cYPrime*cos(xAngle) + yAve | |
// Calculate the psuedo angles theta and deltaTheta | |
let startTheta = angleBetweenVectors(uX: 1, uY: 0, vX: (xPrime-cXPrime)/rX, vY: (yPrime-cYPrime)/rY) | |
// DeltaTheta must be fixed on the range -2*pi<deltaTheta<2*pi | |
var deltaTheta = angleBetweenVectors(uX: (xPrime-cXPrime)/rX, uY: (yPrime-cYPrime)/rY, vX: (-xPrime-cXPrime)/rX, vY: (-yPrime-cYPrime)/rY) | |
// Accounts for the sweep values | |
if !flagSBool && deltaTheta>0 { | |
deltaTheta -= 2*CGFloat.pi | |
}else if flagSBool && deltaTheta<0 { | |
deltaTheta += 2*CGFloat.pi | |
} | |
makeArcFromBezier(cX: cX, cY: cY, rX: rX, rY: rY, xAngle: xAngle, theta: startTheta, deltaTheta: deltaTheta) | |
} | |
private func makeArcFromBezier(cX: CGFloat, cY: CGFloat, rX: CGFloat, rY: CGFloat, xAngle: CGFloat, theta: CGFloat, deltaTheta: CGFloat){ | |
let incrementChoice = CGFloat.pi/6 | |
// Semi-major axis of the ellipse | |
let a = rX | |
// Semi-minor axis of the ellipse | |
let b = rY | |
let startAngle = theta | |
let startPoint = findPointOnEllipse(a: a, b: b, cX: cX, cY: cY, xAngle: xAngle, eta: startAngle) | |
if abs(deltaTheta) <= incrementChoice { | |
let endAngle = theta + deltaTheta | |
let endPoint = findPointOnEllipse(a: a, b: b, cX: cX, cY: cY, xAngle: xAngle, eta: endAngle) | |
let startDerivative = findDerivativeAtAngle(a: a, b: b, xAngle: xAngle, eta: startAngle) | |
let endDerivative = findDerivativeAtAngle(a: a, b: b, xAngle: xAngle, eta: endAngle) | |
let controlPoints = calcControlPoints(startPoint: startPoint, endPoint: endPoint, startDerivative: startDerivative, endDerivative: endDerivative, startAngle: startAngle, endAngle: endAngle) | |
addCubic(to: endPoint, control1: controlPoints.0, control2: controlPoints.1) | |
} else if abs(deltaTheta) > incrementChoice { | |
/* If deltaTheta is greater than the allowable increment, | |
then delta theta is divided into equal parts that are less than or equal to the value of the allowable increment. | |
Those increments are then used to created new start and end angles for computing the values of the startPoint, | |
endPoints and ControlPoints. */ | |
let rawNumberOfDivisions = deltaTheta/incrementChoice | |
let numberOfDivisions = abs(rawNumberOfDivisions.rounded(.awayFromZero)) | |
let deltaIncrement = deltaTheta/numberOfDivisions | |
for i in 1...Int(numberOfDivisions) { | |
let startingAngle = startAngle + CGFloat(i-1)*deltaIncrement | |
let endAngle = startAngle + CGFloat(i)*deltaIncrement | |
let startingPoint = findPointOnEllipse(a: a, b: b, cX: cX, cY: cY, xAngle: xAngle, eta: startingAngle) | |
let endPoint = findPointOnEllipse(a: a, b: b, cX: cX, cY: cY, xAngle: xAngle, eta: endAngle) | |
let startDerivative = findDerivativeAtAngle(a: a, b: b, xAngle: xAngle, eta: startingAngle) | |
let endDerivative = findDerivativeAtAngle(a: a, b: b, xAngle: xAngle, eta: endAngle) | |
let controlPoints = calcControlPoints(startPoint: startingPoint, endPoint: endPoint, startDerivative: startDerivative, endDerivative: endDerivative, startAngle: startingAngle, endAngle: endAngle) | |
addCubic(to: endPoint, control1: controlPoints.0, control2: controlPoints.1) | |
} | |
} | |
} | |
/// calculated the point on the ellipse for an angle value eta | |
private func findPointOnEllipse(a: CGFloat, b: CGFloat, cX: CGFloat, cY: CGFloat, xAngle: CGFloat, eta: CGFloat) -> CGPoint { | |
let pX = (cX + a*cos(xAngle)*cos(eta) - b*sin(xAngle)*sin(eta)).rounded() | |
let pY = (cY + a*sin(xAngle)*cos(eta) + b*cos(xAngle)*sin(eta)).rounded() | |
return CGPoint(x: pX, y: pY) | |
} | |
/// calculates the derivative at on the ellipse at value eta | |
private func findDerivativeAtAngle(a: CGFloat, b: CGFloat, xAngle: CGFloat, eta: CGFloat) -> CGPoint { | |
let dX = -a*cos(xAngle)*sin(eta) - b*sin(xAngle)*cos(eta) | |
let dY = -a*sin(xAngle)*sin(eta) + b*cos(xAngle)*cos(eta) | |
return CGPoint(x: dX, y: dY) | |
} | |
private func calcControlPoints(startPoint: CGPoint, endPoint: CGPoint, startDerivative: CGPoint, endDerivative: CGPoint, startAngle: CGFloat, endAngle: CGFloat) -> (CGPoint, CGPoint) { | |
// calculate the intermediate values to get alpha | |
let tanSquared = tan((endAngle - startAngle)/2)*tan((endAngle - startAngle)/2) | |
let alpha = sin(endAngle - startAngle)*(CGFloat(sqrt(Double(4+3*tanSquared)))-1)/3 | |
//calculates both control points | |
let control1 = startPoint + alpha*startDerivative | |
let control2 = endPoint - alpha*endDerivative | |
return (control1, control2) | |
} | |
/// Calculates the angle between two 2D vectors and changes the sign accordingly | |
private func angleBetweenVectors(uX: CGFloat, uY: CGFloat, vX: CGFloat, vY: CGFloat) -> CGFloat { | |
// Dot product of the two vectors | |
let dot = uX*vX + uY*vY | |
// length of vector U | |
let lenU = CGFloat(sqrt(Double(uX*uX + uY*uY))) | |
// length of vect V | |
let lenV = CGFloat(sqrt(Double(vX*vX + vY*vY))) | |
// Angle Between the vector, still needs to have the sign added. Range of acos is [0, pi] | |
var angle = acos(dot/(lenU*lenV)) | |
// checks and adjusts the sign of the angle. | |
if uX*vY-uY*vX < 0 { | |
angle = -angle | |
} | |
return angle | |
} | |
// Ensure Radii values are proper and has a solution | |
private func checkAndAdjustRadii(startPoint: CGPoint, endPoint: CGPoint, radii: CGPoint, xAngle: CGFloat, flagA: CGFloat, flagS: CGFloat) { | |
/* note in swift all angle values must be converted to radians */ | |
// Break down radii into component values, must be variables because radii could be adjust for no solution values | |
var rX = abs(radii.x) | |
var rY = abs(radii.y) | |
let rXSquared = rX*rX | |
let rYSquared = rY*rY | |
if rX == 0 || rY == 0 { | |
addLine(endPoint) | |
} else { | |
// Break down the points into individual components | |
let x1 = startPoint.x | |
let y1 = startPoint.y | |
let x2 = endPoint.x | |
let y2 = endPoint.y | |
// Calculate midPoints | |
let midX = (x1 - x2)/2 | |
let midY = (y1 - y2)/2 | |
// calculate the intermediate values xPrime and yPrime | |
let xPrime = midX*cos(xAngle) + midY*sin(xAngle) | |
let yPrime = -midX*sin(xAngle) + midY*cos(xAngle) | |
let xPrimeSquared = xPrime*xPrime | |
let yPrimeSquared = yPrime*yPrime | |
// Compare Value to this and if greater than 1 adjust each radii value | |
let lamda = xPrimeSquared/rXSquared + yPrimeSquared/rYSquared | |
// scale the radii up | |
if lamda > 1 { | |
rX = CGFloat(sqrt(Double(lamda)))*rX | |
rY = CGFloat(sqrt(Double(lamda)))*rY | |
} | |
//Convert xAngle to radians before the chain of events starts | |
let radiansXAngle = (xAngle/180)*CGFloat.pi | |
convertEndPointToCenter(startPoint: startPoint, endPoint: endPoint, radii: CGPoint(x: rX, y: rY), xAngle: radiansXAngle, flagA: flagA, flagS: flagS) | |
} | |
} | |
/// Function converts the numerical value of the flag into a boolean | |
private func convertFlagToBool(flag: CGFloat) -> Bool { | |
var flagBool: Bool | |
// as per SVG spec if the flag value is greater than 0 it is to be cosidered a 1 | |
if flag > 0 { | |
flagBool = true | |
}else { | |
flagBool = false | |
} | |
return flagBool | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment