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
|
||||
}
|
||||
|
||||
// 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轴缩放比例
|
||||
// 返回: 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属性值
|
||||
// 入参: start 起始节点, name 属性名
|
||||
// 返回: string 属性值
|
||||
|
||||
@@ -103,10 +103,30 @@ type TextObject struct {
|
||||
type FillColor struct {
|
||||
Value string `xml:"Value,attr"`
|
||||
Alpha *int `xml:"Alpha,attr"`
|
||||
Pattern *Pattern `xml:"Pattern"`
|
||||
AxialShd *AxialShd `xml:"AxialShd"`
|
||||
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 文本内容节点
|
||||
type TextCode struct {
|
||||
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)
|
||||
var fillPaint any = fillColor
|
||||
var strokePaint any = strokeColor
|
||||
var fillPattern *Pattern
|
||||
var fillPatternColor color.Color
|
||||
lineWidth := defaultLW
|
||||
if lineWidth == 0 {
|
||||
lineWidth = 0.353
|
||||
@@ -566,6 +568,8 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
||||
}
|
||||
if dp.FillColor != nil {
|
||||
fillColorNode := withFillAlpha(dp.FillColor, obj.Alpha)
|
||||
fillPattern = fillColorNode.Pattern
|
||||
fillPatternColor = patternColor(fillColorNode)
|
||||
fillColor = parseFillColor(fillColorNode)
|
||||
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 {
|
||||
fillColorNode := withFillAlpha(obj.FillColor, obj.Alpha)
|
||||
fillPattern = fillColorNode.Pattern
|
||||
fillPatternColor = patternColor(fillColorNode)
|
||||
fillColor = parseFillColor(fillColorNode)
|
||||
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 {
|
||||
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.SetStrokeColor(canvas.Transparent)
|
||||
fp := p
|
||||
@@ -691,6 +705,72 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
|
||||
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 渲染文本
|
||||
// 入参: ctx 画布上下文, obj 文本对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, parentCTM 父级CTM
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
if fillColor.Pattern != nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(fillColor.Value) != "" {
|
||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||
}
|
||||
@@ -148,6 +151,9 @@ func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64)
|
||||
if fillColor == nil {
|
||||
return nil
|
||||
}
|
||||
if fillColor.Pattern != nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(fillColor.Value) != "" {
|
||||
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user