package drawing import ( "fmt" "math" ) // PathBuilder describes the interface for path drawing. type PathBuilder interface { // LastPoint returns the current point of the current sub path LastPoint() (x, y float64) // MoveTo creates a new subpath that start at the specified point MoveTo(x, y float64) // LineTo adds a line to the current subpath LineTo(x, y float64) // QuadCurveTo adds a quadratic Bézier curve to the current subpath QuadCurveTo(cx, cy, x, y float64) // CubicCurveTo adds a cubic Bézier curve to the current subpath CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) // ArcTo adds an arc to the current subpath ArcTo(cx, cy, rx, ry, startAngle, angle float64) // 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. Close() } // PathComponent represents component of a path type PathComponent int const ( // MoveToComponent is a MoveTo component in a Path MoveToComponent PathComponent = iota // LineToComponent is a LineTo component in a Path LineToComponent // QuadCurveToComponent is a QuadCurveTo component in a Path QuadCurveToComponent // CubicCurveToComponent is a CubicCurveTo component in a Path CubicCurveToComponent // ArcToComponent is a ArcTo component in a Path ArcToComponent // CloseComponent is a ArcTo component in a Path CloseComponent ) // Path stores points type Path struct { // Components is a slice of PathComponent in a Path and mark the role of each points in the Path Components []PathComponent // Points are combined with Components to have a specific role in the path Points []float64 // Last Point of the Path x, y float64 } func (p *Path) appendToPath(cmd PathComponent, points ...float64) { p.Components = append(p.Components, cmd) p.Points = append(p.Points, points...) } // LastPoint returns the current point of the current path func (p *Path) LastPoint() (x, y float64) { return p.x, p.y } // MoveTo starts a new path at (x, y) position func (p *Path) MoveTo(x, y float64) { p.appendToPath(MoveToComponent, x, y) p.x = x p.y = y } // LineTo adds a line to the current path func (p *Path) LineTo(x, y float64) { if len(p.Components) == 0 { //special case when no move has been done p.MoveTo(0, 0) } p.appendToPath(LineToComponent, x, y) p.x = x p.y = y } // QuadCurveTo adds a quadratic bezier curve to the current path func (p *Path) QuadCurveTo(cx, cy, x, y float64) { if len(p.Components) == 0 { //special case when no move has been done p.MoveTo(0, 0) } p.appendToPath(QuadCurveToComponent, cx, cy, x, y) p.x = x p.y = y } // CubicCurveTo adds a cubic bezier curve to the current path func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { if len(p.Components) == 0 { //special case when no move has been done p.MoveTo(0, 0) } p.appendToPath(CubicCurveToComponent, cx1, cy1, cx2, cy2, x, y) p.x = x p.y = y } // ArcTo adds an arc to the path func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, delta float64) { endAngle := startAngle + delta clockWise := true if delta < 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(ArcToComponent, cx, cy, rx, ry, startAngle, delta) p.x = cx + math.Cos(endAngle)*rx p.y = cy + math.Sin(endAngle)*ry } // Close closes the current path func (p *Path) Close() { p.appendToPath(CloseComponent) } // Copy make a clone of the current path and return it func (p *Path) Copy() (dest *Path) { dest = new(Path) dest.Components = make([]PathComponent, 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 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 func (p *Path) String() string { s := "" j := 0 for _, cmd := range p.Components { switch cmd { case MoveToComponent: s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1]) j = j + 2 case LineToComponent: s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1]) j = j + 2 case QuadCurveToComponent: 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 CubicCurveToComponent: 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 ArcToComponent: 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 CloseComponent: s += "Close\n" } } return s }