Class
The SVGStringParser class converts an SVG string to a CGPath.
class SVGPathStringParser
This class is very handy for allowing exports from Flow to consistently use SVG strings. It can be used on its own in any iOS project, as long as the SVGPathStringTokenizer
is also included.
The path object created and returned by the parser.
let path = CGMutablePath()
As the path is being constructed, this object stores the latest point that has been added to the path.
var currentPoint = CGPoint()
As the path is being constructed, this object stores the latest control point that has been added to the path.
var lastControlPoint = CGPoint()
A UnicodeScalar?
representation of the current SVG command (e.g. "M"
/ move, "L"
/ line to…) being interpreted.
var command: UnicodeScalar?
The values for the current command.
var values = [CGFloat]()
An enum subtype of Swift.Error
with cases specific for identifying issues when parsing SVG strings.
enum Error: Swift.Error {
case invalidSyntax
case missingValues
}
This command tokenizes an SVG string, then iterates through those tokens to construct and return a CGPath
.
func parse(_ string: String) throws -> CGPath {
let tokens = SVGPathStringTokenizer(string: string).tokenize()
for token in tokens {
switch token {
case .command(let c):
command = c
values.removeAll()
case .value(let v):
values.append(v)
}
do {
try addCommand()
} catch Error.missingValues {
// Ignore
}
}
return path
}
This method attempts to add an element to path
by interpreting the current command
(which is a UnicodeScalar?
).
fileprivate func addCommand() throws {
guard let command = command else {
return
}
switch command {
case "M":
if values.count < 2 { throw Error.missingValues }
path.move(to: CGPoint(x: values[0], y: values[1]))
currentPoint = CGPoint(x: values[0], y: values[1])
lastControlPoint = currentPoint
values.removeFirst(2)
case "m":
if values.count < 2 { throw Error.missingValues }
let point = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y + values[1])
path.move(to: point)
currentPoint.x += values[0]
currentPoint.y += values[1]
lastControlPoint = currentPoint
values.removeFirst(2)
case "L":
if values.count < 2 { throw Error.missingValues }
let point = CGPoint(x: values[0], y: values[1])
path.addLine(to: point)
currentPoint = CGPoint(x: values[0], y: values[1])
lastControlPoint = currentPoint
values.removeFirst(2)
case "l":
if values.count < 2 { throw Error.missingValues }
let point = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y + values[1])
path.addLine(to: point)
currentPoint.x += values[0]
currentPoint.y += values[1]
lastControlPoint = currentPoint
values.removeFirst(2)
case "H":
if values.count < 1 { throw Error.missingValues }
let point = CGPoint(x: values[0], y: currentPoint.y)
path.addLine(to: point)
currentPoint.x = values[0]
lastControlPoint = currentPoint
values.removeFirst(1)
case "h":
if values.count < 1 { throw Error.missingValues }
let point = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y)
path.addLine(to: point)
currentPoint.x += values[0]
lastControlPoint = currentPoint
values.removeFirst(1)
case "V":
if values.count < 1 { throw Error.missingValues }
let point = CGPoint(x: currentPoint.x, y: values[0])
path.addLine(to: point)
currentPoint.y = values[0]
lastControlPoint = currentPoint
values.removeFirst(1)
case "v":
if values.count < 1 { throw Error.missingValues }
let point = CGPoint(x: currentPoint.x, y: currentPoint.y + values[0])
path.addLine(to: point)
currentPoint.y += values[0]
lastControlPoint = currentPoint
values.removeFirst(1)
case "C":
if values.count < 6 { throw Error.missingValues }
let c1 = CGPoint(x: values[0], y: values[1])
let c2 = CGPoint(x: values[2], y: values[3])
let to = CGPoint(x: values[4], y: values[5])
path.addCurve(to: to, control1: c1, control2: c2)
lastControlPoint = c2
currentPoint = to
values.removeFirst(6)
case "c":
if values.count < 6 { throw Error.missingValues }
let c1 = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y + values[1])
let c2 = CGPoint(x: currentPoint.x + values[2], y: currentPoint.y + values[3])
let to = CGPoint(x: currentPoint.x + values[4], y: currentPoint.y + values[5])
path.addCurve(to: to, control1: c1, control2: c2)
lastControlPoint = c2
currentPoint = to
values.removeFirst(6)
case "S":
if values.count < 4 { throw Error.missingValues }
var c1 = CGPoint()
c1.x = currentPoint.x + (currentPoint.x - lastControlPoint.x)
c1.y = currentPoint.y + (currentPoint.y - lastControlPoint.y)
let c2 = CGPoint(x: values[0], y: values[1])
let to = CGPoint(x: values[2], y: values[3])
path.addCurve(to: to, control1: c1, control2: c2)
lastControlPoint = c2
currentPoint = to
values.removeFirst(4)
case "s":
if values.count < 4 { throw Error.missingValues }
var c1 = CGPoint()
c1.x = currentPoint.x + (currentPoint.x - lastControlPoint.x)
c1.y = currentPoint.y + (currentPoint.y - lastControlPoint.y)
let c2 = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y + values[1])
let to = CGPoint(x: currentPoint.x + values[2], y: currentPoint.y + values[3])
path.addCurve(to: to, control1: c1, control2: c2)
lastControlPoint = c2
currentPoint = to
values.removeFirst(4)
case "Q":
if values.count < 4 { throw Error.missingValues }
let control = CGPoint(x: values[0], y: values[1])
let to = CGPoint(x: values[2], y: values[3])
path.addQuadCurve(to: to, control: control)
lastControlPoint = control
currentPoint = to
values.removeFirst(4)
case "q":
if values.count < 4 { throw Error.missingValues }
let control = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y + values[1])
let to = CGPoint(x: currentPoint.x + values[2], y: currentPoint.y + values[3])
path.addQuadCurve(to: to, control: control)
lastControlPoint = control
currentPoint = to
values.removeFirst(4)
case "T":
if values.count < 2 { throw Error.missingValues }
var control = CGPoint()
control.x = currentPoint.x + (currentPoint.x - lastControlPoint.x)
control.y = currentPoint.y + (currentPoint.y - lastControlPoint.y)
let to = CGPoint(x: values[0], y: values[1])
path.addQuadCurve(to: to, control: control)
lastControlPoint = control
currentPoint = to
values.removeFirst(2)
case "t":
if values.count < 2 { throw Error.missingValues }
var control = CGPoint()
control.x = currentPoint.x + (currentPoint.x - lastControlPoint.x)
control.y = currentPoint.y + (currentPoint.y - lastControlPoint.y)
let to = CGPoint(x: currentPoint.x + values[0], y: currentPoint.y + values[1])
path.addQuadCurve(to: to, control: control)
lastControlPoint = control
currentPoint = to
values.removeFirst(2)
case "Z", "z":
path.closeSubpath()
self.command = nil
default:
throw Error.invalidSyntax
}
}
result(s) found for “”.