Skip to content

Commit f9c20d7

Browse files
committed
✨ 支持SvgNode
1 parent 944ab72 commit f9c20d7

File tree

6 files changed

+53
-91
lines changed

6 files changed

+53
-91
lines changed

app/src/core/render/canvas2d/basicRenderer/svgRenderer.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Vector } from "@graphif/data-structures";
21
import { Project, service } from "@/core/Project";
2+
import { Base64 } from "@/utils/base64";
3+
import { Vector } from "@graphif/data-structures";
34

45
@service("svgRenderer")
56
export class SvgRenderer {
@@ -65,10 +66,7 @@ export class SvgRenderer {
6566
img.onerror = (error) => {
6667
reject(error);
6768
};
68-
// img.src = "data:image/svg+xml;base64," + btoa(svg);
69-
// 以上方法,出现汉字会报错,因此改用 encodeURIComponent
70-
// 直接使用 URI 编码(无需 Base64)
71-
img.src = "data:image/svg+xml," + encodeURIComponent(svg);
69+
img.src = "data:image/svg+xml;base64," + Base64.encode(svg);
7270
});
7371
}
7472

app/src/core/render/canvas2d/entityRenderer/svgNode/SvgNodeRenderer.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,11 @@ export class SvgNodeRenderer {
2525
16 * this.project.camera.currentScale,
2626
this.project.stageStyleManager.currentStyle.CollideBoxPreSelected,
2727
);
28-
} else if (svgNode.state === "loaded") {
28+
} else if (svgNode.state === "success") {
2929
this.project.svgRenderer.renderSvgFromLeftTopWithoutSize(
3030
svgNode.content,
31-
this.project.renderer.transformWorld2View(svgNode.location),
32-
svgNode.scaleNumber,
33-
);
34-
} else if (svgNode.state === "error") {
35-
this.project.textRenderer.renderTextFromCenter(
36-
"Error",
37-
svgNode.collisionBox.getRectangle().center,
38-
16 * this.project.camera.currentScale,
39-
this.project.stageStyleManager.currentStyle.effects.warningShadow,
31+
this.project.renderer.transformWorld2View(svgNode.collisionBox.getRectangle().location),
32+
svgNode.scale,
4033
);
4134
}
4235

app/src/core/service/dataManageService/copyEngine/copyEngine.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Project, service } from "@/core/Project";
2-
import { SerializedDataAdder } from "@/core/stage/stageManager/concreteMethods/StageSerializedAdder";
32
import { Entity } from "@/core/stage/stageObject/abstract/StageEntity";
43
import { CollisionBox } from "@/core/stage/stageObject/collisionBox/collisionBox";
54
import { ImageNode } from "@/core/stage/stageObject/entity/ImageNode";
@@ -154,7 +153,7 @@ export class CopyEngine {
154153
if (this.isVirtualClipboardEmpty()) {
155154
this.readClipboard();
156155
} else {
157-
SerializedDataAdder.addSerializedData(this.copyBoardData, this.copyBoardMouseVector);
156+
// SerializedDataAdder.addSerializedData(this.copyBoardData, this.copyBoardMouseVector);
158157
}
159158
if (isMac) {
160159
// mac下无法直接粘贴,还要点一个按钮,但这导致
@@ -167,7 +166,7 @@ export class CopyEngine {
167166
}
168167

169168
pasteWithOriginLocation() {
170-
SerializedDataAdder.addSerializedData(this.copyBoardData);
169+
// SerializedDataAdder.addSerializedData(this.copyBoardData);
171170
}
172171

173172
private updateRectangle() {
@@ -223,7 +222,7 @@ export class CopyEngine {
223222
});
224223
this.copyEnginePasteImage(blob);
225224
} catch (err) {
226-
console.error("图片剪贴板是空的", err);
225+
console.warn("图片剪贴板是空的", err);
227226
}
228227
}
229228

