@sliiu/present-renderer 0.0.0-stage → 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.
Files changed (126) hide show
  1. package/COPYRIGHT +5 -0
  2. package/LICENSE +661 -0
  3. package/THIRD-PARTY-NOTICES.md +221 -0
  4. package/dist/cjs/arrow-geom.js +38 -0
  5. package/dist/cjs/canvas-backend.js +1368 -0
  6. package/dist/cjs/cell-borders.js +25 -0
  7. package/dist/cjs/chart-canvas.js +1059 -0
  8. package/dist/cjs/chart-ex-render.js +275 -0
  9. package/dist/cjs/chart-geom.js +313 -0
  10. package/dist/cjs/chart-svg.js +1110 -0
  11. package/dist/cjs/compose.js +74 -0
  12. package/dist/cjs/effect-clip.js +652 -0
  13. package/dist/cjs/entrance-hide.js +63 -0
  14. package/dist/cjs/font-stack.js +103 -0
  15. package/dist/cjs/hit-geom.js +50 -0
  16. package/dist/cjs/hybrid-backend.js +93 -0
  17. package/dist/cjs/hybrid-segments.js +54 -0
  18. package/dist/cjs/index.js +70 -0
  19. package/dist/cjs/linked-src.js +116 -0
  20. package/dist/cjs/metafile-preview.js +142 -0
  21. package/dist/cjs/model3d-frame.js +199 -0
  22. package/dist/cjs/model3d-render.js +78 -0
  23. package/dist/cjs/morph-transition.js +57 -0
  24. package/dist/cjs/node-affine.js +79 -0
  25. package/dist/cjs/ole-render.js +291 -0
  26. package/dist/cjs/package.json +1 -0
  27. package/dist/cjs/paint-canvas.js +701 -0
  28. package/dist/cjs/paint-svg.js +674 -0
  29. package/dist/cjs/scene-node.js +762 -0
  30. package/dist/cjs/selection.js +130 -0
  31. package/dist/cjs/shape-effects.js +66 -0
  32. package/dist/cjs/shape-front.js +38 -0
  33. package/dist/cjs/stage-chrome.js +15 -0
  34. package/dist/cjs/svg-backend.js +2172 -0
  35. package/dist/cjs/text-autofit.js +48 -0
  36. package/dist/cjs/text-hit.js +284 -0
  37. package/dist/cjs/text-layout.js +408 -0
  38. package/dist/cjs/text-shared.js +238 -0
  39. package/dist/cjs/text-stacked.js +82 -0
  40. package/dist/cjs/text-warp.js +377 -0
  41. package/dist/cjs/text-wordart.js +33 -0
  42. package/dist/cjs/video-frame.js +113 -0
  43. package/dist/cjs/webgl-model3d-backend.js +285 -0
  44. package/dist/cjs/webgl-shape-backend.js +360 -0
  45. package/dist/esm/arrow-geom.js +33 -0
  46. package/dist/esm/canvas-backend.js +1363 -0
  47. package/dist/esm/cell-borders.js +21 -0
  48. package/dist/esm/chart-canvas.js +1056 -0
  49. package/dist/esm/chart-ex-render.js +261 -0
  50. package/dist/esm/chart-geom.js +296 -0
  51. package/dist/esm/chart-svg.js +1107 -0
  52. package/dist/esm/compose.js +71 -0
  53. package/dist/esm/effect-clip.js +648 -0
  54. package/dist/esm/entrance-hide.js +59 -0
  55. package/dist/esm/font-stack.js +100 -0
  56. package/dist/esm/hit-geom.js +45 -0
  57. package/dist/esm/hybrid-backend.js +89 -0
  58. package/dist/esm/hybrid-segments.js +51 -0
  59. package/dist/esm/index.js +24 -0
  60. package/dist/esm/linked-src.js +109 -0
  61. package/dist/esm/metafile-preview.js +135 -0
  62. package/dist/esm/model3d-frame.js +158 -0
  63. package/dist/esm/model3d-render.js +73 -0
  64. package/dist/esm/morph-transition.js +54 -0
  65. package/dist/esm/node-affine.js +71 -0
  66. package/dist/esm/ole-render.js +286 -0
  67. package/dist/esm/paint-canvas.js +693 -0
  68. package/dist/esm/paint-svg.js +670 -0
  69. package/dist/esm/scene-node.js +750 -0
  70. package/dist/esm/selection.js +122 -0
  71. package/dist/esm/shape-effects.js +60 -0
  72. package/dist/esm/shape-front.js +35 -0
  73. package/dist/esm/stage-chrome.js +12 -0
  74. package/dist/esm/svg-backend.js +2168 -0
  75. package/dist/esm/text-autofit.js +44 -0
  76. package/dist/esm/text-hit.js +280 -0
  77. package/dist/esm/text-layout.js +400 -0
  78. package/dist/esm/text-shared.js +221 -0
  79. package/dist/esm/text-stacked.js +78 -0
  80. package/dist/esm/text-warp.js +371 -0
  81. package/dist/esm/text-wordart.js +30 -0
  82. package/dist/esm/video-frame.js +109 -0
  83. package/dist/esm/webgl-model3d-backend.js +248 -0
  84. package/dist/esm/webgl-shape-backend.js +323 -0
  85. package/dist/types/arrow-geom.d.ts +10 -0
  86. package/dist/types/canvas-backend.d.ts +55 -0
  87. package/dist/types/cell-borders.d.ts +9 -0
  88. package/dist/types/chart-canvas.d.ts +2 -0
  89. package/dist/types/chart-ex-render.d.ts +79 -0
  90. package/dist/types/chart-geom.d.ts +112 -0
  91. package/dist/types/chart-svg.d.ts +2 -0
  92. package/dist/types/compose.d.ts +2 -0
  93. package/dist/types/effect-clip.d.ts +10 -0
  94. package/dist/types/entrance-hide.d.ts +16 -0
  95. package/dist/types/font-stack.d.ts +9 -0
  96. package/dist/types/hit-geom.d.ts +15 -0
  97. package/dist/types/hybrid-backend.d.ts +21 -0
  98. package/dist/types/hybrid-segments.d.ts +11 -0
  99. package/dist/types/index.d.ts +24 -0
  100. package/dist/types/linked-src.d.ts +13 -0
  101. package/dist/types/metafile-preview.d.ts +11 -0
  102. package/dist/types/model3d-frame.d.ts +8 -0
  103. package/dist/types/model3d-render.d.ts +4 -0
  104. package/dist/types/morph-transition.d.ts +15 -0
  105. package/dist/types/node-affine.d.ts +22 -0
  106. package/dist/types/ole-render.d.ts +7 -0
  107. package/dist/types/paint-canvas.d.ts +22 -0
  108. package/dist/types/paint-svg.d.ts +19 -0
  109. package/dist/types/scene-node.d.ts +128 -0
  110. package/dist/types/selection.d.ts +22 -0
  111. package/dist/types/shape-effects.d.ts +13 -0
  112. package/dist/types/shape-front.d.ts +4 -0
  113. package/dist/types/stage-chrome.d.ts +8 -0
  114. package/dist/types/svg-backend.d.ts +88 -0
  115. package/dist/types/text-autofit.d.ts +7 -0
  116. package/dist/types/text-hit.d.ts +7 -0
  117. package/dist/types/text-layout.d.ts +35 -0
  118. package/dist/types/text-shared.d.ts +86 -0
  119. package/dist/types/text-stacked.d.ts +11 -0
  120. package/dist/types/text-warp.d.ts +37 -0
  121. package/dist/types/text-wordart.d.ts +18 -0
  122. package/dist/types/video-frame.d.ts +2 -0
  123. package/dist/types/webgl-model3d-backend.d.ts +25 -0
  124. package/dist/types/webgl-shape-backend.d.ts +33 -0
  125. package/package.json +33 -4
  126. package/README.md +0 -3
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WebGLModel3DBackend = void 0;
37
+ const THREE = __importStar(require("three"));
38
+ const model3d_frame_1 = require("./model3d-frame");
39
+ let sharedRenderer = null;
40
+ let sharedRendererFailed = false;
41
+ function getSharedRenderer() {
42
+ if (sharedRenderer)
43
+ return sharedRenderer;
44
+ if (sharedRendererFailed)
45
+ return null;
46
+ try {
47
+ const r = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
48
+ r.setPixelRatio(1);
49
+ r.setClearColor(0x000000, 0);
50
+ r.outputColorSpace = THREE.SRGBColorSpace;
51
+ sharedRenderer = r;
52
+ }
53
+ catch {
54
+ sharedRendererFailed = true;
55
+ }
56
+ return sharedRenderer;
57
+ }
58
+ class WebGLModel3DBackend {
59
+ space;
60
+ managed = [];
61
+ dispose = [];
62
+ k = 1;
63
+ rafId = 0;
64
+ readyResolvers = [];
65
+ constructor(scene, container, opts = {}) {
66
+ const shapeFilter = opts.shapeFilter ?? (() => true);
67
+ const interactive = opts.interactive === true;
68
+ this.space = document.createElement("div");
69
+ this.space.style.cssText = "position:absolute;left:0;top:0;transform-origin:0 0;pointer-events:none";
70
+ this.space.style.width = `${scene.width}px`;
71
+ this.space.style.height = `${scene.height}px`;
72
+ container.appendChild(this.space);
73
+ scene.root.walk((n) => {
74
+ if (n.shape.kind !== "model3d")
75
+ return;
76
+ const s = n.shape;
77
+ if (!shapeFilter(s))
78
+ return;
79
+ if (!s.glbSrc)
80
+ return;
81
+ this.add(n, s, interactive);
82
+ });
83
+ const updateScale = () => {
84
+ const w = container.clientWidth || scene.width;
85
+ const k = w / scene.width;
86
+ if (!Number.isFinite(k) || k <= 0)
87
+ return;
88
+ this.k = k;
89
+ this.space.style.transform = `scale(${k})`;
90
+ this.scheduleRender();
91
+ };
92
+ if (typeof ResizeObserver !== "undefined") {
93
+ const ro = new ResizeObserver(updateScale);
94
+ ro.observe(container);
95
+ this.dispose.push(() => ro.disconnect());
96
+ }
97
+ updateScale();
98
+ this.scheduleRender();
99
+ }
100
+ add(node, s, interactive) {
101
+ const wrapper = document.createElement("div");
102
+ wrapper.style.position = "absolute";
103
+ wrapper.style.overflow = "hidden";
104
+ const canvas = document.createElement("canvas");
105
+ canvas.style.width = "100%";
106
+ canvas.style.height = "100%";
107
+ canvas.style.display = "block";
108
+ wrapper.appendChild(canvas);
109
+ this.space.appendChild(wrapper);
110
+ const m = {
111
+ node,
112
+ bbox: { ...s.bbox },
113
+ wrapper,
114
+ canvas,
115
+ ctx: canvas.getContext("2d"),
116
+ scene: null,
117
+ camera: null,
118
+ lat: s.camera?.rot?.lat ?? 15,
119
+ lon: s.camera?.rot?.lon ?? 25,
120
+ lastW: 0,
121
+ lastH: 0,
122
+ };
123
+ this.managed.push(m);
124
+ if (interactive)
125
+ this.bindOrbit(m);
126
+ void (0, model3d_frame_1.loadGltfScene)(s.glbSrc).then((root) => {
127
+ const sc = new THREE.Scene();
128
+ const clone = root.clone(true);
129
+ (0, model3d_frame_1.applyModel3DTransform)(clone, s);
130
+ sc.add(clone);
131
+ const light = new THREE.DirectionalLight(0xffffff, 1.1);
132
+ light.position.set(2, 3, 4);
133
+ sc.add(light);
134
+ sc.add(new THREE.AmbientLight(0xffffff, 0.45));
135
+ const cam = new THREE.PerspectiveCamera(35, 1, 0.01, 100);
136
+ (0, model3d_frame_1.applyModel3DCameraPose)(cam, m.lat, m.lon);
137
+ m.scene = sc;
138
+ m.camera = cam;
139
+ this.syncWrapper(m);
140
+ this.scheduleRender();
141
+ this.resolveReady();
142
+ }).catch(() => this.resolveReady());
143
+ const off = node.on(() => this.syncWrapper(m));
144
+ this.dispose.push(off);
145
+ }
146
+ bindOrbit(m) {
147
+ const el = m.wrapper;
148
+ el.style.pointerEvents = "auto";
149
+ el.style.cursor = "grab";
150
+ el.style.touchAction = "none";
151
+ let dragging = false;
152
+ let lastX = 0;
153
+ let lastY = 0;
154
+ const stop = (e) => e.stopPropagation();
155
+ el.addEventListener("pointerdown", (e) => {
156
+ if (e.button !== 0)
157
+ return;
158
+ e.stopPropagation();
159
+ e.preventDefault();
160
+ dragging = true;
161
+ lastX = e.clientX;
162
+ lastY = e.clientY;
163
+ try {
164
+ el.setPointerCapture(e.pointerId);
165
+ }
166
+ catch { }
167
+ el.style.cursor = "grabbing";
168
+ });
169
+ el.addEventListener("pointermove", (e) => {
170
+ if (!dragging)
171
+ return;
172
+ e.stopPropagation();
173
+ const dx = e.clientX - lastX;
174
+ const dy = e.clientY - lastY;
175
+ lastX = e.clientX;
176
+ lastY = e.clientY;
177
+ this.orbitModel3D(m.node.shape.id, -dx * 0.45, dy * 0.45);
178
+ });
179
+ const end = (e) => {
180
+ if (!dragging)
181
+ return;
182
+ dragging = false;
183
+ el.style.cursor = "grab";
184
+ try {
185
+ el.releasePointerCapture(e.pointerId);
186
+ }
187
+ catch { }
188
+ e.stopPropagation();
189
+ };
190
+ el.addEventListener("pointerup", end);
191
+ el.addEventListener("pointercancel", end);
192
+ el.addEventListener("mousedown", stop);
193
+ el.addEventListener("click", stop);
194
+ el.addEventListener("dblclick", stop);
195
+ el.addEventListener("touchstart", stop, { passive: true });
196
+ }
197
+ syncWrapper(m) {
198
+ const n = m.node;
199
+ const s = n.shape;
200
+ const b = s.bbox;
201
+ m.bbox = { ...b };
202
+ m.wrapper.style.left = `${b.x}px`;
203
+ m.wrapper.style.top = `${b.y}px`;
204
+ m.wrapper.style.width = `${b.w}px`;
205
+ m.wrapper.style.height = `${b.h}px`;
206
+ m.wrapper.style.opacity = String(n.opacity * ((n.visibleOverride ?? s.visible) !== false ? 1 : 0));
207
+ m.wrapper.style.transform = `translate(${n.tx}px,${n.ty}px) rotate(${n.rotation}deg) scale(${n.scale})`;
208
+ }
209
+ scheduleRender() {
210
+ if (this.rafId)
211
+ return;
212
+ this.rafId = requestAnimationFrame(() => {
213
+ this.rafId = 0;
214
+ this.renderAll();
215
+ });
216
+ }
217
+ renderAll() {
218
+ const renderer = getSharedRenderer();
219
+ if (!renderer)
220
+ return;
221
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
222
+ for (const m of this.managed) {
223
+ if (!m.scene || !m.camera || !m.ctx)
224
+ continue;
225
+ const w = Math.max(1, Math.ceil(m.bbox.w * this.k * dpr));
226
+ const h = Math.max(1, Math.ceil(m.bbox.h * this.k * dpr));
227
+ m.lastW = w;
228
+ m.lastH = h;
229
+ if (m.canvas.width !== w)
230
+ m.canvas.width = w;
231
+ if (m.canvas.height !== h)
232
+ m.canvas.height = h;
233
+ m.camera.aspect = w / h;
234
+ m.camera.updateProjectionMatrix();
235
+ renderer.setSize(w, h, false);
236
+ renderer.render(m.scene, m.camera);
237
+ m.ctx.clearRect(0, 0, w, h);
238
+ m.ctx.drawImage(renderer.domElement, 0, 0, w, h);
239
+ }
240
+ }
241
+ resolveReady() {
242
+ const rs = this.readyResolvers.splice(0);
243
+ for (const r of rs)
244
+ r();
245
+ }
246
+ orbitModel3D(shapeId, dLon, dLat) {
247
+ const m = this.managed.find((x) => x.node.shape.id === shapeId);
248
+ if (!m)
249
+ return false;
250
+ if (dLon === 0 && dLat === 0)
251
+ return true;
252
+ m.lon += dLon;
253
+ m.lat = Math.max(-85, Math.min(85, m.lat + dLat));
254
+ if (m.camera) {
255
+ (0, model3d_frame_1.applyModel3DCameraPose)(m.camera, m.lat, m.lon);
256
+ this.scheduleRender();
257
+ }
258
+ return true;
259
+ }
260
+ flush() {
261
+ if (this.rafId) {
262
+ cancelAnimationFrame(this.rafId);
263
+ this.rafId = 0;
264
+ }
265
+ for (const m of this.managed)
266
+ this.syncWrapper(m);
267
+ this.renderAll();
268
+ }
269
+ whenReady() {
270
+ return new Promise((res) => {
271
+ if (this.managed.every((m) => m.scene || !m.node.shape.glbSrc))
272
+ res();
273
+ else
274
+ this.readyResolvers.push(res);
275
+ });
276
+ }
277
+ destroy() {
278
+ for (const d of this.dispose)
279
+ d();
280
+ this.dispose = [];
281
+ this.space.remove();
282
+ this.managed = [];
283
+ }
284
+ }
285
+ exports.WebGLModel3DBackend = WebGLModel3DBackend;
@@ -0,0 +1,360 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WebGLShapeBackend = void 0;
37
+ const THREE = __importStar(require("three"));
38
+ const three_bake_1 = require("@sliiu/present-three-bake");
39
+ const node_affine_1 = require("./node-affine");
40
+ const shape_front_1 = require("./shape-front");
41
+ // ════════════════════════════════════════════════════════════════════════════
42
+ // Live WebGL 3D backend —— player / editor 的 hybrid 视图里,每个需要 3D 的
43
+ // image / 矩形 / 椭圆 / 路径 / 多边形用一块定位 canvas 实时渲染 three.js 3D
44
+ // (透视 / 倒角 / 光照 / 材质)。好处:
45
+ // - 缩放清晰(按显示像素重渲,不再是固定分辨率位图);
46
+ // - 动画(entrance/emphasis 的 tx/ty/scale/opacity)过程中 3D 形体保持立体、光照正确。
47
+ //
48
+ // 设计:
49
+ // - 一个 "slide-space" 容器(slide 像素尺寸)用 transform:scale(k) 适配 layer 实际像素,
50
+ // ResizeObserver 跟随更新;每个 shape 的 wrapper 用 slide 单位定位。
51
+ // - 共享一个 WebGLRenderer(单 GL context),逐 shape 渲染到其 backing 尺寸再 drawImage
52
+ // 到该 shape 的 2D 展示 canvas,避免 per-shape GL context 累积。
53
+ // - 几何/材质/相机全部复用 @sliiu/present-three-bake 的 buildThreeScene / createSceneCamera,
54
+ // 与离屏光栅(缩略图/截图)完全一致。
55
+ // - 每帧 2D 变换(tx/ty/rotation/scale/opacity)经 node.on() 同步到 wrapper CSS,
56
+ // 不重渲 GL(3D 相机不随动画变);仅 resize / 贴图加载完成时重渲 GL。
57
+ // ════════════════════════════════════════════════════════════════════════════
58
+ // ── 共享 WebGLRenderer(单 GL context,跨 backend 实例复用)──────────────────────
59
+ // 编辑器拖动时 drawSlide 每帧重建整个 backend(含 WebGLShapeBackend)。若每个实例都
60
+ // 自建/销毁一个 WebGLRenderer,会以 ~60fps 频繁创建 + forceContextLoss GL context:
61
+ // 浏览器对活动 context 数量有上限、create/loss 自身也有开销,会引发掉帧甚至闪烁。
62
+ // 这里改用一个 module 级单例 renderer,所有 WebGLShapeBackend 复用它(逐 shape
63
+ // setSize→render→drawImage 是同步串行的,复用安全)。backend.destroy() 不再销毁它。
64
+ let sharedRenderer = null;
65
+ let sharedRendererFailed = false;
66
+ function getSharedRenderer() {
67
+ if (sharedRenderer)
68
+ return sharedRenderer;
69
+ if (sharedRendererFailed)
70
+ return null;
71
+ try {
72
+ const r = new THREE.WebGLRenderer({
73
+ alpha: true,
74
+ antialias: true,
75
+ premultipliedAlpha: true,
76
+ preserveDrawingBuffer: true,
77
+ });
78
+ r.setPixelRatio(1);
79
+ r.setClearColor(0x000000, 0);
80
+ r.outputColorSpace = THREE.SRGBColorSpace;
81
+ sharedRenderer = r;
82
+ }
83
+ catch {
84
+ sharedRendererFailed = true;
85
+ sharedRenderer = null;
86
+ }
87
+ return sharedRenderer;
88
+ }
89
+ function paddedRect(bbox, pad) {
90
+ const padW = Math.ceil(bbox.w * (1 + pad));
91
+ const padH = Math.ceil(bbox.h * (1 + pad));
92
+ return {
93
+ x: bbox.x - (padW - bbox.w) / 2,
94
+ y: bbox.y - (padH - bbox.h) / 2,
95
+ w: padW,
96
+ h: padH,
97
+ };
98
+ }
99
+ class WebGLShapeBackend {
100
+ scene;
101
+ space;
102
+ managed = [];
103
+ dispose = [];
104
+ dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
105
+ k = 1;
106
+ rafId = 0;
107
+ ro = null;
108
+ readyResolvers = [];
109
+ constructor(scene, container, opts = {}) {
110
+ this.scene = scene;
111
+ const filter = opts.shapeFilter ?? (() => true);
112
+ this.space = document.createElement("div");
113
+ this.space.style.position = "absolute";
114
+ this.space.style.left = "0";
115
+ this.space.style.top = "0";
116
+ this.space.style.width = `${scene.width}px`;
117
+ this.space.style.height = `${scene.height}px`;
118
+ this.space.style.transformOrigin = "0 0";
119
+ this.space.style.pointerEvents = "none";
120
+ container.appendChild(this.space);
121
+ scene.root.walk((n) => {
122
+ const s = n.shape;
123
+ if (!(0, three_bake_1.shapeSupports3D)(s))
124
+ return;
125
+ if (!filter(s))
126
+ return;
127
+ if (!(0, three_bake_1.needs3DRender)((0, three_bake_1.shape3DInfo)(s)))
128
+ return;
129
+ this.add(n);
130
+ });
131
+ // 适配 layer 实际像素:k = 实际宽 / slide 宽。
132
+ const updateScale = () => {
133
+ const w = container.clientWidth || scene.width;
134
+ const k = w / scene.width;
135
+ if (!Number.isFinite(k) || k <= 0)
136
+ return;
137
+ if (Math.abs(k - this.k) < 1e-4)
138
+ return;
139
+ this.k = k;
140
+ this.space.style.transform = `scale(${k})`;
141
+ this.scheduleGlRender();
142
+ };
143
+ if (typeof ResizeObserver !== "undefined") {
144
+ this.ro = new ResizeObserver(updateScale);
145
+ this.ro.observe(container);
146
+ }
147
+ updateScale();
148
+ this.scheduleSync();
149
+ this.scheduleGlRender();
150
+ }
151
+ add(node) {
152
+ const s = node.shape;
153
+ const meta = (0, three_bake_1.shape3DInfo)(s);
154
+ if (!meta)
155
+ return;
156
+ const pad = (0, three_bake_1.shape3DPad)(s.kind);
157
+ const baseBBox = { x: s.bbox.x, y: s.bbox.y, w: s.bbox.w, h: s.bbox.h };
158
+ const paddedBBox = paddedRect(baseBBox, pad);
159
+ const resolved = (0, three_bake_1.resolveScene3D)(meta);
160
+ const wrapper = document.createElement("div");
161
+ wrapper.style.position = "absolute";
162
+ wrapper.style.left = `${paddedBBox.x}px`;
163
+ wrapper.style.top = `${paddedBBox.y}px`;
164
+ wrapper.style.width = `${paddedBBox.w}px`;
165
+ wrapper.style.height = `${paddedBBox.h}px`;
166
+ wrapper.style.pointerEvents = "none";
167
+ const canvas = document.createElement("canvas");
168
+ canvas.style.width = "100%";
169
+ canvas.style.height = "100%";
170
+ canvas.style.display = "block";
171
+ wrapper.appendChild(canvas);
172
+ this.space.appendChild(wrapper);
173
+ const m = {
174
+ node,
175
+ baseBBox,
176
+ paddedBBox,
177
+ pad,
178
+ resolved,
179
+ wrapper,
180
+ canvas,
181
+ ctx: canvas.getContext("2d"),
182
+ built: null,
183
+ camera: null,
184
+ texture: null,
185
+ lastBackingW: 0,
186
+ lastBackingH: 0,
187
+ disposed: false,
188
+ };
189
+ this.managed.push(m);
190
+ const src = s.kind === "image" ? s.src : s.kind === "text" ? (0, shape_front_1.shapeFrontImageSrc)(this.scene, s) : undefined;
191
+ // 贴图此前已解码过(典型:初次加载已经走过一遍)→ 同步构建场景并 built 就绪,
192
+ // 这样紧随其后的 flush() 能当帧首渲,drawSlide 双缓冲不会出现 3D 空一帧的闪屏。
193
+ const cachedTex = src ? (0, three_bake_1.getCachedImageTexture)(src) : null;
194
+ if (src ? cachedTex !== null : true) {
195
+ this.buildManaged(m, s, cachedTex);
196
+ }
197
+ else {
198
+ // 首次(未缓存)走异步解码;解码完成后会缓存,后续重建即走上面的同步路径。
199
+ void (async () => {
200
+ const texture = src ? await (0, three_bake_1.loadImageTexture)(src) : null;
201
+ if (m.disposed) {
202
+ texture?.dispose();
203
+ return;
204
+ }
205
+ this.buildManaged(m, s, texture);
206
+ })();
207
+ }
208
+ let cur = node;
209
+ while (cur) {
210
+ const off = cur.on(() => this.scheduleSync());
211
+ this.dispose.push(off);
212
+ cur = cur.parent;
213
+ }
214
+ }
215
+ /** 用(可能为空的)贴图构建 three 场景 + 相机,写回 managed 并触发首渲。 */
216
+ buildManaged(m, s, texture) {
217
+ if (m.disposed) {
218
+ texture?.dispose();
219
+ return;
220
+ }
221
+ const isPicture = s.kind === "image" || (s.kind === "text" && !!texture);
222
+ if (s.kind === "image" && texture)
223
+ (0, three_bake_1.applyTextureCrop)(texture, s.cropRect);
224
+ m.texture = texture;
225
+ m.built = (0, three_bake_1.buildThreeScene)({
226
+ baseW: Math.max(8, m.baseBBox.w),
227
+ baseH: Math.max(8, m.baseBBox.h),
228
+ pathD: isPicture ? undefined : (0, three_bake_1.shape3DPathD)(s),
229
+ fill: s.fill,
230
+ stroke: s.stroke,
231
+ resolved: m.resolved,
232
+ texture: isPicture ? texture : null,
233
+ isImage: isPicture,
234
+ });
235
+ m.camera = (0, three_bake_1.createSceneCamera)(m.resolved.camera, m.built.geoW, m.built.geoH, m.paddedBBox.w, m.paddedBBox.h, m.pad, m.resolved.backdrop);
236
+ this.scheduleGlRender();
237
+ this.checkReady();
238
+ }
239
+ getRenderer() {
240
+ return getSharedRenderer();
241
+ }
242
+ // ── 每帧 2D 变换同步(CSS,不重渲 GL)─────────────────────────────────────────
243
+ scheduleSync() {
244
+ if (this.rafId)
245
+ return;
246
+ this.rafId = requestAnimationFrame(() => {
247
+ this.rafId = 0;
248
+ this.syncTransforms();
249
+ });
250
+ }
251
+ syncTransforms() {
252
+ for (const m of this.managed)
253
+ this.syncOne(m);
254
+ }
255
+ syncOne(m) {
256
+ const node = m.node;
257
+ const visible = !(0, node_affine_1.chainHidden)(node);
258
+ m.wrapper.style.display = visible ? "block" : "none";
259
+ if (!visible)
260
+ return;
261
+ // wrapper 放在 paddedBBox。CSS 原点取左上角,矩阵里已经包含祖先组合的旋转。
262
+ const css = (0, node_affine_1.cssAffineForBox)((0, node_affine_1.accumulatedAffine)(node), m.paddedBBox.x, m.paddedBBox.y);
263
+ m.wrapper.style.transformOrigin = "0 0";
264
+ m.wrapper.style.transform = `matrix(${css.a}, ${css.b}, ${css.c}, ${css.d}, ${css.e}, ${css.f})`;
265
+ let alpha = (0, node_affine_1.chainOpacity)(node);
266
+ const clip = node.clip;
267
+ if (clip && clip.kind === "effect" && clip.filter === "fade") {
268
+ const tr = clip.transition ?? "none";
269
+ alpha *= tr === "none" ? 1 : Math.max(0, Math.min(1, clip.progress ?? 0));
270
+ }
271
+ m.wrapper.style.opacity = String(alpha);
272
+ }
273
+ // ── GL 重渲(仅 resize / 贴图加载完成时)──────────────────────────────────────
274
+ scheduleGlRender() {
275
+ if (typeof requestAnimationFrame === "undefined") {
276
+ this.glRenderAll();
277
+ return;
278
+ }
279
+ requestAnimationFrame(() => this.glRenderAll());
280
+ }
281
+ glRenderAll() {
282
+ const renderer = this.getRenderer();
283
+ if (!renderer)
284
+ return;
285
+ for (const m of this.managed)
286
+ this.glRenderOne(renderer, m);
287
+ }
288
+ glRenderOne(renderer, m) {
289
+ if (m.disposed || !m.built || !m.camera || !m.ctx)
290
+ return;
291
+ const backingW = Math.max(1, Math.ceil(m.paddedBBox.w * this.k * this.dpr));
292
+ const backingH = Math.max(1, Math.ceil(m.paddedBBox.h * this.k * this.dpr));
293
+ if (backingW === m.lastBackingW && backingH === m.lastBackingH && m.canvas.width === backingW) {
294
+ // 尺寸没变也重渲一次(贴图刚加载完会走到这里);但避免无意义的频繁循环。
295
+ }
296
+ renderer.setSize(backingW, backingH, false);
297
+ renderer.render(m.built.scene, m.camera);
298
+ if (m.canvas.width !== backingW)
299
+ m.canvas.width = backingW;
300
+ if (m.canvas.height !== backingH)
301
+ m.canvas.height = backingH;
302
+ m.ctx.clearRect(0, 0, backingW, backingH);
303
+ try {
304
+ m.ctx.drawImage(renderer.domElement, 0, 0, backingW, backingH);
305
+ }
306
+ catch {
307
+ // drawImage 偶发失败(context lost)忽略,下次 resize 再试。
308
+ }
309
+ m.lastBackingW = backingW;
310
+ m.lastBackingH = backingH;
311
+ }
312
+ flush() {
313
+ if (this.rafId) {
314
+ cancelAnimationFrame(this.rafId);
315
+ this.rafId = 0;
316
+ }
317
+ this.syncTransforms();
318
+ this.glRenderAll();
319
+ }
320
+ /** 所有 3D shape 的贴图都加载完、场景已构建并首渲后 resolve(截图前等待用)。 */
321
+ whenReady() {
322
+ if (this.isReady()) {
323
+ this.flush();
324
+ return Promise.resolve();
325
+ }
326
+ return new Promise((res) => this.readyResolvers.push(res));
327
+ }
328
+ isReady() {
329
+ return this.managed.every((m) => m.built || m.disposed);
330
+ }
331
+ checkReady() {
332
+ if (!this.isReady())
333
+ return;
334
+ this.flush();
335
+ const rs = this.readyResolvers;
336
+ this.readyResolvers = [];
337
+ for (const r of rs)
338
+ r();
339
+ }
340
+ destroy() {
341
+ if (this.rafId)
342
+ cancelAnimationFrame(this.rafId);
343
+ this.rafId = 0;
344
+ for (const off of this.dispose)
345
+ off();
346
+ this.dispose = [];
347
+ this.ro?.disconnect();
348
+ this.ro = null;
349
+ for (const m of this.managed) {
350
+ m.disposed = true;
351
+ m.built?.dispose();
352
+ m.texture?.dispose();
353
+ }
354
+ this.managed = [];
355
+ // 不销毁 sharedRenderer:它是跨 backend 实例复用的单例 GL context(见顶部注释)。
356
+ // 编辑器拖动每帧重建 backend,这里若 forceContextLoss 会造成 context 抖动 / 闪屏。
357
+ this.space.remove();
358
+ }
359
+ }
360
+ exports.WebGLShapeBackend = WebGLShapeBackend;
@@ -0,0 +1,33 @@
1
+ // 线端箭头(headEnd / tailEnd)的几何,SVG 与 Canvas 两个 backend 共用,保证导出一致。
2
+ // 尺寸单位为 strokeWidth 的倍数(与 SVG markerUnits="strokeWidth" 一致)。
3
+ export function arrowSize(spec) {
4
+ const wMap = { sm: 3, med: 5, lg: 8 };
5
+ const lMap = { sm: 3, med: 5, lg: 8 };
6
+ return { w: wMap[spec.w ?? "med"], l: lMap[spec.len ?? "med"] };
7
+ }
8
+ export function arrowPathD(kind, w, l, side) {
9
+ if (kind === "oval") {
10
+ return `M 0 ${-w / 2} a ${l / 2} ${w / 2} 0 1 0 0 ${w} a ${l / 2} ${w / 2} 0 1 0 0 ${-w} z`;
11
+ }
12
+ if (kind === "diamond") {
13
+ return `M 0 0 L ${l / 2} ${-w / 2} L ${l} 0 L ${l / 2} ${w / 2} Z`;
14
+ }
15
+ if (kind === "stealth") {
16
+ return side === "end"
17
+ ? `M 0 ${-w / 2} L ${l} 0 L 0 ${w / 2} L ${l * 0.3} 0 Z`
18
+ : `M ${l} ${-w / 2} L 0 0 L ${l} ${w / 2} L ${l * 0.7} 0 Z`;
19
+ }
20
+ return side === "end"
21
+ ? `M 0 ${-w / 2} L ${l} 0 L 0 ${w / 2} Z`
22
+ : `M ${l} ${-w / 2} L 0 0 L ${l} ${w / 2} Z`;
23
+ }
24
+ // PowerPoint / WPS 约定下,三角 / 隐身箭头的"基底"锚定在线端点,"尖端"向外延伸;
25
+ // 圆 / 菱形等中心对称形状骑跨在线端点上(中心 = 端点)。orient="auto" 让 +X 沿线方向,
26
+ // 因此 end 侧 refX=0(基底在端点、尖端向 +X 外延),start 侧 path 已镜像、refX=l。
27
+ export function arrowRefX(kind, side, l) {
28
+ if (kind === "oval")
29
+ return 0;
30
+ if (kind === "diamond")
31
+ return l / 2;
32
+ return side === "end" ? 0 : l;
33
+ }