@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.
Files changed (126) 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/arrow-geom.js +38 -0
  6. package/dist/cjs/canvas-backend.js +1368 -0
  7. package/dist/cjs/cell-borders.js +25 -0
  8. package/dist/cjs/chart-canvas.js +1059 -0
  9. package/dist/cjs/chart-ex-render.js +275 -0
  10. package/dist/cjs/chart-geom.js +313 -0
  11. package/dist/cjs/chart-svg.js +1110 -0
  12. package/dist/cjs/compose.js +74 -0
  13. package/dist/cjs/effect-clip.js +652 -0
  14. package/dist/cjs/entrance-hide.js +63 -0
  15. package/dist/cjs/font-stack.js +103 -0
  16. package/dist/cjs/hit-geom.js +50 -0
  17. package/dist/cjs/hybrid-backend.js +93 -0
  18. package/dist/cjs/hybrid-segments.js +54 -0
  19. package/dist/cjs/index.js +70 -0
  20. package/dist/cjs/linked-src.js +116 -0
  21. package/dist/cjs/metafile-preview.js +142 -0
  22. package/dist/cjs/model3d-frame.js +199 -0
  23. package/dist/cjs/model3d-render.js +78 -0
  24. package/dist/cjs/morph-transition.js +57 -0
  25. package/dist/cjs/node-affine.js +79 -0
  26. package/dist/cjs/ole-render.js +291 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/paint-canvas.js +701 -0
  29. package/dist/cjs/paint-svg.js +674 -0
  30. package/dist/cjs/scene-node.js +762 -0
  31. package/dist/cjs/selection.js +130 -0
  32. package/dist/cjs/shape-effects.js +66 -0
  33. package/dist/cjs/shape-front.js +38 -0
  34. package/dist/cjs/stage-chrome.js +15 -0
  35. package/dist/cjs/svg-backend.js +2172 -0
  36. package/dist/cjs/text-autofit.js +48 -0
  37. package/dist/cjs/text-hit.js +284 -0
  38. package/dist/cjs/text-layout.js +408 -0
  39. package/dist/cjs/text-shared.js +238 -0
  40. package/dist/cjs/text-stacked.js +82 -0
  41. package/dist/cjs/text-warp.js +377 -0
  42. package/dist/cjs/text-wordart.js +33 -0
  43. package/dist/cjs/video-frame.js +113 -0
  44. package/dist/cjs/webgl-model3d-backend.js +285 -0
  45. package/dist/cjs/webgl-shape-backend.js +360 -0
  46. package/dist/esm/arrow-geom.js +33 -0
  47. package/dist/esm/canvas-backend.js +1363 -0
  48. package/dist/esm/cell-borders.js +21 -0
  49. package/dist/esm/chart-canvas.js +1056 -0
  50. package/dist/esm/chart-ex-render.js +261 -0
  51. package/dist/esm/chart-geom.js +296 -0
  52. package/dist/esm/chart-svg.js +1107 -0
  53. package/dist/esm/compose.js +71 -0
  54. package/dist/esm/effect-clip.js +648 -0
  55. package/dist/esm/entrance-hide.js +59 -0
  56. package/dist/esm/font-stack.js +100 -0
  57. package/dist/esm/hit-geom.js +45 -0
  58. package/dist/esm/hybrid-backend.js +89 -0
  59. package/dist/esm/hybrid-segments.js +51 -0
  60. package/dist/esm/index.js +24 -0
  61. package/dist/esm/linked-src.js +109 -0
  62. package/dist/esm/metafile-preview.js +135 -0
  63. package/dist/esm/model3d-frame.js +158 -0
  64. package/dist/esm/model3d-render.js +73 -0
  65. package/dist/esm/morph-transition.js +54 -0
  66. package/dist/esm/node-affine.js +71 -0
  67. package/dist/esm/ole-render.js +286 -0
  68. package/dist/esm/paint-canvas.js +693 -0
  69. package/dist/esm/paint-svg.js +670 -0
  70. package/dist/esm/scene-node.js +750 -0
  71. package/dist/esm/selection.js +122 -0
  72. package/dist/esm/shape-effects.js +60 -0
  73. package/dist/esm/shape-front.js +35 -0
  74. package/dist/esm/stage-chrome.js +12 -0
  75. package/dist/esm/svg-backend.js +2168 -0
  76. package/dist/esm/text-autofit.js +44 -0
  77. package/dist/esm/text-hit.js +280 -0
  78. package/dist/esm/text-layout.js +400 -0
  79. package/dist/esm/text-shared.js +221 -0
  80. package/dist/esm/text-stacked.js +78 -0
  81. package/dist/esm/text-warp.js +371 -0
  82. package/dist/esm/text-wordart.js +30 -0
  83. package/dist/esm/video-frame.js +109 -0
  84. package/dist/esm/webgl-model3d-backend.js +248 -0
  85. package/dist/esm/webgl-shape-backend.js +323 -0
  86. package/dist/types/arrow-geom.d.ts +10 -0
  87. package/dist/types/canvas-backend.d.ts +55 -0
  88. package/dist/types/cell-borders.d.ts +9 -0
  89. package/dist/types/chart-canvas.d.ts +2 -0
  90. package/dist/types/chart-ex-render.d.ts +79 -0
  91. package/dist/types/chart-geom.d.ts +112 -0
  92. package/dist/types/chart-svg.d.ts +2 -0
  93. package/dist/types/compose.d.ts +2 -0
  94. package/dist/types/effect-clip.d.ts +10 -0
  95. package/dist/types/entrance-hide.d.ts +16 -0
  96. package/dist/types/font-stack.d.ts +9 -0
  97. package/dist/types/hit-geom.d.ts +15 -0
  98. package/dist/types/hybrid-backend.d.ts +21 -0
  99. package/dist/types/hybrid-segments.d.ts +11 -0
  100. package/dist/types/index.d.ts +24 -0
  101. package/dist/types/linked-src.d.ts +13 -0
  102. package/dist/types/metafile-preview.d.ts +11 -0
  103. package/dist/types/model3d-frame.d.ts +8 -0
  104. package/dist/types/model3d-render.d.ts +4 -0
  105. package/dist/types/morph-transition.d.ts +15 -0
  106. package/dist/types/node-affine.d.ts +22 -0
  107. package/dist/types/ole-render.d.ts +7 -0
  108. package/dist/types/paint-canvas.d.ts +22 -0
  109. package/dist/types/paint-svg.d.ts +19 -0
  110. package/dist/types/scene-node.d.ts +128 -0
  111. package/dist/types/selection.d.ts +22 -0
  112. package/dist/types/shape-effects.d.ts +13 -0
  113. package/dist/types/shape-front.d.ts +4 -0
  114. package/dist/types/stage-chrome.d.ts +8 -0
  115. package/dist/types/svg-backend.d.ts +88 -0
  116. package/dist/types/text-autofit.d.ts +7 -0
  117. package/dist/types/text-hit.d.ts +7 -0
  118. package/dist/types/text-layout.d.ts +35 -0
  119. package/dist/types/text-shared.d.ts +86 -0
  120. package/dist/types/text-stacked.d.ts +11 -0
  121. package/dist/types/text-warp.d.ts +37 -0
  122. package/dist/types/text-wordart.d.ts +18 -0
  123. package/dist/types/video-frame.d.ts +2 -0
  124. package/dist/types/webgl-model3d-backend.d.ts +25 -0
  125. package/dist/types/webgl-shape-backend.d.ts +33 -0
  126. package/package.json +43 -4
