dash.go

  1// Copyright 2017 by the rasterx Authors. All rights reserved.
  2//_
  3// created: 2017 by S.R.Wiley
  4
  5package rasterx
  6
  7import (
  8	"golang.org/x/image/math/fixed"
  9)
 10
 11// Dasher struct extends the Stroker and can draw
 12// dashed lines with end capping
 13type Dasher struct {
 14	Stroker
 15	Dashes                    []fixed.Int26_6
 16	dashPlace                 int
 17	firstDashIsGap, dashIsGap bool
 18	deltaDash, DashOffset     fixed.Int26_6
 19	sgm                       Rasterx
 20	// sgm allows us to switch between dashing
 21	// and non-dashing rasterizers in the SetStroke function.
 22}
 23
 24// joinF overides stroker joinF during dashed stroking, because we need to slightly modify
 25// the the call as below to handle the case of the join being in a dash gap.
 26func (r *Dasher) joinF() {
 27	if len(r.Dashes) == 0 || !r.inStroke || !r.dashIsGap {
 28		r.Stroker.joinF()
 29	}
 30}
 31
 32// Start starts a dashed line
 33func (r *Dasher) Start(a fixed.Point26_6) {
 34	// Advance dashPlace to the dashOffset start point and set deltaDash
 35	if len(r.Dashes) > 0 {
 36		r.deltaDash = r.DashOffset
 37		r.dashIsGap = false
 38		r.dashPlace = 0
 39		for r.deltaDash > r.Dashes[r.dashPlace] {
 40			r.deltaDash -= r.Dashes[r.dashPlace]
 41			r.dashIsGap = !r.dashIsGap
 42			r.dashPlace++
 43			if r.dashPlace == len(r.Dashes) {
 44				r.dashPlace = 0
 45			}
 46		}
 47		r.firstDashIsGap = r.dashIsGap
 48	}
 49	r.Stroker.Start(a)
 50}
 51
 52// lineF overides stroker lineF to modify the the call as below
 53// while performing the join in a dashed stroke.
 54func (r *Dasher) lineF(b fixed.Point26_6) {
 55	var bnorm fixed.Point26_6
 56	a := r.a // Copy local a since r.a is going to change during stroke operation
 57	ba := b.Sub(a)
 58	segLen := Length(ba)
 59	var nlt fixed.Int26_6
 60	if b == r.leadPoint.P { // End of segment
 61		bnorm = r.leadPoint.TNorm // Use more accurate leadPoint tangent
 62	} else {
 63		bnorm = turnPort90(ToLength(b.Sub(a), r.u)) // Intra segment normal
 64	}
 65	for segLen+r.deltaDash > r.Dashes[r.dashPlace] {
 66		nl := r.Dashes[r.dashPlace] - r.deltaDash
 67		nlt += nl
 68		r.dashLineStrokeBit(a.Add(ToLength(ba, nlt)), bnorm, false)
 69		r.dashIsGap = !r.dashIsGap
 70		segLen -= nl
 71		r.deltaDash = 0
 72		r.dashPlace++
 73		if r.dashPlace == len(r.Dashes) {
 74			r.dashPlace = 0
 75		}
 76	}
 77	r.deltaDash += segLen
 78	r.dashLineStrokeBit(b, bnorm, true)
 79}
 80
 81// SetStroke set the parameters for stroking a line. width is the width of the line, miterlimit is the miter cutoff
 82// value for miter, arc, miterclip and arcClip joinModes. CapL and CapT are the capping functions for leading and trailing
 83// line ends. If one is nil, the other function is used at both ends. gp is the gap function that determines how a
 84// gap on the convex side of two lines joining is filled. jm is the JoinMode for curve segments. Dashes is the values for
 85// the dash pattern. Pass in nil or an empty slice for no dashes. dashoffset is the starting offset into the dash array.
 86func (r *Dasher) SetStroke(width, miterLimit fixed.Int26_6, capL, capT CapFunc, gp GapFunc, jm JoinMode, dashes []float64, dashOffset float64) {
 87	r.Stroker.SetStroke(width, miterLimit, capL, capT, gp, jm)
 88
 89	r.Dashes = r.Dashes[:0] // clear the dash array
 90	if len(dashes) == 0 {
 91		r.sgm = &r.Stroker // This is just plain stroking
 92		return
 93	}
 94	// Dashed Stroke
 95	// Convert the float dash array and offset to fixed point and attach to the Filler
 96	oneIsPos := false // Check to see if at least one dash is > 0
 97	for _, v := range dashes {
 98		fv := fixed.Int26_6(v * 64)
 99		if fv <= 0 { // Negatives are considered 0s.
100			fv = 0
101		} else {
102			oneIsPos = true
103		}
104		r.Dashes = append(r.Dashes, fv)
105	}
106	if oneIsPos == false {
107		r.Dashes = r.Dashes[:0]
108		r.sgm = &r.Stroker // This is just plain stroking
109		return
110	}
111	r.DashOffset = fixed.Int26_6(dashOffset * 64)
112	r.sgm = r // Use the full dasher
113}
114
115//Stop terminates a dashed line
116func (r *Dasher) Stop(isClosed bool) {
117	if len(r.Dashes) == 0 {
118		r.Stroker.Stop(isClosed)
119		return
120	}
121	if r.inStroke == false {
122		return
123	}
124	if isClosed && r.a != r.firstP.P {
125		r.LineSeg(r.sgm, r.firstP.P)
126	}
127	ra := &r.Filler
128	if isClosed && !r.firstDashIsGap && !r.dashIsGap { // closed connect w/o caps
129		a := r.a
130		r.firstP.TNorm = r.leadPoint.TNorm
131		r.firstP.RT = r.leadPoint.RT
132		r.firstP.TTan = r.leadPoint.TTan
133		ra.Start(r.firstP.P.Sub(r.firstP.TNorm))
134		ra.Line(a.Sub(r.ln))
135		ra.Start(a.Add(r.ln))
136		ra.Line(r.firstP.P.Add(r.firstP.TNorm))
137		r.Joiner(r.firstP)
138		r.firstP.blackWidowMark(ra)
139	} else { // Cap open ends
140		if !r.dashIsGap {
141			r.CapL(ra, r.leadPoint.P, r.leadPoint.TNorm)
142		}
143		if !r.firstDashIsGap {
144			r.CapT(ra, r.firstP.P, Invert(r.firstP.LNorm))
145		}
146	}
147	r.inStroke = false
148}
149
150// dashLineStrokeBit is a helper function that reduces code redundancey in the
151// lineF function.
152func (r *Dasher) dashLineStrokeBit(b, bnorm fixed.Point26_6, dontClose bool) {
153	if !r.dashIsGap { // Moving from dash to gap
154		a := r.a
155		ra := &r.Filler
156		ra.Start(b.Sub(bnorm))
157		ra.Line(a.Sub(r.ln))
158		ra.Start(a.Add(r.ln))
159		ra.Line(b.Add(bnorm))
160		if dontClose == false {
161			r.CapL(ra, b, bnorm)
162		}
163	} else { // Moving from gap to dash
164		if dontClose == false {
165			ra := &r.Filler
166			r.CapT(ra, b, Invert(bnorm))
167		}
168	}
169	r.a = b
170	r.ln = bnorm
171}
172
173// Line for Dasher is here to pass the dasher sgm to LineP
174func (r *Dasher) Line(b fixed.Point26_6) {
175	r.LineSeg(r.sgm, b)
176}
177
178// QuadBezier for dashing
179func (r *Dasher) QuadBezier(b, c fixed.Point26_6) {
180	r.quadBezierf(r.sgm, b, c)
181}
182
183// CubeBezier starts a stroked cubic bezier.
184// It is a low level function exposed for the purposes of callbacks
185// and debugging.
186func (r *Dasher) CubeBezier(b, c, d fixed.Point26_6) {
187	r.cubeBezierf(r.sgm, b, c, d)
188}
189
190// NewDasher returns a Dasher ptr with default values.
191// A Dasher has all of the capabilities of a Stroker, Filler, and Scanner, plus the ability
192// to stroke curves with solid lines. Use SetStroke to configure with non-default
193// values.
194func NewDasher(width, height int, scanner Scanner) *Dasher {
195	r := new(Dasher)
196	r.Scanner = scanner
197	r.SetBounds(width, height)
198	r.SetWinding(true)
199	r.SetStroke(1*64, 4*64, ButtCap, nil, FlatGap, MiterClip, nil, 0)
200	r.sgm = &r.Stroker
201	return r
202}