2015-04-27 10:14:34 +00:00
|
|
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
|
|
|
// created: 21/11/2010 by Laurent Le Goff
|
|
|
|
|
2015-04-29 12:33:32 +00:00
|
|
|
package draw2d
|
2015-04-27 10:14:34 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
)
|
|
|
|
|
2015-07-09 16:06:14 +00:00
|
|
|
// PathBuilder describes the interface for path drawing.
|
2015-04-27 10:14:34 +00:00
|
|
|
type PathBuilder interface {
|
2015-07-09 16:06:14 +00:00
|
|
|
// LastPoint returns the current point of the current sub path
|
2015-04-27 10:14:34 +00:00
|
|
|
LastPoint() (x, y float64)
|
2015-07-08 08:53:35 +00:00
|
|
|
// MoveTo creates a new subpath that start at the specified point
|
2015-04-27 10:14:34 +00:00
|
|
|
MoveTo(x, y float64)
|
2015-07-08 08:53:35 +00:00
|
|
|
// LineTo adds a line to the current subpath
|
2015-04-27 10:14:34 +00:00
|
|
|
LineTo(x, y float64)
|
2015-07-08 08:53:35 +00:00
|
|
|
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
|
2015-04-27 10:14:34 +00:00
|
|
|
QuadCurveTo(cx, cy, x, y float64)
|
2015-07-08 08:53:35 +00:00
|
|
|
// CubicCurveTo adds a cubic Bézier curve to the current subpath
|
2015-04-27 10:14:34 +00:00
|
|
|
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
2015-07-08 08:53:35 +00:00
|
|
|
// ArcTo adds an arc to the current subpath
|
2015-04-27 10:14:34 +00:00
|
|
|
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
2015-07-08 08:53:35 +00:00
|
|
|
// Close creates a line from the current point to the last MoveTo
|
|
|
|
// point (if not the same) and mark the path as closed so the
|
|
|
|
// first and last lines join nicely.
|
2015-04-27 10:14:34 +00:00
|
|
|
Close()
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:16:15 +00:00
|
|
|
// PathCmp represents component of a path
|
2015-04-29 14:53:36 +00:00
|
|
|
type PathCmp int
|
2015-04-27 10:14:34 +00:00
|
|
|
|
|
|
|
const (
|
2015-07-09 16:06:14 +00:00
|
|
|
// MoveToCmp is a MoveTo component in a Path
|
2015-04-29 14:53:36 +00:00
|
|
|
MoveToCmp PathCmp = iota
|
2015-07-09 16:06:14 +00:00
|
|
|
// LineToCmp is a LineTo component in a Path
|
2015-04-27 10:14:34 +00:00
|
|
|
LineToCmp
|
2015-07-09 16:06:14 +00:00
|
|
|
// QuadCurveToCmp is a QuadCurveTo component in a Path
|
2015-04-27 10:14:34 +00:00
|
|
|
QuadCurveToCmp
|
2015-07-09 16:06:14 +00:00
|
|
|
// CubicCurveToCmp is a CubicCurveTo component in a Path
|
2015-04-27 10:14:34 +00:00
|
|
|
CubicCurveToCmp
|
2015-07-09 16:06:14 +00:00
|
|
|
// ArcToCmp is a ArcTo component in a Path
|
2015-04-27 10:14:34 +00:00
|
|
|
ArcToCmp
|
2015-07-09 16:06:14 +00:00
|
|
|
// CloseCmp is a ArcTo component in a Path
|
2015-04-27 10:14:34 +00:00
|
|
|
CloseCmp
|
|
|
|
)
|
|
|
|
|
2015-04-29 14:53:36 +00:00
|
|
|
// Path stores points
|
2015-04-27 10:14:34 +00:00
|
|
|
type Path struct {
|
2015-07-09 16:06:14 +00:00
|
|
|
// Components is a slice of PathCmp in a Path and mark the role of each points in the Path
|
2015-04-29 14:53:36 +00:00
|
|
|
Components []PathCmp
|
2015-07-09 16:06:14 +00:00
|
|
|
// Points are combined with Components to have a specific role in the path
|
|
|
|
Points []float64
|
|
|
|
// Last Point of the Path
|
|
|
|
x, y float64
|
2015-04-27 10:14:34 +00:00
|
|
|
}
|
|
|
|
|
2015-04-29 14:53:36 +00:00
|
|
|
func (p *Path) appendToPath(cmd PathCmp, points ...float64) {
|
2015-04-27 10:14:34 +00:00
|
|
|
p.Components = append(p.Components, cmd)
|
|
|
|
p.Points = append(p.Points, points...)
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// LastPoint returns the current point of the current path
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) LastPoint() (x, y float64) {
|
|
|
|
return p.x, p.y
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// MoveTo starts a new path at (x, y) position
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) MoveTo(x, y float64) {
|
|
|
|
p.appendToPath(MoveToCmp, x, y)
|
|
|
|
p.x = x
|
|
|
|
p.y = y
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// LineTo adds a line to the current path
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) LineTo(x, y float64) {
|
|
|
|
if len(p.Components) == 0 { //special case when no move has been done
|
2017-04-25 05:59:14 +00:00
|
|
|
p.MoveTo(x, y)
|
|
|
|
} else {
|
|
|
|
p.appendToPath(LineToCmp, x, y)
|
2015-04-27 10:14:34 +00:00
|
|
|
}
|
|
|
|
p.x = x
|
|
|
|
p.y = y
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// QuadCurveTo adds a quadratic bezier curve to the current path
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
|
|
|
|
if len(p.Components) == 0 { //special case when no move has been done
|
2017-04-25 05:59:14 +00:00
|
|
|
p.MoveTo(x, y)
|
|
|
|
} else {
|
|
|
|
p.appendToPath(QuadCurveToCmp, cx, cy, x, y)
|
2015-04-27 10:14:34 +00:00
|
|
|
}
|
|
|
|
p.x = x
|
|
|
|
p.y = y
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// CubicCurveTo adds a cubic bezier curve to the current path
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
|
|
|
if len(p.Components) == 0 { //special case when no move has been done
|
2017-04-25 05:59:14 +00:00
|
|
|
p.MoveTo(x, y)
|
|
|
|
} else {
|
|
|
|
p.appendToPath(CubicCurveToCmp, cx1, cy1, cx2, cy2, x, y)
|
2015-04-27 10:14:34 +00:00
|
|
|
}
|
|
|
|
p.x = x
|
|
|
|
p.y = y
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// ArcTo adds an arc to the path
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
|
|
|
|
endAngle := startAngle + angle
|
|
|
|
clockWise := true
|
|
|
|
if angle < 0 {
|
|
|
|
clockWise = false
|
|
|
|
}
|
|
|
|
// normalize
|
|
|
|
if clockWise {
|
|
|
|
for endAngle < startAngle {
|
|
|
|
endAngle += math.Pi * 2.0
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for startAngle < endAngle {
|
|
|
|
startAngle += math.Pi * 2.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
startX := cx + math.Cos(startAngle)*rx
|
|
|
|
startY := cy + math.Sin(startAngle)*ry
|
|
|
|
if len(p.Components) > 0 {
|
|
|
|
p.LineTo(startX, startY)
|
|
|
|
} else {
|
|
|
|
p.MoveTo(startX, startY)
|
|
|
|
}
|
|
|
|
p.appendToPath(ArcToCmp, cx, cy, rx, ry, startAngle, angle)
|
|
|
|
p.x = cx + math.Cos(endAngle)*rx
|
|
|
|
p.y = cy + math.Sin(endAngle)*ry
|
|
|
|
}
|
|
|
|
|
2015-04-29 15:59:39 +00:00
|
|
|
// Close closes the current path
|
|
|
|
func (p *Path) Close() {
|
|
|
|
p.appendToPath(CloseCmp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy make a clone of the current path and return it
|
2015-07-09 16:06:14 +00:00
|
|
|
func (p *Path) Copy() (dest *Path) {
|
2015-04-29 15:59:39 +00:00
|
|
|
dest = new(Path)
|
2015-07-09 16:06:14 +00:00
|
|
|
dest.Components = make([]PathCmp, len(p.Components))
|
|
|
|
copy(dest.Components, p.Components)
|
|
|
|
dest.Points = make([]float64, len(p.Points))
|
|
|
|
copy(dest.Points, p.Points)
|
|
|
|
dest.x, dest.y = p.x, p.y
|
2015-04-29 15:59:39 +00:00
|
|
|
return dest
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear reset the path
|
|
|
|
func (p *Path) Clear() {
|
|
|
|
p.Components = p.Components[0:0]
|
|
|
|
p.Points = p.Points[0:0]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsEmpty returns true if the path is empty
|
|
|
|
func (p *Path) IsEmpty() bool {
|
|
|
|
return len(p.Components) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a debug text view of the path
|
2015-04-27 10:14:34 +00:00
|
|
|
func (p *Path) String() string {
|
|
|
|
s := ""
|
|
|
|
j := 0
|
|
|
|
for _, cmd := range p.Components {
|
|
|
|
switch cmd {
|
|
|
|
case MoveToCmp:
|
|
|
|
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1])
|
|
|
|
j = j + 2
|
|
|
|
case LineToCmp:
|
|
|
|
s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1])
|
|
|
|
j = j + 2
|
|
|
|
case QuadCurveToCmp:
|
|
|
|
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3])
|
|
|
|
j = j + 4
|
|
|
|
case CubicCurveToCmp:
|
|
|
|
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
|
|
|
|
j = j + 6
|
|
|
|
case ArcToCmp:
|
|
|
|
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
|
|
|
|
j = j + 6
|
|
|
|
case CloseCmp:
|
|
|
|
s += "Close\n"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
2018-01-07 16:57:38 +00:00
|
|
|
|
|
|
|
// Returns new Path with flipped y axes
|
|
|
|
func (path *Path) VerticalFlip() *Path {
|
|
|
|
p := path.Copy()
|
|
|
|
j := 0
|
|
|
|
for _, cmd := range p.Components {
|
|
|
|
switch cmd {
|
|
|
|
case MoveToCmp, LineToCmp:
|
|
|
|
p.Points[j+1] = -p.Points[j+1]
|
|
|
|
j = j + 2
|
|
|
|
case QuadCurveToCmp:
|
|
|
|
p.Points[j+1] = -p.Points[j+1]
|
|
|
|
p.Points[j+3] = -p.Points[j+3]
|
|
|
|
j = j + 4
|
|
|
|
case CubicCurveToCmp:
|
|
|
|
p.Points[j+1] = -p.Points[j+1]
|
|
|
|
p.Points[j+3] = -p.Points[j+3]
|
|
|
|
p.Points[j+5] = -p.Points[j+5]
|
|
|
|
j = j + 6
|
|
|
|
case ArcToCmp:
|
|
|
|
p.Points[j+1] = -p.Points[j+1]
|
|
|
|
p.Points[j+3] = -p.Points[j+3]
|
|
|
|
p.Points[j+4] = -p.Points[j+4] // start angle
|
|
|
|
p.Points[j+5] = -p.Points[j+5] // angle
|
|
|
|
j = j + 6
|
|
|
|
case CloseCmp:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.y = -p.y
|
|
|
|
return p
|
|
|
|
}
|