feat(渲染能力): 完善尖角限制与画刷继承
build.yaml / build (push) Successful in 36s

This commit is contained in:
xiaoqidun committed 2026-09-09 19:58:01 +08:00
1 parent 777d0c7c89
commit e5e07f92cd
7 files changed
+166 -141

No files matched your search

+2
View File
@@ -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"`
+8 -14
View File
@@ -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)
}
+1 -1
View File
@@ -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 {
+68 -72
View File
@@ -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, &currentCTM, true, clipPath)
r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaults, &currentCTM, 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, &currentCTM, boundaryInCTM, clipPath)
r.renderObject(ctx, obj, pageH, defaults, &currentCTM, 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, &currentCTM, boundaryInCTM, clipPath)
r.renderPath(ctx, pathObj, pageH, defaults, &currentCTM, boundaryInCTM, clipPath)
}
for _, textObj := range cgu.TextObject {
textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha)
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, &currentCTM, boundaryInCTM, clipPath)
r.renderText(ctx, textObj, pageH, defaults, &currentCTM, boundaryInCTM, clipPath)
}
for _, subCgu := range cgu.CompositeGraphicUnit {
subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha)
r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM, boundaryInCTM, clipPath)
r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaults, &currentCTM, 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
}
+39 -33
View File
@@ -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,
lineWidth: defaultPathLineWidth,
lineCap: canvas.ButtCap,
lineJoin: canvas.MiterJoin,
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)
}
}
}
+22 -19
View File
@@ -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)
+26 -2
View File
@@ -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) {