Skip to content

Commit 0151092

Browse files
committed
Adding support for ARC segments
1 parent 5ef2405 commit 0151092

File tree

9 files changed

+306
-18
lines changed

9 files changed

+306
-18
lines changed

Examples/Basic/Basic.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
D918D26D22122A7300097C9A /* arc2.svg in Resources */ = {isa = PBXBuildFile; fileRef = D918D26C22122A7300097C9A /* arc2.svg */; };
11+
D956E1B122103A0C00C5ADA2 /* identity.svg in Resources */ = {isa = PBXBuildFile; fileRef = D956E1B022103A0C00C5ADA2 /* identity.svg */; };
1012
D9618466220FDD1100C59D9B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9618465220FDD1100C59D9B /* AppDelegate.swift */; };
1113
D9618468220FDD1100C59D9B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9618467220FDD1100C59D9B /* ViewController.swift */; };
1214
D961846D220FDD1200C59D9B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D961846C220FDD1200C59D9B /* Assets.xcassets */; };
@@ -43,6 +45,8 @@
4345
/* End PBXCopyFilesBuildPhase section */
4446

4547
/* Begin PBXFileReference section */
48+
D918D26C22122A7300097C9A /* arc2.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = arc2.svg; sourceTree = "<group>"; };
49+
D956E1B022103A0C00C5ADA2 /* identity.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = identity.svg; sourceTree = "<group>"; };
4650
D9618462220FDD1100C59D9B /* Basic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Basic.app; sourceTree = BUILT_PRODUCTS_DIR; };
4751
D9618465220FDD1100C59D9B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
4852
D9618467220FDD1100C59D9B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -117,11 +121,13 @@
117121
D9ACD786220FDDE0009717CF /* transform.svg */,
118122
D9ACD787220FDDE0009717CF /* aa.svg */,
119123
D9ACD788220FDDE0009717CF /* curves.svg */,
124+
D956E1B022103A0C00C5ADA2 /* identity.svg */,
120125
D9ACD789220FDDE0009717CF /* shapes.svg */,
121126
D9ACD78A220FDDE0009717CF /* lines.svg */,
122127
D9ACD78B220FDDE0009717CF /* starry.svg */,
123128
D9ACD78C220FDDE0009717CF /* mask.svg */,
124129
D9ACD78D220FDDE0009717CF /* arc.svg */,
130+
D918D26C22122A7300097C9A /* arc2.svg */,
125131
D9ACD78E220FDDE0009717CF /* quad.svg */,
126132
D9ACD78F220FDDE0009717CF /* display.svg */,
127133
D9ACD790220FDDE0009717CF /* invalid.svg */,
@@ -207,9 +213,11 @@
207213
D9ACD793220FDDE0009717CF /* viewbox.svg in Resources */,
208214
D9618470220FDD1200C59D9B /* LaunchScreen.storyboard in Resources */,
209215
D9ACD798220FDDE0009717CF /* lines.svg in Resources */,
216+
D918D26D22122A7300097C9A /* arc2.svg in Resources */,
210217
D9ACD79B220FDDE0009717CF /* arc.svg in Resources */,
211218
D961846D220FDD1200C59D9B /* Assets.xcassets in Resources */,
212219
D9ACD79D220FDDE0009717CF /* display.svg in Resources */,
220+
D956E1B122103A0C00C5ADA2 /* identity.svg in Resources */,
213221
D9ACD792220FDDE0009717CF /* path.svg in Resources */,
214222
D9ACD79A220FDDE0009717CF /* mask.svg in Resources */,
215223
D9ACD794220FDDE0009717CF /* transform.svg in Resources */,

