Repository navigation
Expand file tree
/
Copy pathpattern.go
More file actions
151 lines (133 loc) · 3.96 KB
/
Copy pathpattern.go
File metadata and controls
151 lines (133 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Copyright 2026 The oksvg Authors. All rights reserved.
//
// pattern.go implements SVG patterns as custom rasterx color functions.
package oksvg
import (
"image"
"image/color"
"math"
"github.com/srwiley/rasterx"
)
// maxPatternTileDim caps the per-axis dimension of a rasterized pattern tile
// so attacker-controlled width/height/scale in an SVG can't trigger an
// unbounded image.NewRGBA allocation.
const maxPatternTileDim = 4096
// Pattern holds the data of an SVG pattern element.
type Pattern struct {
ID string
X, Y, Width, Height float64
Units string // "userSpaceOnUse" or "objectBoundingBox"
ContentUnits string // "userSpaceOnUse" or "objectBoundingBox"
Transform rasterx.Matrix2D
Paths []SvgPath
// rendering guards against infinite recursion when a pattern's children
// reference the same pattern (the parse-time fix in compilePattern caches
// the in-progress pointer so children resolve to it; this flag stops the
// draw-time recursion that would otherwise happen).
rendering bool
}
// GetColorFunction returns a ColorFunc that implements the tiling logic for this pattern.
func (p *Pattern) GetColorFunction(opacity float64, objBounds struct{ X, Y, W, H float64 }, parentTransform rasterx.Matrix2D) rasterx.ColorFunc {
if p.rendering {
return func(xi, yi int) color.Color {
return color.Transparent
}
}
p.rendering = true
defer func() { p.rendering = false }()
var x, y, w, h float64
if p.Units == "objectBoundingBox" {
x = objBounds.X + objBounds.W*p.X
y = objBounds.Y + objBounds.H*p.Y
w = objBounds.W * p.Width
h = objBounds.H * p.Height
} else {
x = p.X
y = p.Y
w = p.Width
h = p.Height
}
if w <= 0 || h <= 0 {
return func(xi, yi int) color.Color {
return color.Transparent
}
}
// Compute combined scale factor to render pattern at target resolution
combinedTransform := parentTransform.Mult(p.Transform)
scaleX := math.Sqrt(combinedTransform.A*combinedTransform.A + combinedTransform.B*combinedTransform.B)
scaleY := math.Sqrt(combinedTransform.C*combinedTransform.C + combinedTransform.D*combinedTransform.D)
if scaleX <= 0 {
scaleX = 1
}
if scaleY <= 0 {
scaleY = 1
}
imgW := int(math.Ceil(w * scaleX))
imgH := int(math.Ceil(h * scaleY))
if imgW <= 0 {
imgW = 1
}
if imgH <= 0 {
imgH = 1
}
// Clamp tile dimensions and re-derive scaleX/scaleY so the rasterized tile
// stays consistent with the actual image size after clamping.
if imgW > maxPatternTileDim {
imgW = maxPatternTileDim
scaleX = float64(imgW) / w
}
if imgH > maxPatternTileDim {
imgH = maxPatternTileDim
scaleY = float64(imgH) / h
}
img := image.NewRGBA(image.Rect(0, 0, imgW, imgH))
scanner := rasterx.NewScannerGV(imgW, imgH, img, img.Bounds())
dasher := rasterx.NewDasher(imgW, imgH, scanner)
var contentTransform rasterx.Matrix2D
if p.ContentUnits == "objectBoundingBox" {
contentTransform = rasterx.Identity.Scale(scaleX*w, scaleY*h)
} else {
contentTransform = rasterx.Identity.Scale(scaleX, scaleY)
}
// Draw pattern sub-paths onto tile image
for _, path := range p.Paths {
path.DrawTransformed(dasher, opacity, contentTransform)
}
// Guard against singular combined transforms (det == 0). Invert would
// otherwise produce Inf/NaN entries and corrupt the tile lookup.
det := combinedTransform.A*combinedTransform.D - combinedTransform.B*combinedTransform.C
if math.Abs(det) < 1e-12 {
return func(xi, yi int) color.Color {
return color.Transparent
}
}
invCombined := combinedTransform.Invert()
return func(xi, yi int) color.Color {
xVal, yVal := invCombined.Transform(float64(xi)+0.5, float64(yi)+0.5)
px := xVal - x
py := yVal - y
tx := math.Mod(px, w)
if tx < 0 {
tx += w
}
ty := math.Mod(py, h)
if ty < 0 {
ty += h
}
ix := int(tx * scaleX)
iy := int(ty * scaleY)
if ix < 0 {
ix = 0
}
if ix >= imgW {
ix = imgW - 1
}
if iy < 0 {
iy = 0
}
if iy >= imgH {
iy = imgH - 1
}
return img.At(ix, iy)
}
}