-
Notifications
You must be signed in to change notification settings - Fork 0
/
path.go
226 lines (189 loc) · 6.16 KB
/
path.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package geom
// PathElementType is an enumeration representing the different kinds of path
// elements supported by 2D paths.
type PathElementType int
const (
// MoveTo is used for path elements that move the path position to a new
// location.
MoveTo PathElementType = iota
// LineTo is used for path elements that move the path position to a new
// location while drawin a straight line.
LineTo
// QuadCurveTo is used for path elements that draw a quadriatic cruve.
QuadCurveTo
// CubicCurveTo is used for path elements that draw a cubic curve.
CubicCurveTo
// ClosePath is used for path elements that terminate drawing of a sub-path.
ClosePath
)
// A PathElement represents a single step in a 2D path, it's composed of a type
// that must be one of the constant values of PathElementType, and a list of 2D
// points that are interpreted differently based on the element's type.
type PathElement struct {
// The type of the path element, see PathElementType for details on what
// values this field can take.
Type PathElementType
// The array of points that are arguments to the path operation represented
// by this element. Based on the elemen type, one or more of the points are
// actually meaningful to set.
Points [3]Point
}
// A Path is a drawable representation of an arbitrary shape, it's copmosed of a
// list of elements describing each step of the whole drawing operation.
type Path struct {
// The list of elements in the path. A program shouldn't have to manipulate
// this field and should be considered read-only.
Elements []PathElement
}
// The Shape interface represents values that can be converted to 2D paths.
type Shape interface {
// Returns a 2D path representing the abstract shape that satisifed the
// interface.
//
// The returned path shouldn't be retained by the shape or any other part
// of the program, the caller of that method is considered the owner of the
// returned value and is free to modify it.
Path() Path
}
// MakePath returns a Path value initialized with a slice of elements with the
// capacity given as argument.
func MakePath(cap int) Path {
return Path{
Elements: make([]PathElement, 0, cap),
}
}
// AppendPath concatenates the Path given as second arugment to the one given as
// first argument. It behaves in a similar way to the builtin `append` function,
// the Path value given as first argument may or may not be modified by the append
// operation.
func AppendPath(path Path, other Path) Path {
return Path{Elements: append(path.Elements, other.Elements...)}
}
// AppendRect efficiently append a rectangle to a Path and returns the modified
// value.
//
// Calling this function is equivalent to calling:
//
// path = AppendPath(path, rect.Path())
//
// but the implementation is optimized to avoid unnecessary memory allocations.
func AppendRect(path Path, rect Rect) Path {
x0 := rect.X
y0 := rect.Y
x1 := rect.X + rect.W
y1 := rect.Y + rect.H
return AppendPolygon(path, Point{x0, y0}, Point{x1, y0}, Point{x1, y1}, Point{x0, y1})
}
// AppendPolygon appends an arbitrary polygon to a path and returns the modified
// value, the polygon is made of the points given as arguments to the function.
func AppendPolygon(path Path, points ...Point) Path {
if len(points) != 0 {
path.MoveTo(points[0])
for _, p := range points[1:] {
path.LineTo(p)
}
path.Close()
}
return path
}
// MoveTo appends a path element that moves the current path position to the
// point given as argument.
func (p *Path) MoveTo(pt Point) {
p.append(PathElement{
Type: MoveTo,
Points: [...]Point{pt, {}, {}},
})
}
// LineTo appends a path element that draws a line from the current path
// position to the point given as argument.
func (p *Path) LineTo(pt Point) {
p.ensureStartWithMoveTo()
p.append(PathElement{
Type: LineTo,
Points: [...]Point{pt, {}, {}},
})
}
// QuadCurveTo appends a path element that draws a quadriatic curve from the
// current path position to `pt` and centered in `c`.
func (p *Path) QuadCurveTo(c Point, pt Point) {
p.ensureStartWithMoveTo()
p.append(PathElement{
Type: QuadCurveTo,
Points: [...]Point{c, pt, {}},
})
}
// CubicCurveTo appends a path element that draws a cubic curve from the current
// path position to `pt` with centers in `c1` and `c2`.
func (p *Path) CubicCurveTo(c1 Point, c2 Point, pt Point) {
p.ensureStartWithMoveTo()
p.append(PathElement{
Type: CubicCurveTo,
Points: [...]Point{c1, c2, pt},
})
}
// Close appends a path element that closes the current shape by drawing a line
// between the current path position and the last move-to element added to the
// path.
func (p *Path) Close() {
p.append(PathElement{
Type: ClosePath,
})
}
// Clear erases every element in the path.
func (p *Path) Clear() {
p.Elements = p.Elements[:0]
}
// Copy creates a copy of the path and returns it, the returned value and the
// receiver do not share the same slice of elements and can be safely modified
// independently.
func (p *Path) Copy() Path {
if p.Empty() {
return Path{}
}
p1 := Path{
Elements: make([]PathElement, len(p.Elements)),
}
copy(p1.Elements, p.Elements)
return p1
}
// LastPoint returns the 2D coordinates of the current path position.
func (p *Path) LastPoint() Point {
return p.lastPointAt(len(p.Elements) - 1)
}
func (p *Path) lastPointAt(n int) Point {
if n < 0 {
return Point{}
}
switch e := p.Elements[n]; e.Type {
case MoveTo, LineTo:
return e.Points[0]
case QuadCurveTo:
return e.Points[1]
case CubicCurveTo:
return e.Points[2]
default:
return p.lastPointAt(n - 1)
}
}
// Empty checks if the path is empty, which means it has no element.
func (p *Path) Empty() bool {
return len(p.Elements) == 0
}
// Path satisfies the Shape interface by returning a copy of the path it is
// called on.
func (p Path) Path() Path {
return p.Copy()
}
func (p *Path) append(e PathElement) {
p.Elements = append(p.Elements, e)
}
func (p *Path) ensureStartWithMoveTo() {
if n := len(p.Elements); n == 0 {
// Empty paths must start with a MoveTo element.
p.MoveTo(Point{})
} else if p.Elements[n-1].Type == ClosePath {
// When a subpath is closed the path that contains more elements must
// be restarted with a MoveTo as well.
p.MoveTo(p.LastPoint())
}
}