adam-gui/vendor/github.com/srwiley/rasterx/dash.go

203 lines
5.9 KiB
Go
Raw Normal View History

2024-04-29 19:13:50 +02:00
// Copyright 2017 by the rasterx Authors. All rights reserved.
//_
// created: 2017 by S.R.Wiley
package rasterx
import (
"golang.org/x/image/math/fixed"
)
// Dasher struct extends the Stroker and can draw
// dashed lines with end capping
type Dasher struct {
Stroker
Dashes []fixed.Int26_6
dashPlace int
firstDashIsGap, dashIsGap bool
deltaDash, DashOffset fixed.Int26_6
sgm Rasterx
// sgm allows us to switch between dashing
// and non-dashing rasterizers in the SetStroke function.
}
// joinF overides stroker joinF during dashed stroking, because we need to slightly modify
// the the call as below to handle the case of the join being in a dash gap.
func (r *Dasher) joinF() {
if len(r.Dashes) == 0 || !r.inStroke || !r.dashIsGap {
r.Stroker.joinF()
}
}
// Start starts a dashed line
func (r *Dasher) Start(a fixed.Point26_6) {
// Advance dashPlace to the dashOffset start point and set deltaDash
if len(r.Dashes) > 0 {
r.deltaDash = r.DashOffset
r.dashIsGap = false
r.dashPlace = 0
for r.deltaDash > r.Dashes[r.dashPlace] {
r.deltaDash -= r.Dashes[r.dashPlace]
r.dashIsGap = !r.dashIsGap
r.dashPlace++
if r.dashPlace == len(r.Dashes) {
r.dashPlace = 0
}
}
r.firstDashIsGap = r.dashIsGap
}
r.Stroker.Start(a)
}
// lineF overides stroker lineF to modify the the call as below
// while performing the join in a dashed stroke.
func (r *Dasher) lineF(b fixed.Point26_6) {
var bnorm fixed.Point26_6
a := r.a // Copy local a since r.a is going to change during stroke operation
ba := b.Sub(a)
segLen := Length(ba)
var nlt fixed.Int26_6
if b == r.leadPoint.P { // End of segment
bnorm = r.leadPoint.TNorm // Use more accurate leadPoint tangent
} else {
bnorm = turnPort90(ToLength(b.Sub(a), r.u)) // Intra segment normal
}
for segLen+r.deltaDash > r.Dashes[r.dashPlace] {
nl := r.Dashes[r.dashPlace] - r.deltaDash
nlt += nl
r.dashLineStrokeBit(a.Add(ToLength(ba, nlt)), bnorm, false)
r.dashIsGap = !r.dashIsGap
segLen -= nl
r.deltaDash = 0
r.dashPlace++
if r.dashPlace == len(r.Dashes) {
r.dashPlace = 0
}
}
r.deltaDash += segLen
r.dashLineStrokeBit(b, bnorm, true)
}
// SetStroke set the parameters for stroking a line. width is the width of the line, miterlimit is the miter cutoff
// value for miter, arc, miterclip and arcClip joinModes. CapL and CapT are the capping functions for leading and trailing
// line ends. If one is nil, the other function is used at both ends. gp is the gap function that determines how a
// gap on the convex side of two lines joining is filled. jm is the JoinMode for curve segments. Dashes is the values for
// the dash pattern. Pass in nil or an empty slice for no dashes. dashoffset is the starting offset into the dash array.
func (r *Dasher) SetStroke(width, miterLimit fixed.Int26_6, capL, capT CapFunc, gp GapFunc, jm JoinMode, dashes []float64, dashOffset float64) {
r.Stroker.SetStroke(width, miterLimit, capL, capT, gp, jm)
r.Dashes = r.Dashes[:0] // clear the dash array
if len(dashes) == 0 {
r.sgm = &r.Stroker // This is just plain stroking
return
}
// Dashed Stroke
// Convert the float dash array and offset to fixed point and attach to the Filler
oneIsPos := false // Check to see if at least one dash is > 0
for _, v := range dashes {
fv := fixed.Int26_6(v * 64)
if fv <= 0 { // Negatives are considered 0s.
fv = 0
} else {
oneIsPos = true
}
r.Dashes = append(r.Dashes, fv)
}
if oneIsPos == false {
r.Dashes = r.Dashes[:0]
r.sgm = &r.Stroker // This is just plain stroking
return
}
r.DashOffset = fixed.Int26_6(dashOffset * 64)
r.sgm = r // Use the full dasher
}
//Stop terminates a dashed line
func (r *Dasher) Stop(isClosed bool) {
if len(r.Dashes) == 0 {
r.Stroker.Stop(isClosed)
return
}
if r.inStroke == false {
return
}
if isClosed && r.a != r.firstP.P {
r.LineSeg(r.sgm, r.firstP.P)
}
ra := &r.Filler
if isClosed && !r.firstDashIsGap && !r.dashIsGap { // closed connect w/o caps
a := r.a
r.firstP.TNorm = r.leadPoint.TNorm
r.firstP.RT = r.leadPoint.RT
r.firstP.TTan = r.leadPoint.TTan
ra.Start(r.firstP.P.Sub(r.firstP.TNorm))
ra.Line(a.Sub(r.ln))
ra.Start(a.Add(r.ln))
ra.Line(r.firstP.P.Add(r.firstP.TNorm))
r.Joiner(r.firstP)
r.firstP.blackWidowMark(ra)
} else { // Cap open ends
if !r.dashIsGap {
r.CapL(ra, r.leadPoint.P, r.leadPoint.TNorm)
}
if !r.firstDashIsGap {
r.CapT(ra, r.firstP.P, Invert(r.firstP.LNorm))
}
}
r.inStroke = false
}
// dashLineStrokeBit is a helper function that reduces code redundancey in the
// lineF function.
func (r *Dasher) dashLineStrokeBit(b, bnorm fixed.Point26_6, dontClose bool) {
if !r.dashIsGap { // Moving from dash to gap
a := r.a
ra := &r.Filler
ra.Start(b.Sub(bnorm))
ra.Line(a.Sub(r.ln))
ra.Start(a.Add(r.ln))
ra.Line(b.Add(bnorm))
if dontClose == false {
r.CapL(ra, b, bnorm)
}
} else { // Moving from gap to dash
if dontClose == false {
ra := &r.Filler
r.CapT(ra, b, Invert(bnorm))
}
}
r.a = b
r.ln = bnorm
}
// Line for Dasher is here to pass the dasher sgm to LineP
func (r *Dasher) Line(b fixed.Point26_6) {
r.LineSeg(r.sgm, b)
}
// QuadBezier for dashing
func (r *Dasher) QuadBezier(b, c fixed.Point26_6) {
r.quadBezierf(r.sgm, b, c)
}
// CubeBezier starts a stroked cubic bezier.
// It is a low level function exposed for the purposes of callbacks
// and debugging.
func (r *Dasher) CubeBezier(b, c, d fixed.Point26_6) {
r.cubeBezierf(r.sgm, b, c, d)
}
// NewDasher returns a Dasher ptr with default values.
// A Dasher has all of the capabilities of a Stroker, Filler, and Scanner, plus the ability
// to stroke curves with solid lines. Use SetStroke to configure with non-default
// values.
func NewDasher(width, height int, scanner Scanner) *Dasher {
r := new(Dasher)
r.Scanner = scanner
r.SetBounds(width, height)
r.SetWinding(true)
r.SetStroke(1*64, 4*64, ButtCap, nil, FlatGap, MiterClip, nil, 0)
r.sgm = &r.Stroker
return r
}