@mirage-engine/core 0.0.2 → 0.1.0
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 +6 -0
- package/dist/mirage-engine.js +271 -184
- package/dist/mirage-engine.umd.js +54 -27
- package/dist/src/core/Syncer.d.ts +3 -1
- package/dist/src/dom/Extractor.d.ts +3 -2
- package/dist/src/renderer/Renderer.d.ts +0 -1
- package/dist/src/types/common.d.ts +2 -0
- package/dist/src/types/config.d.ts +8 -0
- package/dist/src/types/flags.d.ts +5 -0
- package/package.json +1 -1
- package/src/core/Engine.ts +1 -1
- package/src/core/Syncer.ts +7 -2
- package/src/dom/Extractor.ts +72 -4
- package/src/renderer/Renderer.ts +9 -11
- package/src/types/common.ts +5 -2
- package/src/types/config.ts +13 -4
- package/src/types/flags.ts +17 -1
|
@@ -1,55 +1,82 @@
|
|
|
1
|
-
(function(d,
|
|
2
|
-
`),n=[];return i.forEach(s=>{const a=s.split(" ");let o=a[0];for(let
|
|
1
|
+
(function(d,m){typeof exports=="object"&&typeof module!="undefined"?m(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],m):(d=typeof globalThis!="undefined"?globalThis:d||self,m(d.MirageEngine={},d.THREE))})(this,function(d,m){"use strict";var J=Object.defineProperty;var K=(d,m,T)=>m in d?J(d,m,{enumerable:!0,configurable:!0,writable:!0,value:T}):d[m]=T;var l=(d,m,T)=>(K(d,typeof m!="symbol"?m+"":m,T),T);function T(r){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const t in r)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(r,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>r[t]})}}return e.default=r,Object.freeze(e)}const u=T(m),R=0,S=1,D=2,I=4,B=8,F=16,M=0,A=29,L=30,X=["include-tree","exclude-tree","include-self","exclude-self","end"];function U(r,e,t){const i=e.split(`
|
|
2
|
+
`),n=[];return i.forEach(s=>{const a=s.split(" ");let o=a[0];for(let h=1;h<a.length;h++){const c=a[h];r.measureText(o+" "+c).width<t?o+=" "+c:(n.push(o),o=c)}n.push(o)}),n}function _(r,e,t,i,n=2){const s=document.createElement("canvas"),a=s.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const h=(window.devicePixelRatio||1)*n;s.width=t*h,s.height=i*h,a.scale(h,h),a.font=e.font,a.fillStyle=e.color,a.textBaseline="top",a.globalAlpha=1;const c=U(a,r,t),p=e.lineHeight;c.forEach((w,E)=>{const C=E*p+2;let x=0;e.textAlign==="center"?x=t/2:e.textAlign==="right"&&(x=t),a.textAlign=e.textAlign,a.fillText(w,x,C)});const g=new u.CanvasTexture(s);return g.colorSpace=u.SRGBColorSpace,g.minFilter=u.LinearFilter,g.magFilter=u.LinearFilter,g.needsUpdate=!0,g}function b(r){return typeof r=="number"?r:parseFloat(r)||0}function k(r,e){var o,h,c,p;if(!e){r.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),i=b(t[0]),n=b((o=t[1])!=null?o:t[0]),s=b((h=t[2])!=null?h:t[0]),a=b((p=(c=t[3])!=null?c:t[1])!=null?p:t[0]);r.set(i,n,s,a)}const W=`
|
|
3
3
|
varying vec2 vUv;
|
|
4
4
|
void main() {
|
|
5
5
|
vUv = uv;
|
|
6
6
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
7
7
|
}
|
|
8
|
-
`,
|
|
8
|
+
`,H=`
|
|
9
|
+
|
|
9
10
|
varying vec2 vUv;
|
|
10
11
|
|
|
11
12
|
uniform vec2 uSize;
|
|
12
|
-
uniform
|
|
13
|
+
uniform vec4 uRadius;
|
|
13
14
|
uniform float uBorderWidth;
|
|
14
|
-
uniform vec3
|
|
15
|
+
uniform vec3 uBgColor;
|
|
15
16
|
uniform vec3 uBorderColor;
|
|
16
17
|
uniform float uOpacity;
|
|
17
18
|
uniform float uBgOpacity;
|
|
19
|
+
uniform float uBorderOpacity;
|
|
18
20
|
|
|
19
|
-
// SDF
|
|
21
|
+
// SDF box
|
|
20
22
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
21
23
|
vec2 q = abs(p) - b + r;
|
|
24
|
+
// return length(max(q, 0.0)) - r;
|
|
22
25
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
void main() {
|
|
29
|
+
// uVu: (0.0, 0.0) ~ (1.0, 1.0) / center: (0.5, 0.5)
|
|
30
|
+
// p: (-1.0, -1.0) ~ (1.0, 1.0) / center: (0.0, 0.0)
|
|
26
31
|
vec2 p = (vUv - 0.5) * uSize;
|
|
27
32
|
vec2 halfSize = uSize * 0.5;
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
float
|
|
34
|
+
// # border-radius
|
|
35
|
+
vec2 xRadii = mix(uRadius.xw, uRadius.yz, step(0.0, p.x));
|
|
36
|
+
float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
|
|
37
|
+
float d = sdRoundedBox(p, halfSize, r);
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
float borderAlpha = 0.0;
|
|
36
|
-
|
|
37
|
-
if (uBorderWidth > 0.01) {
|
|
38
|
-
borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
|
|
39
|
-
}
|
|
39
|
+
// 1px blur for anti-aliasing
|
|
40
|
+
float aa = 1.0;
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// x == 0~aa -> 1
|
|
43
|
+
// x < 0 -> 0
|
|
44
|
+
// x > aa -> 1
|
|
45
|
+
float bgMask = 1.0 - smoothstep(0.0, aa, d);
|
|
46
|
+
|
|
47
|
+
// v valley
|
|
48
|
+
// 10px -> 5
|
|
49
|
+
float halfBorder = uBorderWidth * 0.5;
|
|
50
|
+
// 10px :
|
|
51
|
+
// 0 -> 0
|
|
52
|
+
// -10 -> 0
|
|
53
|
+
// -5 -> -5
|
|
54
|
+
float borderD = abs(d + halfBorder) - halfBorder;
|
|
55
|
+
// ^ border 내부는 음수/외부는 양수
|
|
56
|
+
|
|
57
|
+
// [!] 내부가 음수인 곳에서 시작점이 0이면 내부는 안티 얼리어싱 되지 않음.
|
|
58
|
+
// 1에서 빼든 그대로든 상관없이 경계선 밖 1px
|
|
59
|
+
|
|
60
|
+
// 최종 alpha를 위해 border 부분에 해당하는 픽셀 만 1로 반환.
|
|
61
|
+
float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
|
|
62
|
+
|
|
63
|
+
if (uBorderWidth <= 0.01) {
|
|
64
|
+
borderAlpha = 0.0;
|
|
46
65
|
}
|
|
47
|
-
|
|
48
|
-
float
|
|
49
|
-
float
|
|
50
|
-
|
|
66
|
+
|
|
67
|
+
float aFront = borderAlpha;
|
|
68
|
+
float aBack = uBgOpacity;
|
|
69
|
+
float aOut = aFront + aBack * (1.0 - aFront);
|
|
70
|
+
|
|
71
|
+
float safeAlpha = max(aOut, 0.0001);
|
|
72
|
+
vec3 cOut = (uBorderColor * aFront + uBgColor * aBack * (1.0 - aFront)) / safeAlpha;
|
|
73
|
+
|
|
74
|
+
float finalOpacity = aOut * bgMask * uOpacity;
|
|
75
|
+
|
|
51
76
|
if (finalOpacity < 0.001) discard;
|
|
52
77
|
|
|
53
|
-
gl_FragColor = vec4(
|
|
78
|
+
gl_FragColor = vec4(cOut, finalOpacity);
|
|
79
|
+
|
|
80
|
+
#include <colorspace_fragment>
|
|
54
81
|
}
|
|
55
|
-
`;function E(r){if(!r||r==="transparent")return{color:new h.Color(16777215),alpha:0};const t=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(t){const e=parseInt(t[1],10),i=parseInt(t[2],10),n=parseInt(t[3],10),s=t[4]!==void 0?parseFloat(t[4]):1;return{color:new h.Color(`rgb(${e}, ${i}, ${n})`),alpha:s}}return{color:new h.Color(r),alpha:1}}function L(r,t,e){var o;const i=E(r.backgroundColor),n=E(r.borderColor),s={uSize:{value:new h.Vector2(t,e)},uRadius:{value:S(r.borderRadius)},uBorderWidth:{value:S(r.borderWidth)},uColor:{value:i.color},uBorderColor:{value:n.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha}};return new h.ShaderMaterial({uniforms:s,vertexShader:F,fragmentShader:N,transparent:!0,side:h.FrontSide})}function A(r,t,e,i){var a;const n=E(t.backgroundColor),s=E(t.borderColor);r.uniforms.uSize.value.set(e,i),r.uniforms.uRadius.value=S(t.borderRadius),r.uniforms.uBorderWidth.value=S(t.borderWidth),r.uniforms.uColor.value.copy(n.color),r.uniforms.uBorderColor.value.copy(s.color),r.uniforms.uOpacity.value=(a=t.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=n.alpha}const D={create(r,t,e,i,n,s=2){if(r==="BOX")return L(t,i,n);if(r==="TEXT"){const a=O(e||"",t,i,n,s);return new h.MeshBasicMaterial({map:a,transparent:!0,side:h.FrontSide,color:16777215})}return new h.MeshBasicMaterial({visible:!1})},update(r,t,e,i,n,s,a=2){if(t==="BOX")A(r,e,n,s);else if(t==="TEXT"){const o=r;o.map&&o.map.dispose();const l=O(i||"",e,n,s,a);o.map=l,o.needsUpdate=!0}}};class X{constructor(t,e,i){c(this,"canvas");c(this,"scene");c(this,"camera");c(this,"renderer");c(this,"renderOrder",0);c(this,"textQualityFactor",2);c(this,"mode","overlay");c(this,"customZIndex","9999");c(this,"target");c(this,"mountContainer");c(this,"targetRect");c(this,"meshMap",new Map);var a,o,l;this.target=t,this.mountContainer=i,this.mode=(a=e.mode)!=null?a:"overlay",(o=e.style)!=null&&o.zIndex&&(this.customZIndex=e.style.zIndex),this.canvas=document.createElement("canvas"),this.scene=new h.Scene,this.targetRect=this.target.getBoundingClientRect();const n=this.targetRect.width,s=this.targetRect.height;this.camera=new h.OrthographicCamera(n/-2,n/2,s/2,s/-2,1,1e3),this.camera.position.z=100,this.renderer=new h.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(n,s),this.applyTextQuality((l=e.textQuality)!=null?l:"medium")}applyTextQuality(t){if(typeof t=="number"){this.textQualityFactor=Math.max(.1,t);return}switch(t){case"low":this.textQualityFactor=1;break;case"high":this.textQualityFactor=4;break;case"medium":default:this.textQualityFactor=2;break}}mount(){this.mountContainer.appendChild(this.canvas),this.canvas.style.zIndex=this.customZIndex,this.canvas.style.pointerEvents=this.mode==="overlay"?"none":"auto",this.updateCanvasLayout()}updateCanvasLayout(){this.canvas.style.width=`${this.targetRect.width}px`,this.canvas.style.height=`${this.targetRect.height}px`,this.mode==="duplicate"?(this.canvas.style.position="",this.canvas.style.top="",this.canvas.style.left="",this.canvas.style.display="block"):(this.canvas.style.position="absolute",this.canvas.style.top=`${this.target.offsetTop}px`,this.canvas.style.left=`${this.target.offsetLeft}px`,this.canvas.style.display="block")}dispose(){this.renderer.dispose(),this.canvas.remove()}setSize(t,e){this.renderer.setSize(t,e),this.camera.left=t/-2,this.camera.right=t/2,this.camera.top=e/2,this.camera.bottom=e/-2,this.camera.updateProjectionMatrix()}syncScene(t){const e=this.target.getBoundingClientRect(),i=Math.abs(e.width-this.targetRect.width)>.1||Math.abs(e.height-this.targetRect.height)>.1,n=this.mode==="overlay"&&(Math.abs(e.top-this.targetRect.top)>.1||Math.abs(e.left-this.targetRect.left)>.1);i?(this.targetRect=e,this.renderer.setSize(this.targetRect.width,this.targetRect.height),this.camera.left=this.targetRect.width/-2,this.camera.right=this.targetRect.width/2,this.camera.top=this.targetRect.height/2,this.camera.bottom=this.targetRect.height/-2,this.camera.updateProjectionMatrix(),this.updateCanvasLayout()):n?(this.targetRect=e,this.updateCanvasLayout()):this.targetRect=e,this.renderOrder=0;const s=new Set;this.reconcileNode(t,s);for(const[a,o]of this.meshMap.entries())s.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof h.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(t,e){e.add(t.element);let i=this.meshMap.get(t.element);if(!i){const n=new h.PlaneGeometry(1,1),s=D.create("BOX",t.styles,"",t.rect.width,t.rect.height);i=new h.Mesh(n,s),t.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(t.element,i)}if(i.userData.domRect=t.rect,this.updateMeshProperties(i,t),t.type==="BOX")for(const n of t.children)this.reconcileNode(n,e);else t.type==="TEXT"&&this.reconcileTextChild(i,t)}reconcileTextChild(t,e){var o,l;let i=t.children.find(u=>u.name==="TEXT_CHILD");const n=JSON.stringify(e.textStyles),s=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||e.dirtyMask&M||n!==s){i&&((l=i.material.map)==null||l.dispose(),i.geometry.dispose(),t.remove(i));const u=D.create("TEXT",e.textStyles,e.textContent||"",e.rect.width,e.rect.height,this.textQualityFactor),m=new h.PlaneGeometry(1,1);i=new h.Mesh(m,u),i.name="TEXT_CHILD",i.userData={styleHash:n},i.position.z=.005,t.add(i)}if(i){const u=t.userData.domRect,m=u.x+u.width/2,p=u.y+u.height/2,g=e.rect.x+e.rect.width/2,y=e.rect.y+e.rect.height/2,w=g-m,v=-(y-p);i.position.set(w,v,.005)}}updateMeshProperties(t,e){const{rect:i,styles:n}=e,s=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/s,o=this.renderer.domElement.height/s;t.scale.set(i.width,i.height,1);const l=.001;this.renderOrder++;const u=this.targetRect.left+window.scrollX,m=this.targetRect.top+window.scrollY,p=i.x-u,g=i.y-m;t.position.set(p-a/2+i.width/2,-g+o/2-i.height/2,n.zIndex+this.renderOrder*l),D.update(t.material,"BOX",e.styles,"",e.rect.width,e.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function _(r){const t=document.createRange();t.selectNodeContents(r);const e=t.getBoundingClientRect();return{left:e.left,top:e.top,width:e.width,height:e.height}}function Y(r){const t=parseFloat(r.fontSize);let e=parseFloat(r.lineHeight);isNaN(e)&&(e=t*1.2);let i=parseFloat(r.letterSpacing);return isNaN(i)&&(i=0),{font:`${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,color:r.color,textAlign:r.textAlign||"start",textBaseline:"alphabetic",direction:r.direction||"inherit",lineHeight:e,letterSpacing:i}}function z(r,t=R|b|B|M|C){if(r.nodeType===Node.TEXT_NODE){const p=r;if(!p.textContent||!p.textContent.trim())return null;const g=p.textContent.replace(/\s+/g," ").trim();if(g.length===0)return null;const y=_(p);if(y.width===0||y.height===0)return null;const w=p.parentElement,v=w?window.getComputedStyle(w):null;return v?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:p,rect:{x:y.left+window.scrollX,y:y.top+window.scrollY,width:y.width,height:y.height},styles:{backgroundColor:"transparent",opacity:parseFloat(v.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:g,textStyles:Y(v),dirtyMask:t,children:[]}:null}const e=r,i=e.getBoundingClientRect(),n=window.getComputedStyle(e);if(i.width===0||i.height===0||n.display==="none")return null;let s=e.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),e.setAttribute("data-mid",s));const a=parseInt(n.zIndex),o={backgroundColor:n.backgroundColor,opacity:parseFloat(n.opacity),zIndex:isNaN(a)?0:a,borderRadius:n.borderRadius,borderColor:n.borderColor,borderWidth:n.borderWidth};let l,u;const m=[];return Array.from(e.childNodes).forEach(p=>{const g=z(p,t);g&&m.push(g)}),{id:s,type:"BOX",element:e,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:o,textContent:l,textStyles:u,dirtyMask:t,children:m}}class k{constructor(t,e){c(this,"target");c(this,"renderer");c(this,"observer");c(this,"isDomDirty",!1);c(this,"isRunning",!1);c(this,"pendingMask",x);c(this,"mutationTimer",null);c(this,"cssTimer",null);c(this,"onTransitionFinished",t=>{this.target.contains(t.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=R|b,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});c(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});c(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=t,this.renderer=e,this.observer=new MutationObserver(i=>{let n=x;for(const s of i)s.type==="childList"?n|=C:s.type==="attributes"&&(s.attributeName==="style"||s.attributeName==="class")&&(n|=R|b);if(n!==x){if(this.pendingMask|=n,n&C){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const t=z(this.target,this.pendingMask);t&&this.renderer.syncScene(t),this.pendingMask=x}}class P{constructor(t,e){c(this,"renderer");c(this,"syncer");c(this,"target");var n,s,a;this.target=t;let i;if(e.mode==="duplicate"?i=(s=(n=e.container)!=null?n:this.target.parentElement)!=null?s:void 0:i=(a=this.target.parentElement)!=null?a:void 0,!i)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new X(this.target,e,i),this.renderer.mount(),this.syncer=new k(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}d.DIRTY_CONTENT=M,d.DIRTY_NONE=x,d.DIRTY_RECT=R,d.DIRTY_STRUCTURE=C,d.DIRTY_STYLE=b,d.DIRTY_ZINDEX=B,d.Engine=P,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
|
|
82
|
+
`;function O(r){if(!r||r==="transparent")return{color:new u.Color(16777215),alpha:0};const e=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(e){const t=parseInt(e[1],10),i=parseInt(e[2],10),n=parseInt(e[3],10),s=e[4]!==void 0?parseFloat(e[4]):1;return{color:new u.Color(`rgb(${t}, ${i}, ${n})`),alpha:s}}return{color:new u.Color(r),alpha:1}}function $(r,e,t){var o;const i=O(r.backgroundColor),n=O(r.borderColor),s={uSize:{value:new u.Vector2(e,t)},uRadius:{value:new u.Vector4(0,0,0,0)},uBorderWidth:{value:b(r.borderWidth)},uBgColor:{value:i.color},uBorderColor:{value:n.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha},uBorderOpacity:{value:n.alpha}};return k(s.uRadius.value,r.borderRadius),new u.ShaderMaterial({uniforms:s,vertexShader:W,fragmentShader:H,transparent:!0,side:u.FrontSide})}function j(r,e,t,i){var a;const n=O(e.backgroundColor),s=O(e.borderColor);r.uniforms.uSize.value.set(t,i),k(r.uniforms.uRadius.value,e.borderRadius),r.uniforms.uBorderWidth.value=b(e.borderWidth),r.uniforms.uBgColor.value.copy(n.color),r.uniforms.uBorderColor.value.copy(s.color),r.uniforms.uOpacity.value=(a=e.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=n.alpha,r.uniforms.uBorderOpacity.value=s.alpha}const N={create(r,e,t,i,n,s=2){if(r==="BOX")return $(e,i,n);if(r==="TEXT"){const a=_(t||"",e,i,n,s);return new u.MeshBasicMaterial({map:a,transparent:!0,side:u.FrontSide,color:16777215})}return new u.MeshBasicMaterial({visible:!1})},update(r,e,t,i,n,s,a=2){if(e==="BOX")j(r,t,n,s);else if(e==="TEXT"){const o=r;o.map&&o.map.dispose();const h=_(i||"",t,n,s,a);o.map=h,o.needsUpdate=!0}}};class Q{constructor(e,t,i){l(this,"canvas");l(this,"scene");l(this,"camera");l(this,"renderer");l(this,"renderOrder",0);l(this,"textQualityFactor",2);l(this,"mode","overlay");l(this,"target");l(this,"mountContainer");l(this,"targetRect");l(this,"meshMap",new Map);var a,o;this.target=e,this.mountContainer=i,this.mode=(a=t.mode)!=null?a:"overlay",this.canvas=document.createElement("canvas"),this.scene=new u.Scene,this.targetRect=this.target.getBoundingClientRect();const n=this.targetRect.width,s=this.targetRect.height;this.camera=new u.OrthographicCamera(n/-2,n/2,s/2,s/-2,1,1e3),this.camera.position.z=100,this.renderer=new u.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(n,s),this.applyTextQuality((o=t.textQuality)!=null?o:"medium")}applyTextQuality(e){if(typeof e=="number"){this.textQualityFactor=Math.max(.1,e);return}switch(e){case"low":this.textQualityFactor=1;break;case"high":this.textQualityFactor=4;break;case"medium":default:this.textQualityFactor=2;break}}mount(){this.mountContainer.prepend(this.canvas),this.canvas.style.pointerEvents=this.mode==="overlay"?"none":"auto",this.updateCanvasLayout()}updateCanvasLayout(){this.canvas.style.width=`${this.targetRect.width}px`,this.canvas.style.height=`${this.targetRect.height}px`,this.mode==="duplicate"?(this.canvas.style.position="",this.canvas.style.top="",this.canvas.style.left="",this.canvas.style.display="block"):(this.canvas.style.position="absolute",this.canvas.style.top=`${this.target.offsetTop}px`,this.canvas.style.left=`${this.target.offsetLeft}px`,this.canvas.style.display="block")}dispose(){this.renderer.dispose(),this.canvas.remove()}setSize(e,t){this.renderer.setSize(e,t),this.camera.left=e/-2,this.camera.right=e/2,this.camera.top=t/2,this.camera.bottom=t/-2,this.camera.updateProjectionMatrix()}syncScene(e){const t=this.target.getBoundingClientRect(),i=Math.abs(t.width-this.targetRect.width)>.1||Math.abs(t.height-this.targetRect.height)>.1,n=this.mode==="overlay"&&(Math.abs(t.top-this.targetRect.top)>.1||Math.abs(t.left-this.targetRect.left)>.1);i?(this.targetRect=t,this.renderer.setSize(this.targetRect.width,this.targetRect.height),this.camera.left=this.targetRect.width/-2,this.camera.right=this.targetRect.width/2,this.camera.top=this.targetRect.height/2,this.camera.bottom=this.targetRect.height/-2,this.camera.layers.set(0),this.camera.updateProjectionMatrix(),this.updateCanvasLayout()):n?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const s=new Set;this.reconcileNode(e,s);for(const[a,o]of this.meshMap.entries())s.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof u.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(e,t){t.add(e.element);let i=this.meshMap.get(e.element);if(!i){const n=new u.PlaneGeometry(1,1),s=N.create("BOX",e.styles,"",e.rect.width,e.rect.height);i=new u.Mesh(n,s),i.layers.set(e.visibility),e.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(e.element,i)}if(i.userData.domRect=e.rect,this.updateMeshProperties(i,e),e.type==="BOX")for(const n of e.children)this.reconcileNode(n,t);else e.type==="TEXT"&&this.reconcileTextChild(i,e)}reconcileTextChild(e,t){var o,h;let i=e.children.find(c=>c.name==="TEXT_CHILD");const n=JSON.stringify(t.textStyles),s=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||t.dirtyMask&F||n!==s){i&&((h=i.material.map)==null||h.dispose(),i.geometry.dispose(),e.remove(i));const c=N.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.textQualityFactor),p=new u.PlaneGeometry(1,1);i=new u.Mesh(p,c),i.name="TEXT_CHILD",i.userData={styleHash:n},i.layers.set(t.visibility),i.position.z=.005,e.add(i)}if(i){const c=e.userData.domRect,p=c.x+c.width/2,g=c.y+c.height/2,w=t.rect.x+t.rect.width/2,E=t.rect.y+t.rect.height/2,C=w-p,x=-(E-g);i.position.set(C,x,.005)}}updateMeshProperties(e,t){const{rect:i,styles:n}=t,s=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/s,o=this.renderer.domElement.height/s;e.scale.set(i.width,i.height,1);const h=.001;this.renderOrder++;const c=this.targetRect.left+window.scrollX,p=this.targetRect.top+window.scrollY,g=i.x-c,w=i.y-p;e.position.set(g-a/2+i.width/2,-w+o/2-i.height/2,n.zIndex+this.renderOrder*h),N.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function G(r){const e=document.createRange();e.selectNodeContents(r);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function q(r){const e=parseFloat(r.fontSize);let t=parseFloat(r.lineHeight);isNaN(t)&&(t=e*1.2);let i=parseFloat(r.letterSpacing);return isNaN(i)&&(i=0),{font:`${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,color:r.color,textAlign:r.textAlign||"start",textBaseline:"alphabetic",direction:r.direction||"inherit",lineHeight:t,letterSpacing:i}}function Y(r,e=S|D|I|F|B,t,i){if(r.nodeType===Node.TEXT_NODE){const f=r;if(!f.textContent||!f.textContent.trim())return null;const y=f.textContent.replace(/\s+/g," ").trim();if(y.length===0)return null;const v=G(f);if(v.width===0||v.height===0)return null;const P=f.parentElement,z=P?window.getComputedStyle(P):null;return z?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:f,rect:{x:v.left+window.scrollX,y:v.top+window.scrollY,width:v.width,height:v.height},styles:{backgroundColor:"transparent",opacity:parseFloat(z.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:y,textStyles:q(z),dirtyMask:e,visibility:t,children:[]}:null}const n=r,s=n.dataset.mirageFilter;let a=t,o=t;if(s){const f=new Set(s.split(/\s+/));for(const y of f)if(!X.includes(y))throw new Error(`[MirageEngine] Invalid filter token: '${y}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`);if(f.has("end"))return null;if(f.has("include-tree")&&f.has("exclude-tree"))throw new Error("[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.");if(f.has("include-self")&&f.has("exclude-self"))throw new Error("[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.");f.has("include-tree")?a=M:f.has("exclude-tree")&&(a=L),o=a,f.has("include-self")?o=M:f.has("exclude-self")&&(o=L)}if(i&&i.end&&i.end.length>0&&i.end.some(y=>n.classList.contains(y)))return null;const h=n.getBoundingClientRect(),c=window.getComputedStyle(n);if(h.width===0||h.height===0||c.display==="none")return null;let p=n.getAttribute("data-mid");p||(p=Math.random().toString(36).substring(2,11),n.setAttribute("data-mid",p));const g=parseInt(c.zIndex),w={backgroundColor:c.backgroundColor,opacity:parseFloat(c.opacity),zIndex:isNaN(g)?0:g,borderRadius:c.borderRadius,borderColor:c.borderColor,borderWidth:c.borderWidth};let E,C;const x=[];return Array.from(n.childNodes).forEach(f=>{const y=f.nodeType===Node.TEXT_NODE?o:a,v=Y(f,e,y,i);v&&x.push(v)}),{id:p,type:"BOX",element:n,rect:{x:h.left+window.scrollX,y:h.top+window.scrollY,width:h.width,height:h.height},styles:w,textContent:E,textStyles:C,dirtyMask:e,visibility:o,children:x}}class V{constructor(e,t,i){l(this,"target");l(this,"renderer");l(this,"filter");l(this,"observer");l(this,"isDomDirty",!1);l(this,"isRunning",!1);l(this,"pendingMask",R);l(this,"mutationTimer",null);l(this,"cssTimer",null);l(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=S|D,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});l(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});l(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.filter=i,this.observer=new MutationObserver(n=>{let s=R;for(const a of n)a.type==="childList"?s|=B:a.type==="attributes"&&(a.attributeName==="style"||a.attributeName==="class")&&(s|=S|D);if(s!==R){if(this.pendingMask|=s,s&B){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const e=Y(this.target,this.pendingMask,M,this.filter);e&&this.renderer.syncScene(e),this.pendingMask=R}}class Z{constructor(e,t){l(this,"renderer");l(this,"syncer");l(this,"target");var n,s,a;this.target=e;let i;if(t.mode==="duplicate"?i=(s=(n=t.container)!=null?n:this.target.parentElement)!=null?s:void 0:i=(a=this.target.parentElement)!=null?a:void 0,!i)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new Q(this.target,t,i),this.renderer.mount(),this.syncer=new V(this.target,this.renderer,t.filter)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}d.ALLOWED_FILTERS=X,d.DIRTY_CONTENT=F,d.DIRTY_NONE=R,d.DIRTY_RECT=S,d.DIRTY_STRUCTURE=B,d.DIRTY_STYLE=D,d.DIRTY_ZINDEX=I,d.EXCLUDED=L,d.Engine=Z,d.INCLUDED=M,d.SYSTEM=A,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import { FilterConfig } from '../types/config';
|
|
1
2
|
import { Renderer } from '../renderer/Renderer';
|
|
2
3
|
|
|
3
4
|
export declare class Syncer {
|
|
4
5
|
private target;
|
|
5
6
|
private renderer;
|
|
7
|
+
private filter?;
|
|
6
8
|
private observer;
|
|
7
9
|
private isDomDirty;
|
|
8
10
|
private isRunning;
|
|
9
11
|
private pendingMask;
|
|
10
12
|
private mutationTimer;
|
|
11
13
|
private cssTimer;
|
|
12
|
-
constructor(target: HTMLElement, renderer: Renderer);
|
|
14
|
+
constructor(target: HTMLElement, renderer: Renderer, filter?: FilterConfig);
|
|
13
15
|
start(): void;
|
|
14
16
|
stop(): void;
|
|
15
17
|
private clearTimers;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { SceneNode } from '../types';
|
|
1
|
+
import { SceneNode, Visibility } from '../types';
|
|
2
|
+
import { FilterConfig } from '../types/config';
|
|
2
3
|
|
|
3
|
-
export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask?:
|
|
4
|
+
export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask: number | undefined, inheritedFlow: Visibility, filterConfig?: FilterConfig): SceneNode | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TextStyles, BoxStyles } from '../../packages/painter/src/index.ts';
|
|
2
|
+
import { Visibility } from './flags';
|
|
2
3
|
|
|
3
4
|
export type NodeType = "BOX" | "TEXT";
|
|
4
5
|
export interface NodeRect {
|
|
@@ -16,5 +17,6 @@ export interface SceneNode {
|
|
|
16
17
|
textContent?: string;
|
|
17
18
|
textStyles?: TextStyles;
|
|
18
19
|
dirtyMask: number;
|
|
20
|
+
visibility: Visibility;
|
|
19
21
|
children: SceneNode[];
|
|
20
22
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
export type TextQuality = "low" | "medium" | "high" | number;
|
|
2
2
|
export type MirageMode = "overlay" | "duplicate";
|
|
3
|
+
export interface FilterConfig {
|
|
4
|
+
includeTree?: string[];
|
|
5
|
+
excludeTree?: string[];
|
|
6
|
+
includeSelf?: string[];
|
|
7
|
+
excludeSelf?: string[];
|
|
8
|
+
end?: string[];
|
|
9
|
+
}
|
|
3
10
|
interface BaseConfig {
|
|
4
11
|
debug?: boolean;
|
|
5
12
|
textQuality?: TextQuality;
|
|
6
13
|
style?: {
|
|
7
14
|
zIndex?: string;
|
|
8
15
|
};
|
|
16
|
+
filter?: FilterConfig;
|
|
9
17
|
}
|
|
10
18
|
export interface OverlayConfig extends BaseConfig {
|
|
11
19
|
mode?: "overlay";
|
|
@@ -4,3 +4,8 @@ export declare const DIRTY_STYLE: number;
|
|
|
4
4
|
export declare const DIRTY_ZINDEX: number;
|
|
5
5
|
export declare const DIRTY_STRUCTURE: number;
|
|
6
6
|
export declare const DIRTY_CONTENT: number;
|
|
7
|
+
export declare const INCLUDED = 0;
|
|
8
|
+
export declare const SYSTEM = 29;
|
|
9
|
+
export declare const EXCLUDED = 30;
|
|
10
|
+
export type Visibility = typeof EXCLUDED | typeof SYSTEM | typeof INCLUDED;
|
|
11
|
+
export declare const ALLOWED_FILTERS: string[];
|
package/package.json
CHANGED
package/src/core/Engine.ts
CHANGED
package/src/core/Syncer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FilterConfig } from "../types/config";
|
|
1
2
|
import { Renderer } from "../renderer/Renderer";
|
|
2
3
|
import { extractSceneGraph } from "../dom/Extractor";
|
|
3
4
|
import {
|
|
@@ -5,11 +6,14 @@ import {
|
|
|
5
6
|
DIRTY_RECT,
|
|
6
7
|
DIRTY_STRUCTURE,
|
|
7
8
|
DIRTY_STYLE,
|
|
9
|
+
INCLUDED
|
|
8
10
|
} from "../types";
|
|
9
11
|
|
|
10
12
|
export class Syncer {
|
|
11
13
|
private target: HTMLElement;
|
|
12
14
|
private renderer: Renderer;
|
|
15
|
+
private filter?: FilterConfig;
|
|
16
|
+
|
|
13
17
|
private observer: MutationObserver;
|
|
14
18
|
|
|
15
19
|
private isDomDirty: boolean = false;
|
|
@@ -20,9 +24,10 @@ export class Syncer {
|
|
|
20
24
|
private mutationTimer: number | null = null;
|
|
21
25
|
private cssTimer: number | null = null;
|
|
22
26
|
|
|
23
|
-
constructor(target: HTMLElement, renderer: Renderer) {
|
|
27
|
+
constructor(target: HTMLElement, renderer: Renderer, filter?: FilterConfig) {
|
|
24
28
|
this.target = target;
|
|
25
29
|
this.renderer = renderer;
|
|
30
|
+
this.filter = filter;
|
|
26
31
|
|
|
27
32
|
this.observer = new MutationObserver((mutations) => {
|
|
28
33
|
let currentMask = DIRTY_NONE;
|
|
@@ -126,7 +131,7 @@ export class Syncer {
|
|
|
126
131
|
|
|
127
132
|
private forceUpdateScene() {
|
|
128
133
|
this.isDomDirty = false;
|
|
129
|
-
const sceneGraph = extractSceneGraph(this.target, this.pendingMask);
|
|
134
|
+
const sceneGraph = extractSceneGraph(this.target, this.pendingMask, INCLUDED, this.filter);
|
|
130
135
|
|
|
131
136
|
if (sceneGraph) {
|
|
132
137
|
this.renderer.syncScene(sceneGraph);
|
package/src/dom/Extractor.ts
CHANGED
|
@@ -5,9 +5,14 @@ import {
|
|
|
5
5
|
DIRTY_ZINDEX,
|
|
6
6
|
DIRTY_STRUCTURE,
|
|
7
7
|
SceneNode,
|
|
8
|
+
Visibility,
|
|
9
|
+
EXCLUDED,
|
|
10
|
+
INCLUDED,
|
|
11
|
+
ALLOWED_FILTERS,
|
|
8
12
|
} from "../types";
|
|
9
13
|
|
|
10
14
|
import { BoxStyles, TextStyles } from "@mirage-engine/painter";
|
|
15
|
+
import { FilterConfig } from "../types/config";
|
|
11
16
|
|
|
12
17
|
// Helper function: getTextNodeRect, isValidTextNode, isLeafTextElement, extractTextStyles
|
|
13
18
|
|
|
@@ -50,12 +55,13 @@ export function extractSceneGraph(
|
|
|
50
55
|
DIRTY_STYLE |
|
|
51
56
|
DIRTY_ZINDEX |
|
|
52
57
|
DIRTY_CONTENT |
|
|
53
|
-
DIRTY_STRUCTURE
|
|
58
|
+
DIRTY_STRUCTURE,
|
|
59
|
+
inheritedFlow: Visibility,
|
|
60
|
+
filterConfig?: FilterConfig,
|
|
54
61
|
): SceneNode | null {
|
|
55
62
|
// Check text node
|
|
56
63
|
if (sourceNode.nodeType === Node.TEXT_NODE) {
|
|
57
64
|
const textNode = sourceNode as Text;
|
|
58
|
-
|
|
59
65
|
// empthy text check
|
|
60
66
|
if (!textNode.textContent || !textNode.textContent.trim()) return null;
|
|
61
67
|
const normalizedText = textNode.textContent.replace(/\s+/g, " ").trim();
|
|
@@ -93,11 +99,66 @@ export function extractSceneGraph(
|
|
|
93
99
|
textContent: normalizedText,
|
|
94
100
|
textStyles: extractTextStyles(computed),
|
|
95
101
|
dirtyMask: initialMask,
|
|
102
|
+
visibility: inheritedFlow,
|
|
96
103
|
children: [],
|
|
97
104
|
};
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
const element = sourceNode as HTMLElement;
|
|
108
|
+
// [[Filter]] data attribute based filtering
|
|
109
|
+
const filterData = element.dataset.mirageFilter;
|
|
110
|
+
let visibleFlow = inheritedFlow;
|
|
111
|
+
let visibleFlag = inheritedFlow;
|
|
112
|
+
if (filterData) {
|
|
113
|
+
const filterSet = new Set(filterData.split(/\s+/));
|
|
114
|
+
// error check
|
|
115
|
+
for (const token of filterSet) {
|
|
116
|
+
if (!ALLOWED_FILTERS.includes(token)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[MirageEngine] Invalid filter token: '${token}'. ` +
|
|
119
|
+
`Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (filterSet.has("end")) return null;
|
|
125
|
+
|
|
126
|
+
// error check
|
|
127
|
+
if (filterSet.has("include-tree") && filterSet.has("exclude-tree")) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (filterSet.has("include-self") && filterSet.has("exclude-self")) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (filterSet.has("include-tree")) {
|
|
139
|
+
visibleFlow = INCLUDED;
|
|
140
|
+
} else if (filterSet.has("exclude-tree")) {
|
|
141
|
+
visibleFlow = EXCLUDED;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
visibleFlag = visibleFlow;
|
|
145
|
+
|
|
146
|
+
if (filterSet.has("include-self")) {
|
|
147
|
+
visibleFlag = INCLUDED;
|
|
148
|
+
} else if (filterSet.has("exclude-self")) {
|
|
149
|
+
visibleFlag = EXCLUDED;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// [[filter]] class based filtering
|
|
154
|
+
// [Filter] end
|
|
155
|
+
if (filterConfig && filterConfig.end && filterConfig.end.length > 0) {
|
|
156
|
+
const isEnd = filterConfig.end.some((cls) =>
|
|
157
|
+
element.classList.contains(cls),
|
|
158
|
+
);
|
|
159
|
+
if (isEnd) return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
101
162
|
const rect = element.getBoundingClientRect();
|
|
102
163
|
const computed = window.getComputedStyle(element);
|
|
103
164
|
|
|
@@ -127,8 +188,14 @@ export function extractSceneGraph(
|
|
|
127
188
|
const children: SceneNode[] = [];
|
|
128
189
|
|
|
129
190
|
Array.from(element.childNodes).forEach((child) => {
|
|
130
|
-
|
|
131
|
-
|
|
191
|
+
const visibleFlowToPass =
|
|
192
|
+
child.nodeType === Node.TEXT_NODE ? visibleFlag : visibleFlow;
|
|
193
|
+
const childNode = extractSceneGraph(
|
|
194
|
+
child,
|
|
195
|
+
initialMask,
|
|
196
|
+
visibleFlowToPass,
|
|
197
|
+
filterConfig,
|
|
198
|
+
);
|
|
132
199
|
if (childNode) {
|
|
133
200
|
children.push(childNode);
|
|
134
201
|
}
|
|
@@ -148,6 +215,7 @@ export function extractSceneGraph(
|
|
|
148
215
|
textContent,
|
|
149
216
|
textStyles,
|
|
150
217
|
dirtyMask: initialMask,
|
|
218
|
+
visibility: visibleFlag,
|
|
151
219
|
children,
|
|
152
220
|
};
|
|
153
221
|
}
|
package/src/renderer/Renderer.ts
CHANGED
|
@@ -16,7 +16,6 @@ export class Renderer {
|
|
|
16
16
|
private renderOrder: number = 0;
|
|
17
17
|
private textQualityFactor: number = 2;
|
|
18
18
|
private mode: MirageMode = "overlay";
|
|
19
|
-
private customZIndex: string = "9999";
|
|
20
19
|
|
|
21
20
|
private target: HTMLElement;
|
|
22
21
|
private mountContainer: HTMLElement;
|
|
@@ -34,10 +33,6 @@ export class Renderer {
|
|
|
34
33
|
|
|
35
34
|
this.mode = config.mode ?? "overlay";
|
|
36
35
|
|
|
37
|
-
if (config.style?.zIndex) {
|
|
38
|
-
this.customZIndex = config.style.zIndex;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
36
|
this.canvas = document.createElement("canvas");
|
|
42
37
|
this.scene = new THREE.Scene();
|
|
43
38
|
|
|
@@ -59,10 +54,15 @@ export class Renderer {
|
|
|
59
54
|
);
|
|
60
55
|
this.camera.position.z = 100;
|
|
61
56
|
|
|
57
|
+
// [new]
|
|
58
|
+
// THREE.ColorManagement.enabled = false;
|
|
59
|
+
|
|
62
60
|
this.renderer = new THREE.WebGLRenderer({
|
|
63
61
|
canvas: this.canvas,
|
|
64
62
|
alpha: true,
|
|
65
63
|
antialias: true,
|
|
64
|
+
// [new]
|
|
65
|
+
// premultipliedAlpha: true
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
@@ -91,9 +91,8 @@ export class Renderer {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
public mount() {
|
|
94
|
-
this.mountContainer.
|
|
94
|
+
this.mountContainer.prepend(this.canvas);
|
|
95
95
|
|
|
96
|
-
this.canvas.style.zIndex = this.customZIndex;
|
|
97
96
|
this.canvas.style.pointerEvents = this.mode === "overlay" ? "none" : "auto";
|
|
98
97
|
|
|
99
98
|
this.updateCanvasLayout();
|
|
@@ -107,7 +106,6 @@ export class Renderer {
|
|
|
107
106
|
this.canvas.style.position = "";
|
|
108
107
|
this.canvas.style.top = "";
|
|
109
108
|
this.canvas.style.left = "";
|
|
110
|
-
|
|
111
109
|
this.canvas.style.display = "block";
|
|
112
110
|
} else {
|
|
113
111
|
this.canvas.style.position = "absolute";
|
|
@@ -154,6 +152,7 @@ export class Renderer {
|
|
|
154
152
|
this.camera.right = this.targetRect.width / 2;
|
|
155
153
|
this.camera.top = this.targetRect.height / 2;
|
|
156
154
|
this.camera.bottom = this.targetRect.height / -2;
|
|
155
|
+
this.camera.layers.set(0);
|
|
157
156
|
this.camera.updateProjectionMatrix();
|
|
158
157
|
|
|
159
158
|
this.updateCanvasLayout();
|
|
@@ -174,7 +173,6 @@ export class Renderer {
|
|
|
174
173
|
if (!activeElements.has(el)) {
|
|
175
174
|
this.scene.remove(mesh);
|
|
176
175
|
|
|
177
|
-
// Garbage Collection
|
|
178
176
|
mesh.geometry.dispose();
|
|
179
177
|
if (mesh.material instanceof THREE.Material) mesh.material.dispose();
|
|
180
178
|
this.meshMap.delete(el);
|
|
@@ -197,8 +195,8 @@ export class Renderer {
|
|
|
197
195
|
);
|
|
198
196
|
|
|
199
197
|
mesh = new THREE.Mesh(geometry, material);
|
|
198
|
+
mesh.layers.set(node.visibility);
|
|
200
199
|
if (node.type === "TEXT") mesh.name = "BG_MESH";
|
|
201
|
-
|
|
202
200
|
this.scene.add(mesh);
|
|
203
201
|
this.meshMap.set(node.element, mesh);
|
|
204
202
|
}
|
|
@@ -249,7 +247,7 @@ export class Renderer {
|
|
|
249
247
|
|
|
250
248
|
textMesh.name = "TEXT_CHILD";
|
|
251
249
|
textMesh.userData = { styleHash: currentStyleHash };
|
|
252
|
-
|
|
250
|
+
textMesh.layers.set(node.visibility);
|
|
253
251
|
textMesh.position.z = 0.005;
|
|
254
252
|
parentMesh.add(textMesh);
|
|
255
253
|
}
|
package/src/types/common.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TextStyles, BoxStyles } from "@mirage-engine/painter";
|
|
2
|
+
import { Visibility } from "./flags";
|
|
2
3
|
|
|
3
4
|
export type NodeType = "BOX" | "TEXT"
|
|
4
5
|
|
|
@@ -9,8 +10,6 @@ export interface NodeRect {
|
|
|
9
10
|
height: number;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
13
|
export interface SceneNode {
|
|
15
14
|
id: string;
|
|
16
15
|
type: NodeType;
|
|
@@ -23,6 +22,10 @@ export interface SceneNode {
|
|
|
23
22
|
textStyles?: TextStyles;
|
|
24
23
|
|
|
25
24
|
dirtyMask: number;
|
|
25
|
+
// TODO: SceneNode의 sort 로직 추가(아마도)
|
|
26
|
+
|
|
27
|
+
visibility: Visibility;
|
|
28
|
+
|
|
26
29
|
children: SceneNode[];
|
|
27
30
|
}
|
|
28
31
|
|
package/src/types/config.ts
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
export type TextQuality = "low" | "medium" | "high" | number;
|
|
2
2
|
export type MirageMode = "overlay" | "duplicate";
|
|
3
3
|
|
|
4
|
+
export interface FilterConfig {
|
|
5
|
+
includeTree?: string[];
|
|
6
|
+
excludeTree?: string[];
|
|
7
|
+
includeSelf?: string[];
|
|
8
|
+
excludeSelf?: string[];
|
|
9
|
+
end?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
interface BaseConfig {
|
|
5
13
|
debug?: boolean;
|
|
6
14
|
textQuality?: TextQuality;
|
|
7
15
|
style?: {
|
|
8
16
|
zIndex?: string;
|
|
9
17
|
};
|
|
18
|
+
filter?: FilterConfig;
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
export interface OverlayConfig extends BaseConfig {
|
|
13
|
-
mode?: "overlay";
|
|
22
|
+
mode?: "overlay";
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
export interface DuplicateConfig extends BaseConfig {
|
|
17
|
-
mode: "duplicate";
|
|
18
|
-
container?: HTMLElement;
|
|
26
|
+
mode: "duplicate";
|
|
27
|
+
container?: HTMLElement;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
export type CoreConfig = OverlayConfig | DuplicateConfig;
|
|
30
|
+
export type CoreConfig = OverlayConfig | DuplicateConfig;
|
package/src/types/flags.ts
CHANGED
|
@@ -4,4 +4,20 @@ export const DIRTY_RECT = 1 << 0; // 1: x, y, width, height (00000001)
|
|
|
4
4
|
export const DIRTY_STYLE = 1 << 1; // 2: style (00000010)
|
|
5
5
|
export const DIRTY_ZINDEX = 1 << 2; // 4: zIndex (00000100)
|
|
6
6
|
export const DIRTY_STRUCTURE = 1 << 3; // 8: childNode add / delete (00001000)
|
|
7
|
-
export const DIRTY_CONTENT = 1 << 4;
|
|
7
|
+
export const DIRTY_CONTENT = 1 << 4; // 16: text content changed (00010000)
|
|
8
|
+
|
|
9
|
+
// Filtering Flag
|
|
10
|
+
export const INCLUDED = 0;
|
|
11
|
+
export const SYSTEM = 29;
|
|
12
|
+
export const EXCLUDED = 30;
|
|
13
|
+
|
|
14
|
+
// 다른 파일에서 파라미터 타입 지정용으로 사용
|
|
15
|
+
export type Visibility = typeof EXCLUDED | typeof SYSTEM | typeof INCLUDED;
|
|
16
|
+
|
|
17
|
+
export const ALLOWED_FILTERS = [
|
|
18
|
+
"include-tree",
|
|
19
|
+
"exclude-tree",
|
|
20
|
+
"include-self",
|
|
21
|
+
"exclude-self",
|
|
22
|
+
"end",
|
|
23
|
+
];
|