181 lines
3.6 KiB
Go
181 lines
3.6 KiB
Go
package font
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"zworld/engine/renderapi/color"
|
|
"zworld/engine/renderapi/image"
|
|
"zworld/engine/util"
|
|
"zworld/plugins/math"
|
|
"zworld/plugins/math/vec2"
|
|
|
|
fontlib "golang.org/x/image/font"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
var ErrNoGlyph = errors.New("no glyph for rune")
|
|
|
|
type T interface {
|
|
Name() string
|
|
Glyph(rune) (*Glyph, error)
|
|
Kern(rune, rune) float32
|
|
Measure(string, Args) vec2.T
|
|
Size() float32
|
|
}
|
|
|
|
type Args struct {
|
|
Color color.T
|
|
LineHeight float32
|
|
}
|
|
|
|
type font struct {
|
|
size float32
|
|
scale float32
|
|
name string
|
|
face fontlib.Face
|
|
drawer *fontlib.Drawer
|
|
mutex *sync.Mutex
|
|
glyphs *util.SyncMap[rune, *Glyph]
|
|
kern *util.SyncMap[runepair, float32]
|
|
}
|
|
|
|
type runepair struct {
|
|
a, b rune
|
|
}
|
|
|
|
func (f *font) Name() string { return f.name }
|
|
func (f *font) Size() float32 { return f.size }
|
|
|
|
func (f *font) Glyph(r rune) (*Glyph, error) {
|
|
if cached, exists := f.glyphs.Load(r); exists {
|
|
return cached, nil
|
|
}
|
|
|
|
// grab the font lock
|
|
f.mutex.Lock()
|
|
defer f.mutex.Unlock()
|
|
|
|
bounds, advance, ok := f.face.GlyphBounds(r)
|
|
if !ok {
|
|
return nil, ErrNoGlyph
|
|
}
|
|
|
|
// calculate bearing
|
|
bearing := vec2.New(FixToFloat(bounds.Min.X), FixToFloat(bounds.Min.Y))
|
|
|
|
// texture size
|
|
size := vec2.New(FixToFloat(bounds.Max.X), FixToFloat(bounds.Max.Y)).Sub(bearing)
|
|
|
|
// glyph texture
|
|
_, mask, offset, _, _ := f.face.Glyph(fixed.Point26_6{X: 0, Y: 0}, r)
|
|
width, height := int(size.X), int(size.Y)
|
|
|
|
img := &image.Data{
|
|
Width: width,
|
|
Height: height,
|
|
Buffer: make([]byte, 4*width*height),
|
|
Format: image.FormatRGBA8Unorm,
|
|
}
|
|
i := 0
|
|
for y := 0; y < height; y++ {
|
|
for x := 0; x < width; x++ {
|
|
// grab alpha value as 16-bit integer
|
|
_, _, _, alpha := mask.At(offset.X+x, offset.Y+y).RGBA()
|
|
img.Buffer[i+0] = 0xFF // red
|
|
img.Buffer[i+1] = 0xFF // green
|
|
img.Buffer[i+2] = 0xFF // blue
|
|
img.Buffer[i+3] = uint8(alpha >> 8)
|
|
i += 4
|
|
}
|
|
}
|
|
|
|
scaleFactor := 1 / f.scale
|
|
glyph := &Glyph{
|
|
key: fmt.Sprintf("glyph:%s:%dx%.2f:%c", f.Name(), int(f.size), f.scale, r),
|
|
Size: size.Scaled(scaleFactor),
|
|
Bearing: bearing.Scaled(scaleFactor),
|
|
Advance: FixToFloat(advance) * scaleFactor,
|
|
Mask: img,
|
|
}
|
|
f.glyphs.Store(r, glyph)
|
|
|
|
return glyph, nil
|
|
}
|
|
|
|
func (f *font) Kern(a, b rune) float32 {
|
|
pair := runepair{a, b}
|
|
if k, exists := f.kern.Load(pair); exists {
|
|
return k
|
|
}
|
|
|
|
f.mutex.Lock()
|
|
defer f.mutex.Unlock()
|
|
k := FixToFloat(f.face.Kern(a, b))
|
|
f.kern.Store(pair, k)
|
|
return k
|
|
}
|
|
|
|
func (f *font) MeasureLine(text string) vec2.T {
|
|
size := vec2.Zero
|
|
var prev rune
|
|
for i, r := range text {
|
|
g, err := f.Glyph(r)
|
|
if err != nil {
|
|
panic("no such glyph")
|
|
}
|
|
if i > 0 {
|
|
size.X += f.Kern(prev, r)
|
|
}
|
|
if i < len(text)-1 {
|
|
size.X += g.Advance
|
|
} else {
|
|
size.X += g.Bearing.X + g.Size.X
|
|
}
|
|
size.Y = math.Max(size.Y, g.Size.Y)
|
|
prev = r
|
|
}
|
|
return size.Scaled(f.scale)
|
|
}
|
|
|
|
func (f *font) Measure(text string, args Args) vec2.T {
|
|
if args.LineHeight == 0 {
|
|
args.LineHeight = 1
|
|
}
|
|
|
|
lines := 1
|
|
width := float32(0)
|
|
s := 0
|
|
for i, c := range text {
|
|
if c == '\n' {
|
|
line := text[s:i]
|
|
// w := f.drawer.MeasureString(line).Ceil()
|
|
w := f.MeasureLine(line)
|
|
if w.X > width {
|
|
width = w.X
|
|
}
|
|
s = i + 1
|
|
lines++
|
|
}
|
|
}
|
|
r := len(text)
|
|
if s < r {
|
|
line := text[s:]
|
|
// w := f.drawer.MeasureString(line).Ceil()
|
|
w := f.MeasureLine(line)
|
|
if w.X > width {
|
|
width = w.X
|
|
}
|
|
}
|
|
|
|
lineHeight := int(math.Ceil(f.size * f.scale * args.LineHeight))
|
|
height := lineHeight*lines + (lineHeight/2)*(lines-1)
|
|
return vec2.New(width, float32(height)).Scaled(1 / f.scale).Ceil()
|
|
}
|
|
|
|
func FixToFloat(v fixed.Int26_6) float32 {
|
|
const scalar = 1 / float32(1<<6)
|
|
return float32(v) * scalar
|
|
}
|