Class

SVG String Parser

The SVGStringParser class converts an SVG string to a CGPath.


Declaration

class SVGPathStringParser

Overview

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.

Topics

Properties

path

The path object created and returned by the parser.

let path = CGMutablePath()

currentPoint

As the path is being constructed, this object stores the latest point that has been added to the path.

var currentPoint = CGPoint()

lastControlPoint

As the path is being constructed, this object stores the latest control point that has been added to the path.

var lastControlPoint = CGPoint()

command

A UnicodeScalar? representation of the current SVG command (e.g. "M" / move, "L" / line to…) being interpreted.

var command: UnicodeScalar?

values

The values for the current command.

var values = [CGFloat]()

Enums

Error

An enum subtype of Swift.Error with cases specific for identifying issues when parsing SVG strings.

enum Error: Swift.Error {
    case invalidSyntax
    case missingValues
}

Parsing

parse()

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
}

addCommand()

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
    }
}
background Made with Flow.
underscore Made with Flow.
line2 Made with Flow.
line1 Made with Flow.
circle Made with Flow.
hit Made with Flow.

result(s) found for “”.