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 }