Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple commands MoveTo #160

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions SwiftSVG/SVG/Elements/SVGPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ final class SVGPath: SVGShapeElement, ParsesAsynchronously, DelaysApplyingAttrib
let parsePathClosure = {
var previousCommand: PreviousCommand? = nil
for thisPathCommand in PathDLexer(pathString: workingString) {
thisPathCommand.execute(on: pathDPath, previousCommand: previousCommand)
previousCommand = thisPathCommand
previousCommand = thisPathCommand.execute(on: pathDPath, previousCommand: previousCommand)
}
}

Expand Down
38 changes: 23 additions & 15 deletions SwiftSVG/SVG/Iterators/PathCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ internal protocol PathCommand: PreviousCommand {
- Parameter path: The path to append a new path to
- Parameter previousCommand: An optional previous command. Used primarily with the shortcut cubic and quadratic Bezier types
*/
func execute(on path: UIBezierPath, previousCommand: PreviousCommand?)
func execute(on path: UIBezierPath, previousCommand: PreviousCommand?) -> PreviousCommand?
}

/**
Expand Down Expand Up @@ -162,25 +162,24 @@ internal struct MoveTo: PathCommand {
/**
This will move the current point to `CGPoint(self.coordinateBuffer[0], self.coordinateBuffer[1])`.

Sequential MoveTo commands should be treated as LineTos.
Sequential implicit MoveTo commands should be treated as LineTos.

From Docs (https://www.w3.org/TR/SVG2/paths.html#PathDataMovetoCommands):

```
Start a new sub-path at the given (x,y) coordinates. M (uppercase) indicates that absolute coordinates will follow; m (lowercase) indicates that relative coordinates will follow. If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands. Hence, implicit lineto commands will be relative if the moveto is relative, and absolute if the moveto is absolute.
```
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {

internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
if previousCommand is MoveTo {
var implicitLineTo = LineTo(pathType: self.pathType)
implicitLineTo.coordinateBuffer = [self.coordinateBuffer[0], self.coordinateBuffer[1]]
implicitLineTo.execute(on: path)
return
return implicitLineTo.execute(on: path)
}

let point = self.pointForPathType(CGPoint(x: self.coordinateBuffer[0], y: self.coordinateBuffer[1]), relativeTo: path.currentPoint)
path.move(to: point)
return self
}
}

Expand All @@ -206,8 +205,9 @@ internal struct ClosePath: PathCommand {
/**
Closes the current path
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
path.close()
return self
}

}
Expand All @@ -234,9 +234,10 @@ internal struct LineTo: PathCommand {
/**
Creates a line from the `path.currentPoint` to point `CGPoint(self.coordinateBuffer[0], coordinateBuffer[1])`
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
let point = self.pointForPathType(CGPoint(x: self.coordinateBuffer[0], y: self.coordinateBuffer[1]), relativeTo: path.currentPoint)
path.addLine(to: point)
return self
}
}

Expand All @@ -262,10 +263,11 @@ internal struct HorizontalLineTo: PathCommand {
/**
Adds a horizontal line from the currentPoint to `CGPoint(self.coordinateBuffer[0], path.currentPoint.y)`
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
let x = self.coordinateBuffer[0]
let point = (self.pathType == .absolute ? CGPoint(x: CGFloat(x), y: path.currentPoint.y) : CGPoint(x: path.currentPoint.x + CGFloat(x), y: path.currentPoint.y))
path.addLine(to: point)
return self
}
}

Expand All @@ -291,10 +293,11 @@ internal struct VerticalLineTo: PathCommand {
/**
Adds a vertical line from the currentPoint to `CGPoint(path.currentPoint.y, self.coordinateBuffer[0])`
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
let y = self.coordinateBuffer[0]
let point = (self.pathType == .absolute ? CGPoint(x: path.currentPoint.x, y: CGFloat(y)) : CGPoint(x: path.currentPoint.x, y: path.currentPoint.y + CGFloat(y)))
path.addLine(to: point)
return self
}
}

Expand All @@ -320,11 +323,12 @@ internal struct CurveTo: PathCommand {
/**
Adds a cubic Bezier curve to `path`. The path will end up at `CGPoint(self.coordinateBuffer[4], self.coordinateBuffer[5])`. The control point for `path.currentPoint` will be `CGPoint(self.coordinateBuffer[0], self.coordinateBuffer[1])`. Then controle point for the end point will be CGPoint(self.coordinateBuffer[2], self.coordinateBuffer[3])
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
let startControl = self.pointForPathType(CGPoint(x: self.coordinateBuffer[0], y: self.coordinateBuffer[1]), relativeTo: path.currentPoint)
let endControl = self.pointForPathType(CGPoint(x: self.coordinateBuffer[2], y: self.coordinateBuffer[3]), relativeTo: path.currentPoint)
let point = self.pointForPathType(CGPoint(x: self.coordinateBuffer[4], y: self.coordinateBuffer[5]), relativeTo: path.currentPoint)
path.addCurve(to: point, controlPoint1: startControl, controlPoint2: endControl)
return self
}
}

Expand All @@ -350,7 +354,7 @@ internal struct SmoothCurveTo: PathCommand {
/**
Shortcut cubic Bezier curve to that add a new path ending up at `CGPoint(self.coordinateBuffer[0], self.coordinateBuffer[1])` with a single control point in the middle.
*/
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {

let point = self.pointForPathType(CGPoint(x: self.coordinateBuffer[2], y: self.coordinateBuffer[3]), relativeTo: path.currentPoint)
let controlEnd = self.pointForPathType(CGPoint(x: self.coordinateBuffer[0], y: self.coordinateBuffer[1]), relativeTo: path.currentPoint)
Expand Down Expand Up @@ -394,6 +398,7 @@ internal struct SmoothCurveTo: PathCommand {
controlStart = path.currentPoint
}
path.addCurve(to: point, controlPoint1: controlStart, controlPoint2: controlEnd)
return self
}
}

