From 0b20a3d054eaa89698721278bab7dc0cbbecf24e Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Thu, 25 Jun 2026 21:34:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9B=BE=E5=BD=A2=E7=BB=98=E5=88=B6):=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9B=B4=E5=A4=9A=E7=9A=84=E5=9B=BE=E5=BD=A2?= =?UTF-8?q?=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_page.go | 28 +++++++++++++----- ofdgo_renderer.go | 61 ++++++++++++++++++++++++++++++++++++-- ofdgo_text.go | 75 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 151 insertions(+), 13 deletions(-) diff --git a/ofdgo_page.go b/ofdgo_page.go index fd472f6..b065d6c 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -58,7 +58,8 @@ type GraphicObject struct { // Clips 裁剪区域集合 type Clips struct { - Clip []Clip `xml:"Clip"` + TransFlag bool `xml:"TransFlag,attr"` + Clip []Clip `xml:"Clip"` } // Clip 裁剪 @@ -68,6 +69,7 @@ type Clip struct { // ClipArea 裁剪区域 type ClipArea struct { + CTM string `xml:"CTM,attr"` Path []PathObject `xml:"Path"` Text []TextObject `xml:"Text"` } @@ -97,9 +99,10 @@ type TextObject struct { // FillColor 填充颜色 type FillColor struct { - Value string `xml:"Value,attr"` - Alpha *int `xml:"Alpha,attr"` - AxialShd *AxialShd `xml:"AxialShd"` + Value string `xml:"Value,attr"` + Alpha *int `xml:"Alpha,attr"` + AxialShd *AxialShd `xml:"AxialShd"` + RadialShd *RadialShd `xml:"RadialShd"` } // TextCode 文本内容节点 @@ -142,9 +145,10 @@ type PathObject struct { // StrokeColor 勾边颜色 type StrokeColor struct { - Value string `xml:"Value,attr"` - Alpha *int `xml:"Alpha,attr"` - AxialShd *AxialShd `xml:"AxialShd"` + Value string `xml:"Value,attr"` + Alpha *int `xml:"Alpha,attr"` + AxialShd *AxialShd `xml:"AxialShd"` + RadialShd *RadialShd `xml:"RadialShd"` } // AxialShd 轴向渐变 @@ -155,6 +159,16 @@ type AxialShd struct { Segment []ShdSegment `xml:"Segment"` } +// RadialShd 径向渐变 +type RadialShd struct { + Extend string `xml:"Extend,attr"` + StartPoint string `xml:"StartPoint,attr"` + StartRadius float64 `xml:"StartRadius,attr"` + EndPoint string `xml:"EndPoint,attr"` + EndRadius float64 `xml:"EndRadius,attr"` + Segment []ShdSegment `xml:"Segment"` +} + // ShdSegment 渐变分段 type ShdSegment struct { Position float64 `xml:"Position,attr"` diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index e681896..525b258 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -557,6 +557,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if rectPath := r.buildTinyFillRectPath(obj, pageH, ctm, bx, by); rectPath != nil { p = rectPath } + clipPath := r.buildClipPath(obj.Clips, pageH, bx, by, ctm) shouldFill := true if obj.Fill != nil { shouldFill = *obj.Fill @@ -567,7 +568,13 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if shouldFill && fillPaint != nil { ctx.SetFill(fillPaint) ctx.SetStrokeColor(canvas.Transparent) - ctx.DrawPath(0, 0, p) + fp := p + if clipPath != nil { + fp = p.Copy() + fp.Close() + fp = fp.And(clipPath) + } + ctx.DrawPath(0, 0, fp) } shouldStroke := true if obj.Stroke != nil { @@ -588,7 +595,19 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if len(dashPattern) > 0 { ctx.SetDashes(dashOffset, dashPattern...) } - ctx.DrawPath(0, 0, p) + if clipPath != nil { + sp := p.Copy() + if len(dashPattern) > 0 { + sp = sp.Dash(dashOffset, dashPattern...) + } + sp = sp.Stroke(lineWidth, lineCap, lineJoin, canvas.Tolerance) + sp = sp.And(clipPath) + ctx.SetFill(strokePaint) + ctx.SetStrokeColor(canvas.Transparent) + ctx.DrawPath(0, 0, sp) + } else { + ctx.DrawPath(0, 0, p) + } } ctx.Pop() } @@ -1158,6 +1177,44 @@ func (r *Renderer) buildPath(obj PathObject, pageH float64, ctm Matrix) *canvas. return p } +// buildClipPath 构建裁剪路径 +// 入参: clips 裁剪对象, pageH 页面高度, bx 边界X坐标, by 边界Y坐标, objectCTM 对象CTM +// 返回: *canvas.Path 路径对象 +func (r *Renderer) buildClipPath(clips *Clips, pageH float64, bx, by float64, objectCTM Matrix) *canvas.Path { + if clips == nil { + return nil + } + var p *canvas.Path + for _, clip := range clips.Clip { + var clipPath *canvas.Path + for _, area := range clip.Area { + areaCTM := NewMatrix(area.CTM) + if clips.TransFlag { + areaCTM = objectCTM.Multiply(areaCTM) + } + for _, pathObj := range area.Path { + ctm := areaCTM.Multiply(NewMatrix(pathObj.CTM)) + cp := r.buildPath(pathObj, pageH, ctm) + cp.Translate(bx, -by) + cp.Close() + if clipPath == nil { + clipPath = cp + } else { + clipPath = clipPath.Or(cp) + } + } + } + if clipPath != nil { + if p == nil { + p = clipPath + } else { + p = p.And(clipPath) + } + } + } + return p +} + // buildTinyFillRectPath 构建微小填充矩形路径 // 入参: obj 路径对象, pageH 页面高度, ctm 变换矩阵, bx 边界X坐标, by 边界Y坐标 // 返回: *canvas.Path 路径对象 diff --git a/ofdgo_text.go b/ofdgo_text.go index 25f45b9..2116bfc 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -57,7 +57,10 @@ func parseFillColor(fillColor *FillColor) color.Color { if strings.TrimSpace(fillColor.Value) != "" { return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) } - return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha) + if fillColor.AxialShd != nil { + return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha) + } + return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha) } // parseFillPaint 解析填充画刷 @@ -73,7 +76,13 @@ func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64) if gradient := parseAxialShdGradient(fillColor.AxialShd, fillColor.Alpha, x, y, pageH, originX, originY); gradient != nil { return gradient } - return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha) + 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) + } + return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha) } // parseStrokeColor 解析勾边颜色 @@ -86,7 +95,10 @@ func parseStrokeColor(strokeColor *StrokeColor) color.Color { if strings.TrimSpace(strokeColor.Value) != "" { return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha) } - return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha) + if strokeColor.AxialShd != nil { + return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha) + } + return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha) } // parseStrokePaint 解析勾边画刷 @@ -102,7 +114,13 @@ func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH, originX, originY fl if gradient := parseAxialShdGradient(strokeColor.AxialShd, strokeColor.Alpha, x, y, pageH, originX, originY); gradient != nil { return gradient } - return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha) + 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) + } + return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha) } // parseAxialShdColor 解析轴向渐变颜色 @@ -157,6 +175,55 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX, 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颜色