Skip to content

Commit cd668e6

Browse files
committed
✨ feat: 可正常解析内联图片
1 parent b48633e commit cd668e6

File tree

7 files changed

+144
-38
lines changed

7 files changed

+144
-38
lines changed

src/function/nodeToGroup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const nodeToGroup = (node: Element, options?: Options): Group => {
7171
group.layers.length === 1 &&
7272
(group.layers[0].class === 'rectangle' ||
7373
group.layers[0].class === 'text' ||
74+
group.layers[0].class === 'bitmap' ||
7475
group.layers[0].class === 'svg' ||
7576
group.layers[0].class === 'group')
7677
) {

src/function/nodeToLayers.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
/* eslint-disable no-console */
22
import { defaultNodeStyle } from '../model/utils';
3+
import { Text } from '../model';
34

45
import parserToSvg from '../parser/svg';
5-
import transferToShape from '../parser/shape';
6-
import transferToText from '../parser/text';
6+
import parserToShape from '../parser/shape';
7+
import parserToText from '../parser/text';
78
import parserPseudoText from '../parser/pseudoText';
89
import parserPseudoShape from '../parser/pseudoShape';
910

11+
import { AnyLayer } from '..';
1012
import { isTextVisible } from '../utils/visibility';
11-
import { isTextNode } from '../utils/nodeType';
12-
import { Text } from '../model';
13-
import { AnyLayer } from '../type';
13+
import {
14+
isImageNode,
15+
isSVGDescendantNode,
16+
isTextNode,
17+
} from '../utils/nodeType';
1418
import { isExistPseudoText, isExistPseudoShape } from '../utils/shape';
19+
import parserToImage from '../parser/image';
1520

1621
/**
1722
* 是否是默认样式
@@ -26,12 +31,6 @@ const isDefaultStyles = (styles: CSSStyleDeclaration) =>
2631
return defaultValue === value;
2732
});
2833

29-
/**
30-
* 是否是 SVG 的子级
31-
*/
32-
const isSVGDescendant = (node: Element) =>
33-
node instanceof SVGElement && node.matches('svg *');
34-
3534
/**
3635
* 将节点转为 Layer 对象
3736
* @param {HTMLElement} node 节点
@@ -44,15 +43,19 @@ const nodeToLayers = (node: Element): AnyLayer[] => {
4443

4544
// ----- 初步判断 ------ //
4645
// skip Svg child nodes as they are already covered by `new Svg(…)`
47-
if (isSVGDescendant(node)) {
46+
if (isSVGDescendantNode(node)) {
4847
console.log('SVG 内部节点,跳过...');
4948
return layers;
5049
}
5150

5251
// ---------- 处理子节点 ---------- //
5352

54-
// 使用 Rect 类, 图片填充到 Fill 里
55-
const isImage = nodeName === 'img' && (node as HTMLImageElement).currentSrc;
53+
if (isImageNode(node)) {
54+
const image = parserToImage(<HTMLImageElement>node);
55+
layers.push(image);
56+
return layers;
57+
}
58+
5659
// 图层存在样式(阴影 边框等) 使用 Rect 类
5760
const isShape = !isDefaultStyles(styles);
5861
// 使用 SVG 类
@@ -61,17 +64,17 @@ const nodeToLayers = (node: Element): AnyLayer[] => {
6164
const isText = isTextNode(node);
6265

6366
// 如果图层存在样式(阴影 边框等 返回 shape 节点
64-
if (isImage || isShape || isExistPseudoShape(node)) {
67+
if (isShape || isExistPseudoShape(node)) {
6568
// 判断一下是否有伪类
6669
const afterEl = parserPseudoShape(node, 'after');
6770

6871
if (afterEl) {
6972
layers.push(afterEl);
7073
}
7174

72-
if (isImage || isShape) {
75+
if (isShape) {
7376
// 添加后继续执行,不终止
74-
const shape = transferToShape(node);
77+
const shape = parserToShape(node);
7578

7679
console.info('转换为:', shape);
7780
layers.push(shape);
@@ -102,7 +105,7 @@ const nodeToLayers = (node: Element): AnyLayer[] => {
102105
if (isText || isExistPseudoText(node)) {
103106
let text;
104107
if (isText) {
105-
text = transferToText(node);
108+
text = parserToText(node);
106109
console.info('转换为:', text);
107110
if (text instanceof Array) {
108111
for (let i = 0; i < text.length; i += 1) {

src/model/Layer/Bitmap.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import SketchFormat from '@sketch-hq/sketch-file-format-ts';
22
import BaseLayer, { BaseLayerParams } from '../Base/BaseLayer';
3-
import { sketchImage, defaultExportOptions } from '../utils';
3+
import { defaultExportOptions } from '../utils';
4+
import { getBase64ImageString } from '../../utils/url';
5+
import { uuid } from '../../utils/utils';
46

57
interface BitmapInitParams extends BaseLayerParams {
68
url: string;
@@ -16,6 +18,9 @@ class Bitmap extends BaseLayer {
1618
url.indexOf('data:') === 0 ? Bitmap.ensureBase64DataURL(url) : url;
1719
}
1820

21+
/**
22+
* base64 的
23+
*/
1924
url: string;
2025

2126
/**
@@ -26,7 +31,7 @@ class Bitmap extends BaseLayer {
2631
do_objectID: this.id,
2732
frame: this.frame.toSketchJSON(),
2833
style: this.style.toSketchJSON(),
29-
image: sketchImage(this.url),
34+
image: this.toSketchImageJSON(),
3035
booleanOperation: SketchFormat.BooleanOperation.NA,
3136
exportOptions: defaultExportOptions,
3237
clippingMask: '',
@@ -51,11 +56,19 @@ class Bitmap extends BaseLayer {
5156
/**
5257
* 转换为 Sketch 引用 JSON 对象
5358
* */
54-
toSketchRefJSON = (): SketchFormat.FileRef => {
59+
toSketchImageJSON = (): SketchFormat.DataRef => {
60+
const base64 = getBase64ImageString(this.url);
61+
if (!base64) throw Error('不正确的图像网址');
5562
return {
56-
_class: 'MSJSONFileReference',
63+
_class: 'MSJSONOriginalDataReference',
5764
_ref_class: 'MSImageData',
58-
_ref: `images/${this.url}`,
65+
_ref: `images/${uuid()}`,
66+
data: {
67+
_data: base64,
68+
},
69+
sha1: {
70+
_data: '',
71+
},
5972
};
6073
};
6174

src/parser/image.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,50 @@
1-
import {
2-
Bitmap,
3-
// Svg
4-
} from '../model';
1+
import { Bitmap } from '../model';
52
import { isImageNode } from '../utils/nodeType';
63

4+
/**
5+
*
6+
* @param img html的 img 标签
7+
* @param format 图片格式
8+
* @returns {string}
9+
*/
10+
export const getImageBase64 = (
11+
img: HTMLImageElement,
12+
format: string = 'png',
13+
) => {
14+
// 1. 创建canvas DOM元素,并设置其宽高和图片一样
15+
let canvas: HTMLCanvasElement | null = document.createElement('canvas');
16+
canvas.width = img.width;
17+
canvas.height = img.height;
18+
19+
// 2. 使用画布画图
20+
const ctx = canvas.getContext('2d');
21+
ctx?.drawImage(img, 0, 0, img.width, img.height);
22+
23+
// 3. 返回的是一串Base64编码的URL并指定格式
24+
const dataURL = canvas.toDataURL(`image/${format}`);
25+
canvas = null; // 释放
26+
return dataURL;
27+
};
28+
29+
/**
30+
* 将图片 image 解析为图片
31+
* @param node HTMLImageElement
32+
*/
733
const parserToImage = (node: HTMLImageElement) => {
834
if (!isImageNode(node)) {
935
return;
1036
}
11-
const url = node.src;
12-
if (url.includes('svg')) {
13-
// return new Svg({svgString})
14-
}
15-
const image = new Bitmap({ url });
37+
const { width, height, y, x } = node.getBoundingClientRect();
38+
39+
const url = getImageBase64(node);
1640

17-
return image;
41+
return new Bitmap({
42+
url,
43+
x,
44+
y,
45+
width,
46+
height,
47+
});
1848
};
1949

2050
export default parserToImage;

src/utils/__tests__/url.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { getBase64ImageString } from '../url';
2+
3+
describe('getBase64ImageString', () => {
4+
it('不符合 Base64 则返回空', () => {
5+
const url = 'str';
6+
expect(getBase64ImageString(url)).toBeUndefined();
7+
});
8+
it('符合 Base64 则返回可用的Base64 值', () => {
9+
const url =
10+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg==';
11+
12+
expect(getBase64ImageString(url)).toBe(
13+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg==',
14+
);
15+
});
16+
});

src/utils/nodeType.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/**
2+
* 判断是否是某种节点类型
3+
* @param node
4+
* @param type
5+
*/
6+
export const isNodeType = (node: Element, type: string | string[]): boolean => {
7+
if (!node) return false;
8+
const nodeName = node.nodeName.toLowerCase();
9+
10+
if (typeof type === 'string') {
11+
return type === nodeName;
12+
}
13+
14+
return type.includes(nodeName);
15+
};
16+
117
/**
218
* 判断是否是 Text 节点
319
*/
@@ -12,15 +28,25 @@ export const isTextNode = (node: Element): boolean => {
1228
* 判断是否是 Group 节点
1329
*/
1430
export const isGroupNode = (node: Element): boolean => {
15-
const nodeName = node.nodeName.toLowerCase();
16-
return nodeName === 'div' || nodeName === 'span' || nodeName === 'th';
31+
return isNodeType(node, ['div', 'span', 'th']);
1732
};
1833

1934
/**
2035
* 判断是否是图片节点
2136
*/
2237
export const isImageNode = (node: Element): boolean => {
23-
if (!node) return false;
24-
const nodeName = node.nodeName.toLowerCase();
25-
return nodeName === 'img';
38+
return isNodeType(node, 'img');
2639
};
40+
41+
/**
42+
* 判断是否是图片节点
43+
*/
44+
export const isCanvasNode = (node: Element): boolean => {
45+
return isNodeType(node, 'canvas');
46+
};
47+
48+
/**
49+
* 是否是 SVG 的子级
50+
*/
51+
export const isSVGDescendantNode = (node: Element) =>
52+
node instanceof SVGElement && node.matches('svg *');

src/utils/url.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* 判断是否是 Base64Image 字符串
3+
*
4+
* 如果是 则返回可以用的字符串
5+
*
6+
* 否则返回空
7+
* @param url 解析后的 base64 URL
8+
*/
9+
export const getBase64ImageString = (url: string): string | undefined => {
10+
const reg = /data:image\/.*;base64,(.*==)/;
11+
const group = reg.exec(url);
12+
13+
if (!group) {
14+
return;
15+
}
16+
return group[1];
17+
};

0 commit comments

Comments
 (0)