implemented text stroking

This commit is contained in:
Thomas Friedel 2018-09-01 17:06:00 +02:00
parent 56995ff396
commit bfed5dc792
15 changed files with 116 additions and 2 deletions

View file

@ -100,6 +100,7 @@ These features *should* work just like their HTML5 counterparts, but there are l
- measureText
- textAlign
- fillStyle
- strokeText
- strokeStyle
- linear gradients
- radial gradients
@ -126,4 +127,3 @@ These features *should* work just like their HTML5 counterparts, but there are l
- textBaseline
- isPointInPath
- isPointInStroke
- strokeText

View file

@ -42,7 +42,7 @@ func run(t *testing.T, fn func(cv *canvas.Canvas)) {
callerFuncName := callerFunc.Name()
callerFuncName = callerFuncName[strings.Index(callerFuncName, prefix)+len(prefix):]
fileName := fmt.Sprintf("testimages/%s.png", callerFuncName)
fileName := fmt.Sprintf("testdata/%s.png", callerFuncName)
_, err = os.Stat(fileName)
if err != nil && !os.IsNotExist(err) {
@ -352,3 +352,13 @@ func TestLineDash2(t *testing.T) {
cv.Stroke()
})
}
func TestText(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFont("testdata/Roboto-Light.ttf", 48)
cv.SetFillStyle("#F00")
cv.FillText("A BC", 0, 46)
cv.SetStrokeStyle("#0F0")
cv.SetLineWidth(1)
cv.StrokeText("D EF", 0, 90)
})
}

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 325 B

View file

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 641 B

View file

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 508 B

View file

Before

Width:  |  Height:  |  Size: 310 B

After

Width:  |  Height:  |  Size: 310 B

View file

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 373 B

View file

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 336 B

View file

Before

Width:  |  Height:  |  Size: 492 B

After

Width:  |  Height:  |  Size: 492 B

View file

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 312 B

View file

Before

Width:  |  Height:  |  Size: 529 B

After

Width:  |  Height:  |  Size: 529 B

BIN
testdata/Roboto-Light.ttf vendored Executable file

Binary file not shown.

BIN
testdata/Text.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

104
text.go
View file

@ -232,6 +232,110 @@ func (cv *Canvas) FillText(str string, x, y float64) {
gli.ActiveTexture(gl_TEXTURE0)
}
// StrokeText draws the given string at the given coordinates
// using the currently set font and font height and using the
// current stroke style
func (cv *Canvas) StrokeText(str string, x, y float64) {
cv.activate()
if cv.state.font == nil {
return
}
frc := fontRenderingContext
frc.setFont(cv.state.font.font)
frc.setFontSize(float64(cv.state.fontSize))
fnt := cv.state.font.font
for _, rn := range str {
idx := fnt.Index(rn)
if idx == 0 {
idx = fnt.Index(' ')
}
advance, _, err := frc.glyphMeasure(idx, fixed.Point26_6{})
if err != nil {
continue
}
cv.strokeRune(rn, vec{x, y})
x += float64(advance) / 64
}
}
func (cv *Canvas) strokeRune(rn rune, pos vec) {
gb := &truetype.GlyphBuf{}
gb.Load(cv.state.font.font, fixed.Int26_6(cv.state.fontSize*64), cv.state.font.font.Index(rn), font.HintingNone)
prevPath := cv.path
defer func() {
cv.path = prevPath
}()
from := 0
for _, to := range gb.Ends {
ps := gb.Points[from:to]
start := fixed.Point26_6{
X: ps[0].X,
Y: ps[0].Y,
}
others := []truetype.Point(nil)
if ps[0].Flags&0x01 != 0 {
others = ps[1:]
} else {
last := fixed.Point26_6{
X: ps[len(ps)-1].X,
Y: ps[len(ps)-1].Y,
}
if ps[len(ps)-1].Flags&0x01 != 0 {
start = last
others = ps[:len(ps)-1]
} else {
start = fixed.Point26_6{
X: (start.X + last.X) / 2,
Y: (start.Y + last.Y) / 2,
}
others = ps
}
}
p0, on0 := gb.Points[from], false
cv.MoveTo(float64(p0.X)/64+pos[0], pos[1]-float64(p0.Y)/64)
for _, p := range others {
on := p.Flags&0x01 != 0
if on {
if on0 {
cv.LineTo(float64(p.X)/64+pos[0], pos[1]-float64(p.Y)/64)
} else {
cv.QuadraticCurveTo(float64(p0.X)/64+pos[0], pos[1]-float64(p0.Y)/64, float64(p.X)/64+pos[0], pos[1]-float64(p.Y)/64)
}
} else {
if on0 {
// No-op.
} else {
mid := fixed.Point26_6{
X: (p0.X + p.X) / 2,
Y: (p0.Y + p.Y) / 2,
}
cv.QuadraticCurveTo(float64(p0.X)/64+pos[0], pos[1]-float64(p0.Y)/64, float64(mid.X)/64+pos[0], pos[1]-float64(mid.Y)/64)
}
}
p0, on0 = p, on
}
if on0 {
cv.LineTo(float64(start.X)/64+pos[0], pos[1]-float64(start.Y)/64)
} else {
cv.QuadraticCurveTo(float64(p0.X)/64+pos[0], pos[1]-float64(p0.Y)/64, float64(start.X)/64+pos[0], pos[1]-float64(start.Y)/64)
}
cv.ClosePath()
cv.Stroke()
from = to
}
}
// TextMetrics is the result of a MeasureText call
type TextMetrics struct {
Width float64