diff --git a/ofdgo_paint.go b/ofdgo_paint.go index c37a27e..6ad80c9 100644 --- a/ofdgo_paint.go +++ b/ofdgo_paint.go @@ -16,6 +16,7 @@ package ofdgo import ( "image/color" + "math" "strconv" "strings" @@ -154,19 +155,19 @@ func parseFillColor(fillColor *FillColor) color.Color { } // parseFillPaint 解析填充画刷 -// 入参: fillColor 填充颜色节点, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标 +// 入参: fillColor 填充颜色节点, x X坐标, y Y坐标, pageH 页面高度 // 返回: any 填充画刷 -func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64) any { +func parseFillPaint(fillColor *FillColor, x, y, pageH 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 { + if gradient := parseAxialShdGradient(fillColor.AxialShd, fillColor.Alpha, x, y, pageH); gradient != nil { return gradient } - if gradient := parseRadialShdGradient(fillColor.RadialShd, fillColor.Alpha, x, y, pageH, originX, originY); gradient != nil { + if gradient := parseRadialShdGradient(fillColor.RadialShd, fillColor.Alpha, x, y, pageH); gradient != nil { return gradient } if fillColor.AxialShd != nil { @@ -201,16 +202,16 @@ func parseStrokeColor(strokeColor *StrokeColor) color.Color { } // parseStrokePaint 解析勾边画刷 -// 入参: strokeColor 勾边颜色节点, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标 +// 入参: strokeColor 勾边颜色节点, x X坐标, y Y坐标, pageH 页面高度 // 返回: any 勾边画刷 -func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH, originX, originY float64) any { +func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float64) any { if strokeColor == nil { return nil } - if gradient := parseAxialShdGradient(strokeColor.AxialShd, strokeColor.Alpha, x, y, pageH, originX, originY); gradient != nil { + if gradient := parseAxialShdGradient(strokeColor.AxialShd, strokeColor.Alpha, x, y, pageH); gradient != nil { return gradient } - if gradient := parseRadialShdGradient(strokeColor.RadialShd, strokeColor.Alpha, x, y, pageH, originX, originY); gradient != nil { + if gradient := parseRadialShdGradient(strokeColor.RadialShd, strokeColor.Alpha, x, y, pageH); gradient != nil { return gradient } if strokeColor.AxialShd != nil { @@ -287,9 +288,9 @@ func parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad { } // parseAxialShdGradient 解析轴向渐变 -// 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标 +// 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度 // 返回: canvas.Gradient 渐变对象 -func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX, originY float64) canvas.Gradient { +func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH float64) canvas.Gradient { if axialShd == nil { return nil } @@ -302,18 +303,51 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX, if gradient == nil { 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} + startPoint := canvas.Point{X: x + start[0], Y: pageH - (y + start[1])} + endPoint := canvas.Point{X: x + end[0], Y: pageH - (y + end[1])} if startPoint.Equals(endPoint) { return nil } return gradient.ToLinear(startPoint, endPoint) } +// axialShdClip 获取轴向渐变的延伸裁剪区域 +// 入参: ctx 画布上下文, paint 画刷, shading 轴向渐变节点 +// 返回: *canvas.Path 裁剪区域 +func axialShdClip(ctx *canvas.Context, paint any, shading *AxialShd) *canvas.Path { + gradient, ok := paint.(*canvas.LinearGradient) + if !ok || shading == nil { + return nil + } + extend, _ := strconv.Atoi(shading.Extend) + if extend == 3 { + return nil + } + d := gradient.End.Sub(gradient.Start) + axis := canvas.Matrix{{d.X, -d.Y, gradient.Start.X}, {d.Y, d.X, gradient.Start.Y}} + origin := ctx.CoordView().Dot(canvas.Point{}) + view := ctx.CoordSystemView().Mul(ctx.View()).Translate(origin.X, origin.Y).Mul(axis) + if view.Det() == 0 { + return &canvas.Path{} + } + width, height := ctx.Size() + area := (canvas.Rect{X1: width, Y1: height}).Transform(view.Inv()) + if extend&1 == 0 { + area.X0 = math.Max(area.X0, 0) + } + if extend&2 == 0 { + area.X1 = math.Min(area.X1, 1) + } + if area.X1 <= area.X0 { + return &canvas.Path{} + } + return area.ToPath().Transform(axis) +} + // parseRadialShdGradient 解析径向渐变 -// 入参: radialShd 径向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标 +// 入参: radialShd 径向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度 // 返回: canvas.Gradient 渐变对象 -func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, originX, originY float64) canvas.Gradient { +func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH float64) canvas.Gradient { if radialShd == nil || radialShd.EndRadius <= 0 { return nil } @@ -326,8 +360,8 @@ func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, origi if gradient == nil { 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} + startPoint := canvas.Point{X: x + start[0], Y: pageH - (y + start[1])} + endPoint := canvas.Point{X: x + end[0], Y: pageH - (y + end[1])} return gradient.ToRadial(startPoint, radialShd.StartRadius, endPoint, radialShd.EndRadius) } diff --git a/ofdgo_renderer_path.go b/ofdgo_renderer_path.go index bbc7c5b..2068630 100644 --- a/ofdgo_renderer_path.go +++ b/ofdgo_renderer_path.go @@ -30,6 +30,8 @@ type pathStyle struct { strokeColor color.Color fillPaint any strokePaint any + fillAxialShd *AxialShd + strokeAxialShd *AxialShd fillPattern *Pattern fillPatternColor color.Color lineWidth float64 @@ -65,7 +67,8 @@ func (s *pathStyle) applyFillColor(fill *FillColor, bx, by, pageH float64, alpha s.fillPattern = fillColorNode.Pattern s.fillPatternColor = patternColor(fillColorNode) s.fillColor = parseFillColor(fillColorNode) - s.fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) + s.fillPaint = parseFillPaint(fillColorNode, bx, by, pageH) + s.fillAxialShd = fillColorNode.AxialShd } // applyStrokeColor 应用描边颜色 @@ -73,7 +76,8 @@ func (s *pathStyle) applyFillColor(fill *FillColor, bx, by, pageH float64, alpha func (s *pathStyle) applyStrokeColor(stroke *StrokeColor, bx, by, pageH float64, alpha *int) { strokeColorNode := withStrokeAlpha(stroke, alpha) s.strokeColor = parseStrokeColor(strokeColorNode) - s.strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH, 0, 0) + s.strokePaint = parseStrokePaint(strokeColorNode, bx, by, pageH) + s.strokeAxialShd = strokeColorNode.AxialShd } // pathLineCap 转换线帽样式 @@ -211,18 +215,19 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 style.fillPaint = style.fillColor } if shouldFill { + fillClip := intersectClipPath(clipPath, axialShdClip(ctx, style.fillPaint, style.fillAxialShd)) fp := p fillRule := canvas.NonZero if obj.Rule == "Even-Odd" { fillRule = canvas.EvenOdd - if clipPath != nil || style.fillPattern != nil { + if fillClip != nil || style.fillPattern != nil { fp = fp.Settle(fillRule) } } - if clipPath != nil { + if fillClip != nil { fp = fp.Copy() fp.Close() - fp = applyClipPath(fp, clipPath) + fp = applyClipPath(fp, fillClip) } if style.fillPattern != nil { r.renderPattern(ctx, style.fillPattern, style.fillPatternColor, pageH, fp, ctm, bx, by) @@ -253,13 +258,14 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if len(style.dashPattern) > 0 { ctx.SetDashes(style.dashOffset, style.dashPattern...) } - if clipPath != nil { + strokeClip := intersectClipPath(clipPath, axialShdClip(ctx, style.strokePaint, style.strokeAxialShd)) + if strokeClip != nil { sp := p.Copy() if len(style.dashPattern) > 0 { sp = sp.Dash(style.dashOffset, style.dashPattern...) } sp = sp.Stroke(style.lineWidth, style.lineCap, style.lineJoin, canvas.Tolerance) - sp = applyClipPath(sp, clipPath) + sp = applyClipPath(sp, strokeClip) ctx.SetFill(style.strokePaint) ctx.SetStrokeColor(canvas.Transparent) ctx.DrawPath(0, 0, sp) @@ -543,7 +549,6 @@ func applyClipPath(path, clip *canvas.Path) *canvas.Path { if pathRect, ok := rectangularPath(path); ok { return pathRect.And(rect).ToPath() } - return path.Flatten(canvas.Tolerance).Clip(rect.X0, rect.Y0, rect.X1, rect.Y1) } return path.And(clip) } diff --git a/ofdgo_renderer_text.go b/ofdgo_renderer_text.go index 63f743e..6bbc0f1 100644 --- a/ofdgo_renderer_text.go +++ b/ofdgo_renderer_text.go @@ -80,17 +80,22 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 if dp != nil && dp.FillColor != nil { fillColorNode = withFillAlpha(dp.FillColor, obj.Alpha) fillColor = parseFillColor(fillColorNode) - fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) + fillPaint = parseFillPaint(fillColorNode, bx, by, pageH) } if obj.FillColor != nil { fillColorNode = withFillAlpha(obj.FillColor, obj.Alpha) fillColor = parseFillColor(fillColorNode) - fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0) + fillPaint = parseFillPaint(fillColorNode, bx, by, pageH) } if fillPaint == nil { fillPaint = fillColor } + fillClip := clipPath + if fillColorNode != nil { + fillClip = intersectClipPath(fillClip, axialShdClip(ctx, fillPaint, fillColorNode.AxialShd)) + } strokeStyle := newPathStyle(nil, defaultStroke, 0, obj.Alpha) + strokeClip := clipPath if shouldStroke { if dp != nil { strokeStyle.applyDrawParam(dp, bx, by, pageH, obj.Alpha) @@ -105,6 +110,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 strokeStyle.strokePaint = colorWithAlpha(canvas.Black, obj.Alpha) } strokeStyle.scale(ctm) + strokeClip = intersectClipPath(strokeClip, axialShdClip(ctx, strokeStyle.strokePaint, strokeStyle.strokeAxialShd)) } fontStyle := canvas.FontRegular weight := obj.Weight @@ -144,7 +150,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 face := ff.Face(sizePt, fillPaint, fontStyle, canvas.FontNormal) glyphTransforms := r.textObjectGlyphTransforms(fontID, obj) hasUnderline := strings.Contains(obj.Decoration, "Underline") - useGlyphFillPaint := fillColorNode != nil && fillColorNode.AxialShd != nil + _, axialFill := fillPaint.(*canvas.LinearGradient) verticalAdvance := (obj.ReadDirection-obj.CharDirection)%180 != 0 advanceX, advanceY := 1.0, 0.0 switch obj.ReadDirection { @@ -168,7 +174,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } dxs, dys := parseFloats(tc.DeltaX), parseFloats(tc.DeltaY) xs, ys := parseFloats(tc.X), parseFloats(tc.Y) - drawAsPath := embeddedFont || textCodePositioned(tc, xs, ys) || clipPath != nil || shouldStroke + drawAsPath := embeddedFont || textCodePositioned(tc, xs, ys) || fillClip != nil || axialFill || shouldStroke cx, cy := 0.0, 0.0 previousAdvance := 0.0 if len(xs) > 0 { @@ -219,10 +225,6 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 canvasX, canvasY = tx+bx, pageH-(ty+by) } textWidth := glyphWidth * hScale - glyphFillPaint := fillPaint - if useGlyphFillPaint { - glyphFillPaint = parseFillPaint(fillColorNode, bx, by, pageH, canvasX, canvasY) - } advanceLimit := 0.0 if obj.CharDirection == 0 { advanceLimit = textGlyphAdvanceLimit(dxs, dys, xs, i, len(glyphs), cx) @@ -237,10 +239,10 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 transform = transform.Mul(glyphMatrix) } path := glyphPath.Copy().Transform(transform.Scale(scaleX, 1)) - if shouldFill && glyphFillPaint != nil { - ctx.SetFill(glyphFillPaint) + if shouldFill && fillPaint != nil { + ctx.SetFill(fillPaint) ctx.SetStrokeColor(canvas.Transparent) - ctx.DrawPath(0, 0, applyClipPath(path.Copy(), clipPath)) + ctx.DrawPath(0, 0, applyClipPath(path.Copy(), fillClip)) } if len(strokeStyle.dashPattern) > 0 { path = path.Dash(strokeStyle.dashOffset, strokeStyle.dashPattern...) @@ -248,7 +250,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 path = path.Stroke(strokeStyle.lineWidth, strokeStyle.lineCap, strokeStyle.lineJoin, canvas.Tolerance) ctx.SetFill(strokeStyle.strokePaint) ctx.SetStrokeColor(canvas.Transparent) - ctx.DrawPath(0, 0, applyClipPath(path, clipPath)) + ctx.DrawPath(0, 0, applyClipPath(path, strokeClip)) if hasUnderline { underline := &canvas.Path{} underline.MoveTo(0, -sizeMM*0.1) @@ -261,9 +263,9 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } continue } - if glyphFillPaint != nil { - ctx.SetFill(glyphFillPaint) - if clipPath != nil { + if fillPaint != nil { + ctx.SetFill(fillPaint) + if fillClip != nil || axialFill { scaleX := hScale if advanceLimit > 0 && glyphWidth*scaleX > advanceLimit { scaleX = advanceLimit / glyphWidth @@ -273,7 +275,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 if useTextMatrix { textTransform = textTransform.Mul(glyphMatrix) } - glyphPath = applyClipPath(glyphPath.Copy().Transform(textTransform.Scale(scaleX, 1)), clipPath) + glyphPath = applyClipPath(glyphPath.Copy().Transform(textTransform.Scale(scaleX, 1)), fillClip) ctx.DrawPath(0, 0, glyphPath) if hasUnderline { uw := sizeMM * 0.05 @@ -313,11 +315,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 ctx.Scale(hScale, 1) x, y = 0, 0 } - textFace := face - if useGlyphFillPaint { - textFace = ff.Face(sizePt, glyphFillPaint, fontStyle, canvas.FontNormal) - } - text := canvas.NewTextLine(textFace, str, canvas.Left) + text := canvas.NewTextLine(face, str, canvas.Left) ctx.DrawText(x, y, text) if scaled { ctx.Pop()