Repository navigation
Expand file tree
/
Copy pathpath.go
More file actions
169 lines (149 loc) · 5.1 KB
/
Copy pathpath.go
File metadata and controls
169 lines (149 loc) · 5.1 KB
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
package stilus
import "math"
// Verb is a path construction command.
type Verb uint8
const (
MoveTo Verb = iota // 1 point
LineTo // 1 point
QuadTo // 2 points
CubicTo // 3 points
Close // 0 points
)
// numPoints is the number of points consumed by each verb.
var numPoints = [...]int{MoveTo: 1, LineTo: 1, QuadTo: 2, CubicTo: 3, Close: 0}
// Path is a sequence of subpaths. The zero value is an empty path ready to
// use. Reset keeps the backing storage, so a Path can be rebuilt many times
// without allocating.
type Path struct {
Verbs []Verb
Points []Point
}
// Reset empties the path, keeping its storage.
func (p *Path) Reset() {
p.Verbs = p.Verbs[:0]
p.Points = p.Points[:0]
}
// Empty reports whether the path has no drawing commands.
func (p *Path) Empty() bool { return len(p.Verbs) == 0 }
// MoveTo starts a new subpath at (x, y).
func (p *Path) MoveTo(x, y float32) {
p.Verbs = append(p.Verbs, MoveTo)
p.Points = append(p.Points, Point{x, y})
}
// LineTo adds a line segment to (x, y).
func (p *Path) LineTo(x, y float32) {
p.Verbs = append(p.Verbs, LineTo)
p.Points = append(p.Points, Point{x, y})
}
// QuadTo adds a quadratic Bézier segment.
func (p *Path) QuadTo(cx, cy, x, y float32) {
p.Verbs = append(p.Verbs, QuadTo)
p.Points = append(p.Points, Point{cx, cy}, Point{x, y})
}
// CubicTo adds a cubic Bézier segment.
func (p *Path) CubicTo(c1x, c1y, c2x, c2y, x, y float32) {
p.Verbs = append(p.Verbs, CubicTo)
p.Points = append(p.Points, Point{c1x, c1y}, Point{c2x, c2y}, Point{x, y})
}
// Close closes the current subpath.
func (p *Path) Close() { p.Verbs = append(p.Verbs, Close) }
// Rect appends a closed rectangle subpath (PDF "re").
func (p *Path) Rect(x, y, w, h float32) {
p.MoveTo(x, y)
p.LineTo(x+w, y)
p.LineTo(x+w, y+h)
p.LineTo(x, y+h)
p.Close()
}
// kappa is the cubic control-point distance for a quarter circle.
const kappa = 0.5522847498307936
// Ellipse appends a closed ellipse centred at (cx, cy).
func (p *Path) Ellipse(cx, cy, rx, ry float32) {
kx, ky := rx*kappa, ry*kappa
p.MoveTo(cx+rx, cy)
p.CubicTo(cx+rx, cy+ky, cx+kx, cy+ry, cx, cy+ry)
p.CubicTo(cx-kx, cy+ry, cx-rx, cy+ky, cx-rx, cy)
p.CubicTo(cx-rx, cy-ky, cx-kx, cy-ry, cx, cy-ry)
p.CubicTo(cx+kx, cy-ry, cx+rx, cy-ky, cx+rx, cy)
p.Close()
}
// Bounds returns the bounding box of all points (including control points).
func (p *Path) Bounds() Rect {
if len(p.Points) == 0 {
return Rect{}
}
// Coordinates are compared as integer keys of their float32 bits, which
// order like the values (-0 below +0, NaNs beyond the infinities): an
// integer min or max is a compare and a conditional move, where the
// floating-point min and max, with their NaN and signed-zero rules,
// form a dependency chain several times as long.
x0, y0 := floatKey(math.Float32bits(float32(math.Inf(1)))), floatKey(math.Float32bits(float32(math.Inf(1))))
x1, y1 := floatKey(math.Float32bits(float32(math.Inf(-1)))), floatKey(math.Float32bits(float32(math.Inf(-1))))
for _, q := range p.Points {
kx, ky := floatKey(math.Float32bits(q.X)), floatKey(math.Float32bits(q.Y))
x0 = min(x0, kx)
y0 = min(y0, ky)
x1 = max(x1, kx)
y1 = max(y1, ky)
}
r := Rect{keyFloat(x0), keyFloat(y0), keyFloat(x1), keyFloat(y1)}
if r.hasNaN() {
// A NaN coordinate sorts beyond an infinity, so it is always in r.
return p.boundsNaN()
}
return r
}
// floatKey maps float32 bits to an int32 that orders like the value.
func floatKey(b uint32) int32 { return int32(b ^ uint32(int32(b)>>31)>>1) }
// keyFloat inverts floatKey.
func keyFloat(k int32) float64 { return float64(math.Float32frombits(uint32(k) ^ uint32(k>>31)>>1)) }
// boundsNaN preserves math.Min/Max's infinity precedence for paths with
// NaN coordinates, without adding special-case checks to the point loop.
func (p *Path) boundsNaN() Rect {
r := Rect{math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1)}
for _, q := range p.Points {
x, y := float64(q.X), float64(q.Y)
r.X0 = math.Min(r.X0, x)
r.Y0 = math.Min(r.Y0, y)
r.X1 = math.Max(r.X1, x)
r.Y1 = math.Max(r.Y1, y)
}
return r
}
// asRect reports whether p is a single axis-aligned rectangle (PDF "re"
// followed by optional close), returning it in path coordinates.
func (p *Path) asRect() (Rect, bool) {
v := p.Verbs
if len(v) < 4 || len(v) > 6 || v[0] != MoveTo || v[1] != LineTo || v[2] != LineTo || v[3] != LineTo {
return Rect{}, false
}
pts := p.Points
n := 4
switch {
case len(v) == 4:
case len(v) == 5 && v[4] == Close:
case len(v) == 5 && v[4] == LineTo:
n = 5
case len(v) == 6 && v[4] == LineTo && v[5] == Close:
n = 5
default:
return Rect{}, false
}
if len(pts) < n {
return Rect{}, false
}
if n == 5 && pts[4] != pts[0] {
return Rect{}, false
}
a, b, c, d := pts[0], pts[1], pts[2], pts[3]
horizFirst := a.Y == b.Y && b.X == c.X && c.Y == d.Y && d.X == a.X
vertFirst := a.X == b.X && b.Y == c.Y && c.X == d.X && d.Y == a.Y
if !horizFirst && !vertFirst {
return Rect{}, false
}
r := Rect{
min(float64(a.X), float64(c.X)), min(float64(a.Y), float64(c.Y)),
max(float64(a.X), float64(c.X)), max(float64(a.Y), float64(c.Y)),
}
return r, true
}