diff --git a/ofdgo_page.go b/ofdgo_page.go index d792e5c..4e6a2d2 100644 --- a/ofdgo_page.go +++ b/ofdgo_page.go @@ -82,6 +82,8 @@ type TextObject struct { Boundary string `xml:"Boundary,attr"` DrawParam string `xml:"DrawParam,attr"` LineWidth float64 `xml:"LineWidth,attr"` + Join string `xml:"Join,attr"` + MiterLimit float64 `xml:"MiterLimit,attr"` Font string `xml:"Font,attr"` Size float64 `xml:"Size,attr"` Weight int `xml:"Weight,attr"` diff --git a/ofdgo_paint.go b/ofdgo_paint.go index a163b3f..ced31db 100644 --- a/ofdgo_paint.go +++ b/ofdgo_paint.go @@ -215,18 +215,18 @@ func (r *Renderer) parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float6 type patternPaint struct { *Pattern - color color.Color + color FillColor alpha *int } // parsePatternPaint 解析底纹画刷 -// 入参: pattern 底纹对象, value 颜色值, index 调色板索引, space 颜色空间标识, alpha 透明度 +// 入参: fill 填充颜色节点 // 返回: *patternPaint 底纹画刷 -func (r *Renderer) parsePatternPaint(pattern *Pattern, value string, index *int, space string, alpha *int) *patternPaint { - if pattern == nil { +func parsePatternPaint(fill *FillColor) *patternPaint { + if fill.Pattern == nil { return nil } - return &patternPaint{Pattern: pattern, color: r.parseColorWithAlpha(value, index, space, nil), alpha: alpha} + return &patternPaint{Pattern: fill.Pattern, color: FillColor{Value: fill.Value, Index: fill.Index, ColorSpace: fill.ColorSpace}, alpha: fill.Alpha} } // parseShdColor 解析渐变颜色 @@ -299,21 +299,15 @@ func (r *Renderer) parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, p } // axialShdClip 获取轴向渐变的延伸裁剪区域 -// 入参: ctx 画布上下文, gradient 轴向渐变, extend 延伸方向 +// 入参: gradient 轴向渐变, extend 延伸方向, bounds 可见区域 // 返回: *canvas.Path 裁剪区域 -func axialShdClip(ctx *canvas.Context, gradient *canvas.LinearGradient, extend int) *canvas.Path { +func axialShdClip(gradient *canvas.LinearGradient, extend int, bounds canvas.Rect) *canvas.Path { 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()) + area := bounds.Transform(axis.Inv()) if extend&1 == 0 { area.X0 = math.Max(area.X0, 0) } diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index 1a12029..480c1c6 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -126,7 +126,7 @@ func (r *Renderer) renderPageToContext(ctx *canvas.Context, page *PageContent, d } if page.Content.Layer != nil { for _, layer := range page.Content.Layer { - r.renderLayer(ctx, layer, pageH, nil, nil, 0, nil) + r.renderLayer(ctx, layer, pageH, nil, nil) } } if len(page.Template) > 0 && r.Reader.doc != nil { diff --git a/ofdgo_renderer_layer.go b/ofdgo_renderer_layer.go index ca968ac..c1e4545 100644 --- a/ofdgo_renderer_layer.go +++ b/ofdgo_renderer_layer.go @@ -14,11 +14,7 @@ package ofdgo -import ( - "image/color" - - "github.com/tdewolff/canvas" -) +import "github.com/tdewolff/canvas" // renderAnnotations 渲染页面注释外观 // 入参: ctx 画布上下文, pageID 页面ID, pageH 页面高度 @@ -30,7 +26,7 @@ func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH f box, _ := ParseBox(annot.Appearance.Boundary) ctm := Matrix{a: 1, d: 1, e: box.X, f: box.Y} for _, obj := range annot.Appearance.Objects { - r.renderObject(ctx, obj, pageH, nil, nil, 0, &ctm, false, nil) + r.renderObject(ctx, obj, pageH, nil, &ctm, false, nil) } } } @@ -59,38 +55,38 @@ func (r *Renderer) renderTemplate(ctx *canvas.Context, templateID string, pageH } if tplContent.Content.Layer != nil { for _, layer := range tplContent.Content.Layer { - r.renderLayer(ctx, layer, pageH, nil, nil, 0, nil) + r.renderLayer(ctx, layer, pageH, nil, nil) } } } // renderLayer 渲染图层 -// 入参: ctx 画布上下文, layer 图层对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM -func (r *Renderer) renderLayer(ctx *canvas.Context, layer Layer, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) { - defaultFill, defaultStroke, defaultLW = r.drawParamDefaults(layer.DrawParam, defaultFill, defaultStroke, defaultLW) +// 入参: ctx 画布上下文, layer 图层对象, pageH 页面高度, defaults 默认绘制参数, parentCTM 父级CTM +func (r *Renderer) renderLayer(ctx *canvas.Context, layer Layer, pageH float64, defaults *DrawParam, parentCTM *Matrix) { + defaults = r.drawParamDefaults(layer.DrawParam, defaults) if len(layer.Objects) > 0 { for _, obj := range layer.Objects { - r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false, nil) + r.renderObject(ctx, obj, pageH, defaults, parentCTM, false, nil) } return } for _, textObj := range layer.TextObject { - r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, parentCTM, false, nil) + r.renderText(ctx, textObj, pageH, defaults, parentCTM, false, nil) } for _, pathObj := range layer.PathObject { - r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false, nil) + r.renderPath(ctx, pathObj, pageH, defaults, parentCTM, false, nil) } for _, imgObj := range layer.ImageObject { r.renderImage(ctx, imgObj, pageH, parentCTM, false, nil) } for _, cgu := range layer.CompositeGraphicUnit { - r.renderCompositeGraphicUnit(ctx, cgu, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, false, nil) + r.renderCompositeGraphicUnit(ctx, cgu, pageH, defaults, parentCTM, false, nil) } } // renderCompositeGraphicUnit 渲染复合图元 -// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 -func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { +// 入参: ctx 画布上下文, cgu 复合图元对象, pageH 页面高度, defaults 默认绘制参数, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 +func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu CompositeGraphicUnit, pageH float64, defaults *DrawParam, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if cgu.Visible != nil && !*cgu.Visible { return } @@ -109,18 +105,18 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite clips, clipCTM = &clipCopy, boundaryCTM } clipPath := intersectClipPath(parentClip, r.buildClipPath(clips, pageH, 0, 0, clipCTM)) + defaults = r.drawParamDefaults(cgu.DrawParam, defaults) if cgu.ResourceID != "" { if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok { refCopy := *ref refCopy.Alpha = mergeAlpha(refCopy.Alpha, cgu.Alpha) - r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, true, clipPath) + r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaults, ¤tCTM, true, clipPath) } } - defaultFill, defaultStroke, defaultLW = r.drawParamDefaults(cgu.DrawParam, defaultFill, defaultStroke, defaultLW) if len(cgu.Objects) > 0 { for _, obj := range cgu.Objects { obj = mergeGraphicObjectAlpha(obj, cgu.Alpha) - r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM, clipPath) + r.renderObject(ctx, obj, pageH, defaults, ¤tCTM, boundaryInCTM, clipPath) } ctx.Pop() return @@ -131,31 +127,31 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite } for _, pathObj := range cgu.PathObject { pathObj.Alpha = mergeAlpha(pathObj.Alpha, cgu.Alpha) - r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM, clipPath) + r.renderPath(ctx, pathObj, pageH, defaults, ¤tCTM, boundaryInCTM, clipPath) } for _, textObj := range cgu.TextObject { textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha) - r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM, boundaryInCTM, clipPath) + r.renderText(ctx, textObj, pageH, defaults, ¤tCTM, boundaryInCTM, clipPath) } for _, subCgu := range cgu.CompositeGraphicUnit { subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha) - r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM, boundaryInCTM, clipPath) + r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaults, ¤tCTM, boundaryInCTM, clipPath) } ctx.Pop() } // renderObject 渲染图形对象 -// 入参: ctx 画布上下文, obj 图形对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 -func (r *Renderer) renderObject(ctx *canvas.Context, obj GraphicObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { +// 入参: ctx 画布上下文, obj 图形对象, pageH 页面高度, defaults 默认绘制参数, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 +func (r *Renderer) renderObject(ctx *canvas.Context, obj GraphicObject, pageH float64, defaults *DrawParam, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { switch obj.Type { case "TextObject": - r.renderText(ctx, obj.TextObject, pageH, defaultFill, defaultStroke, parentCTM, boundaryInCTM, parentClip) + r.renderText(ctx, obj.TextObject, pageH, defaults, parentCTM, boundaryInCTM, parentClip) case "PathObject": - r.renderPath(ctx, obj.PathObject, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, boundaryInCTM, parentClip) + r.renderPath(ctx, obj.PathObject, pageH, defaults, parentCTM, boundaryInCTM, parentClip) case "ImageObject": r.renderImage(ctx, obj.ImageObject, pageH, parentCTM, boundaryInCTM, parentClip) case "CompositeGraphicUnit", "CompositeObject": - r.renderCompositeGraphicUnit(ctx, obj.CompositeGraphicUnit, pageH, defaultFill, defaultStroke, defaultLW, parentCTM, boundaryInCTM, parentClip) + r.renderCompositeGraphicUnit(ctx, obj.CompositeGraphicUnit, pageH, defaults, parentCTM, boundaryInCTM, parentClip) } } @@ -180,26 +176,61 @@ func mergeGraphicObjectAlpha(obj GraphicObject, alpha *int) GraphicObject { } // drawParamDefaults 合并绘制参数默认样式 -// 入参: id 绘制参数ID, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽 -// 返回: color.Color 默认填充色, color.Color 默认描边色, float64 默认线宽 -func (r *Renderer) drawParamDefaults(id string, defaultFill, defaultStroke color.Color, defaultLW float64) (color.Color, color.Color, float64) { +// 入参: id 绘制参数ID, defaults 默认绘制参数 +// 返回: *DrawParam 合并后的绘制参数 +func (r *Renderer) drawParamDefaults(id string, defaults *DrawParam) *DrawParam { if id == "" { - return defaultFill, defaultStroke, defaultLW + return defaults } dp := r.getDrawParam(id, nil) if dp == nil { - return defaultFill, defaultStroke, defaultLW + return defaults } + if defaults == nil { + return dp + } + return mergeDrawParam(*defaults, dp) +} + +// mergeDrawParam 合并绘制参数属性 +// 入参: base 基础绘制参数, dp 覆盖绘制参数 +// 返回: *DrawParam 合并后的绘制参数 +func mergeDrawParam(base DrawParam, dp *DrawParam) *DrawParam { if dp.LineWidth > 0 { - defaultLW = dp.LineWidth + base.LineWidth = dp.LineWidth + } + if dp.Join != "" { + base.Join = dp.Join + } + if dp.Cap != "" { + base.Cap = dp.Cap + } + if dp.DashPattern != "" { + base.DashPattern = dp.DashPattern + base.DashOffset = dp.DashOffset + } + if dp.MiterLimit > 0 { + base.MiterLimit = dp.MiterLimit } if dp.FillColor != nil { - defaultFill = r.parseFillColor(dp.FillColor) + base.FillColor = dp.FillColor } if dp.StrokeColor != nil { - defaultStroke = r.parseStrokeColor(dp.StrokeColor) + base.StrokeColor = dp.StrokeColor } - return defaultFill, defaultStroke, defaultLW + if dp.Font != "" { + base.Font = dp.Font + } + if dp.Size > 0 { + base.Size = dp.Size + } + if dp.Weight > 0 { + base.Weight = dp.Weight + } + if dp.Italic { + base.Italic = dp.Italic + } + return &base } // getDrawParam 获取绘制参数逻辑 @@ -219,42 +250,7 @@ func (r *Renderer) getDrawParam(id string, visited map[string]bool) *DrawParam { if base == nil { return dp } - merged := *base - if dp.LineWidth > 0 { - merged.LineWidth = dp.LineWidth - } - if dp.Join != "" { - merged.Join = dp.Join - } - if dp.Cap != "" { - merged.Cap = dp.Cap - } - if dp.DashPattern != "" { - merged.DashPattern = dp.DashPattern - merged.DashOffset = dp.DashOffset - } - if dp.MiterLimit > 0 { - merged.MiterLimit = dp.MiterLimit - } - if dp.FillColor != nil { - merged.FillColor = dp.FillColor - } - if dp.StrokeColor != nil { - merged.StrokeColor = dp.StrokeColor - } - if dp.Font != "" { - merged.Font = dp.Font - } - if dp.Size > 0 { - merged.Size = dp.Size - } - if dp.Weight > 0 { - merged.Weight = dp.Weight - } - if dp.Italic { - merged.Italic = dp.Italic - } - return &merged + return mergeDrawParam(*base, dp) } return dp } diff --git a/ofdgo_renderer_path.go b/ofdgo_renderer_path.go index e395b34..211e6c6 100644 --- a/ofdgo_renderer_path.go +++ b/ofdgo_renderer_path.go @@ -24,6 +24,7 @@ import ( ) const defaultPathLineWidth = 0.353 +const defaultMiterLimit = 3.528 type pathStyle struct { fillColor color.Color @@ -35,26 +36,24 @@ type pathStyle struct { lineWidth float64 lineCap canvas.Capper lineJoin canvas.Joiner + miterLimit float64 dashOffset float64 dashPattern []float64 } // newPathStyle 创建路径样式 -// 入参: defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, alpha 对象透明度 +// 入参: dp 默认绘制参数, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 // 返回: pathStyle 路径样式 -func newPathStyle(defaultFill, defaultStroke color.Color, defaultLW float64, alpha *int) pathStyle { +func (r *Renderer) newPathStyle(dp *DrawParam, bx, by, pageH float64, alpha *int) pathStyle { style := pathStyle{ - fillColor: colorWithAlpha(defaultFill, alpha), - strokeColor: colorWithAlpha(defaultStroke, alpha), - lineWidth: defaultLW, - lineCap: canvas.ButtCap, - lineJoin: canvas.MiterJoin, + lineWidth: defaultPathLineWidth, + lineCap: canvas.ButtCap, + lineJoin: canvas.MiterJoiner{GapJoiner: canvas.BevelJoin, Limit: defaultMiterLimit}, + miterLimit: defaultMiterLimit, } - if style.lineWidth == 0 { - style.lineWidth = defaultPathLineWidth + if dp != nil { + style.applyDrawParam(r, dp, bx, by, pageH, alpha) } - style.fillPaint = style.fillColor - style.strokePaint = style.strokeColor return style } @@ -62,7 +61,7 @@ func newPathStyle(defaultFill, defaultStroke color.Color, defaultLW float64, alp // 入参: r 渲染器, fill 填充颜色, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 func (s *pathStyle) applyFillColor(r *Renderer, fill *FillColor, bx, by, pageH float64, alpha *int) { fillColorNode := withFillAlpha(fill, alpha) - s.fillPattern = r.parsePatternPaint(fillColorNode.Pattern, fillColorNode.Value, fillColorNode.Index, fillColorNode.ColorSpace, fillColorNode.Alpha) + s.fillPattern = parsePatternPaint(fillColorNode) s.fillColor = r.parseFillColor(fillColorNode) s.fillPaint = r.parseFillPaint(fillColorNode, bx, by, pageH) } @@ -71,7 +70,7 @@ func (s *pathStyle) applyFillColor(r *Renderer, fill *FillColor, bx, by, pageH f // 入参: r 渲染器, stroke 描边颜色, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, alpha 对象透明度 func (s *pathStyle) applyStrokeColor(r *Renderer, stroke *StrokeColor, bx, by, pageH float64, alpha *int) { strokeColorNode := withStrokeAlpha(stroke, alpha) - s.strokePattern = r.parsePatternPaint(strokeColorNode.Pattern, strokeColorNode.Value, strokeColorNode.Index, strokeColorNode.ColorSpace, strokeColorNode.Alpha) + s.strokePattern = parsePatternPaint((*FillColor)(strokeColorNode)) s.strokeColor = r.parseStrokeColor(strokeColorNode) s.strokePaint = r.parseStrokePaint(strokeColorNode, bx, by, pageH) } @@ -81,6 +80,8 @@ func (s *pathStyle) applyStrokeColor(r *Renderer, stroke *StrokeColor, bx, by, p // 返回: canvas.Capper 线帽样式 func pathLineCap(cap string, fallback canvas.Capper) canvas.Capper { switch cap { + case "Butt": + return canvas.ButtCap case "Round": return canvas.RoundCap case "Square": @@ -89,17 +90,23 @@ func pathLineCap(cap string, fallback canvas.Capper) canvas.Capper { return fallback } -// pathLineJoin 转换线连接样式 -// 入参: join 线连接名称, fallback 默认线连接 -// 返回: canvas.Joiner 线连接样式 -func pathLineJoin(join string, fallback canvas.Joiner) canvas.Joiner { +// applyLineJoin 应用线连接样式及尖角限制 +// 入参: join 线连接名称, limit 尖角限制 +func (s *pathStyle) applyLineJoin(join string, limit float64) { + if limit > 0 { + s.miterLimit = limit + } switch join { + case "Miter": + s.lineJoin = canvas.MiterJoin case "Round": - return canvas.RoundJoin + s.lineJoin = canvas.RoundJoin case "Bevel": - return canvas.BevelJoin + s.lineJoin = canvas.BevelJoin + } + if _, ok := s.lineJoin.(canvas.MiterJoiner); ok { + s.lineJoin = canvas.MiterJoiner{GapJoiner: canvas.BevelJoin, Limit: s.miterLimit} } - return fallback } // applyDrawParam 应用绘制参数样式 @@ -117,9 +124,7 @@ func (s *pathStyle) applyDrawParam(r *Renderer, dp *DrawParam, bx, by, pageH flo if dp.Cap != "" { s.lineCap = pathLineCap(dp.Cap, s.lineCap) } - if dp.Join != "" { - s.lineJoin = pathLineJoin(dp.Join, s.lineJoin) - } + s.applyLineJoin(dp.Join, dp.MiterLimit) if dp.DashPattern != "" { s.dashPattern = parseFloats(dp.DashPattern) s.dashOffset = dp.DashOffset @@ -141,9 +146,7 @@ func (s *pathStyle) applyPathObject(r *Renderer, obj PathObject, bx, by, pageH f if obj.Cap != "" { s.lineCap = pathLineCap(obj.Cap, canvas.ButtCap) } - if obj.Join != "" { - s.lineJoin = pathLineJoin(obj.Join, canvas.MiterJoin) - } + s.applyLineJoin(obj.Join, obj.MiterLimit) if obj.DashPattern != "" { s.dashPattern = parseFloats(obj.DashPattern) s.dashOffset = obj.DashOffset @@ -163,8 +166,8 @@ func (s *pathStyle) scale(ctm Matrix) { } // renderPath 渲染路径 -// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 -func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { +// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaults 默认绘制参数, parentCTM 父级CTM, boundaryInCTM 边界是否参与CTM变换, parentClip 父级裁剪路径 +func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaults *DrawParam, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if obj.Visible != nil && !*obj.Visible { return } @@ -180,7 +183,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 if parentCTM != nil { ctm = parentCTM.Multiply(ctm) } - style := newPathStyle(defaultFill, defaultStroke, defaultLW, obj.Alpha) + style := r.newPathStyle(defaults, bx, by, pageH, obj.Alpha) if obj.DrawParam != "" { if dp := r.getDrawParam(obj.DrawParam, nil); dp != nil { style.applyDrawParam(r, dp, bx, by, pageH, obj.Alpha) @@ -188,10 +191,12 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 } style.applyPathObject(r, obj, bx, by, pageH) style.scale(ctm) - patternCTM := TranslationMatrix(bx, by).Multiply(ctm) + objectCTM := TranslationMatrix(bx, by).Multiply(ctm) if boundaryInCTM && parentCTM != nil { - patternCTM = parentCTM.Multiply(TranslationMatrix(bx, by)).Multiply(localCTM) + objectCTM = parentCTM.Multiply(TranslationMatrix(bx, by)).Multiply(localCTM) } + transformShdPaint(style.fillPaint, parentCTM, bx, by, pageH, boundaryInCTM) + transformShdPaint(style.strokePaint, parentCTM, bx, by, pageH, boundaryInCTM) pathCTM := ctm if boundaryInCTM { pathCTM = localCTM @@ -231,7 +236,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 fp = applyClipPath(fp, fillClip) } if style.fillPattern != nil { - r.renderPattern(ctx, style.fillPattern, pageH, fp, patternCTM) + r.renderPattern(ctx, style.fillPattern, pageH, fp, objectCTM) } else if style.fillPaint != nil { ctx.SetFillRule(fillRule) ctx.SetFill(fillPaint) @@ -268,7 +273,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64 sp = sp.Stroke(style.lineWidth, style.lineCap, style.lineJoin, canvas.Tolerance) sp = applyClipPath(sp, strokeClip) if style.strokePattern != nil { - r.renderPattern(ctx, style.strokePattern, pageH, sp, patternCTM) + r.renderPattern(ctx, style.strokePattern, pageH, sp, objectCTM) } else { ctx.SetFill(strokePaint) ctx.SetStrokeColor(canvas.Transparent) @@ -330,6 +335,7 @@ func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *patternPaint, pag endX := int(math.Ceil(maxX/xStep)) + 1 startY := int(math.Floor(minY/yStep)) - 1 endY := int(math.Ceil(maxY/yStep)) + 1 + defaults := &DrawParam{FillColor: &pattern.color, StrokeColor: (*StrokeColor)(&pattern.color)} for ix := startX; ix <= endX; ix++ { for iy := startY; iy <= endY; iy++ { tileCTM := patternCTM.Multiply(TranslationMatrix(float64(ix)*xStep, float64(iy)*yStep)) @@ -341,7 +347,7 @@ func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *patternPaint, pag } for _, obj := range pattern.CellContent.Objects { obj = mergeGraphicObjectAlpha(obj, pattern.alpha) - r.renderObject(ctx, obj, pageH, pattern.color, pattern.color, 0, &tileCTM, true, clip) + r.renderObject(ctx, obj, pageH, defaults, &tileCTM, true, clip) } } } diff --git a/ofdgo_renderer_text.go b/ofdgo_renderer_text.go index 39bbc8a..7c8d28f 100644 --- a/ofdgo_renderer_text.go +++ b/ofdgo_renderer_text.go @@ -15,7 +15,6 @@ package ofdgo import ( - "image/color" "strings" "github.com/tdewolff/canvas" @@ -24,8 +23,8 @@ import ( const ptPerMM = 72.0 / 25.4 // renderText 渲染文本 -// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM, parentClip 父级裁剪路径 -func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { +// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaults 默认绘制参数, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM, parentClip 父级裁剪路径 +func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaults *DrawParam, parentCTM *Matrix, boundaryInCTM bool, parentClip *canvas.Path) { if obj.Visible != nil && !*obj.Visible { return } @@ -46,6 +45,10 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 if parentCTM != nil { ctm = parentCTM.Multiply(ctm) } + objectCTM := TranslationMatrix(bx, by).Multiply(ctm) + if boundaryInCTM && parentCTM != nil { + objectCTM = parentCTM.Multiply(TranslationMatrix(bx, by)).Multiply(localCTM) + } clipPath := intersectClipPath(parentClip, r.buildObjectClipPath(obj.Clips, pageH, bx, by, localCTM, parentCTM, boundaryInCTM)) var dp *DrawParam if obj.DrawParam != "" { @@ -71,13 +74,13 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 sizeMM *= scale } sizePt := sizeMM * ptPerMM - fillColor := colorWithAlpha(defaultFill, obj.Alpha) - if fillColor == nil { - fillColor = colorWithAlpha(canvas.Black, obj.Alpha) - } + fillColor := colorWithAlpha(canvas.Black, obj.Alpha) var fillPaint any = fillColor var fillPattern *patternPaint var fillColorNode *FillColor + if defaults != nil { + fillColorNode = defaults.FillColor + } if dp != nil && dp.FillColor != nil { fillColorNode = dp.FillColor } @@ -88,17 +91,19 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 fillColorNode = withFillAlpha(fillColorNode, obj.Alpha) fillColor = r.parseFillColor(fillColorNode) fillPaint = r.parseFillPaint(fillColorNode, bx, by, pageH) - fillPattern = r.parsePatternPaint(fillColorNode.Pattern, fillColorNode.Value, fillColorNode.Index, fillColorNode.ColorSpace, fillColorNode.Alpha) + fillPattern = parsePatternPaint(fillColorNode) } if fillPaint == nil { fillPaint = fillColor } + transformShdPaint(fillPaint, parentCTM, bx, by, pageH, boundaryInCTM) fillPaint, shadingClip, fillView := resolveShdPaint(ctx, fillPaint) fillClip := intersectClipPath(clipPath, shadingClip) - strokeStyle := newPathStyle(nil, defaultStroke, 0, obj.Alpha) + var strokeStyle pathStyle strokeClip := clipPath strokeView := canvas.Identity if shouldStroke { + strokeStyle = r.newPathStyle(defaults, bx, by, pageH, obj.Alpha) if dp != nil { strokeStyle.applyDrawParam(r, dp, bx, by, pageH, obj.Alpha) } @@ -108,17 +113,15 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 if obj.LineWidth > 0 { strokeStyle.lineWidth = obj.LineWidth } + strokeStyle.applyLineJoin(obj.Join, obj.MiterLimit) if strokeStyle.strokePaint == nil { strokeStyle.strokePaint = colorWithAlpha(canvas.Black, obj.Alpha) } strokeStyle.scale(ctm) + transformShdPaint(strokeStyle.strokePaint, parentCTM, bx, by, pageH, boundaryInCTM) strokeStyle.strokePaint, shadingClip, strokeView = resolveShdPaint(ctx, strokeStyle.strokePaint) strokeClip = intersectClipPath(strokeClip, shadingClip) } - patternCTM := TranslationMatrix(bx, by).Multiply(ctm) - if boundaryInCTM && parentCTM != nil { - patternCTM = parentCTM.Multiply(TranslationMatrix(bx, by)).Multiply(localCTM) - } fontStyle := canvas.FontRegular weight := obj.Weight if weight == 0 && dp != nil && dp.Weight > 0 { @@ -247,7 +250,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } path := glyphPath.Copy().Transform(transform.Scale(scaleX, 1)) if shouldFill && fillPattern != nil { - r.renderPattern(ctx, fillPattern, pageH, applyClipPath(path.Copy(), fillClip), patternCTM) + r.renderPattern(ctx, fillPattern, pageH, applyClipPath(path.Copy(), fillClip), objectCTM) } else if shouldFill && fillPaint != nil { ctx.SetFill(fillPaint) ctx.SetStrokeColor(canvas.Transparent) @@ -259,7 +262,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 path = path.Stroke(strokeStyle.lineWidth, strokeStyle.lineCap, strokeStyle.lineJoin, canvas.Tolerance) path = applyClipPath(path, strokeClip) if strokeStyle.strokePattern != nil { - r.renderPattern(ctx, strokeStyle.strokePattern, pageH, path, patternCTM) + r.renderPattern(ctx, strokeStyle.strokePattern, pageH, path, objectCTM) } else { ctx.SetFill(strokeStyle.strokePaint) ctx.SetStrokeColor(canvas.Transparent) @@ -271,12 +274,12 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 underline.LineTo(glyphWidth*scaleX, -sizeMM*0.1) underline = underline.Stroke(sizeMM*0.05, canvas.ButtCap, canvas.MiterJoin, canvas.Tolerance) if shouldFill && fillPattern != nil { - r.renderPattern(ctx, fillPattern, pageH, applyClipPath(underline.Transform(transform), clipPath), patternCTM) + r.renderPattern(ctx, fillPattern, pageH, applyClipPath(underline.Transform(transform), clipPath), objectCTM) } else if shouldFill { ctx.SetFillColor(fillColor) ctx.DrawPath(0, 0, applyClipPath(underline.Transform(transform), clipPath)) } else if strokeStyle.strokePattern != nil { - r.renderPattern(ctx, strokeStyle.strokePattern, pageH, applyClipPath(underline.Transform(transform), strokeClip), patternCTM) + r.renderPattern(ctx, strokeStyle.strokePattern, pageH, applyClipPath(underline.Transform(transform), strokeClip), objectCTM) } else { drawShdPath(ctx, applyClipPath(underline.Transform(transform), strokeClip), strokeView) } @@ -297,7 +300,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 } glyphPath = applyClipPath(glyphPath.Copy().Transform(textTransform.Scale(scaleX, 1)), fillClip) if fillPattern != nil { - r.renderPattern(ctx, fillPattern, pageH, glyphPath, patternCTM) + r.renderPattern(ctx, fillPattern, pageH, glyphPath, objectCTM) } else { drawShdPath(ctx, glyphPath, fillView) } @@ -310,7 +313,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64 underline = underline.Stroke(uw, canvas.ButtCap, canvas.MiterJoin, canvas.Tolerance) underline = applyClipPath(underline.Transform(textTransform), clipPath) if fillPattern != nil { - r.renderPattern(ctx, fillPattern, pageH, underline, patternCTM) + r.renderPattern(ctx, fillPattern, pageH, underline, objectCTM) } else { ctx.SetFillColor(fillColor) ctx.SetStrokeColor(canvas.Transparent) diff --git a/ofdgo_shading.go b/ofdgo_shading.go index 7d44e46..9fb2728 100644 --- a/ofdgo_shading.go +++ b/ofdgo_shading.go @@ -56,15 +56,22 @@ func resolveShdPaint(ctx *canvas.Context, paint any) (any, *canvas.Path, canvas. if !ok { return paint, nil, canvas.Identity } + if canvas.Equal(s.view.Det(), 0) { + return nil, &canvas.Path{}, canvas.Identity + } switch g := s.gradient.(type) { case *canvas.LinearGradient: - clip := axialShdClip(ctx, g, s.extend) + bounds := shdCanvasBounds(ctx).Transform(s.view.Inv()) + clip := axialShdClip(g, s.extend, bounds) + if clip != nil && s.view != canvas.Identity { + clip = clip.Transform(s.view) + } if s.mapType != "Reflect" { return g, clip, s.view } d := g.End.Sub(g.Start) axis := canvas.Matrix{{d.X, -d.Y, g.Start.X}, {d.Y, d.X, g.Start.Y}} - area := shdCanvasBounds(ctx).Transform(axis.Inv()) + area := bounds.Transform(axis.Inv()) lo, hi := s.rangeLimits(area.X0, area.X1) if hi <= lo { return g, &canvas.Path{}, s.view @@ -108,6 +115,23 @@ func resolveShdPaint(ctx *canvas.Context, paint any) (any, *canvas.Path, canvas. return s.gradient, nil, s.view } +// transformShdPaint 应用渐变的父级坐标变换 +// 入参: paint 画刷, parentCTM 父级变换矩阵, bx 边界X坐标, by 边界Y坐标, pageH 页面高度, boundaryInCTM 边界是否参与父级变换 +func transformShdPaint(paint any, parentCTM *Matrix, bx, by, pageH float64, boundaryInCTM bool) { + s, ok := paint.(*shdPaint) + if !ok || parentCTM == nil { + return + } + ctm := *parentCTM + if !boundaryInCTM { + ctm = TranslationMatrix(bx, by).Multiply(ctm).Multiply(TranslationMatrix(-bx, -by)) + } + s.view = canvas.Matrix{ + {ctm.a, -ctm.c, ctm.c*pageH + ctm.e}, + {-ctm.b, ctm.d, pageH*(1-ctm.d) - ctm.f}, + }.Mul(s.view) +} + // drawShdPath 绘制渐变路径并保持图形轮廓不变 // 入参: ctx 画布上下文, path 填充路径, view 渐变变换 func drawShdPath(ctx *canvas.Context, path *canvas.Path, view canvas.Matrix) {