diff --git a/ofdgo_font_sfnt.go b/ofdgo_font_sfnt.go index 58f4903..ad164c0 100644 --- a/ofdgo_font_sfnt.go +++ b/ofdgo_font_sfnt.go @@ -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 { diff --git a/ofdgo_renderer_glyph_canvas.go b/ofdgo_renderer_glyph_canvas.go index 546ae4f..b3c8e4d 100644 --- a/ofdgo_renderer_glyph_canvas.go +++ b/ofdgo_renderer_glyph_canvas.go @@ -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)) - if err != nil { - r.renderError = err - return &canvas.Path{}, 0 + 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))) } - outlines = &sfntOutliner{font: metrics} r.canvasState().fontOutlines[face.Font] = outlines } path, width, err := textGlyphPath(face, glyph, outlines) diff --git a/ofdgo_renderer_state_canvas.go b/ofdgo_renderer_state_canvas.go index ab62bc9..bfa67ab 100644 --- a/ofdgo_renderer_state_canvas.go +++ b/ofdgo_renderer_state_canvas.go @@ -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 {