@retikz/core 0.1.0-alpha.3 → 0.1.0-alpha.5
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/dist/es/compile/compile.d.ts +9 -10
- package/dist/es/compile/compile.d.ts.map +1 -1
- package/dist/es/compile/compile.js +31 -8
- package/dist/es/compile/node.d.ts +56 -54
- package/dist/es/compile/node.d.ts.map +1 -1
- package/dist/es/compile/node.js +137 -39
- package/dist/es/compile/parseTarget.d.ts +3 -8
- package/dist/es/compile/parseTarget.d.ts.map +1 -1
- package/dist/es/compile/parseTarget.js +7 -19
- package/dist/es/compile/path.d.ts +2 -19
- package/dist/es/compile/path.d.ts.map +1 -1
- package/dist/es/compile/path.js +232 -213
- package/dist/es/compile/position.d.ts +4 -5
- package/dist/es/compile/position.d.ts.map +1 -1
- package/dist/es/compile/position.js +32 -5
- package/dist/es/compile/text-metrics.d.ts +2 -4
- package/dist/es/compile/text-metrics.d.ts.map +1 -1
- package/dist/es/compile/view-box.d.ts +1 -4
- package/dist/es/compile/view-box.d.ts.map +1 -1
- package/dist/es/compile/view-box.js +1 -4
- package/dist/es/geometry/arc.d.ts +3 -28
- package/dist/es/geometry/arc.d.ts.map +1 -1
- package/dist/es/geometry/arc.js +4 -31
- package/dist/es/geometry/bend.d.ts +2 -13
- package/dist/es/geometry/bend.d.ts.map +1 -1
- package/dist/es/geometry/bend.js +2 -13
- package/dist/es/geometry/circle.d.ts +5 -13
- package/dist/es/geometry/circle.d.ts.map +1 -1
- package/dist/es/geometry/circle.js +1 -4
- package/dist/es/geometry/diamond.d.ts +9 -24
- package/dist/es/geometry/diamond.d.ts.map +1 -1
- package/dist/es/geometry/diamond.js +4 -14
- package/dist/es/geometry/ellipse.d.ts +9 -15
- package/dist/es/geometry/ellipse.d.ts.map +1 -1
- package/dist/es/geometry/ellipse.js +4 -8
- package/dist/es/geometry/point.d.ts +8 -9
- package/dist/es/geometry/point.d.ts.map +1 -1
- package/dist/es/geometry/point.js +8 -9
- package/dist/es/geometry/polar.d.ts +12 -21
- package/dist/es/geometry/polar.d.ts.map +1 -1
- package/dist/es/geometry/polar.js +7 -14
- package/dist/es/geometry/rect.d.ts +8 -32
- package/dist/es/geometry/rect.d.ts.map +1 -1
- package/dist/es/geometry/rect.js +7 -33
- package/dist/es/geometry/segment.d.ts +11 -18
- package/dist/es/geometry/segment.d.ts.map +1 -1
- package/dist/es/geometry/segment.js +9 -16
- package/dist/es/index.d.ts +6 -10
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +7 -4
- package/dist/es/ir/coordinate.d.ts +65 -0
- package/dist/es/ir/coordinate.d.ts.map +1 -0
- package/dist/es/ir/coordinate.js +22 -0
- package/dist/es/ir/index.d.ts +1 -0
- package/dist/es/ir/index.d.ts.map +1 -1
- package/dist/es/ir/node.d.ts +290 -50
- package/dist/es/ir/node.d.ts.map +1 -1
- package/dist/es/ir/node.js +32 -38
- package/dist/es/ir/path/arrow.d.ts +210 -12
- package/dist/es/ir/path/arrow.d.ts.map +1 -1
- package/dist/es/ir/path/arrow.js +39 -12
- package/dist/es/ir/path/path.d.ts +477 -153
- package/dist/es/ir/path/path.d.ts.map +1 -1
- package/dist/es/ir/path/path.js +2 -2
- package/dist/es/ir/path/step.d.ts +395 -223
- package/dist/es/ir/path/step.d.ts.map +1 -1
- package/dist/es/ir/path/step.js +13 -14
- package/dist/es/ir/path/target.d.ts +25 -16
- package/dist/es/ir/path/target.d.ts.map +1 -1
- package/dist/es/ir/path/target.js +8 -6
- package/dist/es/ir/position/at-position.d.ts +43 -0
- package/dist/es/ir/position/at-position.d.ts.map +1 -0
- package/dist/es/ir/position/at-position.js +23 -0
- package/dist/es/ir/position/index.d.ts +2 -0
- package/dist/es/ir/position/index.d.ts.map +1 -1
- package/dist/es/ir/position/offset-position.d.ts +14 -0
- package/dist/es/ir/position/offset-position.d.ts.map +1 -0
- package/dist/es/ir/position/offset-position.js +14 -0
- package/dist/es/ir/position/polar-position.d.ts +1 -4
- package/dist/es/ir/position/polar-position.d.ts.map +1 -1
- package/dist/es/ir/position/polar-position.js +1 -4
- package/dist/es/ir/scene.d.ts +1736 -386
- package/dist/es/ir/scene.d.ts.map +1 -1
- package/dist/es/ir/scene.js +6 -1
- package/dist/es/parsers/parseTargetSugar.d.ts.map +1 -1
- package/dist/es/parsers/parseTargetSugar.js +6 -19
- package/dist/es/parsers/parseWay.d.ts +26 -118
- package/dist/es/parsers/parseWay.d.ts.map +1 -1
- package/dist/es/parsers/parseWay.js +19 -61
- package/dist/es/primitive/ellipse.d.ts +4 -14
- package/dist/es/primitive/ellipse.d.ts.map +1 -1
- package/dist/es/primitive/group.d.ts +21 -3
- package/dist/es/primitive/group.d.ts.map +1 -1
- package/dist/es/primitive/path.d.ts +67 -7
- package/dist/es/primitive/path.d.ts.map +1 -1
- package/dist/es/primitive/scene.d.ts +2 -4
- package/dist/es/primitive/scene.d.ts.map +1 -1
- package/dist/es/primitive/text.d.ts +9 -32
- package/dist/es/primitive/text.d.ts.map +1 -1
- package/dist/lib/compile/compile.cjs +31 -8
- package/dist/lib/compile/compile.d.ts +9 -10
- package/dist/lib/compile/compile.d.ts.map +1 -1
- package/dist/lib/compile/node.cjs +137 -39
- package/dist/lib/compile/node.d.ts +56 -54
- package/dist/lib/compile/node.d.ts.map +1 -1
- package/dist/lib/compile/parseTarget.cjs +7 -19
- package/dist/lib/compile/parseTarget.d.ts +3 -8
- package/dist/lib/compile/parseTarget.d.ts.map +1 -1
- package/dist/lib/compile/path.cjs +231 -212
- package/dist/lib/compile/path.d.ts +2 -19
- package/dist/lib/compile/path.d.ts.map +1 -1
- package/dist/lib/compile/position.cjs +32 -5
- package/dist/lib/compile/position.d.ts +4 -5
- package/dist/lib/compile/position.d.ts.map +1 -1
- package/dist/lib/compile/text-metrics.d.ts +2 -4
- package/dist/lib/compile/text-metrics.d.ts.map +1 -1
- package/dist/lib/compile/view-box.cjs +1 -4
- package/dist/lib/compile/view-box.d.ts +1 -4
- package/dist/lib/compile/view-box.d.ts.map +1 -1
- package/dist/lib/geometry/arc.cjs +3 -31
- package/dist/lib/geometry/arc.d.ts +3 -28
- package/dist/lib/geometry/arc.d.ts.map +1 -1
- package/dist/lib/geometry/bend.cjs +2 -13
- package/dist/lib/geometry/bend.d.ts +2 -13
- package/dist/lib/geometry/bend.d.ts.map +1 -1
- package/dist/lib/geometry/circle.cjs +1 -4
- package/dist/lib/geometry/circle.d.ts +5 -13
- package/dist/lib/geometry/circle.d.ts.map +1 -1
- package/dist/lib/geometry/diamond.cjs +4 -14
- package/dist/lib/geometry/diamond.d.ts +9 -24
- package/dist/lib/geometry/diamond.d.ts.map +1 -1
- package/dist/lib/geometry/ellipse.cjs +4 -8
- package/dist/lib/geometry/ellipse.d.ts +9 -15
- package/dist/lib/geometry/ellipse.d.ts.map +1 -1
- package/dist/lib/geometry/point.cjs +8 -9
- package/dist/lib/geometry/point.d.ts +8 -9
- package/dist/lib/geometry/point.d.ts.map +1 -1
- package/dist/lib/geometry/polar.cjs +7 -14
- package/dist/lib/geometry/polar.d.ts +12 -21
- package/dist/lib/geometry/polar.d.ts.map +1 -1
- package/dist/lib/geometry/rect.cjs +7 -33
- package/dist/lib/geometry/rect.d.ts +8 -32
- package/dist/lib/geometry/rect.d.ts.map +1 -1
- package/dist/lib/geometry/segment.cjs +9 -16
- package/dist/lib/geometry/segment.d.ts +11 -18
- package/dist/lib/geometry/segment.d.ts.map +1 -1
- package/dist/lib/index.cjs +15 -2
- package/dist/lib/index.d.ts +6 -10
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/ir/coordinate.cjs +22 -0
- package/dist/lib/ir/coordinate.d.ts +65 -0
- package/dist/lib/ir/coordinate.d.ts.map +1 -0
- package/dist/lib/ir/index.d.ts +1 -0
- package/dist/lib/ir/index.d.ts.map +1 -1
- package/dist/lib/ir/node.cjs +32 -37
- package/dist/lib/ir/node.d.ts +290 -50
- package/dist/lib/ir/node.d.ts.map +1 -1
- package/dist/lib/ir/path/arrow.cjs +43 -11
- package/dist/lib/ir/path/arrow.d.ts +210 -12
- package/dist/lib/ir/path/arrow.d.ts.map +1 -1
- package/dist/lib/ir/path/path.cjs +1 -1
- package/dist/lib/ir/path/path.d.ts +477 -153
- package/dist/lib/ir/path/path.d.ts.map +1 -1
- package/dist/lib/ir/path/step.cjs +13 -14
- package/dist/lib/ir/path/step.d.ts +395 -223
- package/dist/lib/ir/path/step.d.ts.map +1 -1
- package/dist/lib/ir/path/target.cjs +9 -7
- package/dist/lib/ir/path/target.d.ts +25 -16
- package/dist/lib/ir/path/target.d.ts.map +1 -1
- package/dist/lib/ir/position/at-position.cjs +24 -0
- package/dist/lib/ir/position/at-position.d.ts +43 -0
- package/dist/lib/ir/position/at-position.d.ts.map +1 -0
- package/dist/lib/ir/position/index.d.ts +2 -0
- package/dist/lib/ir/position/index.d.ts.map +1 -1
- package/dist/lib/ir/position/offset-position.cjs +14 -0
- package/dist/lib/ir/position/offset-position.d.ts +14 -0
- package/dist/lib/ir/position/offset-position.d.ts.map +1 -0
- package/dist/lib/ir/position/polar-position.cjs +1 -4
- package/dist/lib/ir/position/polar-position.d.ts +1 -4
- package/dist/lib/ir/position/polar-position.d.ts.map +1 -1
- package/dist/lib/ir/scene.cjs +6 -1
- package/dist/lib/ir/scene.d.ts +1736 -386
- package/dist/lib/ir/scene.d.ts.map +1 -1
- package/dist/lib/parsers/parseTargetSugar.cjs +6 -19
- package/dist/lib/parsers/parseTargetSugar.d.ts.map +1 -1
- package/dist/lib/parsers/parseWay.cjs +19 -61
- package/dist/lib/parsers/parseWay.d.ts +26 -118
- package/dist/lib/parsers/parseWay.d.ts.map +1 -1
- package/dist/lib/primitive/ellipse.d.ts +4 -14
- package/dist/lib/primitive/ellipse.d.ts.map +1 -1
- package/dist/lib/primitive/group.d.ts +21 -3
- package/dist/lib/primitive/group.d.ts.map +1 -1
- package/dist/lib/primitive/path.d.ts +67 -7
- package/dist/lib/primitive/path.d.ts.map +1 -1
- package/dist/lib/primitive/scene.d.ts +2 -4
- package/dist/lib/primitive/scene.d.ts.map +1 -1
- package/dist/lib/primitive/text.d.ts +9 -32
- package/dist/lib/primitive/text.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_arrow = require("../ir/path/arrow.cjs");
|
|
1
2
|
const require_position = require("./position.cjs");
|
|
2
3
|
const require_node = require("./node.cjs");
|
|
3
4
|
const require_arc = require("../geometry/arc.cjs");
|
|
@@ -7,46 +8,65 @@ const require_parseTarget = require("./parseTarget.cjs");
|
|
|
7
8
|
const require_text_metrics = require("./text-metrics.cjs");
|
|
8
9
|
//#region src/compile/path.ts
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* 端点级 spec:顶层默认 ⊕ end-side override(逐字段 merge)
|
|
12
|
+
* @description 缺省字段继承顶层(不是"完全替换");空心 shape 上 fill 字段被丢(silent no-op)
|
|
12
13
|
*/
|
|
13
|
-
var
|
|
14
|
+
var resolveArrowEndSpec = (topLevel, endSide) => {
|
|
15
|
+
const baseShape = endSide?.shape ?? topLevel.shape ?? "normal";
|
|
16
|
+
const out = { shape: baseShape };
|
|
17
|
+
const scale = endSide?.scale ?? topLevel.scale;
|
|
18
|
+
if (scale !== void 0) out.scale = scale;
|
|
19
|
+
const length = endSide?.length ?? topLevel.length;
|
|
20
|
+
if (length !== void 0) out.length = length;
|
|
21
|
+
const width = endSide?.width ?? topLevel.width;
|
|
22
|
+
if (width !== void 0) out.width = width;
|
|
23
|
+
const color = endSide?.color ?? topLevel.color;
|
|
24
|
+
if (color !== void 0) out.color = color;
|
|
25
|
+
const opacity = endSide?.opacity ?? topLevel.opacity;
|
|
26
|
+
if (opacity !== void 0) out.opacity = opacity;
|
|
27
|
+
const lineWidth = endSide?.lineWidth ?? topLevel.lineWidth;
|
|
28
|
+
if (lineWidth !== void 0) out.lineWidth = lineWidth;
|
|
29
|
+
if (!require_arrow.HOLLOW_ARROW_SHAPES.has(baseShape)) {
|
|
30
|
+
const fill = endSide?.fill ?? topLevel.fill;
|
|
31
|
+
if (fill !== void 0) out.fill = fill;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
};
|
|
35
|
+
/** IR path-level `arrow` + `arrowDetail` → PathPrim 起末视觉规格 */
|
|
36
|
+
var arrowMarkers = (arrow, detail) => {
|
|
37
|
+
if (!arrow || arrow === "none") return {};
|
|
38
|
+
const top = detail ?? {};
|
|
39
|
+
const startSpec = resolveArrowEndSpec(top, top.start);
|
|
40
|
+
const endSpec = resolveArrowEndSpec(top, top.end);
|
|
14
41
|
switch (arrow) {
|
|
15
|
-
case "->": return { arrowEnd:
|
|
16
|
-
case "<-": return { arrowStart:
|
|
42
|
+
case "->": return { arrowEnd: endSpec };
|
|
43
|
+
case "<-": return { arrowStart: startSpec };
|
|
17
44
|
case "<->": return {
|
|
18
|
-
arrowStart:
|
|
19
|
-
arrowEnd:
|
|
45
|
+
arrowStart: startSpec,
|
|
46
|
+
arrowEnd: endSpec
|
|
20
47
|
};
|
|
21
|
-
default: return {};
|
|
22
48
|
}
|
|
23
49
|
};
|
|
24
50
|
/**
|
|
25
|
-
*
|
|
51
|
+
* 端点级 shrink(strokeWidth 倍):line 末端朝起点缩这么多,让 marker apex 落回原 target
|
|
52
|
+
* @description 不分实心/空心:所有 shape 都让 line 端点接在箭头尾部、apex 顶端仍贴原 target。低 opacity 下不会再透出 line。viewBox=10,shrink = (apex.x - refX) × length × scale / 10(strokeWidth 倍)。
|
|
26
53
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* 各 shape 的 refX / 形状定义保持一致:
|
|
33
|
-
* shrink = (apexX - refX) × markerWidth / viewBoxWidth
|
|
34
|
-
* open: apexX=9, refX=1, scale=6/10 → 8 × 0.6 = 4.8
|
|
35
|
-
* openDiamond: apexX=9, refX=1, scale=6/10 → 8 × 0.6 = 4.8
|
|
36
|
-
* openCircle: apexX=10, refX=0, scale=6/10 → 10 × 0.6 = 6
|
|
37
|
-
* 实心 shape(normal / stealth / diamond / circle)apex / 边缘已贴 refX=10,
|
|
38
|
-
* line 被 fill 覆盖看不见,shrink=0。
|
|
54
|
+
* 几何对齐(必须与 react/render/arrowMarkers.tsx 中 renderInner 的 refX 一致):
|
|
55
|
+
* - `normal` / `diamond` / `circle`:apex 在 viewBox x=10、back 外缘 x=0 → refX=0,shrink = length × scale
|
|
56
|
+
* - `stealth`:apex x=10、V tip x=3(line 嵌进 V 凹口)→ refX=3,shrink = 0.7 × length × scale
|
|
57
|
+
* - `open` / `openDiamond`:apex x=9、back stroke 外缘 x = 1 - lineWidth/2 → refX = 1 - lineWidth/2,shrink = (8 + lineWidth/2) × length × scale / 10
|
|
58
|
+
* - `openCircle`:apex 外缘右 x ≈ 10、back 外缘左 x = 0.75 - lineWidth/2 → refX = 0.75 - lineWidth/2,shrink ≈ length × scale
|
|
39
59
|
*/
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
var computeShrink = (spec) => {
|
|
61
|
+
const length = (spec.length ?? 6) * (spec.scale ?? 1);
|
|
62
|
+
if (require_arrow.HOLLOW_ARROW_SHAPES.has(spec.shape)) {
|
|
63
|
+
if (spec.shape === "openCircle") return length;
|
|
64
|
+
return (8 + (spec.lineWidth ?? 1.5) / 2) * length / 10;
|
|
65
|
+
}
|
|
66
|
+
if (spec.shape === "stealth") return 7 * length / 10;
|
|
67
|
+
return length;
|
|
48
68
|
};
|
|
49
|
-
/**
|
|
69
|
+
/** 把 p 朝 target 方向移动 dist */
|
|
50
70
|
var shiftToward = (p, target, dist) => {
|
|
51
71
|
const dx = target[0] - p[0];
|
|
52
72
|
const dy = target[1] - p[1];
|
|
@@ -55,15 +75,8 @@ var shiftToward = (p, target, dist) => {
|
|
|
55
75
|
return [p[0] + dx / len * dist, p[1] + dy / len * dist];
|
|
56
76
|
};
|
|
57
77
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* 三态字符串语法(ADR-0004):
|
|
61
|
-
* - `'A'`(auto):节点中心;邻居端点 clip 时按"A 中心 → toward"射线求边界
|
|
62
|
-
* - `'A.<anchor>'`:命名 anchor 位置(位置即 endpoint,refPoint 也是它)
|
|
63
|
-
* - `'A.<deg>'`:节点 deg 方向上的视觉边界点(位置即 endpoint)
|
|
64
|
-
*
|
|
65
|
-
* 后两种"显式锚点"模式下 refPoint = endpoint(位置不随邻居变),auto 模式
|
|
66
|
-
* 下 refPoint 仍是中心。直接坐标 / 极坐标:解析后的笛卡尔。
|
|
78
|
+
* 求 step.to 的参考点(给 boundary clip 算方向 / 折角 corner 用)
|
|
79
|
+
* @description 三态:`'A'`(auto) 节点中心;`'A.<anchor>'`/`'A.<deg>'` 显式锚点 refPoint=endpoint 位置不随邻居变。直接坐标/极坐标解析为笛卡尔
|
|
67
80
|
*/
|
|
68
81
|
var refPointOfTarget = (target, nodeIndex) => {
|
|
69
82
|
if (typeof target === "string") {
|
|
@@ -76,22 +89,14 @@ var refPointOfTarget = (target, nodeIndex) => {
|
|
|
76
89
|
case "angle": return require_node.angleBoundaryOf(node, ref.angle);
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
|
-
if (typeof target === "object" && !Array.isArray(target) && ("
|
|
92
|
+
if (typeof target === "object" && !Array.isArray(target) && ("relative" in target || "relativeAccumulate" in target)) return null;
|
|
80
93
|
return require_position.resolvePosition(target, nodeIndex);
|
|
81
94
|
};
|
|
82
|
-
/**
|
|
83
|
-
* 折角中间点:基于"参考点"(节点中心或直接坐标)算直角拐点。
|
|
84
|
-
* `-|` corner = (curr.x, prev.y);`|-` corner = (prev.x, curr.y)。
|
|
85
|
-
*/
|
|
95
|
+
/** 折角中间点:`-|` → (curr.x, prev.y);`|-` → (prev.x, curr.y) */
|
|
86
96
|
var cornerOf = (prev, curr, via) => via === "-|" ? [curr[0], prev[1]] : [prev[0], curr[1]];
|
|
87
97
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* 求边界与"中心→toward"射线交点
|
|
91
|
-
* - 节点 ref 命名 anchor(`'A.north'`)/ 角度(`'A.30'`):位置已被解析定死,
|
|
92
|
-
* 直接返回,**不再受 toward 影响**
|
|
93
|
-
* - 直接坐标 / 极坐标:解析后直接返回(不做 clip)
|
|
94
|
-
* 解析失败返回 null。
|
|
98
|
+
* 在 toward 方向算 step.to 的实际绘制端点
|
|
99
|
+
* @description 节点 auto `'A'`:按 shape 走 boundaryPointOf 求中心→toward 射线交点;命名 anchor/角度:位置已定不受 toward 影响;直接坐标/极坐标:解析后返回;失败返回 null
|
|
95
100
|
*/
|
|
96
101
|
var clipForTarget = (target, toward, nodeIndex) => {
|
|
97
102
|
if (typeof target === "string") {
|
|
@@ -104,24 +109,14 @@ var clipForTarget = (target, toward, nodeIndex) => {
|
|
|
104
109
|
case "angle": return require_node.angleBoundaryOf(node, ref.angle);
|
|
105
110
|
}
|
|
106
111
|
}
|
|
107
|
-
if (typeof target === "object" && !Array.isArray(target) && ("
|
|
112
|
+
if (typeof target === "object" && !Array.isArray(target) && ("relative" in target || "relativeAccumulate" in target)) return null;
|
|
108
113
|
return require_position.resolvePosition(target, nodeIndex);
|
|
109
114
|
};
|
|
110
|
-
/**
|
|
115
|
+
/** 两个 IRPosition 两分量精确相等(未 round) */
|
|
111
116
|
var samePoint = (a, b) => !!a && !!b && a[0] === b[0] && a[1] === b[1];
|
|
112
117
|
/**
|
|
113
|
-
* 语义 stroke 档位 →
|
|
114
|
-
*
|
|
115
|
-
* 对齐 TikZ 比例(thin = 默认 0.4pt,retikz 默认 strokeWidth = 1,所以 thin → 1):
|
|
116
|
-
* ultra thin 0.1pt → 0.25
|
|
117
|
-
* very thin 0.2pt → 0.5
|
|
118
|
-
* thin 0.4pt → 1 (= 默认 strokeWidth)
|
|
119
|
-
* semithick 0.6pt → 1.5
|
|
120
|
-
* thick 0.8pt → 2
|
|
121
|
-
* very thick 1.2pt → 3
|
|
122
|
-
* ultra thick 1.6pt → 4
|
|
123
|
-
*
|
|
124
|
-
* 显式 `strokeWidth` 始终覆盖 `thickness`——thickness 仅在 strokeWidth 缺省时生效。
|
|
118
|
+
* 语义 stroke 档位 → 数值(user units)
|
|
119
|
+
* @description 对齐 TikZ 比例(thin=0.4pt→1=默认 strokeWidth):ultraThin 0.25、veryThin 0.5、thin 1、semithick 1.5、thick 2、veryThick 3、ultraThick 4。显式 strokeWidth 覆盖 thickness
|
|
125
120
|
*/
|
|
126
121
|
var THICKNESS_TO_WIDTH = {
|
|
127
122
|
ultraThin: .25,
|
|
@@ -132,26 +127,33 @@ var THICKNESS_TO_WIDTH = {
|
|
|
132
127
|
veryThick: 3,
|
|
133
128
|
ultraThick: 4
|
|
134
129
|
};
|
|
135
|
-
/**
|
|
130
|
+
/** 边标注默认字号 / 偏移量 */
|
|
136
131
|
var LABEL_FONT_SIZE = 14;
|
|
137
132
|
var LABEL_LINE_HEIGHT_FACTOR = 1.2;
|
|
138
133
|
var LABEL_SIDE_OFFSET = 4;
|
|
139
134
|
var RAD_TO_DEG = 180 / Math.PI;
|
|
140
|
-
/**
|
|
141
|
-
var
|
|
135
|
+
/** keyword → t 数值映射;含旧 3 keyword(midway/near-start/near-end)+ 新 4 keyword */
|
|
136
|
+
var KEYWORD_TO_T = {
|
|
137
|
+
"at-start": 0,
|
|
138
|
+
"very-near-start": .125,
|
|
139
|
+
"near-start": .25,
|
|
140
|
+
midway: .5,
|
|
141
|
+
"near-end": .75,
|
|
142
|
+
"very-near-end": .875,
|
|
143
|
+
"at-end": 1
|
|
144
|
+
};
|
|
142
145
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* 返回 primitive + 用于 viewBox 的若干外接点(sloped 时按"近似最大半径"四角扩张)。
|
|
146
|
+
* label.position → 段参数 t∈[0,1]
|
|
147
|
+
* @description 数值原样返回(schema 已 clamp 0..1);keyword 走 KEYWORD_TO_T 映射;undefined 退默认 midway (0.5)
|
|
148
|
+
*/
|
|
149
|
+
var tForLabelPosition = (pos) => {
|
|
150
|
+
if (typeof pos === "number") return pos;
|
|
151
|
+
if (typeof pos === "string" && pos in KEYWORD_TO_T) return KEYWORD_TO_T[pos];
|
|
152
|
+
return .5;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* step.label + 段采样 → TextPrim(sloped 时裹一层 group 旋转)
|
|
156
|
+
* @description 默认 side='above'/position='midway':above/below 锚点 y±offset、align=middle、baseline=bottom/top;left/right x±offset、align=end/start、baseline=middle;sloped 不偏移裹 group rotate(angle, cx, cy) 由切线 atan2 算(SVG y-down CW 正)。返回 primitive + viewBox 外接点
|
|
155
157
|
*/
|
|
156
158
|
var emitLabelPrimitive = (label, sample, measureText, round) => {
|
|
157
159
|
const fontSize = LABEL_FONT_SIZE;
|
|
@@ -193,7 +195,12 @@ var emitLabelPrimitive = (label, sample, measureText, round) => {
|
|
|
193
195
|
if (side === "sloped") {
|
|
194
196
|
const groupPrim = {
|
|
195
197
|
type: "group",
|
|
196
|
-
|
|
198
|
+
transforms: [{
|
|
199
|
+
kind: "rotate",
|
|
200
|
+
degrees: round(Math.atan2(sample.tangent[1], sample.tangent[0]) * RAD_TO_DEG),
|
|
201
|
+
cx: round(x),
|
|
202
|
+
cy: round(y)
|
|
203
|
+
}],
|
|
197
204
|
children: [text]
|
|
198
205
|
};
|
|
199
206
|
const r = Math.max(measuredWidth / 2, measuredHeight / 2);
|
|
@@ -220,22 +227,8 @@ var emitLabelPrimitive = (label, sample, measureText, round) => {
|
|
|
220
227
|
};
|
|
221
228
|
};
|
|
222
229
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* 决策(ADR-0003 §影响 与 §背景 文本有矛盾):
|
|
227
|
-
* 两者都相对 prevEnd 解析,区别仅在是否更新 prevEnd——
|
|
228
|
-
* rel 不更新(保持 TikZ `+` 语义),relAccumulate 更新(TikZ `++` 累积)。
|
|
229
|
-
* 选这个语义因为:(1) 与 TikZ `+`/`++` 一致;(2) 与字段名"Accumulate"
|
|
230
|
-
* 语义匹配;(3) 与 ADR 背景段一致;§影响 段写"pathStart + offset"是 typo。
|
|
231
|
-
*
|
|
232
|
-
* 跨 step kind 的 prevEnd 推进:
|
|
233
|
-
* - 有 to 的 kind(move/line/step/curve/cubic/bend):prevEnd = refPointOfTarget(to)
|
|
234
|
-
* - arc:prevEnd = arcEndPoint(prevEnd, radius, endAngle)
|
|
235
|
-
* - circlePath / ellipsePath:prevEnd 不变(画完留圆心,即 prevEnd 本身)
|
|
236
|
-
* - cycle:prevEnd 不变(不重置到 pathStart,保持简单;后续如有需要再扩)
|
|
237
|
-
*
|
|
238
|
-
* prevEnd 为 null(首步是 rel)时回退到 [0, 0] 当锚点;解析失败保持原 step。
|
|
230
|
+
* relative/relativeAccumulate 目标解析为绝对 Position(step kind 不变,to 全为绝对坐标)
|
|
231
|
+
* @description relative 不更新 prevEnd(TikZ `+`),relativeAccumulate 更新(TikZ `++`)。prevEnd 推进:有 to 的 kind 用 refPointOfTarget(to);arc 用 arcEndPoint;circlePath/ellipsePath/cycle 不变。首步 relative 时 prevEnd 回退 [0,0];解析失败保持原 step
|
|
239
232
|
*/
|
|
240
233
|
var normalizeRelativeTargets = (steps, nodeIndex) => {
|
|
241
234
|
let prevEnd = null;
|
|
@@ -257,13 +250,13 @@ var normalizeRelativeTargets = (steps, nodeIndex) => {
|
|
|
257
250
|
const original = step.to;
|
|
258
251
|
let resolvedTo = original;
|
|
259
252
|
let updatePrevEnd = true;
|
|
260
|
-
if (typeof original === "object" && !Array.isArray(original) && "
|
|
253
|
+
if (typeof original === "object" && !Array.isArray(original) && "relative" in original) {
|
|
261
254
|
const ref = prevEnd ?? [0, 0];
|
|
262
|
-
resolvedTo = [ref[0] + original.
|
|
255
|
+
resolvedTo = [ref[0] + original.relative[0], ref[1] + original.relative[1]];
|
|
263
256
|
updatePrevEnd = false;
|
|
264
|
-
} else if (typeof original === "object" && !Array.isArray(original) && "
|
|
257
|
+
} else if (typeof original === "object" && !Array.isArray(original) && "relativeAccumulate" in original) {
|
|
265
258
|
const ref = prevEnd ?? [0, 0];
|
|
266
|
-
resolvedTo = [ref[0] + original.
|
|
259
|
+
resolvedTo = [ref[0] + original.relativeAccumulate[0], ref[1] + original.relativeAccumulate[1]];
|
|
267
260
|
}
|
|
268
261
|
out.push({
|
|
269
262
|
...step,
|
|
@@ -277,33 +270,15 @@ var normalizeRelativeTargets = (steps, nodeIndex) => {
|
|
|
277
270
|
return out;
|
|
278
271
|
};
|
|
279
272
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* 关键算法(v0.1.0-alpha.1):每个绘制段(line / fold)**独立**地用节点中心
|
|
283
|
-
* 算两端 boundary clip——一个节点在路径中段时,"入边"和"出边" boundary 点
|
|
284
|
-
* 通常不同,路径会在该节点处可见地"断开"。这与 TikZ 原生语义一致:
|
|
285
|
-
*
|
|
286
|
-
* `\draw (A) -- (B) -- (C);`
|
|
287
|
-
* 段 1:A.center → B.center 决定 A 出口、B 入口的 boundary 交点
|
|
288
|
-
* 段 2:B.center → C.center 决定 B 出口、C 入口的 boundary 交点
|
|
289
|
-
* B 在两段里 clip 出来的点不同——视觉上看到两条独立线段。
|
|
290
|
-
*
|
|
291
|
-
* 实现上仍只产一个 PathPrim:d 字符串里以多组 `M ... L ...` 表达多个 sub-path。
|
|
292
|
-
* 当某段起点恰好等于上一段终点(例如直接坐标连续,或未触发 clip 差异)时,
|
|
293
|
-
* 复用 cursor,省掉冗余 M。
|
|
294
|
-
*
|
|
295
|
-
* cycle 段:闭回最近一次 move 起点。若 cycle 起点 == lastEnd 且终点 == subPathStart,
|
|
296
|
-
* 输出 `Z`(最优雅);否则显式画一段 line(与"段独立 clip"一致)。
|
|
297
|
-
*
|
|
298
|
-
* 引用未定义节点 / 解析失败时返回 null(path 整体跳过)。
|
|
273
|
+
* IR Path → PathPrim
|
|
274
|
+
* @description 每个绘制段独立用节点中心算两端 boundary clip——中段节点的入/出 boundary 点通常不同,path 在该节点可见"断开"(与 TikZ `\draw (A)--(B)--(C);` 段独立 clip 一致)。仍产一个 PathPrim:commands 用多组 move/line 表达 sub-path;段起点等于上段终点时复用 cursor 省 move。cycle 段闭回最近 move 起点,起点==lastEnd && 终点==subPathStart 时输出 close,否则显式画段 line。引用未定义节点/解析失败返回 null
|
|
299
275
|
*/
|
|
300
276
|
var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metrics.fallbackMeasurer) => {
|
|
301
277
|
const steps = normalizeRelativeTargets(path.children, nodeIndex);
|
|
302
278
|
if (steps.length < 2) return null;
|
|
303
|
-
/**
|
|
304
|
-
* 与 path 主体 primitive 同级返回;调用方 push 进 scene.primitives */
|
|
279
|
+
/** 每段 step.label 翻译出的 TextPrim(或 sloped 旋转的 group),与 path 主体同级返回 */
|
|
305
280
|
const labelPrims = [];
|
|
306
|
-
/**
|
|
281
|
+
/** 算 sample 后 emitLabelPrimitive,结果累积到 labelPrims/points */
|
|
307
282
|
const collectLabel = (step, sampleAt) => {
|
|
308
283
|
if (step.kind === "move" || step.kind === "cycle" || !("label" in step) || !step.label) return;
|
|
309
284
|
const sample = sampleAt(tForLabelPosition(step.label.position));
|
|
@@ -313,7 +288,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
313
288
|
};
|
|
314
289
|
const hasTo = (s) => s.kind !== "cycle" && s.kind !== "arc" && s.kind !== "circlePath" && s.kind !== "ellipsePath";
|
|
315
290
|
const anchors = steps.map((s) => hasTo(s) ? refPointOfTarget(s.to, nodeIndex) : null);
|
|
316
|
-
/** 找 i 之前最近的"有 to 字段的 step"
|
|
291
|
+
/** 找 i 之前最近的"有 to 字段的 step" 及其 anchor */
|
|
317
292
|
const findPrev = (i) => {
|
|
318
293
|
for (let j = i - 1; j >= 0; j--) {
|
|
319
294
|
const s = steps[j];
|
|
@@ -327,7 +302,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
327
302
|
}
|
|
328
303
|
return null;
|
|
329
304
|
};
|
|
330
|
-
/** 找 i 之前最近的 move 的 to
|
|
305
|
+
/** 找 i 之前最近的 move 的 to,cycle 闭合的目标 */
|
|
331
306
|
const findRecentMoveTo = (i) => {
|
|
332
307
|
for (let j = i - 1; j >= 0; j--) {
|
|
333
308
|
const s = steps[j];
|
|
@@ -335,80 +310,96 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
335
310
|
}
|
|
336
311
|
return null;
|
|
337
312
|
};
|
|
338
|
-
const
|
|
313
|
+
const commands = [];
|
|
339
314
|
const points = [];
|
|
340
315
|
let lastEnd = null;
|
|
341
316
|
let subPathStart = null;
|
|
342
317
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* 绘制段(line/curve/cubic/bend/step)直接用这个点当 fromClip,之后清空。
|
|
346
|
-
*
|
|
347
|
-
* - arc:endpoint(弧终点)—— 与 SVG 实际 cursor 一致
|
|
348
|
-
* - circlePath/ellipsePath:center(圆心)—— ADR-0002 决策"画完留在圆心",
|
|
349
|
-
* 注意 SVG 实际 cursor 在弧端点而不在 center,必须靠 startSegment 发 M
|
|
350
|
-
* teleport 回中心
|
|
318
|
+
* 笔位覆盖:arc/circlePath/ellipsePath 无 `to` 字段不能用 prev.step.to 重算起点
|
|
319
|
+
* @description 设置 penOverride 让下个绘制段直接用此点当 fromClip 后清空。arc=弧终点;circlePath/ellipsePath=center("画完留在圆心")
|
|
351
320
|
*/
|
|
352
321
|
let penOverride = null;
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
322
|
+
const roundPoint = (p) => [round(p[0]), round(p[1])];
|
|
323
|
+
const emitMove = (p) => {
|
|
324
|
+
const rp = roundPoint(p);
|
|
325
|
+
commands.push({
|
|
326
|
+
kind: "move",
|
|
327
|
+
to: [rp[0], rp[1]]
|
|
357
328
|
});
|
|
358
329
|
points.push(p);
|
|
359
330
|
subPathStart = p;
|
|
360
331
|
lastEnd = p;
|
|
361
332
|
};
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
333
|
+
const emitLine = (p) => {
|
|
334
|
+
const rp = roundPoint(p);
|
|
335
|
+
commands.push({
|
|
336
|
+
kind: "line",
|
|
337
|
+
to: [rp[0], rp[1]]
|
|
366
338
|
});
|
|
367
339
|
points.push(p);
|
|
368
340
|
lastEnd = p;
|
|
369
341
|
};
|
|
370
|
-
const
|
|
371
|
-
|
|
342
|
+
const emitClose = () => {
|
|
343
|
+
commands.push({ kind: "close" });
|
|
372
344
|
lastEnd = subPathStart;
|
|
373
345
|
};
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
346
|
+
const emitQuad = (control, p) => {
|
|
347
|
+
const rc = roundPoint(control);
|
|
348
|
+
const rp = roundPoint(p);
|
|
349
|
+
commands.push({
|
|
350
|
+
kind: "quad",
|
|
351
|
+
control: [rc[0], rc[1]],
|
|
352
|
+
to: [rp[0], rp[1]]
|
|
379
353
|
});
|
|
380
354
|
points.push(control);
|
|
381
355
|
points.push(p);
|
|
382
356
|
lastEnd = p;
|
|
383
357
|
};
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
358
|
+
const emitCubic = (c1, c2, p) => {
|
|
359
|
+
const rc1 = roundPoint(c1);
|
|
360
|
+
const rc2 = roundPoint(c2);
|
|
361
|
+
const rp = roundPoint(p);
|
|
362
|
+
commands.push({
|
|
363
|
+
kind: "cubic",
|
|
364
|
+
control1: [rc1[0], rc1[1]],
|
|
365
|
+
control2: [rc2[0], rc2[1]],
|
|
366
|
+
to: [rp[0], rp[1]]
|
|
390
367
|
});
|
|
391
368
|
points.push(c1);
|
|
392
369
|
points.push(c2);
|
|
393
370
|
points.push(p);
|
|
394
371
|
lastEnd = p;
|
|
395
372
|
};
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
373
|
+
const emitArc = (center, radius, startAngle, endAngle) => {
|
|
374
|
+
const rc = roundPoint(center);
|
|
375
|
+
commands.push({
|
|
376
|
+
kind: "arc",
|
|
377
|
+
center: [rc[0], rc[1]],
|
|
378
|
+
radius: round(radius),
|
|
379
|
+
startAngle,
|
|
380
|
+
endAngle
|
|
404
381
|
});
|
|
405
|
-
points.push(
|
|
406
|
-
lastEnd =
|
|
382
|
+
points.push(require_arc.arcEndPoint(center, radius, endAngle));
|
|
383
|
+
lastEnd = require_arc.arcEndPoint(center, radius, endAngle);
|
|
407
384
|
};
|
|
408
|
-
|
|
385
|
+
const emitEllipseArc = (center, radiusX, radiusY, startAngle, endAngle) => {
|
|
386
|
+
const rc = roundPoint(center);
|
|
387
|
+
commands.push({
|
|
388
|
+
kind: "ellipseArc",
|
|
389
|
+
center: [rc[0], rc[1]],
|
|
390
|
+
radiusX: round(radiusX),
|
|
391
|
+
radiusY: round(radiusY),
|
|
392
|
+
startAngle,
|
|
393
|
+
endAngle
|
|
394
|
+
});
|
|
395
|
+
const endPt = [center[0] + Math.cos(endAngle * Math.PI / 180) * radiusX, center[1] + Math.sin(endAngle * Math.PI / 180) * radiusY];
|
|
396
|
+
points.push(endPt);
|
|
397
|
+
lastEnd = endPt;
|
|
398
|
+
};
|
|
399
|
+
/** 段起点:与 lastEnd 相同则复用 cursor(省 move),否则发 move */
|
|
409
400
|
const startSegment = (p) => {
|
|
410
401
|
if (samePoint(p, lastEnd)) return;
|
|
411
|
-
|
|
402
|
+
emitMove(p);
|
|
412
403
|
};
|
|
413
404
|
for (let i = 0; i < steps.length; i++) {
|
|
414
405
|
const step = steps[i];
|
|
@@ -423,11 +414,11 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
423
414
|
const toClip = clipForTarget(moveTo, prev.anchor, nodeIndex);
|
|
424
415
|
if (!fromClip || !toClip) return null;
|
|
425
416
|
if (samePoint(fromClip, lastEnd) && samePoint(toClip, subPathStart)) {
|
|
426
|
-
|
|
417
|
+
emitClose();
|
|
427
418
|
continue;
|
|
428
419
|
}
|
|
429
420
|
startSegment(fromClip);
|
|
430
|
-
|
|
421
|
+
emitLine(toClip);
|
|
431
422
|
continue;
|
|
432
423
|
}
|
|
433
424
|
const prev = findPrev(i);
|
|
@@ -436,9 +427,8 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
436
427
|
const center = prev.anchor;
|
|
437
428
|
const startPt = require_arc.arcEndPoint(center, step.radius, step.startAngle);
|
|
438
429
|
const endPt = require_arc.arcEndPoint(center, step.radius, step.endAngle);
|
|
439
|
-
const flags = require_arc.arcSvgFlags(step.startAngle, step.endAngle);
|
|
440
430
|
startSegment(startPt);
|
|
441
|
-
|
|
431
|
+
emitArc(center, step.radius, step.startAngle, step.endAngle);
|
|
442
432
|
for (const p of require_arc.arcBoundingPoints(center, step.radius, step.startAngle, step.endAngle)) points.push(p);
|
|
443
433
|
collectLabel(step, (t) => require_segment.arcSegmentSample(center, step.radius, step.startAngle, step.endAngle, t));
|
|
444
434
|
penOverride = endPt;
|
|
@@ -447,11 +437,8 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
447
437
|
if (step.kind === "circlePath") {
|
|
448
438
|
const center = prev.anchor;
|
|
449
439
|
const r = step.radius;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
startSegment(right);
|
|
453
|
-
emitA(r, r, 0, 1, left);
|
|
454
|
-
emitA(r, r, 0, 1, right);
|
|
440
|
+
startSegment([center[0] + r, center[1]]);
|
|
441
|
+
emitEllipseArc(center, r, r, 0, 360);
|
|
455
442
|
points.push([center[0] + r, center[1]]);
|
|
456
443
|
points.push([center[0] - r, center[1]]);
|
|
457
444
|
points.push([center[0], center[1] + r]);
|
|
@@ -464,11 +451,8 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
464
451
|
const center = prev.anchor;
|
|
465
452
|
const rx = step.radiusX;
|
|
466
453
|
const ry = step.radiusY;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
startSegment(right);
|
|
470
|
-
emitA(rx, ry, 0, 1, left);
|
|
471
|
-
emitA(rx, ry, 0, 1, right);
|
|
454
|
+
startSegment([center[0] + rx, center[1]]);
|
|
455
|
+
emitEllipseArc(center, rx, ry, 0, 360);
|
|
472
456
|
points.push([center[0] + rx, center[1]]);
|
|
473
457
|
points.push([center[0] - rx, center[1]]);
|
|
474
458
|
points.push([center[0], center[1] + ry]);
|
|
@@ -486,7 +470,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
486
470
|
const toClip = clipForTarget(step.to, prev.anchor, nodeIndex);
|
|
487
471
|
if (!fromClip || !toClip) return null;
|
|
488
472
|
startSegment(fromClip);
|
|
489
|
-
|
|
473
|
+
emitLine(toClip);
|
|
490
474
|
collectLabel(step, (t) => require_segment.lineSegmentSample(fromClip, toClip, t));
|
|
491
475
|
continue;
|
|
492
476
|
}
|
|
@@ -495,7 +479,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
495
479
|
const toClip = clipForTarget(step.to, step.control, nodeIndex);
|
|
496
480
|
if (!fromClip || !toClip) return null;
|
|
497
481
|
startSegment(fromClip);
|
|
498
|
-
|
|
482
|
+
emitQuad(step.control, toClip);
|
|
499
483
|
collectLabel(step, (t) => require_segment.quadSegmentSample(fromClip, step.control, toClip, t));
|
|
500
484
|
continue;
|
|
501
485
|
}
|
|
@@ -504,7 +488,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
504
488
|
const toClip = clipForTarget(step.to, step.control2, nodeIndex);
|
|
505
489
|
if (!fromClip || !toClip) return null;
|
|
506
490
|
startSegment(fromClip);
|
|
507
|
-
|
|
491
|
+
emitCubic(step.control1, step.control2, toClip);
|
|
508
492
|
collectLabel(step, (t) => require_segment.cubicSegmentSample(fromClip, step.control1, step.control2, toClip, t));
|
|
509
493
|
continue;
|
|
510
494
|
}
|
|
@@ -515,7 +499,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
515
499
|
const toClip = clipForTarget(step.to, c2, nodeIndex);
|
|
516
500
|
if (!fromClip || !toClip) return null;
|
|
517
501
|
startSegment(fromClip);
|
|
518
|
-
|
|
502
|
+
emitCubic(c1, c2, toClip);
|
|
519
503
|
collectLabel(step, (t) => require_segment.cubicSegmentSample(fromClip, c1, c2, toClip, t));
|
|
520
504
|
continue;
|
|
521
505
|
}
|
|
@@ -524,8 +508,8 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
524
508
|
const toClip = clipForTarget(step.to, corner, nodeIndex);
|
|
525
509
|
if (!fromClip || !toClip) return null;
|
|
526
510
|
startSegment(fromClip);
|
|
527
|
-
|
|
528
|
-
|
|
511
|
+
emitLine(corner);
|
|
512
|
+
emitLine(toClip);
|
|
529
513
|
collectLabel(step, (t) => require_segment.foldSegmentSample(fromClip, corner, toClip, t));
|
|
530
514
|
}
|
|
531
515
|
const strokeWidth = path.strokeWidth ?? (path.thickness ? THICKNESS_TO_WIDTH[path.thickness] : 1);
|
|
@@ -541,49 +525,84 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
541
525
|
fillOpacity: path.fillOpacity,
|
|
542
526
|
strokeOpacity: path.drawOpacity
|
|
543
527
|
};
|
|
544
|
-
const markers = arrowMarkers(path.arrow, path.
|
|
528
|
+
const markers = arrowMarkers(path.arrow, path.arrowDetail);
|
|
545
529
|
const hasArrows = !!markers.arrowStart || !!markers.arrowEnd;
|
|
546
|
-
const shrinkStart = markers.arrowStart ?
|
|
547
|
-
const shrinkEnd = markers.arrowEnd ?
|
|
530
|
+
const shrinkStart = markers.arrowStart ? computeShrink(markers.arrowStart) : 0;
|
|
531
|
+
const shrinkEnd = markers.arrowEnd ? computeShrink(markers.arrowEnd) : 0;
|
|
532
|
+
/** 取一个 PathCommand 末端 endpoint(move/line/quad/cubic → to;arc/ellipseArc → polar(end);close 无端点) */
|
|
533
|
+
const endpointOf = (cmd) => {
|
|
534
|
+
switch (cmd.kind) {
|
|
535
|
+
case "move":
|
|
536
|
+
case "line":
|
|
537
|
+
case "quad":
|
|
538
|
+
case "cubic": return [cmd.to[0], cmd.to[1]];
|
|
539
|
+
case "arc": {
|
|
540
|
+
const rad = cmd.endAngle * Math.PI / 180;
|
|
541
|
+
return [cmd.center[0] + Math.cos(rad) * cmd.radius, cmd.center[1] + Math.sin(rad) * cmd.radius];
|
|
542
|
+
}
|
|
543
|
+
case "ellipseArc": {
|
|
544
|
+
const rad = cmd.endAngle * Math.PI / 180;
|
|
545
|
+
return [cmd.center[0] + Math.cos(rad) * cmd.radiusX, cmd.center[1] + Math.sin(rad) * cmd.radiusY];
|
|
546
|
+
}
|
|
547
|
+
case "close": return null;
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
/** 改写一个 PathCommand 的 endpoint(用于 shrink) */
|
|
551
|
+
const setEndpoint = (idx, newPt) => {
|
|
552
|
+
const cmd = commands[idx];
|
|
553
|
+
if (cmd.kind === "close") return;
|
|
554
|
+
const rp = [round(newPt[0]), round(newPt[1])];
|
|
555
|
+
if (cmd.kind === "move" || cmd.kind === "line") commands[idx] = {
|
|
556
|
+
...cmd,
|
|
557
|
+
to: rp
|
|
558
|
+
};
|
|
559
|
+
else if (cmd.kind === "quad") commands[idx] = {
|
|
560
|
+
...cmd,
|
|
561
|
+
to: rp
|
|
562
|
+
};
|
|
563
|
+
else if (cmd.kind === "cubic") commands[idx] = {
|
|
564
|
+
...cmd,
|
|
565
|
+
to: rp
|
|
566
|
+
};
|
|
567
|
+
};
|
|
548
568
|
if (shrinkStart > 0) {
|
|
549
|
-
const firstIdx =
|
|
569
|
+
const firstIdx = commands.findIndex((o) => o.kind === "move");
|
|
550
570
|
if (firstIdx >= 0) {
|
|
551
|
-
const cur =
|
|
552
|
-
const
|
|
553
|
-
if (cur.
|
|
571
|
+
const cur = commands[firstIdx];
|
|
572
|
+
const nextIdx = commands.findIndex((o, idx) => idx > firstIdx && o.kind !== "close");
|
|
573
|
+
if (cur.kind === "move" && nextIdx >= 0) {
|
|
574
|
+
const nextPt = endpointOf(commands[nextIdx]);
|
|
575
|
+
if (nextPt) setEndpoint(firstIdx, shiftToward([cur.to[0], cur.to[1]], nextPt, shrinkStart * strokeWidth));
|
|
576
|
+
}
|
|
554
577
|
}
|
|
555
578
|
}
|
|
556
579
|
if (shrinkEnd > 0) {
|
|
557
580
|
let lastIdx = -1;
|
|
558
|
-
for (let i =
|
|
581
|
+
for (let i = commands.length - 1; i >= 0; i--) if (commands[i].kind !== "close") {
|
|
559
582
|
lastIdx = i;
|
|
560
583
|
break;
|
|
561
584
|
}
|
|
562
585
|
if (lastIdx > 0) {
|
|
563
586
|
let prevIdx = lastIdx - 1;
|
|
564
|
-
while (prevIdx >= 0 &&
|
|
587
|
+
while (prevIdx >= 0 && commands[prevIdx].kind === "close") prevIdx--;
|
|
565
588
|
if (prevIdx >= 0) {
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
if (
|
|
589
|
+
const curPt = endpointOf(commands[lastIdx]);
|
|
590
|
+
const prevPt = endpointOf(commands[prevIdx]);
|
|
591
|
+
if (curPt && prevPt) {
|
|
592
|
+
const shifted = shiftToward(curPt, prevPt, shrinkEnd * strokeWidth);
|
|
593
|
+
setEndpoint(lastIdx, shifted);
|
|
594
|
+
}
|
|
569
595
|
}
|
|
570
596
|
}
|
|
571
597
|
}
|
|
572
|
-
const tokens = ops.map((op) => {
|
|
573
|
-
if (op.cmd === "Z") return "Z";
|
|
574
|
-
if (op.cmd === "Q") return `Q ${round(op.control[0])} ${round(op.control[1])} ${round(op.point[0])} ${round(op.point[1])}`;
|
|
575
|
-
if (op.cmd === "C") return `C ${round(op.control1[0])} ${round(op.control1[1])} ${round(op.control2[0])} ${round(op.control2[1])} ${round(op.point[0])} ${round(op.point[1])}`;
|
|
576
|
-
if (op.cmd === "A") return `A ${round(op.rx)} ${round(op.ry)} 0 ${op.largeArc} ${op.sweep} ${round(op.point[0])} ${round(op.point[1])}`;
|
|
577
|
-
return `${op.cmd} ${round(op.point[0])} ${round(op.point[1])}`;
|
|
578
|
-
});
|
|
579
598
|
const subPathStarts = [];
|
|
580
|
-
|
|
581
|
-
if (
|
|
599
|
+
commands.forEach((cmd, idx) => {
|
|
600
|
+
if (cmd.kind === "move") subPathStarts.push(idx);
|
|
582
601
|
});
|
|
583
602
|
if (!hasArrows || subPathStarts.length <= 1) return {
|
|
584
603
|
primitives: [{
|
|
585
604
|
type: "path",
|
|
586
|
-
|
|
605
|
+
commands,
|
|
587
606
|
...baseProps,
|
|
588
607
|
...markers
|
|
589
608
|
}, ...labelPrims],
|
|
@@ -592,8 +611,8 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
592
611
|
const subPathSlices = [];
|
|
593
612
|
for (let s = 0; s < subPathStarts.length; s++) {
|
|
594
613
|
const start = subPathStarts[s];
|
|
595
|
-
const end = s + 1 < subPathStarts.length ? subPathStarts[s + 1] :
|
|
596
|
-
subPathSlices.push(
|
|
614
|
+
const end = s + 1 < subPathStarts.length ? subPathStarts[s + 1] : commands.length;
|
|
615
|
+
subPathSlices.push(commands.slice(start, end));
|
|
597
616
|
}
|
|
598
617
|
return {
|
|
599
618
|
primitives: [{
|
|
@@ -603,7 +622,7 @@ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metr
|
|
|
603
622
|
const isLast = i === subPathSlices.length - 1;
|
|
604
623
|
return {
|
|
605
624
|
type: "path",
|
|
606
|
-
|
|
625
|
+
commands: sub,
|
|
607
626
|
...baseProps,
|
|
608
627
|
...isFirst && markers.arrowStart ? { arrowStart: markers.arrowStart } : {},
|
|
609
628
|
...isLast && markers.arrowEnd ? { arrowEnd: markers.arrowEnd } : {}
|