implemented text stroking
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
0
testimages/Alpha.png → testdata/Alpha.png
vendored
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 325 B |
0
testimages/Curves.png → testdata/Curves.png
vendored
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 508 B |
Before Width: | Height: | Size: 310 B After Width: | Height: | Size: 310 B |
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 492 B |
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 529 B |
BIN
testdata/Roboto-Light.ttf
vendored
Executable file
BIN
testdata/Text.png
vendored
Executable file
After Width: | Height: | Size: 1.9 KiB |
104
text.go
|
@ -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
|
||||
|
|