new way to fill text (work in progress)
This commit is contained in:
parent
39e9e6400b
commit
30531aaab7
2 changed files with 323 additions and 146 deletions
14
canvas.go
14
canvas.go
|
@ -30,6 +30,7 @@ type Canvas struct {
|
||||||
images map[interface{}]*Image
|
images map[interface{}]*Image
|
||||||
fonts map[interface{}]*Font
|
fonts map[interface{}]*Font
|
||||||
fontCtxs map[fontKey]*frCache
|
fontCtxs map[fontKey]*frCache
|
||||||
|
fontTriCache map[*Font]*fontTriCache
|
||||||
|
|
||||||
shadowBuf []backendbase.Vec
|
shadowBuf []backendbase.Vec
|
||||||
}
|
}
|
||||||
|
@ -143,6 +144,7 @@ func New(backend backendbase.Backend) *Canvas {
|
||||||
images: make(map[interface{}]*Image),
|
images: make(map[interface{}]*Image),
|
||||||
fonts: make(map[interface{}]*Font),
|
fonts: make(map[interface{}]*Font),
|
||||||
fontCtxs: make(map[fontKey]*frCache),
|
fontCtxs: make(map[fontKey]*frCache),
|
||||||
|
fontTriCache: make(map[*Font]*fontTriCache),
|
||||||
}
|
}
|
||||||
cv.state.lineWidth = 1
|
cv.state.lineWidth = 1
|
||||||
cv.state.lineAlpha = 1
|
cv.state.lineAlpha = 1
|
||||||
|
@ -479,6 +481,7 @@ func (cv *Canvas) reduceCache(keepSize, rec int) {
|
||||||
var total int
|
var total int
|
||||||
oldest := time.Now()
|
oldest := time.Now()
|
||||||
var oldestFontKey fontKey
|
var oldestFontKey fontKey
|
||||||
|
var oldestFontKey2 *Font
|
||||||
var oldestImageKey interface{}
|
var oldestImageKey interface{}
|
||||||
for src, img := range cv.images {
|
for src, img := range cv.images {
|
||||||
w, h := img.img.Size()
|
w, h := img.img.Size()
|
||||||
|
@ -496,6 +499,15 @@ func (cv *Canvas) reduceCache(keepSize, rec int) {
|
||||||
oldestImageKey = nil
|
oldestImageKey = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for fnt, cache := range cv.fontTriCache {
|
||||||
|
total += cache.size()
|
||||||
|
if cache.lastUsed.Before(oldest) {
|
||||||
|
oldest = cache.lastUsed
|
||||||
|
oldestFontKey2 = fnt
|
||||||
|
oldestFontKey = fontKey{}
|
||||||
|
oldestImageKey = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
if total <= keepSize {
|
if total <= keepSize {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -503,6 +515,8 @@ func (cv *Canvas) reduceCache(keepSize, rec int) {
|
||||||
if oldestImageKey != nil {
|
if oldestImageKey != nil {
|
||||||
cv.images[oldestImageKey].Delete()
|
cv.images[oldestImageKey].Delete()
|
||||||
delete(cv.images, oldestImageKey)
|
delete(cv.images, oldestImageKey)
|
||||||
|
} else if oldestFontKey2 != nil {
|
||||||
|
delete(cv.fontTriCache, oldestFontKey2)
|
||||||
} else {
|
} else {
|
||||||
cv.fontCtxs[oldestFontKey].ctx = nil
|
cv.fontCtxs[oldestFontKey].ctx = nil
|
||||||
delete(cv.fontCtxs, oldestFontKey)
|
delete(cv.fontCtxs, oldestFontKey)
|
||||||
|
|
439
text.go
439
text.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/golang/freetype"
|
"github.com/golang/freetype"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
@ -33,11 +34,27 @@ type frCache struct {
|
||||||
lastUsed time.Time
|
lastUsed time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fontTriCache struct {
|
||||||
|
cache map[truetype.Index]*Path2D
|
||||||
|
lastUsed time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ftc *fontTriCache) size() int {
|
||||||
|
size := 0
|
||||||
|
pps := int(unsafe.Sizeof(pathPoint{}))
|
||||||
|
for _, p := range ftc.cache {
|
||||||
|
size += len(p.p) * pps
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
var zeroes [alphaTexSize]byte
|
var zeroes [alphaTexSize]byte
|
||||||
var textImage *image.Alpha
|
var textImage *image.Alpha
|
||||||
|
|
||||||
var defaultFont *Font
|
var defaultFont *Font
|
||||||
|
|
||||||
|
var baseFontSize = fixed.I(42)
|
||||||
|
|
||||||
// LoadFont loads a font and returns the result. The font
|
// LoadFont loads a font and returns the result. The font
|
||||||
// can be a file name or a byte slice in TTF format
|
// can be a file name or a byte slice in TTF format
|
||||||
func (cv *Canvas) LoadFont(src interface{}) (*Font, error) {
|
func (cv *Canvas) LoadFont(src interface{}) (*Font, error) {
|
||||||
|
@ -102,8 +119,8 @@ func (cv *Canvas) getFRContext(font *Font, size fixed.Int26_6) *frContext {
|
||||||
|
|
||||||
cv.reduceCache(Performance.CacheSize, 0)
|
cv.reduceCache(Performance.CacheSize, 0)
|
||||||
frctx := newFRContext()
|
frctx := newFRContext()
|
||||||
frctx.fontSize = k.size
|
frctx.fontSize = size
|
||||||
frctx.f = k.font.font
|
frctx.f = font.font
|
||||||
frctx.recalc()
|
frctx.recalc()
|
||||||
|
|
||||||
cv.fontCtxs[k] = &frCache{ctx: frctx, lastUsed: time.Now()}
|
cv.fontCtxs[k] = &frCache{ctx: frctx, lastUsed: time.Now()}
|
||||||
|
@ -123,126 +140,19 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
scale := (scaleX + scaleY) * 0.5
|
scale := (scaleX + scaleY) * 0.5
|
||||||
fontSize := fixed.Int26_6(math.Round(float64(cv.state.fontSize) * scale))
|
fontSize := fixed.Int26_6(math.Round(float64(cv.state.fontSize) * scale))
|
||||||
|
|
||||||
|
// if fontSize > fixed.I(30) {
|
||||||
|
// cv.fillText2(str, x, y, fontSize)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
frc := cv.getFRContext(cv.state.font, fontSize)
|
frc := cv.getFRContext(cv.state.font, fontSize)
|
||||||
fnt := cv.state.font.font
|
fnt := cv.state.font.font
|
||||||
|
|
||||||
// measure rendered text size
|
strWidth, strHeight, textOffset, str := cv.measureTextRendering(str, &x, &y, frc, scale)
|
||||||
var p fixed.Point26_6
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
var textOffset image.Point
|
|
||||||
var strWidth, strMaxY int
|
|
||||||
for i, rn := range str {
|
|
||||||
idx := fnt.Index(rn)
|
|
||||||
if idx == 0 {
|
|
||||||
idx = fnt.Index(' ')
|
|
||||||
}
|
|
||||||
advance, bounds, err := frc.glyphMeasure(idx, p)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var kern fixed.Int26_6
|
|
||||||
if hasPrev {
|
|
||||||
kern = fnt.Kern(fontSize, prev, idx)
|
|
||||||
if frc.hinting != font.HintingNone {
|
|
||||||
kern = (kern + 32) &^ 63
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
textOffset.X = bounds.Min.X
|
|
||||||
}
|
|
||||||
if bounds.Min.Y < textOffset.Y {
|
|
||||||
textOffset.Y = bounds.Min.Y
|
|
||||||
}
|
|
||||||
if bounds.Max.Y > strMaxY {
|
|
||||||
strMaxY = bounds.Max.Y
|
|
||||||
}
|
|
||||||
p.X += advance + kern
|
|
||||||
}
|
|
||||||
strWidth = p.X.Ceil() - textOffset.X
|
|
||||||
strHeight := strMaxY - textOffset.Y
|
|
||||||
|
|
||||||
if strWidth <= 0 || strHeight <= 0 {
|
if strWidth <= 0 || strHeight <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fstrWidth := float64(strWidth) / scale
|
|
||||||
fstrHeight := float64(strHeight) / scale
|
|
||||||
|
|
||||||
// calculate offsets
|
|
||||||
if cv.state.textAlign == Center {
|
|
||||||
x -= float64(fstrWidth) * 0.5
|
|
||||||
} else if cv.state.textAlign == Right || cv.state.textAlign == End {
|
|
||||||
x -= float64(fstrWidth)
|
|
||||||
}
|
|
||||||
metrics := cv.state.fontMetrics
|
|
||||||
switch cv.state.textBaseline {
|
|
||||||
case Alphabetic:
|
|
||||||
case Middle:
|
|
||||||
y += (-float64(metrics.Descent)/64 + float64(metrics.Height)*0.5/64) / scale
|
|
||||||
case Top, Hanging:
|
|
||||||
y += (-float64(metrics.Descent)/64 + float64(metrics.Height)/64) / scale
|
|
||||||
case Bottom, Ideographic:
|
|
||||||
y += -float64(metrics.Descent) / 64 / scale
|
|
||||||
}
|
|
||||||
|
|
||||||
// find out which characters are inside the visible area
|
|
||||||
p = fixed.Point26_6{}
|
|
||||||
prev, hasPrev = truetype.Index(0), false
|
|
||||||
var insideCount int
|
|
||||||
strFrom, strTo := 0, len(str)
|
|
||||||
curInside := false
|
|
||||||
curX := x
|
|
||||||
for i, rn := range str {
|
|
||||||
idx := fnt.Index(rn)
|
|
||||||
if idx == 0 {
|
|
||||||
idx = fnt.Index(' ')
|
|
||||||
}
|
|
||||||
advance, bounds, err := frc.glyphMeasure(idx, p)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var kern fixed.Int26_6
|
|
||||||
if hasPrev {
|
|
||||||
kern = fnt.Kern(fontSize, prev, idx)
|
|
||||||
if frc.hinting != font.HintingNone {
|
|
||||||
kern = (kern + 32) &^ 63
|
|
||||||
}
|
|
||||||
curX += float64(kern) / 64 / scale
|
|
||||||
}
|
|
||||||
|
|
||||||
w, h := cv.b.Size()
|
|
||||||
fw, fh := float64(w), float64(h)
|
|
||||||
|
|
||||||
p0 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
|
|
||||||
p1 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
|
|
||||||
p2 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Max.Y)/scale + y})
|
|
||||||
p3 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(bounds.Min.Y)/scale + y})
|
|
||||||
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
|
|
||||||
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
|
|
||||||
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
|
|
||||||
(p0[1] < fh || p1[1] < fh || p2[1] < fh || p3[1] < fh)
|
|
||||||
|
|
||||||
if inside {
|
|
||||||
insideCount++
|
|
||||||
}
|
|
||||||
if !curInside && inside {
|
|
||||||
curInside = true
|
|
||||||
strFrom = i
|
|
||||||
x = curX
|
|
||||||
} else if curInside && !inside {
|
|
||||||
strTo = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
p.X += advance + kern
|
|
||||||
curX += float64(advance) / 64 / scale
|
|
||||||
}
|
|
||||||
|
|
||||||
if strFrom == strTo || insideCount == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure textImage is large enough for the rendered string
|
// make sure textImage is large enough for the rendered string
|
||||||
if textImage == nil || textImage.Bounds().Dx() < strWidth || textImage.Bounds().Dy() < strHeight {
|
if textImage == nil || textImage.Bounds().Dx() < strWidth || textImage.Bounds().Dy() < strHeight {
|
||||||
var size int
|
var size int
|
||||||
|
@ -257,9 +167,6 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
textImage = image.NewAlpha(image.Rect(0, 0, size, size))
|
textImage = image.NewAlpha(image.Rect(0, 0, size, size))
|
||||||
}
|
}
|
||||||
|
|
||||||
curX = x
|
|
||||||
p = fixed.Point26_6{}
|
|
||||||
|
|
||||||
// clear the render region in textImage
|
// clear the render region in textImage
|
||||||
for y := 0; y < strHeight; y++ {
|
for y := 0; y < strHeight; y++ {
|
||||||
off := textImage.PixOffset(0, y)
|
off := textImage.PixOffset(0, y)
|
||||||
|
@ -270,8 +177,10 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// render the string into textImage
|
// render the string into textImage
|
||||||
prev, hasPrev = truetype.Index(0), false
|
curX := x
|
||||||
for _, rn := range str[strFrom:strTo] {
|
p := fixed.Point26_6{}
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rn := range str {
|
||||||
idx := fnt.Index(rn)
|
idx := fnt.Index(rn)
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
prev = 0
|
prev = 0
|
||||||
|
@ -301,9 +210,9 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
// render textImage to the screen
|
// render textImage to the screen
|
||||||
var pts [4]backendbase.Vec
|
var pts [4]backendbase.Vec
|
||||||
pts[0] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
|
pts[0] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
|
||||||
pts[1] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + fstrHeight + y})
|
pts[1] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + float64(strHeight) + y})
|
||||||
pts[2] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + fstrHeight + y})
|
pts[2] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + float64(strWidth) + x, float64(textOffset.Y)/scale + float64(strHeight) + y})
|
||||||
pts[3] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + fstrWidth + x, float64(textOffset.Y)/scale + y})
|
pts[3] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + float64(strWidth) + x, float64(textOffset.Y)/scale + y})
|
||||||
|
|
||||||
mask := textImage.SubImage(image.Rect(0, 0, strWidth, strHeight)).(*image.Alpha)
|
mask := textImage.SubImage(image.Rect(0, 0, strWidth, strHeight)).(*image.Alpha)
|
||||||
|
|
||||||
|
@ -313,6 +222,50 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
||||||
cv.b.FillImageMask(&stl, mask, pts)
|
cv.b.FillImageMask(&stl, mask, pts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cv *Canvas) fillText2(str string, x, y float64, fontSize fixed.Int26_6) {
|
||||||
|
if cv.state.font == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frc := cv.getFRContext(cv.state.font, fontSize)
|
||||||
|
fnt := cv.state.font.font
|
||||||
|
|
||||||
|
strWidth, strHeight, _, str := cv.measureTextRendering(str, &x, &y, frc, 1)
|
||||||
|
if strWidth <= 0 || strHeight <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := float64(fontSize) / float64(baseFontSize)
|
||||||
|
scaleMat := backendbase.MatScale(backendbase.Vec{scale, scale})
|
||||||
|
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rn := range str {
|
||||||
|
idx := fnt.Index(rn)
|
||||||
|
if idx == 0 {
|
||||||
|
idx = fnt.Index(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasPrev {
|
||||||
|
kern := fnt.Kern(fontSize, prev, idx)
|
||||||
|
if frc.hinting != font.HintingNone {
|
||||||
|
kern = (kern + 32) &^ 63
|
||||||
|
}
|
||||||
|
x += float64(kern) / 64
|
||||||
|
}
|
||||||
|
advance, _, err := frc.glyphMeasure(idx, fixed.Point26_6{})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
path := cv.runePath(rn)
|
||||||
|
tf := scaleMat.Mul(backendbase.MatTranslate(backendbase.Vec{x, y})).Mul(cv.state.transform)
|
||||||
|
cv.fillPath(path, tf)
|
||||||
|
|
||||||
|
x += float64(advance) / 64
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// StrokeText draws the given string at the given coordinates
|
// StrokeText draws the given string at the given coordinates
|
||||||
// using the currently set font and font height and using the
|
// using the currently set font and font height and using the
|
||||||
// current stroke style
|
// current stroke style
|
||||||
|
@ -324,8 +277,30 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
|
||||||
frc := cv.getFRContext(cv.state.font, cv.state.fontSize)
|
frc := cv.getFRContext(cv.state.font, cv.state.fontSize)
|
||||||
fnt := cv.state.font.font
|
fnt := cv.state.font.font
|
||||||
|
|
||||||
prevPath := cv.path
|
strWidth, strHeight, _, str := cv.measureTextRendering(str, &x, &y, frc, 1)
|
||||||
cv.BeginPath()
|
if strWidth <= 0 || strHeight <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate offsets
|
||||||
|
if cv.state.textAlign == Center {
|
||||||
|
x -= float64(strWidth) * 0.5
|
||||||
|
} else if cv.state.textAlign == Right || cv.state.textAlign == End {
|
||||||
|
x -= float64(strWidth)
|
||||||
|
}
|
||||||
|
metrics := cv.state.fontMetrics
|
||||||
|
switch cv.state.textBaseline {
|
||||||
|
case Alphabetic:
|
||||||
|
case Middle:
|
||||||
|
y += (-float64(metrics.Descent)/64 + float64(metrics.Height)*0.5/64)
|
||||||
|
case Top, Hanging:
|
||||||
|
y += (-float64(metrics.Descent)/64 + float64(metrics.Height)/64)
|
||||||
|
case Bottom, Ideographic:
|
||||||
|
y += -float64(metrics.Descent) / 64
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := float64(cv.state.fontSize) / float64(baseFontSize)
|
||||||
|
basetf := backendbase.MatScale(backendbase.Vec{scale, scale}).Mul(cv.state.transform)
|
||||||
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
prev, hasPrev := truetype.Index(0), false
|
||||||
for _, rn := range str {
|
for _, rn := range str {
|
||||||
|
@ -346,18 +321,197 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cv.runePath(rn, backendbase.Vec{x, y})
|
path := cv.runePath(rn)
|
||||||
|
tf := backendbase.MatTranslate(backendbase.Vec{x, y}).Mul(basetf)
|
||||||
|
cv.strokePath(path, tf, false)
|
||||||
|
|
||||||
x += float64(advance) / 64
|
x += float64(advance) / 64
|
||||||
}
|
}
|
||||||
|
|
||||||
cv.Stroke()
|
|
||||||
cv.path = prevPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) runePath(rn rune, pos backendbase.Vec) {
|
func (cv *Canvas) measureTextRendering(str string, x, y *float64, frc *frContext, scale float64) (int, int, image.Point, string) {
|
||||||
|
// measure rendered text size
|
||||||
|
var p fixed.Point26_6
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
var textOffset image.Point
|
||||||
|
var strWidth, strMaxY int
|
||||||
|
strMinY := math.MaxInt32
|
||||||
|
for i, rn := range str {
|
||||||
|
idx := frc.f.Index(rn)
|
||||||
|
if idx == 0 {
|
||||||
|
idx = frc.f.Index(' ')
|
||||||
|
}
|
||||||
|
advance, bounds, err := frc.glyphMeasure(idx, p)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var kern fixed.Int26_6
|
||||||
|
if hasPrev {
|
||||||
|
kern = frc.f.Kern(frc.fontSize, prev, idx)
|
||||||
|
if frc.hinting != font.HintingNone {
|
||||||
|
kern = (kern + 32) &^ 63
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
textOffset.X = bounds.Min.X
|
||||||
|
}
|
||||||
|
if bounds.Max.Y > strMaxY {
|
||||||
|
strMaxY = bounds.Max.Y
|
||||||
|
}
|
||||||
|
if bounds.Min.Y < strMinY {
|
||||||
|
strMinY = bounds.Min.Y
|
||||||
|
}
|
||||||
|
p.X += advance + kern
|
||||||
|
}
|
||||||
|
textOffset.Y = strMinY
|
||||||
|
strWidth = p.X.Ceil() - textOffset.X
|
||||||
|
strHeight := strMaxY - textOffset.Y
|
||||||
|
|
||||||
|
if strWidth <= 0 || strHeight <= 0 {
|
||||||
|
return 0, 0, image.Point{}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate offsets
|
||||||
|
if cv.state.textAlign == Center {
|
||||||
|
*x -= float64(strWidth) / scale * 0.5
|
||||||
|
} else if cv.state.textAlign == Right || cv.state.textAlign == End {
|
||||||
|
*x -= float64(strWidth) / scale
|
||||||
|
}
|
||||||
|
metrics := cv.state.fontMetrics
|
||||||
|
switch cv.state.textBaseline {
|
||||||
|
case Alphabetic:
|
||||||
|
case Middle:
|
||||||
|
*y += (-float64(metrics.Descent)/64 + float64(metrics.Height)*0.5/64) / scale
|
||||||
|
case Top, Hanging:
|
||||||
|
*y += (-float64(metrics.Descent)/64 + float64(metrics.Height)/64) / scale
|
||||||
|
case Bottom, Ideographic:
|
||||||
|
*y += -float64(metrics.Descent) / 64 / scale
|
||||||
|
}
|
||||||
|
|
||||||
|
// find out which characters are inside the visible area
|
||||||
|
p = fixed.Point26_6{}
|
||||||
|
prev, hasPrev = truetype.Index(0), false
|
||||||
|
var insideCount int
|
||||||
|
strFrom, strTo := 0, len(str)
|
||||||
|
curInside := false
|
||||||
|
curX := *x
|
||||||
|
for i, rn := range str {
|
||||||
|
idx := frc.f.Index(rn)
|
||||||
|
if idx == 0 {
|
||||||
|
idx = frc.f.Index(' ')
|
||||||
|
}
|
||||||
|
advance, bounds, err := frc.glyphMeasure(idx, p)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var kern fixed.Int26_6
|
||||||
|
if hasPrev {
|
||||||
|
kern = frc.f.Kern(frc.fontSize, prev, idx)
|
||||||
|
if frc.hinting != font.HintingNone {
|
||||||
|
kern = (kern + 32) &^ 63
|
||||||
|
}
|
||||||
|
curX += float64(kern) / 64 / scale
|
||||||
|
}
|
||||||
|
|
||||||
|
w, h := cv.b.Size()
|
||||||
|
fw, fh := float64(w), float64(h)
|
||||||
|
|
||||||
|
p0 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMinY)/scale + *y})
|
||||||
|
p1 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMaxY)/scale + *y})
|
||||||
|
p2 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(strMaxY)/scale + *y})
|
||||||
|
p3 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(strMinY)/scale + *y})
|
||||||
|
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
|
||||||
|
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
|
||||||
|
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
|
||||||
|
(p0[1] < fh || p1[1] < fh || p2[1] < fh || p3[1] < fh)
|
||||||
|
|
||||||
|
if inside {
|
||||||
|
insideCount++
|
||||||
|
}
|
||||||
|
if !curInside && inside {
|
||||||
|
curInside = true
|
||||||
|
strFrom = i
|
||||||
|
*x = curX
|
||||||
|
} else if curInside && !inside {
|
||||||
|
strTo = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p.X += advance + kern
|
||||||
|
curX += float64(advance) / 64 / scale
|
||||||
|
}
|
||||||
|
|
||||||
|
if strFrom == strTo || insideCount == 0 {
|
||||||
|
return 0, 0, image.Point{}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// if necessary, measure rendered text size again with the substring
|
||||||
|
if strFrom > 0 || strTo < len(str) {
|
||||||
|
str = str[strFrom:strTo]
|
||||||
|
p = fixed.Point26_6{}
|
||||||
|
prev, hasPrev = truetype.Index(0), false
|
||||||
|
textOffset = image.Point{}
|
||||||
|
strWidth, strMaxY = 0, 0
|
||||||
|
for i, rn := range str {
|
||||||
|
idx := frc.f.Index(rn)
|
||||||
|
if idx == 0 {
|
||||||
|
idx = frc.f.Index(' ')
|
||||||
|
}
|
||||||
|
advance, bounds, err := frc.glyphMeasure(idx, p)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var kern fixed.Int26_6
|
||||||
|
if hasPrev {
|
||||||
|
kern = frc.f.Kern(frc.fontSize, prev, idx)
|
||||||
|
if frc.hinting != font.HintingNone {
|
||||||
|
kern = (kern + 32) &^ 63
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
textOffset.X = bounds.Min.X
|
||||||
|
}
|
||||||
|
if bounds.Min.Y < textOffset.Y {
|
||||||
|
textOffset.Y = bounds.Min.Y
|
||||||
|
}
|
||||||
|
if bounds.Max.Y > strMaxY {
|
||||||
|
strMaxY = bounds.Max.Y
|
||||||
|
}
|
||||||
|
p.X += advance + kern
|
||||||
|
}
|
||||||
|
strWidth = p.X.Ceil() - textOffset.X
|
||||||
|
strHeight = strMaxY - textOffset.Y
|
||||||
|
|
||||||
|
if strWidth <= 0 || strHeight <= 0 {
|
||||||
|
return 0, 0, image.Point{}, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strWidth, strHeight, textOffset, str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *Canvas) runePath(rn rune) *Path2D {
|
||||||
|
idx := cv.state.font.font.Index(rn)
|
||||||
|
if idx == 0 {
|
||||||
|
idx = cv.state.font.font.Index(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
if cache, ok := cv.fontTriCache[cv.state.font]; ok {
|
||||||
|
if path, ok := cache.cache[idx]; ok {
|
||||||
|
cache.lastUsed = time.Now()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path := &Path2D{cv: cv, p: make([]pathPoint, 0, 50), standalone: true}
|
||||||
|
|
||||||
|
const scale = 1.0 / 64.0
|
||||||
|
|
||||||
gb := &truetype.GlyphBuf{}
|
gb := &truetype.GlyphBuf{}
|
||||||
gb.Load(cv.state.font.font, cv.state.fontSize, cv.state.font.font.Index(rn), font.HintingNone)
|
gb.Load(cv.state.font.font, baseFontSize, idx, font.HintingNone)
|
||||||
|
|
||||||
from := 0
|
from := 0
|
||||||
for _, to := range gb.Ends {
|
for _, to := range gb.Ends {
|
||||||
|
@ -388,14 +542,14 @@ func (cv *Canvas) runePath(rn rune, pos backendbase.Vec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p0, on0 := gb.Points[from], false
|
p0, on0 := gb.Points[from], false
|
||||||
cv.MoveTo(float64(p0.X)/64+pos[0], pos[1]-float64(p0.Y)/64)
|
path.MoveTo(float64(p0.X)*scale, -float64(p0.Y)*scale)
|
||||||
for _, p := range others {
|
for _, p := range others {
|
||||||
on := p.Flags&0x01 != 0
|
on := p.Flags&0x01 != 0
|
||||||
if on {
|
if on {
|
||||||
if on0 {
|
if on0 {
|
||||||
cv.LineTo(float64(p.X)/64+pos[0], pos[1]-float64(p.Y)/64)
|
path.LineTo(float64(p.X)*scale, -float64(p.Y)*scale)
|
||||||
} else {
|
} 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)
|
path.QuadraticCurveTo(float64(p0.X)*scale, -float64(p0.Y)*scale, float64(p.X)*scale, -float64(p.Y)*scale)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if on0 {
|
if on0 {
|
||||||
|
@ -405,22 +559,31 @@ func (cv *Canvas) runePath(rn rune, pos backendbase.Vec) {
|
||||||
X: (p0.X + p.X) / 2,
|
X: (p0.X + p.X) / 2,
|
||||||
Y: (p0.Y + p.Y) / 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)
|
path.QuadraticCurveTo(float64(p0.X)*scale, -float64(p0.Y)*scale, float64(mid.X)*scale, -float64(mid.Y)*scale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p0, on0 = p, on
|
p0, on0 = p, on
|
||||||
}
|
}
|
||||||
|
|
||||||
if on0 {
|
if on0 {
|
||||||
cv.LineTo(float64(start.X)/64+pos[0], pos[1]-float64(start.Y)/64)
|
path.LineTo(float64(start.X)*scale, -float64(start.Y)*scale)
|
||||||
} else {
|
} 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)
|
path.QuadraticCurveTo(float64(p0.X)*scale, -float64(p0.Y)*scale, float64(start.X)*scale, -float64(start.Y)*scale)
|
||||||
}
|
}
|
||||||
cv.ClosePath()
|
path.ClosePath()
|
||||||
cv.Stroke()
|
|
||||||
|
|
||||||
from = to
|
from = to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache, ok := cv.fontTriCache[cv.state.font]
|
||||||
|
if !ok {
|
||||||
|
cache = &fontTriCache{cache: make(map[truetype.Index]*Path2D, 1024)}
|
||||||
|
cv.fontTriCache[cv.state.font] = cache
|
||||||
|
}
|
||||||
|
cache.lastUsed = time.Now()
|
||||||
|
cache.cache[idx] = path
|
||||||
|
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextMetrics is the result of a MeasureText call
|
// TextMetrics is the result of a MeasureText call
|
||||||
|
|
Loading…
Reference in a new issue