@sliiu/present-transition-fx 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.
Files changed (48) hide show
  1. package/COPYRIGHT +5 -0
  2. package/LICENSE +661 -0
  3. package/README.md +8 -2
  4. package/THIRD-PARTY-NOTICES.md +221 -0
  5. package/dist/cjs/2d.js +487 -0
  6. package/dist/cjs/3d.js +566 -0
  7. package/dist/cjs/geometry.js +141 -0
  8. package/dist/cjs/index.js +98 -0
  9. package/dist/cjs/morph-audit.js +603 -0
  10. package/dist/cjs/morph-match.js +103 -0
  11. package/dist/cjs/morph-path.js +649 -0
  12. package/dist/cjs/morph.js +223 -0
  13. package/dist/cjs/package.json +1 -0
  14. package/dist/cjs/registry.js +130 -0
  15. package/dist/cjs/snapshot.js +420 -0
  16. package/dist/cjs/twin.js +88 -0
  17. package/dist/cjs/types.js +2 -0
  18. package/dist/cjs/util.js +50 -0
  19. package/dist/cjs/webgl.js +271 -0
  20. package/dist/esm/2d.js +454 -0
  21. package/dist/esm/3d.js +538 -0
  22. package/dist/esm/geometry.js +127 -0
  23. package/dist/esm/index.js +6 -0
  24. package/dist/esm/morph-audit.js +558 -0
  25. package/dist/esm/morph-match.js +97 -0
  26. package/dist/esm/morph-path.js +636 -0
  27. package/dist/esm/morph.js +219 -0
  28. package/dist/esm/registry.js +91 -0
  29. package/dist/esm/snapshot.js +414 -0
  30. package/dist/esm/twin.js +84 -0
  31. package/dist/esm/types.js +1 -0
  32. package/dist/esm/util.js +43 -0
  33. package/dist/esm/webgl.js +231 -0
  34. package/dist/types/2d.d.ts +40 -0
  35. package/dist/types/3d.d.ts +38 -0
  36. package/dist/types/geometry.d.ts +38 -0
  37. package/dist/types/index.d.ts +7 -0
  38. package/dist/types/morph-audit.d.ts +127 -0
  39. package/dist/types/morph-match.d.ts +28 -0
  40. package/dist/types/morph-path.d.ts +37 -0
  41. package/dist/types/morph.d.ts +2 -0
  42. package/dist/types/registry.d.ts +6 -0
  43. package/dist/types/snapshot.d.ts +19 -0
  44. package/dist/types/twin.d.ts +2 -0
  45. package/dist/types/types.d.ts +13 -0
  46. package/dist/types/util.d.ts +12 -0
  47. package/dist/types/webgl.d.ts +5 -0
  48. package/package.json +36 -4
