Skip to content

Commit

Permalink
added sorting of font contours for correct triangulation
Browse files Browse the repository at this point in the history
  • Loading branch information
tfriedel6 committed Apr 4, 2020
1 parent c4d3130 commit e37f4f5
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 18 deletions.
106 changes: 106 additions & 0 deletions earcut.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
69 changes: 51 additions & 18 deletions text.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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))

for i := 0; i < len(idxs); {
var j int
for j = i; j < len(idxs); j++ {
if idxs[j] == -1 {
break
}
}

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 := polyWithHoles[pidx]
for idx >= len(poly) {
idx -= len(poly)
pidx++
poly = polyWithHoles[pidx]
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]
}
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]
Expand All @@ -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
Expand Down

0 comments on commit e37f4f5

Please sign in to comment.