Skip to content

Commit 9860090

Browse files
committed
✨ feat: support rad angle line-gradient #96
1 parent 1626b8f commit 9860090

File tree

5 files changed

+95
-32
lines changed

5 files changed

+95
-32
lines changed

src/models/Style/Style.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ class Style extends BaseStyle {
100100
const from = { x: 0.5, y: 0 };
101101
const to = { x: 0.5, y: 1 };
102102

103+
// 处理带 rad 弧度的对象
104+
if (angle.includes('rad')) {
105+
const rad = parseFloat(angle.split('rad')[0]);
106+
from.x = 0;
107+
from.y = 0;
108+
109+
// 获取自然数 0 (-0 -> 0)
110+
const getNaturalZero = (num: number) => (Math.abs(num) === 0 ? 0 : num);
111+
112+
const x = Math.round(Math.cos(rad) * 100) / 100;
113+
const y = Math.round(Math.sin(rad) * 100) / 100;
114+
115+
to.x = getNaturalZero(x);
116+
to.y = getNaturalZero(y);
117+
}
103118
// Learn math or find someone smarter to figure this out correctly
104119
switch (angle) {
105120
case 'to top':

src/utils/background.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@ export const parseLinearGradient = (value: string) => {
5454
};
5555
}
5656
if (parts.length > 2) {
57-
// 如果 parts 的第一个对象 不包含 deg to
57+
// 如果 parts 的第一个对象 不包含 deg to 或者 rad
5858
// 那就意味着全部都是 stops
59-
if (!parts[0].includes('deg') && !parts[0].includes('to')) {
59+
if (
60+
!parts[0].includes('deg') &&
61+
!parts[0].includes('to') &&
62+
!parts[0].includes('rad')
63+
) {
6064
return { angle: '180deg', stops: parts };
6165
}
6266

tests/__tests__/parser/html/shape.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
.linear-gradient {
1414
background-image: linear-gradient(red, yellow, blue);
1515
}
16+
.linear-gradient-angle {
17+
background-image: linear-gradient(1.5708rad, red, yellow, blue);
18+
}
1619

1720
.radial-gradient {
1821
background-image: radial-gradient(red, yellow, blue);
@@ -26,6 +29,12 @@
2629
style="width: 200px; height: 200px"
2730
></div>
2831

32+
<div
33+
id="linear-gradient-angle"
34+
class="linear-gradient-angle"
35+
style="width: 200px; height: 200px"
36+
></div>
37+
2938
<div
3039
id="linear-gradient"
3140
class="linear-gradient"

tests/__tests__/parser/shape.spec.ts

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -165,23 +165,56 @@ describe('parseToShape', () => {
165165
expect(shadow4?.offsetX).toBe(2);
166166
expect(shadow4?.color.blue).toBe(1);
167167
});
168-
it('background-image 正常解析', async () => {
169-
const node = document.getElementById('background-image') as HTMLDivElement;
170-
const rectangle = (await parseToShape(node)) as Rectangle;
171-
172-
expect(rectangle.class).toBe('rectangle');
173-
const fill = rectangle.style.fills[0];
174-
expect(fill.type).toBe(4);
175-
176-
expect(fill.image?.url).toBe(
177-
'https://gw.alipayobjects.com/zos/rmsportal/mZBWtboYbnMkTBaRIuWQ.png',
178-
);
179-
180-
expect(rectangle.frame.width).toBe(200);
181-
expect(rectangle.frame.height).toBe(200);
182-
expect(fill.image?.base64).toBe(
183-
'iVBORw0KGgoAAAANSUhEUgAAALoAAAC6CAMAAAAu0KfDAAABa1BMVEUAAAAy/MoNt+ELtOUc0cMc2LwUxNgVyM4j4bEm56og4K8SwtQYz8UPvNwZzsYVyc0i2L1c5dMPvtoRwdUWycso7KoRvdwOtOUp8Zwc1b8MtuIl5qkf27ga0sAr8pwMtOQp8pwr9Joe2bko66Yh4a8Ux88i5KsKtOQNt+Er9Zgb1r4Pu9sm6qUNt98c1b8MtuMq85kr9Joc08Ac170p750b1MAp85sd1bwb08ALtuEo8JwLteIKs+Qa1L0Z078o8pkn750Z0sEp85kTxs4p9Jgb1b0o8poKtOQZ074RwtMg4a4o8psSxNAY0MAi5Koh4q0Sxc8Mt98h46sSw9Ih4qwSwtEa1L4a1bsi5KwTxNEj5aoo8J0QwNYi5Kkl6qMb17oc2bgj6KYm7KEOvNoVysoXz8QPvtgf3rId2rYe3LQVy8gXzcYf37Ak6aUNutwUyMwj5qgn7p8Mud4e3LMm7aAY0MIg4a8Tx84WzMikfcgIAAAAOXRSTlMABe6fN3YarV9J/p+fn+6fEQLu7u4hVy3lhHhAKvb025NaVjL48e7IuLKwkHL05OLbzMvFr417c2nUVZYYAAAEnUlEQVR42u3WaVMTQRAG4F5F1HhlVU5RDu/7PghXIARJCAFCIuHGQDgCGEUg/Hx7J5md2lqT0pk41Fj9VBfdM7MNb/lJIIQQQgghhBBCCCGEEELIvxHsftw6qtXjbhvqwOoacYyOjGKN8Bl5zliic/wssf8qCMqa7ogoQyNDDA5YvPvvvOT2X6snH/Lgf8J/Hhsawxrykd6/C2qsO/jrsMYYPou7arMgvf8M1HRNOMYmxhgcsDydv1d7k9//AEqCodBEaAKLdcTn33dOnBX2b4GS7tCpabVBybsQEw6Fw4vhRSa06LnDs5jxB44hvOKz9P6dJlDTGj4drV0WKAq7gpZOoO6HywLDGBz90GVc9Fwud5g7xMqZGL2Momu0ksut5FYc5kV3UHTN9leY/ZV986JzFF2jry6Krs93g6O7KLo+ywvchYsqbjXZoBlGX15YZnDAYp3jZ19PLiSTy0lxhx7cBb2SdaQru4ieSqZceMDydEbcud9xYv+BBTqlyrKpLINDKp1KY4mOl+we8Vnw7DeBTulsNp1Nu7LszLoD2x/dV1wEnaa80lNpBgcs3n13VXSATlPx+FR8ionzuRbxnX++Z4FO8Tq6BVoFAoHjwPFx/DjOBOIBLG9HbMaPsHxvfP+e5uQQcD27oKKnwwLN9lzG/R/G4Ohbe3tbe1sO86I7KLpmP13mRd/Z+bljZvQdF0XXZ2ebMy76trnR17aZte0186JzFL06ii6suih6VRRduL26Orc652g7p8fHngYL6uHt3Fx0LoolOmIzqnbP4VFq/0kjqGuLRgeiA1hRH7xE/I1/JzontX8OlHUM1ML/VA2y+09Alf3iy8CXAQYHLNY5fvZ1z5vcfhuoahjnBscHscYlyO03gar3+OewBqvi795ZkN1/A6rsy/hrhgeHGRx88BLLNwuy+0FQ9mnSNTw5jMU6qj2L7yX3e0BdsO328O8jse6/85PZPwf1YAc7GnS5PVlxGUxzdfKozMDoR/n8Ud7M6PkKiq7R1d08s2tm9N387q6R0R0UXa+rhZOTwkmhUDAxuoOi63WzsMEUroFpbm5UUHSNbhY3mKKJ0csouk73i6VSsVQslrRHDzZfO6+kFClhRSIR3dGbI/WhP3pDxNTo9v3p+rkEOgWnEx7TiWksb0fY+LtvFvRGb0zMzyfmE1ise8z73/g94m+ia45uZcp/NjOfySRwxh8OPHrvePffi33N0eFSJjObmWVwwGId8Vm8e7/xv2c0R7duzMZmYwwOLjxgiS6+qfGmOTpYT2P1cgl0s4ONSm6I6Ka5sRSLLcWWlpZMjO6g6DVR9P8i+vXNCoOjXwHTmBx9fXNzfXN9fd3E6A6Krtf1bwZHrzAv+hmTox8cfDswM/pBBUXX6MxMhXL0lkfPz2g1w7WDmvaZ3plerBlGzJ6z6P5v5febFf/Ne09PIyh52vvX+nr7sPhJfr8T1Lzs88BfieWfOf+9/H4LqHnUd1rOgqLmz1L6P/djsVFy/6ENiuxOjMCi8M6Js4fvXW7/rA3Kgg/79etsgbpoaT+rVXtzIxBCCCGEEEIIIYQQQgj5c78Aun4NkqgvDW0AAAAASUVORK5CYII=',
184-
);
168+
describe('background-image 正常解析', () => {
169+
it('普通图片正常', async () => {
170+
const node = document.getElementById(
171+
'background-image',
172+
) as HTMLDivElement;
173+
const rectangle = (await parseToShape(node)) as Rectangle;
174+
175+
expect(rectangle.class).toBe('rectangle');
176+
const fill = rectangle.style.fills[0];
177+
expect(fill.type).toBe(4);
178+
179+
expect(fill.image?.url).toBe(
180+
'https://gw.alipayobjects.com/zos/rmsportal/mZBWtboYbnMkTBaRIuWQ.png',
181+
);
182+
183+
expect(rectangle.frame.width).toBe(200);
184+
expect(rectangle.frame.height).toBe(200);
185+
expect(fill.image?.base64).toBe(
186+
'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',
187+
);
188+
});
189+
it('linear-gradient 正常解析', async () => {
190+
const node = document.getElementById('linear-gradient') as HTMLDivElement;
191+
const rectangle = (await parseToShape(node)) as Rectangle;
192+
193+
expect(rectangle.class).toBe('rectangle');
194+
const fill = rectangle.style.fills[0];
195+
196+
expect(fill.type).toBe(1);
197+
198+
expect(rectangle.frame.width).toBe(200);
199+
expect(rectangle.frame.height).toBe(200);
200+
expect(fill.gradient?.stops.length).toBe(3);
201+
});
202+
it('linear-gradient 如果有角度也可以正常解析', async () => {
203+
const node = document.getElementById(
204+
'linear-gradient-angle',
205+
) as HTMLDivElement;
206+
const rectangle = (await parseToShape(node)) as Rectangle;
207+
208+
expect(rectangle.class).toBe('rectangle');
209+
const fill = rectangle.style.fills[0];
210+
211+
expect(fill.type).toBe(1);
212+
213+
expect(rectangle.frame.width).toBe(200);
214+
expect(rectangle.frame.height).toBe(200);
215+
expect(fill.gradient?.from).toEqual({ x: 0, y: 0 });
216+
expect(fill.gradient?.to).toEqual({ x: 0, y: 1 });
217+
});
185218
});
186219
it('clip-background-image 正常解析', async () => {
187220
const node = document.getElementById(
@@ -214,19 +247,6 @@ describe('parseToShape', () => {
214247
'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',
215248
);
216249
});
217-
it('linear-gradient 正常解析', async () => {
218-
const node = document.getElementById('linear-gradient') as HTMLDivElement;
219-
const rectangle = (await parseToShape(node)) as Rectangle;
220-
221-
expect(rectangle.class).toBe('rectangle');
222-
const fill = rectangle.style.fills[0];
223-
224-
expect(fill.type).toBe(1);
225-
226-
expect(rectangle.frame.width).toBe(200);
227-
expect(rectangle.frame.height).toBe(200);
228-
expect(fill.gradient?.stops.length).toBe(3);
229-
});
230250
it('radial-gradient 暂不支持解析', async () => {
231251
const node = document.getElementById('radial-gradient') as HTMLDivElement;
232252
const rectangle = (await parseToShape(node)) as Rectangle;

tests/__tests__/utils/background.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ describe('parseBackgroundImage', () => {
166166
const result = parseBackgroundImageType(str) as BackgroundImageType;
167167
expect(result.type).toStrictEqual('LinearGradient');
168168
});
169+
it('解析 angle 三种颜色', () => {
170+
const str = 'linear-gradient(1.5708rad, red, yellow, blue)';
171+
const result = parseBackgroundImageType(str) as BackgroundImageType;
172+
expect(result.type).toStrictEqual('LinearGradient');
173+
expect(result.value.angle).toStrictEqual('1.5708rad');
174+
expect(result.value.stops.length).toEqual(3);
175+
});
169176
});
170177
});
171178

@@ -194,6 +201,14 @@ describe('parseLinearGradient', () => {
194201
angle: '90deg',
195202
});
196203
});
204+
it('解析 angle 类型的方向', () => {
205+
const str = '1.5708rad, red, blue';
206+
const result = parseLinearGradient(str);
207+
expect(result).toStrictEqual({
208+
stops: ['red', 'blue'],
209+
angle: '1.5708rad',
210+
});
211+
});
197212
it('解析三种颜色', () => {
198213
const str = 'red, yellow, blue';
199214
const result = parseLinearGradient(str);

0 commit comments

Comments
 (0)