diff --git a/earcut.go b/earcut.go index e536556..bdeeb5e 100644 --- a/earcut.go +++ b/earcut.go @@ -799,3 +799,109 @@ func (ec *earcut) removeNode(p *node) { p.nextZ.prevZ = p.prevZ } } + +// sortFontContours takes the contours of a font glyph +// and checks whether each contour is the outside or a +// hole, and returns an array that is sorted so that +// it contains an index of an outer contour followed by +// any number of indices of hole contours followed by +// a terminating -1 +func sortFontContours(contours [][]backendbase.Vec) []int { + type cut struct { + idx int + count int + } + type info struct { + cuts []cut + cutTotal int + outer bool + } + + cutBuf := make([]cut, len(contours)*len(contours)) + cinf := make([]info, len(contours)) + for i := range contours { + cinf[i].cuts = cutBuf[i*len(contours) : i*len(contours)] + } + + // go through each contour, pick one point on it, and + // project that point to the right. count the number of + // other contours that it cuts + for i, p1 := range contours { + pt := p1[0] + for j, p2 := range contours { + if i == j { + continue + } + + for k := range p2 { + a, b := p2[k], p2[(k+1)%len(p2)] + if a == b { + continue + } + + minY := math.Min(a[1], b[1]) + maxY := math.Max(a[1], b[1]) + + if pt[1] <= minY || pt[1] > maxY { + continue + } + + r := (pt[1] - a[1]) / (b[1] - a[1]) + x := (b[0]-a[0])*r + a[0] + if x <= pt[0] { + continue + } + + found := false + for l := range cinf[i].cuts { + if cinf[i].cuts[l].idx == j { + cinf[i].cuts[l].count++ + found = true + break + } + } + if !found { + cinf[i].cuts = append(cinf[i].cuts, cut{idx: j, count: 1}) + } + cinf[i].cutTotal++ + } + } + } + + // any contour with an even number of cuts is outer, + // odd number of cuts means it is a hole + for i := range cinf { + cinf[i].outer = cinf[i].cutTotal%2 == 0 + } + + // go through them again, pick any outer contour, then + // find any hole where the first outer contour it cuts + // an odd number of times is the picked contour and add + // it to the list of its holes + result := make([]int, 0, len(contours)*2) + for i := range cinf { + if !cinf[i].outer { + continue + } + result = append(result, i) + + for j := range cinf { + if cinf[j].outer { + continue + } + for _, cut := range cinf[j].cuts { + if cut.count%2 == 0 { + continue + } + if cut.idx == i { + result = append(result, j) + break + } + } + } + + result = append(result, -1) + } + + return result +} diff --git a/text.go b/text.go index d9c0a60..18f09ff 100644 --- a/text.go +++ b/text.go @@ -596,7 +596,7 @@ func (cv *Canvas) runeTris(rn rune) []backendbase.Vec { var gb truetype.GlyphBuf gb.Load(cv.state.font.font, baseFontSize, idx, font.HintingFull) - polyWithHoles := make([][]backendbase.Vec, 0, len(gb.Ends)) + contours := make([][]backendbase.Vec, 0, len(gb.Ends)) from := 0 for _, to := range gb.Ends { @@ -608,7 +608,7 @@ func (cv *Canvas) runeTris(rn rune) []backendbase.Vec { others = ps[1:] } else { last := ps[len(ps)-1] - if ps[len(ps)-1].Flags&0x01 != 0 { + if last.Flags&0x01 != 0 { start = last others = ps[:len(ps)-1] } else { @@ -652,29 +652,62 @@ func (cv *Canvas) runeTris(rn rune) []backendbase.Vec { } path.ClosePath() - poly := make([]backendbase.Vec, len(path.p)) + contour := make([]backendbase.Vec, len(path.p)) for i, pt := range path.p { - poly[i] = pt.pos + contour[i] = pt.pos } - polyWithHoles = append(polyWithHoles, poly) + contours = append(contours, contour) from = to } - var ec earcut - ec.run(polyWithHoles) + idxs := sortFontContours(contours) + sortedContours := make([][]backendbase.Vec, 0, len(idxs)) + trisList := make([][]backendbase.Vec, 0, len(contours)) - tris := make([]backendbase.Vec, len(ec.indices)) - for i, idx := range ec.indices { - pidx := 0 - poly := polyWithHoles[pidx] - for idx >= len(poly) { - idx -= len(poly) - pidx++ - poly = polyWithHoles[pidx] + for i := 0; i < len(idxs); { + var j int + for j = i; j < len(idxs); j++ { + if idxs[j] == -1 { + break + } } - tris[i] = poly[idx] + + sortedContours = sortedContours[:j-i] + for k, idx := range idxs[i:j] { + sortedContours[k] = contours[idx] + } + + var ec earcut + ec.run(sortedContours) + + tris := make([]backendbase.Vec, len(ec.indices)) + for i, idx := range ec.indices { + pidx := 0 + poly := sortedContours[pidx] + for idx >= len(poly) { + idx -= len(poly) + pidx++ + poly = sortedContours[pidx] + } + tris[i] = poly[idx] + } + trisList = append(trisList, tris) + + i = j + 1 + } + + count := 0 + for _, tris := range trisList { + count += len(tris) + } + + allTris := make([]backendbase.Vec, count) + pos := 0 + for _, tris := range trisList { + copy(allTris[pos:], tris) + pos += len(tris) } cache, ok := cv.fontTriCache[cv.state.font] @@ -683,9 +716,9 @@ func (cv *Canvas) runeTris(rn rune) []backendbase.Vec { cv.fontTriCache[cv.state.font] = cache } cache.lastUsed = time.Now() - cache.cache[idx] = tris + cache.cache[idx] = allTris - return tris + return allTris } // TextMetrics is the result of a MeasureText call