@@ -0,0 +1,231 @@
1
+ import * as fx2d from "./2d.js";
2
+ import * as fx3d from "./3d.js";
3
+ import { snapshotElement } from "./snapshot.js";
4
+ import { ms } from "./util.js";
5
+ function compile(gl, type, src) {
6
+ const sh = gl.createShader(type);
7
+ if (!sh)
8
+ return null;
9
+ gl.shaderSource(sh, src);
10
+ gl.compileShader(sh);
11
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
12
+ gl.deleteShader(sh);
13
+ return null;
14
+ }
15
+ return sh;
16
+ }
17
+ function program(gl, vs, fs) {
18
+ const v = compile(gl, gl.VERTEX_SHADER, vs);
19
+ const f = compile(gl, gl.FRAGMENT_SHADER, fs);
20
+ if (!v || !f)
21
+ return null;
22
+ const p = gl.createProgram();
23
+ if (!p)
24
+ return null;
25
+ gl.attachShader(p, v);
26
+ gl.attachShader(p, f);
27
+ gl.bindAttribLocation(p, 0, "aPos");
28
+ gl.linkProgram(p);
29
+ if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
30
+ gl.deleteProgram(p);
31
+ return null;
32
+ }
33
+ return p;
34
+ }
35
+ function uploadTexture(gl, canvas) {
36
+ const tex = gl.createTexture();
37
+ if (!tex)
38
+ return null;
39
+ gl.bindTexture(gl.TEXTURE_2D, tex);
40
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
41
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
42
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
43
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
44
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
45
+ try {
46
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
47
+ }
48
+ catch {
49
+ // 画布被跨域资源弄脏时浏览器抛 SecurityError。删掉纹理,让切换改走 CSS。
50
+ gl.deleteTexture(tex);
51
+ return null;
52
+ }
53
+ return tex;
54
+ }
55
+ const VS = `attribute vec2 aPos; varying vec2 vUv; void main(){ vUv = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;
56
+ const FS_PAGE_CURL = `
57
+ precision highp float;
58
+ varying vec2 vUv;
59
+ uniform sampler2D uFrom;
60
+ uniform sampler2D uTo;
61
+ uniform float uProgress;
62
+ const float MIN_AMOUNT = -0.16;
63
+ const float MAX_AMOUNT = 1.5;
64
+ float amount() { return uProgress * (MAX_AMOUNT - MIN_AMOUNT) + MIN_AMOUNT; }
65
+ const float PI = 3.141592653589793;
66
+ const float scale = 512.0;
67
+ const float sharpness = 3.0;
68
+
69
+ float cylinder(float r) {
70
+ float amt = amount();
71
+ return atan(r, 1.0 - amt);
72
+ }
73
+
74
+ void main() {
75
+ vec2 uv = vUv;
76
+ float a = amount();
77
+ float dist = (uv.x - 1.0) * (uv.x - 1.0) + (uv.y - 1.0) * (uv.y - 1.0);
78
+ vec2 fromCoord = uv;
79
+ vec4 colorFrom = texture2D(uFrom, fromCoord);
80
+ vec4 colorTo = texture2D(uTo, uv);
81
+ float radius = 0.1 + a * 0.4;
82
+ vec2 center = vec2(1.0 - a, 1.0 - a);
83
+ float d = length(uv - center);
84
+ float curl = smoothstep(radius, radius + 0.05, d);
85
+ vec4 res = mix(colorTo, colorFrom, curl);
86
+ // Shadow under the curl
87
+ float shadow = smoothstep(radius - 0.08, radius, d) * (1.0 - smoothstep(radius, radius + 0.04, d));
88
+ res.rgb *= 1.0 - 0.35 * shadow * (1.0 - uProgress);
89
+ gl_FragColor = res;
90
+ }`;
91
+ const FS_CUBE = `
92
+ precision highp float;
93
+ varying vec2 vUv;
94
+ uniform sampler2D uFrom;
95
+ uniform sampler2D uTo;
96
+ uniform float uProgress;
97
+ uniform float uDir;
98
+ void main() {
99
+ float p = uProgress;
100
+ vec2 uv = vUv;
101
+ float face = uv.x + (uDir > 0.0 ? -p : p);
102
+ if (face < 0.0 || face > 1.0) {
103
+ vec2 to = vec2(face < 0.0 ? face + 1.0 : face - 1.0, uv.y);
104
+ gl_FragColor = texture2D(uTo, to);
105
+ } else {
106
+ gl_FragColor = texture2D(uFrom, vec2(face, uv.y));
107
+ }
108
+ }`;
109
+ const FS_DISSOLVE = `
110
+ precision highp float;
111
+ varying vec2 vUv;
112
+ uniform sampler2D uFrom;
113
+ uniform sampler2D uTo;
114
+ uniform float uProgress;
115
+ float rand(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); }
116
+ void main(){
117
+ float n = rand(vUv);
118
+ gl_FragColor = (n < uProgress) ? texture2D(uTo, vUv) : texture2D(uFrom, vUv);
119
+ }`;
120
+ async function runWebGLTransition(ctx, fragment, uniforms = () => undefined) {
121
+ const w = ctx.stageSize.w;
122
+ const h = ctx.stageSize.h;
123
+ const cache = new Map();
124
+ const [from, to] = await Promise.all([
125
+ snapshotElement(ctx.ghost, w, h, cache),
126
+ snapshotElement(ctx.stage, w, h, cache),
127
+ ]);
128
+ if (!from || !to)
129
+ return false;
130
+ const canvas = document.createElement("canvas");
131
+ canvas.width = w;
132
+ canvas.height = h;
133
+ Object.assign(canvas.style, {
134
+ position: "absolute",
135
+ left: ctx.stage.style.left || `${ctx.stage.offsetLeft}px`,
136
+ top: ctx.stage.style.top || `${ctx.stage.offsetTop}px`,
137
+ width: ctx.stage.style.width || `${w}px`,
138
+ height: ctx.stage.style.height || `${h}px`,
139
+ transform: ctx.stage.style.transform || "",
140
+ transformOrigin: ctx.stage.style.transformOrigin || "center center",
141
+ zIndex: "3",
142
+ pointerEvents: "none",
143
+ });
144
+ const ghostVis = ctx.ghost.style.visibility;
145
+ const stageVis = ctx.stage.style.visibility;
146
+ let gl = null;
147
+ let succeeded = false;
148
+ ctx.stage.parentElement?.appendChild(canvas);
149
+ try {
150
+ gl = canvas.getContext("webgl", { premultipliedAlpha: true });
151
+ if (!gl)
152
+ return false;
153
+ const prog = program(gl, VS, fragment);
154
+ if (!prog)
155
+ return false;
156
+ const buf = gl.createBuffer();
157
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
158
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
159
+ gl.enableVertexAttribArray(0);
160
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
161
+ const tFrom = uploadTexture(gl, from.canvas);
162
+ const tTo = uploadTexture(gl, to.canvas);
163
+ if (!tFrom || !tTo)
164
+ return false;
165
+ gl.useProgram(prog);
166
+ gl.activeTexture(gl.TEXTURE0);
167
+ gl.bindTexture(gl.TEXTURE_2D, tFrom);
168
+ gl.uniform1i(gl.getUniformLocation(prog, "uFrom"), 0);
169
+ gl.activeTexture(gl.TEXTURE1);
170
+ gl.bindTexture(gl.TEXTURE_2D, tTo);
171
+ gl.uniform1i(gl.getUniformLocation(prog, "uTo"), 1);
172
+ const uP = gl.getUniformLocation(prog, "uProgress");
173
+ uniforms(gl, prog);
174
+ ctx.ghost.style.visibility = "hidden";
175
+ ctx.stage.style.visibility = "hidden";
176
+ const duration = ms(ctx);
177
+ const start = performance.now();
178
+ const glCtx = gl;
179
+ await new Promise((resolve) => {
180
+ const step = () => {
181
+ try {
182
+ const t = Math.min(1, (performance.now() - start) / Math.max(1, duration));
183
+ glCtx.viewport(0, 0, canvas.width, canvas.height);
184
+ glCtx.clearColor(0, 0, 0, 0);
185
+ glCtx.clear(glCtx.COLOR_BUFFER_BIT);
186
+ glCtx.uniform1f(uP, t);
187
+ glCtx.drawArrays(glCtx.TRIANGLE_STRIP, 0, 4);
188
+ if (t < 1)
189
+ requestAnimationFrame(step);
190
+ else
191
+ resolve();
192
+ }
193
+ catch {
194
+ resolve();
195
+ }
196
+ };
197
+ requestAnimationFrame(step);
198
+ });
199
+ succeeded = true;
200
+ return true;
201
+ }
202
+ catch {
203
+ return false;
204
+ }
205
+ finally {
206
+ canvas.remove();
207
+ ctx.stage.style.visibility = stageVis;
208
+ if (!succeeded)
209
+ ctx.ghost.style.visibility = ghostVis;
210
+ gl?.getExtension("WEBGL_lose_context")?.loseContext();
211
+ }
212
+ }
213
+ export const pageCurlWebGL = async (ctx) => {
214
+ const ok = await runWebGLTransition(ctx, FS_PAGE_CURL);
215
+ if (!ok)
216
+ await fx3d.pageCurl(ctx);
217
+ };
218
+ export const cubeWebGL = async (ctx) => {
219
+ const dir = String(ctx.params.dir ?? "left");
220
+ const ok = await runWebGLTransition(ctx, FS_CUBE, (gl, p) => {
221
+ gl.uniform1f(gl.getUniformLocation(p, "uDir"), dir === "left" || dir === "up" ? 1 : -1);
222
+ });
223
+ if (!ok)
224
+ await fx3d.cube(ctx);
225
+ };
226
+ export const boxWebGL = (ctx) => fx3d.box(ctx);
227
+ export const dissolveWebGL = async (ctx) => {
228
+ const ok = await runWebGLTransition(ctx, FS_DISSOLVE);
229
+ if (!ok)
230
+ await fx2d.dissolve(ctx);
231
+ };
@@ -0,0 +1,40 @@
1
+ import type { TransitionFactory } from "./types.js";
2
+ export declare const fade: TransitionFactory;
3
+ export declare const cut: TransitionFactory;
4
+ export declare const push: TransitionFactory;
5
+ export declare const cover: TransitionFactory;
6
+ export declare const uncover: TransitionFactory;
7
+ export declare const wipe: TransitionFactory;
8
+ export declare const split: TransitionFactory;
9
+ export declare const zoom: TransitionFactory;
10
+ export declare const blinds: TransitionFactory;
11
+ export declare const checker: TransitionFactory;
12
+ export declare const randomBar: TransitionFactory;
13
+ /** 旧页切成小格,按固定乱序一块块消失。着色器快照失败时用这条。 */
14
+ export declare const dissolve: TransitionFactory;
15
+ export declare const newsflash: TransitionFactory;
16
+ export declare const wheel: TransitionFactory;
17
+ export declare const wheelReverse: TransitionFactory;
18
+ export declare const circle: TransitionFactory;
19
+ export declare const diamond: TransitionFactory;
20
+ export declare const plus: TransitionFactory;
21
+ export declare const random: TransitionFactory;
22
+ export declare const fall: TransitionFactory;
23
+ export declare const reveal: TransitionFactory;
24
+ export declare const flash: TransitionFactory;
25
+ /** 菱形或六边形沿方向扫过,同时旧页闪几下。pattern 来自 OOXML hexagon / diamond。 */
26
+ export declare const glitter: TransitionFactory;
27
+ /** 疏六边形从中心铺开,格子之间留缝,不闪。 */
28
+ export declare const honeycomb: TransitionFactory;
29
+ /** 斜向细条撕开。向外从中心铺开新页,向内让旧页的细条收到中心。 */
30
+ export declare const shred: TransitionFactory;
31
+ /** 旧页切成小块飞散,新页从稍大缩回。 */
32
+ export declare const fracture: TransitionFactory;
33
+ /** 旧页向左掀起,一道高光扫过新页。 */
34
+ export declare const prestige: TransitionFactory;
35
+ /** 梳齿交错抽走旧页。水平是上下两排齿,垂直是左右两排齿。 */
36
+ export declare const comb: TransitionFactory;
37
+ /** 斜向宽带从指定角扫过。 */
38
+ export declare const strips: TransitionFactory;
39
+ /** 从中心张开一块扇形,和整圆轮子分开。 */
40
+ export declare const wedge: TransitionFactory;
@@ -0,0 +1,38 @@
1
+ import type { TransitionFactory } from "./types.js";
2
+ export declare const cube: TransitionFactory;
3
+ /** 三棱柱转 120°,和立方体的 90° 分开。左右绕 Y,上下绕 X。 */
4
+ export declare const prism: TransitionFactory;
5
+ /** 走进盒子:新页从内侧转出来,旧页往外让开。和立方体外表面对转分开。 */
6
+ export declare const box: TransitionFactory;
7
+ export declare const gallery: TransitionFactory;
8
+ export declare const orbit: TransitionFactory;
9
+ export declare const conveyor: TransitionFactory;
10
+ /** 旧页对开。垂直沿左右铰链转开,水平沿上下铰链转开。 */
11
+ export declare const doors: TransitionFactory;
12
+ /** 窗:两扇平着滑开,不绕铰链转。和门分开。 */
13
+ export declare const window: TransitionFactory;
14
+ export declare const pageCurl: TransitionFactory;
15
+ /** p15 pageCurlSingle:只掀起一角。整页卷曲走 pageCurl 的书页翻边。 */
16
+ export declare const pageCurlSingle: TransitionFactory;
17
+ /** p15 wind:旧页切成细条,顺着风向依次吹出。向右 / 向下镜像。 */
18
+ export declare const wind: TransitionFactory;
19
+ /** p15 crush:旧页朝选定的边压成一条线。 */
20
+ export declare const crush: TransitionFactory;
21
+ export declare const airplane: TransitionFactory;
22
+ /** 对折两次再飞走。向右时折痕在右缘。和翻页的单次卷起分开。 */
23
+ export declare const origami: TransitionFactory;
24
+ export declare const flythrough: TransitionFactory;
25
+ export declare const rotate: TransitionFactory;
26
+ /** 绕画面外侧的轴转过去,像摩天轮的一格,不是原地翻面。 */
27
+ export declare const ferris: TransitionFactory;
28
+ export declare const peelOff: TransitionFactory;
29
+ /** 卡片翻面:转到 90° 时只看见棱,没有旋转那种半透明交叠。 */
30
+ export declare const flip: TransitionFactory;
31
+ /** 两页左右错身换位,带一点倾斜。 */
32
+ export declare const switchFx: TransitionFactory;
33
+ export declare const drape: TransitionFactory;
34
+ /** 帘幕:多条竖褶从中缝向两边收走。 */
35
+ export declare const curtains: TransitionFactory;
36
+ export declare const ripple: TransitionFactory;
37
+ export declare const warp: TransitionFactory;
38
+ export declare const vortex: TransitionFactory;
@@ -0,0 +1,38 @@
1
+ export type SplitMotion = {
2
+ kind: "expand";
3
+ clip: string;
4
+ } | {
5
+ kind: "meet";
6
+ axis: "x" | "y";
7
+ };
8
+ /** orient=vert 左右分开,horz 上下分开。dir=in 从两侧收到中间,其余从中间向外展开。 */
9
+ export declare function splitMotion(orient: unknown, dir: unknown): SplitMotion;
10
+ /** 面板上的四档(中央向左右展开 / 左右向中央收缩 / …)和 OOXML orient+dir 互转。 */
11
+ export declare function splitToken(orient: unknown, dir: unknown): string;
12
+ export declare function splitParamsFromToken(token: string): {
13
+ orient: "horz" | "vert";
14
+ dir: "in" | "out";
15
+ };
16
+ /** OOXML CT_WheelTransition 的 spokes。缺省 4。界面提供 1/2/3/4/8,其它 1–8 的整数照播。 */
17
+ export declare function wheelSpokes(pattern: unknown): number;
18
+ /** 固定点数的轮辐多边形,portion 0 收成射线,1 铺满画面。reverse 往反方向张开。 */
19
+ export declare function wheelPolygon(spokes: number, portion: number, reverse: boolean): string;
20
+ /** 条纹沿 dir 扫过。dir 是运动方向(topLeft = 朝左上,即从右下进来)。三点同点数,方便插值。 */
21
+ export declare function stripsClips(dir: string): [string, string, string];
22
+ /** 涟漪收缩的圆心。角方向用面板的 topLeft / topRight / bottomLeft / bottomRight。 */
23
+ export declare function rippleAt(dir: string): string;
24
+ /** 闪耀的菱形 / 六边形,以及蜂巢的疏六边形。mask 里黑色是露出的部分。 */
25
+ export declare function cellMask(kind: string): string;
26
+ /** 面板上「方向 · 图案」和 params.dir + params.pattern 互转。缺省向左、菱形。 */
27
+ export declare function glitterToken(dir: unknown, pattern: unknown): string;
28
+ export declare function glitterParamsFromToken(token: string): {
29
+ dir: "left" | "right" | "up" | "down";
30
+ pattern: "diamond" | "hexagon";
31
+ };
32
+ /** 闪耀扫过的起点和终点。向左从左缘扫到右缘,其余镜像。 */
33
+ export declare function glitterSweep(dir: string): {
34
+ from: string;
35
+ to: string;
36
+ };
37
+ /** 剥离后仍平贴的那一块。towardRight 从左下角往右揭,否则从右下角往左揭。amount 0 整页,1 几乎揭完。 */
38
+ export declare function peelFlatClip(towardRight: boolean, amount: number): string;
@@ -0,0 +1,7 @@
1
+ export { alignClosedPolylines, ellipsePathFromSize, flattenPathToPoints, makeMorphPathTween, morphPathCompatible, morphPathSignature, linePathFromPoints, parsePathCommands, pathToAbsolute, polygonPointsToPath, rectPathFromSize, } from "./morph-path.js";
2
+ export { auditMorphDeck, auditMorphSlides, averageMorphIssuesPerPair, averageMorphReadinessPercent, averagePathMorphCoveragePercent, collectMorphCandidatesFromSlide, collectUnmatchedMorphShapesInDeck, countMorphPairsClean, countMorphPairsWithIssues, exportMorphAuditText, exportMorphDeckAuditText, exportMorphDeckCompactText, exportMorphDeckFilteredCompactText, exportMorphDeckIssuesCompactText, exportMorphDeckReadinessText, exportMorphIssuesCompactText, exportMorphPairCoverageText, exportMorphPairCompactText, exportMorphReadinessText, exportMorphUnmatchedShapesText, filterMorphDeckEntries, findFirstIssueMorphPair, findHighestPathCoverageMorphPair, findLowestPathCoverageMorphPair, findMostIssueMorphPair, findStrongestMorphPair, findWeakestMorphPair, formatMorphDeckChromeLines, formatMorphDeckReadinessLine, formatMorphDeckStatsLine, formatMorphLowestPathLine, formatMorphMatchReasonBreakdown, formatMorphPairCoverageBreakdownLine, formatMorphReadinessLine, formatMorphReadinessScoreLine, formatMorphReadinessSummary, morphCandidateFromShape, morphIssueCount, morphPairsWithIssues, morphReadinessPercent, countMorphDeckIssues, pathMorphCoveragePercent, summarizeMorphMatchReasons, } from "./morph-audit.js";
3
+ export type { MorphAuditFilter, MorphAuditPair, MorphAuditReport, MorphDeckAuditEntry, MorphDeckEntryFilter, MorphMatchReasonKind, MorphUnmatchedShapeRef, } from "./morph-audit.js";
4
+ export { matchMorphShapePairs, morphGeometryHash, morphMatchReason, morphSimilarity, } from "./morph-match.js";
5
+ export * from "./types.js";
6
+ export { cellMask, glitterParamsFromToken, glitterSweep, glitterToken, peelFlatClip, rippleAt, splitMotion, splitParamsFromToken, splitToken, stripsClips, wheelPolygon, wheelSpokes, } from "./geometry.js";
7
+ export { getTransition, listTransitions, registerTransition, runTransition, } from "./registry.js";
@@ -0,0 +1,127 @@
1
+ import type { Presentation, Shape, Slide } from "@sliiu/present-core";
2
+ import { morphMatchReason, type MorphShapeCandidate } from "./morph-match.js";
3
+ export type MorphAuditFilter = "all" | "path" | "flip" | "issues";
4
+ export interface MorphAuditPair {
5
+ ghost: MorphShapeCandidate;
6
+ stage: MorphShapeCandidate;
7
+ pathMorph: boolean;
8
+ }
9
+ export interface MorphAuditReport {
10
+ ghostCount: number;
11
+ stageCount: number;
12
+ matched: number;
13
+ pathMorphCount: number;
14
+ flipOnlyCount: number;
15
+ unmatchedStage: MorphShapeCandidate[];
16
+ unmatchedGhost: MorphShapeCandidate[];
17
+ pairs: MorphAuditPair[];
18
+ }
19
+ /** Build a morph candidate from an in-memory shape (slide coordinates). */
20
+ export declare function morphCandidateFromShape(shape: Shape): MorphShapeCandidate | null;
21
+ /** Collect morph candidates from all visible layers on a slide (top-level shapes, as the player matches them). */
22
+ export declare function collectMorphCandidatesFromSlide(slide: Slide): MorphShapeCandidate[];
23
+ /** Analyze morph readiness between two slides (current → next). */
24
+ export declare function auditMorphSlides(fromSlide: Slide, toSlide: Slide): MorphAuditReport;
25
+ export declare function formatMorphReadinessSummary(report: MorphAuditReport): string;
26
+ /** 0–100 morph readiness score for the slide pair. */
27
+ export declare function morphReadinessPercent(report: MorphAuditReport): number;
28
+ export type MorphMatchReasonKind = ReturnType<typeof morphMatchReason>;
29
+ /** Count matched pairs by match reason (id / name / placeholder / geometry). */
30
+ export declare function summarizeMorphMatchReasons(report: MorphAuditReport): Partial<Record<MorphMatchReasonKind, number>>;
31
+ export declare function formatMorphMatchReasonBreakdown(report: MorphAuditReport): string;
32
+ /** Unmatched shapes + FLIP-only pairs (actionable morph issues). */
33
+ export declare function morphIssueCount(report: MorphAuditReport): number;
34
+ export declare function averageMorphReadinessPercent(entries: readonly MorphDeckAuditEntry[]): number;
35
+ export declare function findWeakestMorphPair(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry | null;
36
+ export declare function findStrongestMorphPair(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry | null;
37
+ export declare function formatMorphReadinessScoreLine(report: MorphAuditReport): string;
38
+ /** Percent of matched pairs that use path morph (0–100). */
39
+ export declare function pathMorphCoveragePercent(report: MorphAuditReport): number;
40
+ export declare function countMorphDeckIssues(entries: readonly MorphDeckAuditEntry[]): number;
41
+ export declare function morphPairsWithIssues(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry[];
42
+ /** Average path-morph coverage across adjacent slide pairs (0–100). */
43
+ export declare function averagePathMorphCoveragePercent(entries: readonly MorphDeckAuditEntry[]): number;
44
+ /** Slide pair with the most morph issues (ties → lowest from index). */
45
+ export declare function findMostIssueMorphPair(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry | null;
46
+ /** Slide pair with the highest path-morph coverage (ties → lowest from index). */
47
+ export declare function findHighestPathCoverageMorphPair(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry | null;
48
+ /** One-line morph readiness for chips and compact copy. */
49
+ export declare function formatMorphReadinessLine(report: MorphAuditReport): string;
50
+ /** Labeled readiness export for a single slide pair. */
51
+ export declare function exportMorphReadinessText(report: MorphAuditReport, opts?: {
52
+ fromSlide?: number;
53
+ toSlide?: number;
54
+ }): string;
55
+ /** One-line deck morph readiness (stats + average path coverage). */
56
+ export declare function formatMorphDeckReadinessLine(pres: Pick<Presentation, "slides">): string;
57
+ /** Deck morph lines for the developer panel. Audits the deck once. */
58
+ export declare function formatMorphDeckChromeLines(pres: Pick<Presentation, "slides">): {
59
+ stats: string;
60
+ readiness: string;
61
+ pairCoverage: string;
62
+ lowestPath: string;
63
+ };
64
+ /** Labeled deck readiness export for copy. */
65
+ export declare function exportMorphDeckReadinessText(pres: Pick<Presentation, "slides">): string;
66
+ /** Count of adjacent slide pairs with zero morph issues. */
67
+ export declare function countMorphPairsClean(entries: readonly MorphDeckAuditEntry[]): number;
68
+ /** Count of adjacent slide pairs with at least one morph issue. */
69
+ export declare function countMorphPairsWithIssues(entries: readonly MorphDeckAuditEntry[]): number;
70
+ /** Average morph issues per adjacent slide pair (0 if no pairs). */
71
+ export declare function averageMorphIssuesPerPair(entries: readonly MorphDeckAuditEntry[]): number;
72
+ /** First slide pair (lowest from index) with morph issues. */
73
+ export declare function findFirstIssueMorphPair(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry | null;
74
+ /** Slide pair with the lowest path-morph coverage among pairs with matches (ties → lowest from index). */
75
+ export declare function findLowestPathCoverageMorphPair(entries: readonly MorphDeckAuditEntry[]): MorphDeckAuditEntry | null;
76
+ /** One-line pair coverage breakdown (clean / issues / avg issues). */
77
+ export declare function formatMorphPairCoverageBreakdownLine(pres: Pick<Presentation, "slides">): string;
78
+ /** One-line lowest path-coverage pair summary. */
79
+ export declare function formatMorphLowestPathLine(pres: Pick<Presentation, "slides">): string;
80
+ export interface MorphUnmatchedShapeRef {
81
+ slideIndex: number;
82
+ direction: "incoming" | "outgoing";
83
+ pairFromIndex: number;
84
+ id: string;
85
+ name: string;
86
+ }
87
+ /** Unmatched morph shapes across all adjacent slide pairs. */
88
+ export declare function collectUnmatchedMorphShapesInDeck(pres: Pick<Presentation, "slides">): MorphUnmatchedShapeRef[];
89
+ /** Plain-text report of unmatched morph shapes in the deck. */
90
+ export declare function exportMorphUnmatchedShapesText(pres: Pick<Presentation, "slides">): string;
91
+ /** Labeled pair-coverage export for copy. */
92
+ export declare function exportMorphPairCoverageText(pres: Pick<Presentation, "slides">): string;
93
+ export type MorphDeckEntryFilter = "all" | "issues" | "path" | "clean";
94
+ export declare function filterMorphDeckEntries(entries: readonly MorphDeckAuditEntry[], filter?: MorphDeckEntryFilter): MorphDeckAuditEntry[];
95
+ /** Compact deck pair list filtered by issues / path / clean. */
96
+ export declare function exportMorphDeckFilteredCompactText(pres: Pick<Presentation, "slides">, filter?: MorphDeckEntryFilter): string;
97
+ /** One-line deck morph stats for chips and compact copy. */
98
+ export declare function formatMorphDeckStatsLine(pres: Pick<Presentation, "slides">): string;
99
+ /** Compact single-pair morph summary (one-liners). */
100
+ export declare function exportMorphPairCompactText(report: MorphAuditReport, opts?: {
101
+ fromSlide?: number;
102
+ toSlide?: number;
103
+ }): string;
104
+ /** Comma-separated deck pairs that have morph issues. */
105
+ export declare function exportMorphDeckIssuesCompactText(pres: Pick<Presentation, "slides">): string;
106
+ export interface MorphDeckAuditEntry {
107
+ fromIndex: number;
108
+ toIndex: number;
109
+ report: MorphAuditReport;
110
+ }
111
+ /** Audit morph readiness for every adjacent slide pair in a deck. */
112
+ export declare function auditMorphDeck(pres: Pick<Presentation, "slides">): MorphDeckAuditEntry[];
113
+ /** Plain-text morph audit across the whole deck. */
114
+ export declare function exportMorphDeckAuditText(pres: Pick<Presentation, "slides">): string;
115
+ /** Compact deck-wide morph summary for copy / support. */
116
+ export declare function exportMorphDeckCompactText(pres: Pick<Presentation, "slides">): string;
117
+ /** Short issues-only export (unmatched + FLIP-only pairs). */
118
+ export declare function exportMorphIssuesCompactText(report: MorphAuditReport, opts?: {
119
+ fromSlide?: number;
120
+ toSlide?: number;
121
+ }): string;
122
+ /** Plain-text morph audit for copy / export. */
123
+ export declare function exportMorphAuditText(report: MorphAuditReport, opts?: {
124
+ fromSlide?: number;
125
+ toSlide?: number;
126
+ filter?: MorphAuditFilter;
127
+ }): string;
@@ -0,0 +1,28 @@
1
+ export interface MorphShapeRect {
2
+ left: number;
3
+ top: number;
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export interface MorphShapeCandidate {
8
+ id: string;
9
+ name: string;
10
+ tag: string;
11
+ rect: MorphShapeRect;
12
+ pathD?: string;
13
+ placeholderType?: string;
14
+ }
15
+ export declare function morphGeometryHash(c: MorphShapeCandidate): string;
16
+ export declare function morphSimilarity(a: MorphShapeCandidate, b: MorphShapeCandidate): number;
17
+ /** How a morph pair was matched (mirrors `matchMorphShapePairs` priority). */
18
+ export declare function morphMatchReason(ghost: MorphShapeCandidate, stage: MorphShapeCandidate): "id" | "name" | "placeholder" | "geometry";
19
+ /**
20
+ * name/id/placeholder/geometry matching for morph FLIP (testable without DOM).
21
+ * Each tier is resolved for every shape before the next one, and geometry pairs are assigned
22
+ * best-score-first: a stage-order greedy pass lets an early shape take the ghost that an
23
+ * unchanged later shape (same kicker, same folio) matches exactly.
24
+ */
25
+ export declare function matchMorphShapePairs(ghost: MorphShapeCandidate[], stage: MorphShapeCandidate[]): Array<{
26
+ ghost: MorphShapeCandidate;
27
+ stage: MorphShapeCandidate;
28
+ }>;
@@ -0,0 +1,37 @@
1
+ /** SVG path helpers for Morph transitions (normalize + interpolate). */
2
+ export interface PathCommand {
3
+ cmd: string;
4
+ args: number[];
5
+ }
6
+ interface Point {
7
+ x: number;
8
+ y: number;
9
+ }
10
+ export declare function parsePathCommands(d: string): PathCommand[];
11
+ /** Convert relative path commands to absolute (uppercase) equivalents. */
12
+ export declare function pathToAbsolute(commands: PathCommand[]): PathCommand[];
13
+ /** Open line path in local coordinates. */
14
+ export declare function linePathFromPoints(x1: number, y1: number, x2: number, y2: number): string;
15
+ /** Closed rectangle path in local coordinates (0,0)–(w,h). */
16
+ export declare function rectPathFromSize(w: number, h: number): string;
17
+ /** Closed ellipse path approximated as a polyline in local bbox (w×h). */
18
+ export declare function ellipsePathFromSize(w: number, h: number, segments?: number): string;
19
+ /** Convert SVG polygon/polyline `points` attribute to a path `d` string. */
20
+ export declare function polygonPointsToPath(points: string, closed: boolean): string;
21
+ /**
22
+ * Rotate / reverse `b` so closed polylines align for smoother morph tweens.
23
+ * Exported for regression tests.
24
+ */
25
+ export declare function alignClosedPolylines(a: Point[], b: Point[]): {
26
+ a: Point[];
27
+ b: Point[];
28
+ };
29
+ /** Sample absolute path commands into a polyline (Bezier / arc aware). */
30
+ export declare function flattenPathToPoints(commands: PathCommand[], segmentsPerCurve?: number): Point[];
31
+ /** Build a tween for morphing `fromD` → `toD`, or null when only FLIP should apply. */
32
+ export declare function makeMorphPathTween(fromD: string, toD: string): ((t: number) => string) | null;
33
+ /** Whether path geometry can morph (vs FLIP-only). */
34
+ export declare function morphPathCompatible(fromD: string, toD: string): boolean;
35
+ /** Compare two paths after normalization (for shape matching). */
36
+ export declare function morphPathSignature(d: string): string;
37
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { TransitionFactory } from "./types.js";
2
+ export declare const morph: TransitionFactory;
@@ -0,0 +1,6 @@
1
+ import type { SlideTransition } from "@sliiu/present-core";
2
+ import type { TransitionFactory } from "./types.js";
3
+ export declare function getTransition(kind: string): TransitionFactory | undefined;
4
+ export declare function listTransitions(): string[];
5
+ export declare function registerTransition(kind: string, fn: TransitionFactory): void;
6
+ export declare function runTransition(ghost: HTMLElement, stage: HTMLElement, transition: SlideTransition): Promise<void>;
@@ -0,0 +1,19 @@
1
+ export interface Snapshot {
2
+ canvas: HTMLCanvasElement;
3
+ width: number;
4
+ height: number;
5
+ }
6
+ export type SnapshotCache = Map<string, Promise<string | null>>;
7
+ export declare function isExternalSnapshotUrl(url: string): boolean;
8
+ /** 把样式里的外部 url() 换成 data URL;换不掉的可见背景改成 none,避免再去拉取。 */
9
+ export declare function rewriteCssForSnapshot(css: string, resolved: Map<string, string | null>): {
10
+ css: string;
11
+ droppedVisible: boolean;
12
+ };
13
+ /**
14
+ * 在克隆树上把外部图片换成 data URL,并拆掉会弄脏画布的 video / iframe。
15
+ * 返回 false 表示有一张可见图读不出来,快照不能代表这一页。
16
+ * 不改 live。
17
+ */
18
+ export declare function sanitizeSnapshotClone(live: HTMLElement, clone: HTMLElement, cache?: SnapshotCache): Promise<boolean>;
19
+ export declare function snapshotElement(el: HTMLElement, w: number, h: number, cache?: SnapshotCache): Promise<Snapshot | null>;
@@ -0,0 +1,2 @@
1
+ export declare function mountTwin(source: HTMLElement, clip: string): HTMLElement;
2
+ export declare function dropTwins(twins: HTMLElement[], source: HTMLElement, prevVisibility: string): void;
@@ -0,0 +1,13 @@
1
+ import type { SlideTransition } from "@sliiu/present-core";
2
+ export interface TransitionCtx {
3
+ ghost: HTMLElement;
4
+ stage: HTMLElement;
5
+ duration: number;
6
+ params: Record<string, unknown>;
7
+ transition: SlideTransition;
8
+ stageSize: {
9
+ w: number;
10
+ h: number;
11
+ };
12
+ }
13
+ export type TransitionFactory = (ctx: TransitionCtx) => Promise<void>;
@@ -0,0 +1,12 @@
1
+ import type { TransitionCtx } from "./types.js";
2
+ export declare const STD_EASE = "cubic-bezier(0.4, 0, 0.2, 1)";
3
+ export declare const LINEAR = "linear";
4
+ export declare function dirVec(dir: string, w: number, h: number): {
5
+ dx: number;
6
+ dy: number;
7
+ axis: "x" | "y";
8
+ sign: 1 | -1;
9
+ };
10
+ export declare function runAll(anims: Animation[], cleanup?: () => void): Promise<void>;
11
+ export declare function ms(ctx: TransitionCtx): number;
12
+ export declare function easing(ctx: TransitionCtx): string;
@@ -0,0 +1,5 @@
1
+ import type { TransitionFactory } from "./types.js";
2
+ export declare const pageCurlWebGL: TransitionFactory;
3
+ export declare const cubeWebGL: TransitionFactory;
4
+ export declare const boxWebGL: TransitionFactory;
5
+ export declare const dissolveWebGL: TransitionFactory;