Skip to content

Commit bdd0dca

Browse files
authoredSep 22, 2023
fix: use splitEffects to split multiple effects (#553)
We always need to handle mutiple effects. Let's give it a fn and add some tests
1 parent fc356ec commit bdd0dca

File tree

4 files changed

+65
-54
lines changed

4 files changed

+65
-54
lines changed
 

‎src/handler/expand.ts

+2-25
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import CssDimension from '../vendor/parse-css-dimension/index.js'
1212
import parseTransformOrigin, {
1313
ParsedTransformOrigin,
1414
} from '../transform-origin.js'
15-
import { isString, lengthToNumber, v } from '../utils.js'
15+
import { isString, lengthToNumber, v, splitEffects } from '../utils.js'
1616
import { MaskProperty, parseMask } from '../parser/mask.js'
1717

1818
// https://react-cn.github.io/react/tips/style-props-value-px.html
@@ -173,7 +173,7 @@ function handleSpecialCase(
173173
// Handle multiple text shadows if provided.
174174
value = value.toString().trim()
175175
if (value.includes(',')) {
176-
const shadows = splitTextShadow(value)
176+
const shadows = splitEffects(value)
177177
const result = {}
178178
for (const shadow of shadows) {
179179
const styles = getStylesForProperty('textShadow', shadow, true)
@@ -192,29 +192,6 @@ function handleSpecialCase(
192192
return
193193
}
194194

195-
function splitTextShadow(str: string) {
196-
const result: string[] = []
197-
let skip = false
198-
let startPos = 0
199-
const len = str.length
200-
201-
for (let i = 0; i < len; ++i) {
202-
const t = str[i]
203-
if (t === ')') skip = false
204-
if (skip) continue
205-
if (t === '(') skip = true
206-
207-
if (t === ',') {
208-
result.push(str.substring(startPos, i))
209-
startPos = i + 1
210-
}
211-
}
212-
213-
result.push(str.substring(startPos, len))
214-
215-
return result.map((s) => s.trim())
216-
}
217-
218195
function getErrorHint(name: string) {
219196
if (name === 'transform') {
220197
return ' Only absolute lengths such as `10px` are supported.'

‎src/parser/mask.ts

+2-29
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getPropertyName } from 'css-to-react-native'
2+
import { splitEffects } from '../utils.js'
23

34
function getMaskProperty(style: Record<string, string | number>, name: string) {
45
const key = getPropertyName(`mask-${name}`)
@@ -14,34 +15,6 @@ export interface MaskProperty {
1415
clip: string
1516
}
1617

17-
function splitMaskImages(maskImage) {
18-
let maskImages = []
19-
let start = 0
20-
let parenCount = 0
21-
22-
for (let i = 0; i < maskImage.length; i++) {
23-
if (maskImage[i] === '(') {
24-
parenCount++
25-
} else if (maskImage[i] === ')') {
26-
parenCount--
27-
}
28-
29-
if (parenCount === 0 && maskImage[i] === ',') {
30-
maskImages.push(maskImage.slice(start, i).trim())
31-
start = i + 1
32-
}
33-
}
34-
35-
maskImages.push(maskImage.slice(start).trim())
36-
37-
return maskImages
38-
}
39-
40-
/**
41-
* url(https:a.png), linear-gradient(blue, red) => [url(https:a.png), linear-gradient(blue, red)]
42-
* rgba(0,0,0,.7) => [rgba(0,0,0,.7)]
43-
*/
44-
4518
export function parseMask(
4619
style: Record<string, string | number>
4720
): MaskProperty[] {
@@ -55,7 +28,7 @@ export function parseMask(
5528
clip: getMaskProperty(style, 'origin') || 'border-box',
5629
}
5730

58-
let maskImages = splitMaskImages(maskImage).filter((v) => v && v !== 'none')
31+
let maskImages = splitEffects(maskImage).filter((v) => v && v !== 'none')
5932

6033
return maskImages.reverse().map((m) => ({
6134
image: m,

‎src/utils.ts

+27
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,30 @@ export const midline = (s: string) => {
318318
(_, letter: string) => `-${letter.toLowerCase()}`
319319
)
320320
}
321+
322+
export function splitEffects(
323+
input: string,
324+
separator: string | RegExp = ','
325+
): string[] {
326+
const result = []
327+
let l = 0
328+
let parenCount = 0
329+
separator = new RegExp(separator)
330+
331+
for (let i = 0; i < input.length; i++) {
332+
if (input[i] === '(') {
333+
parenCount++
334+
} else if (input[i] === ')') {
335+
parenCount--
336+
}
337+
338+
if (parenCount === 0 && separator.test(input[i])) {
339+
result.push(input.slice(l, i).trim())
340+
l = i + 1
341+
}
342+
}
343+
344+
result.push(input.slice(l).trim())
345+
346+
return result
347+
}

‎test/units.test.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { it, describe, expect } from 'vitest'
22

33
import { initFonts, toImage } from './utils.js'
44
import satori from '../src/index.js'
5+
import { splitEffects } from '../src/utils.js'
56

67
describe('Units', () => {
78
let fonts
@@ -133,4 +134,37 @@ describe('Units', () => {
133134
)
134135
expect(toImage(svg, 100)).toMatchImageSnapshot()
135136
})
137+
138+
it('should support split multiple effect', () => {
139+
const tests = {
140+
'url(https:a.png), linear-gradient(blue, red)': [
141+
'url(https:a.png)',
142+
'linear-gradient(blue, red)',
143+
],
144+
'rgba(0,0,0,.7)': ['rgba(0,0,0,.7)'],
145+
'1px 1px 2px black, 0 0 1em blue': ['1px 1px 2px black', '0 0 1em blue'],
146+
'2px 2px red, 4px 4px #4bf542, 6px 6px rgba(186, 147, 17, 30%)': [
147+
'2px 2px red',
148+
'4px 4px #4bf542',
149+
'6px 6px rgba(186, 147, 17, 30%)',
150+
],
151+
}
152+
153+
for (const [k, v] of Object.entries(tests)) {
154+
expect(splitEffects(k, ',')).toEqual(v)
155+
}
156+
157+
;[' ', /\s{1}/].forEach((v) => {
158+
expect(
159+
splitEffects(
160+
'drop-shadow(4px 4px 10px blue) blur(4px) saturate(150%)',
161+
v
162+
)
163+
).toEqual([
164+
'drop-shadow(4px 4px 10px blue)',
165+
'blur(4px)',
166+
'saturate(150%)',
167+
])
168+
})
169+
})
136170
})

0 commit comments

Comments
 (0)
Please sign in to comment.