Files
ofdgo/ofdgo_text.go
T
xiaoqidun fa3260c108
build.yaml / build (push) Successful in 34s
refactor(重构代码): 提升代码质量
2026-07-09 10:33:21 +08:00

301 lines
8.6 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 (
"math"
"strconv"
"strings"
"github.com/tdewolff/canvas"
"github.com/tdewolff/font"
)
// textGlyph 绘制字形
type textGlyph struct {
Text string
GlyphID int
}
// textGlyphTransform 字符到字形变换
type textGlyphTransform struct {
CodeCount int
Glyphs []textGlyph
}
// textObjectFontID 获取文本对象字体ID
// 入参: text 文本对象
// 返回: string 字体ID
func (r *Renderer) textObjectFontID(text TextObject) string {
fontID := text.Font
if fontID == "" && text.DrawParam != "" {
if dp := r.getDrawParam(text.DrawParam, nil); dp != nil && dp.Font != "" {
fontID = dp.Font
}
}
return fontID
}
// textObjectGlyphTransforms 获取文本对象的字形变换
// 入参: fontID 字体ID, text 文本对象
// 返回: map[int]textGlyphTransform 文本位置到字形变换的映射
func (r *Renderer) textObjectGlyphTransforms(fontID string, text TextObject) map[int]textGlyphTransform {
if fontID == "" || len(text.CGTransform) == 0 {
return nil
}
result := make(map[int]textGlyphTransform)
for _, transform := range text.CGTransform {
ids := parseInts(transform.Glyphs)
glyphCount := len(ids)
if transform.GlyphCount > 0 && transform.GlyphCount < glyphCount {
glyphCount = transform.GlyphCount
}
if glyphCount == 0 {
continue
}
codeCount := transform.CodeCount
if codeCount <= 0 {
codeCount = 1
}
glyphs := make([]textGlyph, 0, glyphCount)
for i := 0; i < glyphCount; i++ {
glyphs = append(glyphs, r.textGlyphFromID(fontID, ids[i]))
}
result[transform.CodePosition] = textGlyphTransform{
CodeCount: codeCount,
Glyphs: glyphs,
}
}
if len(result) == 0 {
return nil
}
return result
}
// textCodeGlyphs 获取文本编码对应的绘制字形
// 入参: runes 文本字符, transforms 字形变换, codeOffset 文本编码偏移
// 返回: []textGlyph 绘制字形列表
func textCodeGlyphs(runes []rune, transforms map[int]textGlyphTransform, codeOffset int) []textGlyph {
if len(transforms) == 0 {
return textRuneGlyphs(runes)
}
glyphs := make([]textGlyph, 0, len(runes))
for i := 0; i < len(runes); {
if transform, ok := transforms[codeOffset+i]; ok && i+transform.CodeCount <= len(runes) {
glyphs = append(glyphs, transform.Glyphs...)
i += transform.CodeCount
continue
}
glyphs = append(glyphs, textGlyph{Text: string(runes[i]), GlyphID: -1})
i++
}
return glyphs
}
// textRuneGlyphs 转换文本字符为绘制字形
// 入参: runes 文本字符
// 返回: []textGlyph 绘制字形列表
func textRuneGlyphs(runes []rune) []textGlyph {
glyphs := make([]textGlyph, 0, len(runes))
for _, r := range runes {
glyphs = append(glyphs, textGlyph{Text: string(r), GlyphID: -1})
}
return glyphs
}
// textGlyphFromID 获取字形ID对应的绘制字形
// 入参: fontID 字体ID, glyphID 字形ID或CID
// 返回: textGlyph 绘制字形
func (r *Renderer) textGlyphFromID(fontID string, glyphID int) textGlyph {
if mapped, ok := r.fontGlyphRune(fontID, glyphID); ok {
return textGlyph{Text: string(mapped), GlyphID: -1}
}
return textGlyph{GlyphID: glyphID}
}
// 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 绘制字形
// 返回: *canvas.Path 字形路径, float64 字形宽度
func textGlyphPath(face *canvas.FontFace, glyph textGlyph) (*canvas.Path, float64) {
if glyph.GlyphID < 0 || glyph.GlyphID > 0xFFFF {
return face.ToPath(glyph.Text)
}
p := &canvas.Path{}
glyphID := uint16(glyph.GlyphID)
_ = face.Font.GlyphPath(p, glyphID, face.PPEM(canvas.DefaultResolution), 0, 0, face.MmPerEm, font.NoHinting)
if face.FauxBold != 0 {
d := face.FauxBold * face.Size
if face.Font.IsTrueType {
d = -d
}
origFastStroke := canvas.FastStroke
canvas.FastStroke = true
p = p.Offset(d, canvas.Tolerance)
canvas.FastStroke = origFastStroke
}
if face.FauxItalic != 0 {
p = p.Transform(canvas.Identity.Shear(face.FauxItalic, 0))
}
return p, face.MmPerEm * float64(face.Font.GlyphAdvance(glyphID))
}
// hasTextMatrix 判断文本是否需要应用字形变换
// 入参: ctm 变换矩阵
// 返回: bool 是否需要变换
func hasTextMatrix(ctm Matrix) bool {
const eps = 1e-9
return math.Abs(ctm.a-1) > eps || math.Abs(ctm.b) > eps || math.Abs(ctm.c) > eps || math.Abs(ctm.d-1) > eps
}
// textMatrix 获取文本字形变换矩阵
// 入参: ctm OFD变换矩阵
// 返回: canvas.Matrix 画布变换矩阵
func textMatrix(ctm Matrix) canvas.Matrix {
return canvas.Matrix{
{ctm.a, -ctm.c, 0},
{-ctm.b, ctm.d, 0},
}
}
// fontGlyphRune 获取字形ID对应的包装字体字符
// 入参: fontID 字体ID, glyphID 字形ID或CID
// 返回: rune 包装字体字符, bool 是否存在
func (r *Renderer) fontGlyphRune(fontID string, glyphID int) (rune, bool) {
if glyphID < 0 || glyphID > 0xFFFF {
return 0, false
}
id := uint16(glyphID)
if r.FontCIDMap != nil {
if mapping := r.FontCIDMap[fontID]; mapping != nil {
if mapped, ok := mapping[id]; ok {
return mapped, true
}
}
}
if r.FontGIDMap != nil {
if mapping := r.FontGIDMap[fontID]; mapping != nil {
if mapped, ok := mapping[id]; ok {
return mapped, true
}
}
}
return 0, false
}
// parseIndexRunes 解析索引字形
// 入参: indexStr 索引字符串, fontID 字体ID
// 返回: []rune 字形列表
func (r *Renderer) parseIndexRunes(indexStr string, fontID string) []rune {
var gids []int
parts := strings.Fields(indexStr)
for _, p := range parts {
if strings.Contains(p, "-") {
sub := strings.Split(p, "-")
if len(sub) == 2 {
start, _ := strconv.Atoi(sub[0])
end, _ := strconv.Atoi(sub[1])
for k := start; k <= end; k++ {
gids = append(gids, k)
}
}
} else {
val, _ := strconv.Atoi(p)
gids = append(gids, val)
}
}
var res []rune
for _, gid := range gids {
if rVal, ok := r.fontGlyphRune(fontID, gid); ok {
res = append(res, rVal)
continue
}
res = append(res, rune(gid))
}
return res
}
// textGlyphAdvanceLimit 获取显式字形推进宽度
// 入参: dxs X方向偏移, dys Y方向偏移, xs X坐标列表, index 字形索引, count 字形数量, currentX 当前X坐标
// 返回: float64 推进宽度
func textGlyphAdvanceLimit(dxs, dys, xs []float64, index int, count int, currentX float64) float64 {
if index+1 >= count || len(dys) > 0 {
return 0
}
if index+1 < len(xs) {
if advance := xs[index+1] - currentX; advance > 0 {
return advance
}
}
if advance, ok := textDelta(dxs, index); ok && advance > 0 {
return advance
}
return 0
}
// textCodePositioned 判断文本编码是否带显式定位
// 入参: textCode 文本编码, xs X坐标列表, ys Y坐标列表
// 返回: bool 是否带显式定位
func textCodePositioned(textCode TextCode, xs, ys []float64) bool {
return strings.TrimSpace(textCode.DeltaX) != "" ||
strings.TrimSpace(textCode.DeltaY) != "" ||
len(xs) > 1 ||
len(ys) > 1
}
// textDelta 获取文本偏移量
// 入参: deltas 偏移量数组, index 偏移量索引
// 返回: float64 偏移量, bool 是否存在
func textDelta(deltas []float64, index int) (float64, bool) {
if len(deltas) == 0 {
return 0, false
}
if index < len(deltas) {
return deltas[index], true
}
return deltas[len(deltas)-1], true
}
// textCodeRunes 获取文本编码字符
// 入参: value 文本编码内容
// 返回: []rune 文本字符
func textCodeRunes(value string) []rune {
if strings.ContainsAny(value, "\r\n") {
value = strings.TrimSpace(value)
}
return []rune(value)
}
// GetDeltaX 获取X轴偏移量数组
// 返回: []float64 偏移量数组
func (tc *TextCode) GetDeltaX() []float64 {
return parseFloats(tc.DeltaX)
}
// GetDeltaY 获取Y轴偏移量数组
// 返回: []float64 偏移量数组
func (tc *TextCode) GetDeltaY() []float64 {
return parseFloats(tc.DeltaY)
}