mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(增强渲染): 支持Pattern
This commit is contained in:
@@ -86,6 +86,30 @@ func (m Matrix) Transform(x, y float64) (float64, float64) {
|
|||||||
return nx, ny
|
return nx, ny
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invert 求逆矩阵
|
||||||
|
// 返回: Matrix 逆矩阵, bool 是否可逆
|
||||||
|
func (m Matrix) Invert() (Matrix, bool) {
|
||||||
|
det := m.a*m.d - m.b*m.c
|
||||||
|
if math.Abs(det) < 1e-9 {
|
||||||
|
return IdentityMatrix, false
|
||||||
|
}
|
||||||
|
return Matrix{
|
||||||
|
a: m.d / det,
|
||||||
|
b: -m.b / det,
|
||||||
|
c: -m.c / det,
|
||||||
|
d: m.a / det,
|
||||||
|
e: (m.c*m.f - m.d*m.e) / det,
|
||||||
|
f: (m.b*m.e - m.a*m.f) / det,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslationMatrix 构造平移矩阵
|
||||||
|
// 入参: x X轴偏移, y Y轴偏移
|
||||||
|
// 返回: Matrix 平移矩阵
|
||||||
|
func TranslationMatrix(x, y float64) Matrix {
|
||||||
|
return Matrix{a: 1, d: 1, e: x, f: y}
|
||||||
|
}
|
||||||
|
|
||||||
// YScale 获取Y轴缩放比例
|
// YScale 获取Y轴缩放比例
|
||||||
// 返回: float64 缩放比例
|
// 返回: float64 缩放比例
|
||||||
func (m Matrix) YScale() float64 {
|
func (m Matrix) YScale() float64 {
|
||||||
|
|||||||
@@ -302,6 +302,92 @@ func (a *Appearance) decodePageBlock(d *xml.Decoder, start xml.StartElement) err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML 解析图案单元内容并保留对象顺序
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
*p = PatternContent{}
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch node := tok.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if err := p.decodeObject(d, node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
if node.Name.Local == start.Name.Local {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeObject 解析图案单元内容子对象
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (p *PatternContent) decodeObject(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
switch start.Name.Local {
|
||||||
|
case "TextObject":
|
||||||
|
var obj TextObject
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.TextObject = append(p.TextObject, obj)
|
||||||
|
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
|
||||||
|
case "PathObject":
|
||||||
|
var obj PathObject
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.PathObject = append(p.PathObject, obj)
|
||||||
|
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
|
||||||
|
case "ImageObject":
|
||||||
|
var obj ImageObject
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.ImageObject = append(p.ImageObject, obj)
|
||||||
|
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
|
||||||
|
case "CompositeGraphicUnit", "CompositeObject":
|
||||||
|
var obj CompositeGraphicUnit
|
||||||
|
if err := d.DecodeElement(&obj, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.CompositeGraphicUnit = append(p.CompositeGraphicUnit, obj)
|
||||||
|
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
|
||||||
|
case "PageBlock":
|
||||||
|
return p.decodePageBlock(d, start)
|
||||||
|
default:
|
||||||
|
return d.Skip()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodePageBlock 解析图案单元内容页块子对象
|
||||||
|
// 入参: d XML解码器, start 起始节点
|
||||||
|
// 返回: error 错误信息
|
||||||
|
func (p *PatternContent) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch node := tok.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if err := p.decodeObject(d, node); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
if node.Name.Local == start.Name.Local {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// attrValue 获取XML属性值
|
// attrValue 获取XML属性值
|
||||||
// 入参: start 起始节点, name 属性名
|
// 入参: start 起始节点, name 属性名
|
||||||
// 返回: string 属性值
|
// 返回: string 属性值
|
||||||
|
|||||||
@@ -103,10 +103,30 @@ type TextObject struct {
|
|||||||
type FillColor struct {
|
type FillColor struct {
|
||||||
Value string `xml:"Value,attr"`
|
Value string `xml:"Value,attr"`
|
||||||
Alpha *int `xml:"Alpha,attr"`
|
Alpha *int `xml:"Alpha,attr"`
|
||||||
|
Pattern *Pattern `xml:"Pattern"`
|
||||||
AxialShd *AxialShd `xml:"AxialShd"`
|
AxialShd *AxialShd `xml:"AxialShd"`
|
||||||
RadialShd *RadialShd `xml:"RadialShd"`
|
RadialShd *RadialShd `xml:"RadialShd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pattern 图案填充
|
||||||
|
type Pattern struct {
|
||||||
|
Width float64 `xml:"Width,attr"`
|
||||||
|
Height float64 `xml:"Height,attr"`
|
||||||
|
XStep float64 `xml:"XStep,attr"`
|
||||||
|
YStep float64 `xml:"YStep,attr"`
|
||||||
|
CTM string `xml:"CTM,attr"`
|
||||||
|
CellContent PatternContent `xml:"CellContent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatternContent 图案单元内容
|
||||||
|
type PatternContent struct {
|
||||||
|
Objects []GraphicObject `xml:"-"`
|
||||||
|
TextObject []TextObject `xml:"TextObject"`
|
||||||
|
PathObject []PathObject `xml:"PathObject"`
|
||||||
|
ImageObject []ImageObject `xml:"ImageObject"`
|
||||||
|
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
||||||
|
}
|
||||||
|
|
||||||
// TextCode 文本内容节点
|
// TextCode 文本内容节点
|
||||||
type TextCode struct {
|
type TextCode struct {
|
||||||
X string `xml:"X,attr"`
|
X string `xml:"X,attr"`
|
||||||
|
|||||||
+81
-1
@@ -551,6 +551,8 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
fillColor, strokeColor := colorWithAlpha(defaultFill, obj.Alpha), colorWithAlpha(defaultStroke, obj.Alpha)
|
fillColor, strokeColor := colorWithAlpha(defaultFill, obj.Alpha), colorWithAlpha(defaultStroke, obj.Alpha)
|
||||||
var fillPaint any = fillColor
|
var fillPaint any = fillColor
|
||||||
var strokePaint any = strokeColor
|
var strokePaint any = strokeColor
|
||||||
|
var fillPattern *Pattern
|
||||||
|
var fillPatternColor color.Color
|
||||||
lineWidth := defaultLW
|
lineWidth := defaultLW
|
||||||
if lineWidth == 0 {
|
if lineWidth == 0 {
|
||||||
lineWidth = 0.353
|
lineWidth = 0.353
|
||||||
@@ -566,6 +568,8 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
}
|
}
|
||||||
if dp.FillColor != nil {
|
if dp.FillColor != nil {
|
||||||
fillColorNode := withFillAlpha(dp.FillColor, obj.Alpha)
|
fillColorNode := withFillAlpha(dp.FillColor, obj.Alpha)
|
||||||
|
fillPattern = fillColorNode.Pattern
|
||||||
|
fillPatternColor = patternColor(fillColorNode)
|
||||||
fillColor = parseFillColor(fillColorNode)
|
fillColor = parseFillColor(fillColorNode)
|
||||||
fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
|
fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
|
||||||
}
|
}
|
||||||
@@ -595,6 +599,8 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
}
|
}
|
||||||
if obj.FillColor != nil {
|
if obj.FillColor != nil {
|
||||||
fillColorNode := withFillAlpha(obj.FillColor, obj.Alpha)
|
fillColorNode := withFillAlpha(obj.FillColor, obj.Alpha)
|
||||||
|
fillPattern = fillColorNode.Pattern
|
||||||
|
fillPatternColor = patternColor(fillColorNode)
|
||||||
fillColor = parseFillColor(fillColorNode)
|
fillColor = parseFillColor(fillColorNode)
|
||||||
fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
|
fillPaint = parseFillPaint(fillColorNode, bx, by, pageH, 0, 0)
|
||||||
}
|
}
|
||||||
@@ -644,7 +650,15 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
if fillPaint == nil {
|
if fillPaint == nil {
|
||||||
fillPaint = fillColor
|
fillPaint = fillColor
|
||||||
}
|
}
|
||||||
if shouldFill && fillPaint != nil {
|
if shouldFill && fillPattern != nil {
|
||||||
|
fp := p
|
||||||
|
if clipPath != nil {
|
||||||
|
fp = p.Copy()
|
||||||
|
fp.Close()
|
||||||
|
fp = fp.And(clipPath)
|
||||||
|
}
|
||||||
|
r.renderPattern(ctx, fillPattern, fillPatternColor, pageH, fp, ctm)
|
||||||
|
} else if shouldFill && fillPaint != nil {
|
||||||
ctx.SetFill(fillPaint)
|
ctx.SetFill(fillPaint)
|
||||||
ctx.SetStrokeColor(canvas.Transparent)
|
ctx.SetStrokeColor(canvas.Transparent)
|
||||||
fp := p
|
fp := p
|
||||||
@@ -691,6 +705,72 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
|||||||
ctx.Pop()
|
ctx.Pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderPattern 渲染图案填充
|
||||||
|
// 入参: ctx 画布上下文, pattern 图案对象, defaultColor 默认颜色, pageH 页面高度, clip 填充区域, parentCTM 父级CTM
|
||||||
|
func (r *Renderer) renderPattern(ctx *canvas.Context, pattern *Pattern, defaultColor color.Color, pageH float64, clip *canvas.Path, parentCTM Matrix) {
|
||||||
|
if pattern == nil || clip == nil || len(pattern.CellContent.Objects) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xStep, yStep := pattern.XStep, pattern.YStep
|
||||||
|
if xStep == 0 {
|
||||||
|
xStep = pattern.Width
|
||||||
|
}
|
||||||
|
if yStep == 0 {
|
||||||
|
yStep = pattern.Height
|
||||||
|
}
|
||||||
|
if xStep <= 0 || yStep <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
patternCTM := parentCTM.Multiply(NewMatrix(pattern.CTM))
|
||||||
|
invCTM, ok := patternCTM.Invert()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bounds := clip.FastBounds()
|
||||||
|
points := [][2]float64{
|
||||||
|
{bounds.X0, pageH - bounds.Y0},
|
||||||
|
{bounds.X1, pageH - bounds.Y0},
|
||||||
|
{bounds.X1, pageH - bounds.Y1},
|
||||||
|
{bounds.X0, pageH - bounds.Y1},
|
||||||
|
}
|
||||||
|
minX, maxX := 0.0, 0.0
|
||||||
|
minY, maxY := 0.0, 0.0
|
||||||
|
for i, point := range points {
|
||||||
|
x, y := invCTM.Transform(point[0], point[1])
|
||||||
|
if i == 0 {
|
||||||
|
minX, maxX = x, x
|
||||||
|
minY, maxY = y, y
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
minX = math.Min(minX, x)
|
||||||
|
maxX = math.Max(maxX, x)
|
||||||
|
minY = math.Min(minY, y)
|
||||||
|
maxY = math.Max(maxY, y)
|
||||||
|
}
|
||||||
|
startX := int(math.Floor(minX/xStep)) - 1
|
||||||
|
endX := int(math.Ceil(maxX/xStep)) + 1
|
||||||
|
startY := int(math.Floor(minY/yStep)) - 1
|
||||||
|
endY := int(math.Ceil(maxY/yStep)) + 1
|
||||||
|
for ix := startX; ix <= endX; ix++ {
|
||||||
|
for iy := startY; iy <= endY; iy++ {
|
||||||
|
tileCTM := patternCTM.Multiply(TranslationMatrix(float64(ix)*xStep, float64(iy)*yStep))
|
||||||
|
for _, obj := range pattern.CellContent.Objects {
|
||||||
|
r.renderObject(ctx, obj, pageH, defaultColor, defaultColor, 0, &tileCTM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// patternColor 获取图案单元默认颜色
|
||||||
|
// 入参: fillColor 填充颜色节点
|
||||||
|
// 返回: color.Color 默认颜色
|
||||||
|
func patternColor(fillColor *FillColor) color.Color {
|
||||||
|
if fillColor == nil || strings.TrimSpace(fillColor.Value) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||||
|
}
|
||||||
|
|
||||||
// renderText 渲染文本
|
// renderText 渲染文本
|
||||||
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM
|
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM
|
||||||
func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix) {
|
func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64, defaultFill, defaultStroke color.Color, parentCTM *Matrix) {
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ func parseFillColor(fillColor *FillColor) color.Color {
|
|||||||
if fillColor == nil {
|
if fillColor == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if fillColor.Pattern != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if strings.TrimSpace(fillColor.Value) != "" {
|
if strings.TrimSpace(fillColor.Value) != "" {
|
||||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
@@ -148,6 +151,9 @@ func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64)
|
|||||||
if fillColor == nil {
|
if fillColor == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if fillColor.Pattern != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if strings.TrimSpace(fillColor.Value) != "" {
|
if strings.TrimSpace(fillColor.Value) != "" {
|
||||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user