diff --git a/ofdgo_font_loader.go b/ofdgo_font_loader.go new file mode 100644 index 0000000..8b4019e --- /dev/null +++ b/ofdgo_font_loader.go @@ -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` + } +} diff --git a/ofdgo_paint.go b/ofdgo_paint.go new file mode 100644 index 0000000..6c49483 --- /dev/null +++ b/ofdgo_paint.go @@ -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)} +} diff --git a/ofdgo_render_image.go b/ofdgo_render_image.go new file mode 100644 index 0000000..6688965 --- /dev/null +++ b/ofdgo_render_image.go @@ -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 +} diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index ca2e0c6..e3cbe05 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -22,8 +22,6 @@ import ( "io" "io/fs" "math" - "path/filepath" - "runtime" "strconv" "strings" @@ -516,173 +514,6 @@ func (r *Renderer) childRenderer(reader *Reader) *Renderer { 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 渲染路径 // 入参: 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) { @@ -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 渲染印章 // 入参: ctx 画布上下文, s 印章对象, pageH 页面高度 func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) { diff --git a/ofdgo_text.go b/ofdgo_text.go index 91deaac..862e76e 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -15,217 +15,12 @@ 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 @@ -238,6 +33,19 @@ type textGlyphTransform struct { 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 文本位置到字形变换的映射 @@ -427,118 +235,6 @@ func textCodeRunes(value string) []rune { 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 {