Skip to content

Commit 2f5b441

Browse files
Smooth Quadratics (#4)
* Fixing Smooth Quadratic Parsing * Fixed and corrected reflection logic. * Fixed ellipse and path processing
1 parent ef024d6 commit 2f5b441

14 files changed

+177
-93
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.2
1+
// swift-tools-version:5.3
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
@@ -14,17 +14,27 @@ let package = Package(
1414
dependencies: [
1515
// Dependencies declare other packages that this package depends on.
1616
// .package(url: /* package url */, from: "1.0.0"),
17-
.package(url: "https://github.com/MaxDesiatov/XMLCoder.git", from: "0.11.1"),
17+
.package(url: "https://github.com/CoreOffice/XMLCoder.git", from: "0.15.0"),
1818
.package(url: "https://github.com/richardpiazza/Swift2D.git", from: "2.0.0"),
1919
],
2020
targets: [
2121
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2222
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
2323
.target(
2424
name: "SwiftSVG",
25-
dependencies: ["XMLCoder", "Swift2D"]),
25+
dependencies: [
26+
"XMLCoder",
27+
"Swift2D"
28+
]
29+
),
2630
.testTarget(
2731
name: "SwiftSVGTests",
28-
dependencies: ["SwiftSVG"]),
32+
dependencies: [
33+
"SwiftSVG"
34+
],
35+
resources: [
36+
.process("Resources")
37+
]
38+
),
2939
]
3040
)

Sources/SwiftSVG/Internal/EllipseProcessor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct EllipseProcessor {
2727
x = circle.x
2828
y = circle.y
2929
rx = circle.r
30-
ry = circle.y
30+
ry = circle.r
3131
}
3232

3333
func commands(clockwise: Bool) -> [Path.Command] {

Sources/SwiftSVG/Internal/PathProcessor.swift

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class PathProcessor {
4242
_command = nil
4343
currentPoint = pathOrigin
4444
default:
45-
try setupCommand(prefix: prefix)
45+
try setupCommand(prefix: prefix, lastCommand: _commands.last, currentPoint: currentPoint)
4646
}
4747
} else if let _value = Double(component) {
4848
let value = Double(_value)
@@ -81,17 +81,11 @@ class PathProcessor {
8181
}
8282
}
8383

84-
if case .closePath = _commands.last {
85-
// Do Nothing
86-
} else {
87-
_commands.append(.closePath)
88-
}
89-
9084
return _commands
9185
}
9286

