@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,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scaleTextShapeForAutofit = scaleTextShapeForAutofit;
|
|
4
|
+
exports.autofitTextShape = autofitTextShape;
|
|
5
|
+
const text_shared_1 = require("./text-shared");
|
|
6
|
+
const MIN_SCALE = 0.25;
|
|
7
|
+
const OVERFLOW_TOLERANCE = 1.05;
|
|
8
|
+
function scaleTextShapeForAutofit(s, scale) {
|
|
9
|
+
const scaleRun = (r) => ({ ...r, fontSize: (r.fontSize ?? text_shared_1.FALLBACK_FONT_SIZE) * scale });
|
|
10
|
+
const scalePara = (p) => ({
|
|
11
|
+
...p,
|
|
12
|
+
...(p.lineHeightPx !== undefined ? { lineHeightPx: p.lineHeightPx * scale } : {}),
|
|
13
|
+
...(p.spaceBeforePx !== undefined ? { spaceBeforePx: p.spaceBeforePx * scale } : {}),
|
|
14
|
+
...(p.spaceAfterPx !== undefined ? { spaceAfterPx: p.spaceAfterPx * scale } : {}),
|
|
15
|
+
runs: p.runs.map(scaleRun),
|
|
16
|
+
});
|
|
17
|
+
return { ...s, paragraphs: s.paragraphs.map(scalePara) };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* normAutofit 文本溢出时返回缩小后的派生 shape;不溢出(或非 normAutofit)原样返回。
|
|
21
|
+
* measureTotalH 由调用方注入(svg / canvas 后端的段落排版逻辑不同但签名一致)。
|
|
22
|
+
*/
|
|
23
|
+
function autofitTextShape(s, innerH, measureTotalH) {
|
|
24
|
+
if (s.autoFit?.kind !== "normAutofit")
|
|
25
|
+
return s;
|
|
26
|
+
if (innerH <= 0 || s.paragraphs.length === 0)
|
|
27
|
+
return s;
|
|
28
|
+
let baseH;
|
|
29
|
+
try {
|
|
30
|
+
baseH = measureTotalH(s);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return s;
|
|
34
|
+
}
|
|
35
|
+
if (!Number.isFinite(baseH) || baseH <= innerH * OVERFLOW_TOLERANCE)
|
|
36
|
+
return s;
|
|
37
|
+
// 缩字号后 wrap 行数会变(通常变少),单次比例外推不够准 —— 迭代 3 轮逼近。
|
|
38
|
+
let scale = Math.max(MIN_SCALE, innerH / baseH);
|
|
39
|
+
let candidate = scaleTextShapeForAutofit(s, scale);
|
|
40
|
+
for (let i = 0; i < 3; i++) {
|
|
41
|
+
const h = measureTotalH(candidate);
|
|
42
|
+
if (h <= innerH || scale <= MIN_SCALE)
|
|
43
|
+
break;
|
|
44
|
+
scale = Math.max(MIN_SCALE, scale * (innerH / h));
|
|
45
|
+
candidate = scaleTextShapeForAutofit(s, scale);
|
|
46
|
+
}
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hitTestTextRunAt = hitTestTextRunAt;
|
|
4
|
+
exports.runLinkAt = runLinkAt;
|
|
5
|
+
const text_shared_1 = require("./text-shared");
|
|
6
|
+
const text_layout_1 = require("./text-layout");
|
|
7
|
+
const text_stacked_1 = require("./text-stacked");
|
|
8
|
+
let measureCtx;
|
|
9
|
+
function fallbackMeasureCtx() {
|
|
10
|
+
let font = "16px sans-serif";
|
|
11
|
+
return {
|
|
12
|
+
get font() {
|
|
13
|
+
return font;
|
|
14
|
+
},
|
|
15
|
+
set font(v) {
|
|
16
|
+
font = v;
|
|
17
|
+
},
|
|
18
|
+
measureText(text) {
|
|
19
|
+
const fs = Number(/(\d+(?:\.\d+)?)px/.exec(font)?.[1]) || 16;
|
|
20
|
+
let w = 0;
|
|
21
|
+
for (const ch of Array.from(text)) {
|
|
22
|
+
w += ch === " " || ch === "\t" ? fs * 0.3 : fs * 0.6;
|
|
23
|
+
}
|
|
24
|
+
return { width: w };
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function getMeasureCtx() {
|
|
29
|
+
if (measureCtx !== undefined)
|
|
30
|
+
return measureCtx;
|
|
31
|
+
if (typeof document === "undefined") {
|
|
32
|
+
measureCtx = fallbackMeasureCtx();
|
|
33
|
+
return measureCtx;
|
|
34
|
+
}
|
|
35
|
+
const c = document.createElement("canvas");
|
|
36
|
+
measureCtx = c.getContext("2d") ?? fallbackMeasureCtx();
|
|
37
|
+
return measureCtx;
|
|
38
|
+
}
|
|
39
|
+
function rotatePoint(x, y, cx, cy, deg) {
|
|
40
|
+
if (!deg)
|
|
41
|
+
return { x, y };
|
|
42
|
+
const rad = (deg * Math.PI) / 180;
|
|
43
|
+
const cos = Math.cos(rad);
|
|
44
|
+
const sin = Math.sin(rad);
|
|
45
|
+
const dx = x - cx;
|
|
46
|
+
const dy = y - cy;
|
|
47
|
+
return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 按与渲染层相同的换行 / 对齐 / 边距,命中文字框内某个 run。
|
|
51
|
+
* 竖排堆叠走列布局;`vert`/`vert270`/`bodyRot` 先把点逆旋转到布局坐标系。
|
|
52
|
+
*/
|
|
53
|
+
function hitTestTextRunAt(shape, x, y) {
|
|
54
|
+
if (shape.kind !== "text")
|
|
55
|
+
return undefined;
|
|
56
|
+
if ((0, text_shared_1.isVerticalStack)(shape.vert)) {
|
|
57
|
+
return hitTestStackedRunAt(shape, x, y);
|
|
58
|
+
}
|
|
59
|
+
const ctx = getMeasureCtx();
|
|
60
|
+
if (!ctx)
|
|
61
|
+
return undefined;
|
|
62
|
+
const b = shape.bbox;
|
|
63
|
+
const cx = b.x + b.w / 2;
|
|
64
|
+
const cy = b.y + b.h / 2;
|
|
65
|
+
const rot = (shape.bodyRot ?? 0) + (0, text_shared_1.vertRotateDeg)(shape.vert);
|
|
66
|
+
const p = rotatePoint(x, y, cx, cy, -rot);
|
|
67
|
+
const ins = shape.insets ?? text_shared_1.DEFAULT_TEXT_INSETS;
|
|
68
|
+
const swap = (0, text_shared_1.vertRotateDeg)(shape.vert) !== 0;
|
|
69
|
+
const baseW = Math.max(1, b.w - ins.l - ins.r);
|
|
70
|
+
const baseH = Math.max(1, b.h - ins.t - ins.b);
|
|
71
|
+
const innerW = swap ? baseH : baseW;
|
|
72
|
+
const innerH = swap ? baseW : baseH;
|
|
73
|
+
const innerX = swap ? b.x + b.w / 2 - innerW / 2 : b.x + ins.l;
|
|
74
|
+
const innerY = swap ? b.y + b.h / 2 - innerH / 2 : b.y + ins.t;
|
|
75
|
+
return hitTestHorizontalAt(shape, p.x, p.y, innerX, innerY, innerW, innerH, ctx);
|
|
76
|
+
}
|
|
77
|
+
function hitTestHorizontalAt(shape, x, y, innerX, innerY, innerW, innerH, ctx) {
|
|
78
|
+
const paras = shape.paragraphs ?? [];
|
|
79
|
+
const numCol = Math.max(1, Math.floor(shape.numCol ?? 1));
|
|
80
|
+
const spcCol = Math.max(0, shape.spcCol ?? 0);
|
|
81
|
+
const colW = numCol > 1 ? Math.max(0, (innerW - spcCol * (numCol - 1)) / numCol) : innerW;
|
|
82
|
+
const layouts = paras.map((p) => layoutPara(shape, p, colW, ctx));
|
|
83
|
+
const cols = numCol > 1 ? distributeParaCols(layouts, innerH, numCol) : [layouts.map((_, i) => i)];
|
|
84
|
+
const rtlCol = !!shape.rtlCol;
|
|
85
|
+
const respectFirstLast = shape.spcFirstLastPara !== false;
|
|
86
|
+
const lastIdx = layouts.length - 1;
|
|
87
|
+
const effBefore = (k) => (!respectFirstLast && k === 0 ? 0 : layouts[k].before);
|
|
88
|
+
const effAfter = (k) => (!respectFirstLast && k === lastIdx ? 0 : layouts[k].after);
|
|
89
|
+
const vertOverflow = shape.vertOverflow ?? "overflow";
|
|
90
|
+
const clampMiddle = (space) => Math.max(0, space);
|
|
91
|
+
const clampBottom = (space) => (vertOverflow === "overflow" ? space : Math.max(0, space));
|
|
92
|
+
for (let ci = 0; ci < cols.length; ci++) {
|
|
93
|
+
const visualCol = rtlCol ? cols.length - 1 - ci : ci;
|
|
94
|
+
const colX = innerX + visualCol * (colW + spcCol);
|
|
95
|
+
if (x < colX - 2 || x > colX + colW + 2)
|
|
96
|
+
continue;
|
|
97
|
+
const colParas = cols[ci];
|
|
98
|
+
const totalH = colParas.reduce((acc, k) => acc + effBefore(k) + layouts[k].totalH + effAfter(k), 0);
|
|
99
|
+
let yCursor = innerY;
|
|
100
|
+
if (shape.vAlign === "middle")
|
|
101
|
+
yCursor = innerY + clampMiddle((innerH - totalH) / 2);
|
|
102
|
+
else if (shape.vAlign === "bottom")
|
|
103
|
+
yCursor = innerY + clampBottom(innerH - totalH);
|
|
104
|
+
for (const k of colParas) {
|
|
105
|
+
const pl = layouts[k];
|
|
106
|
+
const yStart = yCursor + effBefore(k);
|
|
107
|
+
const hit = hitPara(pl, colX, colW, yStart, x, y);
|
|
108
|
+
if (hit)
|
|
109
|
+
return hit;
|
|
110
|
+
yCursor += effBefore(k) + pl.totalH + effAfter(k);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
function distributeParaCols(layouts, innerH, numCol) {
|
|
116
|
+
const cols = Array.from({ length: numCol }, () => []);
|
|
117
|
+
const heights = new Array(numCol).fill(0);
|
|
118
|
+
let curCol = 0;
|
|
119
|
+
for (let i = 0; i < layouts.length; i++) {
|
|
120
|
+
const l = layouts[i];
|
|
121
|
+
const h = l.before + l.totalH + l.after;
|
|
122
|
+
if (curCol < numCol - 1 && heights[curCol] > 0 && heights[curCol] + h > innerH) {
|
|
123
|
+
curCol += 1;
|
|
124
|
+
}
|
|
125
|
+
cols[curCol].push(i);
|
|
126
|
+
heights[curCol] += h;
|
|
127
|
+
}
|
|
128
|
+
return cols;
|
|
129
|
+
}
|
|
130
|
+
function hitTestStackedRunAt(s, x, y) {
|
|
131
|
+
const vert = s.vert;
|
|
132
|
+
const b = s.bbox;
|
|
133
|
+
const ins = s.insets ?? text_shared_1.DEFAULT_TEXT_INSETS;
|
|
134
|
+
const innerX = b.x + ins.l;
|
|
135
|
+
const innerY = b.y + ins.t;
|
|
136
|
+
const innerW = Math.max(1, b.w - ins.l - ins.r);
|
|
137
|
+
const innerH = Math.max(1, b.h - ins.t - ins.b);
|
|
138
|
+
const ltrColumns = (0, text_shared_1.stackedColumnsLtr)(vert, s.rtlCol);
|
|
139
|
+
const spcCol = Math.max(0, s.spcCol ?? 0);
|
|
140
|
+
const allowVertOverflow = (s.vertOverflow ?? "overflow") === "overflow";
|
|
141
|
+
const clampMiddle = (sp) => Math.max(0, sp);
|
|
142
|
+
const clampEnd = (sp) => (allowVertOverflow ? sp : Math.max(0, sp));
|
|
143
|
+
const paraColumns = [];
|
|
144
|
+
for (const p of s.paragraphs ?? []) {
|
|
145
|
+
const glyphs = (0, text_stacked_1.buildStackedGlyphs)(p, undefined, vert);
|
|
146
|
+
if (glyphs.length === 0) {
|
|
147
|
+
const fs = p.runs.find((r) => r.fontSize)?.fontSize ?? text_shared_1.FALLBACK_FONT_SIZE;
|
|
148
|
+
paraColumns.push({ paragraph: p, glyphs: [], width: fs, isFirstColOfPara: true });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const wrapped = (0, text_stacked_1.wrapGlyphsIntoColumns)(glyphs, innerH);
|
|
152
|
+
for (let wi = 0; wi < wrapped.length; wi++) {
|
|
153
|
+
const colGlyphs = wrapped[wi];
|
|
154
|
+
const colW = colGlyphs.reduce((m, g) => Math.max(m, g.fontSize), 0);
|
|
155
|
+
paraColumns.push({
|
|
156
|
+
paragraph: p,
|
|
157
|
+
glyphs: colGlyphs,
|
|
158
|
+
width: colW,
|
|
159
|
+
isFirstColOfPara: wi === 0,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
let cursorX = ltrColumns ? innerX : innerX + innerW;
|
|
164
|
+
for (const col of paraColumns) {
|
|
165
|
+
if (!ltrColumns)
|
|
166
|
+
cursorX -= col.width;
|
|
167
|
+
const colLeft = cursorX;
|
|
168
|
+
const colRight = cursorX + col.width;
|
|
169
|
+
const glyphs = col.glyphs;
|
|
170
|
+
const totalH = glyphs.reduce((acc, g) => acc + g.stepY, 0);
|
|
171
|
+
const space = innerH - totalH;
|
|
172
|
+
let yCursor = innerY;
|
|
173
|
+
let extraStep = 0;
|
|
174
|
+
const pAlign = col.paragraph.align;
|
|
175
|
+
if (pAlign === "middle")
|
|
176
|
+
yCursor = innerY + clampMiddle(space / 2);
|
|
177
|
+
else if (pAlign === "end")
|
|
178
|
+
yCursor = innerY + clampEnd(space);
|
|
179
|
+
else if (pAlign === "justify" && glyphs.length > 1 && space > 0) {
|
|
180
|
+
extraStep = space / (glyphs.length - 1);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
if (s.vAlign === "middle")
|
|
184
|
+
yCursor = innerY + clampMiddle(space / 2);
|
|
185
|
+
else if (s.vAlign === "bottom")
|
|
186
|
+
yCursor = innerY + clampEnd(space);
|
|
187
|
+
}
|
|
188
|
+
if (col.isFirstColOfPara && glyphs.length > 0 && col.paragraph.bullet && col.paragraph.bullet.kind !== "none") {
|
|
189
|
+
yCursor += glyphs[0].stepY;
|
|
190
|
+
}
|
|
191
|
+
for (let gi = 0; gi < glyphs.length; gi++) {
|
|
192
|
+
const g = glyphs[gi];
|
|
193
|
+
const y1 = yCursor;
|
|
194
|
+
const y2 = yCursor + g.stepY + (gi < glyphs.length - 1 ? extraStep : 0);
|
|
195
|
+
if (x >= colLeft && x <= colRight && y >= y1 && y <= y2)
|
|
196
|
+
return g.run;
|
|
197
|
+
yCursor = y2;
|
|
198
|
+
}
|
|
199
|
+
if (ltrColumns)
|
|
200
|
+
cursorX += col.width + spcCol;
|
|
201
|
+
else
|
|
202
|
+
cursorX -= spcCol;
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
function layoutPara(s, p, maxWidth, ctx) {
|
|
207
|
+
const fallbackFs = (0, text_shared_1.paragraphFallbackFontSize)(p);
|
|
208
|
+
const rtl = !!p.rtl;
|
|
209
|
+
const align = (0, text_layout_1.mapVisualAlignWithAnchorCtr)(s, p);
|
|
210
|
+
const marL = (rtl ? p.marginRight : p.marginLeft) ?? 0;
|
|
211
|
+
const indent = p.indent ?? 0;
|
|
212
|
+
const hasBullet = !!p.bullet && p.bullet.kind !== "none";
|
|
213
|
+
const firstLineIndent = hasBullet ? 0 : Math.max(0, indent);
|
|
214
|
+
const textAreaW = Math.max(0, maxWidth - marL);
|
|
215
|
+
const firstLineW = Math.max(0, textAreaW - firstLineIndent);
|
|
216
|
+
const lines = (0, text_layout_1.wrapParagraphIntoLines)(p, textAreaW, fallbackFs, ctx, firstLineW, s.wrap === false, undefined, s.compatLnSpc);
|
|
217
|
+
const totalH = lines.reduce((acc, l) => acc + l.height, 0);
|
|
218
|
+
return {
|
|
219
|
+
lines,
|
|
220
|
+
totalH,
|
|
221
|
+
before: p.spaceBeforePx ?? 0,
|
|
222
|
+
after: p.spaceAfterPx ?? 0,
|
|
223
|
+
rtl,
|
|
224
|
+
align,
|
|
225
|
+
marL,
|
|
226
|
+
firstLineIndent,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function hitPara(pl, innerX, innerW, yStart, x, y) {
|
|
230
|
+
const { rtl, align, marL, lines } = pl;
|
|
231
|
+
const textLeft = rtl ? innerX : innerX + marL;
|
|
232
|
+
const textRight = rtl ? innerX + innerW - marL : innerX + innerW;
|
|
233
|
+
let yLine = yStart;
|
|
234
|
+
for (let li = 0; li < lines.length; li++) {
|
|
235
|
+
const line = lines[li];
|
|
236
|
+
const y1 = yLine;
|
|
237
|
+
const y2 = yLine + line.height;
|
|
238
|
+
if (y >= y1 && y <= y2) {
|
|
239
|
+
let lineLeft = textLeft;
|
|
240
|
+
let lineRight = textRight;
|
|
241
|
+
if (li === 0 && pl.firstLineIndent > 0) {
|
|
242
|
+
if (rtl)
|
|
243
|
+
lineRight = textRight - pl.firstLineIndent;
|
|
244
|
+
else
|
|
245
|
+
lineLeft = textLeft + pl.firstLineIndent;
|
|
246
|
+
}
|
|
247
|
+
const lineAreaW = Math.max(0, lineRight - lineLeft);
|
|
248
|
+
const alignW = Math.max(0, line.width - line.hang);
|
|
249
|
+
let x0 = lineLeft;
|
|
250
|
+
if (align === "middle")
|
|
251
|
+
x0 = lineLeft + (lineAreaW - alignW) / 2;
|
|
252
|
+
else if (align === "end")
|
|
253
|
+
x0 = lineRight - alignW;
|
|
254
|
+
if (rtl) {
|
|
255
|
+
let xr = lineRight;
|
|
256
|
+
for (let i = line.segments.length - 1; i >= 0; i--) {
|
|
257
|
+
const seg = line.segments[i];
|
|
258
|
+
const x1 = xr - seg.width;
|
|
259
|
+
if (x >= x1 && x <= xr)
|
|
260
|
+
return seg.run;
|
|
261
|
+
xr = x1;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
let xl = x0;
|
|
266
|
+
for (const seg of line.segments) {
|
|
267
|
+
const x1 = xl + seg.width;
|
|
268
|
+
if (x >= xl && x <= x1)
|
|
269
|
+
return seg.run;
|
|
270
|
+
xl = x1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// 点在行高内但落在空白:取最近 segment 的 run(仍可能是链接)。
|
|
274
|
+
if (line.segments.length === 1)
|
|
275
|
+
return line.segments[0].run;
|
|
276
|
+
}
|
|
277
|
+
yLine += line.height;
|
|
278
|
+
}
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
function runLinkAt(shape, x, y, slot) {
|
|
282
|
+
const run = hitTestTextRunAt(shape, x, y);
|
|
283
|
+
return run?.[slot];
|
|
284
|
+
}
|