mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
553 lines
16 KiB
Go
553 lines
16 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 (
|
|
"image/color"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/tdewolff/canvas"
|
|
"github.com/tdewolff/font"
|
|
)
|
|
|
|
// parseColorWithAlpha 解析带透明度的颜色
|
|
// 入参: val 颜色值, alpha 透明度
|
|
// 返回: color.Color 颜色对象
|
|
func parseColorWithAlpha(val string, alpha *int) color.Color {
|
|
parts := strings.Fields(val)
|
|
if len(parts) >= 3 {
|
|
r := parseColorComponent(parts[0])
|
|
g := parseColorComponent(parts[1])
|
|
b := parseColorComponent(parts[2])
|
|
a := 255
|
|
if alpha != nil {
|
|
a = *alpha
|
|
}
|
|
a = clampColor(a)
|
|
return color.RGBA{
|
|
R: uint8(clampColor(r) * a / 255),
|
|
G: uint8(clampColor(g) * a / 255),
|
|
B: uint8(clampColor(b) * a / 255),
|
|
A: uint8(a),
|
|
}
|
|
}
|
|
return color.Black
|
|
}
|
|
|
|
// parseColorComponent 解析颜色分量
|
|
// 入参: s 颜色分量
|
|
// 返回: int 颜色分量值
|
|
func parseColorComponent(s string) int {
|
|
s = strings.TrimSpace(s)
|
|
if strings.HasPrefix(s, "#") {
|
|
v, _ := strconv.ParseInt(strings.TrimPrefix(s, "#"), 16, 0)
|
|
return int(v)
|
|
}
|
|
v, _ := strconv.Atoi(s)
|
|
return v
|
|
}
|
|
|
|
// clampColor 限制颜色分量范围
|
|
// 入参: v 颜色分量
|
|
// 返回: int 颜色分量
|
|
func clampColor(v int) int {
|
|
if v < 0 {
|
|
return 0
|
|
}
|
|
if v > 255 {
|
|
return 255
|
|
}
|
|
return v
|
|
}
|
|
|
|
// mergeAlpha 合并透明度
|
|
// 入参: colorAlpha 颜色透明度, objectAlpha 对象透明度
|
|
// 返回: *int 合并后的透明度
|
|
func mergeAlpha(colorAlpha, objectAlpha *int) *int {
|
|
if colorAlpha == nil && objectAlpha == nil {
|
|
return nil
|
|
}
|
|
alpha := 255
|
|
if colorAlpha != nil {
|
|
alpha = *colorAlpha
|
|
}
|
|
if objectAlpha != nil {
|
|
alpha = alpha * *objectAlpha / 255
|
|
}
|
|
alpha = clampColor(alpha)
|
|
return &alpha
|
|
}
|
|
|
|
// withFillAlpha 合并填充色透明度
|
|
// 入参: fillColor 填充颜色节点, alpha 对象透明度
|
|
// 返回: *FillColor 合并后的填充颜色节点
|
|
func withFillAlpha(fillColor *FillColor, alpha *int) *FillColor {
|
|
if fillColor == nil || alpha == nil {
|
|
return fillColor
|
|
}
|
|
merged := *fillColor
|
|
merged.Alpha = mergeAlpha(fillColor.Alpha, alpha)
|
|
return &merged
|
|
}
|
|
|
|
// withStrokeAlpha 合并勾边色透明度
|
|
// 入参: strokeColor 勾边颜色节点, alpha 对象透明度
|
|
// 返回: *StrokeColor 合并后的勾边颜色节点
|
|
func withStrokeAlpha(strokeColor *StrokeColor, alpha *int) *StrokeColor {
|
|
if strokeColor == nil || alpha == nil {
|
|
return strokeColor
|
|
}
|
|
merged := *strokeColor
|
|
merged.Alpha = mergeAlpha(strokeColor.Alpha, alpha)
|
|
return &merged
|
|
}
|
|
|
|
// colorWithAlpha 合并颜色透明度
|
|
// 入参: c 颜色对象, alpha 对象透明度
|
|
// 返回: color.Color 合并后的颜色对象
|
|
func colorWithAlpha(c color.Color, alpha *int) color.Color {
|
|
if c == nil || alpha == nil {
|
|
return c
|
|
}
|
|
a := clampColor(*alpha)
|
|
rgba := colorToRGBA(c)
|
|
return color.RGBA{
|
|
R: uint8(int(rgba.R) * a / 255),
|
|
G: uint8(int(rgba.G) * a / 255),
|
|
B: uint8(int(rgba.B) * a / 255),
|
|
A: uint8(int(rgba.A) * a / 255),
|
|
}
|
|
}
|
|
|
|
// parseFillColor 解析填充颜色
|
|
// 入参: fillColor 填充颜色节点
|
|
// 返回: color.Color 颜色对象
|
|
func parseFillColor(fillColor *FillColor) color.Color {
|
|
if fillColor == nil {
|
|
return nil
|
|
}
|
|
if fillColor.Pattern != nil {
|
|
return nil
|
|
}
|
|
if fillColor.AxialShd != nil {
|
|
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha)
|
|
}
|
|
if fillColor.RadialShd != nil {
|
|
return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha)
|
|
}
|
|
if strings.TrimSpace(fillColor.Value) != "" {
|
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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 fillColor.Pattern != nil {
|
|
return nil
|
|
}
|
|
if gradient := parseAxialShdGradient(fillColor.AxialShd, fillColor.Alpha, x, y, pageH, originX, originY); gradient != nil {
|
|
return gradient
|
|
}
|
|
if gradient := parseRadialShdGradient(fillColor.RadialShd, fillColor.Alpha, x, y, pageH, originX, originY); gradient != nil {
|
|
return gradient
|
|
}
|
|
if fillColor.AxialShd != nil {
|
|
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha)
|
|
}
|
|
if fillColor.RadialShd != nil {
|
|
return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha)
|
|
}
|
|
if strings.TrimSpace(fillColor.Value) != "" {
|
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseStrokeColor 解析勾边颜色
|
|
// 入参: strokeColor 勾边颜色节点
|
|
// 返回: color.Color 颜色对象
|
|
func parseStrokeColor(strokeColor *StrokeColor) color.Color {
|
|
if strokeColor == nil {
|
|
return nil
|
|
}
|
|
if strokeColor.AxialShd != nil {
|
|
return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha)
|
|
}
|
|
if strokeColor.RadialShd != nil {
|
|
return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha)
|
|
}
|
|
if strings.TrimSpace(strokeColor.Value) != "" {
|
|
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseStrokePaint 解析勾边画刷
|
|
// 入参: strokeColor 勾边颜色节点, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
|
|
// 返回: any 勾边画刷
|
|
func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH, originX, originY float64) any {
|
|
if strokeColor == nil {
|
|
return nil
|
|
}
|
|
if gradient := parseAxialShdGradient(strokeColor.AxialShd, strokeColor.Alpha, x, y, pageH, originX, originY); gradient != nil {
|
|
return gradient
|
|
}
|
|
if gradient := parseRadialShdGradient(strokeColor.RadialShd, strokeColor.Alpha, x, y, pageH, originX, originY); gradient != nil {
|
|
return gradient
|
|
}
|
|
if strokeColor.AxialShd != nil {
|
|
return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha)
|
|
}
|
|
if strokeColor.RadialShd != nil {
|
|
return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha)
|
|
}
|
|
if strings.TrimSpace(strokeColor.Value) != "" {
|
|
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// textGlyph 绘制字形
|
|
type textGlyph struct {
|
|
Text string
|
|
GlyphID int
|
|
}
|
|
|
|
// textGlyphTransform 字符到字形变换
|
|
type textGlyphTransform struct {
|
|
CodeCount int
|
|
Glyphs []textGlyph
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 文本编码
|
|
// 返回: bool 是否带显式定位
|
|
func textCodePositioned(textCode TextCode) bool {
|
|
return strings.TrimSpace(textCode.DeltaX) != "" ||
|
|
strings.TrimSpace(textCode.DeltaY) != "" ||
|
|
len(parseFloats(textCode.X)) > 1 ||
|
|
len(parseFloats(textCode.Y)) > 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)
|
|
}
|
|
|
|
// parseAxialShdColor 解析轴向渐变颜色
|
|
// 入参: axialShd 轴向渐变节点, alpha 透明度
|
|
// 返回: color.Color 颜色对象
|
|
func parseAxialShdColor(axialShd *AxialShd, alpha *int) color.Color {
|
|
if axialShd != nil {
|
|
for _, segment := range axialShd.Segment {
|
|
if strings.TrimSpace(segment.Color.Value) == "" {
|
|
continue
|
|
}
|
|
if alpha == nil {
|
|
alpha = segment.Color.Alpha
|
|
}
|
|
return parseColorWithAlpha(segment.Color.Value, alpha)
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
|
|
// parseRadialShdColor 解析径向渐变颜色
|
|
// 入参: radialShd 径向渐变节点, alpha 透明度
|
|
// 返回: color.Color 颜色对象
|
|
func parseRadialShdColor(radialShd *RadialShd, alpha *int) color.Color {
|
|
if radialShd != nil {
|
|
for _, segment := range radialShd.Segment {
|
|
if strings.TrimSpace(segment.Color.Value) == "" {
|
|
continue
|
|
}
|
|
if alpha == nil {
|
|
alpha = segment.Color.Alpha
|
|
}
|
|
return parseColorWithAlpha(segment.Color.Value, alpha)
|
|
}
|
|
}
|
|
return color.Black
|
|
}
|
|
|
|
// parseRadialShdGradient 解析径向渐变
|
|
// 入参: radialShd 径向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
|
|
// 返回: canvas.Gradient 渐变对象
|
|
func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, originX, originY float64) canvas.Gradient {
|
|
if radialShd == nil || radialShd.EndRadius <= 0 {
|
|
return nil
|
|
}
|
|
start := parseFloats(radialShd.StartPoint)
|
|
end := parseFloats(radialShd.EndPoint)
|
|
if len(start) < 2 || len(end) < 2 {
|
|
return nil
|
|
}
|
|
grad := canvas.NewGradient()
|
|
for _, segment := range radialShd.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}
|
|
return grad.ToRadial(startPoint, radialShd.StartRadius, endPoint, radialShd.EndRadius)
|
|
}
|
|
|
|
// colorToRGBA 转换颜色对象
|
|
// 入参: c 颜色对象
|
|
// 返回: color.RGBA RGBA颜色
|
|
func colorToRGBA(c color.Color) color.RGBA {
|
|
if rgba, ok := c.(color.RGBA); ok {
|
|
return 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轴偏移量数组
|
|
// 返回: []float64 偏移量数组
|
|
func (tc *TextCode) GetDeltaX() []float64 {
|
|
return parseFloats(tc.DeltaX)
|
|
}
|
|
|
|
// GetDeltaY 获取Y轴偏移量数组
|
|
// 返回: []float64 偏移量数组
|
|
func (tc *TextCode) GetDeltaY() []float64 {
|
|
return parseFloats(tc.DeltaY)
|
|
}
|