1
- import { parseLinearGradient } from 'css-gradient-parser'
1
+ import { parseLinearGradient , ColorStop } from 'css-gradient-parser'
2
2
import { normalizeStops } from './utils.js'
3
- import { buildXMLString , calcDegree } from '../../utils.js'
3
+ import { buildXMLString , calcDegree , lengthToNumber } from '../../utils.js'
4
4
5
5
export function buildLinearGradient (
6
6
{
@@ -24,90 +24,44 @@ export function buildLinearGradient(
24
24
) {
25
25
const parsed = parseLinearGradient ( image )
26
26
const [ imageWidth , imageHeight ] = dimensions
27
+ const repeating = image . startsWith ( 'repeating' )
27
28
28
29
// Calculate the direction.
29
- let x1 , y1 , x2 , y2 , length
30
+ let points , length , xys
30
31
31
32
if ( parsed . orientation . type === 'directional' ) {
32
- ; [ x1 , y1 , x2 , y2 ] = resolveXYFromDirection ( parsed . orientation . value )
33
+ points = resolveXYFromDirection ( parsed . orientation . value )
33
34
34
35
length = Math . sqrt (
35
- Math . pow ( ( x2 - x1 ) * imageWidth , 2 ) + Math . pow ( ( y2 - y1 ) * imageHeight , 2 )
36
+ Math . pow ( ( points . x2 - points . x1 ) * imageWidth , 2 ) +
37
+ Math . pow ( ( points . y2 - points . y1 ) * imageHeight , 2 )
36
38
)
37
39
} else if ( parsed . orientation . type === 'angular' ) {
38
- const EPS = 0.000001
39
- const r = imageWidth / imageHeight
40
-
41
- function calc ( angle ) {
42
- angle = ( ( angle % ( Math . PI * 2 ) ) + Math . PI * 2 ) % ( Math . PI * 2 )
43
-
44
- if ( Math . abs ( angle - Math . PI / 2 ) < EPS ) {
45
- x1 = 0
46
- y1 = 0
47
- x2 = 1
48
- y2 = 0
49
- length = imageWidth
50
- return
51
- } else if ( Math . abs ( angle ) < EPS ) {
52
- x1 = 0
53
- y1 = 1
54
- x2 = 0
55
- y2 = 0
56
- length = imageHeight
57
- return
58
- }
59
-
60
- // Assuming 0 <= angle < PI / 2.
61
- if ( angle >= Math . PI / 2 && angle < Math . PI ) {
62
- calc ( Math . PI - angle )
63
- y1 = 1 - y1
64
- y2 = 1 - y2
65
- return
66
- } else if ( angle >= Math . PI ) {
67
- calc ( angle - Math . PI )
68
- let tmp = x1
69
- x1 = x2
70
- x2 = tmp
71
- tmp = y1
72
- y1 = y2
73
- y2 = tmp
74
- return
75
- }
76
-
77
- // Remap SVG distortion
78
- const tan = Math . tan ( angle )
79
- const tanTexture = tan * r
80
- const angleTexture = Math . atan ( tanTexture )
81
- const l = Math . sqrt ( 2 ) * Math . cos ( Math . PI / 4 - angleTexture )
82
- x1 = 0
83
- y1 = 1
84
- x2 = Math . sin ( angleTexture ) * l
85
- y2 = 1 - Math . cos ( angleTexture ) * l
86
-
87
- // Get the angle between the distored gradient direction and diagonal.
88
- const x = 1
89
- const y = 1 / tan
90
- const cosA = Math . abs (
91
- ( x * r + y ) / Math . sqrt ( x * x + y * y ) / Math . sqrt ( r * r + 1 )
92
- )
93
-
94
- // Get the distored gradient length.
95
- const diagonal = Math . sqrt (
96
- imageWidth * imageWidth + imageHeight * imageHeight
97
- )
98
- length = diagonal * cosA
99
- }
100
-
101
- calc (
40
+ const { length : l , ...p } = calcNormalPoint (
102
41
( calcDegree (
103
42
`${ parsed . orientation . value . value } ${ parsed . orientation . value . unit } `
104
43
) /
105
44
180 ) *
106
- Math . PI
45
+ Math . PI ,
46
+ imageWidth ,
47
+ imageHeight
107
48
)
49
+
50
+ length = l
51
+ points = p
108
52
}
109
53
110
- const stops = normalizeStops ( length , parsed . stops , inheritableStyle , from )
54
+ xys = repeating
55
+ ? calcPercentage ( parsed . stops , length , points , inheritableStyle )
56
+ : points
57
+
58
+ const stops = normalizeStops (
59
+ length ,
60
+ parsed . stops ,
61
+ inheritableStyle ,
62
+ repeating ,
63
+ from
64
+ )
111
65
112
66
const gradientId = `satori_bi${ id } `
113
67
const patternId = `satori_pattern_${ id } `
@@ -126,10 +80,8 @@ export function buildLinearGradient(
126
80
'linearGradient' ,
127
81
{
128
82
id : gradientId ,
129
- x1,
130
- y1,
131
- x2,
132
- y2,
83
+ ...xys ,
84
+ spreadMethod : repeating ? 'repeat' : 'pad' ,
133
85
} ,
134
86
stops
135
87
. map ( ( stop ) =>
@@ -173,5 +125,134 @@ function resolveXYFromDirection(dir: string) {
173
125
y1 = 1
174
126
}
175
127
176
- return [ x1 , y1 , x2 , y2 ]
128
+ return { x1, y1, x2, y2 }
129
+ }
130
+
131
+ /**
132
+ * calc start point and end point of linear gradient
133
+ */
134
+ function calcNormalPoint ( v : number , w : number , h : number ) {
135
+ const r = Math . pow ( h / w , 2 )
136
+
137
+ // make sure angle is 0 <= angle <= 360
138
+ v = ( ( v % ( Math . PI * 2 ) ) + Math . PI * 2 ) % ( Math . PI * 2 )
139
+
140
+ let x1 , y1 , x2 , y2 , length , tmp , a , b
141
+
142
+ const dfs = ( angle : number ) => {
143
+ if ( angle === 0 ) {
144
+ x1 = 0
145
+ y1 = h
146
+ x2 = 0
147
+ y2 = 0
148
+ length = h
149
+ return
150
+ } else if ( angle === Math . PI / 2 ) {
151
+ x1 = 0
152
+ y1 = 0
153
+ x2 = w
154
+ y2 = 0
155
+ length = w
156
+ return
157
+ }
158
+ if ( angle > 0 && angle < Math . PI / 2 ) {
159
+ x1 =
160
+ ( ( r * w ) / 2 / Math . tan ( angle ) - h / 2 ) /
161
+ ( Math . tan ( angle ) + r / Math . tan ( angle ) )
162
+ y1 = Math . tan ( angle ) * x1 + h
163
+ x2 = Math . abs ( w / 2 - x1 ) + w / 2
164
+ y2 = h / 2 - Math . abs ( y1 - h / 2 )
165
+ length = Math . sqrt ( Math . pow ( x2 - x1 , 2 ) + Math . pow ( y2 - y1 , 2 ) )
166
+ // y = -1 / tan * x = h / 2 +1 / tan * w/2
167
+ // y = tan * x + h
168
+ a =
169
+ ( w / 2 / Math . tan ( angle ) - h / 2 ) /
170
+ ( Math . tan ( angle ) + 1 / Math . tan ( angle ) )
171
+ b = Math . tan ( angle ) * a + h
172
+ length = 2 * Math . sqrt ( Math . pow ( w / 2 - a , 2 ) + Math . pow ( h / 2 - b , 2 ) )
173
+ return
174
+ } else if ( angle > Math . PI / 2 && angle < Math . PI ) {
175
+ x1 =
176
+ ( h / 2 + ( r * w ) / 2 / Math . tan ( angle ) ) /
177
+ ( Math . tan ( angle ) + r / Math . tan ( angle ) )
178
+ y1 = Math . tan ( angle ) * x1
179
+ x2 = Math . abs ( w / 2 - x1 ) + w / 2
180
+ y2 = h / 2 + Math . abs ( y1 - h / 2 )
181
+ // y = -1 / tan * x + h / 2 + 1 / tan * w / 2
182
+ // y = tan * x
183
+ a =
184
+ ( w / 2 / Math . tan ( angle ) + h / 2 ) /
185
+ ( Math . tan ( angle ) + 1 / Math . tan ( angle ) )
186
+ b = Math . tan ( angle ) * a
187
+ length = 2 * Math . sqrt ( Math . pow ( w / 2 - a , 2 ) + Math . pow ( h / 2 - b , 2 ) )
188
+ return
189
+ } else if ( angle >= Math . PI ) {
190
+ dfs ( angle - Math . PI )
191
+
192
+ tmp = x1
193
+ x1 = x2
194
+ x2 = tmp
195
+ tmp = y1
196
+ y1 = y2
197
+ y2 = tmp
198
+ }
199
+ }
200
+
201
+ dfs ( v )
202
+
203
+ return {
204
+ x1 : x1 / w ,
205
+ y1 : y1 / h ,
206
+ x2 : x2 / w ,
207
+ y2 : y2 / h ,
208
+ length,
209
+ }
210
+ }
211
+
212
+ function calcPercentage (
213
+ stops : ColorStop [ ] ,
214
+ length : number ,
215
+ points : {
216
+ x1 : number
217
+ y1 : number
218
+ x2 : number
219
+ y2 : number
220
+ } ,
221
+ inheritableStyle : Record < string , string | number >
222
+ ) {
223
+ const { x1, x2, y1, y2 } = points
224
+ const p1 = ! stops [ 0 ] . offset
225
+ ? 0
226
+ : stops [ 0 ] . offset . unit === '%'
227
+ ? Number ( stops [ 0 ] . offset . value ) / 100
228
+ : lengthToNumber (
229
+ `${ stops [ 0 ] . offset . value } ${ stops [ 0 ] . offset . unit } ` ,
230
+ inheritableStyle . fontSize as number ,
231
+ length ,
232
+ inheritableStyle ,
233
+ true
234
+ ) / length
235
+ const p2 = ! stops . at ( - 1 ) . offset
236
+ ? 1
237
+ : stops . at ( - 1 ) . offset . unit === '%'
238
+ ? Number ( stops . at ( - 1 ) . offset . value ) / 100
239
+ : lengthToNumber (
240
+ `${ stops . at ( - 1 ) . offset . value } ${ stops . at ( - 1 ) . offset . unit } ` ,
241
+ inheritableStyle . fontSize as number ,
242
+ length ,
243
+ inheritableStyle ,
244
+ true
245
+ ) / length
246
+
247
+ const sx = ( x2 - x1 ) * p1 + x1
248
+ const sy = ( y2 - y1 ) * p1 + y1
249
+ const ex = ( x2 - x1 ) * p2 + x1
250
+ const ey = ( y2 - y1 ) * p2 + y1
251
+
252
+ return {
253
+ x1 : sx ,
254
+ y1 : sy ,
255
+ x2 : ex ,
256
+ y2 : ey ,
257
+ }
177
258
}
0 commit comments