mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
affc84459e
commit
e78ea6f919
18 files changed
+553
-37
No files matched your search
+1
-1
@@ -1173,7 +1173,7 @@ func creationColor(color *FillColor) error {
|
||||
if color == nil {
|
||||
return nil
|
||||
}
|
||||
if color.ColorSpace != "" || color.Index != nil || color.Pattern != nil || color.AxialShd != nil || color.RadialShd != nil || color.unsupported {
|
||||
if color.ColorSpace != "" || color.Index != nil || color.Pattern != nil || color.AxialShd != nil || color.RadialShd != nil || color.GouraudShd != nil || color.LaGouraudShd != nil || color.unsupported {
|
||||
return fmt.Errorf("only direct RGB colors are supported for creation")
|
||||
}
|
||||
values, err := creationNumbers(color.Value, 3)
|
||||
|
||||
+59
-1
@@ -111,7 +111,7 @@ func (e *Editor) Color(value *FillColor) (color.NRGBA, error) {
|
||||
if value == nil {
|
||||
return color.NRGBA{A: 255}, nil
|
||||
}
|
||||
if value.Pattern != nil || value.AxialShd != nil || value.RadialShd != nil || value.unsupported {
|
||||
if value.Pattern != nil || value.AxialShd != nil || value.RadialShd != nil || value.GouraudShd != nil || value.LaGouraudShd != nil || value.unsupported {
|
||||
return color.NRGBA{}, fmt.Errorf("color is not a supported solid color")
|
||||
}
|
||||
id := value.ColorSpace
|
||||
@@ -292,6 +292,20 @@ func collectPaintReferences(paint *FillColor, used map[string]bool) {
|
||||
for _, segment := range segments {
|
||||
used[segment.Color.ColorSpace] = true
|
||||
}
|
||||
var points []ShdPoint
|
||||
var back *ShdColor
|
||||
if node := paint.GouraudShd; node != nil {
|
||||
points, back = node.Point, node.BackColor
|
||||
}
|
||||
if node := paint.LaGouraudShd; node != nil {
|
||||
points, back = node.Point, node.BackColor
|
||||
}
|
||||
for _, point := range points {
|
||||
used[point.Color.ColorSpace] = true
|
||||
}
|
||||
if back != nil {
|
||||
used[back.ColorSpace] = true
|
||||
}
|
||||
if paint.Pattern != nil {
|
||||
for _, object := range paint.Pattern.CellContent.Objects {
|
||||
collectObjectReferences(object, used)
|
||||
@@ -339,10 +353,54 @@ func collectObjectReferences(object GraphicObject, used map[string]bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// editorMeshColor 校验网格拓扑和文档颜色引用
|
||||
// 入参: value 网格颜色
|
||||
// 返回: error 非法拓扑或颜色
|
||||
func (e *Editor) editorMeshColor(value *FillColor) error {
|
||||
if value.unsupported || value.Pattern != nil || value.AxialShd != nil || value.RadialShd != nil || value.GouraudShd != nil && value.LaGouraudShd != nil || value.Value != "" || value.Index != nil {
|
||||
return fmt.Errorf("specify one paint source")
|
||||
}
|
||||
if value.Alpha != nil && (*value.Alpha < 0 || *value.Alpha > 255) {
|
||||
return fmt.Errorf("color alpha must be between 0 and 255")
|
||||
}
|
||||
var points []ShdPoint
|
||||
var back *ShdColor
|
||||
var columns, extend int
|
||||
if node := value.GouraudShd; node != nil {
|
||||
points, back, extend = node.Point, node.BackColor, node.Extend
|
||||
} else {
|
||||
points, back = value.LaGouraudShd.Point, value.LaGouraudShd.BackColor
|
||||
columns, extend = value.LaGouraudShd.VerticesPerRow, value.LaGouraudShd.Extend
|
||||
if columns == 0 {
|
||||
return fmt.Errorf("invalid lattice shading dimensions")
|
||||
}
|
||||
}
|
||||
colors := make([]ShdColor, 0, len(points)+1)
|
||||
for _, point := range points {
|
||||
colors = append(colors, point.Color)
|
||||
}
|
||||
if back != nil {
|
||||
colors = append(colors, *back)
|
||||
}
|
||||
for _, color := range colors {
|
||||
if color.ColorSpace == "" {
|
||||
color.ColorSpace = value.ColorSpace
|
||||
}
|
||||
if _, err := e.Color(&FillColor{Value: color.Value, Index: color.Index, ColorSpace: color.ColorSpace, Alpha: color.Alpha}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := meshTriangleIndices(points, columns, extend)
|
||||
return err
|
||||
}
|
||||
|
||||
// editorColor 校验当前文档的纯色和渐变
|
||||
// 入参: value 颜色
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) editorColor(value *FillColor) error {
|
||||
if value != nil && (value.GouraudShd != nil || value.LaGouraudShd != nil) {
|
||||
return e.editorMeshColor(value)
|
||||
}
|
||||
if value != nil && value.Pattern != nil {
|
||||
pattern := value.Pattern
|
||||
if value.unsupported || value.AxialShd != nil || value.RadialShd != nil || !finite(pattern.Width) || !finite(pattern.Height) || pattern.Width <= 0 || pattern.Height <= 0 || !finite(pattern.XStep) || !finite(pattern.YStep) || pattern.XStep < pattern.Width || pattern.YStep < pattern.Height {
|
||||
|
||||
@@ -92,12 +92,16 @@ func (c *FillColor) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
type plain FillColor
|
||||
var value struct {
|
||||
plain
|
||||
LegacyMesh *LaGouraudShd `xml:"LaGourandShd"`
|
||||
Other *struct{} `xml:",any"`
|
||||
}
|
||||
if err := d.DecodeElement(&value, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
*c = FillColor(value.plain)
|
||||
if c.LaGouraudShd == nil {
|
||||
c.LaGouraudShd = value.LegacyMesh
|
||||
}
|
||||
c.unsupported = value.Other != nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@ type FillColor struct {
|
||||
Pattern *Pattern `xml:"Pattern"`
|
||||
AxialShd *AxialShd `xml:"AxialShd"`
|
||||
RadialShd *RadialShd `xml:"RadialShd"`
|
||||
GouraudShd *GouraudShd `xml:"GouraudShd"`
|
||||
LaGouraudShd *LaGouraudShd `xml:"LaGouraudShd"`
|
||||
unsupported bool
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// GouraudShd 高洛德渐变,按EdgeFlag连接三角形
|
||||
type GouraudShd struct {
|
||||
Extend int `xml:"Extend,attr,omitempty"`
|
||||
Point []ShdPoint `xml:"Point"`
|
||||
BackColor *ShdColor `xml:"BackColor,omitempty"`
|
||||
}
|
||||
|
||||
// LaGouraudShd 格构高洛德渐变,按行排列控制点
|
||||
type LaGouraudShd struct {
|
||||
VerticesPerRow int `xml:"VerticesPerRow,attr"`
|
||||
Extend int `xml:"Extend,attr,omitempty"`
|
||||
Point []ShdPoint `xml:"Point"`
|
||||
BackColor *ShdColor `xml:"BackColor,omitempty"`
|
||||
}
|
||||
|
||||
// ShdPoint 网格控制点及基本颜色
|
||||
type ShdPoint struct {
|
||||
X float64 `xml:"X,attr"`
|
||||
Y float64 `xml:"Y,attr"`
|
||||
EdgeFlag int `xml:"EdgeFlag,attr,omitempty"`
|
||||
Color ShdColor `xml:"Color"`
|
||||
}
|
||||
|
||||
// MeshVertex 保存局部毫米坐标、原颜色分量及非预乘透明度
|
||||
type MeshVertex struct {
|
||||
Point Point
|
||||
Space string
|
||||
Values [4]float64
|
||||
Alpha float64
|
||||
}
|
||||
|
||||
// MeshShading 保存后端无关的三角网格,后出现的三角形覆盖先出现的三角形
|
||||
type MeshShading struct {
|
||||
Triangles [][3]MeshVertex
|
||||
Background color.RGBA
|
||||
}
|
||||
|
||||
// outline 返回方向一致的三角形轮廓,供绘制裁剪和对象度量共用
|
||||
// 返回: GeometryPath 非零绕数填充轮廓
|
||||
func (m *MeshShading) outline() GeometryPath {
|
||||
var path GeometryPath
|
||||
for _, t := range m.Triangles {
|
||||
a, b, c := t[0].Point, t[1].Point, t[2].Point
|
||||
cross := (b.X-a.X)*(c.Y-a.Y) - (b.Y-a.Y)*(c.X-a.X)
|
||||
if cross == 0 {
|
||||
continue
|
||||
}
|
||||
if cross < 0 {
|
||||
b, c = c, b
|
||||
}
|
||||
path = append(path, GeometrySegment{Verb: GeometryMove, End: a}, GeometrySegment{Verb: GeometryLine, End: b}, GeometrySegment{Verb: GeometryLine, End: c}, GeometrySegment{Verb: GeometryClose})
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// resolveMesh 按GB/T 33190的三角形连接规则解析网格
|
||||
// 入参: fill 网格颜色
|
||||
// 返回: *MeshShading 网格画刷, error 无效控制点
|
||||
func (r *Renderer) resolveMesh(fill *FillColor) (*MeshShading, error) {
|
||||
var points []ShdPoint
|
||||
var back *ShdColor
|
||||
var extend, columns int
|
||||
if node := fill.GouraudShd; node != nil {
|
||||
points, back, extend = node.Point, node.BackColor, node.Extend
|
||||
} else {
|
||||
node := fill.LaGouraudShd
|
||||
points, back, extend, columns = node.Point, node.BackColor, node.Extend, node.VerticesPerRow
|
||||
if columns == 0 {
|
||||
return nil, fmt.Errorf("invalid lattice shading dimensions")
|
||||
}
|
||||
}
|
||||
indices, err := meshTriangleIndices(points, columns, extend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vertices := make([]MeshVertex, len(points))
|
||||
for i, point := range points {
|
||||
space := point.Color.ColorSpace
|
||||
if space == "" {
|
||||
space = fill.ColorSpace
|
||||
}
|
||||
kind, values := r.colorComponents(point.Color.Value, point.Color.Index, space)
|
||||
alpha := 1.0
|
||||
if value := mergeAlpha(point.Color.Alpha, fill.Alpha); value != nil {
|
||||
alpha = float64(*value) / 255
|
||||
}
|
||||
vertices[i] = MeshVertex{Point{point.X, point.Y}, kind, values, alpha}
|
||||
}
|
||||
mesh := &MeshShading{}
|
||||
if extend == 1 && back != nil {
|
||||
space := back.ColorSpace
|
||||
if space == "" {
|
||||
space = fill.ColorSpace
|
||||
}
|
||||
mesh.Background = r.ResolveColor(back.Value, back.Index, space, mergeAlpha(back.Alpha, fill.Alpha))
|
||||
}
|
||||
for _, triangle := range indices {
|
||||
mesh.Triangles = append(mesh.Triangles, [3]MeshVertex{vertices[triangle[0]], vertices[triangle[1]], vertices[triangle[2]]})
|
||||
}
|
||||
for i := range mesh.Triangles {
|
||||
t := &mesh.Triangles[i]
|
||||
if t[0].Space != t[1].Space || t[0].Space != t[2].Space {
|
||||
for j := range t {
|
||||
r, g, b := meshRGB(t[j].Space, t[j].Values)
|
||||
t[j].Space, t[j].Values = "RGB", [4]float64{r, g, b}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mesh, nil
|
||||
}
|
||||
|
||||
// meshTriangleIndices 校验控制点并生成共享边或格构三角形编号
|
||||
// 入参: points 控制点, columns 每行点数,零表示非格构, extend 延伸标志
|
||||
// 返回: [][3]int 三角形顶点编号, error 非法拓扑
|
||||
func meshTriangleIndices(points []ShdPoint, columns, extend int) ([][3]int, error) {
|
||||
if len(points) < 3 || extend < 0 || extend > 1 {
|
||||
return nil, fmt.Errorf("invalid mesh shading")
|
||||
}
|
||||
if columns != 0 && (columns < 2 || columns > len(points)/2 || len(points)%columns != 0) {
|
||||
return nil, fmt.Errorf("invalid lattice shading dimensions")
|
||||
}
|
||||
for i, p := range points {
|
||||
if !finite(p.X) || !finite(p.Y) || p.EdgeFlag < 0 || p.EdgeFlag > 2 {
|
||||
return nil, fmt.Errorf("invalid mesh shading point %d", i)
|
||||
}
|
||||
}
|
||||
var result [][3]int
|
||||
if columns != 0 {
|
||||
for i := columns; i < len(points); i++ {
|
||||
if i%columns != 0 {
|
||||
result = append(result, [3]int{i - columns - 1, i - columns, i - 1}, [3]int{i - columns, i - 1, i})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
var triangle [3]int
|
||||
for i := 0; i < len(points); {
|
||||
switch points[i].EdgeFlag {
|
||||
case 0:
|
||||
if len(points)-i < 3 {
|
||||
return nil, fmt.Errorf("incomplete mesh shading triangle")
|
||||
}
|
||||
triangle = [3]int{i, i + 1, i + 2}
|
||||
i += 3
|
||||
case 1, 2:
|
||||
if i == 0 {
|
||||
return nil, fmt.Errorf("mesh shading starts with a shared edge")
|
||||
}
|
||||
if points[i].EdgeFlag == 1 {
|
||||
triangle[0] = triangle[1]
|
||||
}
|
||||
triangle[1], triangle[2] = triangle[2], i
|
||||
i++
|
||||
}
|
||||
result = append(result, triangle)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// At 按三角形面积权重插值原颜色分量,返回预乘RGBA
|
||||
// 入参: x 横坐标, y 纵坐标
|
||||
// 返回: color.RGBA 采样颜色
|
||||
func (m *MeshShading) At(x, y float64) color.RGBA {
|
||||
for i := len(m.Triangles) - 1; i >= 0; i-- {
|
||||
t := m.Triangles[i]
|
||||
a, b, c := t[0].Point, t[1].Point, t[2].Point
|
||||
d := (b.Y-c.Y)*(a.X-c.X) + (c.X-b.X)*(a.Y-c.Y)
|
||||
if d == 0 {
|
||||
continue
|
||||
}
|
||||
u := ((b.Y-c.Y)*(x-c.X) + (c.X-b.X)*(y-c.Y)) / d
|
||||
v := ((c.Y-a.Y)*(x-c.X) + (a.X-c.X)*(y-c.Y)) / d
|
||||
w := 1 - u - v
|
||||
if u < -1e-12 || v < -1e-12 || w < -1e-12 {
|
||||
continue
|
||||
}
|
||||
var values [4]float64
|
||||
alpha := 0.0
|
||||
for j, weight := range [3]float64{u, v, w} {
|
||||
alpha += weight * t[j].Alpha
|
||||
for k := range values {
|
||||
values[k] += weight * t[j].Values[k]
|
||||
}
|
||||
}
|
||||
red, green, blue := meshRGB(t[0].Space, values)
|
||||
return color.RGBA{meshByte(red * alpha), meshByte(green * alpha), meshByte(blue * alpha), meshByte(alpha)}
|
||||
}
|
||||
return m.Background
|
||||
}
|
||||
|
||||
// meshRGB 将插值后的颜色分量转换为RGB
|
||||
// 入参: space 颜色模型, values 分量
|
||||
// 返回: float64 红、绿、蓝分量
|
||||
func meshRGB(space string, values [4]float64) (float64, float64, float64) {
|
||||
switch space {
|
||||
case "GRAY":
|
||||
return values[0], values[0], values[0]
|
||||
case "CMYK":
|
||||
return (1 - values[0]) * (1 - values[3]), (1 - values[1]) * (1 - values[3]), (1 - values[2]) * (1 - values[3])
|
||||
default:
|
||||
return values[0], values[1], values[2]
|
||||
}
|
||||
}
|
||||
|
||||
// meshByte 将归一化颜色分量量化为八位整数
|
||||
// 入参: value 分量
|
||||
// 返回: uint8 量化结果
|
||||
func meshByte(value float64) uint8 { return uint8(math.Round(math.Max(0, math.Min(1, value)) * 255)) }
|
||||
+17
-8
@@ -22,7 +22,7 @@ type ColorStop struct {
|
||||
Color color.RGBA
|
||||
}
|
||||
|
||||
// PaintKind 区分无画刷、纯色、轴向渐变、径向渐变和图案
|
||||
// PaintKind 区分无画刷、纯色、轴向渐变、径向渐变、图案和网格渐变
|
||||
type PaintKind uint8
|
||||
|
||||
const (
|
||||
@@ -31,6 +31,7 @@ const (
|
||||
PaintLinear
|
||||
PaintRadial
|
||||
PaintPattern
|
||||
PaintMesh
|
||||
)
|
||||
|
||||
// Paint 保存后端无关的颜色、渐变和图案,不修改源节点
|
||||
@@ -39,6 +40,7 @@ type Paint struct {
|
||||
Color color.RGBA
|
||||
Gradient *Shading
|
||||
Pattern *PatternPaint
|
||||
Mesh *MeshShading
|
||||
}
|
||||
|
||||
// Shading 保存OFD对象局部坐标中的渐变,单位为毫米,纵轴向下
|
||||
@@ -53,17 +55,24 @@ type Shading struct {
|
||||
|
||||
// ResolvePaint 解析颜色、透明度、渐变分段和图案,不依赖绘图库
|
||||
// 入参: fill 填充或转换后的描边节点
|
||||
// 返回: Paint 只读画刷
|
||||
func (r *Renderer) ResolvePaint(fill *FillColor) Paint {
|
||||
// 返回: Paint 只读画刷, error 无效渐变
|
||||
func (r *Renderer) ResolvePaint(fill *FillColor) (Paint, error) {
|
||||
if fill == nil {
|
||||
return Paint{}
|
||||
return Paint{}, nil
|
||||
}
|
||||
if fill.Pattern != nil {
|
||||
return Paint{Kind: PaintPattern, Pattern: parsePatternPaint(fill)}
|
||||
return Paint{Kind: PaintPattern, Pattern: parsePatternPaint(fill)}, nil
|
||||
}
|
||||
if fill.GouraudShd != nil || fill.LaGouraudShd != nil {
|
||||
mesh, err := r.resolveMesh(fill)
|
||||
if err != nil {
|
||||
return Paint{}, err
|
||||
}
|
||||
return Paint{Kind: PaintMesh, Mesh: mesh}, nil
|
||||
}
|
||||
base := r.parseFillColor(fill)
|
||||
if base == nil {
|
||||
return Paint{}
|
||||
return Paint{}, nil
|
||||
}
|
||||
paint := Paint{Kind: PaintSolid, Color: colorToRGBA(base)}
|
||||
if node := fill.AxialShd; node != nil {
|
||||
@@ -72,7 +81,7 @@ func (r *Renderer) ResolvePaint(fill *FillColor) Paint {
|
||||
if len(start) >= 2 && len(end) >= 2 && len(stops) != 0 && (!geometryEqual(start[0], end[0]) || !geometryEqual(start[1], end[1])) {
|
||||
paint.Kind = PaintLinear
|
||||
paint.Gradient = &Shading{Start: Point{X: start[0], Y: start[1]}, End: Point{X: end[0], Y: end[1]}, Stops: stops, Extend: node.Extend, MapType: node.MapType, MapUnit: node.MapUnit}
|
||||
return paint
|
||||
return paint, nil
|
||||
}
|
||||
}
|
||||
if node := fill.RadialShd; node != nil && node.EndRadius > 0 {
|
||||
@@ -83,5 +92,5 @@ func (r *Renderer) ResolvePaint(fill *FillColor) Paint {
|
||||
paint.Gradient = &Shading{Start: Point{X: start[0], Y: start[1]}, End: Point{X: end[0], Y: end[1]}, StartRadius: node.StartRadius, EndRadius: node.EndRadius, Stops: stops, Extend: node.Extend, MapType: node.MapType, MapUnit: node.MapUnit, Eccentricity: node.Eccentricity, Angle: node.Angle}
|
||||
}
|
||||
}
|
||||
return paint
|
||||
return paint, nil
|
||||
}
|
||||
+42
-1
@@ -81,15 +81,18 @@ type RasterGradientKind uint8
|
||||
const (
|
||||
RasterLinear RasterGradientKind = iota
|
||||
RasterRadial
|
||||
RasterMesh
|
||||
)
|
||||
|
||||
// RasterGradient 局部坐标下的线性或双圆径向渐变,Spread为空时延续边界颜色
|
||||
// RasterGradient 局部坐标下的轴向、双圆径向或三角网格渐变
|
||||
// 轴向及径向渐变的Spread为空时延续边界颜色,网格渐变使用Mesh
|
||||
type RasterGradient struct {
|
||||
Kind RasterGradientKind
|
||||
Start, End RasterPoint
|
||||
R0, R1 float64
|
||||
Stops []ColorStop
|
||||
Spread *RasterSpread
|
||||
Mesh *MeshShading
|
||||
}
|
||||
|
||||
// RasterSpread 保存OFD渐变延伸和周期,nil表示连续延伸
|
||||
@@ -140,12 +143,22 @@ func (m RasterMatrix) Inverse() (RasterMatrix, error) {
|
||||
// 入参: x 横坐标, y 纵坐标
|
||||
// 返回: color.RGBA 采样颜色
|
||||
func (g *RasterGradient) At(x, y float64) color.RGBA {
|
||||
if g.Kind == RasterMesh {
|
||||
return g.Mesh.At(x, y)
|
||||
}
|
||||
dx, dy := g.End.X-g.Start.X, g.End.Y-g.Start.Y
|
||||
px, py := x-g.Start.X, y-g.Start.Y
|
||||
t := 0.0
|
||||
if g.Kind == RasterRadial {
|
||||
dr := g.R1 - g.R0
|
||||
a, b, c := dx*dx+dy*dy-dr*dr, -2*(px*dx+py*dy+g.R0*dr), px*px+py*py-g.R0*g.R0
|
||||
if g.Spread != nil && math.Hypot(dx, dy) >= math.Abs(dr) {
|
||||
var ok bool
|
||||
t, ok = rasterRadialDomain(a, b, c, g.R0, dr, g.Spread.Extend)
|
||||
if !ok {
|
||||
return color.RGBA{}
|
||||
}
|
||||
} else {
|
||||
t = rasterRadialPosition(a, b, c, g.R0, dr)
|
||||
if g.Spread != nil && math.Hypot(dx, dy) < math.Abs(dr) {
|
||||
d := b*b - 4*a*c
|
||||
@@ -154,6 +167,7 @@ func (g *RasterGradient) At(x, y float64) color.RGBA {
|
||||
}
|
||||
t = (-b - math.Copysign(math.Sqrt(d), dr)) / (2 * a)
|
||||
}
|
||||
}
|
||||
} else if length := dx*dx + dy*dy; length > 0 {
|
||||
t = (px*dx + py*dy) / length
|
||||
}
|
||||
@@ -175,6 +189,33 @@ func (g *RasterGradient) At(x, y float64) color.RGBA {
|
||||
return g.colorAt(t)
|
||||
}
|
||||
|
||||
// rasterRadialDomain 求延伸区间内半径非负的最后一个双圆参数
|
||||
// 入参: a、b、c 方程系数, radius 起始半径, delta 半径变化, extend 延伸标志
|
||||
// 返回: float64 参数, bool 是否覆盖采样点
|
||||
func rasterRadialDomain(a, b, c, radius, delta float64, extend int) (float64, bool) {
|
||||
x, y := math.NaN(), math.NaN()
|
||||
if a == 0 {
|
||||
if b != 0 {
|
||||
x = -c / b
|
||||
}
|
||||
} else if d := b*b - 4*a*c; d >= 0 {
|
||||
q := -.5 * (b + math.Copysign(math.Sqrt(d), b))
|
||||
if q == 0 {
|
||||
x = -b / (2 * a)
|
||||
} else {
|
||||
x, y = q/a, c/q
|
||||
}
|
||||
}
|
||||
best := math.Inf(-1)
|
||||
for _, t := range []float64{x, y} {
|
||||
if !finite(t) || radius+delta*t < 0 || t < 0 && extend&1 == 0 || t > 1 && extend&2 == 0 {
|
||||
continue
|
||||
}
|
||||
best = math.Max(best, t)
|
||||
}
|
||||
return best, !math.IsInf(best, -1)
|
||||
}
|
||||
|
||||
// rasterRadialPosition 求双圆渐变中半径非负的有效参数
|
||||
// 入参: a 二次项系数, b 一次项系数, c 常数项, radius 起始半径, delta 半径增量
|
||||
// 返回: float64 渐变位置,范围为0至1
|
||||
|
||||
@@ -127,6 +127,11 @@ func cloneRasterPage(page *RasterPage) *RasterPage {
|
||||
if cmd.Paint.Gradient != nil {
|
||||
gradient := *cmd.Paint.Gradient
|
||||
gradient.Stops = slices.Clone(gradient.Stops)
|
||||
if gradient.Mesh != nil {
|
||||
mesh := *gradient.Mesh
|
||||
mesh.Triangles = slices.Clone(mesh.Triangles)
|
||||
gradient.Mesh = &mesh
|
||||
}
|
||||
if gradient.Spread != nil {
|
||||
spread := *gradient.Spread
|
||||
gradient.Spread = &spread
|
||||
|
||||
@@ -79,6 +79,9 @@ func rasterCommandCost(command RasterCommand) int {
|
||||
}
|
||||
if command.Paint.Gradient != nil {
|
||||
cost += 256 + len(command.Paint.Gradient.Stops)*64
|
||||
if mesh := command.Paint.Gradient.Mesh; mesh != nil {
|
||||
cost += 32 + len(mesh.Triangles)*240
|
||||
}
|
||||
}
|
||||
return cost
|
||||
}
|
||||
+52
-10
@@ -137,10 +137,10 @@ func (c *semanticCompiler) fill(path GeometryPath, paint Paint, evenOdd bool, cl
|
||||
return c.command(path, paint, evenOdd, clip, shadingMatrix, nil)
|
||||
}
|
||||
var err error
|
||||
if clip != nil || paint.Kind == PaintPattern || paint.Gradient != nil {
|
||||
if clip != nil || paint.Kind == PaintPattern || paint.Gradient != nil || paint.Mesh != nil {
|
||||
path = closedGeometry(path)
|
||||
}
|
||||
if evenOdd && (clip != nil || paint.Kind == PaintPattern || paint.Gradient != nil) {
|
||||
if evenOdd && (clip != nil || paint.Kind == PaintPattern || paint.Gradient != nil || paint.Mesh != nil) {
|
||||
path, err = c.geometry.Normalize(path, true)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -158,6 +158,20 @@ func (c *semanticCompiler) fill(path GeometryPath, paint Paint, evenOdd bool, cl
|
||||
}
|
||||
}
|
||||
if c.measure {
|
||||
if paint.Mesh != nil && paint.Mesh.Background.A == 0 {
|
||||
outline, err := c.geometry.Normalize(paint.Mesh.outline(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outline, err = c.geometry.Transform(outline, shadingMatrix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path, err = clipGeometry(c.geometry, path, &outline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.addBounds(path, evenOdd)
|
||||
}
|
||||
if paint.Kind == PaintPattern {
|
||||
@@ -192,6 +206,18 @@ func (c *semanticCompiler) command(path GeometryPath, paint Paint, evenOdd bool,
|
||||
command.Transform = RasterMatrix(view.Values())
|
||||
command.Paint.Gradient = gradient
|
||||
}
|
||||
if paint.Mesh != nil {
|
||||
inverse, ok := shadingMatrix.Invert()
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid mesh shading transform")
|
||||
}
|
||||
path, err = c.geometry.Transform(path, inverse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command.Transform = RasterMatrix(shadingMatrix.Values())
|
||||
command.Paint.Gradient = &RasterGradient{Kind: RasterMesh, Mesh: paint.Mesh}
|
||||
}
|
||||
command.Path, err = c.segments(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -238,9 +264,6 @@ func semanticGradient(paint Paint, matrix Matrix) (*RasterGradient, Matrix) {
|
||||
g.End.X, g.End.Y = inverse.Transform(g.End.X, g.End.Y)
|
||||
matrix = matrix.Multiply(view)
|
||||
}
|
||||
if math.Hypot(g.End.X-g.Start.X, g.End.Y-g.Start.Y) >= math.Abs(g.R1-g.R0) {
|
||||
g.Spread = nil
|
||||
}
|
||||
}
|
||||
return g, matrix
|
||||
}
|
||||
@@ -358,12 +381,20 @@ func (c *semanticCompiler) pathObject(object PathObject, state RenderState) erro
|
||||
style := c.objectStyle(object, state.Defaults, linear)
|
||||
shading, _ := renderObjectMatrix(object.Boundary, IdentityMatrix, state)
|
||||
if object.Fill != nil && *object.Fill {
|
||||
if err := c.fill(path, c.renderer.ResolvePaint(style.fill), object.Rule == "Even-Odd", clip, matrix, shading); err != nil {
|
||||
paint, err := c.renderer.ResolvePaint(style.fill)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.fill(path, paint, object.Rule == "Even-Odd", clip, matrix, shading); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if object.Stroke == nil || *object.Stroke {
|
||||
return c.stroke(path, c.renderer.ResolvePaint(style.stroke), style.options, clip, matrix, shading)
|
||||
paint, err := c.renderer.ResolvePaint(style.stroke)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.stroke(path, paint, style.options, clip, matrix, shading)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -397,18 +428,29 @@ func (c *semanticCompiler) textObject(object TextObject, state RenderState) erro
|
||||
style.fill = withFillAlpha(&FillColor{}, object.Alpha)
|
||||
}
|
||||
shading, _ := renderObjectMatrix(object.Boundary, IdentityMatrix, state)
|
||||
fill, err := c.renderer.ResolvePaint(style.fill)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var stroke Paint
|
||||
if object.Stroke != nil && *object.Stroke {
|
||||
stroke, err = c.renderer.ResolvePaint(style.stroke)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, glyph := range positioned.Glyphs {
|
||||
if object.Fill == nil || *object.Fill {
|
||||
if err := c.fill(glyph.Path, c.renderer.ResolvePaint(style.fill), false, positioned.Clip, positioned.Matrix, shading); err != nil {
|
||||
if err := c.fill(glyph.Path, fill, false, positioned.Clip, positioned.Matrix, shading); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if object.Stroke != nil && *object.Stroke && len(glyph.Path) > 0 {
|
||||
if err := c.stroke(glyph.Path, c.renderer.ResolvePaint(style.stroke), style.options, positioned.Clip, positioned.Matrix, shading); err != nil {
|
||||
if err := c.stroke(glyph.Path, stroke, style.options, positioned.Clip, positioned.Matrix, shading); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.fill(glyph.Underline, c.renderer.ResolvePaint(style.fill), false, positioned.Clip, positioned.Matrix, shading); err != nil {
|
||||
if err := c.fill(glyph.Underline, fill, false, positioned.Clip, positioned.Matrix, shading); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ func reflectShdPosition(position float64) float64 {
|
||||
func (c *semanticCompiler) shadingClip(path GeometryPath, paint Paint, matrix Matrix) (GeometryPath, error) {
|
||||
gradient, view := semanticGradient(paint, matrix)
|
||||
spread := gradient.Spread
|
||||
if gradient.Kind == RasterRadial && math.Hypot(gradient.End.X-gradient.Start.X, gradient.End.Y-gradient.Start.Y) >= math.Abs(gradient.R1-gradient.R0) {
|
||||
return path, nil
|
||||
}
|
||||
if spread == nil || spread.Extend == 3 {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@@ -111,11 +111,13 @@ func (b CanvasBackend) Render(page *RasterPage) (image.Image, error) {
|
||||
paint = canvas.Paint{Gradient: stops.ToLinear(start, end)}
|
||||
case RasterRadial:
|
||||
paint = canvas.Paint{Gradient: stops.ToRadial(start, g.R0, end, g.R1)}
|
||||
case RasterMesh:
|
||||
paint = canvas.Paint{Gradient: rasterGradientCanvas{gradient: g}}
|
||||
default:
|
||||
return nil, fmt.Errorf("canvas command %d: unsupported gradient %d", i, g.Kind)
|
||||
}
|
||||
if g.Spread != nil {
|
||||
paint = canvas.Paint{Gradient: rasterGradientCanvas{g}}
|
||||
paint = canvas.Paint{Gradient: rasterGradientCanvas{gradient: g}}
|
||||
}
|
||||
}
|
||||
style := canvas.Style{Fill: paint, FillRule: canvas.NonZero}
|
||||
@@ -134,7 +136,7 @@ func (b CanvasBackend) Render(page *RasterPage) (image.Image, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paint = canvas.Paint{Gradient: rasterStrokeGradientCanvas{rasterGradientCanvas{cmd.Paint.Gradient}, inverse}}
|
||||
paint = canvas.Paint{Gradient: rasterStrokeGradientCanvas{rasterGradientCanvas{gradient: cmd.Paint.Gradient}, inverse}}
|
||||
}
|
||||
line := pathStyle{lineJoin: canvas.MiterJoin, miterLimit: defaultMiterLimit}
|
||||
line.applyLineJoin(options.Join, options.MiterLimit)
|
||||
@@ -182,7 +184,7 @@ type rasterStrokeGradientCanvas struct {
|
||||
// 返回: color.RGBA 预乘颜色
|
||||
func (g rasterStrokeGradientCanvas) At(x, y float64) color.RGBA {
|
||||
p := g.inverse.Apply(RasterPoint{x, y})
|
||||
return g.gradient.At(p.X, p.Y)
|
||||
return g.rasterGradientCanvas.At(p.X, p.Y)
|
||||
}
|
||||
|
||||
// SetColorSpace 转换描边渐变颜色,不修改共享分段
|
||||
@@ -194,21 +196,34 @@ func (g rasterStrokeGradientCanvas) SetColorSpace(space canvas.ColorSpace) canva
|
||||
}
|
||||
|
||||
// rasterGradientCanvas 将公共渐变交给Canvas采样,不改变OFD周期语义
|
||||
type rasterGradientCanvas struct{ gradient *RasterGradient }
|
||||
type rasterGradientCanvas struct {
|
||||
gradient *RasterGradient
|
||||
space canvas.ColorSpace
|
||||
}
|
||||
|
||||
// At 返回局部坐标的预乘颜色
|
||||
// 入参: x 横坐标, y 纵坐标
|
||||
// 返回: color.RGBA 渐变颜色
|
||||
func (g rasterGradientCanvas) At(x, y float64) color.RGBA { return g.gradient.At(x, y) }
|
||||
func (g rasterGradientCanvas) At(x, y float64) color.RGBA {
|
||||
value := g.gradient.At(x, y)
|
||||
if g.space != nil {
|
||||
return g.space.ToLinear(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// SetColorSpace 返回转换颜色空间后的独立渐变
|
||||
// 入参: space 目标颜色空间
|
||||
// 返回: canvas.Gradient 采样器
|
||||
func (g rasterGradientCanvas) SetColorSpace(space canvas.ColorSpace) canvas.Gradient {
|
||||
if g.gradient.Mesh != nil {
|
||||
g.space = space
|
||||
return g
|
||||
}
|
||||
copy := *g.gradient
|
||||
copy.Stops = append([]ColorStop(nil), copy.Stops...)
|
||||
for i := range copy.Stops {
|
||||
copy.Stops[i].Color = space.ToLinear(copy.Stops[i].Color)
|
||||
}
|
||||
return rasterGradientCanvas{©}
|
||||
return rasterGradientCanvas{gradient: ©}
|
||||
}
|
||||
@@ -100,6 +100,10 @@ func (c *canvasPageCompiler) fill(path *canvas.Path, paint canvas.Paint, rule ca
|
||||
g := &RasterGradient{}
|
||||
var stops canvas.Grad
|
||||
switch source := paint.Gradient.(type) {
|
||||
case rasterGradientCanvas:
|
||||
copy := *source.gradient
|
||||
g = ©
|
||||
stops = canvasGradient(g.Stops)
|
||||
case *canvas.LinearGradient:
|
||||
g.Kind, g.Start, g.End = RasterLinear, rasterPoint(source.Start), rasterPoint(source.End)
|
||||
stops = source.Grad
|
||||
|
||||
@@ -24,12 +24,18 @@ import (
|
||||
// 入参: fillColor 填充颜色节点, x X坐标, y Y坐标, pageH 页面高度
|
||||
// 返回: any 填充画刷
|
||||
func (r *Renderer) parseFillPaint(fillColor *FillColor, x, y, pageH float64) any {
|
||||
paint := r.ResolvePaint(fillColor)
|
||||
paint, err := r.ResolvePaint(fillColor)
|
||||
if err != nil {
|
||||
r.renderError = err
|
||||
return nil
|
||||
}
|
||||
switch paint.Kind {
|
||||
case PaintNone, PaintPattern:
|
||||
return nil
|
||||
case PaintLinear, PaintRadial:
|
||||
return canvasShading(paint, x, y, pageH)
|
||||
case PaintMesh:
|
||||
return &shdPaint{gradient: rasterGradientCanvas{gradient: &RasterGradient{Kind: RasterMesh, Mesh: paint.Mesh}}, view: canvas.Matrix{{1, 0, x}, {0, -1, pageH - y}}}
|
||||
default:
|
||||
return paint.Color
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
||||
ctx.SetFillRule(fillRule)
|
||||
ctx.SetFill(fillPaint)
|
||||
ctx.SetStrokeColor(canvas.Transparent)
|
||||
drawShdPath(ctx, fp, fillView)
|
||||
r.drawShdPath(ctx, fp, fillView)
|
||||
}
|
||||
}
|
||||
shouldStroke := true
|
||||
@@ -306,7 +306,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
||||
} else {
|
||||
ctx.SetFill(strokePaint)
|
||||
ctx.SetStrokeColor(canvas.Transparent)
|
||||
drawShdPath(ctx, sp, strokeView)
|
||||
r.drawShdPath(ctx, sp, strokeView)
|
||||
}
|
||||
} else {
|
||||
ctx.DrawPath(0, 0, sp)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package ofdgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"strconv"
|
||||
@@ -22,6 +23,43 @@ import (
|
||||
"github.com/tdewolff/canvas"
|
||||
)
|
||||
|
||||
// drawSampledShading 按输出精度采样后端不能原生表达的局部渐变,不改写OFD对象
|
||||
// 入参: ctx 绘制上下文, path 已裁剪轮廓, view 渐变变换, gradient 渐变采样器
|
||||
func (r *Renderer) drawSampledShading(ctx *canvas.Context, path *canvas.Path, view canvas.Matrix, gradient rasterGradientCanvas) {
|
||||
if path.Empty() {
|
||||
return
|
||||
}
|
||||
bounds := path.FastBounds().And(shdCanvasBounds(ctx))
|
||||
if bounds.W() <= 0 || bounds.H() <= 0 {
|
||||
return
|
||||
}
|
||||
scale := r.DPI / 25.4
|
||||
w, h := math.Ceil(bounds.W()*scale), math.Ceil(bounds.H()*scale)
|
||||
page := &RasterPage{Width: w / scale, Height: h / scale, DPI: r.DPI}
|
||||
if _, _, err := page.PixelSize(); err != nil {
|
||||
r.renderError = err
|
||||
return
|
||||
}
|
||||
compiler := &canvasPageCompiler{page: page, geometry: r.backends.Geometry}
|
||||
style := ctx.Style
|
||||
style.Fill = canvas.Paint{Gradient: gradient}
|
||||
compiler.RenderPath(path.Copy().Transform(view.Inv()), style, canvas.Identity.Translate(-bounds.X0, -bounds.Y0).Mul(view))
|
||||
if compiler.err != nil {
|
||||
r.renderError = compiler.err
|
||||
return
|
||||
}
|
||||
if r.backends.Raster == nil {
|
||||
r.renderError = fmt.Errorf("shading raster: %w", ErrBackendUnavailable)
|
||||
return
|
||||
}
|
||||
img, err := r.backends.Raster.Render(page)
|
||||
if err != nil {
|
||||
r.renderError = err
|
||||
return
|
||||
}
|
||||
ctx.DrawImage(bounds.X0, bounds.Y0, img, canvas.DPMM(scale))
|
||||
}
|
||||
|
||||
// shdPaint 渐变画刷
|
||||
type shdPaint struct {
|
||||
gradient canvas.Gradient
|
||||
@@ -84,6 +122,13 @@ func resolveShdPaint(ctx *canvas.Context, paint any) (any, *canvas.Path, canvas.
|
||||
return nil, &canvas.Path{}, canvas.Identity
|
||||
}
|
||||
switch g := s.gradient.(type) {
|
||||
case rasterGradientCanvas:
|
||||
if g.gradient.Mesh != nil && g.gradient.Mesh.Background.A == 0 {
|
||||
outline := g.gradient.Mesh.outline()
|
||||
clip, _ := canvasObjectPath(outline)
|
||||
return g, clip.Settle(canvas.NonZero).Transform(s.view), s.view
|
||||
}
|
||||
return g, nil, s.view
|
||||
case *canvas.LinearGradient:
|
||||
bounds := shdCanvasBounds(ctx).Transform(s.view.Inv())
|
||||
clip := axialShdClip(g, s.extend, bounds)
|
||||
@@ -108,8 +153,13 @@ func resolveShdPaint(ctx *canvas.Context, paint any) (any, *canvas.Path, canvas.
|
||||
case *canvas.RadialGradient:
|
||||
d, dr := g.C1.Sub(g.C0), g.R1-g.R0
|
||||
length := d.Length()
|
||||
if length >= math.Abs(dr) {
|
||||
return g, nil, s.view
|
||||
if length >= math.Abs(dr) || s.mapType == "Repeat" {
|
||||
stops := make([]ColorStop, len(g.Grad))
|
||||
for i, stop := range g.Grad {
|
||||
stops[i] = ColorStop{Offset: stop.Offset, Color: stop.Color}
|
||||
}
|
||||
gradient := &RasterGradient{Kind: RasterRadial, Start: rasterPoint(g.C0), End: rasterPoint(g.C1), R0: g.R0, R1: g.R1, Stops: stops, Spread: &RasterSpread{Extend: s.extend, MapType: s.mapType, Period: s.period}}
|
||||
return rasterGradientCanvas{gradient: gradient}, nil, s.view
|
||||
}
|
||||
bounds := shdCanvasBounds(ctx).Transform(s.view.Inv())
|
||||
clip := radialShdClip(g, s.extend, bounds)
|
||||
@@ -161,7 +211,15 @@ func transformShdPaint(paint any, parentCTM *Matrix, bx, by, pageH float64, boun
|
||||
|
||||
// drawShdPath 绘制渐变路径并保持图形轮廓不变
|
||||
// 入参: ctx 画布上下文, path 填充路径, view 渐变变换
|
||||
func drawShdPath(ctx *canvas.Context, path *canvas.Path, view canvas.Matrix) {
|
||||
func (r *Renderer) drawShdPath(ctx *canvas.Context, path *canvas.Path, view canvas.Matrix) {
|
||||
if gradient, ok := ctx.Style.Fill.Gradient.(rasterGradientCanvas); ok {
|
||||
if _, compiling := ctx.Renderer.(*canvasPageCompiler); !compiling {
|
||||
if _, measuring := ctx.Renderer.(*boundsRenderer); !measuring {
|
||||
r.drawSampledShading(ctx, path, view, gradient)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if gradient, ok := ctx.Style.Fill.Gradient.(*repeatAxialGradient); ok {
|
||||
drawRepeatAxialPath(ctx, path, gradient, view)
|
||||
return
|
||||
|
||||
@@ -293,7 +293,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
} else if shouldFill && fillPaint != nil {
|
||||
ctx.SetFill(fillPaint)
|
||||
ctx.SetStrokeColor(canvas.Transparent)
|
||||
drawShdPath(ctx, applyClipPath(path.Copy(), fillClip), fillView)
|
||||
r.drawShdPath(ctx, applyClipPath(path.Copy(), fillClip), fillView)
|
||||
}
|
||||
if len(strokeStyle.dashPattern) > 0 {
|
||||
path = path.Dash(strokeStyle.dashOffset, strokeStyle.dashPattern...)
|
||||
@@ -305,7 +305,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
} else {
|
||||
ctx.SetFill(strokeStyle.strokePaint)
|
||||
ctx.SetStrokeColor(canvas.Transparent)
|
||||
drawShdPath(ctx, path, strokeView)
|
||||
r.drawShdPath(ctx, path, strokeView)
|
||||
}
|
||||
if hasUnderline {
|
||||
underline := &canvas.Path{}
|
||||
@@ -320,7 +320,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
} else if strokeStyle.strokePattern != nil {
|
||||
r.renderPattern(ctx, strokeStyle.strokePattern, pageH, applyClipPath(underline.Transform(transform), strokeClip), objectCTM)
|
||||
} else {
|
||||
drawShdPath(ctx, applyClipPath(underline.Transform(transform), strokeClip), strokeView)
|
||||
r.drawShdPath(ctx, applyClipPath(underline.Transform(transform), strokeClip), strokeView)
|
||||
}
|
||||
}
|
||||
continue
|
||||
@@ -342,7 +342,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
||||
if fillPattern != nil {
|
||||
r.renderPattern(ctx, fillPattern, pageH, glyphPath, objectCTM)
|
||||
} else {
|
||||
drawShdPath(ctx, glyphPath, fillView)
|
||||
r.drawShdPath(ctx, glyphPath, fillView)
|
||||
}
|
||||
if hasUnderline {
|
||||
uw := sizeMM * 0.05
|
||||
|
||||
@@ -855,6 +855,42 @@ func (x *ofdXML) color(name string, color *FillColor) {
|
||||
if color.Pattern != nil {
|
||||
x.pattern(color.Pattern)
|
||||
}
|
||||
if color.GouraudShd != nil {
|
||||
node := color.GouraudShd
|
||||
x.meshShading("GouraudShd", node.Extend, 0, node.Point, node.BackColor)
|
||||
}
|
||||
if color.LaGouraudShd != nil {
|
||||
node := color.LaGouraudShd
|
||||
x.meshShading("LaGouraudShd", node.Extend, node.VerticesPerRow, node.Point, node.BackColor)
|
||||
}
|
||||
x.end(name)
|
||||
}
|
||||
|
||||
// meshShading 写出网格控制点,仅非格构渐变保存共享边标志
|
||||
// 入参: name 元素名称, extend 延伸标志, columns 格构列数, points 控制点, back 背景颜色
|
||||
func (x *ofdXML) meshShading(name string, extend, columns int, points []ShdPoint, back *ShdColor) {
|
||||
var attrs ofdAttrs
|
||||
if extend != 0 {
|
||||
attrs.add("Extend", strconv.Itoa(extend))
|
||||
}
|
||||
if columns != 0 {
|
||||
attrs.add("VerticesPerRow", strconv.Itoa(columns))
|
||||
}
|
||||
x.start(name, attrs)
|
||||
for _, point := range points {
|
||||
var attrs ofdAttrs
|
||||
attrs.add("X", ofdNumber(point.X))
|
||||
attrs.add("Y", ofdNumber(point.Y))
|
||||
if columns == 0 && point.EdgeFlag != 0 {
|
||||
attrs.add("EdgeFlag", strconv.Itoa(point.EdgeFlag))
|
||||
}
|
||||
x.start("Point", attrs)
|
||||
x.element("Color", point.Color)
|
||||
x.end("Point")
|
||||
}
|
||||
if back != nil {
|
||||
x.element("BackColor", back)
|
||||
}
|
||||
x.end(name)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user