Expand All @@ -416,10 +421,11 @@ internal struct QuadraticCurveTo: PathCommand {
self.pathType = pathType
}

internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
let controlPoint = self.pointForPathType(CGPoint(x: self.coordinateBuffer[0], y: self.coordinateBuffer[1]), relativeTo: path.currentPoint)
let point = self.pointForPathType(CGPoint(x: self.coordinateBuffer[2], y: self.coordinateBuffer[3]), relativeTo: path.currentPoint)
path.addQuadCurve(to: point, controlPoint: controlPoint)
return self
}
}

Expand All @@ -445,7 +451,7 @@ internal struct SmoothQuadraticCurveTo: PathCommand {
self.pathType = pathType
}

internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {

let point = self.pointForPathType(CGPoint(x: self.coordinateBuffer[0], y: self.coordinateBuffer[1]), relativeTo: path.currentPoint)

Expand All @@ -471,6 +477,7 @@ internal struct SmoothQuadraticCurveTo: PathCommand {
controlPoint = path.currentPoint
}
path.addQuadCurve(to: point, controlPoint: controlPoint)
return self
}
}

Expand All @@ -495,7 +502,8 @@ internal struct EllipticalArc: PathCommand {
}

/// :nodoc:
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
internal func execute(on path: UIBezierPath, previousCommand: PreviousCommand? = nil) -> PreviousCommand? {
assert(false, "Needs Implementation")
return self
}
}
2 changes: 1 addition & 1 deletion SwiftSVGTests/CGPathPoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ extension PathCommand {
init(parameters: [Double], pathType: PathType, path: UIBezierPath, previousCommand: PreviousCommand? = nil) {
self.init(pathType: pathType)
self.coordinateBuffer = parameters
self.execute(on: path, previousCommand: previousCommand)
_ = self.execute(on: path, previousCommand: previousCommand)
}

}
Expand Down
27 changes: 20 additions & 7 deletions SwiftSVGTests/PathDLexerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class PathDLexerTests: XCTestCase {
let testString = "M80,45z"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
XCTAssert(testPath.cgPath.pointsAndTypes[0].1 == .moveToPoint, "Expected MoveTo, got \(type(of: testPath.cgPath.pointsAndTypes[0].1))")
XCTAssert(testPath.currentPoint.x == 80 && testPath.currentPoint.y == 45, "Expected {80, 45}, got \(testPath.currentPoint)")
Expand All @@ -46,7 +46,7 @@ class PathDLexerTests: XCTestCase {
let testString = "M80-45z"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
XCTAssert(testPath.cgPath.pointsAndTypes[0].1 == .moveToPoint, "Expected MoveTo, got \(type(of: testPath.cgPath.pointsAndTypes[0].1))")
XCTAssert(testPath.currentPoint.x == 80 && testPath.currentPoint.y == -45, "Expected {80, -45}, got \(testPath.currentPoint)")
Expand All @@ -56,7 +56,7 @@ class PathDLexerTests: XCTestCase {
let testString = "M80 -45z"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
XCTAssert(testPath.cgPath.pointsAndTypes[0].1 == .moveToPoint, "Expected MoveTo, got \(type(of: testPath.cgPath.pointsAndTypes[0].1))")
XCTAssert(testPath.currentPoint.x == 80 && testPath.currentPoint.y == -45, "Expected {80, -45}, got \(testPath.currentPoint)")
Expand All @@ -66,7 +66,7 @@ class PathDLexerTests: XCTestCase {
let testString = "M 47 -127 z"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
XCTAssert(testPath.cgPath.pointsAndTypes[0].1 == .moveToPoint, "Expected MoveTo, got \(type(of: testPath.cgPath.pointsAndTypes[0].1))")
XCTAssert(testPath.currentPoint.x == 47 && testPath.currentPoint.y == -127, "Expected {47, -127}, got \(testPath.currentPoint)")
Expand All @@ -76,7 +76,7 @@ class PathDLexerTests: XCTestCase {
let testString = "M30-40L20,50z M10,20L80,90z"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
XCTAssert(testPath.cgPath.pointsAndTypes[2].1 == .closeSubpath, "Expected .closeSubpath, got \(String(describing: testPath.cgPath.pointsAndTypes[2].1))")
XCTAssert(testPath.cgPath.pointsAndTypes.last!.1 == .closeSubpath, "Expected .closeSubpath, got \(String(describing: testPath.cgPath.pointsAndTypes.last!.1))")
Expand All @@ -86,19 +86,32 @@ class PathDLexerTests: XCTestCase {
let testString = "m193.2 162.44c4.169 0 8.065 0.631 12.327 0.631 3.391 0 7.062-0.404 10.427 0 0.881 0.105 3.056 0.187 3.793 0.633 1.551 0.939 1.013 0.261 1.265 2.527 0.164 1.476 0 3.078 0 4.565 0 1.211-0.826 6.618-0.315 7.129 1.238 1.238 5.366 2.607 7.269 3.476 2.513 1.146 4.488 2.562 6.952 3.793 2.435 1.217 5.177 1.804 4.426 4.424-0.364 1.271-1.808 2.668-2.529 3.793-0.438 0.684-2.528 3.339-2.528 4.109 0 0.606-13.593-2.429-15.17-2.846-2.26-0.598-5.387-0.692-6.004-3.16-0.631-2.525-0.661-5.73-0.949-8.217-0.358-3.095 1.059-6.32-2.844-6.32-2.102 0-5.384 0.628-7.269-0.316-1.008-0.505-2.737 0.105-3.476-0.632-0.979-0.977-1.59-1.802-2.214-3.478-1.23-3.25-2.25-6.73-3.17-10.1"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
}

func testLeadingPoint() {
let testString = "M1.5.5z"
let testPath = UIBezierPath()
for thisCommand in PathDLexer(pathString: testString) {
thisCommand.execute(on: testPath, previousCommand: nil)
_ = thisCommand.execute(on: testPath, previousCommand: nil)
}
XCTAssert(testPath.cgPath.pointsAndTypes[0].1 == .moveToPoint, "Expected MoveTo, got \(type(of: testPath.cgPath.pointsAndTypes[0].1))")
XCTAssert(testPath.currentPoint.x == 1.5 && testPath.currentPoint.y == 0.5, "Expected {1.5, 0.5}, got \(testPath.currentPoint)")
}

func testMultipleMoveTo() {
let testString = "m40,40 40,0 m0,40 0,-40"
let testPath = UIBezierPath()
var previousCommand: PreviousCommand? = nil
for thisCommand in PathDLexer(pathString: testString) {
previousCommand = thisCommand.execute(on: testPath, previousCommand: previousCommand)
}
XCTAssert(testPath.cgPath.pointsAndTypes[0].1 == .moveToPoint, "Expected MoveTo, got \(type(of: testPath.cgPath.pointsAndTypes[0].1))")
XCTAssert(testPath.cgPath.pointsAndTypes[1].1 == .addLineToPoint, "Expected addLineToPoint, got \(type(of: testPath.cgPath.pointsAndTypes[1].1))")
XCTAssert(testPath.cgPath.pointsAndTypes[2].1 == .moveToPoint, "Expected moveToPoint, got \(type(of: testPath.cgPath.pointsAndTypes[2].1))")
XCTAssert(testPath.cgPath.pointsAndTypes[3].1 == .addLineToPoint, "Expected addLineToPoint, got \(type(of: testPath.cgPath.pointsAndTypes[3].1))")
}
}


Expand Down