feat(字体渲染): 支持渐变字体渲染

This commit is contained in:
2026-06-25 14:38:55 +08:00
parent 1ac0fe6ca4
commit f00f320429
5 changed files with 108 additions and 11 deletions
+12 -5
View File
@@ -803,10 +803,9 @@ func getCmapFromCFF(data []byte, numGlyphs int) map[rune]uint16 {
if gid == 0 { if gid == 0 {
continue continue
} }
mapping[packedGlyphRune(uint16(gid))] = uint16(gid)
if r, ok := adobeGB1CIDToUnicode(cid); ok { if r, ok := adobeGB1CIDToUnicode(cid); ok {
mapping[r] = uint16(gid) mapping[r] = uint16(gid)
} else {
mapping[0xE000+rune(gid)] = uint16(gid)
} }
} }
return mapping return mapping
@@ -816,7 +815,7 @@ func getCmapFromCFF(data []byte, numGlyphs int) map[rune]uint16 {
if gid == 0 { if gid == 0 {
continue continue
} }
mapping[0xE000+rune(gid)] = uint16(gid) mapping[packedGlyphRune(uint16(gid))] = uint16(gid)
} }
return mapping return mapping
} }
@@ -838,8 +837,9 @@ func getCmapFromCFF(data []byte, numGlyphs int) map[rune]uint16 {
r = getUnicodeFromName(name) r = getUnicodeFromName(name)
} }
if r == 0 { if r == 0 {
r = 0xE000 + rune(gid) r = packedGlyphRune(uint16(gid))
} }
mapping[packedGlyphRune(uint16(gid))] = uint16(gid)
mapping[r] = uint16(gid) mapping[r] = uint16(gid)
} }
return mapping return mapping
@@ -867,7 +867,14 @@ func getCFFCIDRuneMap(data []byte) map[uint16]rune {
} }
gidRunes := make(map[uint16]rune) gidRunes := make(map[uint16]rune)
for run, gid := range mapping { for run, gid := range mapping {
gidRunes[gid] = run if run != packedGlyphRune(gid) {
gidRunes[gid] = run
}
}
for run, gid := range mapping {
if _, ok := gidRunes[gid]; !ok {
gidRunes[gid] = run
}
} }
result := make(map[uint16]rune) result := make(map[uint16]rune)
for gid, cid := range sids { for gid, cid := range sids {
+1 -1
View File
@@ -143,7 +143,7 @@ func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uin
if len(mapping) == 0 { if len(mapping) == 0 {
mapping = make(map[rune]uint16) mapping = make(map[rune]uint16)
for i := uint16(0); i < numGlyphs; i++ { for i := uint16(0); i < numGlyphs; i++ {
mapping[rune(i)] = i mapping[packedGlyphRune(i)] = i
} }
} }
newTables["cmap"] = buildCmapTable(numGlyphs, mapping) newTables["cmap"] = buildCmapTable(numGlyphs, mapping)
+7
View File
@@ -27,6 +27,13 @@ func align4(n uint32) uint32 {
return (n + 3) & ^uint32(3) return (n + 3) & ^uint32(3)
} }
// packedGlyphRune 获取包装字体字符
// 入参: gid 字形ID
// 返回: rune 包装字体字符
func packedGlyphRune(gid uint16) rune {
return 0xF0000 + rune(gid)
}
// calcTableChecksum 计算字体表校验和 // calcTableChecksum 计算字体表校验和
// 入参: data 表数据 // 入参: data 表数据
// 返回: uint32 校验和 // 返回: uint32 校验和
+29 -5
View File
@@ -602,11 +602,20 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
if fillColor == nil { if fillColor == nil {
fillColor = canvas.Black fillColor = canvas.Black
} }
var fillPaint any = fillColor
var fillColorNode *FillColor
if dp != nil && dp.FillColor != nil { if dp != nil && dp.FillColor != nil {
fillColor = parseFillColor(dp.FillColor) fillColor = parseFillColor(dp.FillColor)
fillColorNode = dp.FillColor
fillPaint = parseFillPaint(dp.FillColor, bx, by, pageH, 0, 0)
} }
if obj.FillColor != nil { if obj.FillColor != nil {
fillColor = parseFillColor(obj.FillColor) fillColor = parseFillColor(obj.FillColor)
fillColorNode = obj.FillColor
fillPaint = parseFillPaint(obj.FillColor, bx, by, pageH, 0, 0)
}
if fillPaint == nil {
fillPaint = fillColor
} }
fontStyle := canvas.FontRegular fontStyle := canvas.FontRegular
weight := obj.Weight weight := obj.Weight
@@ -638,7 +647,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
if ff == nil { if ff == nil {
return return
} }
face := ff.Face(sizePt, fillColor, fontStyle, canvas.FontNormal) face := ff.Face(sizePt, fillPaint, fontStyle, canvas.FontNormal)
glyphRunes := r.textObjectGlyphRunes(fontID, obj) glyphRunes := r.textObjectGlyphRunes(fontID, obj)
codePos := 0 codePos := 0
for _, tc := range obj.TextCode { for _, tc := range obj.TextCode {
@@ -683,14 +692,22 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
tx, ty := ctm.Transform(cx, cy) tx, ty := ctm.Transform(cx, cy)
canvasX, canvasY := tx+bx, pageH-(ty+by) canvasX, canvasY := tx+bx, pageH-(ty+by)
textWidth := face.TextWidth(str) textWidth := face.TextWidth(str)
if fillColor != nil { glyphFillPaint := fillPaint
ctx.SetFillColor(fillColor) if fillColorNode != nil && fillColorNode.AxialShd != nil {
glyphFillPaint = parseFillPaint(fillColorNode, bx, by, pageH, canvasX, canvasY)
}
if glyphFillPaint != nil {
ctx.SetFill(glyphFillPaint)
if embeddedFont { if embeddedFont {
path, width := face.ToPath(str) path, width := face.ToPath(str)
textWidth = width textWidth = width
ctx.DrawPath(canvasX, canvasY, path) ctx.DrawPath(canvasX, canvasY, path)
} else { } else {
text := canvas.NewTextLine(face, str, canvas.Left) textFace := face
if fillColorNode != nil && fillColorNode.AxialShd != nil {
textFace = ff.Face(sizePt, glyphFillPaint, fontStyle, canvas.FontNormal)
}
text := canvas.NewTextLine(textFace, str, canvas.Left)
ctx.DrawText(canvasX, canvasY, text) ctx.DrawText(canvasX, canvasY, text)
} }
} }
@@ -738,7 +755,14 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
} }
inv := make(map[uint16]rune) inv := make(map[uint16]rune)
for k, v := range mapping { for k, v := range mapping {
inv[v] = k if k == packedGlyphRune(v) {
inv[v] = k
}
}
for k, v := range mapping {
if _, ok := inv[v]; !ok {
inv[v] = k
}
} }
r.FontGIDMap[fontID] = inv r.FontGIDMap[fontID] = inv
} }
+59
View File
@@ -18,6 +18,7 @@ import (
"image/color" "image/color"
"strconv" "strconv"
"strings" "strings"
"github.com/tdewolff/canvas"
) )
// parseColor 解析颜色字符串 // parseColor 解析颜色字符串
@@ -58,6 +59,22 @@ func parseFillColor(fillColor *FillColor) color.Color {
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha) return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha)
} }
// parseFillPaint 解析填充画刷
// 入参: fillColor 填充颜色节点, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
// 返回: any 填充画刷
func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64) any {
if fillColor == nil {
return nil
}
if strings.TrimSpace(fillColor.Value) != "" {
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
}
if gradient := parseAxialShdGradient(fillColor.AxialShd, fillColor.Alpha, x, y, pageH, originX, originY); gradient != nil {
return gradient
}
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha)
}
// parseStrokeColor 解析勾边颜色 // parseStrokeColor 解析勾边颜色
// 入参: strokeColor 勾边颜色节点 // 入参: strokeColor 勾边颜色节点
// 返回: color.Color 颜色对象 // 返回: color.Color 颜色对象
@@ -89,6 +106,48 @@ func parseAxialShdColor(axialShd *AxialShd, alpha *int) color.Color {
return color.Black return color.Black
} }
// parseAxialShdGradient 解析轴向渐变
// 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
// 返回: canvas.Gradient 渐变对象
func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX, originY float64) canvas.Gradient {
if axialShd == nil {
return nil
}
start := parseFloats(axialShd.StartPoint)
end := parseFloats(axialShd.EndPoint)
if len(start) < 2 || len(end) < 2 {
return nil
}
grad := canvas.NewGradient()
for _, segment := range axialShd.Segment {
if strings.TrimSpace(segment.Color.Value) == "" {
continue
}
segmentAlpha := alpha
if segmentAlpha == nil {
segmentAlpha = segment.Color.Alpha
}
grad.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
}
if len(grad) == 0 {
return nil
}
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
endPoint := canvas.Point{X: x + end[0] - originX, Y: pageH - (y + end[1]) - originY}
if startPoint.Equals(endPoint) {
return nil
}
return grad.ToLinear(startPoint, endPoint)
}
// colorToRGBA 转换颜色对象
// 入参: c 颜色对象
// 返回: color.RGBA RGBA颜色
func colorToRGBA(c color.Color) color.RGBA {
r, g, b, a := c.RGBA()
return color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)}
}
// GetDeltaX 获取X轴偏移量数组 // GetDeltaX 获取X轴偏移量数组
// 返回: []float64 偏移量数组 // 返回: []float64 偏移量数组
func (tc *TextCode) GetDeltaX() []float64 { func (tc *TextCode) GetDeltaX() []float64 {