swhitty / SwiftDraw

@@ -43,6 +43,11 @@
Loading
43 43
      case cubic(to: Point, control1: Point, control2: Point)
44 44
      case close
45 45
    }
46 +
47 +
    enum Direction {
48 +
      case clockwise
49 +
      case anticlockwise
50 +
    }
46 51
    
47 52
    func hash(into hasher: inout Hasher) {
48 53
      hasher.combine(self.segments)
@@ -125,3 +130,30 @@
Loading
125 130
        }
126 131
    }
127 132
}
133 +
134 +
135 +
extension BidirectionalCollection where Element == LayerTree.Path.Segment, Index == Int {
136 +
137 +
    // Determine direction by sign of calculated area
138 +
    // https://www.101computing.net/the-shoelace-algorithm/
139 +
    //
140 +
    var direction: LayerTree.Path.Direction {
141 +
        var lhs: LayerTree.Float = 0
142 +
        var rhs: LayerTree.Float = 0
143 +
        for (current, next) in compactMap(\.location).paired(with: .nextWrappingToFirst) {
144 +
            lhs += current.x * next.y
145 +
            rhs += current.y * next.x
146 +
        }
147 +
148 +
        return (lhs - rhs) < 0 ? .anticlockwise : .clockwise
149 +
    }
150 +
}
151 +
152 +
prefix func !(direction: LayerTree.Path.Direction) -> LayerTree.Path.Direction  {
153 +
    switch direction {
154 +
    case .clockwise:
155 +
        return .anticlockwise
156 +
    case .anticlockwise:
157 +
        return .clockwise
158 +
    }
159 +
}

@@ -0,0 +1,111 @@
Loading
1 +
//
2 +
//  PairedSequence.swift
3 +
//  SwiftDraw
4 +
//
5 +
//  Created by Simon Whitty on 6/8/22.
6 +
//  Copyright 2022 Simon Whitty
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 +
extension Sequence {
33 +
34 +
    // Iterate a sequence by including the next element each time.
35 +
    // A---B---C---D
36 +
    //
37 +
    // nextSkippingLast: (A,B)--(B,C)--(C,D)
38 +
    // nextWrappingToFirst: (A,B)--(B,C)--(C,D)--(D,A)
39 +
    func paired(with options: PairedSequence<Self>.Options = .nextWrappingToFirst) -> PairedSequence<Self> {
40 +
        PairedSequence(self, options: options)
41 +
    }
42 +
}
43 +
44 +
struct PairedSequence<S: Sequence>: Sequence {
45 +
    typealias Element = (S.Element, next: S.Element)
46 +
47 +
    enum Options {
48 +
        case nextSkippingLast
49 +
        case nextWrappingToFirst
50 +
    }
51 +
52 +
    init(_ inner: S, options: Options) {
53 +
        self.inner = inner
54 +
        self.options = options
55 +
    }
56 +
57 +
    private let inner: S
58 +
    private let options: Options
59 +
60 +
    func makeIterator() -> Iterator {
61 +
        return Iterator(inner.makeIterator(), options: options)
62 +
    }
63 +
64 +
    struct Iterator: IteratorProtocol {
65 +
        private var inner: S.Iterator
66 +
        private let options: Options
67 +
68 +
        init(_ inner: S.Iterator, options: Options) {
69 +
            self.inner = inner
70 +
            self.options = options
71 +
        }
72 +
73 +
        mutating func next() -> (S.Element, next: S.Element)? {
74 +
            guard !isComplete else { return  nil }
75 +
76 +
            guard let element = inner.next() else {
77 +
                isComplete = true
78 +
                return makeWrappedIfRequired()
79 +
            }
80 +
81 +
            if let previous = previous {
82 +
                self.previous = element
83 +
                return (previous, element)
84 +
            } else {
85 +
                first = element
86 +
                if let another = inner.next() {
87 +
                    self.previous = another
88 +
                    return (element, another)
89 +
                } else {
90 +
                    isComplete = true
91 +
                    return nil
92 +
                }
93 +
            }
94 +
        }
95 +
96 +
        private mutating func makeWrappedIfRequired() -> (S.Element, next: S.Element)? {
97 +
            guard options == .nextWrappingToFirst,
98 +
               let first = first,
99 +
               let previous = previous else {
100 +
                return nil
101 +
            }
102 +
            self.first = nil
103 +
            self.previous = nil
104 +
            return (previous, first)
105 +
        }
106 +
107 +
        private var isComplete: Bool = false
108 +
        private var first: S.Element?
109 +
        private var previous: S.Element?
110 +
    }
111 +
}
Files Coverage
SwiftDraw 76.30%
Project Totals (60 files) 76.30%
1
ignore:
2
  - "SwiftDrawTests"
3
  - "CommandLine"
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading