@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,82 @@
1
+ "use strict";
2
+ // 竖排堆叠(eaVert / wordArtVert / mongolianVert)共享类型与列拆分工具。
3
+ // svg-backend 与 canvas-backend 同时依赖:保证两个后端的列拆分 / 列宽计算完全一致。
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.buildStackedGlyphs = buildStackedGlyphs;
6
+ exports.wrapGlyphsIntoColumns = wrapGlyphsIntoColumns;
7
+ const core_1 = require("@sliiu/present-core");
8
+ const text_layout_1 = require("./text-layout");
9
+ const text_shared_1 = require("./text-shared");
10
+ // 半 / 全 / 零宽空白 + \t:作为竖排"列拆分点",并在拆列后丢弃位于新列首部的此类空白。
11
+ function isStackBreakWs(ch) {
12
+ return ch === " " || ch === "\t" || ch === "\u3000" || ch === "\u200B";
13
+ }
14
+ // 从段构建字符级 glyph:letterSpacing 加进 stepY;upright 标记是否直立
15
+ // (仅 eaVert 下非 CJK 字符旋转 90° 侧躺)。
16
+ function buildStackedGlyphs(p, para, vert, fieldCtx) {
17
+ const baseLh = (0, text_shared_1.lineHeightFactor)(p);
18
+ const glyphs = [];
19
+ for (const run of p.runs) {
20
+ const runText = fieldCtx ? (0, core_1.runDisplayText)(run, fieldCtx) : run.text;
21
+ const fs = (para?.fontSize ?? run.fontSize) ?? text_shared_1.FALLBACK_FONT_SIZE;
22
+ // ECMA-376 §21.1.2.3.10 rPr@spc — 竖排时 letterSpacing 作为字符间垂直额外步进。
23
+ const ls = run.letterSpacing ?? 0;
24
+ const stepY = (p.lineHeightPx ?? fs * baseLh) + ls;
25
+ for (const ch of Array.from(runText)) {
26
+ const cp = ch.codePointAt(0);
27
+ const upright = vert === "wordArtVert" || vert === "wordArtVertRtl" || vert === "mongolianVert"
28
+ ? true
29
+ : (0, text_layout_1.isEastAsianChar)(cp); // eaVert:仅 CJK 直立
30
+ glyphs.push({ ch, run, fontSize: fs, stepY, upright });
31
+ }
32
+ }
33
+ return glyphs;
34
+ }
35
+ // 把段内字符流按列高 maxH 拆成多列:
36
+ // - 优先在最近一个空白字符处拆(空白随后丢弃,类似横排首字空白处理);
37
+ // - 若当前列还没有空白可拆,则在当前位置硬拆(避免单字单列死锁);
38
+ // - 新列首部一律去除空白。
39
+ // 例:para 文本 "abc def" 高度 maxH 容下 5 个字符 → 拆为 ["abc", "def"](首列尾空格被吃掉)。
40
+ function wrapGlyphsIntoColumns(glyphs, maxH) {
41
+ const columns = [];
42
+ let curCol = [];
43
+ let curH = 0;
44
+ let lastBreakBefore = -1; // curCol 内"最近的空白"索引;在该位置拆 → 空白进下一列后被丢弃
45
+ for (const g of glyphs) {
46
+ const ws = isStackBreakWs(g.ch);
47
+ // 1) 列首抑制空白:当前列空 + 当前字符是空白 → 丢弃
48
+ if (curCol.length === 0 && ws)
49
+ continue;
50
+ // 2) 即将溢出:先尝试在 lastBreakBefore 拆,否则硬拆封口
51
+ if (curCol.length > 0 && curH + g.stepY > maxH) {
52
+ if (lastBreakBefore > 0) {
53
+ const left = curCol.slice(0, lastBreakBefore);
54
+ let right = curCol.slice(lastBreakBefore);
55
+ columns.push(left);
56
+ let i = 0;
57
+ while (i < right.length && isStackBreakWs(right[i].ch))
58
+ i++;
59
+ right = right.slice(i);
60
+ curCol = right;
61
+ curH = curCol.reduce((acc, gg) => acc + gg.stepY, 0);
62
+ lastBreakBefore = -1;
63
+ }
64
+ else {
65
+ columns.push(curCol);
66
+ curCol = [];
67
+ curH = 0;
68
+ lastBreakBefore = -1;
69
+ if (ws)
70
+ continue; // 新列首部继续抑制
71
+ }
72
+ }
73
+ if (ws && curCol.length > 0) {
74
+ lastBreakBefore = curCol.length; // 标记空白位置("空白前可拆")
75
+ }
76
+ curCol.push(g);
77
+ curH += g.stepY;
78
+ }
79
+ if (curCol.length > 0)
80
+ columns.push(curCol);
81
+ return columns;
82
+ }
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.warpActive = warpActive;
4
+ exports.sampleWarpPath = sampleWarpPath;
5
+ exports.placeGlyphsOnWarp = placeGlyphsOnWarp;
6
+ exports.flattenWarpGlyphs = flattenWarpGlyphs;
7
+ const text_shared_1 = require("./text-shared");
8
+ const SKIP = new Set(["textNoShape", "textPlain", ""]);
9
+ function warpActive(s) {
10
+ const prst = s.prstTxWarp?.prst;
11
+ return !!prst && !SKIP.has(prst);
12
+ }
13
+ function adj(av, key, fallback) {
14
+ const v = av?.[key];
15
+ if (v == null || !Number.isFinite(v))
16
+ return fallback;
17
+ // OOXML adj 多为 0..100000(百分比 × 1000)。已是 0..1 或像素时原样用。
18
+ if (Math.abs(v) <= 1)
19
+ return v;
20
+ if (Math.abs(v) <= 100)
21
+ return v / 100;
22
+ return v / 100000;
23
+ }
24
+ function lerp(a, b, t) {
25
+ return a + (b - a) * t;
26
+ }
27
+ function samplePolyline(pts, n) {
28
+ if (pts.length < 2)
29
+ return pts.map((p) => ({ ...p, angle: 0 }));
30
+ const seg = [0];
31
+ let total = 0;
32
+ for (let i = 1; i < pts.length; i++) {
33
+ const dx = pts[i].x - pts[i - 1].x;
34
+ const dy = pts[i].y - pts[i - 1].y;
35
+ total += Math.hypot(dx, dy);
36
+ seg.push(total);
37
+ }
38
+ if (total <= 0)
39
+ return pts.map((p) => ({ ...p, angle: 0 }));
40
+ const out = [];
41
+ const count = Math.max(2, n);
42
+ for (let i = 0; i < count; i++) {
43
+ const d = (i / (count - 1)) * total;
44
+ let k = 1;
45
+ while (k < seg.length && seg[k] < d)
46
+ k++;
47
+ const a = pts[k - 1];
48
+ const b = pts[Math.min(k, pts.length - 1)];
49
+ const len = seg[k] - seg[k - 1];
50
+ const t = len > 0 ? (d - seg[k - 1]) / len : 0;
51
+ const x = lerp(a.x, b.x, t);
52
+ const y = lerp(a.y, b.y, t);
53
+ const angle = (Math.atan2(b.y - a.y, b.x - a.x) * 180) / Math.PI;
54
+ out.push({ x, y, angle });
55
+ }
56
+ return out;
57
+ }
58
+ /** 同心外/内路径按同一参数下标配对(不要各自按弧长重采样,否则角度错位、径向厚度抖动)。 */
59
+ function dualPath(outer, inner) {
60
+ const count = Math.min(outer.length, inner.length);
61
+ if (count < 2)
62
+ return [];
63
+ const out = [];
64
+ for (let k = 0; k < count; k++) {
65
+ const a = outer[k];
66
+ const b = inner[k];
67
+ const nxt = outer[Math.min(k + 1, count - 1)];
68
+ const prv = outer[Math.max(k - 1, 0)];
69
+ const angle = (Math.atan2(nxt.y - prv.y, nxt.x - prv.x) * 180) / Math.PI;
70
+ out.push({
71
+ x: (a.x + b.x) / 2,
72
+ y: (a.y + b.y) / 2,
73
+ angle,
74
+ radial: Math.max(1, Math.hypot(a.x - b.x, a.y - b.y)),
75
+ });
76
+ }
77
+ return out;
78
+ }
79
+ function offsetToward(pts, target, dist) {
80
+ const n = pts.length;
81
+ if (n < 2)
82
+ return pts;
83
+ return pts.map((p, i) => {
84
+ const nxt = pts[Math.min(i + 1, n - 1)];
85
+ const prv = pts[Math.max(i - 1, 0)];
86
+ const ang = Math.atan2(nxt.y - prv.y, nxt.x - prv.x);
87
+ let nx = -Math.sin(ang);
88
+ let ny = Math.cos(ang);
89
+ if (nx * (target.x - p.x) + ny * (target.y - p.y) < 0) {
90
+ nx = -nx;
91
+ ny = -ny;
92
+ }
93
+ return { x: p.x + nx * dist, y: p.y + ny * dist };
94
+ });
95
+ }
96
+ function envelopePath(prst, x, y, w, h, n) {
97
+ const cy = y + h / 2;
98
+ const topFlat = y + h * 0.12;
99
+ const botFlat = y + h * 0.88;
100
+ const minHalf = h * 0.12;
101
+ const maxHalf = h * 0.48;
102
+ const outer = [];
103
+ const inner = [];
104
+ for (let i = 0; i <= n; i++) {
105
+ const t = i / n;
106
+ const px = x + t * w;
107
+ const bulge = Math.sin(Math.PI * t);
108
+ const inflate = prst.startsWith("textInflate");
109
+ const half = inflate
110
+ ? minHalf + (maxHalf - minHalf) * bulge
111
+ : maxHalf - (maxHalf - minHalf) * bulge;
112
+ if (prst === "textInflateTop" || prst === "textDeflateTop") {
113
+ outer.push({ x: px, y: cy - half });
114
+ inner.push({ x: px, y: botFlat });
115
+ }
116
+ else if (prst === "textInflateBottom" || prst === "textDeflateBottom") {
117
+ outer.push({ x: px, y: topFlat });
118
+ inner.push({ x: px, y: cy + half });
119
+ }
120
+ else {
121
+ outer.push({ x: px, y: cy - half });
122
+ inner.push({ x: px, y: cy + half });
123
+ }
124
+ }
125
+ return dualPath(outer, inner);
126
+ }
127
+ function clamp(v, lo, hi) {
128
+ return Math.min(hi, Math.max(lo, v));
129
+ }
130
+ function arcPoints(cx, cy, rx, ry, a0, a1, n = 48) {
131
+ const pts = [];
132
+ for (let i = 0; i <= n; i++) {
133
+ const t = i / n;
134
+ const a = a0 + (a1 - a0) * t;
135
+ pts.push({ x: cx + Math.cos(a) * rx, y: cy + Math.sin(a) * ry });
136
+ }
137
+ return pts;
138
+ }
139
+ /** 在文字框内生成 warp 基线采样(沿路径从左到右)。 */
140
+ function sampleWarpPath(prst, box, avLst) {
141
+ const { x, y, w, h } = box;
142
+ const cx = x + w / 2;
143
+ const cy = y + h / 2;
144
+ const amp = adj(avLst, "adj", 0.25);
145
+ const n = 64;
146
+ const wave = (periods, phase = 0) => {
147
+ const pts = [];
148
+ const a = Math.max(4, h * amp);
149
+ for (let i = 0; i <= n; i++) {
150
+ const t = i / n;
151
+ pts.push({
152
+ x: x + t * w,
153
+ y: cy + Math.sin((t * periods + phase) * Math.PI * 2) * a,
154
+ });
155
+ }
156
+ return pts;
157
+ };
158
+ switch (prst) {
159
+ case "textArchUp":
160
+ return samplePolyline(arcPoints(cx, y + h * 0.92, w / 2, -h * 0.82, Math.PI, 0), n);
161
+ case "textArchUpPour": {
162
+ const outer = arcPoints(cx, y + h * 0.92, w / 2, -h * 0.82, Math.PI, 0);
163
+ const band = Math.max(6, Math.min(w, h) * clamp(0.38 + amp * 0.25, 0.22, 0.7));
164
+ return dualPath(outer, offsetToward(outer, { x: cx, y: cy }, band));
165
+ }
166
+ case "textArchDown":
167
+ return samplePolyline(arcPoints(cx, y + h * 0.08, w / 2, h * 0.82, Math.PI, 0), n);
168
+ case "textArchDownPour": {
169
+ const outer = arcPoints(cx, y + h * 0.08, w / 2, h * 0.82, Math.PI, 0);
170
+ const band = Math.max(6, Math.min(w, h) * clamp(0.38 + amp * 0.25, 0.22, 0.7));
171
+ return dualPath(outer, offsetToward(outer, { x: cx, y: cy }, band));
172
+ }
173
+ case "textArchLeft":
174
+ return samplePolyline(arcPoints(x + w * 0.92, cy, w * 0.82, h / 2, -Math.PI / 2, -3 * Math.PI / 2), n);
175
+ case "textArchLeftPour": {
176
+ const outer = arcPoints(x + w * 0.92, cy, w * 0.82, h / 2, -Math.PI / 2, -3 * Math.PI / 2);
177
+ const band = Math.max(6, Math.min(w, h) * clamp(0.38 + amp * 0.25, 0.22, 0.7));
178
+ return dualPath(outer, offsetToward(outer, { x: cx, y: cy }, band));
179
+ }
180
+ case "textArchRight":
181
+ return samplePolyline(arcPoints(x + w * 0.08, cy, w * 0.82, h / 2, -Math.PI / 2, Math.PI / 2), n);
182
+ case "textArchRightPour": {
183
+ const outer = arcPoints(x + w * 0.08, cy, w * 0.82, h / 2, -Math.PI / 2, Math.PI / 2);
184
+ const band = Math.max(6, Math.min(w, h) * clamp(0.38 + amp * 0.25, 0.22, 0.7));
185
+ return dualPath(outer, offsetToward(outer, { x: cx, y: cy }, band));
186
+ }
187
+ case "textLeft":
188
+ return samplePolyline([
189
+ { x: x + w * 0.08, y },
190
+ { x: x + w * 0.08, y: y + h },
191
+ ], n);
192
+ case "textRight":
193
+ return samplePolyline([
194
+ { x: x + w * 0.92, y },
195
+ { x: x + w * 0.92, y: y + h },
196
+ ], n);
197
+ case "textUp":
198
+ return samplePolyline([
199
+ { x: cx, y: y + h },
200
+ { x: cx, y },
201
+ ], n);
202
+ case "textDown":
203
+ return samplePolyline([
204
+ { x: cx, y },
205
+ { x: cx, y: y + h },
206
+ ], n);
207
+ case "textCircle":
208
+ case "textButton":
209
+ case "textRingOutside":
210
+ return samplePolyline(arcPoints(cx, cy, w / 2 * 0.92, h / 2 * 0.92, Math.PI, Math.PI * 3), n);
211
+ case "textRingInside":
212
+ return samplePolyline(arcPoints(cx, cy, w / 2 * (0.42 + amp * 0.2), h / 2 * (0.42 + amp * 0.2), Math.PI, Math.PI * 3), n);
213
+ case "textCirclePour":
214
+ case "textButtonPour": {
215
+ const outer = arcPoints(cx, cy, w / 2 * 0.92, h / 2 * 0.92, Math.PI, Math.PI * 3);
216
+ const band = Math.max(6, Math.min(w, h) * clamp(0.36 + amp * 0.25, 0.2, 0.65));
217
+ return dualPath(outer, offsetToward(outer, { x: cx, y: cy }, band));
218
+ }
219
+ case "textWave1":
220
+ case "textDoubleWave1":
221
+ return samplePolyline(wave(1), n);
222
+ case "textWave2":
223
+ case "textWave4":
224
+ return samplePolyline(wave(2), n);
225
+ case "textCurveUp":
226
+ case "textCanUp":
227
+ return samplePolyline([
228
+ { x, y: y + h * 0.72 },
229
+ { x: cx, y: y + h * 0.22 },
230
+ { x: x + w, y: y + h * 0.72 },
231
+ ], n);
232
+ case "textCurveDown":
233
+ case "textCanDown":
234
+ return samplePolyline([
235
+ { x, y: y + h * 0.28 },
236
+ { x: cx, y: y + h * 0.78 },
237
+ { x: x + w, y: y + h * 0.28 },
238
+ ], n);
239
+ case "textChevron":
240
+ case "textChevronInverted": {
241
+ const up = prst === "textChevron";
242
+ const midY = up ? y + h * 0.28 : y + h * 0.72;
243
+ const sideY = up ? y + h * 0.72 : y + h * 0.28;
244
+ return samplePolyline([
245
+ { x, y: sideY },
246
+ { x: cx, y: midY },
247
+ { x: x + w, y: sideY },
248
+ ], n);
249
+ }
250
+ case "textTriangle":
251
+ case "textTriangleInverted": {
252
+ const up = prst === "textTriangle";
253
+ const top = up ? y + h * 0.2 : y + h * 0.8;
254
+ const bot = up ? y + h * 0.8 : y + h * 0.2;
255
+ return samplePolyline([
256
+ { x, y: bot },
257
+ { x: cx, y: top },
258
+ { x: x + w, y: bot },
259
+ ], n);
260
+ }
261
+ case "textStop":
262
+ return samplePolyline([
263
+ { x, y: cy },
264
+ { x: x + w * 0.12, y: y + h * 0.28 },
265
+ { x: x + w * 0.88, y: y + h * 0.28 },
266
+ { x: x + w, y: cy },
267
+ ], n);
268
+ case "textSlantUp":
269
+ return samplePolyline([
270
+ { x, y: y + h * 0.78 },
271
+ { x: x + w, y: y + h * 0.22 },
272
+ ], n);
273
+ case "textSlantDown":
274
+ return samplePolyline([
275
+ { x, y: y + h * 0.22 },
276
+ { x: x + w, y: y + h * 0.78 },
277
+ ], n);
278
+ case "textCascadeUp":
279
+ return samplePolyline([
280
+ { x, y: y + h * 0.7 },
281
+ { x: x + w, y: y + h * 0.3 },
282
+ ], n);
283
+ case "textCascadeDown":
284
+ return samplePolyline([
285
+ { x, y: y + h * 0.3 },
286
+ { x: x + w, y: y + h * 0.7 },
287
+ ], n);
288
+ case "textInflate":
289
+ case "textInflateTop":
290
+ case "textInflateBottom":
291
+ case "textDeflate":
292
+ case "textDeflateTop":
293
+ case "textDeflateBottom":
294
+ return envelopePath(prst, x, y, w, h, n);
295
+ default:
296
+ return samplePolyline([
297
+ { x, y: cy },
298
+ { x: x + w, y: cy },
299
+ ], n);
300
+ }
301
+ }
302
+ function glyphScaleY(prst, t) {
303
+ switch (prst) {
304
+ case "textInflate":
305
+ case "textInflateBottom":
306
+ case "textInflateTop":
307
+ return 1 + 0.45 * Math.sin(Math.PI * t);
308
+ case "textDeflate":
309
+ case "textDeflateBottom":
310
+ case "textDeflateTop":
311
+ case "textDeflateInflate":
312
+ case "textDeflateInflateDeflate":
313
+ return Math.max(0.35, 1 - 0.45 * Math.sin(Math.PI * t));
314
+ case "textFadeRight":
315
+ return Math.max(0.25, 1 - 0.75 * t);
316
+ case "textFadeLeft":
317
+ return Math.max(0.25, 0.25 + 0.75 * t);
318
+ case "textFadeUp":
319
+ case "textFadeDown":
320
+ return Math.max(0.25, 0.35 + 0.65 * (prst === "textFadeUp" ? t : 1 - t));
321
+ case "textCascadeUp":
322
+ case "textCascadeDown":
323
+ return 0.55 + 0.7 * t;
324
+ default:
325
+ return 1;
326
+ }
327
+ }
328
+ function placeGlyphsOnWarp(glyphs, samples, prst) {
329
+ const total = glyphs.reduce((a, g) => a + g.width, 0);
330
+ if (!samples.length || total <= 0)
331
+ return [];
332
+ let acc = 0;
333
+ const out = [];
334
+ for (const g of glyphs) {
335
+ const t = (acc + g.width / 2) / total;
336
+ const idx = Math.min(samples.length - 1, Math.max(0, t * (samples.length - 1)));
337
+ const i0 = Math.floor(idx);
338
+ const i1 = Math.min(samples.length - 1, i0 + 1);
339
+ const f = idx - i0;
340
+ const a = samples[i0];
341
+ const b = samples[i1];
342
+ const radial = a.radial != null && b.radial != null ? lerp(a.radial, b.radial, f) : undefined;
343
+ out.push({
344
+ ch: g.ch,
345
+ x: lerp(a.x, b.x, f),
346
+ y: lerp(a.y, b.y, f),
347
+ angle: lerp(a.angle, b.angle, f),
348
+ scaleY: radial != null && g.fontSize > 0
349
+ ? Math.max(0.35, radial / g.fontSize)
350
+ : glyphScaleY(prst ?? "", t),
351
+ run: g.run,
352
+ fontSize: g.fontSize,
353
+ });
354
+ acc += g.width;
355
+ }
356
+ return out;
357
+ }
358
+ function flattenWarpGlyphs(s, measure) {
359
+ const out = [];
360
+ for (const p of s.paragraphs) {
361
+ for (const run of p.runs) {
362
+ const fs = run.fontSize ?? p.defRunStyle?.fontSize ?? text_shared_1.FALLBACK_FONT_SIZE;
363
+ for (const ch of Array.from(run.text ?? "")) {
364
+ if (ch === "\n")
365
+ continue;
366
+ const width = measure(run, ch) + (run.letterSpacing ?? 0);
367
+ out.push({ ch, width, run, fontSize: fs });
368
+ }
369
+ }
370
+ if (p !== s.paragraphs[s.paragraphs.length - 1]) {
371
+ const last = out[out.length - 1];
372
+ if (last)
373
+ out.push({ ch: " ", width: last.fontSize * 0.3, run: last.run, fontSize: last.fontSize });
374
+ }
375
+ }
376
+ return out;
377
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ // fromWordArt:老艺术字把字号当排版起点,画面上的字形要铺满形状框。
3
+ // WPS / PowerPoint 对 `bodyPr@fromWordArt="1"` 做非均匀拉伸:墨迹外接矩形贴到
4
+ // xfrm 的外框。`sz` 仍是文件里的字号,渲染时不改模型,导出仍写原值。
5
+ //
6
+ // textPlain / textNoShape 没有这个标志时仍按普通文本排,不在这里拉伸。
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.wordArtFitTransform = wordArtFitTransform;
9
+ const MIN_INK = 0.5;
10
+ const MAX_SCALE = 8;
11
+ /**
12
+ * 把已排好的墨迹框映射到形状框。
13
+ * 变换顺序是先缩放再平移:`x' = x * sx + tx`。
14
+ * 近乎 1:1 或比例离谱时返回 null,调用方保持原排版。
15
+ */
16
+ function wordArtFitTransform(ink, box) {
17
+ if (!(ink.w > MIN_INK && ink.h > MIN_INK && box.w > MIN_INK && box.h > MIN_INK))
18
+ return null;
19
+ const sx = box.w / ink.w;
20
+ const sy = box.h / ink.h;
21
+ if (!Number.isFinite(sx) || !Number.isFinite(sy))
22
+ return null;
23
+ if (sx > MAX_SCALE || sy > MAX_SCALE || sx < 1 / MAX_SCALE || sy < 1 / MAX_SCALE)
24
+ return null;
25
+ if (Math.abs(sx - 1) < 0.02 && Math.abs(sy - 1) < 0.02)
26
+ return null;
27
+ return {
28
+ sx,
29
+ sy,
30
+ tx: box.x - ink.x * sx,
31
+ ty: box.y - ink.y * sy,
32
+ };
33
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVideoPosterFrame = getVideoPosterFrame;
4
+ exports.waitForVideoFrames = waitForVideoFrames;
5
+ const cache = new Map();
6
+ function cacheKey(src, seekTo) {
7
+ return `${src}#t=${seekTo.toFixed(3)}`;
8
+ }
9
+ function getVideoPosterFrame(src, onReady, seekTo = 0.08) {
10
+ if (typeof document === "undefined" || !src)
11
+ return null;
12
+ if (src.startsWith("file:"))
13
+ return null;
14
+ const key = cacheKey(src, seekTo);
15
+ const hit = cache.get(key);
16
+ if (hit?.status === "ready")
17
+ return hit.img;
18
+ if (hit?.status === "failed")
19
+ return null;
20
+ if (hit?.status === "pending") {
21
+ if (onReady)
22
+ void hit.wait.then(() => onReady());
23
+ return null;
24
+ }
25
+ const wait = captureFrame(src, seekTo).then((img) => {
26
+ if (img) {
27
+ cache.set(key, { status: "ready", img });
28
+ onReady?.();
29
+ }
30
+ else {
31
+ cache.set(key, { status: "failed" });
32
+ }
33
+ });
34
+ cache.set(key, { status: "pending", wait });
35
+ return null;
36
+ }
37
+ function waitForVideoFrames(timeoutMs = 8000) {
38
+ const pending = [];
39
+ for (const e of cache.values()) {
40
+ if (e.status === "pending")
41
+ pending.push(e.wait);
42
+ }
43
+ if (!pending.length)
44
+ return Promise.resolve();
45
+ return Promise.race([
46
+ Promise.all(pending).then(() => undefined),
47
+ new Promise((resolve) => setTimeout(resolve, timeoutMs)),
48
+ ]);
49
+ }
50
+ function captureFrame(src, seekTo) {
51
+ return new Promise((resolve) => {
52
+ const video = document.createElement("video");
53
+ video.muted = true;
54
+ video.playsInline = true;
55
+ video.preload = "auto";
56
+ if (!src.startsWith("data:") && !src.startsWith("blob:"))
57
+ video.crossOrigin = "anonymous";
58
+ let settled = false;
59
+ const done = (img) => {
60
+ if (settled)
61
+ return;
62
+ settled = true;
63
+ video.removeAttribute("src");
64
+ video.load();
65
+ resolve(img);
66
+ };
67
+ const grab = () => {
68
+ try {
69
+ const w = video.videoWidth;
70
+ const h = video.videoHeight;
71
+ if (!w || !h) {
72
+ done(null);
73
+ return;
74
+ }
75
+ const c = document.createElement("canvas");
76
+ c.width = w;
77
+ c.height = h;
78
+ const ctx = c.getContext("2d");
79
+ if (!ctx) {
80
+ done(null);
81
+ return;
82
+ }
83
+ ctx.drawImage(video, 0, 0);
84
+ const url = c.toDataURL("image/jpeg", 0.72);
85
+ const img = new Image();
86
+ img.onload = () => done(img);
87
+ img.onerror = () => done(null);
88
+ img.src = url;
89
+ }
90
+ catch {
91
+ done(null);
92
+ }
93
+ };
94
+ video.addEventListener("error", () => done(null), { once: true });
95
+ video.addEventListener("loadeddata", () => {
96
+ const dur = Number.isFinite(video.duration) ? video.duration : 0;
97
+ const t = Math.max(0, dur > 0 ? Math.min(seekTo, Math.max(0, dur - 0.05)) : seekTo);
98
+ if (t <= 0.001 && video.readyState >= 2) {
99
+ grab();
100
+ return;
101
+ }
102
+ video.addEventListener("seeked", grab, { once: true });
103
+ try {
104
+ video.currentTime = t;
105
+ }
106
+ catch {
107
+ grab();
108
+ }
109
+ }, { once: true });
110
+ video.src = src;
111
+ setTimeout(() => done(null), 8000);
112
+ });
113
+ }