@@ -236,11 +235,8 @@ export class CopyEngine {
236235
if (isSvgString(item)) {
237236
// 是SVG类型
238237
entity = new SvgNode(this.project, {
239-
uuid: crypto.randomUUID(),
240238
content: item,
241-
location: [MouseLocation.x, MouseLocation.y],
242-
size: [400, 100],
243-
color: [0, 0, 0, 0],
239+
collisionBox,
244240
});
245241
} else if (PathString.isValidURL(item)) {
246242
// 是URL类型
@@ -341,15 +337,12 @@ function isSvgString(str: string): boolean {
341337
const trimmed = str.trim();
342338

343339
// 基础结构检查
344-
if (
345-
!trimmed.startsWith("<svg") || // 是否以 <svg 开头
346-
!trimmed.endsWith("</svg>") // 是否以 </svg> 结尾
347-
) {
348-
return false;
340+
if (trimmed.startsWith("<svg") || trimmed.endsWith("</svg>")) {
341+
return true;
349342
}
350343

351344
// 提取 <svg> 标签的属性部分
352-
const openTagMatch = trimmed.match(/<svg\s+([^>]*)>/i);
345+
const openTagMatch = trimmed.match(/<svg/i);
353346
if (!openTagMatch) return false; // 无有效属性则直接失败
354347

355348
// 检查是否存在 xmlns 命名空间声明

app/src/core/stage/stageManager/StageHistoryManager.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class HistoryManager {
8888
return applyPatch(acc, patch).newDocument;
8989
}, this.initialStage);
9090
// 反序列化得到舞台对象
91-
const stage = deserialize(data);
91+
const stage = deserialize(data, this.project);
9292
return stage;
9393
}
9494
}

app/src/core/stage/stageObject/entity/SvgNode.tsx

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,90 @@
1-
import { Color, Vector } from "@graphif/data-structures";
2-
import { Rectangle } from "@graphif/shapes";
3-
import { Serialized } from "@/types/node";
41
import { Project } from "@/core/Project";
5-
import { SvgRenderer } from "@/core/render/canvas2d/basicRenderer/svgRenderer";
62
import { ConnectableEntity } from "@/core/stage/stageObject/abstract/ConnectableEntity";
73
import { CollisionBox } from "@/core/stage/stageObject/collisionBox/collisionBox";
4+
import { Color, Vector } from "@graphif/data-structures";
5+
import { passExtraAtArg1, passObject, serializable } from "@graphif/serializer";
6+
import { Rectangle } from "@graphif/shapes";
87

98
/**
109
* Svg 节点
1110
*/
11+
@passExtraAtArg1
12+
@passObject
1213
export class SvgNode extends ConnectableEntity {
14+
@serializable
1315
color: Color = Color.Transparent;
16+
@serializable
1417
uuid: string;
18+
@serializable
1519
details: string;
16-
scaleNumber: number;
17-
public collisionBox: CollisionBox;
20+
@serializable
21+
scale: number;
22+
@serializable
23+
collisionBox: CollisionBox;
24+
@serializable
1825
content: string;
19-
location: Vector;
20-
originSize: Vector;
21-
state: "loading" | "loaded" | "error" = "loading";
26+
state: "loading" | "success" = "loading";
2227
isHiddenBySectionCollapse: boolean = false;
2328

29+
originalSize: Vector = Vector.getZero();
30+
2431
constructor(
2532
protected readonly project: Project,
2633
{
27-
uuid,
34+
uuid = crypto.randomUUID(),
2835
details = "",
2936
content = "",
30-
location = [0, 0],
37+
collisionBox = new CollisionBox([new Rectangle(Vector.getZero(), Vector.getZero())]),
3138
scale = 1,
32-
color = [0, 0, 0, 0],
33-
}: Partial<Serialized.SvgNode> & { uuid: string },
39+
color = Color.Transparent,
40+
},
3441
) {
3542
super();
3643
this.uuid = uuid;
3744
this.details = details;
38-
this.scaleNumber = scale;
45+
this.scale = scale;
3946
this.content = content;
40-
this.location = new Vector(...location);
41-
this.color = new Color(...color);
42-
43-
this.originSize = new Vector(100, 100);
44-
// 解析svg尺寸
45-
SvgRenderer.getSvgOriginalSize(content)
46-
.then((size) => {
47-
this.originSize = size;
48-
this.collisionBox = new CollisionBox([
49-
new Rectangle(new Vector(...location), this.originSize.multiply(this.scaleNumber)),
50-
]);
51-
this.state = "loaded";
52-
})
53-
.catch((error) => {
54-
this.state = "error";
55-
console.error(error);
56-
});
57-
58-
this.collisionBox = new CollisionBox([
59-
new Rectangle(new Vector(...location), this.originSize.multiply(this.scaleNumber)),
60-
]);
47+
this.collisionBox = collisionBox;
48+
this.color = color;
49+
// 获取SVG原始大小
50+
this.project.svgRenderer.getSvgOriginalSize(content).then((size) => {
51+
this.originalSize = size;
52+
this.collisionBox = new CollisionBox([
53+
new Rectangle(this.collisionBox.getRectangle().location, this.originalSize.multiply(this.scale)),
54+
]);
55+
this.state = "success";
56+
});
6157
}
6258

6359
public get geometryCenter(): Vector {
6460
return this.collisionBox.getRectangle().center;
6561
}
6662

6763
public scaleUpdate(scaleDiff: number) {
68-
this.scaleNumber += scaleDiff;
69-
if (this.scaleNumber < 0.1) {
70-
this.scaleNumber = 0.1;
64+
this.scale += scaleDiff;
65+
if (this.scale < 0.1) {
66+
this.scale = 0.1;
7167
}
72-
if (this.scaleNumber > 10) {
73-
this.scaleNumber = 10;
68+
if (this.scale > 10) {
69+
this.scale = 10;
7470
}
7571

76-
this.collisionBox = new CollisionBox([new Rectangle(this.location, this.originSize.multiply(this.scaleNumber))]);
72+
this.collisionBox = new CollisionBox([
73+
new Rectangle(this.collisionBox.getRectangle().location, this.originalSize.multiply(this.scale)),
74+
]);
7775
}
7876

7977
move(delta: Vector): void {
8078
const newRectangle = this.collisionBox.getRectangle().clone();
8179
newRectangle.location = newRectangle.location.add(delta);
8280
this.collisionBox.shapes[0] = newRectangle;
83-
this.location = newRectangle.location.clone();
8481
this.updateFatherSectionByMove();
8582
}
8683

8784
moveTo(location: Vector): void {
8885
const newRectangle = this.collisionBox.getRectangle().clone();
8986
newRectangle.location = location.clone();
9087
this.collisionBox.shapes[0] = newRectangle;
91-
this.location = newRectangle.location.clone();
9288
this.updateFatherSectionByMove();
9389
}
9490

app/src/core/stage/stageObject/entity/UnknownEntity.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)