@mirage-engine/core 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/mirage-engine.js +1016 -418
- package/dist/mirage-engine.umd.js +136 -72
- package/dist/src/animation/Animator.d.ts +11 -0
- package/dist/src/core/Engine.d.ts +2 -0
- package/dist/src/core/Syncer.d.ts +8 -1
- package/dist/src/renderer/Renderer.d.ts +9 -4
- package/dist/src/store/MeshRegistry.d.ts +9 -0
- package/dist/src/store/TextureLifecycleManager.d.ts +16 -0
- package/dist/src/types/animate.d.ts +19 -0
- package/dist/src/types/common.d.ts +7 -1
- package/dist/src/types/config.d.ts +4 -0
- package/dist/src/types/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/animation/Animator.ts +163 -0
- package/src/core/Engine.ts +56 -4
- package/src/core/Syncer.ts +71 -16
- package/src/dom/Extractor.ts +176 -21
- package/src/env.d.ts +4 -0
- package/src/renderer/Renderer.ts +206 -80
- package/src/store/MeshRegistry.ts +32 -0
- package/src/store/TextureLifecycleManager.ts +111 -0
- package/src/types/animate.ts +26 -0
- package/src/types/common.ts +4 -1
- package/src/types/config.ts +4 -0
- package/src/types/index.ts +2 -1
package/src/dom/Extractor.ts
CHANGED
|
@@ -11,21 +11,124 @@ import {
|
|
|
11
11
|
ALLOWED_FILTERS,
|
|
12
12
|
} from "../types";
|
|
13
13
|
|
|
14
|
-
import { BoxStyles, TextStyles } from "@mirage-engine/painter";
|
|
14
|
+
import { BoxStyles, TextStyles, ShaderHooks } from "@mirage-engine/painter";
|
|
15
15
|
import { FilterConfig } from "../types/config";
|
|
16
16
|
|
|
17
17
|
// Helper function: getTextNodeRect, isValidTextNode, isLeafTextElement, extractTextStyles
|
|
18
18
|
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
function extractTextLines(textNode: Text): { text: string; rect: { left: number; top: number; width: number; height: number } }[] {
|
|
20
|
+
const text = textNode.textContent || "";
|
|
21
|
+
const lines: { text: string; rect: { left: number; top: number; width: number; height: number } }[] = [];
|
|
22
|
+
|
|
23
|
+
let currentLineText = "";
|
|
24
|
+
let currentLineRect: { left: number; top: number; right: number; bottom: number } | null = null;
|
|
25
|
+
let currentTop = -1;
|
|
26
|
+
|
|
27
|
+
const processChunk = (chunkText: string, offset: number) => {
|
|
28
|
+
for (let i = 0; i < chunkText.length; i++) {
|
|
29
|
+
const char = chunkText[i];
|
|
30
|
+
const range = document.createRange();
|
|
31
|
+
range.setStart(textNode, offset + i);
|
|
32
|
+
range.setEnd(textNode, offset + i + 1);
|
|
33
|
+
const rect = range.getBoundingClientRect();
|
|
34
|
+
|
|
35
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
36
|
+
currentLineText += char;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (currentTop === -1 || Math.abs(rect.top - currentTop) > rect.height / 2) {
|
|
41
|
+
if (currentLineText && currentLineRect) {
|
|
42
|
+
lines.push({
|
|
43
|
+
text: currentLineText,
|
|
44
|
+
rect: {
|
|
45
|
+
left: currentLineRect.left,
|
|
46
|
+
top: currentLineRect.top,
|
|
47
|
+
width: currentLineRect.right - currentLineRect.left,
|
|
48
|
+
height: currentLineRect.bottom - currentLineRect.top,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
currentLineText = char;
|
|
53
|
+
currentLineRect = { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
|
|
54
|
+
currentTop = rect.top;
|
|
55
|
+
} else {
|
|
56
|
+
currentLineText += char;
|
|
57
|
+
if (currentLineRect) {
|
|
58
|
+
currentLineRect.left = Math.min(currentLineRect.left, rect.left);
|
|
59
|
+
currentLineRect.top = Math.min(currentLineRect.top, rect.top);
|
|
60
|
+
currentLineRect.right = Math.max(currentLineRect.right, rect.right);
|
|
61
|
+
currentLineRect.bottom = Math.max(currentLineRect.bottom, rect.bottom);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
28
65
|
};
|
|
66
|
+
|
|
67
|
+
const tokens = text.match(/[^\s\-]+\-?|\-|\s+/g) || [];
|
|
68
|
+
let currentOffset = 0;
|
|
69
|
+
|
|
70
|
+
for (const token of tokens) {
|
|
71
|
+
const range = document.createRange();
|
|
72
|
+
range.setStart(textNode, currentOffset);
|
|
73
|
+
range.setEnd(textNode, currentOffset + token.length);
|
|
74
|
+
const rects = range.getClientRects();
|
|
75
|
+
|
|
76
|
+
if (rects.length > 1) {
|
|
77
|
+
// Token wraps across lines (e.g. word-break: break-all)
|
|
78
|
+
// Fallback to precise character-by-character iteration for this token
|
|
79
|
+
processChunk(token, currentOffset);
|
|
80
|
+
} else {
|
|
81
|
+
// Token is on a single line. Process it as a single block!
|
|
82
|
+
const rect = rects.length === 1 ? rects[0] : range.getBoundingClientRect();
|
|
83
|
+
|
|
84
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
85
|
+
currentLineText += token;
|
|
86
|
+
currentOffset += token.length;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (currentTop === -1 || Math.abs(rect.top - currentTop) > rect.height / 2) {
|
|
91
|
+
if (currentLineText && currentLineRect) {
|
|
92
|
+
lines.push({
|
|
93
|
+
text: currentLineText,
|
|
94
|
+
rect: {
|
|
95
|
+
left: currentLineRect.left,
|
|
96
|
+
top: currentLineRect.top,
|
|
97
|
+
width: currentLineRect.right - currentLineRect.left,
|
|
98
|
+
height: currentLineRect.bottom - currentLineRect.top,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
currentLineText = token;
|
|
103
|
+
currentLineRect = { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
|
|
104
|
+
currentTop = rect.top;
|
|
105
|
+
} else {
|
|
106
|
+
currentLineText += token;
|
|
107
|
+
if (currentLineRect) {
|
|
108
|
+
currentLineRect.left = Math.min(currentLineRect.left, rect.left);
|
|
109
|
+
currentLineRect.top = Math.min(currentLineRect.top, rect.top);
|
|
110
|
+
currentLineRect.right = Math.max(currentLineRect.right, rect.right);
|
|
111
|
+
currentLineRect.bottom = Math.max(currentLineRect.bottom, rect.bottom);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
currentOffset += token.length;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (currentLineText && currentLineRect) {
|
|
120
|
+
lines.push({
|
|
121
|
+
text: currentLineText,
|
|
122
|
+
rect: {
|
|
123
|
+
left: currentLineRect.left,
|
|
124
|
+
top: currentLineRect.top,
|
|
125
|
+
width: currentLineRect.right - currentLineRect.left,
|
|
126
|
+
height: currentLineRect.bottom - currentLineRect.top,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return lines.filter(line => line.text.trim().length > 0 && line.rect.width > 0 && line.rect.height > 0);
|
|
29
132
|
}
|
|
30
133
|
|
|
31
134
|
function extractTextStyles(computed: CSSStyleDeclaration): TextStyles {
|
|
@@ -40,6 +143,7 @@ function extractTextStyles(computed: CSSStyleDeclaration): TextStyles {
|
|
|
40
143
|
}
|
|
41
144
|
return {
|
|
42
145
|
font: `${computed.fontStyle} ${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`,
|
|
146
|
+
fontSize: computed.fontSize,
|
|
43
147
|
color: computed.color,
|
|
44
148
|
textAlign: (computed.textAlign as CanvasTextAlign) || "start",
|
|
45
149
|
textBaseline: "alphabetic",
|
|
@@ -64,47 +168,71 @@ export function extractSceneGraph(
|
|
|
64
168
|
const textNode = sourceNode as Text;
|
|
65
169
|
// empthy text check
|
|
66
170
|
if (!textNode.textContent || !textNode.textContent.trim()) return null;
|
|
67
|
-
const normalizedText = textNode.textContent.replace(/\s+/g, " ")
|
|
171
|
+
const normalizedText = textNode.textContent.replace(/\s+/g, " ");
|
|
68
172
|
if (normalizedText.length === 0) return null;
|
|
69
173
|
|
|
70
|
-
const
|
|
174
|
+
const textLines = extractTextLines(textNode);
|
|
71
175
|
|
|
72
|
-
|
|
73
|
-
if (rect.width === 0 || rect.height === 0) return null;
|
|
176
|
+
if (textLines.length === 0) return null;
|
|
74
177
|
|
|
75
178
|
// Cascading Styles
|
|
76
179
|
const parent = textNode.parentElement;
|
|
77
180
|
const computed = parent ? window.getComputedStyle(parent) : null;
|
|
78
181
|
if (!computed) return null;
|
|
79
182
|
|
|
80
|
-
//
|
|
183
|
+
// Calculate overall bounding box of the lines
|
|
184
|
+
const minX = Math.min(...textLines.map(l => l.rect.left));
|
|
185
|
+
const minY = Math.min(...textLines.map(l => l.rect.top));
|
|
186
|
+
const maxX = Math.max(...textLines.map(l => l.rect.left + l.rect.width));
|
|
187
|
+
const maxY = Math.max(...textLines.map(l => l.rect.top + l.rect.height));
|
|
188
|
+
|
|
189
|
+
// Create SceneNode for the text node
|
|
81
190
|
return {
|
|
82
191
|
id: Math.random().toString(36).substring(2, 9),
|
|
83
192
|
type: "TEXT",
|
|
84
193
|
element: textNode as unknown as HTMLElement,
|
|
85
194
|
rect: {
|
|
86
|
-
x:
|
|
87
|
-
y:
|
|
88
|
-
width:
|
|
89
|
-
height:
|
|
195
|
+
x: minX + window.scrollX,
|
|
196
|
+
y: minY + window.scrollY,
|
|
197
|
+
width: maxX - minX,
|
|
198
|
+
height: maxY - minY,
|
|
90
199
|
},
|
|
91
200
|
styles: {
|
|
92
201
|
backgroundColor: "transparent",
|
|
93
|
-
|
|
202
|
+
backgroundImage: "",
|
|
203
|
+
opacity:
|
|
204
|
+
parent && parent.dataset.mirageDom === "hide"
|
|
205
|
+
? 1
|
|
206
|
+
: parseFloat(computed.opacity),
|
|
94
207
|
zIndex: 0,
|
|
95
208
|
borderRadius: "0px",
|
|
96
209
|
borderColor: "transparent",
|
|
97
210
|
borderWidth: "0px",
|
|
211
|
+
isTraveler: false,
|
|
98
212
|
},
|
|
99
213
|
textContent: normalizedText,
|
|
214
|
+
textLines: textLines.map(l => ({
|
|
215
|
+
text: l.text.trim(),
|
|
216
|
+
rect: {
|
|
217
|
+
x: l.rect.left + window.scrollX,
|
|
218
|
+
y: l.rect.top + window.scrollY,
|
|
219
|
+
width: l.rect.width,
|
|
220
|
+
height: l.rect.height,
|
|
221
|
+
}
|
|
222
|
+
})),
|
|
100
223
|
textStyles: extractTextStyles(computed),
|
|
101
224
|
dirtyMask: initialMask,
|
|
102
225
|
visibility: inheritedFlow,
|
|
103
226
|
isTraveler: false,
|
|
227
|
+
isFixed: computed.position === "fixed",
|
|
104
228
|
children: [],
|
|
105
229
|
};
|
|
106
230
|
}
|
|
107
231
|
|
|
232
|
+
if (sourceNode.nodeType !== Node.ELEMENT_NODE) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
108
236
|
const element = sourceNode as HTMLElement;
|
|
109
237
|
// [[Filter]] data attribute based filtering
|
|
110
238
|
const filterData = element.dataset.mirageFilter;
|
|
@@ -173,6 +301,14 @@ export function extractSceneGraph(
|
|
|
173
301
|
}
|
|
174
302
|
}
|
|
175
303
|
|
|
304
|
+
const shaderData = element.dataset.mirageShader;
|
|
305
|
+
let shaderHooks: ShaderHooks | undefined;
|
|
306
|
+
if (shaderData) {
|
|
307
|
+
shaderHooks = JSON.parse(shaderData) as ShaderHooks;
|
|
308
|
+
// [TODO] object 형태로 shader hooks 받기
|
|
309
|
+
// [TODO] 변수형태로 저장해서 return scene node에 넣어주기
|
|
310
|
+
}
|
|
311
|
+
|
|
176
312
|
const rect = element.getBoundingClientRect();
|
|
177
313
|
const computed = window.getComputedStyle(element);
|
|
178
314
|
|
|
@@ -181,6 +317,7 @@ export function extractSceneGraph(
|
|
|
181
317
|
return null;
|
|
182
318
|
}
|
|
183
319
|
|
|
320
|
+
// [TODO] dataset 방식으로 변경
|
|
184
321
|
let id = element.getAttribute("data-mid");
|
|
185
322
|
if (!id) {
|
|
186
323
|
id = Math.random().toString(36).substring(2, 11);
|
|
@@ -188,13 +325,29 @@ export function extractSceneGraph(
|
|
|
188
325
|
}
|
|
189
326
|
|
|
190
327
|
const zIndex = parseInt(computed.zIndex);
|
|
328
|
+
// console.log(`${element.id}: ${computed.background}`);
|
|
329
|
+
// console.log(computed.backgroundImage);
|
|
330
|
+
let imageSrc: string | undefined;
|
|
331
|
+
if (element.tagName === "IMG") {
|
|
332
|
+
imageSrc = (element as HTMLImageElement).src;
|
|
333
|
+
} else if (computed.backgroundImage && computed.backgroundImage !== "none") {
|
|
334
|
+
const match = computed.backgroundImage.match(/url\(['"]?(.*?)['"]?\)/);
|
|
335
|
+
if (match) {
|
|
336
|
+
imageSrc = match[1];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
191
340
|
const styles: BoxStyles = {
|
|
192
341
|
backgroundColor: computed.backgroundColor,
|
|
193
|
-
|
|
342
|
+
backgroundImage: computed.backgroundImage,
|
|
343
|
+
opacity:
|
|
344
|
+
element.dataset.mirageDom === "hide" ? 1 : parseFloat(computed.opacity),
|
|
194
345
|
zIndex: isNaN(zIndex) ? 0 : zIndex,
|
|
195
346
|
borderRadius: computed.borderRadius,
|
|
196
347
|
borderColor: computed.borderColor,
|
|
197
348
|
borderWidth: computed.borderWidth,
|
|
349
|
+
imageSrc,
|
|
350
|
+
isTraveler,
|
|
198
351
|
};
|
|
199
352
|
|
|
200
353
|
let textContent: string | undefined;
|
|
@@ -231,6 +384,8 @@ export function extractSceneGraph(
|
|
|
231
384
|
dirtyMask: initialMask,
|
|
232
385
|
visibility: visibleFlag,
|
|
233
386
|
isTraveler: isTraveler,
|
|
387
|
+
isFixed: computed.position === "fixed",
|
|
234
388
|
children,
|
|
389
|
+
shaderHooks,
|
|
235
390
|
};
|
|
236
391
|
}
|
package/src/env.d.ts
ADDED