9387
/// Setup Command
94-
private func setupCommand(prefix: Path.Command.Prefix) throws {
88+
private func setupCommand(prefix: Path.Command.Prefix, lastCommand: Path.Command?, currentPoint: Point) throws {
9589
switch prefix {
9690
case .move:
9791
_command = .moveTo(point: .nan)
@@ -138,7 +132,7 @@ class PathProcessor {
138132
case .smoothCubicBezierCurve:
139133
_command = .cubicBezierCurve(cp1: currentPoint, cp2: .nan, point: .nan)
140134
positioning = .absolute
141-
argumentPosition = 0
135+
argumentPosition = 2
142136
case .relativeSmoothCubicBezierCurve:
143137
_command = .cubicBezierCurve(cp1: currentPoint, cp2: currentPoint, point: currentPoint)
144138
positioning = .relative
@@ -152,15 +146,13 @@ class PathProcessor {
152146
positioning = .relative
153147
argumentPosition = 0
154148
case .smoothQuadraticBezierCurve:
155-
guard let command = _commands.last else {
156-
throw Path.Command.Error.invalidRelativeCommand
157-
}
158-
guard let lastControlPoint = command.lastControlPoint else {
159-
throw Path.Command.Error.invalidRelativeCommand
149+
if case .quadraticBezierCurve(let cp, _) = lastCommand {
150+
_command = .quadraticBezierCurve(cp: cp.reflection(using: currentPoint), point: .nan)
151+
} else {
152+
_command = .quadraticBezierCurve(cp: currentPoint, point: .nan)
160153
}
161-
_command = .quadraticBezierCurve(cp: lastControlPoint, point: .nan)
162154
positioning = .absolute
163-
argumentPosition = 0
155+
argumentPosition = 2
164156
case .relativeSmoothQuadraticBezierCurve:
165157
guard let command = _commands.last else {
166158
throw Path.Command.Error.invalidRelativeCommand

Sources/SwiftSVG/Internal/Point+SwiftSVG.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,43 @@ extension Point {
3434
func adjusting(y value: Double) -> Point {
3535
return (y.isNaN) ? with(y: value) : with(y: y + value)
3636
}
37+
38+
/// Determine the reflection
39+
func reflection(using point: Point) -> Point {
40+
if x < point.x {
41+
let reflectionX = point.x + (point.x - x)
42+
43+
if y < point.y {
44+
let reflectionY = point.y + (point.y - y)
45+
return Point(x: reflectionX, y: reflectionY)
46+
} else if y > point.y {
47+
let reflectionY = point.y - (y - point.y)
48+
return Point(x: reflectionX, y: reflectionY)
49+
}
50+
51+
return Point(x: reflectionX, y: y)
52+
} else if x > point.x {
53+
let reflectionX = point.x - (x - point.x)
54+
55+
if y < point.y {
56+
let reflectionY = point.y + (point.y - y)
57+
return Point(x: reflectionX, y: reflectionY)
58+
} else if y > point.y {
59+
let reflectionY = point.y - (y - point.y)
60+
return Point(x: reflectionX, y: reflectionY)
61+
}
62+
63+
return Point(x: reflectionX, y: y)
64+
}
65+
66+
if y < point.y {
67+
let reflectionY = point.y + (point.y - y)
68+
return Point(x: x, y: reflectionY)
69+
} else if y > point.y {
70+
let reflectionY = point.y - (y - point.y)
71+
return Point(x: x, y: reflectionY)
72+
}
73+
74+
return Point(x: x, y: y)
75+
}
3776
}

Tests/LinuxMain.swift

Lines changed: 0 additions & 7 deletions
This file was deleted.

Tests/SwiftSVGTests/CommandRepresentableTests.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,6 @@ import XCTest
33

44
final class CommandRepresentableTests: XCTestCase {
55

6-
static var allTests = [
7-
("testCircle", testCircle),
8-
("testLine", testLine),
9-
("testPolygon", testPolygon),
10-
("testPolyline", testPolyline),
11-
("testRectangle", testRectangle),
12-
("testRoundedRectangle", testRoundedRectangle),
13-
("testCubicRoundedRectangle", testCubicRoundedRectangle)
14-
]
15-
166
func testCircle() throws {
177
let circle = Circle(x: 50, y: 50, r: 50)
188
let offset = EllipseProcessor.controlPointOffset(circle.r)

Tests/SwiftSVGTests/Extensions.swift

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import XCTest
22
import Swift2D
33
@testable import SwiftSVG
44

5+
extension Bundle {
6+
static var swiftSVGTests: Bundle = .module
7+
}
8+
59
infix operator ~~
610
public protocol RoughEquatability {
711
static func ~~ (lhs: Self, rhs: Self) -> Bool
812
}
913

10-
#if swift(>=5.3)
1114
public func XCTAssertRoughlyEqual<T>(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) where T : RoughEquatability {
1215
let lhs: T
1316
let rhs: T
@@ -24,24 +27,6 @@ public func XCTAssertRoughlyEqual<T>(_ expression1: @autoclosure () throws -> T,
2427
return
2528
}
2629
}
27-
#else
28-
public func XCTAssertRoughlyEqual<T>(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) where T : RoughEquatability {
29-
let lhs: T
30-
let rhs: T
31-
do {
32-
lhs = try expression1()
33-
rhs = try expression2()
34-
} catch {
35-
XCTFail(error.localizedDescription, file: file, line: line)
36-
return
37-
}
38-
39-
guard lhs ~~ rhs else {
40-
XCTFail(message(), file: file, line: line)
41-
return
42-
}
43-
}
44-
#endif
4530

4631
public extension Path.Command {
4732
func hasPrefix(_ prefix: Path.Command.Prefix) -> Bool {

Tests/SwiftSVGTests/PathDataTests.swift

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import XCTest
33

44
final class PathDataTests: XCTestCase {
55

6-
static var allTests = [
7-
("testDataFormatAppleSymbols", testDataFormatAppleSymbols),
8-
("testDataFormatPixelmatorPro", testDataFormatPixelmatorPro),
9-
("testDataFormatSketch", testDataFormatSketch),
10-
("testRelativePath", testRelativePath),
11-
("testCompareFormats", testCompareFormats),
12-
("testSingleValueProcessing", testSingleValueProcessing),
13-
]
14-
156
func testDataFormatAppleSymbols() throws {
167
let pathData = "M 11.709 2.91016 C 17.1582 2.91016 21.6699 -1.60156 21.6699 -7.05078 " +
178
"C 21.6699 -12.4902 17.1484 -17.0117 11.6992 -17.0117 C 6.25977 -17.0117 1.74805 -12.4902 1.74805 -7.05078 " +
@@ -58,9 +49,9 @@ final class PathDataTests: XCTestCase {
5849

5950
let path = Path(data: pathData)
6051
let commands = try path.commands()
61-
XCTAssertEqual(commands.count, 24)
52+
XCTAssertEqual(commands.count, 23)
6253
XCTAssertEqual(commands.filter({ $0.hasPrefix(.move) }).count, 2)
63-
XCTAssertEqual(commands.filter({ $0.hasPrefix(.close) }).count, 1)
54+
XCTAssertEqual(commands.filter({ $0.hasPrefix(.close) }).count, 0)
6455
}
6556

6657
func testRelativePath() throws {

Tests/SwiftSVGTests/PointTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import XCTest
2+
import Swift2D
3+
@testable import SwiftSVG
4+
5+
final class PointTests: XCTestCase {
6+
7+
let rect = Rect(origin: .zero, size: Size(width: 500, height: 500))
8+
var center: Point { rect.center }
9+
10+
func testReflection() throws {
11+
// x=x y=y
12+
var point = Point(x: 250, y: 250)
13+
var reflection = point.reflection(using: center)
14+
XCTAssertEqual(reflection, Point(x: 250, y: 250))
15+
16+
// x→x y↓y
17+
point = Point(x: 150, y: 50)
18+
reflection = point.reflection(using: center)
19+
XCTAssertEqual(reflection, Point(x: 350, y: 450))
20+
21+
// x→x y=y
22+
point = Point(x: 150, y: 250)
23+
reflection = point.reflection(using: center)
24+
XCTAssertEqual(reflection, Point(x: 350, y: 250))
25+
26+
// x=x y↓y
27+
point = Point(x: 250, y: 50)
28+
reflection = point.reflection(using: center)
29+
XCTAssertEqual(reflection, Point(x: 250, y: 450))
30+
31+
// x←x y↑y
32+
point = Point(x: 350, y: 450)
33+
reflection = point.reflection(using: center)
34+
XCTAssertEqual(reflection, Point(x: 150, y: 50))
35+
36+
// x=x y↑y
37+
point = Point(x: 250, y: 450)
38+
reflection = point.reflection(using: center)
39+
XCTAssertEqual(reflection, Point(x: 250, y: 50))
40+
41+
// x←x y=y
42+
point = Point(x: 350, y: 250)
43+
reflection = point.reflection(using: center)
44+
XCTAssertEqual(reflection, Point(x: 150, y: 250))
45+
46+
// x→x y↑y
47+
point = Point(x: 150, y: 450)
48+
reflection = point.reflection(using: center)
49+
XCTAssertEqual(reflection, Point(x: 350, y: 50))
50+
51+
// x←x y↓y
52+
point = Point(x: 350, y: 50)
53+
reflection = point.reflection(using: center)
54+
XCTAssertEqual(reflection, Point(x: 150, y: 450))
55+
}
56+
}
Lines changed: 28 additions & 0 deletions
Loading

Tests/SwiftSVGTests/SVGTests.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import Swift2D
44

55
final class SVGTests: XCTestCase {
66

7-
static var allTests = [
8-
("testSimpleDecode", testSimpleDecode),
9-
]
10-
117
func testSimpleDecode() throws {
128
let doc = """
139
<?xml version="1.0" encoding="UTF-8"?>
@@ -45,4 +41,26 @@ final class SVGTests: XCTestCase {
4541
let svg = try SVG.make(with: data)
4642
XCTAssertEqual(svg.outputSize, Size(width: 2500, height: 2500))
4743
}
44+
45+
func testQuad01Decode() throws {
46+
let url = try XCTUnwrap(Bundle.swiftSVGTests.url(forResource: "quad01", withExtension: "svg"))
47+
let data = try Data(contentsOf: url)
48+
let svg = try SVG.make(with: data)
49+
let path = try XCTUnwrap(svg.paths?.first)
50+
XCTAssertEqual(path.data, "M200,300 Q400,50 600,300 T1000,300")
51+
let commands = try path.commands()
52+
XCTAssertEqual(commands, [
53+
.moveTo(point: Point(x: 200, y: 300)),
54+
.quadraticBezierCurve(cp: Point(x: 400, y: 50), point: Point(x: 600, y: 300)),
55+
.quadraticBezierCurve(cp: Point(x: 800, y: 550), point: Point(x: 1000, y: 300)),
56+
])
57+
58+
let primaryGroup = try XCTUnwrap(svg.groups?.first)
59+
let primaryPoints = try XCTUnwrap(primaryGroup.circles)
60+
XCTAssertEqual(primaryPoints.count, 3)
61+
62+
let secondaryGroup = try XCTUnwrap(svg.groups?.last)
63+
let secondaryPoints = try XCTUnwrap(secondaryGroup.circles)
64+
XCTAssertEqual(secondaryPoints.count, 2)
65+
}
4866
}

Tests/SwiftSVGTests/TranformationTests.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ import XCTest
33

44
final class TransformationTests: XCTestCase {
55

6-
static var allTests = [
7-
("testTranslateInitialization", testTranslateInitialization),
8-
("testMatrixInitialization", testMatrixInitialization),
9-
("testCommandTransformation", testCommandTransformation)
10-
]
11-
126
func testTranslateInitialization() {
137
var input: String = "translate(0.000000, 39.000000)"
148
var transformation: Transformation? = Transformation(input)

0 commit comments

Comments
 (0)