Skip to content

Commit a4826a3

Browse files
committed
fixed a problem with closed paths and self intersecting polygons
added the readme example as a test
1 parent aa059cf commit a4826a3

6 files changed

Lines changed: 59 additions & 9 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,5 @@ These features *should* work just like their HTML5 counterparts, but there are l
126126
# Missing features
127127

128128
- globalCompositeOperation
129+
- imageSmoothingEnabled
129130
- textBaseline hanging and ideographic (currently work just like top and bottom)

canvas_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,3 +584,26 @@ func TestShadow(t *testing.T) {
584584
cv.FillRect(50, 15, 30, 60)
585585
})
586586
}
587+
588+
func TestReadme(t *testing.T) {
589+
run(t, func(cv *canvas.Canvas) {
590+
w, h := 100.0, 100.0
591+
cv.SetFillStyle("#000")
592+
cv.FillRect(0, 0, w, h)
593+
594+
for r := 0.0; r < math.Pi*2; r += math.Pi * 0.1 {
595+
cv.SetFillStyle(int(r*10), int(r*20), int(r*40))
596+
cv.BeginPath()
597+
cv.MoveTo(w*0.5, h*0.5)
598+
cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, r, r+0.1*math.Pi, false)
599+
cv.ClosePath()
600+
cv.Fill()
601+
}
602+
603+
cv.SetStrokeStyle("#FFF")
604+
cv.SetLineWidth(10)
605+
cv.BeginPath()
606+
cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, 0, math.Pi*2, false)
607+
cv.Stroke()
608+
})
609+
}

path2d.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,20 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
8282
newp.flags |= pathIsConvex
8383
} else if prev.flags&pathIsConvex > 0 {
8484
cuts := false
85+
var cutPoint vec
8586
if checkSelfIntersection && !Performance.IgnoreSelfIntersections {
8687
b0, b1 := prev.pos, vec{x, y}
8788
for i := 1; i < count; i++ {
8889
a0, a1 := p.p[i-1].pos, p.p[i].pos
89-
_, r1, r2 := lineIntersection(a0, a1, b0, b1)
90+
var r1, r2 float64
91+
cutPoint, r1, r2 = lineIntersection(a0, a1, b0, b1)
9092
if r1 > 0 && r1 < 1 && r2 > 0 && r2 < 1 {
9193
cuts = true
9294
break
9395
}
9496
}
9597
}
96-
if cuts {
98+
if cuts && !isSamePoint(cutPoint, vec{x, y}, samePointTolerance) {
9799
newp.flags |= pathSelfIntersects
98100
} else {
99101
prev2 := &p.p[len(p.p)-3]
@@ -288,24 +290,39 @@ func (p *Path2D) Rect(x, y, w, h float64) {
288290
// func (p *Path2D) Ellipse(...) {
289291
// }
290292

291-
func runSubPaths(path []pathPoint, fn func(subPath []pathPoint) bool) {
293+
func runSubPaths(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) {
292294
start := 0
293295
for i, p := range path {
294296
if p.flags&pathMove == 0 {
295297
continue
296298
}
297299
if i >= start+3 {
298-
if fn(path[start:i]) {
300+
end := i
301+
if runSubPath(path[start:end], close, fn) {
299302
return
300303
}
301304
}
302305
start = i
303306
}
304307
if len(path) >= start+3 {
305-
fn(path[start:])
308+
runSubPath(path[start:], close, fn)
306309
}
307310
}
308311

312+
func runSubPath(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) bool {
313+
if !close || path[0].pos == path[len(path)-1].pos {
314+
return fn(path)
315+
}
316+
317+
var buf [64]pathPoint
318+
path2 := Path2D{
319+
p: append(buf[:0], path...),
320+
move: path[0].pos,
321+
}
322+
path2.lineTo(path[0].pos[0], path[0].pos[1], true)
323+
return fn(path2.p)
324+
}
325+
309326
type pathRule uint8
310327

311328
// Path rule constants. See https://en.wikipedia.org/wiki/Nonzero-rule
@@ -319,7 +336,7 @@ const (
319336
// to the given rule
320337
func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool {
321338
inside := false
322-
runSubPaths(p.p, func(sp []pathPoint) bool {
339+
runSubPaths(p.p, false, func(sp []pathPoint) bool {
323340
num := 0
324341
prev := sp[len(sp)-1].pos
325342
for _, pt := range p.p {

paths.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,8 @@ func (cv *Canvas) fillPath(path *Path2D, tf mat) {
362362

363363
var triBuf [500][2]float64
364364
tris := triBuf[:0]
365-
runSubPaths(path.p, func(sp []pathPoint) bool {
365+
366+
runSubPaths(path.p, true, func(sp []pathPoint) bool {
366367
tris = appendSubPathTriangles(tris, tf, sp)
367368
return false
368369
})
@@ -381,6 +382,9 @@ func appendSubPathTriangles(tris [][2]float64, mat mat, path []pathPoint) [][2]f
381382
if last.flags&pathIsConvex != 0 {
382383
p0, p1 := path[0].pos.mulMat(mat), path[1].pos.mulMat(mat)
383384
last := len(path)
385+
if path[0].pos == path[last-1].pos {
386+
last--
387+
}
384388
for i := 2; i < last; i++ {
385389
p2 := path[i].pos.mulMat(mat)
386390
tris = append(tris, p0, p1, p2)
@@ -410,7 +414,7 @@ func (cv *Canvas) clip(path *Path2D, tf mat) {
410414

411415
var triBuf [500][2]float64
412416
tris := triBuf[:0]
413-
runSubPaths(path.p, func(sp []pathPoint) bool {
417+
runSubPaths(path.p, true, func(sp []pathPoint) bool {
414418
tris = appendSubPathTriangles(tris, tf, sp)
415419
return false
416420
})

testdata/Readme.png

1.59 KB
Loading

triangulation.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ func polygonContainsPoint(polygon []vec, p vec) bool {
8585
}
8686

8787
func triangulatePath(path []pathPoint, mat mat, target [][2]float64) [][2]float64 {
88+
if path[0].pos == path[len(path)-1].pos {
89+
path = path[:len(path)-1]
90+
}
91+
8892
var buf [500]vec
8993
polygon := buf[:0]
9094
for _, p := range path {
@@ -345,9 +349,10 @@ func setPathLeftRightInside(net *tessNet) {
345349
}
346350

347351
func selfIntersectingPathParts(p []pathPoint, partFn func(sp []pathPoint) bool) {
348-
runSubPaths(p, func(sp1 []pathPoint) bool {
352+
runSubPaths(p, false, func(sp1 []pathPoint) bool {
349353
net := cutIntersections(sp1)
350354
if net.verts == nil {
355+
partFn(sp1)
351356
return false
352357
}
353358

0 commit comments

Comments
 (0)