vision/text.go

211 lines
4.7 KiB
Go

package vision
import (
"fmt"
"image"
"path/filepath"
"runtime"
"strings"
"sync"
"apigo.cc/go/file"
"github.com/flopp/go-findfont"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
var (
fontCache = make(map[string]*sfnt.Font)
fontLock sync.RWMutex
loaded = make(map[string]bool)
)
// 各操作系统默认字体文件列表
var defaultFontFiles = map[string]map[string][]string{
"windows": {
"serif": {"simsun.ttc", "times.ttf"},
"sans-serif": {"msyh.ttc", "arial.ttf"},
"monospace": {"consola.ttf", "simsun.ttc"},
},
"darwin": {
"serif": {"Songti.ttc", "Times New Roman.ttf"},
"sans-serif": {"Hiragino Sans GB.ttc", "PingFang.ttc", "Helvetica.ttf"},
"monospace": {"Menlo.ttc", "Courier New.ttf", "Hiragino Sans GB.ttc"},
},
"linux": {
"serif": {"dejavu/DejaVuSerif.ttf", "wqy-microhei.ttc", "noto/NotoSerifCJK-Regular.ttc"},
"sans-serif": {"dejavu/DejaVuSans.ttf", "wqy-microhei.ttc", "noto/NotoSansCJK-Regular.ttc"},
"monospace": {"dejavu/DejaVuSansMono.ttf", "wqy-microhei_mono.ttc", "droid/DroidSansMono.ttf"},
},
}
// LoadFonts 加载指定路径的字体文件
func LoadFonts(paths ...string) {
if len(paths) == 0 {
// 加载系统默认字体
if ffs, ok := defaultFontFiles[runtime.GOOS]; ok {
for _, list := range ffs {
paths = append(paths, list...)
}
}
}
buf := &sfnt.Buffer{}
for _, p := range paths {
fontLock.RLock()
isLoaded := loaded[p]
fontLock.RUnlock()
if isLoaded {
continue
}
fullPath := p
if !filepath.IsAbs(p) {
if f, err := findfont.Find(p); err == nil {
fullPath = f
}
}
data, err := file.ReadBytes(fullPath)
if err != nil {
continue
}
fontLock.Lock()
if strings.HasSuffix(strings.ToLower(fullPath), ".ttc") {
if collection, err := sfnt.ParseCollection(data); err == nil {
for i := 0; i < collection.NumFonts(); i++ {
if f, err := collection.Font(i); err == nil {
cacheFont(buf, f)
}
}
}
} else {
if f, err := sfnt.Parse(data); err == nil {
cacheFont(buf, f)
}
}
loaded[p] = true
fontLock.Unlock()
}
}
func cacheFont(buf *sfnt.Buffer, f *sfnt.Font) {
names := []sfnt.NameID{sfnt.NameIDFull, sfnt.NameIDFamily, sfnt.NameIDTypographicFamily}
for _, id := range names {
if name, err := f.Name(buf, id); err == nil && name != "" {
fontCache[strings.TrimSpace(name)] = f
}
}
}
// SetFont 设置画布字体
func (c *Canvas) SetFont(size float64, names ...string) error {
if len(names) == 0 {
LoadFonts()
}
cf := &CompositeFace{Faces: make([]font.Face, 0), Names: names}
fontLock.RLock()
for _, name := range names {
if f, ok := fontCache[name]; ok {
if face, err := opentype.NewFace(f, &opentype.FaceOptions{Size: size, DPI: 72}); err == nil {
cf.Faces = append(cf.Faces, face)
}
}
}
fontLock.RUnlock()
if len(cf.Faces) > 0 {
c.lastFont = cf
c.dc.SetFontFace(cf)
return nil
}
return fmt.Errorf("no font found for: %v", names)
}
// TextOption 定义文本绘制选项
type TextOption struct {
Width float64
Height float64
LineHeight float64
Align string // left, center, right
VAlign string // top, middle, bottom
Color string
BgColor string
BorderColor string
BorderWidth float64
Padding [4]float64 // top, right, bottom, left
}
// DrawText 在画布上绘制文本
func (c *Canvas) DrawText(x, y float64, text string, opt *TextOption) {
if opt == nil {
opt = &TextOption{}
}
if opt.Color != "" {
c.dc.SetColor(ParseColor(opt.Color))
}
// 基础绘制逻辑
c.dc.DrawStringAnchored(text, x, y, 0, 0)
}
// CompositeFace 支持多字体回退的字体接口实现
type CompositeFace struct {
Names []string
Faces []font.Face
}
func (c *CompositeFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
for _, f := range c.Faces {
if dr, mask, maskp, advance, ok = f.Glyph(dot, r); ok {
return
}
}
return
}
func (c *CompositeFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
for _, f := range c.Faces {
if bounds, advance, ok = f.GlyphBounds(r); ok {
return
}
}
return
}
func (c *CompositeFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
for _, f := range c.Faces {
if advance, ok = f.GlyphAdvance(r); ok {
return
}
}
return
}
func (c *CompositeFace) Kern(r0, r1 rune) fixed.Int26_6 {
for _, f := range c.Faces {
if k := f.Kern(r0, r1); k != 0 {
return k
}
}
return 0
}
func (c *CompositeFace) Metrics() font.Metrics {
if len(c.Faces) == 0 {
return font.Metrics{}
}
return c.Faces[0].Metrics()
}
func (c *CompositeFace) Close() error {
for _, f := range c.Faces {
f.Close()
}
return nil
}