@@ -0,0 +1,762 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CharRangeAnimatable = exports.ParagraphAnimatable = exports.SceneNode = void 0;
4
+ exports.mergeParaOverrides = mergeParaOverrides;
5
+ exports.netFlipParity = netFlipParity;
6
+ exports.resolveAnimatable = resolveAnimatable;
7
+ exports.createScene = createScene;
8
+ exports.shapeRenderFill = shapeRenderFill;
9
+ exports.shapeRenderFillBBox = shapeRenderFillBBox;
10
+ const core_1 = require("@sliiu/present-core");
11
+ const compose_1 = require("./compose");
12
+ /** iterate 子目标上的属性叠在段落动画之上(同名字段以后者为准)。 */
13
+ function mergeParaOverrides(base, over) {
14
+ if (!base && !over)
15
+ return undefined;
16
+ return { ...(base ?? {}), ...(over ?? {}) };
17
+ }
18
+ const ANIM_PROPS = new Set([
19
+ "opacity",
20
+ "rotation",
21
+ "scale",
22
+ "scaleX",
23
+ "scaleY",
24
+ "tx",
25
+ "ty",
26
+ "fill",
27
+ "stroke",
28
+ "fillColor",
29
+ "strokeColor",
30
+ "strokeWidth",
31
+ "width",
32
+ "height",
33
+ "clipDirection",
34
+ "wheelReveal",
35
+ "visible",
36
+ ]);
37
+ class SceneNode {
38
+ id;
39
+ shape;
40
+ parent = null;
41
+ children = [];
42
+ layerId;
43
+ tx = 0;
44
+ ty = 0;
45
+ scale = 1;
46
+ scaleX = 1;
47
+ scaleY = 1;
48
+ rotation = 0;
49
+ /** 动画写入的旋转增量。渲染角 = shape.transform.rotation + animRotation。 */
50
+ animRotation = 0;
51
+ opacity = 1;
52
+ widthScale = 1;
53
+ heightScale = 1;
54
+ clip = null;
55
+ fillOverride = null;
56
+ strokeOverride = null;
57
+ strokeWidthOverride = null;
58
+ visibleOverride = null;
59
+ paraOverrides = new Map();
60
+ charOverrides = new Map();
61
+ mediaEl = null;
62
+ lastMediaAction = null;
63
+ listeners = new Set();
64
+ constructor(shape, layerId) {
65
+ this.id = shape.id;
66
+ this.shape = shape;
67
+ this.layerId = layerId;
68
+ this.opacity = shape.opacity ?? 1;
69
+ this.tx = shape.transform?.tx ?? 0;
70
+ this.ty = shape.transform?.ty ?? 0;
71
+ this.animRotation = 0;
72
+ this.rotation = this.shapeRotation();
73
+ // SceneNode 上的 scaleX/scaleY 是 **动画基线**(默认 1),承载 animScale / preset
74
+ // 的 scaleX/scaleY 通道;§20.1.7.6 ST_TransformGroup 的 flipH/flipV(parser 把它
75
+ // 编码到 shape.transform.scaleX/scaleY = ±1)由 backend 在最终 transform 公式
76
+ // 里单独乘入,避免动画把缩放归 1 时把翻转一起冲掉。
77
+ if (shape.kind === "group") {
78
+ this.children = shape.children
79
+ .filter((c) => !c.cellMeta?.hidden)
80
+ .map((c) => {
81
+ const n = new SceneNode(c, layerId);
82
+ n.parent = this;
83
+ return n;
84
+ });
85
+ }
86
+ }
87
+ shapeRotation() {
88
+ return this.shape.transform?.rotation ?? 0;
89
+ }
90
+ on(fn) {
91
+ this.listeners.add(fn);
92
+ return () => this.listeners.delete(fn);
93
+ }
94
+ fire(key, value) {
95
+ for (const l of this.listeners)
96
+ l(key, value, this);
97
+ }
98
+ /**
99
+ * 触发一次 backend redraw 而**不**改变任何节点 / shape 属性。用于离屏光栅 3D 烘焙
100
+ * 把结果写进 shape.meta3D.bakedSrc 副通道后,让 SvgBackend / CanvasBackend 重画读取
101
+ * 新的 bakedSrc(既不污染 shape.src/bbox,也不影响 live WebGL 视图)。
102
+ */
103
+ markDirty() {
104
+ this.fire("dirty", undefined);
105
+ }
106
+ /**
107
+ * 内部用:跳过外部 setProperty 的开关 / 派发逻辑,直接把 opacity 强制设为 v 并
108
+ * 通知 listener。专门给 ParagraphAnimatable / CharRangeAnimatable 在 reveal
109
+ * 文字时同步解除 ancestor group hide 用,避免在每次字符 reveal 都走完整 setProperty
110
+ * switch(性能 + 也避免触发不必要 fire-key 字符串)。
111
+ */
112
+ forceOpacity(v) {
113
+ if (this.opacity === v)
114
+ return;
115
+ this.opacity = v;
116
+ this.fire("opacity", v);
117
+ }
118
+ setProperty(key, value) {
119
+ switch (key) {
120
+ case "tx":
121
+ this.tx = value;
122
+ break;
123
+ case "ty":
124
+ this.ty = value;
125
+ break;
126
+ case "scale":
127
+ this.scale = value;
128
+ break;
129
+ case "scaleX":
130
+ this.scaleX = value;
131
+ break;
132
+ case "scaleY":
133
+ this.scaleY = value;
134
+ break;
135
+ case "rotation":
136
+ // style.rotation / animRot 写的是动画通道,叠在 xfrm rot 上。
137
+ // Spin 入口 360→0 的末态是 0,形状停在自己的旋转角,而不是被归零。
138
+ this.animRotation = typeof value === "number" ? value : 0;
139
+ this.rotation = this.shapeRotation() + this.animRotation;
140
+ break;
141
+ case "opacity":
142
+ this.opacity = value;
143
+ break;
144
+ case "width":
145
+ this.widthScale = value;
146
+ break;
147
+ case "height":
148
+ this.heightScale = value;
149
+ break;
150
+ case "visible":
151
+ this.visibleOverride = value;
152
+ break;
153
+ case "fill":
154
+ case "fillColor":
155
+ this.fillOverride = value;
156
+ break;
157
+ case "stroke":
158
+ case "strokeColor":
159
+ this.strokeOverride = value;
160
+ break;
161
+ case "strokeWidth":
162
+ this.strokeWidthOverride = value;
163
+ break;
164
+ case "clipDirection": {
165
+ if (typeof value === "string") {
166
+ const [dir, prog] = value.split(":");
167
+ this.clip = {
168
+ kind: "rect",
169
+ direction: (dir ?? "left"),
170
+ progress: Number(prog ?? "0"),
171
+ };
172
+ }
173
+ break;
174
+ }
175
+ case "wheelReveal": {
176
+ if (typeof value === "string") {
177
+ const [s, p] = value.split(":");
178
+ this.clip = {
179
+ kind: "wheel",
180
+ spokes: Math.max(1, Math.floor(Number(s ?? "1"))),
181
+ progress: Number(p ?? "0"),
182
+ };
183
+ }
184
+ break;
185
+ }
186
+ case "mediaAction": {
187
+ if (typeof value !== "string")
188
+ break;
189
+ if (this.lastMediaAction === value)
190
+ break;
191
+ this.lastMediaAction = value;
192
+ const el = this.mediaEl;
193
+ if (el) {
194
+ const trim = this.shape.trim;
195
+ const fallbackDur = this.shape.duration;
196
+ const dur = Number.isFinite(el.duration) && el.duration > 0 ? el.duration : fallbackDur;
197
+ const { start } = (0, core_1.mediaTrimBounds)(trim, dur);
198
+ switch (value) {
199
+ case "play":
200
+ if (el.currentTime < start) {
201
+ try {
202
+ el.currentTime = start;
203
+ }
204
+ catch {
205
+ /* ignore */
206
+ }
207
+ }
208
+ el.play().catch(() => { });
209
+ break;
210
+ case "pause":
211
+ el.pause();
212
+ break;
213
+ case "stop":
214
+ el.pause();
215
+ try {
216
+ el.currentTime = start;
217
+ }
218
+ catch {
219
+ /* ignore */
220
+ }
221
+ break;
222
+ case "resume":
223
+ el.play().catch(() => { });
224
+ break;
225
+ }
226
+ }
227
+ break;
228
+ }
229
+ default: {
230
+ if (key.startsWith("effect:")) {
231
+ this.applyEffectClip(key.slice(7), value);
232
+ break;
233
+ }
234
+ if (ANIM_PROPS.has(key)) {
235
+ this[key] = value;
236
+ }
237
+ else {
238
+ this.shape[key] = value;
239
+ }
240
+ }
241
+ }
242
+ this.fire(key, value);
243
+ }
244
+ // §19.5.8 animEffect 子节点的 filter+prLst+progress 落到 EffectClipState。
245
+ // 兼容 string 编码 "<prLst>|<progress>|<transition>" 与裸数字 progress。
246
+ // 特殊:value === null 或 value === "" 表示"撤销该 filter",把 clip 复位
247
+ // 为 null(元素回到未应用 effect 的 base 状态)——用于 fill="remove" 末态。
248
+ applyEffectClip(filter, value) {
249
+ if (value === null || value === undefined || value === "") {
250
+ this.clip = null;
251
+ return;
252
+ }
253
+ let progress = 0;
254
+ let prLst;
255
+ let transition = "in";
256
+ if (typeof value === "string") {
257
+ const parts = value.split("|");
258
+ prLst = parts[0] ? parts[0] : undefined;
259
+ progress = Number(parts[1] ?? "0");
260
+ transition = parts[2] ?? "in";
261
+ }
262
+ else if (typeof value === "number") {
263
+ progress = value;
264
+ }
265
+ this.clip = {
266
+ kind: "effect",
267
+ filter,
268
+ prLst,
269
+ progress: Number.isFinite(progress) ? progress : 0,
270
+ transition,
271
+ };
272
+ }
273
+ getProperty(key) {
274
+ switch (key) {
275
+ case "tx":
276
+ return this.tx;
277
+ case "ty":
278
+ return this.ty;
279
+ case "scale":
280
+ return this.scale;
281
+ case "scaleX":
282
+ return this.scaleX;
283
+ case "scaleY":
284
+ return this.scaleY;
285
+ case "rotation":
286
+ return this.animRotation;
287
+ case "opacity":
288
+ return this.opacity;
289
+ case "width":
290
+ return this.widthScale;
291
+ case "height":
292
+ return this.heightScale;
293
+ case "fill":
294
+ case "fillColor":
295
+ return this.fillOverride;
296
+ case "stroke":
297
+ case "strokeColor":
298
+ return this.strokeOverride;
299
+ case "strokeWidth":
300
+ return this.strokeWidthOverride ?? this.shape.stroke?.width ?? 0;
301
+ default:
302
+ return this.shape[key];
303
+ }
304
+ }
305
+ walk(fn) {
306
+ fn(this);
307
+ for (const c of this.children)
308
+ c.walk(fn);
309
+ }
310
+ findById(id) {
311
+ if (this.id === id)
312
+ return this;
313
+ for (const c of this.children) {
314
+ const f = c.findById(id);
315
+ if (f)
316
+ return f;
317
+ }
318
+ return null;
319
+ }
320
+ resetAnimState() {
321
+ this.tx = 0;
322
+ this.ty = 0;
323
+ this.scale = 1;
324
+ // 动画基线统一归 1;shape.transform.scaleX/scaleY (flipH/flipV ±1) 由 backend
325
+ // 渲染时再乘入,所以这里不需要恢复翻转。
326
+ this.scaleX = 1;
327
+ this.scaleY = 1;
328
+ this.widthScale = 1;
329
+ this.heightScale = 1;
330
+ this.animRotation = 0;
331
+ this.rotation = this.shapeRotation();
332
+ this.opacity = this.shape.opacity ?? 1;
333
+ this.clip = null;
334
+ this.fillOverride = null;
335
+ this.strokeOverride = null;
336
+ this.strokeWidthOverride = null;
337
+ this.visibleOverride = null;
338
+ this.paraOverrides.clear();
339
+ this.charOverrides.clear();
340
+ this.lastMediaAction = null;
341
+ if (this.mediaEl) {
342
+ this.mediaEl.pause();
343
+ try {
344
+ this.mediaEl.currentTime = 0;
345
+ }
346
+ catch { }
347
+ }
348
+ for (const c of this.children)
349
+ c.resetAnimState();
350
+ }
351
+ setParaProperty(idx, key, value) {
352
+ let s = this.paraOverrides.get(idx);
353
+ if (!s) {
354
+ s = {};
355
+ this.paraOverrides.set(idx, s);
356
+ }
357
+ applyOverrideProp(s, key, value);
358
+ this.fire(`para:${idx}:${key}`, value);
359
+ }
360
+ getParaProperty(idx, key) {
361
+ const s = this.paraOverrides.get(idx);
362
+ if (!s)
363
+ return paraDefault(key);
364
+ return readOverrideProp(s, key);
365
+ }
366
+ setCharRangeProperty(paraIdx, unit, start, end, key, value) {
367
+ let m = this.charOverrides.get(paraIdx);
368
+ if (!m) {
369
+ m = new Map();
370
+ this.charOverrides.set(paraIdx, m);
371
+ }
372
+ const k = `${unit[0]}${start}-${end}`;
373
+ let s = m.get(k);
374
+ if (!s) {
375
+ s = { start, end, unit };
376
+ m.set(k, s);
377
+ }
378
+ applyOverrideProp(s, key, value);
379
+ this.fire(`char:${paraIdx}:${k}:${key}`, value);
380
+ }
381
+ getCharRangeProperty(paraIdx, unit, start, end, key) {
382
+ const m = this.charOverrides.get(paraIdx);
383
+ const k = `${unit[0]}${start}-${end}`;
384
+ const s = m?.get(k);
385
+ if (!s)
386
+ return paraDefault(key);
387
+ return readOverrideProp(s, key);
388
+ }
389
+ }
390
+ exports.SceneNode = SceneNode;
391
+ function applyOverrideProp(s, key, value) {
392
+ if (key.startsWith("effect:")) {
393
+ // value === null/""/undefined:撤销 filter clip(fill="remove" 末态)。
394
+ if (value === null || value === undefined || value === "") {
395
+ s.clip = undefined;
396
+ return;
397
+ }
398
+ const filter = key.slice(7);
399
+ let progress = 0;
400
+ let prLst;
401
+ let transition = "in";
402
+ if (typeof value === "string") {
403
+ const parts = value.split("|");
404
+ prLst = parts[0] ? parts[0] : undefined;
405
+ progress = Number(parts[1] ?? "0");
406
+ transition = parts[2] ?? "in";
407
+ }
408
+ else if (typeof value === "number") {
409
+ progress = value;
410
+ }
411
+ s.clip = {
412
+ kind: "effect",
413
+ filter,
414
+ prLst,
415
+ progress: Number.isFinite(progress) ? progress : 0,
416
+ transition,
417
+ };
418
+ return;
419
+ }
420
+ switch (key) {
421
+ case "opacity":
422
+ s.opacity = value;
423
+ break;
424
+ case "tx":
425
+ s.tx = value;
426
+ break;
427
+ case "ty":
428
+ s.ty = value;
429
+ break;
430
+ case "scale":
431
+ s.scale = value;
432
+ break;
433
+ case "scaleX":
434
+ s.scaleX = value;
435
+ break;
436
+ case "scaleY":
437
+ s.scaleY = value;
438
+ break;
439
+ case "rotation":
440
+ s.rotation = value;
441
+ break;
442
+ case "visible":
443
+ s.visible = value;
444
+ break;
445
+ case "fill":
446
+ case "fillColor":
447
+ s.fillColor = value;
448
+ break;
449
+ case "fontColor":
450
+ s.fontColor = value;
451
+ break;
452
+ case "fontSize":
453
+ s.fontSize = value;
454
+ break;
455
+ case "fontWeight":
456
+ s.fontWeight = value;
457
+ break;
458
+ case "fontStyle":
459
+ s.fontStyle = value;
460
+ break;
461
+ case "fontFamily":
462
+ s.fontFamily = value;
463
+ break;
464
+ case "underline":
465
+ s.underline = value;
466
+ break;
467
+ }
468
+ }
469
+ function readOverrideProp(s, key) {
470
+ switch (key) {
471
+ case "opacity":
472
+ return s.opacity ?? 1;
473
+ case "tx":
474
+ return s.tx ?? 0;
475
+ case "ty":
476
+ return s.ty ?? 0;
477
+ case "scale":
478
+ return s.scale ?? 1;
479
+ case "scaleX":
480
+ return s.scaleX ?? 1;
481
+ case "scaleY":
482
+ return s.scaleY ?? 1;
483
+ case "rotation":
484
+ return s.rotation ?? 0;
485
+ case "visible":
486
+ return s.visible ?? true;
487
+ case "fill":
488
+ case "fillColor":
489
+ return s.fillColor;
490
+ case "fontColor":
491
+ return s.fontColor;
492
+ case "fontSize":
493
+ return s.fontSize;
494
+ case "fontWeight":
495
+ return s.fontWeight;
496
+ case "fontStyle":
497
+ return s.fontStyle;
498
+ case "fontFamily":
499
+ return s.fontFamily;
500
+ case "underline":
501
+ return s.underline;
502
+ default:
503
+ return undefined;
504
+ }
505
+ }
506
+ function paraDefault(key) {
507
+ switch (key) {
508
+ case "opacity":
509
+ return 1;
510
+ case "tx":
511
+ case "ty":
512
+ case "rotation":
513
+ return 0;
514
+ case "scale":
515
+ case "scaleX":
516
+ case "scaleY":
517
+ return 1;
518
+ case "visible":
519
+ return true;
520
+ default:
521
+ return undefined;
522
+ }
523
+ }
524
+ // 当 entrance 通过 paragraph/char range 触发文字 reveal 时,
525
+ // 必须同步把所属 SceneNode(_text)以及它的 ancestor group(如 sp_12 蓝色按钮块)的
526
+ // hide 解除。否则 parser 给 group 设的 opacity=0 会一直压制 paragraph reveal,
527
+ // 文字虽然写了 opacity=1 但视觉上仍不可见。
528
+ //
529
+ // 仅在第一次写入 opacity!=0 时把 ancestor 链上 opacity 强制拉到 1,避免反向覆盖
530
+ // 后续可能存在的强调/退出动画。同样适用于 fill/visible 等"显式 reveal" 信号。
531
+ function autoRevealAncestors(node, key, value) {
532
+ if (key !== "opacity")
533
+ return;
534
+ if (typeof value !== "number" || value <= 0)
535
+ return;
536
+ let cur = node;
537
+ while (cur) {
538
+ if (cur.opacity < 1)
539
+ cur.forceOpacity(1);
540
+ cur = cur.parent;
541
+ }
542
+ }
543
+ // §20.1.7.6 flipH/flipV:返回 node 自身 + 所有祖先 group 累计的翻转奇偶性(±1)。
544
+ // 文字渲染据此对字形做反向翻转,使其在被翻转的 shape/group 内保持可读(PowerPoint
545
+ // 行为:翻转只镜像几何与子元素位置,不镜像文字字形)。
546
+ function netFlipParity(node) {
547
+ let x = 1;
548
+ let y = 1;
549
+ let cur = node;
550
+ while (cur) {
551
+ if ((cur.shape.transform?.scaleX ?? 1) < 0)
552
+ x = -x;
553
+ if ((cur.shape.transform?.scaleY ?? 1) < 0)
554
+ y = -y;
555
+ cur = cur.parent;
556
+ }
557
+ return { x, y };
558
+ }
559
+ class ParagraphAnimatable {
560
+ node;
561
+ paraIdx;
562
+ constructor(node, paraIdx) {
563
+ this.node = node;
564
+ this.paraIdx = paraIdx;
565
+ }
566
+ setProperty(key, value) {
567
+ this.node.setParaProperty(this.paraIdx, key, value);
568
+ autoRevealAncestors(this.node, key, value);
569
+ }
570
+ getProperty(key) {
571
+ return this.node.getParaProperty(this.paraIdx, key);
572
+ }
573
+ }
574
+ exports.ParagraphAnimatable = ParagraphAnimatable;
575
+ class CharRangeAnimatable {
576
+ node;
577
+ paraIdx;
578
+ unit;
579
+ start;
580
+ end;
581
+ constructor(node, paraIdx, unit, start, end) {
582
+ this.node = node;
583
+ this.paraIdx = paraIdx;
584
+ this.unit = unit;
585
+ this.start = start;
586
+ this.end = end;
587
+ }
588
+ setProperty(key, value) {
589
+ this.node.setCharRangeProperty(this.paraIdx, this.unit, this.start, this.end, key, value);
590
+ autoRevealAncestors(this.node, key, value);
591
+ }
592
+ getProperty(key) {
593
+ return this.node.getCharRangeProperty(this.paraIdx, this.unit, this.start, this.end, key);
594
+ }
595
+ }
596
+ exports.CharRangeAnimatable = CharRangeAnimatable;
597
+ const RANGE_RX = /^(?:p(\d+):)?([cwp])(\d+)-(\d+)$/;
598
+ function resolveAnimatable(scene, id) {
599
+ const hashIdx = id.indexOf("#");
600
+ if (hashIdx === -1)
601
+ return scene.root.findById(id);
602
+ const baseId = id.slice(0, hashIdx);
603
+ const sel = id.slice(hashIdx + 1);
604
+ const base = scene.root.findById(baseId);
605
+ if (!base)
606
+ return null;
607
+ if (/^p\d+$/.test(sel)) {
608
+ const idx = Number(sel.slice(1));
609
+ if (Number.isFinite(idx))
610
+ return new ParagraphAnimatable(base, idx);
611
+ }
612
+ const m = RANGE_RX.exec(sel);
613
+ if (m) {
614
+ const paraIdx = m[1] !== undefined ? Number(m[1]) : 0;
615
+ const kind = m[2];
616
+ const start = Number(m[3]);
617
+ const end = Number(m[4]);
618
+ if (kind === "c")
619
+ return new CharRangeAnimatable(base, paraIdx, "char", start, end);
620
+ if (kind === "w")
621
+ return new CharRangeAnimatable(base, paraIdx, "word", start, end);
622
+ if (kind === "p")
623
+ return new ParagraphAnimatable(base, start);
624
+ }
625
+ return base;
626
+ }
627
+ function createScene(slide, size, options = {}) {
628
+ const layerNodes = new Map();
629
+ const identity = {
630
+ tx: 0,
631
+ ty: 0,
632
+ rotation: 0,
633
+ scaleX: 1,
634
+ scaleY: 1,
635
+ skewX: 0,
636
+ skewY: 0,
637
+ };
638
+ // composedLayers 阶段 2 起取代 slide.layers.flatMap:把 deck masters[] 中
639
+ // slide.layoutRef 指向的 master/layout 装饰合成为 layers 前缀。
640
+ const composedLayers = (0, compose_1.composeSlideForRender)(slide, options.masters);
641
+ const layerShapes = composedLayers.flatMap((l) => l.shapes);
642
+ // 背景优先级:slide.background > master.background > 不渲染。
643
+ // slide 没有显式背景时(新建 / 从模板创建的初始 slide),回退到 master 主题背景。
644
+ let effectiveBg = slide.background;
645
+ if ((!effectiveBg || effectiveBg.kind === "none") && slide.layoutRef && options.masters) {
646
+ const masterForBg = options.masters.find((m) => m.id === slide.layoutRef.masterId);
647
+ if (masterForBg?.background && masterForBg.background.kind !== "none") {
648
+ effectiveBg = masterForBg.background;
649
+ }
650
+ }
651
+ let background = null;
652
+ if (effectiveBg && effectiveBg.kind !== "none") {
653
+ const bg = effectiveBg;
654
+ const bbox = { x: 0, y: 0, w: size.width, h: size.height };
655
+ const common = {
656
+ id: `__bg_${slide.id}`,
657
+ name: "Background",
658
+ zIndex: -1,
659
+ visible: true,
660
+ locked: true,
661
+ opacity: 1,
662
+ bbox,
663
+ transform: { ...identity },
664
+ };
665
+ // 所有背景(含 image)统一渲染成一个铺满整页的 rect,fill 直接挂 Paint,
666
+ // 由 paintToSvg / paintToCanvas 统一处理 fit / adjust / alpha / tile / srcRect。
667
+ // 旧实现把 image 背景特判成 image shape,只带 src + opacity,丢掉了 fit 和
668
+ // 图片调整,导致"切换填充方式 / 灰度亮度"在背景上全部无效。
669
+ const bgShape = { ...common, kind: "rect", fill: bg };
670
+ background = bgShape;
671
+ layerShapes.unshift(bgShape);
672
+ }
673
+ const rootShape = {
674
+ id: `__slide_${slide.id}`,
675
+ kind: "group",
676
+ name: slide.name,
677
+ zIndex: 0,
678
+ visible: true,
679
+ locked: false,
680
+ opacity: 1,
681
+ bbox: { x: 0, y: 0, w: size.width, h: size.height },
682
+ transform: { ...identity },
683
+ children: layerShapes,
684
+ };
685
+ const root = new SceneNode(rootShape, "__root");
686
+ for (const layer of composedLayers) {
687
+ const nodes = [];
688
+ for (const s of layer.shapes) {
689
+ const n = root.findById(s.id);
690
+ if (n) {
691
+ n.layerId = layer.id;
692
+ nodes.push(n);
693
+ }
694
+ }
695
+ layerNodes.set(layer.id, nodes);
696
+ }
697
+ if (slide.initialParaOverrides) {
698
+ for (const o of slide.initialParaOverrides) {
699
+ const n = root.findById(o.shapeId);
700
+ if (!n)
701
+ continue;
702
+ if (o.opacity !== undefined)
703
+ n.setParaProperty(o.paraIdx, "opacity", o.opacity);
704
+ if (o.visible !== undefined)
705
+ n.setParaProperty(o.paraIdx, "visible", o.visible);
706
+ }
707
+ }
708
+ if (slide.initialCharOverrides) {
709
+ for (const o of slide.initialCharOverrides) {
710
+ const n = root.findById(o.shapeId);
711
+ if (!n)
712
+ continue;
713
+ if (o.opacity !== undefined)
714
+ n.setCharRangeProperty(o.paraIdx, o.unit, o.start, o.end, "opacity", o.opacity);
715
+ if (o.visible !== undefined)
716
+ n.setCharRangeProperty(o.paraIdx, o.unit, o.start, o.end, "visible", o.visible);
717
+ }
718
+ }
719
+ return { width: size.width, height: size.height, background, slide, layerNodes, root };
720
+ }
721
+ /** `useBgFill`:用幻灯片合成背景填形状,坐标系对齐整页而不是 shape bbox。 */
722
+ function shapeRenderFill(s, scene, node) {
723
+ if (s.useBgFill) {
724
+ const fromBg = scene.background && "fill" in scene.background
725
+ ? scene.background.fill
726
+ : undefined;
727
+ if (fromBg && fromBg.kind !== "none")
728
+ return fromBg;
729
+ const slideBg = scene.slide.background;
730
+ if (slideBg && slideBg.kind !== "none")
731
+ return slideBg;
732
+ return undefined;
733
+ }
734
+ if (s.fill?.kind === "groupFill" && node)
735
+ return inheritedGroupFill(node)?.paint;
736
+ return s.fill;
737
+ }
738
+ function shapeRenderFillBBox(s, scene, node) {
739
+ if (s.useBgFill)
740
+ return { x: 0, y: 0, w: scene.width, h: scene.height };
741
+ if (s.fill?.kind === "groupFill" && node) {
742
+ const inherited = inheritedGroupFill(node);
743
+ if (inherited)
744
+ return inherited.bbox;
745
+ }
746
+ return s.bbox;
747
+ }
748
+ // ECMA-376 §20.1.8.35 grpFill:子形状使用所在组 grpSpPr 的填充。
749
+ // 渐变 / 图片按组的外接框铺,子形状只露出自己那一块。
750
+ function inheritedGroupFill(node) {
751
+ let p = node.parent;
752
+ while (p) {
753
+ const fill = p.shape.fill;
754
+ if (fill && fill.kind !== "groupFill") {
755
+ if (fill.kind === "none")
756
+ return undefined;
757
+ return { paint: fill, bbox: p.shape.bbox };
758
+ }
759
+ p = p.parent;
760
+ }
761
+ return undefined;
762
+ }