mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:31:12 +08:00
144 lines
4.5 KiB
Go
144 lines
4.5 KiB
Go
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ofdgo
|
|
|
|
import (
|
|
"github.com/tdewolff/canvas"
|
|
canvastext "github.com/tdewolff/canvas/text"
|
|
)
|
|
|
|
// textGlyphPathCacheKey 字形路径缓存键
|
|
type textGlyphPathCacheKey struct {
|
|
font *canvas.Font
|
|
size float64
|
|
fauxBold float64
|
|
fauxItalic float64
|
|
xOffset int32
|
|
yOffset int32
|
|
language string
|
|
script canvastext.Script
|
|
direction canvastext.Direction
|
|
glyph textGlyph
|
|
}
|
|
|
|
// textGlyphPathCacheValue 字形路径缓存值
|
|
type textGlyphPathCacheValue struct {
|
|
path *canvas.Path
|
|
width float64
|
|
}
|
|
|
|
// textGlyphWidth 获取绘制字形宽度
|
|
// 入参: face 字体, glyph 绘制字形
|
|
// 返回: float64 字形宽度
|
|
func textGlyphWidth(face *canvas.FontFace, glyph textGlyph) float64 {
|
|
if glyph.GlyphID >= 0 && glyph.GlyphID <= 0xFFFF {
|
|
return face.MmPerEm * float64(face.Font.GlyphAdvance(uint16(glyph.GlyphID)))
|
|
}
|
|
return face.TextWidth(glyph.Text)
|
|
}
|
|
|
|
// textGlyphPath 获取绘制字形路径
|
|
// 入参: face 字体, glyph 绘制字形, outlines 轮廓解析器
|
|
// 返回: *canvas.Path 字形路径, float64 字形宽度, error 字形解析错误
|
|
func textGlyphPath(face *canvas.FontFace, glyph textGlyph, outlines *sfntOutliner) (*canvas.Path, float64, error) {
|
|
p := &canvas.Path{}
|
|
width := 0.0
|
|
if glyph.GlyphID < 0 || glyph.GlyphID > 0xFFFF {
|
|
x, y := face.XOffset, face.YOffset
|
|
for _, shaped := range face.Glyphs(glyph.Text) {
|
|
part := &canvas.Path{}
|
|
if err := outlines.path(part, shaped.ID, face.MmPerEm); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
p = p.Append(part.Translate(face.MmPerEm*float64(x+shaped.XOffset), face.MmPerEm*float64(y+shaped.YOffset)))
|
|
x, y = x+shaped.XAdvance, y+shaped.YAdvance
|
|
}
|
|
width = face.MmPerEm * float64(x)
|
|
} else {
|
|
glyphID := uint16(glyph.GlyphID)
|
|
if err := outlines.path(p, glyphID, face.MmPerEm); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
width = face.MmPerEm * float64(face.Font.GlyphAdvance(glyphID))
|
|
}
|
|
if face.FauxBold != 0 {
|
|
p = p.Offset(face.FauxBold*face.Size, canvas.Tolerance)
|
|
}
|
|
if face.FauxItalic != 0 {
|
|
p = p.Transform(canvas.Identity.Shear(face.FauxItalic, 0))
|
|
}
|
|
return p, width, nil
|
|
}
|
|
|
|
// cachedTextGlyphPath 获取缓存的字形路径
|
|
// 入参: face 字体, glyph 绘制字形
|
|
// 返回: *canvas.Path 字形路径, float64 字形宽度
|
|
func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (*canvas.Path, float64) {
|
|
key := textGlyphPathCacheKey{
|
|
font: face.Font,
|
|
size: face.Size,
|
|
fauxBold: face.FauxBold,
|
|
fauxItalic: face.FauxItalic,
|
|
xOffset: face.XOffset,
|
|
yOffset: face.YOffset,
|
|
language: face.Language,
|
|
script: face.Script,
|
|
direction: face.Direction,
|
|
glyph: glyph,
|
|
}
|
|
if cached, ok := r.canvasState().textGlyphPathCache.get(key); ok {
|
|
return cached.path, cached.width
|
|
}
|
|
outlines := r.canvasState().fontOutlines[face.Font]
|
|
if outlines == nil {
|
|
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)
|
|
if err != nil {
|
|
r.renderError = err
|
|
return &canvas.Path{}, 0
|
|
}
|
|
r.canvasState().textGlyphPathCache.put(key, textGlyphPathCacheValue{path: path, width: width}, 256+len(path.Data())*8+len(glyph.Text)+len(face.Language))
|
|
return path, width
|
|
}
|
|
|
|
// textMatrix 获取文本字形变换矩阵
|
|
// 入参: ctm OFD变换矩阵
|
|
// 返回: canvas.Matrix 画布变换矩阵
|
|
func textMatrix(ctm Matrix) canvas.Matrix {
|
|
return canvas.Matrix{
|
|
{ctm.a, -ctm.c, 0},
|
|
{-ctm.b, ctm.d, 0},
|
|
}
|
|
}
|