feat(字体渲染): 优化字体解析与渲染缓存
build.yaml / build (push) Successful in 49s

This commit is contained in:
xiaoqidun committed 2026-10-11 18:57:10 +08:00
1 parent 2c06e5ff69
commit 478c18cb1f
3 files changed
+39 -3

No files matched your search

+11 -2
View File
@@ -17,6 +17,7 @@ package ofdgo
import (
"encoding/binary"
"fmt"
"maps"
"sort"
"sync"
@@ -47,11 +48,18 @@ func parseSFNTFont(data []byte) (*sfntFont, error) {
if err != nil {
return nil, err
}
return parseSFNTTables(table)
}
// parseSFNTTables 校验字体表并解析度量和字符映射,借用表数据且不修改其内容
// 入参: table 字体表
// 返回: *sfntFont 字体, error 格式错误
func parseSFNTTables(table map[string][]byte) (*sfntFont, error) {
head, maxp, hhea := table["head"], table["maxp"], table["hhea"]
if len(head) != 54 || len(maxp) < 6 || len(hhea) < 36 {
return nil, fmt.Errorf("missing or truncated font metrics")
}
f := &sfntFont{Tables: table, IsTrueType: table["glyf"] != nil, IsCFF: table["CFF "] != nil}
f := &sfntFont{Tables: maps.Clone(table), IsTrueType: table["glyf"] != nil, IsCFF: table["CFF "] != nil}
f.units, f.count = binary.BigEndian.Uint16(head[18:]), binary.BigEndian.Uint16(maxp[4:])
f.horizontal = binary.BigEndian.Uint16(hhea[34:])
if f.units < 16 || f.units > 16384 || f.count == 0 || f.IsTrueType == f.IsCFF || !validSFNTMetrics(table["hmtx"], f.count, f.horizontal) {
@@ -72,10 +80,11 @@ func parseSFNTFont(data []byte) (*sfntFont, error) {
return nil, fmt.Errorf("invalid glyph location table")
}
}
f.cmaps, err = readSFNTCmaps(table["cmap"])
cmaps, err := readSFNTCmaps(table["cmap"])
if err != nil {
return nil, err
}
f.cmaps = cmaps
for _, cmap := range f.cmaps {
for _, glyph := range cmap.mapping {
if glyph >= f.count {
+13 -1
View File
@@ -103,12 +103,24 @@ func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (
}
outlines := r.canvasState().fontOutlines[face.Font]
if outlines == nil {
metrics, err := parseSFNTFont(fontSFNTData(face.Font.Tables))
cache := r.canvasFontOutlines()
outlines, _ = cache.get(face.Font)
if outlines == nil {
metrics, err := parseSFNTTables(face.Font.Tables)
if err != nil {
r.renderError = err
return &canvas.Path{}, 0
}
outlines = &sfntOutliner{font: metrics}
cost := uint64(512)
for _, data := range metrics.Tables {
cost += uint64(len(data)) * 8
}
for _, cmap := range metrics.cmaps {
cost += uint64(len(cmap.mapping)) * 48
}
cache.put(face.Font, outlines, int(min(cost, uint64(cache.limit)+1)))
}
r.canvasState().fontOutlines[face.Font] = outlines
}
path, width, err := textGlyphPath(face, glyph, outlines)
+15
View File
@@ -36,6 +36,9 @@ type canvasPathCacheKey struct{}
// canvasFillCacheKey 区分按填充规则规范化的共享路径缓存
type canvasFillCacheKey struct{}
// canvasOutlineCacheKey 区分子渲染器共享的字体轮廓解析缓存
type canvasOutlineCacheKey struct{}
// canvasFillKey 保存完整几何摘要及填充规则
type canvasFillKey struct {
digest [32]byte
@@ -114,6 +117,18 @@ func (r *Renderer) canvasFontFamilies() *renderCache[canvasFontKey, *canvas.Font
return cache
}
// canvasFontOutlines 获取子渲染器共享的字体轮廓解析缓存,限制字体及解析数据保留量
// 返回: *renderCache[*canvas.Font, *sfntOutliner] 字体轮廓缓存
func (r *Renderer) canvasFontOutlines() *renderCache[*canvas.Font, *sfntOutliner] {
key := canvasOutlineCacheKey{}
if state, ok := r.sharedBackendStates[key]; ok {
return state.(*renderCache[*canvas.Font, *sfntOutliner])
}
cache := &renderCache[*canvas.Font, *sfntOutliner]{limit: 32 << 20}
r.sharedBackendStates[key] = cache
return cache
}
// canvasState 获取当前渲染器的Canvas私有缓存,按需创建且不向其他后端暴露
// 返回: *canvasBackendState Canvas缓存
func (r *Renderer) canvasState() *canvasBackendState {