Examples/Basic/Sources/ViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ViewController: UIViewController {
3636

3737
override func loadView() {
3838
let imageView = UIImageView(frame: UIScreen.main.bounds)
39-
imageView.image = UIImage(svgNamed: "shapes.svg")
39+
imageView.image = UIImage(svgNamed: "arc2.svg")
4040
imageView.contentMode = .scaleAspectFit
4141
imageView.backgroundColor = .white
4242
self.view = imageView
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict/>
5+
</plist>

Samples/arc2.svg

Lines changed: 61 additions & 0 deletions
Loading

Samples/identity.svg

Lines changed: 10 additions & 0 deletions
Loading

SwiftDraw.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,12 @@
182182
D9778E5C1E6ED66300FF6EBB /* shapes.svg in Resources */ = {isa = PBXBuildFile; fileRef = D965B5811E3C48C6003E859E /* shapes.svg */; };
183183
D97AD81A21BE40A700F7DD10 /* Parser.XML.GradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97AD81921BE40A700F7DD10 /* Parser.XML.GradientTests.swift */; };
184184
D97AD81B21BE40A700F7DD10 /* Parser.XML.GradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97AD81921BE40A700F7DD10 /* Parser.XML.GradientTests.swift */; };
185+
D97C36582212194F006D28D2 /* LayerTree.Builder.Path.Arc.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9ACD7A4220FDF79009717CF /* LayerTree.Builder.Path.Arc.swift */; };
185186
D9A843931E85F1C1002A804B /* quad.svg in Resources */ = {isa = PBXBuildFile; fileRef = D9A843921E85F1C1002A804B /* quad.svg */; };
186187
D9A843941E85F1C1002A804B /* quad.svg in Resources */ = {isa = PBXBuildFile; fileRef = D9A843921E85F1C1002A804B /* quad.svg */; };
187188
D9A843951E8611D6002A804B /* curves.svg in Resources */ = {isa = PBXBuildFile; fileRef = D9A843901E855672002A804B /* curves.svg */; };
188189
D9A843961E8611D7002A804B /* curves.svg in Resources */ = {isa = PBXBuildFile; fileRef = D9A843901E855672002A804B /* curves.svg */; };
190+
D9ACD7A5220FDF79009717CF /* LayerTree.Builder.Path.Arc.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9ACD7A4220FDF79009717CF /* LayerTree.Builder.Path.Arc.swift */; };
189191
D9BA810021B9C5E5005159E7 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BA80FF21B9C5E5005159E7 /* CommandLine.swift */; };
190192
D9BA810221B9C624005159E7 /* CommandLine.Arguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BA810121B9C624005159E7 /* CommandLine.Arguments.swift */; };
191193
D9BA810521B9C665005159E7 /* CommandLine.ArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BA810421B9C665005159E7 /* CommandLine.ArgumentsTests.swift */; };
@@ -340,6 +342,7 @@
340342
D97AD81921BE40A700F7DD10 /* Parser.XML.GradientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.XML.GradientTests.swift; sourceTree = "<group>"; };
341343
D9A843901E855672002A804B /* curves.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = curves.svg; sourceTree = "<group>"; };
342344
D9A843921E85F1C1002A804B /* quad.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = quad.svg; sourceTree = "<group>"; };
345+
D9ACD7A4220FDF79009717CF /* LayerTree.Builder.Path.Arc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerTree.Builder.Path.Arc.swift; sourceTree = "<group>"; };
343346
D9BA80FF21B9C5E5005159E7 /* CommandLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLine.swift; sourceTree = "<group>"; };
344347
D9BA810121B9C624005159E7 /* CommandLine.Arguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLine.Arguments.swift; sourceTree = "<group>"; };
345348
D9BA810421B9C665005159E7 /* CommandLine.ArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLine.ArgumentsTests.swift; sourceTree = "<group>"; };
@@ -434,6 +437,7 @@
434437
D90DB3A7219CCC4000D374D2 /* LayerTree.swift */,
435438
D925CC6921A4B7E700C8897B /* LayerTree.Builder.Layer.swift */,
436439
D90DB3A8219CCC4000D374D2 /* LayerTree.Builder.Path.swift */,
440+
D9ACD7A4220FDF79009717CF /* LayerTree.Builder.Path.Arc.swift */,
437441
D925CC6021A49C9C00C8897B /* LayerTree.Builder.Shape.swift */,
438442
D90DB3AA219CCC4000D374D2 /* LayerTree.AB.Builder.swift */,
439443
D90DB3AB219CCC4000D374D2 /* LayerTree.Color.swift */,
@@ -1014,6 +1018,7 @@
10141018
D90DB3DA219CCCDA00D374D2 /* LayerTree.Builder.Path.swift in Sources */,
10151019
D90DB3C2219CCC4500D374D2 /* Renderer.CoreGraphics.swift in Sources */,
10161020
D90DB3B1219CCC4100D374D2 /* LayerTree.Transform.swift in Sources */,
1021+
D97C36582212194F006D28D2 /* LayerTree.Builder.Path.Arc.swift in Sources */,
10171022
D90DB44F219CD27800D374D2 /* Parser.XML.swift in Sources */,
10181023
D90DB453219CD27800D374D2 /* Parser.XML.Path.swift in Sources */,
10191024
D90DB457219CD27800D374D2 /* Parser.XML.Attributes.swift in Sources */,
@@ -1091,6 +1096,7 @@
10911096
D90DB3AE219CCC4100D374D2 /* LayerTree.Shape.swift in Sources */,
10921097
D90DB431219CD1C600D374D2 /* DOM.Text.swift in Sources */,
10931098
D90DB408219CD06800D374D2 /* XML.Parser.Scanner.swift in Sources */,
1099+
D9ACD7A5220FDF79009717CF /* LayerTree.Builder.Path.Arc.swift in Sources */,
10941100
D90DB3C0219CCC4100D374D2 /* LayerTree.CommandGenerator.swift in Sources */,
10951101
D90DB3F8219CCF6A00D374D2 /* Stack.swift in Sources */,
10961102
D90DB3A2219CCBBD00D374D2 /* Renderer.LayerTree.swift in Sources */,
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//
2+
// LayerTree.Builder.Path.Arc.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 10/2/19.
6+
// Copyright © 2019 WhileLoop Pty Ltd. All rights reserved.
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
import Foundation
33+
34+
//converts DOM.Path.Arc -> LayerTree.Path.Cubic
35+
36+
private extension Comparable {
37+
func clamped(to limits: ClosedRange<Self>) -> Self {
38+
return min(max(self, limits.lowerBound), limits.upperBound)
39+
}
40+
}
41+
42+
private func almostEqual<T: FloatingPoint>(_ a: T, _ b: T) -> Bool {
43+
return a >= b.nextDown && a <= b.nextUp
44+
}
45+
46+
private func vectorAngle(ux: LayerTree.Float, uy: LayerTree.Float, vx: LayerTree.Float, vy: LayerTree.Float) -> LayerTree.Float {
47+
let sign: LayerTree.Float = (ux * vy - uy * vx) < 0.0 ? -1.0 : 1.0
48+
let dot = (ux * vx + uy * vy).clamped(to: -1.0...1.0)
49+
return sign * acos(dot)
50+
}
51+
52+
private extension LayerTree.Float {
53+
54+
static var tau: LayerTree.Float {
55+
return .pi * 2
56+
}
57+
}
58+
59+
private func getArcCenter(from origin: LayerTree.Point, to destination: LayerTree.Point,
60+
fa: Bool, fs: Bool,
61+
rx: LayerTree.Float, ry: LayerTree.Float,
62+
sin_phi: LayerTree.Float,
63+
cos_phi: LayerTree.Float) -> (point: LayerTree.Point, theta: LayerTree.Float, deltaT: LayerTree.Float) {
64+
65+
let x1p = cos_phi * (origin.x - destination.x) / 2 + sin_phi * (origin.y - destination.y) / 2
66+
let y1p = -sin_phi * (origin.x - destination.x) / 2 + cos_phi * (origin.y - destination.y) / 2
67+
68+
let rx_sq = rx * rx
69+
let ry_sq = ry * ry
70+
let x1p_sq = x1p * x1p
71+
let y1p_sq = y1p * y1p
72+
73+
var radicant = max((rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq), 0.0)
74+
let radicantSign: LayerTree.Float = fa == fs ? -1.0 : 1.0
75+
76+
radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq)
77+
radicant = radicant.squareRoot() * radicantSign
78+
79+
let cxp = radicant * rx / ry * y1p
80+
let cyp = radicant * -ry / rx * x1p
81+
82+
let cx = cos_phi * cxp - sin_phi * cyp + (origin.x + destination.x) / 2
83+
let cy = sin_phi * cxp + cos_phi * cyp + (origin.y + destination.y) / 2
84+
85+
let v1x = (x1p - cxp) / rx
86+
let v1y = (y1p - cyp) / ry
87+
let v2x = (-x1p - cxp) / rx
88+
let v2y = (-y1p - cyp) / ry
89+
90+
let theta1 = vectorAngle(ux: 1, uy: 0, vx: v1x, vy: v1y);
91+
var delta_theta = vectorAngle(ux: v1x, uy: v1y, vx: v2x, vy: v2y);
92+
93+
if (fs == false && delta_theta > 0) {
94+
delta_theta -= .tau;
95+
}
96+
if (fs == true && delta_theta < 0) {
97+
delta_theta += .tau;
98+
}
99+
100+
return (point: LayerTree.Point(cx, cy), theta: theta1, deltaT: delta_theta)
101+
}
102+
103+
private func approximateUnitArc(theta: LayerTree.Float, deltaT: LayerTree.Float) -> [LayerTree.Float] {
104+
let alpha = (4.0 / 3.0) * tan(deltaT / 4.0)
105+
106+
let x1 = cos(theta)
107+
let y1 = sin(theta)
108+
let x2 = cos(theta + deltaT)
109+
let y2 = sin(theta + deltaT)
110+
111+
return [x1,
112+
y1,
113+
x1 - y1 * alpha,
114+
y1 + x1 * alpha,
115+
x2 + y2 * alpha,
116+
y2 - x2 * alpha,
117+
x2,
118+
y2]
119+
}
120+
121+
122+
private func makePoint(x: LayerTree.Float, y: LayerTree.Float, rx: LayerTree.Float, ry: LayerTree.Float, cos_phi: LayerTree.Float, sin_phi: LayerTree.Float, center: LayerTree.Point) -> LayerTree.Point {
123+
124+
let x1 = x * rx
125+
let y1 = y * ry
126+
127+
let xp = cos_phi * x1 - sin_phi * y1
128+
let yp = sin_phi * x1 + cos_phi * y1
129+
130+
return LayerTree.Point(xp + center.x, yp + center.y)
131+
}
132+
133+
func makeCubic(from origin: LayerTree.Point, to destination: LayerTree.Point,
134+
large: Bool, sweep: Bool,
135+
rx: LayerTree.Float, ry: LayerTree.Float,
136+
rotation: LayerTree.Float) -> [(cp1: LayerTree.Point, cp2: LayerTree.Point, p: LayerTree.Point)] {
137+
138+
let sin_phi = sin(rotation * .tau / 360.0)
139+
let cos_phi = cos(rotation * .tau / 360.0)
140+
141+
let x1p = cos_phi * (origin.x - destination.x) / 2.0 + sin_phi * (origin.y - destination.y) / 2.0
142+
let y1p = -sin_phi * (origin.x - destination.x) / 2.0 + cos_phi * (origin.y - destination.y) / 2.0
143+
144+
if almostEqual(x1p, 0.0) && almostEqual(y1p, 0.0) {
145+
return [];
146+
}
147+
148+
if almostEqual(rx, 0.0) || almostEqual(ry, 0.0) {
149+
return [];
150+
}
151+
152+
var rx1 = abs(rx)
153+
var ry1 = abs(ry)
154+
155+
let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry)
156+
if (lambda > 1.0) {
157+
let lambdaSquareRoot = lambda.squareRoot()
158+
rx1 *= lambdaSquareRoot
159+
ry1 *= lambdaSquareRoot
160+
}
161+
162+
let cc = getArcCenter(from: origin, to: destination, fa: large, fs: sweep, rx: rx, ry: ry, sin_phi: sin_phi, cos_phi: cos_phi)
163+
164+
var result = [[LayerTree.Float]]()
165+
166+
let segments = max(Int(ceil(abs(cc.deltaT) / (LayerTree.Float.tau / 4.0))), 1)
167+
let deltaT = cc.deltaT / LayerTree.Float(segments)
168+
169+
var theta1 = cc.theta
170+
171+
for _ in 0..<segments {
172+
let unit = approximateUnitArc(theta: theta1, deltaT: deltaT)
173+
result.append(unit)
174+
theta1 += deltaT
175+
}
176+
177+
return result.map {
178+
let cp1 = makePoint(x: $0[2], y: $0[3], rx: rx1, ry: ry1, cos_phi: cos_phi, sin_phi: sin_phi, center: cc.point)
179+
let cp2 = makePoint(x: $0[4], y: $0[5], rx: rx1, ry: ry1, cos_phi: cos_phi, sin_phi: sin_phi, center: cc.point)
180+
let p = makePoint(x: $0[6], y: $0[7], rx: rx1, ry: ry1, cos_phi: cos_phi, sin_phi: sin_phi, center: cc.point)
181+
182+
return (cp1: cp1, cp2: cp2, p: p)
183+
}
184+
}

0 commit comments

Comments
 (0)