@sliiu/present-renderer 0.0.0-stage → 0.1.1
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/COPYRIGHT +5 -0
- package/LICENSE +661 -0
- package/README.md +8 -2
- package/THIRD-PARTY-NOTICES.md +221 -0
- package/dist/cjs/arrow-geom.js +38 -0
- package/dist/cjs/canvas-backend.js +1368 -0
- package/dist/cjs/cell-borders.js +25 -0
- package/dist/cjs/chart-canvas.js +1059 -0
- package/dist/cjs/chart-ex-render.js +275 -0
- package/dist/cjs/chart-geom.js +313 -0
- package/dist/cjs/chart-svg.js +1110 -0
- package/dist/cjs/compose.js +74 -0
- package/dist/cjs/effect-clip.js +652 -0
- package/dist/cjs/entrance-hide.js +63 -0
- package/dist/cjs/font-stack.js +103 -0
- package/dist/cjs/hit-geom.js +50 -0
- package/dist/cjs/hybrid-backend.js +93 -0
- package/dist/cjs/hybrid-segments.js +54 -0
- package/dist/cjs/index.js +70 -0
- package/dist/cjs/linked-src.js +116 -0
- package/dist/cjs/metafile-preview.js +142 -0
- package/dist/cjs/model3d-frame.js +199 -0
- package/dist/cjs/model3d-render.js +78 -0
- package/dist/cjs/morph-transition.js +57 -0
- package/dist/cjs/node-affine.js +79 -0
- package/dist/cjs/ole-render.js +291 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/paint-canvas.js +701 -0
- package/dist/cjs/paint-svg.js +674 -0
- package/dist/cjs/scene-node.js +762 -0
- package/dist/cjs/selection.js +130 -0
- package/dist/cjs/shape-effects.js +66 -0
- package/dist/cjs/shape-front.js +38 -0
- package/dist/cjs/stage-chrome.js +15 -0
- package/dist/cjs/svg-backend.js +2172 -0
- package/dist/cjs/text-autofit.js +48 -0
- package/dist/cjs/text-hit.js +284 -0
- package/dist/cjs/text-layout.js +408 -0
- package/dist/cjs/text-shared.js +238 -0
- package/dist/cjs/text-stacked.js +82 -0
- package/dist/cjs/text-warp.js +377 -0
- package/dist/cjs/text-wordart.js +33 -0
- package/dist/cjs/video-frame.js +113 -0
- package/dist/cjs/webgl-model3d-backend.js +285 -0
- package/dist/cjs/webgl-shape-backend.js +360 -0
- package/dist/esm/arrow-geom.js +33 -0
- package/dist/esm/canvas-backend.js +1363 -0
- package/dist/esm/cell-borders.js +21 -0
- package/dist/esm/chart-canvas.js +1056 -0
- package/dist/esm/chart-ex-render.js +261 -0
- package/dist/esm/chart-geom.js +296 -0
- package/dist/esm/chart-svg.js +1107 -0
- package/dist/esm/compose.js +71 -0
- package/dist/esm/effect-clip.js +648 -0
- package/dist/esm/entrance-hide.js +59 -0
- package/dist/esm/font-stack.js +100 -0
- package/dist/esm/hit-geom.js +45 -0
- package/dist/esm/hybrid-backend.js +89 -0
- package/dist/esm/hybrid-segments.js +51 -0
- package/dist/esm/index.js +24 -0
- package/dist/esm/linked-src.js +109 -0
- package/dist/esm/metafile-preview.js +135 -0
- package/dist/esm/model3d-frame.js +158 -0
- package/dist/esm/model3d-render.js +73 -0
- package/dist/esm/morph-transition.js +54 -0
- package/dist/esm/node-affine.js +71 -0
- package/dist/esm/ole-render.js +286 -0
- package/dist/esm/paint-canvas.js +693 -0
- package/dist/esm/paint-svg.js +670 -0
- package/dist/esm/scene-node.js +750 -0
- package/dist/esm/selection.js +122 -0
- package/dist/esm/shape-effects.js +60 -0
- package/dist/esm/shape-front.js +35 -0
- package/dist/esm/stage-chrome.js +12 -0
- package/dist/esm/svg-backend.js +2168 -0
- package/dist/esm/text-autofit.js +44 -0
- package/dist/esm/text-hit.js +280 -0
- package/dist/esm/text-layout.js +400 -0
- package/dist/esm/text-shared.js +221 -0
- package/dist/esm/text-stacked.js +78 -0
- package/dist/esm/text-warp.js +371 -0
- package/dist/esm/text-wordart.js +30 -0
- package/dist/esm/video-frame.js +109 -0
- package/dist/esm/webgl-model3d-backend.js +248 -0
- package/dist/esm/webgl-shape-backend.js +323 -0
- package/dist/types/arrow-geom.d.ts +10 -0
- package/dist/types/canvas-backend.d.ts +55 -0
- package/dist/types/cell-borders.d.ts +9 -0
- package/dist/types/chart-canvas.d.ts +2 -0
- package/dist/types/chart-ex-render.d.ts +79 -0
- package/dist/types/chart-geom.d.ts +112 -0
- package/dist/types/chart-svg.d.ts +2 -0
- package/dist/types/compose.d.ts +2 -0
- package/dist/types/effect-clip.d.ts +10 -0
- package/dist/types/entrance-hide.d.ts +16 -0
- package/dist/types/font-stack.d.ts +9 -0
- package/dist/types/hit-geom.d.ts +15 -0
- package/dist/types/hybrid-backend.d.ts +21 -0
- package/dist/types/hybrid-segments.d.ts +11 -0
- package/dist/types/index.d.ts +24 -0
- package/dist/types/linked-src.d.ts +13 -0
- package/dist/types/metafile-preview.d.ts +11 -0
- package/dist/types/model3d-frame.d.ts +8 -0
- package/dist/types/model3d-render.d.ts +4 -0
- package/dist/types/morph-transition.d.ts +15 -0
- package/dist/types/node-affine.d.ts +22 -0
- package/dist/types/ole-render.d.ts +7 -0
- package/dist/types/paint-canvas.d.ts +22 -0
- package/dist/types/paint-svg.d.ts +19 -0
- package/dist/types/scene-node.d.ts +128 -0
- package/dist/types/selection.d.ts +22 -0
- package/dist/types/shape-effects.d.ts +13 -0
- package/dist/types/shape-front.d.ts +4 -0
- package/dist/types/stage-chrome.d.ts +8 -0
- package/dist/types/svg-backend.d.ts +88 -0
- package/dist/types/text-autofit.d.ts +7 -0
- package/dist/types/text-hit.d.ts +7 -0
- package/dist/types/text-layout.d.ts +35 -0
- package/dist/types/text-shared.d.ts +86 -0
- package/dist/types/text-stacked.d.ts +11 -0
- package/dist/types/text-warp.d.ts +37 -0
- package/dist/types/text-wordart.d.ts +18 -0
- package/dist/types/video-frame.d.ts +2 -0
- package/dist/types/webgl-model3d-backend.d.ts +25 -0
- package/dist/types/webgl-shape-backend.d.ts +33 -0
- package/package.json +43 -4
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// EMF / WMF 浏览器画不出来。上屏前转成 SVG,原 src 不动,导出仍写回图元文件。
|
|
3
|
+
// 转出来的 SVG 不放进 shape.svgSrc:那个字段会写成 asvg:svgBlip,文件就会多出一个部件。
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.METAFILE_MARKER_SRC = void 0;
|
|
6
|
+
exports.isMetafileDataUrl = isMetafileDataUrl;
|
|
7
|
+
exports.isMetafileBytes = isMetafileBytes;
|
|
8
|
+
exports.previewImageSrc = previewImageSrc;
|
|
9
|
+
exports.waitForMetafilePreviews = waitForMetafilePreviews;
|
|
10
|
+
const emf_converter_1 = require("emf-converter");
|
|
11
|
+
exports.METAFILE_MARKER_SRC = "data:image/svg+xml," +
|
|
12
|
+
encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect x="1.5" y="1.5" width="61" height="61" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1" stroke-dasharray="4 3"/></svg>`);
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
const jobs = new Map();
|
|
15
|
+
const watched = new WeakSet();
|
|
16
|
+
function isMetafileDataUrl(src) {
|
|
17
|
+
return /^data:image\/(?:x-)?(?:emf|wmf)(?:;|,)/i.test(src);
|
|
18
|
+
}
|
|
19
|
+
/** EMF 签名在偏移 40(" EMF")。可放置 WMF 以 0x9AC6CDD7 开头。 */
|
|
20
|
+
function isMetafileBytes(bytes, mime = "") {
|
|
21
|
+
if (/emf|wmf/i.test(mime))
|
|
22
|
+
return true;
|
|
23
|
+
if (bytes.length >= 4) {
|
|
24
|
+
const key = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
|
|
25
|
+
if (key === 0x9ac6cdd7)
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (bytes.length >= 44) {
|
|
29
|
+
const sig = bytes[40] | (bytes[41] << 8) | (bytes[42] << 16) | (bytes[43] << 24);
|
|
30
|
+
if (sig === 0x464d4520)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 画布该用的地址。
|
|
37
|
+
* 返回空字符串表示图元文件还在转,调用方先留白,完成后再画。
|
|
38
|
+
* 转失败返回虚线占位。普通图片原样返回。
|
|
39
|
+
*/
|
|
40
|
+
function previewImageSrc(src, img, onReady) {
|
|
41
|
+
const hit = cache.get(src);
|
|
42
|
+
if (hit?.kind === "native")
|
|
43
|
+
return src;
|
|
44
|
+
if (hit?.kind === "svg" || hit?.kind === "marker")
|
|
45
|
+
return hit.url;
|
|
46
|
+
if (jobs.has(src))
|
|
47
|
+
return "";
|
|
48
|
+
if (isMetafileDataUrl(src)) {
|
|
49
|
+
ensure(src, onReady);
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
if (img && !img.complete && onReady && !watched.has(img)) {
|
|
53
|
+
watched.add(img);
|
|
54
|
+
img.addEventListener("error", () => {
|
|
55
|
+
ensure(src, onReady);
|
|
56
|
+
}, { once: true });
|
|
57
|
+
}
|
|
58
|
+
if (!img || !img.complete)
|
|
59
|
+
return src;
|
|
60
|
+
if (img.naturalWidth > 0) {
|
|
61
|
+
cache.set(src, { kind: "native" });
|
|
62
|
+
return src;
|
|
63
|
+
}
|
|
64
|
+
ensure(src, onReady);
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
function waitForMetafilePreviews() {
|
|
68
|
+
return Promise.all([...jobs.values()]).then(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
function ensure(src, onReady) {
|
|
71
|
+
let job = jobs.get(src);
|
|
72
|
+
if (!job) {
|
|
73
|
+
job = run(src);
|
|
74
|
+
jobs.set(src, job);
|
|
75
|
+
void job.finally(() => {
|
|
76
|
+
jobs.delete(src);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (onReady)
|
|
80
|
+
void job.then(() => onReady());
|
|
81
|
+
}
|
|
82
|
+
async function run(src) {
|
|
83
|
+
if (cache.has(src))
|
|
84
|
+
return;
|
|
85
|
+
const claimed = isMetafileDataUrl(src);
|
|
86
|
+
try {
|
|
87
|
+
const loaded = await loadBytes(src);
|
|
88
|
+
if (!loaded) {
|
|
89
|
+
cache.set(src, claimed ? { kind: "marker", url: exports.METAFILE_MARKER_SRC } : { kind: "native" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!claimed && !isMetafileBytes(loaded.bytes, loaded.mime)) {
|
|
93
|
+
cache.set(src, { kind: "native" });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const svg = await (0, emf_converter_1.convertMetafileToSvgDataUrl)(toArrayBuffer(loaded.bytes));
|
|
97
|
+
cache.set(src, svg ? { kind: "svg", url: svg } : { kind: "marker", url: exports.METAFILE_MARKER_SRC });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
cache.set(src, claimed ? { kind: "marker", url: exports.METAFILE_MARKER_SRC } : { kind: "native" });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function toArrayBuffer(bytes) {
|
|
104
|
+
const copy = new ArrayBuffer(bytes.byteLength);
|
|
105
|
+
new Uint8Array(copy).set(bytes);
|
|
106
|
+
return copy;
|
|
107
|
+
}
|
|
108
|
+
async function loadBytes(src) {
|
|
109
|
+
if (src.startsWith("data:")) {
|
|
110
|
+
const bytes = decodeDataUrl(src);
|
|
111
|
+
return bytes ? { bytes, mime: "" } : undefined;
|
|
112
|
+
}
|
|
113
|
+
if (typeof fetch !== "function")
|
|
114
|
+
return undefined;
|
|
115
|
+
const res = await fetch(src);
|
|
116
|
+
if (!res.ok)
|
|
117
|
+
return undefined;
|
|
118
|
+
return {
|
|
119
|
+
bytes: new Uint8Array(await res.arrayBuffer()),
|
|
120
|
+
mime: res.headers.get("content-type") ?? "",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function decodeDataUrl(src) {
|
|
124
|
+
const comma = src.indexOf(",");
|
|
125
|
+
if (comma < 0)
|
|
126
|
+
return undefined;
|
|
127
|
+
const head = src.slice(0, comma);
|
|
128
|
+
const body = src.slice(comma + 1);
|
|
129
|
+
try {
|
|
130
|
+
if (/;base64/i.test(head)) {
|
|
131
|
+
const bin = atob(body);
|
|
132
|
+
const out = new Uint8Array(bin.length);
|
|
133
|
+
for (let i = 0; i < bin.length; i++)
|
|
134
|
+
out[i] = bin.charCodeAt(i) & 0xff;
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
return new TextEncoder().encode(decodeURIComponent(body));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
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.loadGltfScene = loadGltfScene;
|
|
37
|
+
exports.applyModel3DCameraPose = applyModel3DCameraPose;
|
|
38
|
+
exports.applyModel3DCamera = applyModel3DCamera;
|
|
39
|
+
exports.applyModel3DTransform = applyModel3DTransform;
|
|
40
|
+
exports.getModel3DPreviewFrame = getModel3DPreviewFrame;
|
|
41
|
+
exports.waitForModel3DFrames = waitForModel3DFrames;
|
|
42
|
+
const THREE = __importStar(require("three"));
|
|
43
|
+
const GLTFLoader_js_1 = require("three/examples/jsm/loaders/GLTFLoader.js");
|
|
44
|
+
const gltfCache = new Map();
|
|
45
|
+
function loadGltfScene(src) {
|
|
46
|
+
let hit = gltfCache.get(src);
|
|
47
|
+
if (!hit) {
|
|
48
|
+
const loader = new GLTFLoader_js_1.GLTFLoader();
|
|
49
|
+
hit = new Promise((resolve, reject) => {
|
|
50
|
+
loader.load(src, (gltf) => {
|
|
51
|
+
const root = gltf.scene;
|
|
52
|
+
const box = new THREE.Box3().setFromObject(root);
|
|
53
|
+
const size = new THREE.Vector3();
|
|
54
|
+
const center = new THREE.Vector3();
|
|
55
|
+
box.getSize(size);
|
|
56
|
+
box.getCenter(center);
|
|
57
|
+
root.position.sub(center);
|
|
58
|
+
const maxDim = Math.max(size.x, size.y, size.z, 1e-6);
|
|
59
|
+
root.scale.setScalar(1 / maxDim);
|
|
60
|
+
resolve(root);
|
|
61
|
+
}, undefined, reject);
|
|
62
|
+
});
|
|
63
|
+
gltfCache.set(src, hit);
|
|
64
|
+
}
|
|
65
|
+
return hit;
|
|
66
|
+
}
|
|
67
|
+
function deg(v) {
|
|
68
|
+
return ((v ?? 0) * Math.PI) / 180;
|
|
69
|
+
}
|
|
70
|
+
function applyModel3DCameraPose(cam, latDeg, lonDeg, dist = 2.8) {
|
|
71
|
+
const lat = deg(latDeg);
|
|
72
|
+
const lon = deg(lonDeg);
|
|
73
|
+
cam.position.set(dist * Math.cos(lat) * Math.sin(lon), dist * Math.sin(lat), dist * Math.cos(lat) * Math.cos(lon));
|
|
74
|
+
cam.lookAt(0, 0, 0);
|
|
75
|
+
cam.updateProjectionMatrix();
|
|
76
|
+
}
|
|
77
|
+
function applyModel3DCamera(cam, s) {
|
|
78
|
+
applyModel3DCameraPose(cam, s.camera?.rot?.lat ?? 15, s.camera?.rot?.lon ?? 25);
|
|
79
|
+
}
|
|
80
|
+
function applyModel3DTransform(root, s) {
|
|
81
|
+
const t = s.modelTransform;
|
|
82
|
+
if (!t)
|
|
83
|
+
return;
|
|
84
|
+
const sc = t.scale;
|
|
85
|
+
if (sc)
|
|
86
|
+
root.scale.multiply(new THREE.Vector3(sc.x || 1, sc.y || 1, sc.z || 1));
|
|
87
|
+
const r = t.rot;
|
|
88
|
+
if (r)
|
|
89
|
+
root.rotation.set(deg(r.lat), deg(r.lon), deg(r.rev), "YXZ");
|
|
90
|
+
const o = t.off;
|
|
91
|
+
if (o)
|
|
92
|
+
root.position.add(new THREE.Vector3(o.x, o.y, o.z));
|
|
93
|
+
}
|
|
94
|
+
const previewCache = new Map();
|
|
95
|
+
let previewRenderer = null;
|
|
96
|
+
let previewRendererFailed = false;
|
|
97
|
+
function getPreviewRenderer() {
|
|
98
|
+
if (previewRenderer)
|
|
99
|
+
return previewRenderer;
|
|
100
|
+
if (previewRendererFailed)
|
|
101
|
+
return null;
|
|
102
|
+
if (typeof document === "undefined") {
|
|
103
|
+
previewRendererFailed = true;
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const r = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
|
|
108
|
+
r.setPixelRatio(1);
|
|
109
|
+
r.setClearColor(0x000000, 0);
|
|
110
|
+
r.outputColorSpace = THREE.SRGBColorSpace;
|
|
111
|
+
previewRenderer = r;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
previewRendererFailed = true;
|
|
115
|
+
}
|
|
116
|
+
return previewRenderer;
|
|
117
|
+
}
|
|
118
|
+
function previewKey(s) {
|
|
119
|
+
return JSON.stringify({
|
|
120
|
+
src: s.glbSrc,
|
|
121
|
+
cam: s.camera ?? null,
|
|
122
|
+
xf: s.modelTransform ?? null,
|
|
123
|
+
ar: s.bbox.h > 0 ? Math.round((s.bbox.w / s.bbox.h) * 1000) / 1000 : 1,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function rasterize(s, root) {
|
|
127
|
+
const renderer = getPreviewRenderer();
|
|
128
|
+
if (!renderer)
|
|
129
|
+
return null;
|
|
130
|
+
const aspect = s.bbox.h > 0 ? s.bbox.w / s.bbox.h : 1;
|
|
131
|
+
const w = 256;
|
|
132
|
+
const h = Math.max(1, Math.round(w / Math.max(aspect, 0.2)));
|
|
133
|
+
const scene = new THREE.Scene();
|
|
134
|
+
const clone = root.clone(true);
|
|
135
|
+
applyModel3DTransform(clone, s);
|
|
136
|
+
scene.add(clone);
|
|
137
|
+
const light = new THREE.DirectionalLight(0xffffff, 1.1);
|
|
138
|
+
light.position.set(2, 3, 4);
|
|
139
|
+
scene.add(light);
|
|
140
|
+
scene.add(new THREE.AmbientLight(0xffffff, 0.45));
|
|
141
|
+
const cam = new THREE.PerspectiveCamera(35, w / h, 0.01, 100);
|
|
142
|
+
applyModel3DCamera(cam, s);
|
|
143
|
+
renderer.setSize(w, h, false);
|
|
144
|
+
renderer.render(scene, cam);
|
|
145
|
+
return renderer.domElement.toDataURL("image/png");
|
|
146
|
+
}
|
|
147
|
+
function getModel3DPreviewFrame(s, onReady) {
|
|
148
|
+
if (typeof document === "undefined" || !s.glbSrc)
|
|
149
|
+
return null;
|
|
150
|
+
const key = previewKey(s);
|
|
151
|
+
const hit = previewCache.get(key);
|
|
152
|
+
if (hit?.status === "ready")
|
|
153
|
+
return hit.img;
|
|
154
|
+
if (hit?.status === "failed")
|
|
155
|
+
return null;
|
|
156
|
+
if (hit?.status === "pending") {
|
|
157
|
+
if (onReady)
|
|
158
|
+
void hit.wait.then(() => onReady());
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const wait = loadGltfScene(s.glbSrc)
|
|
162
|
+
.then((root) => new Promise((resolve) => {
|
|
163
|
+
const url = rasterize(s, root);
|
|
164
|
+
if (!url) {
|
|
165
|
+
previewCache.set(key, { status: "failed" });
|
|
166
|
+
resolve();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const img = new Image();
|
|
170
|
+
img.onload = () => {
|
|
171
|
+
previewCache.set(key, { status: "ready", img });
|
|
172
|
+
onReady?.();
|
|
173
|
+
resolve();
|
|
174
|
+
};
|
|
175
|
+
img.onerror = () => {
|
|
176
|
+
previewCache.set(key, { status: "failed" });
|
|
177
|
+
resolve();
|
|
178
|
+
};
|
|
179
|
+
img.src = url;
|
|
180
|
+
}))
|
|
181
|
+
.catch(() => {
|
|
182
|
+
previewCache.set(key, { status: "failed" });
|
|
183
|
+
});
|
|
184
|
+
previewCache.set(key, { status: "pending", wait });
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function waitForModel3DFrames(timeoutMs = 8000) {
|
|
188
|
+
const pending = [];
|
|
189
|
+
for (const e of previewCache.values()) {
|
|
190
|
+
if (e.status === "pending")
|
|
191
|
+
pending.push(e.wait);
|
|
192
|
+
}
|
|
193
|
+
if (!pending.length)
|
|
194
|
+
return Promise.resolve();
|
|
195
|
+
return Promise.race([
|
|
196
|
+
Promise.all(pending).then(() => undefined),
|
|
197
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs)),
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.model3DDisplaySrc = model3DDisplaySrc;
|
|
4
|
+
exports.drawModel3DCanvas = drawModel3DCanvas;
|
|
5
|
+
exports.renderModel3DSvg = renderModel3DSvg;
|
|
6
|
+
const paint_canvas_1 = require("./paint-canvas");
|
|
7
|
+
const model3d_frame_1 = require("./model3d-frame");
|
|
8
|
+
function model3DDisplaySrc(s, onReady) {
|
|
9
|
+
const frame = s.glbSrc ? (0, model3d_frame_1.getModel3DPreviewFrame)(s, onReady) : null;
|
|
10
|
+
return frame?.src ?? s.rasterSrc;
|
|
11
|
+
}
|
|
12
|
+
function drawModel3DCanvas(ctx, s, onReady) {
|
|
13
|
+
const { x, y, w, h } = s.bbox;
|
|
14
|
+
const src = model3DDisplaySrc(s, onReady);
|
|
15
|
+
ctx.save();
|
|
16
|
+
if (src) {
|
|
17
|
+
const img = (0, paint_canvas_1.getImage)(src, onReady);
|
|
18
|
+
ctx.fillStyle = "#ffffff";
|
|
19
|
+
ctx.fillRect(x, y, w, h);
|
|
20
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
21
|
+
const scale = Math.max(w / img.naturalWidth, h / img.naturalHeight);
|
|
22
|
+
const dw = img.naturalWidth * scale;
|
|
23
|
+
const dh = img.naturalHeight * scale;
|
|
24
|
+
ctx.drawImage(img, x + (w - dw) / 2, y + (h - dh) / 2, dw, dh);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
ctx.fillStyle = "#e2e8f0";
|
|
29
|
+
ctx.fillRect(x, y, w, h);
|
|
30
|
+
ctx.fillStyle = "#475569";
|
|
31
|
+
ctx.font = `600 ${Math.max(11, Math.min(16, h * 0.12))}px system-ui, sans-serif`;
|
|
32
|
+
ctx.textAlign = "center";
|
|
33
|
+
ctx.textBaseline = "middle";
|
|
34
|
+
ctx.fillText(s.objectName ?? "3D Model", x + w / 2, y + h / 2);
|
|
35
|
+
}
|
|
36
|
+
ctx.strokeStyle = "#94a3b8";
|
|
37
|
+
ctx.lineWidth = 1;
|
|
38
|
+
ctx.strokeRect(x + 0.5, y + 0.5, w - 1, h - 1);
|
|
39
|
+
ctx.restore();
|
|
40
|
+
}
|
|
41
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
42
|
+
function renderModel3DSvg(container, s, onReady) {
|
|
43
|
+
while (container.firstChild)
|
|
44
|
+
container.removeChild(container.firstChild);
|
|
45
|
+
const { x, y, w, h } = s.bbox;
|
|
46
|
+
const src = model3DDisplaySrc(s, onReady);
|
|
47
|
+
if (src) {
|
|
48
|
+
const im = document.createElementNS(SVG_NS, "image");
|
|
49
|
+
im.setAttribute("x", String(x));
|
|
50
|
+
im.setAttribute("y", String(y));
|
|
51
|
+
im.setAttribute("width", String(w));
|
|
52
|
+
im.setAttribute("height", String(h));
|
|
53
|
+
im.setAttribute("href", src);
|
|
54
|
+
im.setAttributeNS("http://www.w3.org/1999/xlink", "href", src);
|
|
55
|
+
im.setAttribute("preserveAspectRatio", "xMidYMid slice");
|
|
56
|
+
container.appendChild(im);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const bg = document.createElementNS(SVG_NS, "rect");
|
|
60
|
+
bg.setAttribute("x", String(x));
|
|
61
|
+
bg.setAttribute("y", String(y));
|
|
62
|
+
bg.setAttribute("width", String(w));
|
|
63
|
+
bg.setAttribute("height", String(h));
|
|
64
|
+
bg.setAttribute("fill", "#e2e8f0");
|
|
65
|
+
bg.setAttribute("stroke", "#94a3b8");
|
|
66
|
+
container.appendChild(bg);
|
|
67
|
+
const label = document.createElementNS(SVG_NS, "text");
|
|
68
|
+
label.setAttribute("x", String(x + w / 2));
|
|
69
|
+
label.setAttribute("y", String(y + h / 2));
|
|
70
|
+
label.setAttribute("fill", "#475569");
|
|
71
|
+
label.setAttribute("font-size", String(Math.max(11, Math.min(16, h * 0.12))));
|
|
72
|
+
label.setAttribute("font-weight", "600");
|
|
73
|
+
label.setAttribute("text-anchor", "middle");
|
|
74
|
+
label.setAttribute("dominant-baseline", "middle");
|
|
75
|
+
label.textContent = s.objectName ?? "3D Model";
|
|
76
|
+
container.appendChild(label);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mountMorphTransitionLayers = mountMorphTransitionLayers;
|
|
4
|
+
const svg_backend_1 = require("./svg-backend");
|
|
5
|
+
function overlayBoxStyle(stage) {
|
|
6
|
+
return [
|
|
7
|
+
"position:absolute",
|
|
8
|
+
`left:${stage.offsetLeft}px`,
|
|
9
|
+
`top:${stage.offsetTop}px`,
|
|
10
|
+
`width:${stage.style.width || `${stage.clientWidth}px`}`,
|
|
11
|
+
`height:${stage.style.height || `${stage.clientHeight}px`}`,
|
|
12
|
+
"pointer-events:none",
|
|
13
|
+
"overflow:hidden",
|
|
14
|
+
].join(";");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Mount SVG shape layers for morph (FLIP) when the live stage lacks per-shape DOM
|
|
18
|
+
* ids (canvas / hybrid bitmap segments). Pure-SVG stages only need the ghost overlay.
|
|
19
|
+
*/
|
|
20
|
+
function mountMorphTransitionLayers(stageWrap, stage, prevScene, curScene, opts = {}) {
|
|
21
|
+
const ghostZ = opts.ghostZIndex ?? 1000;
|
|
22
|
+
const stageZ = opts.stageZIndex ?? 1001;
|
|
23
|
+
const ghostWrap = document.createElement("div");
|
|
24
|
+
ghostWrap.style.cssText = `${overlayBoxStyle(stage)};z-index:${ghostZ}`;
|
|
25
|
+
if (opts.chromeBg)
|
|
26
|
+
ghostWrap.style.background = opts.chromeBg;
|
|
27
|
+
stageWrap.appendChild(ghostWrap);
|
|
28
|
+
const ghostBe = new svg_backend_1.SvgBackend(prevScene, ghostWrap, { transparent: true });
|
|
29
|
+
ghostBe.flush();
|
|
30
|
+
const needsStageOverlay = !stage.querySelector("[data-id],[data-shape-id]");
|
|
31
|
+
let stageOverlay = null;
|
|
32
|
+
let stageBe = null;
|
|
33
|
+
let target = stage;
|
|
34
|
+
let hidStage = false;
|
|
35
|
+
if (needsStageOverlay) {
|
|
36
|
+
stageOverlay = document.createElement("div");
|
|
37
|
+
stageOverlay.style.cssText = `${overlayBoxStyle(stage)};z-index:${stageZ}`;
|
|
38
|
+
stageWrap.appendChild(stageOverlay);
|
|
39
|
+
stageBe = new svg_backend_1.SvgBackend(curScene, stageOverlay, { transparent: true });
|
|
40
|
+
stageBe.flush();
|
|
41
|
+
target = stageOverlay;
|
|
42
|
+
stage.style.visibility = "hidden";
|
|
43
|
+
hidStage = true;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ghost: ghostWrap,
|
|
47
|
+
target,
|
|
48
|
+
cleanup() {
|
|
49
|
+
ghostBe.destroy();
|
|
50
|
+
stageBe?.destroy();
|
|
51
|
+
ghostWrap.remove();
|
|
52
|
+
stageOverlay?.remove();
|
|
53
|
+
if (hidStage)
|
|
54
|
+
stage.style.visibility = "";
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.affMul = affMul;
|
|
4
|
+
exports.nodeAffine = nodeAffine;
|
|
5
|
+
exports.accumulatedAffine = accumulatedAffine;
|
|
6
|
+
exports.cssAffineForBox = cssAffineForBox;
|
|
7
|
+
exports.chainOpacity = chainOpacity;
|
|
8
|
+
exports.chainHidden = chainHidden;
|
|
9
|
+
function affMul(m, n) {
|
|
10
|
+
return {
|
|
11
|
+
a: m.a * n.a + m.c * n.b,
|
|
12
|
+
b: m.b * n.a + m.d * n.b,
|
|
13
|
+
c: m.a * n.c + m.c * n.d,
|
|
14
|
+
d: m.b * n.c + m.d * n.d,
|
|
15
|
+
e: m.a * n.e + m.c * n.f + m.e,
|
|
16
|
+
f: m.b * n.e + m.d * n.f + m.f,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function affTranslate(x, y) {
|
|
20
|
+
return { a: 1, b: 0, c: 0, d: 1, e: x, f: y };
|
|
21
|
+
}
|
|
22
|
+
/** 单个节点的 SVG 变换:先绕 bbox 中心旋转缩放,再平移 tx/ty。 */
|
|
23
|
+
function nodeAffine(node) {
|
|
24
|
+
const s = node.shape;
|
|
25
|
+
const cx = s.bbox.x + s.bbox.w / 2;
|
|
26
|
+
const cy = s.bbox.y + s.bbox.h / 2;
|
|
27
|
+
const flipX = s.transform?.scaleX ?? 1;
|
|
28
|
+
const flipY = s.transform?.scaleY ?? 1;
|
|
29
|
+
const sx = node.scale * node.scaleX * node.widthScale * (flipX < 0 ? -1 : 1);
|
|
30
|
+
const sy = node.scale * node.scaleY * node.heightScale * (flipY < 0 ? -1 : 1);
|
|
31
|
+
const rad = ((node.rotation || 0) * Math.PI) / 180;
|
|
32
|
+
const cos = Math.cos(rad);
|
|
33
|
+
const sin = Math.sin(rad);
|
|
34
|
+
const a = cos * sx;
|
|
35
|
+
const b = sin * sx;
|
|
36
|
+
const c = -sin * sy;
|
|
37
|
+
const d = cos * sy;
|
|
38
|
+
const e = cx - (a * cx + c * cy) + node.tx;
|
|
39
|
+
const f = cy - (b * cx + d * cy) + node.ty;
|
|
40
|
+
return { a, b, c, d, e, f };
|
|
41
|
+
}
|
|
42
|
+
/** 从最外层祖先乘到自身。幻灯片根是恒等变换,乘进去不改变结果。 */
|
|
43
|
+
function accumulatedAffine(node) {
|
|
44
|
+
const chain = [];
|
|
45
|
+
let cur = node;
|
|
46
|
+
while (cur) {
|
|
47
|
+
chain.push(cur);
|
|
48
|
+
cur = cur.parent;
|
|
49
|
+
}
|
|
50
|
+
let m = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
51
|
+
for (let i = chain.length - 1; i >= 0; i--)
|
|
52
|
+
m = affMul(m, nodeAffine(chain[i]));
|
|
53
|
+
return m;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* wrapper 已放在 slide 坐标 (originX, originY)。CSS transform-origin 为 0 0 时,
|
|
57
|
+
* 用这个矩阵才能让视觉位置等于 accumulatedAffine。
|
|
58
|
+
*/
|
|
59
|
+
function cssAffineForBox(m, originX, originY) {
|
|
60
|
+
return affMul(affMul(affTranslate(-originX, -originY), m), affTranslate(originX, originY));
|
|
61
|
+
}
|
|
62
|
+
function chainOpacity(node) {
|
|
63
|
+
let alpha = 1;
|
|
64
|
+
let cur = node;
|
|
65
|
+
while (cur) {
|
|
66
|
+
alpha *= cur.opacity;
|
|
67
|
+
cur = cur.parent;
|
|
68
|
+
}
|
|
69
|
+
return alpha;
|
|
70
|
+
}
|
|
71
|
+
function chainHidden(node) {
|
|
72
|
+
let cur = node;
|
|
73
|
+
while (cur) {
|
|
74
|
+
if ((cur.visibleOverride ?? cur.shape.visible) === false)
|
|
75
|
+
return true;
|
|
76
|
+
cur = cur.parent;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|