stroke.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. // Copyright 2017 by the rasterx Authors. All rights reserved.
  2. //
  3. // created: 2017 by S.R.Wiley
  4. package rasterx
  5. import (
  6. "math"
  7. "golang.org/x/image/math/fixed"
  8. )
  9. const (
  10. cubicsPerHalfCircle = 8 // Number of cubic beziers to approx half a circle
  11. epsilonFixed = fixed.Int26_6(16) // 1/4 in fixed point
  12. // fixed point t paramaterization shift factor;
  13. // (2^this)/64 is the max length of t for fixed.Int26_6
  14. tStrokeShift = 14
  15. )
  16. type (
  17. // JoinMode type to specify how segments join.
  18. JoinMode uint8
  19. // CapFunc defines a function that draws caps on the ends of lines
  20. CapFunc func(p Adder, a, eNorm fixed.Point26_6)
  21. // GapFunc defines a function to bridge gaps when the miter limit is
  22. // exceeded
  23. GapFunc func(p Adder, a, tNorm, lNorm fixed.Point26_6)
  24. // C2Point represents a point that connects two stroke segments
  25. // and holds the tangent, normal and radius of curvature
  26. // of the trailing and leading segments in fixed point values.
  27. C2Point struct {
  28. P, TTan, LTan, TNorm, LNorm fixed.Point26_6
  29. RT, RL fixed.Int26_6
  30. }
  31. // Stroker does everything a Filler does, but
  32. // also allows for stroking and dashed stroking in addition to
  33. // filling
  34. Stroker struct {
  35. Filler
  36. CapT, CapL CapFunc // Trailing and leading cap funcs may be set separately
  37. JoinGap GapFunc // When gap appears between segments, this function is called
  38. firstP, trailPoint, leadPoint C2Point // Tracks progress of the stroke
  39. ln fixed.Point26_6 // last normal of intra-seg connection.
  40. u, mLimit fixed.Int26_6 // u is the half-width of the stroke.
  41. JoinMode JoinMode
  42. inStroke bool
  43. }
  44. )
  45. // JoinMode constants determine how stroke segments bridge the gap at a join
  46. // ArcClip mode is like MiterClip applied to arcs, and is not part of the SVG2.0
  47. // standard.
  48. const (
  49. Arc JoinMode = iota
  50. ArcClip
  51. Miter
  52. MiterClip
  53. Bevel
  54. Round
  55. )
  56. // NewStroker returns a ptr to a Stroker with default values.
  57. // A Stroker has all of the capabilities of a Filler and Scanner, plus the ability
  58. // to stroke curves with solid lines. Use SetStroke to configure with non-default
  59. // values.
  60. func NewStroker(width, height int, scanner Scanner) *Stroker {
  61. r := new(Stroker)
  62. r.Scanner = scanner
  63. r.SetBounds(width, height)
  64. //Defaults for stroking
  65. r.SetWinding(true)
  66. r.u = 2 << 6
  67. r.mLimit = 4 << 6
  68. r.JoinMode = MiterClip
  69. r.JoinGap = RoundGap
  70. r.CapL = RoundCap
  71. r.CapT = RoundCap
  72. r.SetStroke(1<<6, 4<<6, ButtCap, nil, FlatGap, MiterClip)
  73. return r
  74. }
  75. // SetStroke set the parameters for stroking a line. width is the width of the line, miterlimit is the miter cutoff
  76. // value for miter, arc, miterclip and arcClip joinModes. CapL and CapT are the capping functions for leading and trailing
  77. // line ends. If one is nil, the other function is used at both ends. If both are nil, both ends are ButtCapped.
  78. // gp is the gap function that determines how a gap on the convex side of two joining lines is filled. jm is the JoinMode
  79. // for curve segments.
  80. func (r *Stroker) SetStroke(width, miterLimit fixed.Int26_6, capL, capT CapFunc, gp GapFunc, jm JoinMode) {
  81. r.u = width / 2
  82. r.CapL = capL
  83. r.CapT = capT
  84. r.JoinMode = jm
  85. r.JoinGap = gp
  86. r.mLimit = (r.u * miterLimit) >> 6
  87. if r.CapT == nil {
  88. if r.CapL == nil {
  89. r.CapT = ButtCap
  90. } else {
  91. r.CapT = r.CapL
  92. }
  93. }
  94. if r.CapL == nil {
  95. r.CapL = r.CapT
  96. }
  97. if gp == nil {
  98. if r.JoinMode == Round {
  99. r.JoinGap = RoundGap
  100. } else {
  101. r.JoinGap = FlatGap
  102. }
  103. }
  104. }
  105. // GapToCap is a utility that converts a CapFunc to GapFunc
  106. func GapToCap(p Adder, a, eNorm fixed.Point26_6, gf GapFunc) {
  107. p.Start(a.Add(eNorm))
  108. gf(p, a, eNorm, Invert(eNorm))
  109. p.Line(a.Sub(eNorm))
  110. }
  111. var (
  112. // ButtCap caps lines with a straight line
  113. ButtCap CapFunc = func(p Adder, a, eNorm fixed.Point26_6) {
  114. p.Start(a.Add(eNorm))
  115. p.Line(a.Sub(eNorm))
  116. }
  117. // SquareCap caps lines with a square which is slightly longer than ButtCap
  118. SquareCap CapFunc = func(p Adder, a, eNorm fixed.Point26_6) {
  119. tpt := a.Add(turnStarboard90(eNorm))
  120. p.Start(a.Add(eNorm))
  121. p.Line(tpt.Add(eNorm))
  122. p.Line(tpt.Sub(eNorm))
  123. p.Line(a.Sub(eNorm))
  124. }
  125. // RoundCap caps lines with a half-circle
  126. RoundCap CapFunc = func(p Adder, a, eNorm fixed.Point26_6) {
  127. GapToCap(p, a, eNorm, RoundGap)
  128. }
  129. // CubicCap caps lines with a cubic bezier
  130. CubicCap CapFunc = func(p Adder, a, eNorm fixed.Point26_6) {
  131. GapToCap(p, a, eNorm, CubicGap)
  132. }
  133. // QuadraticCap caps lines with a quadratic bezier
  134. QuadraticCap CapFunc = func(p Adder, a, eNorm fixed.Point26_6) {
  135. GapToCap(p, a, eNorm, QuadraticGap)
  136. }
  137. // Gap functions
  138. //FlatGap bridges miter-limit gaps with a straight line
  139. FlatGap GapFunc = func(p Adder, a, tNorm, lNorm fixed.Point26_6) {
  140. p.Line(a.Add(lNorm))
  141. }
  142. // RoundGap bridges miter-limit gaps with a circular arc
  143. RoundGap GapFunc = func(p Adder, a, tNorm, lNorm fixed.Point26_6) {
  144. strokeArc(p, a, a.Add(tNorm), a.Add(lNorm), true, 0, 0, p.Line)
  145. p.Line(a.Add(lNorm)) // just to be sure line joins cleanly,
  146. // last pt in stoke arc may not be precisely s2
  147. }
  148. // CubicGap bridges miter-limit gaps with a cubic bezier
  149. CubicGap GapFunc = func(p Adder, a, tNorm, lNorm fixed.Point26_6) {
  150. p.CubeBezier(a.Add(tNorm).Add(turnStarboard90(tNorm)), a.Add(lNorm).Add(turnPort90(lNorm)), a.Add(lNorm))
  151. }
  152. // QuadraticGap bridges miter-limit gaps with a quadratic bezier
  153. QuadraticGap GapFunc = func(p Adder, a, tNorm, lNorm fixed.Point26_6) {
  154. c1, c2 := a.Add(tNorm).Add(turnStarboard90(tNorm)), a.Add(lNorm).Add(turnPort90(lNorm))
  155. cm := c1.Add(c2).Mul(fixed.Int26_6(1 << 5))
  156. p.QuadBezier(cm, a.Add(lNorm))
  157. }
  158. )
  159. // StrokeArc strokes a circular arc by approximation with bezier curves
  160. func strokeArc(p Adder, a, s1, s2 fixed.Point26_6, clockwise bool, trimStart,
  161. trimEnd fixed.Int26_6, firstPoint func(p fixed.Point26_6)) (ps1, ds1, ps2, ds2 fixed.Point26_6) {
  162. // Approximate the circular arc using a set of cubic bezier curves by the method of
  163. // L. Maisonobe, "Drawing an elliptical arc using polylines, quadratic
  164. // or cubic Bezier curves", 2003
  165. // https://www.spaceroots.org/documents/elllipse/elliptical-arc.pdf
  166. // The method was simplified for circles.
  167. theta1 := math.Atan2(float64(s1.Y-a.Y), float64(s1.X-a.X))
  168. theta2 := math.Atan2(float64(s2.Y-a.Y), float64(s2.X-a.X))
  169. if !clockwise {
  170. for theta1 < theta2 {
  171. theta1 += math.Pi * 2
  172. }
  173. } else {
  174. for theta2 < theta1 {
  175. theta2 += math.Pi * 2
  176. }
  177. }
  178. deltaTheta := theta2 - theta1
  179. if trimStart > 0 {
  180. ds := (deltaTheta * float64(trimStart)) / float64(1<<tStrokeShift)
  181. deltaTheta -= ds
  182. theta1 += ds
  183. }
  184. if trimEnd > 0 {
  185. ds := (deltaTheta * float64(trimEnd)) / float64(1<<tStrokeShift)
  186. deltaTheta -= ds
  187. }
  188. segs := int(math.Abs(deltaTheta)/(math.Pi/cubicsPerHalfCircle)) + 1
  189. dTheta := deltaTheta / float64(segs)
  190. tde := math.Tan(dTheta / 2)
  191. alpha := fixed.Int26_6(math.Sin(dTheta) * (math.Sqrt(4+3*tde*tde) - 1) * (64.0 / 3.0)) // Math is fun!
  192. r := float64(Length(s1.Sub(a))) // Note r is *64
  193. ldp := fixed.Point26_6{X: -fixed.Int26_6(r * math.Sin(theta1)), Y: fixed.Int26_6(r * math.Cos(theta1))}
  194. ds1 = ldp
  195. ps1 = fixed.Point26_6{X: a.X + ldp.Y, Y: a.Y - ldp.X}
  196. firstPoint(ps1)
  197. s1 = ps1
  198. for i := 1; i <= segs; i++ {
  199. eta := theta1 + dTheta*float64(i)
  200. ds2 = fixed.Point26_6{X: -fixed.Int26_6(r * math.Sin(eta)), Y: fixed.Int26_6(r * math.Cos(eta))}
  201. ps2 = fixed.Point26_6{X: a.X + ds2.Y, Y: a.Y - ds2.X} // Using deriviative to calc new pt, because circle
  202. p1 := s1.Add(ldp.Mul(alpha))
  203. p2 := ps2.Sub(ds2.Mul(alpha))
  204. p.CubeBezier(p1, p2, ps2)
  205. s1, ldp = ps2, ds2
  206. }
  207. return
  208. }
  209. // Joiner is called when two segments of a stroke are joined. it is exposed
  210. // so that if can be wrapped to generate callbacks for the join points.
  211. func (r *Stroker) Joiner(p C2Point) {
  212. crossProd := p.LNorm.X*p.TNorm.Y - p.TNorm.X*p.LNorm.Y
  213. // stroke bottom edge, with the reverse of p
  214. r.strokeEdge(C2Point{P: p.P, TNorm: Invert(p.LNorm), LNorm: Invert(p.TNorm),
  215. TTan: Invert(p.LTan), LTan: Invert(p.TTan), RT: -p.RL, RL: -p.RT}, -crossProd)
  216. // stroke top edge
  217. r.strokeEdge(p, crossProd)
  218. }
  219. // strokeEdge reduces code redundancy in the Joiner function by 2x since it handles
  220. // the top and bottom edges. This function encodes most of the logic of how to
  221. // handle joins between the given C2Point point p, and the end of the line.
  222. func (r *Stroker) strokeEdge(p C2Point, crossProd fixed.Int26_6) {
  223. ra := &r.Filler
  224. s1, s2 := p.P.Add(p.TNorm), p.P.Add(p.LNorm) // Bevel points for top leading and trailing
  225. ra.Start(s1)
  226. if crossProd > -epsilonFixed*epsilonFixed { // Almost co-linear or convex
  227. ra.Line(s2)
  228. return // No need to fill any gaps
  229. }
  230. var ct, cl fixed.Point26_6 // Center of curvature trailing, leading
  231. var rt, rl fixed.Int26_6 // Radius of curvature trailing, leading
  232. // Adjust radiuses for stroke width
  233. if r.JoinMode == Arc || r.JoinMode == ArcClip {
  234. // Find centers of radius of curvature and adjust the radius to be drawn
  235. // by half the stroke width.
  236. if p.RT != 0 {
  237. if p.RT > 0 {
  238. ct = p.P.Add(ToLength(turnPort90(p.TTan), p.RT))
  239. rt = p.RT - r.u
  240. } else {
  241. ct = p.P.Sub(ToLength(turnPort90(p.TTan), -p.RT))
  242. rt = -p.RT + r.u
  243. }
  244. if rt < 0 {
  245. rt = 0
  246. }
  247. }
  248. if p.RL != 0 {
  249. if p.RL > 0 {
  250. cl = p.P.Add(ToLength(turnPort90(p.LTan), p.RL))
  251. rl = p.RL - r.u
  252. } else {
  253. cl = p.P.Sub(ToLength(turnPort90(p.LTan), -p.RL))
  254. rl = -p.RL + r.u
  255. }
  256. if rl < 0 {
  257. rl = 0
  258. }
  259. }
  260. }
  261. if r.JoinMode == MiterClip || r.JoinMode == Miter ||
  262. // Arc or ArcClip with 0 tRadCurve and 0 lRadCurve is treated the same as a
  263. // Miter or MiterClip join, resp.
  264. ((r.JoinMode == Arc || r.JoinMode == ArcClip) && (rt == 0 && rl == 0)) {
  265. xt := CalcIntersect(s1.Sub(p.TTan), s1, s2, s2.Sub(p.LTan))
  266. xa := xt.Sub(p.P)
  267. if Length(xa) < r.mLimit { // within miter limit
  268. ra.Line(xt)
  269. ra.Line(s2)
  270. return
  271. }
  272. if r.JoinMode == MiterClip || (r.JoinMode == ArcClip) {
  273. //Projection of tNorm onto xa
  274. tProjP := xa.Mul(fixed.Int26_6((DotProd(xa, p.TNorm) << 6) / DotProd(xa, xa)))
  275. projLen := Length(tProjP)
  276. if r.mLimit > projLen { // the miter limit line is past the bevel point
  277. // t is the fraction shifted by tStrokeShift to scale the vectors from the bevel point
  278. // to the line intersection, so that they abbut the miter limit line.
  279. tiLength := Length(xa)
  280. sx1, sx2 := xt.Sub(s1), xt.Sub(s2)
  281. t := (r.mLimit - projLen) << tStrokeShift / (tiLength - projLen)
  282. tx := ToLength(sx1, t*Length(sx1)>>tStrokeShift)
  283. lx := ToLength(sx2, t*Length(sx2)>>tStrokeShift)
  284. vx := ToLength(xa, t*Length(xa)>>tStrokeShift)
  285. s1p, _, ap := s1.Add(tx), s2.Add(lx), p.P.Add(vx)
  286. gLen := Length(ap.Sub(s1p))
  287. ra.Line(s1p)
  288. r.JoinGap(ra, ap, ToLength(turnPort90(p.TTan), gLen), ToLength(turnPort90(p.LTan), gLen))
  289. ra.Line(s2)
  290. return
  291. }
  292. } // Fallthrough
  293. } else if r.JoinMode == Arc || r.JoinMode == ArcClip {
  294. // Test for cases of a bezier meeting line, an line meeting a bezier,
  295. // or a bezier meeting a bezier. (Line meeting line is handled above.)
  296. switch {
  297. case rt == 0: // rl != 0, because one must be non-zero as checked above
  298. xt, intersect := RayCircleIntersection(s1.Add(p.TTan), s1, cl, rl)
  299. if intersect {
  300. ray1, ray2 := xt.Sub(cl), s2.Sub(cl)
  301. clockwise := (ray1.X*ray2.Y > ray1.Y*ray2.X) // Sign of xprod
  302. if Length(p.P.Sub(xt)) < r.mLimit { // within miter limit
  303. strokeArc(ra, cl, xt, s2, clockwise, 0, 0, ra.Line)
  304. ra.Line(s2)
  305. return
  306. }
  307. // Not within miter limit line
  308. if r.JoinMode == ArcClip { // Scale bevel points towards xt, and call gap func
  309. xa := xt.Sub(p.P)
  310. //Projection of tNorm onto xa
  311. tProjP := xa.Mul(fixed.Int26_6((DotProd(xa, p.TNorm) << 6) / DotProd(xa, xa)))
  312. projLen := Length(tProjP)
  313. if r.mLimit > projLen { // the miter limit line is past the bevel point
  314. // t is the fraction shifted by tStrokeShift to scale the line or arc from the bevel point
  315. // to the line intersection, so that they abbut the miter limit line.
  316. sx1 := xt.Sub(s1) //, xt.Sub(s2)
  317. t := fixed.Int26_6(1<<tStrokeShift) - ((r.mLimit - projLen) << tStrokeShift / (Length(xa) - projLen))
  318. tx := ToLength(sx1, t*Length(sx1)>>tStrokeShift)
  319. s1p := xt.Sub(tx)
  320. ra.Line(s1p)
  321. sp1, ds1, ps2, _ := strokeArc(ra, cl, xt, s2, clockwise, t, 0, ra.Start)
  322. ra.Start(s1p)
  323. // calc gap center as pt where -tnorm and line perp to midcoord
  324. midP := sp1.Add(s1p).Mul(fixed.Int26_6(1 << 5)) // midpoint
  325. midLine := turnPort90(midP.Sub(sp1))
  326. if midLine.X*midLine.X+midLine.Y*midLine.Y > epsilonFixed { // if midline is zero, CalcIntersect is invalid
  327. ap := CalcIntersect(s1p, s1p.Sub(p.TNorm), midLine.Add(midP), midP)
  328. gLen := Length(ap.Sub(s1p))
  329. if clockwise {
  330. ds1 = Invert(ds1)
  331. }
  332. r.JoinGap(ra, ap, ToLength(turnPort90(p.TTan), gLen), ToLength(turnStarboard90(ds1), gLen))
  333. }
  334. ra.Line(sp1)
  335. ra.Start(ps2)
  336. ra.Line(s2)
  337. return
  338. }
  339. //Bevel points not past miter limit: fallthrough
  340. }
  341. }
  342. case rl == 0: // rt != 0, because one must be non-zero as checked above
  343. xt, intersect := RayCircleIntersection(s2.Sub(p.LTan), s2, ct, rt)
  344. if intersect {
  345. ray1, ray2 := s1.Sub(ct), xt.Sub(ct)
  346. clockwise := ray1.X*ray2.Y > ray1.Y*ray2.X
  347. if Length(p.P.Sub(xt)) < r.mLimit { // within miter limit
  348. strokeArc(ra, ct, s1, xt, clockwise, 0, 0, ra.Line)
  349. ra.Line(s2)
  350. return
  351. }
  352. // Not within miter limit line
  353. if r.JoinMode == ArcClip { // Scale bevel points towards xt, and call gap func
  354. xa := xt.Sub(p.P)
  355. //Projection of lNorm onto xa
  356. lProjP := xa.Mul(fixed.Int26_6((DotProd(xa, p.LNorm) << 6) / DotProd(xa, xa)))
  357. projLen := Length(lProjP)
  358. if r.mLimit > projLen { // The miter limit line is past the bevel point,
  359. // t is the fraction to scale the line or arc from the bevel point
  360. // to the line intersection, so that they abbut the miter limit line.
  361. sx2 := xt.Sub(s2)
  362. t := fixed.Int26_6(1<<tStrokeShift) - ((r.mLimit - projLen) << tStrokeShift / (Length(xa) - projLen))
  363. lx := ToLength(sx2, t*Length(sx2)>>tStrokeShift)
  364. s2p := xt.Sub(lx)
  365. _, _, ps2, ds2 := strokeArc(ra, ct, s1, xt, clockwise, 0, t, ra.Line)
  366. // calc gap center as pt where -lnorm and line perp to midcoord
  367. midP := s2p.Add(ps2).Mul(fixed.Int26_6(1 << 5)) // midpoint
  368. midLine := turnStarboard90(midP.Sub(ps2))
  369. if midLine.X*midLine.X+midLine.Y*midLine.Y > epsilonFixed { // if midline is zero, CalcIntersect is invalid
  370. ap := CalcIntersect(midP, midLine.Add(midP), s2p, s2p.Sub(p.LNorm))
  371. gLen := Length(ap.Sub(ps2))
  372. if clockwise {
  373. ds2 = Invert(ds2)
  374. }
  375. r.JoinGap(ra, ap, ToLength(turnStarboard90(ds2), gLen), ToLength(turnPort90(p.LTan), gLen))
  376. }
  377. ra.Line(s2)
  378. return
  379. }
  380. //Bevel points not past miter limit: fallthrough
  381. }
  382. }
  383. default: // Both rl != 0 and rt != 0 as checked above
  384. xt1, xt2, gIntersect := CircleCircleIntersection(ct, cl, rt, rl)
  385. xt, intersect := ClosestPortside(s1, s2, xt1, xt2, gIntersect)
  386. if intersect {
  387. ray1, ray2 := s1.Sub(ct), xt.Sub(ct)
  388. clockwiseT := (ray1.X*ray2.Y > ray1.Y*ray2.X)
  389. ray1, ray2 = xt.Sub(cl), s2.Sub(cl)
  390. clockwiseL := ray1.X*ray2.Y > ray1.Y*ray2.X
  391. if Length(p.P.Sub(xt)) < r.mLimit { // within miter limit
  392. strokeArc(ra, ct, s1, xt, clockwiseT, 0, 0, ra.Line)
  393. strokeArc(ra, cl, xt, s2, clockwiseL, 0, 0, ra.Line)
  394. ra.Line(s2)
  395. return
  396. }
  397. if r.JoinMode == ArcClip { // Scale bevel points towards xt, and call gap func
  398. xa := xt.Sub(p.P)
  399. //Projection of lNorm onto xa
  400. lProjP := xa.Mul(fixed.Int26_6((DotProd(xa, p.LNorm) << 6) / DotProd(xa, xa)))
  401. projLen := Length(lProjP)
  402. if r.mLimit > projLen { // The miter limit line is past the bevel point,
  403. // t is the fraction to scale the line or arc from the bevel point
  404. // to the line intersection, so that they abbut the miter limit line.
  405. t := fixed.Int26_6(1<<tStrokeShift) - ((r.mLimit - projLen) << tStrokeShift / (Length(xa) - projLen))
  406. _, _, ps1, ds1 := strokeArc(ra, ct, s1, xt, clockwiseT, 0, t, r.Filler.Line)
  407. ps2, ds2, fs2, _ := strokeArc(ra, cl, xt, s2, clockwiseL, t, 0, ra.Start)
  408. midP := ps1.Add(ps2).Mul(fixed.Int26_6(1 << 5)) // midpoint
  409. midLine := turnStarboard90(midP.Sub(ps1))
  410. ra.Start(ps1)
  411. if midLine.X*midLine.X+midLine.Y*midLine.Y > epsilonFixed { // if midline is zero, CalcIntersect is invalid
  412. if clockwiseT {
  413. ds1 = Invert(ds1)
  414. }
  415. if clockwiseL {
  416. ds2 = Invert(ds2)
  417. }
  418. ap := CalcIntersect(midP, midLine.Add(midP), ps2, ps2.Sub(turnStarboard90(ds2)))
  419. gLen := Length(ap.Sub(ps2))
  420. r.JoinGap(ra, ap, ToLength(turnStarboard90(ds1), gLen), ToLength(turnStarboard90(ds2), gLen))
  421. }
  422. ra.Line(ps2)
  423. ra.Start(fs2)
  424. ra.Line(s2)
  425. return
  426. }
  427. }
  428. }
  429. // fallthrough to final JoinGap
  430. }
  431. }
  432. r.JoinGap(ra, p.P, p.TNorm, p.LNorm)
  433. ra.Line(s2)
  434. return
  435. }
  436. // Stop a stroked line. The line will close
  437. // is isClosed is true. Otherwise end caps will
  438. // be drawn at both ends.
  439. func (r *Stroker) Stop(isClosed bool) {
  440. if r.inStroke == false {
  441. return
  442. }
  443. rf := &r.Filler
  444. if isClosed {
  445. if r.firstP.P != rf.a {
  446. r.Line(r.firstP.P)
  447. }
  448. a := rf.a
  449. r.firstP.TNorm = r.leadPoint.TNorm
  450. r.firstP.RT = r.leadPoint.RT
  451. r.firstP.TTan = r.leadPoint.TTan
  452. rf.Start(r.firstP.P.Sub(r.firstP.TNorm))
  453. rf.Line(a.Sub(r.ln))
  454. rf.Start(a.Add(r.ln))
  455. rf.Line(r.firstP.P.Add(r.firstP.TNorm))
  456. r.Joiner(r.firstP)
  457. r.firstP.blackWidowMark(rf)
  458. } else {
  459. a := rf.a
  460. rf.Start(r.leadPoint.P.Sub(r.leadPoint.TNorm))
  461. rf.Line(a.Sub(r.ln))
  462. rf.Start(a.Add(r.ln))
  463. rf.Line(r.leadPoint.P.Add(r.leadPoint.TNorm))
  464. r.CapL(rf, r.leadPoint.P, r.leadPoint.TNorm)
  465. r.CapT(rf, r.firstP.P, Invert(r.firstP.LNorm))
  466. }
  467. r.inStroke = false
  468. }
  469. // QuadBezier starts a stroked quadratic bezier.
  470. func (r *Stroker) QuadBezier(b, c fixed.Point26_6) {
  471. r.quadBezierf(r, b, c)
  472. }
  473. // CubeBezier starts a stroked quadratic bezier.
  474. func (r *Stroker) CubeBezier(b, c, d fixed.Point26_6) {
  475. r.cubeBezierf(r, b, c, d)
  476. }
  477. // quadBezierf calcs end curvature of beziers
  478. func (r *Stroker) quadBezierf(s Rasterx, b, c fixed.Point26_6) {
  479. r.trailPoint = r.leadPoint
  480. r.CalcEndCurvature(r.a, b, c, c, b, r.a, fixed.Int52_12(2<<12), doCalcCurvature(s))
  481. r.QuadBezierF(s, b, c)
  482. r.a = c
  483. }
  484. // doCalcCurvature determines if calculation of the end curvature is required
  485. // depending on the raster type and JoinMode
  486. func doCalcCurvature(r Rasterx) bool {
  487. switch q := r.(type) {
  488. case *Filler:
  489. return false // never for filler
  490. case *Stroker:
  491. return (q.JoinMode == Arc || q.JoinMode == ArcClip)
  492. case *Dasher:
  493. return (q.JoinMode == Arc || q.JoinMode == ArcClip)
  494. default:
  495. return true // Better safe than sorry if another raster type is used
  496. }
  497. }
  498. func (r *Stroker) cubeBezierf(sgm Rasterx, b, c, d fixed.Point26_6) {
  499. if (r.a == b && c == d) || (r.a == b && b == c) || (c == b && d == c) {
  500. sgm.Line(d)
  501. return
  502. }
  503. r.trailPoint = r.leadPoint
  504. // Only calculate curvature if stroking or and using arc or arc-clip
  505. doCalcCurve := doCalcCurvature(sgm)
  506. const dm = fixed.Int52_12((3 << 12) / 2)
  507. switch {
  508. // b != c, and c != d see above
  509. case r.a == b:
  510. r.CalcEndCurvature(b, c, d, d, c, b, dm, doCalcCurve)
  511. // b != a, and b != c, see above
  512. case c == d:
  513. r.CalcEndCurvature(r.a, b, c, c, b, r.a, dm, doCalcCurve)
  514. default:
  515. r.CalcEndCurvature(r.a, b, c, d, c, b, dm, doCalcCurve)
  516. }
  517. r.CubeBezierF(sgm, b, c, d)
  518. r.a = d
  519. }
  520. // Line adds a line segment to the rasterizer
  521. func (r *Stroker) Line(b fixed.Point26_6) {
  522. r.LineSeg(r, b)
  523. }
  524. //LineSeg is called by both the Stroker and Dasher
  525. func (r *Stroker) LineSeg(sgm Rasterx, b fixed.Point26_6) {
  526. r.trailPoint = r.leadPoint
  527. ba := b.Sub(r.a)
  528. if ba.X == 0 && ba.Y == 0 { // a == b, line is degenerate
  529. if r.trailPoint.TTan.X != 0 || r.trailPoint.TTan.Y != 0 {
  530. ba = r.trailPoint.TTan // Use last tangent for seg tangent
  531. } else { // Must be on top of last moveto; set ba to X axis unit vector
  532. ba = fixed.Point26_6{X: 1 << 6, Y: 0}
  533. }
  534. }
  535. bnorm := turnPort90(ToLength(ba, r.u))
  536. r.trailPoint.LTan = ba
  537. r.leadPoint.TTan = ba
  538. r.trailPoint.LNorm = bnorm
  539. r.leadPoint.TNorm = bnorm
  540. r.trailPoint.RL = 0.0
  541. r.leadPoint.RT = 0.0
  542. r.trailPoint.P = r.a
  543. r.leadPoint.P = b
  544. sgm.joinF()
  545. sgm.lineF(b)
  546. r.a = b
  547. }
  548. // lineF is for intra-curve lines. It is required for the Rasterizer interface
  549. // so that if the line is being stroked or dash stroked, different actions can be
  550. // taken.
  551. func (r *Stroker) lineF(b fixed.Point26_6) {
  552. // b is either an intra-segment value, or
  553. // the end of the segment.
  554. var bnorm fixed.Point26_6
  555. a := r.a // Hold a since r.a is going to change during stroke operation
  556. if b == r.leadPoint.P { // End of segment
  557. bnorm = r.leadPoint.TNorm // Use more accurate leadPoint tangent
  558. } else {
  559. bnorm = turnPort90(ToLength(b.Sub(a), r.u)) // Intra segment normal
  560. }
  561. ra := &r.Filler
  562. ra.Start(b.Sub(bnorm))
  563. ra.Line(a.Sub(r.ln))
  564. ra.Start(a.Add(r.ln))
  565. ra.Line(b.Add(bnorm))
  566. r.a = b
  567. r.ln = bnorm
  568. }
  569. // Start iniitates a stroked path
  570. func (r *Stroker) Start(a fixed.Point26_6) {
  571. r.inStroke = false
  572. r.Filler.Start(a)
  573. }
  574. // CalcEndCurvature calculates the radius of curvature given the control points
  575. // of a bezier curve.
  576. // It is a low level function exposed for the purposes of callbacks
  577. // and debugging.
  578. func (r *Stroker) CalcEndCurvature(p0, p1, p2, q0, q1, q2 fixed.Point26_6,
  579. dm fixed.Int52_12, calcRadCuve bool) {
  580. r.trailPoint.P = p0
  581. r.leadPoint.P = q0
  582. r.trailPoint.LTan = p1.Sub(p0)
  583. r.leadPoint.TTan = q0.Sub(q1)
  584. r.trailPoint.LNorm = turnPort90(ToLength(r.trailPoint.LTan, r.u))
  585. r.leadPoint.TNorm = turnPort90(ToLength(r.leadPoint.TTan, r.u))
  586. if calcRadCuve {
  587. r.trailPoint.RL = RadCurvature(p0, p1, p2, dm)
  588. r.leadPoint.RT = -RadCurvature(q0, q1, q2, dm)
  589. } else {
  590. r.trailPoint.RL = 0
  591. r.leadPoint.RT = 0
  592. }
  593. }
  594. func (r *Stroker) joinF() {
  595. if r.inStroke == false {
  596. r.inStroke = true
  597. r.firstP = r.trailPoint
  598. } else {
  599. ra := &r.Filler
  600. tl := r.trailPoint.P.Sub(r.trailPoint.TNorm)
  601. th := r.trailPoint.P.Add(r.trailPoint.TNorm)
  602. if r.a != r.trailPoint.P || r.ln != r.trailPoint.TNorm {
  603. a := r.a
  604. ra.Start(tl)
  605. ra.Line(a.Sub(r.ln))
  606. ra.Start(a.Add(r.ln))
  607. ra.Line(th)
  608. }
  609. r.Joiner(r.trailPoint)
  610. r.trailPoint.blackWidowMark(ra)
  611. }
  612. r.ln = r.trailPoint.LNorm
  613. r.a = r.trailPoint.P
  614. }
  615. // blackWidowMark handles a gap in a stroke that can occur when a line end is too close
  616. // to a segment to segment join point. Although it is only required in those cases,
  617. // at this point, no code has been written to properly detect when it is needed,
  618. // so for now it just draws by default.
  619. func (jp *C2Point) blackWidowMark(ra Adder) {
  620. xprod := jp.TNorm.X*jp.LNorm.Y - jp.TNorm.Y*jp.LNorm.X
  621. if xprod > epsilonFixed*epsilonFixed {
  622. tl := jp.P.Sub(jp.TNorm)
  623. ll := jp.P.Sub(jp.LNorm)
  624. ra.Start(jp.P)
  625. ra.Line(tl)
  626. ra.Line(ll)
  627. ra.Line(jp.P)
  628. } else if xprod < -epsilonFixed*epsilonFixed {
  629. th := jp.P.Add(jp.TNorm)
  630. lh := jp.P.Add(jp.LNorm)
  631. ra.Start(jp.P)
  632. ra.Line(lh)
  633. ra.Line(th)
  634. ra.Line(jp.P)
  635. }
  636. }