mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
This commit is contained in:
@@ -0,0 +1,191 @@
|
|||||||
|
// 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 (
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/tdewolff/canvas"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadFont 加载字体
|
||||||
|
// 入参: fontID 字体ID
|
||||||
|
// 返回: *canvas.FontFamily 字体族
|
||||||
|
func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
||||||
|
if ff, ok := r.FontMap[fontID]; ok {
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
var defaultFont *canvas.FontFamily
|
||||||
|
if r.defaultFontLoaded {
|
||||||
|
defaultFont = r.fontFamily
|
||||||
|
}
|
||||||
|
of, ok := r.Reader.fontCache[fontID]
|
||||||
|
if !ok {
|
||||||
|
return defaultFont
|
||||||
|
}
|
||||||
|
ff := canvas.NewFontFamily(of.FontName)
|
||||||
|
if of.FontFile != "" {
|
||||||
|
if fontData, err := r.Reader.ResData(of.FontFile); err == nil {
|
||||||
|
if cidMap := getCFFCIDRuneMap(fontData); len(cidMap) > 0 {
|
||||||
|
if r.FontCIDMap == nil {
|
||||||
|
r.FontCIDMap = make(map[string]map[uint16]rune)
|
||||||
|
}
|
||||||
|
r.FontCIDMap[fontID] = cidMap
|
||||||
|
}
|
||||||
|
if _, fixedData, mapping, _, err := FixFontDataAggressive(fontData, true, true); err == nil {
|
||||||
|
fontData = fixedData
|
||||||
|
if mapping != nil {
|
||||||
|
if r.FontGIDMap == nil {
|
||||||
|
r.FontGIDMap = make(map[string]map[uint16]rune)
|
||||||
|
}
|
||||||
|
inv := make(map[uint16]rune)
|
||||||
|
for k, v := range mapping {
|
||||||
|
if k == packedGlyphRune(v) {
|
||||||
|
inv[v] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range mapping {
|
||||||
|
if _, ok := inv[v]; !ok {
|
||||||
|
inv[v] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.FontGIDMap[fontID] = inv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var fontStyle canvas.FontStyle
|
||||||
|
if of.Bold {
|
||||||
|
fontStyle |= canvas.FontBold
|
||||||
|
}
|
||||||
|
if of.Italic {
|
||||||
|
fontStyle |= canvas.FontItalic
|
||||||
|
}
|
||||||
|
if err := ff.LoadFont(fontData, 0, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var fontStyle canvas.FontStyle
|
||||||
|
if of.Bold {
|
||||||
|
fontStyle |= canvas.FontBold
|
||||||
|
}
|
||||||
|
if of.Italic {
|
||||||
|
fontStyle |= canvas.FontItalic
|
||||||
|
}
|
||||||
|
boldStyle := fontStyle&canvas.FontBold != 0
|
||||||
|
italicStyle := fontStyle&canvas.FontItalic != 0
|
||||||
|
patterns := fontFilePatterns(of.FontName, of.FamilyName)
|
||||||
|
for _, dir := range r.fontDirs {
|
||||||
|
for _, m := range r.matchFontFiles(dir, patterns, boldStyle, italicStyle) {
|
||||||
|
if err := ff.LoadFontFile(m, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, fsys := range r.fontFS {
|
||||||
|
for _, m := range fontFSMatchesStyle(fsys, patterns, boldStyle, italicStyle) {
|
||||||
|
resData, err := fs.ReadFile(fsys, m)
|
||||||
|
if err == nil {
|
||||||
|
if err := ff.LoadFont(resData, 0, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !canLoadSystemFonts() {
|
||||||
|
for _, fsys := range r.fontFS {
|
||||||
|
if matches, err := fs.Glob(fsys, "*"); err == nil {
|
||||||
|
for _, m := range matches {
|
||||||
|
resData, err := fs.ReadFile(fsys, m)
|
||||||
|
if err == nil {
|
||||||
|
if err := ff.LoadFont(resData, 0, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.FontMap[fontID] = defaultFont
|
||||||
|
return defaultFont
|
||||||
|
}
|
||||||
|
names := []string{of.FamilyName, of.FontName}
|
||||||
|
for _, name := range names {
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, targetName := range fontSystemNames(name) {
|
||||||
|
for _, m := range r.matchFontFiles(systemFontDir(), fontFilePatterns(targetName), boldStyle, italicStyle) {
|
||||||
|
if err := ff.LoadFontFile(m, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := ff.LoadSystemFont(targetName, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range r.matchFontFiles(systemFontDir(), fontFilePatterns(name), boldStyle, italicStyle) {
|
||||||
|
if err := ff.LoadFontFile(m, fontStyle); err == nil {
|
||||||
|
r.FontMap[fontID] = ff
|
||||||
|
return ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.FontMap[fontID] = defaultFont
|
||||||
|
return defaultFont
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchFontFiles 查找字体文件
|
||||||
|
// 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体
|
||||||
|
// 返回: []string 文件列表
|
||||||
|
func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string {
|
||||||
|
var matches []fontFileMatch
|
||||||
|
index := make(map[string]int)
|
||||||
|
add := func(pattern, name string, rank int) {
|
||||||
|
if !isFontFileName(name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appendFontFileMatch(&matches, index, pattern, name, rank, bold, italic)
|
||||||
|
}
|
||||||
|
files, _ := filepath.Glob(filepath.Join(dir, "*"))
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
for _, name := range files {
|
||||||
|
add(pattern, name, matchFontPatternRank(pattern, filepath.Base(name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortFontFileMatches(matches, bold, italic)
|
||||||
|
return fontFileMatchNames(matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
// systemFontDir 获取系统字体目录
|
||||||
|
// 返回: string 字体目录
|
||||||
|
func systemFontDir() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
return `/usr/share/fonts`
|
||||||
|
case "darwin":
|
||||||
|
return `/Library/Fonts`
|
||||||
|
default:
|
||||||
|
return `C:\Windows\Fonts`
|
||||||
|
}
|
||||||
|
}
|
||||||
+338
@@ -0,0 +1,338 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
// 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"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/tdewolff/canvas"
|
||||||
|
)
|
||||||
|
|
||||||
|
// renderImage 渲染图片
|
||||||
|
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM
|
||||||
|
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool) {
|
||||||
|
if obj.Visible != nil && !*obj.Visible {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resPath, ok := r.Reader.ResMap[obj.ResourceID]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imgData, err := r.Reader.ResData(resPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img, _, err := decodeImageData(imgData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
box, _ := ParseBox(obj.Boundary)
|
||||||
|
imgBounds := img.Bounds()
|
||||||
|
imgW, imgH := float64(imgBounds.Dx()), float64(imgBounds.Dy())
|
||||||
|
if imgW <= 0 || imgH <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img = imageWithAlpha(img, obj.Alpha)
|
||||||
|
pad := 0
|
||||||
|
img, pad = imageWithTransparentEdge(img)
|
||||||
|
ctm := NewMatrix(obj.CTM)
|
||||||
|
if obj.CTM == "" {
|
||||||
|
ctm = Matrix{a: box.W, d: box.H}
|
||||||
|
}
|
||||||
|
var m canvas.Matrix
|
||||||
|
if boundaryInCTM && parentCTM != nil {
|
||||||
|
x0 := box.X + ctm.c + ctm.e
|
||||||
|
y0 := box.Y + ctm.d + ctm.f
|
||||||
|
m = canvas.Matrix{
|
||||||
|
{(parentCTM.a*ctm.a + parentCTM.c*ctm.b) / imgW, -(parentCTM.a*ctm.c + parentCTM.c*ctm.d) / imgH, parentCTM.a*x0 + parentCTM.c*y0 + parentCTM.e},
|
||||||
|
{-(parentCTM.b*ctm.a + parentCTM.d*ctm.b) / imgW, (parentCTM.b*ctm.c + parentCTM.d*ctm.d) / imgH, pageH - (parentCTM.b*x0 + parentCTM.d*y0 + parentCTM.f)},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if parentCTM != nil {
|
||||||
|
ctm = parentCTM.Multiply(ctm)
|
||||||
|
}
|
||||||
|
m = canvas.Matrix{
|
||||||
|
{ctm.a / imgW, -ctm.c / imgH, box.X + ctm.c + ctm.e},
|
||||||
|
{-ctm.b / imgW, ctm.d / imgH, pageH - box.Y - ctm.d - ctm.f},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pad > 0 {
|
||||||
|
p := float64(pad)
|
||||||
|
m[0][2] -= m[0][0]*p + m[0][1]*p
|
||||||
|
m[1][2] -= m[1][0]*p + m[1][1]*p
|
||||||
|
}
|
||||||
|
ctx.RenderImage(img, ctx.CoordSystemView().Mul(ctx.View()).Mul(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageWithAlpha 合并图片透明度
|
||||||
|
// 入参: img 图片对象, alpha 对象透明度
|
||||||
|
// 返回: image.Image 合并后的图片对象
|
||||||
|
func imageWithAlpha(img image.Image, alpha *int) image.Image {
|
||||||
|
if img == nil || alpha == nil {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
a := clampColor(*alpha)
|
||||||
|
bounds := img.Bounds()
|
||||||
|
out := image.NewNRGBA(bounds)
|
||||||
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
|
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
||||||
|
c.A = uint8(int(c.A) * a / 255)
|
||||||
|
out.SetNRGBA(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageWithTransparentEdge 补齐透明图片边缘颜色
|
||||||
|
// 入参: img 图片对象
|
||||||
|
// 返回: image.Image 补齐后的图片对象, int 补齐像素数
|
||||||
|
func imageWithTransparentEdge(img image.Image) (image.Image, int) {
|
||||||
|
if img == nil {
|
||||||
|
return img, 0
|
||||||
|
}
|
||||||
|
bounds := img.Bounds()
|
||||||
|
w, h := bounds.Dx(), bounds.Dy()
|
||||||
|
if w == 0 || h == 0 {
|
||||||
|
return img, 0
|
||||||
|
}
|
||||||
|
src := image.NewNRGBA(image.Rect(0, 0, w, h))
|
||||||
|
hasZero, hasVisible := false, false
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
c := color.NRGBAModel.Convert(img.At(bounds.Min.X+x, bounds.Min.Y+y)).(color.NRGBA)
|
||||||
|
src.SetNRGBA(x, y, c)
|
||||||
|
if c.A == 0 {
|
||||||
|
hasZero = true
|
||||||
|
} else {
|
||||||
|
hasVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasZero || !hasVisible {
|
||||||
|
return img, 0
|
||||||
|
}
|
||||||
|
out := image.NewNRGBA(image.Rect(0, 0, w+2, h+2))
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
c := src.NRGBAAt(x, y)
|
||||||
|
if c.A == 0 {
|
||||||
|
if edge, ok := transparentEdgeColor(src, x, y); ok {
|
||||||
|
c = edge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.SetNRGBA(x+1, y+1, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
out.SetNRGBA(x+1, 0, transparentPaddingColor(out.NRGBAAt(x+1, 1)))
|
||||||
|
out.SetNRGBA(x+1, h+1, transparentPaddingColor(out.NRGBAAt(x+1, h)))
|
||||||
|
}
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
out.SetNRGBA(0, y+1, transparentPaddingColor(out.NRGBAAt(1, y+1)))
|
||||||
|
out.SetNRGBA(w+1, y+1, transparentPaddingColor(out.NRGBAAt(w, y+1)))
|
||||||
|
}
|
||||||
|
out.SetNRGBA(0, 0, transparentPaddingColor(out.NRGBAAt(1, 1)))
|
||||||
|
out.SetNRGBA(w+1, 0, transparentPaddingColor(out.NRGBAAt(w, 1)))
|
||||||
|
out.SetNRGBA(0, h+1, transparentPaddingColor(out.NRGBAAt(1, h)))
|
||||||
|
out.SetNRGBA(w+1, h+1, transparentPaddingColor(out.NRGBAAt(w, h)))
|
||||||
|
return out, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// transparentEdgeColor 获取透明像素相邻的可见颜色
|
||||||
|
// 入参: img 图片对象, x X坐标, y Y坐标
|
||||||
|
// 返回: color.NRGBA 颜色, bool 是否存在
|
||||||
|
func transparentEdgeColor(img *image.NRGBA, x, y int) (color.NRGBA, bool) {
|
||||||
|
bounds := img.Bounds()
|
||||||
|
var best color.NRGBA
|
||||||
|
for dy := -1; dy <= 1; dy++ {
|
||||||
|
for dx := -1; dx <= 1; dx++ {
|
||||||
|
if dx == 0 && dy == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nx, ny := x+dx, y+dy
|
||||||
|
if nx < bounds.Min.X || nx >= bounds.Max.X || ny < bounds.Min.Y || ny >= bounds.Max.Y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := img.NRGBAAt(nx, ny)
|
||||||
|
if c.A > best.A {
|
||||||
|
best = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if best.A == 0 {
|
||||||
|
return color.NRGBA{}, false
|
||||||
|
}
|
||||||
|
best.A = 1
|
||||||
|
return best, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// transparentPaddingColor 获取透明补齐颜色
|
||||||
|
// 入参: c 边缘颜色
|
||||||
|
// 返回: color.NRGBA 补齐颜色
|
||||||
|
func transparentPaddingColor(c color.NRGBA) color.NRGBA {
|
||||||
|
c.A = 1
|
||||||
|
return c
|
||||||
|
}
|
||||||
@@ -22,8 +22,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -516,173 +514,6 @@ func (r *Renderer) childRenderer(reader *Reader) *Renderer {
|
|||||||
return NewRenderer(reader, opts...)
|
return NewRenderer(reader, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderImage 渲染图片
|
|
||||||
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM
|
|
||||||
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool) {
|
|
||||||
if obj.Visible != nil && !*obj.Visible {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resPath, ok := r.Reader.ResMap[obj.ResourceID]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
imgData, err := r.Reader.ResData(resPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
img, _, err := decodeImageData(imgData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
box, _ := ParseBox(obj.Boundary)
|
|
||||||
imgBounds := img.Bounds()
|
|
||||||
imgW, imgH := float64(imgBounds.Dx()), float64(imgBounds.Dy())
|
|
||||||
if imgW <= 0 || imgH <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
img = imageWithAlpha(img, obj.Alpha)
|
|
||||||
pad := 0
|
|
||||||
img, pad = imageWithTransparentEdge(img)
|
|
||||||
ctm := NewMatrix(obj.CTM)
|
|
||||||
if obj.CTM == "" {
|
|
||||||
ctm = Matrix{a: box.W, d: box.H}
|
|
||||||
}
|
|
||||||
var m canvas.Matrix
|
|
||||||
if boundaryInCTM && parentCTM != nil {
|
|
||||||
x0 := box.X + ctm.c + ctm.e
|
|
||||||
y0 := box.Y + ctm.d + ctm.f
|
|
||||||
m = canvas.Matrix{
|
|
||||||
{(parentCTM.a*ctm.a + parentCTM.c*ctm.b) / imgW, -(parentCTM.a*ctm.c + parentCTM.c*ctm.d) / imgH, parentCTM.a*x0 + parentCTM.c*y0 + parentCTM.e},
|
|
||||||
{-(parentCTM.b*ctm.a + parentCTM.d*ctm.b) / imgW, (parentCTM.b*ctm.c + parentCTM.d*ctm.d) / imgH, pageH - (parentCTM.b*x0 + parentCTM.d*y0 + parentCTM.f)},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if parentCTM != nil {
|
|
||||||
ctm = parentCTM.Multiply(ctm)
|
|
||||||
}
|
|
||||||
m = canvas.Matrix{
|
|
||||||
{ctm.a / imgW, -ctm.c / imgH, box.X + ctm.c + ctm.e},
|
|
||||||
{-ctm.b / imgW, ctm.d / imgH, pageH - box.Y - ctm.d - ctm.f},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pad > 0 {
|
|
||||||
p := float64(pad)
|
|
||||||
m[0][2] -= m[0][0]*p + m[0][1]*p
|
|
||||||
m[1][2] -= m[1][0]*p + m[1][1]*p
|
|
||||||
}
|
|
||||||
ctx.RenderImage(img, ctx.CoordSystemView().Mul(ctx.View()).Mul(m))
|
|
||||||
}
|
|
||||||
|
|
||||||
// imageWithAlpha 合并图片透明度
|
|
||||||
// 入参: img 图片对象, alpha 对象透明度
|
|
||||||
// 返回: image.Image 合并后的图片对象
|
|
||||||
func imageWithAlpha(img image.Image, alpha *int) image.Image {
|
|
||||||
if img == nil || alpha == nil {
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
a := clampColor(*alpha)
|
|
||||||
bounds := img.Bounds()
|
|
||||||
out := image.NewNRGBA(bounds)
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
|
||||||
c.A = uint8(int(c.A) * a / 255)
|
|
||||||
out.SetNRGBA(x, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// imageWithTransparentEdge 补齐透明图片边缘颜色
|
|
||||||
// 入参: img 图片对象
|
|
||||||
// 返回: image.Image 补齐后的图片对象, int 补齐像素数
|
|
||||||
func imageWithTransparentEdge(img image.Image) (image.Image, int) {
|
|
||||||
if img == nil {
|
|
||||||
return img, 0
|
|
||||||
}
|
|
||||||
bounds := img.Bounds()
|
|
||||||
w, h := bounds.Dx(), bounds.Dy()
|
|
||||||
if w == 0 || h == 0 {
|
|
||||||
return img, 0
|
|
||||||
}
|
|
||||||
src := image.NewNRGBA(image.Rect(0, 0, w, h))
|
|
||||||
hasZero, hasVisible := false, false
|
|
||||||
for y := 0; y < h; y++ {
|
|
||||||
for x := 0; x < w; x++ {
|
|
||||||
c := color.NRGBAModel.Convert(img.At(bounds.Min.X+x, bounds.Min.Y+y)).(color.NRGBA)
|
|
||||||
src.SetNRGBA(x, y, c)
|
|
||||||
if c.A == 0 {
|
|
||||||
hasZero = true
|
|
||||||
} else {
|
|
||||||
hasVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasZero || !hasVisible {
|
|
||||||
return img, 0
|
|
||||||
}
|
|
||||||
out := image.NewNRGBA(image.Rect(0, 0, w+2, h+2))
|
|
||||||
for y := 0; y < h; y++ {
|
|
||||||
for x := 0; x < w; x++ {
|
|
||||||
c := src.NRGBAAt(x, y)
|
|
||||||
if c.A == 0 {
|
|
||||||
if edge, ok := transparentEdgeColor(src, x, y); ok {
|
|
||||||
c = edge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.SetNRGBA(x+1, y+1, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for x := 0; x < w; x++ {
|
|
||||||
out.SetNRGBA(x+1, 0, transparentPaddingColor(out.NRGBAAt(x+1, 1)))
|
|
||||||
out.SetNRGBA(x+1, h+1, transparentPaddingColor(out.NRGBAAt(x+1, h)))
|
|
||||||
}
|
|
||||||
for y := 0; y < h; y++ {
|
|
||||||
out.SetNRGBA(0, y+1, transparentPaddingColor(out.NRGBAAt(1, y+1)))
|
|
||||||
out.SetNRGBA(w+1, y+1, transparentPaddingColor(out.NRGBAAt(w, y+1)))
|
|
||||||
}
|
|
||||||
out.SetNRGBA(0, 0, transparentPaddingColor(out.NRGBAAt(1, 1)))
|
|
||||||
out.SetNRGBA(w+1, 0, transparentPaddingColor(out.NRGBAAt(w, 1)))
|
|
||||||
out.SetNRGBA(0, h+1, transparentPaddingColor(out.NRGBAAt(1, h)))
|
|
||||||
out.SetNRGBA(w+1, h+1, transparentPaddingColor(out.NRGBAAt(w, h)))
|
|
||||||
return out, 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// transparentEdgeColor 获取透明像素相邻的可见颜色
|
|
||||||
// 入参: img 图片对象, x X坐标, y Y坐标
|
|
||||||
// 返回: color.NRGBA 颜色, bool 是否存在
|
|
||||||
func transparentEdgeColor(img *image.NRGBA, x, y int) (color.NRGBA, bool) {
|
|
||||||
bounds := img.Bounds()
|
|
||||||
var best color.NRGBA
|
|
||||||
for dy := -1; dy <= 1; dy++ {
|
|
||||||
for dx := -1; dx <= 1; dx++ {
|
|
||||||
if dx == 0 && dy == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nx, ny := x+dx, y+dy
|
|
||||||
if nx < bounds.Min.X || nx >= bounds.Max.X || ny < bounds.Min.Y || ny >= bounds.Max.Y {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c := img.NRGBAAt(nx, ny)
|
|
||||||
if c.A > best.A {
|
|
||||||
best = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if best.A == 0 {
|
|
||||||
return color.NRGBA{}, false
|
|
||||||
}
|
|
||||||
best.A = 1
|
|
||||||
return best, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// transparentPaddingColor 获取透明补齐颜色
|
|
||||||
// 入参: c 边缘颜色
|
|
||||||
// 返回: color.NRGBA 补齐颜色
|
|
||||||
func transparentPaddingColor(c color.NRGBA) color.NRGBA {
|
|
||||||
c.A = 1
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderPath 渲染路径
|
// renderPath 渲染路径
|
||||||
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换
|
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换
|
||||||
func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool) {
|
func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool) {
|
||||||
@@ -1163,187 +994,6 @@ func textMatrix(ctm Matrix) canvas.Matrix {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadFont 加载字体
|
|
||||||
// 入参: fontID 字体ID
|
|
||||||
// 返回: *canvas.FontFamily 字体族
|
|
||||||
func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
|
||||||
if ff, ok := r.FontMap[fontID]; ok {
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
var defaultFont *canvas.FontFamily
|
|
||||||
if r.defaultFontLoaded {
|
|
||||||
defaultFont = r.fontFamily
|
|
||||||
}
|
|
||||||
of, ok := r.Reader.fontCache[fontID]
|
|
||||||
if !ok {
|
|
||||||
return defaultFont
|
|
||||||
}
|
|
||||||
ff := canvas.NewFontFamily(of.FontName)
|
|
||||||
if of.FontFile != "" {
|
|
||||||
if fontData, err := r.Reader.ResData(of.FontFile); err == nil {
|
|
||||||
if cidMap := getCFFCIDRuneMap(fontData); len(cidMap) > 0 {
|
|
||||||
if r.FontCIDMap == nil {
|
|
||||||
r.FontCIDMap = make(map[string]map[uint16]rune)
|
|
||||||
}
|
|
||||||
r.FontCIDMap[fontID] = cidMap
|
|
||||||
}
|
|
||||||
if _, fixedData, mapping, _, err := FixFontDataAggressive(fontData, true, true); err == nil {
|
|
||||||
fontData = fixedData
|
|
||||||
if mapping != nil {
|
|
||||||
if r.FontGIDMap == nil {
|
|
||||||
r.FontGIDMap = make(map[string]map[uint16]rune)
|
|
||||||
}
|
|
||||||
inv := make(map[uint16]rune)
|
|
||||||
for k, v := range mapping {
|
|
||||||
if k == packedGlyphRune(v) {
|
|
||||||
inv[v] = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range mapping {
|
|
||||||
if _, ok := inv[v]; !ok {
|
|
||||||
inv[v] = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.FontGIDMap[fontID] = inv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var fontStyle canvas.FontStyle
|
|
||||||
if of.Bold {
|
|
||||||
fontStyle |= canvas.FontBold
|
|
||||||
}
|
|
||||||
if of.Italic {
|
|
||||||
fontStyle |= canvas.FontItalic
|
|
||||||
}
|
|
||||||
if err := ff.LoadFont(fontData, 0, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var fontStyle canvas.FontStyle
|
|
||||||
if of.Bold {
|
|
||||||
fontStyle |= canvas.FontBold
|
|
||||||
}
|
|
||||||
if of.Italic {
|
|
||||||
fontStyle |= canvas.FontItalic
|
|
||||||
}
|
|
||||||
boldStyle := fontStyle&canvas.FontBold != 0
|
|
||||||
italicStyle := fontStyle&canvas.FontItalic != 0
|
|
||||||
patterns := fontFilePatterns(of.FontName, of.FamilyName)
|
|
||||||
for _, dir := range r.fontDirs {
|
|
||||||
for _, m := range r.matchFontFiles(dir, patterns, boldStyle, italicStyle) {
|
|
||||||
if err := ff.LoadFontFile(m, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, fsys := range r.fontFS {
|
|
||||||
for _, m := range fontFSMatchesStyle(fsys, patterns, boldStyle, italicStyle) {
|
|
||||||
resData, err := fs.ReadFile(fsys, m)
|
|
||||||
if err == nil {
|
|
||||||
if err := ff.LoadFont(resData, 0, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !canLoadSystemFonts() {
|
|
||||||
for _, fsys := range r.fontFS {
|
|
||||||
if matches, err := fs.Glob(fsys, "*"); err == nil {
|
|
||||||
for _, m := range matches {
|
|
||||||
resData, err := fs.ReadFile(fsys, m)
|
|
||||||
if err == nil {
|
|
||||||
if err := ff.LoadFont(resData, 0, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.FontMap[fontID] = defaultFont
|
|
||||||
return defaultFont
|
|
||||||
}
|
|
||||||
names := []string{of.FamilyName, of.FontName}
|
|
||||||
for _, name := range names {
|
|
||||||
if name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, targetName := range fontSystemNames(name) {
|
|
||||||
for _, m := range r.matchFontFiles(systemFontDir(), fontFilePatterns(targetName), boldStyle, italicStyle) {
|
|
||||||
if err := ff.LoadFontFile(m, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := ff.LoadSystemFont(targetName, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, m := range r.matchFontFiles(systemFontDir(), fontFilePatterns(name), boldStyle, italicStyle) {
|
|
||||||
if err := ff.LoadFontFile(m, fontStyle); err == nil {
|
|
||||||
r.FontMap[fontID] = ff
|
|
||||||
return ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.FontMap[fontID] = defaultFont
|
|
||||||
return defaultFont
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchFontFiles 查找字体文件
|
|
||||||
// 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体
|
|
||||||
// 返回: []string 文件列表
|
|
||||||
func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string {
|
|
||||||
var matches []fontFileMatch
|
|
||||||
index := make(map[string]int)
|
|
||||||
add := func(pattern, name string, rank int) {
|
|
||||||
if !isFontFileName(name) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
appendFontFileMatch(&matches, index, pattern, name, rank, bold, italic)
|
|
||||||
}
|
|
||||||
files, _ := filepath.Glob(filepath.Join(dir, "*"))
|
|
||||||
for _, pattern := range patterns {
|
|
||||||
for _, name := range files {
|
|
||||||
add(pattern, name, matchFontPatternRank(pattern, filepath.Base(name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortFontFileMatches(matches, bold, italic)
|
|
||||||
return fontFileMatchNames(matches)
|
|
||||||
}
|
|
||||||
|
|
||||||
// systemFontDir 获取系统字体目录
|
|
||||||
// 返回: string 字体目录
|
|
||||||
func systemFontDir() string {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
return `/usr/share/fonts`
|
|
||||||
case "darwin":
|
|
||||||
return `/Library/Fonts`
|
|
||||||
default:
|
|
||||||
return `C:\Windows\Fonts`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderStamp 渲染印章
|
// renderStamp 渲染印章
|
||||||
// 入参: ctx 画布上下文, s 印章对象, pageH 页面高度
|
// 入参: ctx 画布上下文, s 印章对象, pageH 页面高度
|
||||||
func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
|
func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
|
||||||
|
|||||||
+13
-317
@@ -15,217 +15,12 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/color"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
"github.com/tdewolff/font"
|
"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 绘制字形
|
// textGlyph 绘制字形
|
||||||
type textGlyph struct {
|
type textGlyph struct {
|
||||||
Text string
|
Text string
|
||||||
@@ -238,6 +33,19 @@ type textGlyphTransform struct {
|
|||||||
Glyphs []textGlyph
|
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 获取文本对象的字形变换
|
// textObjectGlyphTransforms 获取文本对象的字形变换
|
||||||
// 入参: fontID 字体ID, text 文本对象
|
// 入参: fontID 字体ID, text 文本对象
|
||||||
// 返回: map[int]textGlyphTransform 文本位置到字形变换的映射
|
// 返回: map[int]textGlyphTransform 文本位置到字形变换的映射
|
||||||
@@ -427,118 +235,6 @@ func textCodeRunes(value string) []rune {
|
|||||||
return []rune(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轴偏移量数组
|
// GetDeltaX 获取X轴偏移量数组
|
||||||
// 返回: []float64 偏移量数组
|
// 返回: []float64 偏移量数组
|
||||||
func (tc *TextCode) GetDeltaX() []float64 {
|
func (tc *TextCode) GetDeltaX() []float64 {
|
||||||
|
|||||||
Reference in New Issue
Block a user