@retikz/core 0.1.0-alpha.4 → 0.1.0-beta.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 (275) hide show
  1. package/dist/es/compile/compile.d.ts +23 -13
  2. package/dist/es/compile/compile.d.ts.map +1 -1
  3. package/dist/es/compile/compile.js +44 -25
  4. package/dist/es/compile/index.d.ts +1 -1
  5. package/dist/es/compile/index.d.ts.map +1 -1
  6. package/dist/es/compile/node.d.ts +40 -59
  7. package/dist/es/compile/node.d.ts.map +1 -1
  8. package/dist/es/compile/node.js +51 -43
  9. package/dist/es/compile/parseTarget.d.ts +3 -8
  10. package/dist/es/compile/parseTarget.d.ts.map +1 -1
  11. package/dist/es/compile/parseTarget.js +7 -19
  12. package/dist/es/compile/path/anchor.d.ts +19 -0
  13. package/dist/es/compile/path/anchor.d.ts.map +1 -0
  14. package/dist/es/compile/path/anchor.js +54 -0
  15. package/dist/es/compile/path/index.d.ts +24 -0
  16. package/dist/es/compile/path/index.d.ts.map +1 -0
  17. package/dist/es/compile/path/index.js +308 -0
  18. package/dist/es/compile/path/label.d.ts +18 -0
  19. package/dist/es/compile/path/label.d.ts.map +1 -0
  20. package/dist/es/compile/path/label.js +102 -0
  21. package/dist/es/compile/path/relative.d.ts +8 -0
  22. package/dist/es/compile/path/relative.d.ts.map +1 -0
  23. package/dist/es/compile/path/relative.js +48 -0
  24. package/dist/es/compile/path/shrink.d.ts +24 -0
  25. package/dist/es/compile/path/shrink.d.ts.map +1 -0
  26. package/dist/es/compile/path/shrink.js +136 -0
  27. package/dist/es/compile/path/split.d.ts +15 -0
  28. package/dist/es/compile/path/split.d.ts.map +1 -0
  29. package/dist/es/compile/path/split.js +46 -0
  30. package/dist/es/compile/position.d.ts +4 -8
  31. package/dist/es/compile/position.d.ts.map +1 -1
  32. package/dist/es/compile/position.js +10 -10
  33. package/dist/es/compile/text-metrics.d.ts +6 -5
  34. package/dist/es/compile/text-metrics.d.ts.map +1 -1
  35. package/dist/es/compile/text-metrics.js +11 -5
  36. package/dist/es/compile/view-box.d.ts +1 -4
  37. package/dist/es/compile/view-box.d.ts.map +1 -1
  38. package/dist/es/compile/view-box.js +1 -4
  39. package/dist/es/geometry/_transform.d.ts +21 -0
  40. package/dist/es/geometry/_transform.d.ts.map +1 -0
  41. package/dist/es/geometry/_transform.js +27 -0
  42. package/dist/es/geometry/arc.d.ts +3 -28
  43. package/dist/es/geometry/arc.d.ts.map +1 -1
  44. package/dist/es/geometry/arc.js +4 -31
  45. package/dist/es/geometry/bend.d.ts +2 -13
  46. package/dist/es/geometry/bend.d.ts.map +1 -1
  47. package/dist/es/geometry/bend.js +2 -13
  48. package/dist/es/geometry/circle.d.ts +6 -15
  49. package/dist/es/geometry/circle.d.ts.map +1 -1
  50. package/dist/es/geometry/circle.js +2 -20
  51. package/dist/es/geometry/diamond.d.ts +10 -26
  52. package/dist/es/geometry/diamond.d.ts.map +1 -1
  53. package/dist/es/geometry/diamond.js +5 -30
  54. package/dist/es/geometry/ellipse.d.ts +10 -17
  55. package/dist/es/geometry/ellipse.d.ts.map +1 -1
  56. package/dist/es/geometry/ellipse.js +5 -24
  57. package/dist/es/geometry/point.d.ts +8 -9
  58. package/dist/es/geometry/point.d.ts.map +1 -1
  59. package/dist/es/geometry/point.js +8 -9
  60. package/dist/es/geometry/polar.d.ts +12 -21
  61. package/dist/es/geometry/polar.d.ts.map +1 -1
  62. package/dist/es/geometry/polar.js +8 -19
  63. package/dist/es/geometry/rect.d.ts +8 -32
  64. package/dist/es/geometry/rect.d.ts.map +1 -1
  65. package/dist/es/geometry/rect.js +6 -49
  66. package/dist/es/geometry/segment.d.ts +15 -18
  67. package/dist/es/geometry/segment.d.ts.map +1 -1
  68. package/dist/es/geometry/segment.js +9 -16
  69. package/dist/es/index.d.ts +13 -13
  70. package/dist/es/index.d.ts.map +1 -1
  71. package/dist/es/index.js +7 -4
  72. package/dist/es/ir/coordinate.d.ts +18 -10
  73. package/dist/es/ir/coordinate.d.ts.map +1 -1
  74. package/dist/es/ir/coordinate.js +6 -11
  75. package/dist/es/ir/font.d.ts +21 -0
  76. package/dist/es/ir/font.d.ts.map +1 -0
  77. package/dist/es/ir/font.js +15 -0
  78. package/dist/es/ir/index.d.ts +2 -0
  79. package/dist/es/ir/index.d.ts.map +1 -1
  80. package/dist/es/ir/node.d.ts +51 -181
  81. package/dist/es/ir/node.d.ts.map +1 -1
  82. package/dist/es/ir/node.js +15 -70
  83. package/dist/es/ir/path/arrow.d.ts +210 -12
  84. package/dist/es/ir/path/arrow.d.ts.map +1 -1
  85. package/dist/es/ir/path/arrow.js +39 -12
  86. package/dist/es/ir/path/path.d.ts +477 -153
  87. package/dist/es/ir/path/path.d.ts.map +1 -1
  88. package/dist/es/ir/path/path.js +6 -6
  89. package/dist/es/ir/path/step.d.ts +395 -223
  90. package/dist/es/ir/path/step.d.ts.map +1 -1
  91. package/dist/es/ir/path/step.js +21 -22
  92. package/dist/es/ir/path/target.d.ts +25 -16
  93. package/dist/es/ir/path/target.d.ts.map +1 -1
  94. package/dist/es/ir/path/target.js +8 -6
  95. package/dist/es/ir/position/at-position.d.ts +4 -11
  96. package/dist/es/ir/position/at-position.d.ts.map +1 -1
  97. package/dist/es/ir/position/at-position.js +2 -9
  98. package/dist/es/ir/position/index.d.ts +1 -0
  99. package/dist/es/ir/position/index.d.ts.map +1 -1
  100. package/dist/es/ir/position/offset-position.d.ts +14 -0
  101. package/dist/es/ir/position/offset-position.d.ts.map +1 -0
  102. package/dist/es/ir/position/offset-position.js +14 -0
  103. package/dist/es/ir/position/polar-position.d.ts +1 -4
  104. package/dist/es/ir/position/polar-position.d.ts.map +1 -1
  105. package/dist/es/ir/position/polar-position.js +3 -6
  106. package/dist/es/ir/position/position.d.ts.map +1 -1
  107. package/dist/es/ir/position/position.js +1 -1
  108. package/dist/es/ir/scene.d.ts +1236 -422
  109. package/dist/es/ir/scene.d.ts.map +1 -1
  110. package/dist/es/ir/text.d.ts +96 -0
  111. package/dist/es/ir/text.d.ts.map +1 -0
  112. package/dist/es/ir/text.js +20 -0
  113. package/dist/es/parsers/parseTargetSugar.d.ts +4 -0
  114. package/dist/es/parsers/parseTargetSugar.d.ts.map +1 -1
  115. package/dist/es/parsers/parseTargetSugar.js +7 -19
  116. package/dist/es/parsers/parseWay.d.ts +26 -118
  117. package/dist/es/parsers/parseWay.d.ts.map +1 -1
  118. package/dist/es/parsers/parseWay.js +19 -61
  119. package/dist/es/primitive/ellipse.d.ts +4 -14
  120. package/dist/es/primitive/ellipse.d.ts.map +1 -1
  121. package/dist/es/primitive/group.d.ts +37 -3
  122. package/dist/es/primitive/group.d.ts.map +1 -1
  123. package/dist/es/primitive/path.d.ts +107 -7
  124. package/dist/es/primitive/path.d.ts.map +1 -1
  125. package/dist/es/primitive/rect.d.ts +2 -2
  126. package/dist/es/primitive/rect.d.ts.map +1 -1
  127. package/dist/es/primitive/scene.d.ts +2 -4
  128. package/dist/es/primitive/scene.d.ts.map +1 -1
  129. package/dist/es/primitive/text.d.ts +16 -32
  130. package/dist/es/primitive/text.d.ts.map +1 -1
  131. package/dist/es/primitive/view-box.d.ts +1 -1
  132. package/dist/es/primitive/view-box.d.ts.map +1 -1
  133. package/dist/es/types.d.ts +8 -0
  134. package/dist/es/types.d.ts.map +1 -1
  135. package/dist/lib/compile/compile.cjs +44 -25
  136. package/dist/lib/compile/compile.d.ts +23 -13
  137. package/dist/lib/compile/compile.d.ts.map +1 -1
  138. package/dist/lib/compile/index.d.ts +1 -1
  139. package/dist/lib/compile/index.d.ts.map +1 -1
  140. package/dist/lib/compile/node.cjs +51 -43
  141. package/dist/lib/compile/node.d.ts +40 -59
  142. package/dist/lib/compile/node.d.ts.map +1 -1
  143. package/dist/lib/compile/parseTarget.cjs +7 -19
  144. package/dist/lib/compile/parseTarget.d.ts +3 -8
  145. package/dist/lib/compile/parseTarget.d.ts.map +1 -1
  146. package/dist/lib/compile/path/anchor.cjs +58 -0
  147. package/dist/lib/compile/path/anchor.d.ts +19 -0
  148. package/dist/lib/compile/path/anchor.d.ts.map +1 -0
  149. package/dist/lib/compile/path/index.cjs +308 -0
  150. package/dist/lib/compile/path/index.d.ts +24 -0
  151. package/dist/lib/compile/path/index.d.ts.map +1 -0
  152. package/dist/lib/compile/path/label.cjs +103 -0
  153. package/dist/lib/compile/path/label.d.ts +18 -0
  154. package/dist/lib/compile/path/label.d.ts.map +1 -0
  155. package/dist/lib/compile/path/relative.cjs +48 -0
  156. package/dist/lib/compile/path/relative.d.ts +8 -0
  157. package/dist/lib/compile/path/relative.d.ts.map +1 -0
  158. package/dist/lib/compile/path/shrink.cjs +138 -0
  159. package/dist/lib/compile/path/shrink.d.ts +24 -0
  160. package/dist/lib/compile/path/shrink.d.ts.map +1 -0
  161. package/dist/lib/compile/path/split.cjs +46 -0
  162. package/dist/lib/compile/path/split.d.ts +15 -0
  163. package/dist/lib/compile/path/split.d.ts.map +1 -0
  164. package/dist/lib/compile/position.cjs +10 -10
  165. package/dist/lib/compile/position.d.ts +4 -8
  166. package/dist/lib/compile/position.d.ts.map +1 -1
  167. package/dist/lib/compile/text-metrics.cjs +11 -5
  168. package/dist/lib/compile/text-metrics.d.ts +6 -5
  169. package/dist/lib/compile/text-metrics.d.ts.map +1 -1
  170. package/dist/lib/compile/view-box.cjs +1 -4
  171. package/dist/lib/compile/view-box.d.ts +1 -4
  172. package/dist/lib/compile/view-box.d.ts.map +1 -1
  173. package/dist/lib/geometry/_transform.cjs +28 -0
  174. package/dist/lib/geometry/_transform.d.ts +21 -0
  175. package/dist/lib/geometry/_transform.d.ts.map +1 -0
  176. package/dist/lib/geometry/arc.cjs +3 -31
  177. package/dist/lib/geometry/arc.d.ts +3 -28
  178. package/dist/lib/geometry/arc.d.ts.map +1 -1
  179. package/dist/lib/geometry/bend.cjs +2 -13
  180. package/dist/lib/geometry/bend.d.ts +2 -13
  181. package/dist/lib/geometry/bend.d.ts.map +1 -1
  182. package/dist/lib/geometry/circle.cjs +6 -24
  183. package/dist/lib/geometry/circle.d.ts +6 -15
  184. package/dist/lib/geometry/circle.d.ts.map +1 -1
  185. package/dist/lib/geometry/diamond.cjs +9 -34
  186. package/dist/lib/geometry/diamond.d.ts +10 -26
  187. package/dist/lib/geometry/diamond.d.ts.map +1 -1
  188. package/dist/lib/geometry/ellipse.cjs +9 -28
  189. package/dist/lib/geometry/ellipse.d.ts +10 -17
  190. package/dist/lib/geometry/ellipse.d.ts.map +1 -1
  191. package/dist/lib/geometry/point.cjs +8 -9
  192. package/dist/lib/geometry/point.d.ts +8 -9
  193. package/dist/lib/geometry/point.d.ts.map +1 -1
  194. package/dist/lib/geometry/polar.cjs +8 -19
  195. package/dist/lib/geometry/polar.d.ts +12 -21
  196. package/dist/lib/geometry/polar.d.ts.map +1 -1
  197. package/dist/lib/geometry/rect.cjs +10 -53
  198. package/dist/lib/geometry/rect.d.ts +8 -32
  199. package/dist/lib/geometry/rect.d.ts.map +1 -1
  200. package/dist/lib/geometry/segment.cjs +9 -16
  201. package/dist/lib/geometry/segment.d.ts +15 -18
  202. package/dist/lib/geometry/segment.d.ts.map +1 -1
  203. package/dist/lib/index.cjs +14 -5
  204. package/dist/lib/index.d.ts +13 -13
  205. package/dist/lib/index.d.ts.map +1 -1
  206. package/dist/lib/ir/coordinate.cjs +6 -11
  207. package/dist/lib/ir/coordinate.d.ts +18 -10
  208. package/dist/lib/ir/coordinate.d.ts.map +1 -1
  209. package/dist/lib/ir/font.cjs +15 -0
  210. package/dist/lib/ir/font.d.ts +21 -0
  211. package/dist/lib/ir/font.d.ts.map +1 -0
  212. package/dist/lib/ir/index.d.ts +2 -0
  213. package/dist/lib/ir/index.d.ts.map +1 -1
  214. package/dist/lib/ir/node.cjs +16 -74
  215. package/dist/lib/ir/node.d.ts +51 -181
  216. package/dist/lib/ir/node.d.ts.map +1 -1
  217. package/dist/lib/ir/path/arrow.cjs +43 -11
  218. package/dist/lib/ir/path/arrow.d.ts +210 -12
  219. package/dist/lib/ir/path/arrow.d.ts.map +1 -1
  220. package/dist/lib/ir/path/path.cjs +5 -5
  221. package/dist/lib/ir/path/path.d.ts +477 -153
  222. package/dist/lib/ir/path/path.d.ts.map +1 -1
  223. package/dist/lib/ir/path/step.cjs +21 -22
  224. package/dist/lib/ir/path/step.d.ts +395 -223
  225. package/dist/lib/ir/path/step.d.ts.map +1 -1
  226. package/dist/lib/ir/path/target.cjs +9 -7
  227. package/dist/lib/ir/path/target.d.ts +25 -16
  228. package/dist/lib/ir/path/target.d.ts.map +1 -1
  229. package/dist/lib/ir/position/at-position.cjs +2 -9
  230. package/dist/lib/ir/position/at-position.d.ts +4 -11
  231. package/dist/lib/ir/position/at-position.d.ts.map +1 -1
  232. package/dist/lib/ir/position/index.d.ts +1 -0
  233. package/dist/lib/ir/position/index.d.ts.map +1 -1
  234. package/dist/lib/ir/position/offset-position.cjs +14 -0
  235. package/dist/lib/ir/position/offset-position.d.ts +14 -0
  236. package/dist/lib/ir/position/offset-position.d.ts.map +1 -0
  237. package/dist/lib/ir/position/polar-position.cjs +3 -6
  238. package/dist/lib/ir/position/polar-position.d.ts +1 -4
  239. package/dist/lib/ir/position/polar-position.d.ts.map +1 -1
  240. package/dist/lib/ir/position/position.cjs +1 -1
  241. package/dist/lib/ir/position/position.d.ts.map +1 -1
  242. package/dist/lib/ir/scene.d.ts +1236 -422
  243. package/dist/lib/ir/scene.d.ts.map +1 -1
  244. package/dist/lib/ir/text.cjs +21 -0
  245. package/dist/lib/ir/text.d.ts +96 -0
  246. package/dist/lib/ir/text.d.ts.map +1 -0
  247. package/dist/lib/parsers/parseTargetSugar.cjs +7 -19
  248. package/dist/lib/parsers/parseTargetSugar.d.ts +4 -0
  249. package/dist/lib/parsers/parseTargetSugar.d.ts.map +1 -1
  250. package/dist/lib/parsers/parseWay.cjs +19 -61
  251. package/dist/lib/parsers/parseWay.d.ts +26 -118
  252. package/dist/lib/parsers/parseWay.d.ts.map +1 -1
  253. package/dist/lib/primitive/ellipse.d.ts +4 -14
  254. package/dist/lib/primitive/ellipse.d.ts.map +1 -1
  255. package/dist/lib/primitive/group.d.ts +37 -3
  256. package/dist/lib/primitive/group.d.ts.map +1 -1
  257. package/dist/lib/primitive/path.d.ts +107 -7
  258. package/dist/lib/primitive/path.d.ts.map +1 -1
  259. package/dist/lib/primitive/rect.d.ts +2 -2
  260. package/dist/lib/primitive/rect.d.ts.map +1 -1
  261. package/dist/lib/primitive/scene.d.ts +2 -4
  262. package/dist/lib/primitive/scene.d.ts.map +1 -1
  263. package/dist/lib/primitive/text.d.ts +16 -32
  264. package/dist/lib/primitive/text.d.ts.map +1 -1
  265. package/dist/lib/primitive/view-box.d.ts +1 -1
  266. package/dist/lib/primitive/view-box.d.ts.map +1 -1
  267. package/dist/lib/types.d.ts +8 -0
  268. package/dist/lib/types.d.ts.map +1 -1
  269. package/package.json +13 -4
  270. package/dist/es/compile/path.d.ts +0 -30
  271. package/dist/es/compile/path.d.ts.map +0 -1
  272. package/dist/es/compile/path.js +0 -617
  273. package/dist/lib/compile/path.cjs +0 -617
  274. package/dist/lib/compile/path.d.ts +0 -30
  275. package/dist/lib/compile/path.d.ts.map +0 -1
@@ -1,617 +0,0 @@
1
- import { resolvePosition } from "./position.js";
2
- import { anchorOf, angleBoundaryOf, boundaryPointOf } from "./node.js";
3
- import { arcBoundingPoints, arcEndPoint, arcSvgFlags } from "../geometry/arc.js";
4
- import { bendControlPoints } from "../geometry/bend.js";
5
- import { arcSegmentSample, circleSegmentSample, cubicSegmentSample, ellipseSegmentSample, foldSegmentSample, lineSegmentSample, quadSegmentSample } from "../geometry/segment.js";
6
- import { parseNodeRef } from "./parseTarget.js";
7
- import { fallbackMeasurer } from "./text-metrics.js";
8
- //#region src/compile/path.ts
9
- /**
10
- * IR 的 path-level `arrow` + `arrowShape` 映射到 PathPrim 的
11
- * arrowStart / arrowEnd(值就是 shape 名)。`arrowShape` 省略时默认 'normal'。
12
- */
13
- var arrowMarkers = (arrow, shape = "normal") => {
14
- switch (arrow) {
15
- case "->": return { arrowEnd: shape };
16
- case "<-": return { arrowStart: shape };
17
- case "<->": return {
18
- arrowStart: shape,
19
- arrowEnd: shape
20
- };
21
- default: return {};
22
- }
23
- };
24
- /**
25
- * 按 arrow shape 决定线段需要从端点向内"缩短"多少(单位:strokeWidth 倍)。
26
- *
27
- * 用途:避免 hollow shape(如 `open` / `openDiamond` / `openCircle`)的
28
- * 空心内部被 path 描边穿过——把线末端退到形状背面位置,marker 的 apex
29
- * 才能正好落在原始端点上。
30
- *
31
- * 这个值与 marker 几何配套,必须与 react/render/arrowMarkers.tsx 里
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。
39
- */
40
- var SHRINK_FOR_SHAPE = {
41
- normal: 0,
42
- open: 4.8,
43
- stealth: 0,
44
- diamond: 0,
45
- openDiamond: 4.8,
46
- circle: 0,
47
- openCircle: 6
48
- };
49
- /** 把点 p 朝 target 方向移动 dist 个 path 单位 */
50
- var shiftToward = (p, target, dist) => {
51
- const dx = target[0] - p[0];
52
- const dy = target[1] - p[1];
53
- const len = Math.sqrt(dx * dx + dy * dy);
54
- if (len === 0 || dist === 0) return p;
55
- return [p[0] + dx / len * dist, p[1] + dy / len * dist];
56
- };
57
- /**
58
- * 求一个 step.to 的"参考点"——给段内 boundary clip 算方向 / 折角 corner 用。
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 仍是中心。直接坐标 / 极坐标:解析后的笛卡尔。
67
- */
68
- var refPointOfTarget = (target, nodeIndex) => {
69
- if (typeof target === "string") {
70
- const ref = parseNodeRef(target);
71
- const node = nodeIndex.get(ref.id);
72
- if (!node) return null;
73
- switch (ref.kind) {
74
- case "node": return [node.rect.x, node.rect.y];
75
- case "anchor": return anchorOf(node, ref.anchor);
76
- case "angle": return angleBoundaryOf(node, ref.angle);
77
- }
78
- }
79
- if (typeof target === "object" && !Array.isArray(target) && ("rel" in target || "relAccumulate" in target)) return null;
80
- return resolvePosition(target, nodeIndex);
81
- };
82
- /**
83
- * 折角中间点:基于"参考点"(节点中心或直接坐标)算直角拐点。
84
- * `-|` corner = (curr.x, prev.y);`|-` corner = (prev.x, curr.y)。
85
- */
86
- var cornerOf = (prev, curr, via) => via === "-|" ? [curr[0], prev[1]] : [prev[0], curr[1]];
87
- /**
88
- * 把 step.to 在给定方向 `toward` 上算出"实际绘制端点":
89
- * - 节点 ref auto(`'A'`):按 shape 多态走 boundaryPointOf——外扩 margin 后
90
- * 求边界与"中心→toward"射线交点
91
- * - 节点 ref 命名 anchor(`'A.north'`)/ 角度(`'A.30'`):位置已被解析定死,
92
- * 直接返回,**不再受 toward 影响**
93
- * - 直接坐标 / 极坐标:解析后直接返回(不做 clip)
94
- * 解析失败返回 null。
95
- */
96
- var clipForTarget = (target, toward, nodeIndex) => {
97
- if (typeof target === "string") {
98
- const ref = parseNodeRef(target);
99
- const node = nodeIndex.get(ref.id);
100
- if (!node) return null;
101
- switch (ref.kind) {
102
- case "node": return boundaryPointOf(node, toward);
103
- case "anchor": return anchorOf(node, ref.anchor);
104
- case "angle": return angleBoundaryOf(node, ref.angle);
105
- }
106
- }
107
- if (typeof target === "object" && !Array.isArray(target) && ("rel" in target || "relAccumulate" in target)) return null;
108
- return resolvePosition(target, nodeIndex);
109
- };
110
- /** 浅相等:两个 IRPosition 的两个分量都精确相等(未 round) */
111
- var samePoint = (a, b) => !!a && !!b && a[0] === b[0] && a[1] === b[1];
112
- /**
113
- * 语义 stroke 档位 → 数值映射(user units)。
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 缺省时生效。
125
- */
126
- var THICKNESS_TO_WIDTH = {
127
- ultraThin: .25,
128
- veryThin: .5,
129
- thin: 1,
130
- semithick: 1.5,
131
- thick: 2,
132
- veryThick: 3,
133
- ultraThick: 4
134
- };
135
- /** ADR-0004:边标注的默认字号 / 偏移量(user units) */
136
- var LABEL_FONT_SIZE = 14;
137
- var LABEL_LINE_HEIGHT_FACTOR = 1.2;
138
- var LABEL_SIDE_OFFSET = 4;
139
- var RAD_TO_DEG = 180 / Math.PI;
140
- /** label.position → 段参数 t */
141
- var tForLabelPosition = (pos) => pos === "near-start" ? .25 : pos === "near-end" ? .75 : .5;
142
- /**
143
- * 把 step.label + 段采样结果翻成 TextPrim(sloped 时再裹一层 group 旋转)。
144
- *
145
- * 几何(默认 side='above',position='midway'):
146
- * - 'above': 锚点 (x, y - LABEL_SIDE_OFFSET),align=middle, baseline=bottom
147
- * - 'below': 锚点 (x, y + LABEL_SIDE_OFFSET),align=middle, baseline=top
148
- * - 'left' : 锚点 (x - LABEL_SIDE_OFFSET, y),align=end, baseline=middle
149
- * - 'right': 锚点 (x + LABEL_SIDE_OFFSET, y),align=start, baseline=middle
150
- * - 'sloped': 锚点 (x, y) 不偏移,align=middle baseline=bottom,外裹 group
151
- * `transform="rotate(angle x y)"`,angle 由切线 atan2 算出(SVG y-down,CW 正向)
152
- *
153
- * 文本宽高交给 measureText;fallbackMeasurer 时只是估算,但不影响渲染坐标。
154
- * 返回 primitive + 用于 viewBox 的若干外接点(sloped 时按"近似最大半径"四角扩张)。
155
- */
156
- var emitLabelPrimitive = (label, sample, measureText, round) => {
157
- const fontSize = LABEL_FONT_SIZE;
158
- const lineHeight = fontSize * LABEL_LINE_HEIGHT_FACTOR;
159
- const m = measureText(label.text, { size: fontSize });
160
- const measuredWidth = m.width;
161
- const measuredHeight = m.height || lineHeight;
162
- const side = label.side ?? "above";
163
- let x = sample.point[0];
164
- let y = sample.point[1];
165
- let align = "middle";
166
- let baseline = "middle";
167
- if (side === "above") {
168
- y -= LABEL_SIDE_OFFSET;
169
- baseline = "bottom";
170
- } else if (side === "below") {
171
- y += LABEL_SIDE_OFFSET;
172
- baseline = "top";
173
- } else if (side === "left") {
174
- x -= LABEL_SIDE_OFFSET;
175
- align = "end";
176
- } else if (side === "right") {
177
- x += LABEL_SIDE_OFFSET;
178
- align = "start";
179
- } else baseline = "bottom";
180
- const text = {
181
- type: "text",
182
- x: round(x),
183
- y: round(y),
184
- lines: [{ text: label.text }],
185
- fontSize,
186
- align,
187
- baseline,
188
- lineHeight: round(lineHeight),
189
- measuredWidth: round(measuredWidth),
190
- measuredHeight: round(measuredHeight),
191
- fill: "currentColor"
192
- };
193
- if (side === "sloped") {
194
- const groupPrim = {
195
- type: "group",
196
- transform: `rotate(${round(Math.atan2(sample.tangent[1], sample.tangent[0]) * RAD_TO_DEG)} ${round(x)} ${round(y)})`,
197
- children: [text]
198
- };
199
- const r = Math.max(measuredWidth / 2, measuredHeight / 2);
200
- return {
201
- primitive: groupPrim,
202
- points: [
203
- [x - r, y - r],
204
- [x + r, y - r],
205
- [x - r, y + r],
206
- [x + r, y + r]
207
- ]
208
- };
209
- }
210
- const halfW = measuredWidth / 2;
211
- const halfH = measuredHeight / 2;
212
- return {
213
- primitive: text,
214
- points: [
215
- [x - halfW, y - halfH],
216
- [x + halfW, y - halfH],
217
- [x - halfW, y + halfH],
218
- [x + halfW, y + halfH]
219
- ]
220
- };
221
- };
222
- /**
223
- * 把 rel / relAccumulate 目标解析为绝对 Position,产出 step kind 不变但
224
- * `to` 字段全为绝对坐标的步序列。
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。
239
- */
240
- var normalizeRelativeTargets = (steps, nodeIndex) => {
241
- let prevEnd = null;
242
- const out = [];
243
- for (const step of steps) {
244
- if (step.kind === "cycle") {
245
- out.push(step);
246
- continue;
247
- }
248
- if (step.kind === "circlePath" || step.kind === "ellipsePath") {
249
- out.push(step);
250
- continue;
251
- }
252
- if (step.kind === "arc") {
253
- out.push(step);
254
- if (prevEnd) prevEnd = arcEndPoint(prevEnd, step.radius, step.endAngle);
255
- continue;
256
- }
257
- const original = step.to;
258
- let resolvedTo = original;
259
- let updatePrevEnd = true;
260
- if (typeof original === "object" && !Array.isArray(original) && "rel" in original) {
261
- const ref = prevEnd ?? [0, 0];
262
- resolvedTo = [ref[0] + original.rel[0], ref[1] + original.rel[1]];
263
- updatePrevEnd = false;
264
- } else if (typeof original === "object" && !Array.isArray(original) && "relAccumulate" in original) {
265
- const ref = prevEnd ?? [0, 0];
266
- resolvedTo = [ref[0] + original.relAccumulate[0], ref[1] + original.relAccumulate[1]];
267
- }
268
- out.push({
269
- ...step,
270
- to: resolvedTo
271
- });
272
- if (updatePrevEnd) {
273
- const pos = refPointOfTarget(resolvedTo, nodeIndex);
274
- if (pos) prevEnd = pos;
275
- }
276
- }
277
- return out;
278
- };
279
- /**
280
- * 把 IR Path 翻译为单个 PathPrim。
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 整体跳过)。
299
- */
300
- var emitPathPrimitive = (path, nodeIndex, round, measureText = fallbackMeasurer) => {
301
- const steps = normalizeRelativeTargets(path.children, nodeIndex);
302
- if (steps.length < 2) return null;
303
- /** ADR-0004:每段 step.label 翻译出的 TextPrim(或裹 sloped 旋转的 group),
304
- * 与 path 主体 primitive 同级返回;调用方 push 进 scene.primitives */
305
- const labelPrims = [];
306
- /** 为当前 step 收 label:算 sample 后调用 emitLabelPrimitive,结果累积到 labelPrims / points */
307
- const collectLabel = (step, sampleAt) => {
308
- if (step.kind === "move" || step.kind === "cycle" || !("label" in step) || !step.label) return;
309
- const sample = sampleAt(tForLabelPosition(step.label.position));
310
- const r = emitLabelPrimitive(step.label, sample, measureText, round);
311
- labelPrims.push(r.primitive);
312
- for (const p of r.points) points.push(p);
313
- };
314
- const hasTo = (s) => s.kind !== "cycle" && s.kind !== "arc" && s.kind !== "circlePath" && s.kind !== "ellipsePath";
315
- const anchors = steps.map((s) => hasTo(s) ? refPointOfTarget(s.to, nodeIndex) : null);
316
- /** 找 i 之前最近的"有 to 字段的 step" + 它的 anchor */
317
- const findPrev = (i) => {
318
- for (let j = i - 1; j >= 0; j--) {
319
- const s = steps[j];
320
- if (!hasTo(s)) continue;
321
- const a = anchors[j];
322
- if (!a) return null;
323
- return {
324
- step: s,
325
- anchor: a
326
- };
327
- }
328
- return null;
329
- };
330
- /** 找 i 之前最近的 move 的 to——cycle 闭合的目标 */
331
- const findRecentMoveTo = (i) => {
332
- for (let j = i - 1; j >= 0; j--) {
333
- const s = steps[j];
334
- if (s.kind === "move") return s.to;
335
- }
336
- return null;
337
- };
338
- const ops = [];
339
- const points = [];
340
- let lastEnd = null;
341
- let subPathStart = null;
342
- /**
343
- * "笔位覆盖"——arc / circlePath / ellipsePath 这种没有 `to` 字段的 step
344
- * 不能通过 `prev.step.to` 重算下一段的起点。它们设置 penOverride,下一个
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 回中心
351
- */
352
- let penOverride = null;
353
- const emitM = (p) => {
354
- ops.push({
355
- cmd: "M",
356
- point: p
357
- });
358
- points.push(p);
359
- subPathStart = p;
360
- lastEnd = p;
361
- };
362
- const emitL = (p) => {
363
- ops.push({
364
- cmd: "L",
365
- point: p
366
- });
367
- points.push(p);
368
- lastEnd = p;
369
- };
370
- const emitZ = () => {
371
- ops.push({ cmd: "Z" });
372
- lastEnd = subPathStart;
373
- };
374
- const emitQ = (control, p) => {
375
- ops.push({
376
- cmd: "Q",
377
- control,
378
- point: p
379
- });
380
- points.push(control);
381
- points.push(p);
382
- lastEnd = p;
383
- };
384
- const emitC = (c1, c2, p) => {
385
- ops.push({
386
- cmd: "C",
387
- control1: c1,
388
- control2: c2,
389
- point: p
390
- });
391
- points.push(c1);
392
- points.push(c2);
393
- points.push(p);
394
- lastEnd = p;
395
- };
396
- const emitA = (rx, ry, largeArc, sweep, p) => {
397
- ops.push({
398
- cmd: "A",
399
- rx,
400
- ry,
401
- largeArc,
402
- sweep,
403
- point: p
404
- });
405
- points.push(p);
406
- lastEnd = p;
407
- };
408
- /** 段起点:与 lastEnd 相同就复用 cursor(省掉冗余 M),否则发 M */
409
- const startSegment = (p) => {
410
- if (samePoint(p, lastEnd)) return;
411
- emitM(p);
412
- };
413
- for (let i = 0; i < steps.length; i++) {
414
- const step = steps[i];
415
- if (step.kind === "move") continue;
416
- if (step.kind === "cycle") {
417
- const moveTo = findRecentMoveTo(i);
418
- const prev = findPrev(i);
419
- if (!moveTo || !prev) continue;
420
- const moveAnchor = refPointOfTarget(moveTo, nodeIndex);
421
- if (!moveAnchor) return null;
422
- const fromClip = clipForTarget(prev.step.to, moveAnchor, nodeIndex);
423
- const toClip = clipForTarget(moveTo, prev.anchor, nodeIndex);
424
- if (!fromClip || !toClip) return null;
425
- if (samePoint(fromClip, lastEnd) && samePoint(toClip, subPathStart)) {
426
- emitZ();
427
- continue;
428
- }
429
- startSegment(fromClip);
430
- emitL(toClip);
431
- continue;
432
- }
433
- const prev = findPrev(i);
434
- if (!prev) return null;
435
- if (step.kind === "arc") {
436
- const center = prev.anchor;
437
- const startPt = arcEndPoint(center, step.radius, step.startAngle);
438
- const endPt = arcEndPoint(center, step.radius, step.endAngle);
439
- const flags = arcSvgFlags(step.startAngle, step.endAngle);
440
- startSegment(startPt);
441
- emitA(step.radius, step.radius, flags.largeArc, flags.sweep, endPt);
442
- for (const p of arcBoundingPoints(center, step.radius, step.startAngle, step.endAngle)) points.push(p);
443
- collectLabel(step, (t) => arcSegmentSample(center, step.radius, step.startAngle, step.endAngle, t));
444
- penOverride = endPt;
445
- continue;
446
- }
447
- if (step.kind === "circlePath") {
448
- const center = prev.anchor;
449
- const r = step.radius;
450
- const right = [center[0] + r, center[1]];
451
- const left = [center[0] - r, center[1]];
452
- startSegment(right);
453
- emitA(r, r, 0, 1, left);
454
- emitA(r, r, 0, 1, right);
455
- points.push([center[0] + r, center[1]]);
456
- points.push([center[0] - r, center[1]]);
457
- points.push([center[0], center[1] + r]);
458
- points.push([center[0], center[1] - r]);
459
- collectLabel(step, (t) => circleSegmentSample(center, r, t));
460
- penOverride = center;
461
- continue;
462
- }
463
- if (step.kind === "ellipsePath") {
464
- const center = prev.anchor;
465
- const rx = step.radiusX;
466
- const ry = step.radiusY;
467
- const right = [center[0] + rx, center[1]];
468
- const left = [center[0] - rx, center[1]];
469
- startSegment(right);
470
- emitA(rx, ry, 0, 1, left);
471
- emitA(rx, ry, 0, 1, right);
472
- points.push([center[0] + rx, center[1]]);
473
- points.push([center[0] - rx, center[1]]);
474
- points.push([center[0], center[1] + ry]);
475
- points.push([center[0], center[1] - ry]);
476
- collectLabel(step, (t) => ellipseSegmentSample(center, rx, ry, t));
477
- penOverride = center;
478
- continue;
479
- }
480
- const currAnchor = anchors[i];
481
- if (!currAnchor) return null;
482
- const usedOverride = penOverride;
483
- penOverride = null;
484
- if (step.kind === "line") {
485
- const fromClip = usedOverride ?? clipForTarget(prev.step.to, currAnchor, nodeIndex);
486
- const toClip = clipForTarget(step.to, prev.anchor, nodeIndex);
487
- if (!fromClip || !toClip) return null;
488
- startSegment(fromClip);
489
- emitL(toClip);
490
- collectLabel(step, (t) => lineSegmentSample(fromClip, toClip, t));
491
- continue;
492
- }
493
- if (step.kind === "curve") {
494
- const fromClip = usedOverride ?? clipForTarget(prev.step.to, step.control, nodeIndex);
495
- const toClip = clipForTarget(step.to, step.control, nodeIndex);
496
- if (!fromClip || !toClip) return null;
497
- startSegment(fromClip);
498
- emitQ(step.control, toClip);
499
- collectLabel(step, (t) => quadSegmentSample(fromClip, step.control, toClip, t));
500
- continue;
501
- }
502
- if (step.kind === "cubic") {
503
- const fromClip = usedOverride ?? clipForTarget(prev.step.to, step.control1, nodeIndex);
504
- const toClip = clipForTarget(step.to, step.control2, nodeIndex);
505
- if (!fromClip || !toClip) return null;
506
- startSegment(fromClip);
507
- emitC(step.control1, step.control2, toClip);
508
- collectLabel(step, (t) => cubicSegmentSample(fromClip, step.control1, step.control2, toClip, t));
509
- continue;
510
- }
511
- if (step.kind === "bend") {
512
- const angle = step.bendAngle ?? 30;
513
- const [c1, c2] = bendControlPoints(prev.anchor, currAnchor, step.bendDirection, angle);
514
- const fromClip = usedOverride ?? clipForTarget(prev.step.to, c1, nodeIndex);
515
- const toClip = clipForTarget(step.to, c2, nodeIndex);
516
- if (!fromClip || !toClip) return null;
517
- startSegment(fromClip);
518
- emitC(c1, c2, toClip);
519
- collectLabel(step, (t) => cubicSegmentSample(fromClip, c1, c2, toClip, t));
520
- continue;
521
- }
522
- const corner = cornerOf(prev.anchor, currAnchor, step.via);
523
- const fromClip = usedOverride ?? clipForTarget(prev.step.to, corner, nodeIndex);
524
- const toClip = clipForTarget(step.to, corner, nodeIndex);
525
- if (!fromClip || !toClip) return null;
526
- startSegment(fromClip);
527
- emitL(corner);
528
- emitL(toClip);
529
- collectLabel(step, (t) => foldSegmentSample(fromClip, corner, toClip, t));
530
- }
531
- const strokeWidth = path.strokeWidth ?? (path.thickness ? THICKNESS_TO_WIDTH[path.thickness] : 1);
532
- const baseProps = {
533
- stroke: path.stroke ?? "currentColor",
534
- strokeWidth,
535
- fill: path.fill ?? "none",
536
- fillRule: path.fillRule,
537
- strokeDasharray: path.strokeDasharray,
538
- strokeLinecap: path.lineCap,
539
- strokeLinejoin: path.lineJoin,
540
- opacity: path.opacity,
541
- fillOpacity: path.fillOpacity,
542
- strokeOpacity: path.drawOpacity
543
- };
544
- const markers = arrowMarkers(path.arrow, path.arrowShape);
545
- const hasArrows = !!markers.arrowStart || !!markers.arrowEnd;
546
- const shrinkStart = markers.arrowStart ? SHRINK_FOR_SHAPE[markers.arrowStart] : 0;
547
- const shrinkEnd = markers.arrowEnd ? SHRINK_FOR_SHAPE[markers.arrowEnd] : 0;
548
- if (shrinkStart > 0) {
549
- const firstIdx = ops.findIndex((o) => o.cmd === "M");
550
- if (firstIdx >= 0) {
551
- const cur = ops[firstIdx];
552
- const next = ops.slice(firstIdx + 1).find((o) => o.cmd !== "Z");
553
- if (cur.cmd !== "Z" && next) cur.point = shiftToward(cur.point, next.point, shrinkStart * strokeWidth);
554
- }
555
- }
556
- if (shrinkEnd > 0) {
557
- let lastIdx = -1;
558
- for (let i = ops.length - 1; i >= 0; i--) if (ops[i].cmd !== "Z") {
559
- lastIdx = i;
560
- break;
561
- }
562
- if (lastIdx > 0) {
563
- let prevIdx = lastIdx - 1;
564
- while (prevIdx >= 0 && ops[prevIdx].cmd === "Z") prevIdx--;
565
- if (prevIdx >= 0) {
566
- const cur = ops[lastIdx];
567
- const prev = ops[prevIdx];
568
- if (cur.cmd !== "Z" && prev.cmd !== "Z") cur.point = shiftToward(cur.point, prev.point, shrinkEnd * strokeWidth);
569
- }
570
- }
571
- }
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
- const subPathStarts = [];
580
- tokens.forEach((tok, idx) => {
581
- if (tok.startsWith("M ")) subPathStarts.push(idx);
582
- });
583
- if (!hasArrows || subPathStarts.length <= 1) return {
584
- primitives: [{
585
- type: "path",
586
- d: tokens.join(" "),
587
- ...baseProps,
588
- ...markers
589
- }, ...labelPrims],
590
- points
591
- };
592
- const subPathSlices = [];
593
- for (let s = 0; s < subPathStarts.length; s++) {
594
- const start = subPathStarts[s];
595
- const end = s + 1 < subPathStarts.length ? subPathStarts[s + 1] : tokens.length;
596
- subPathSlices.push(tokens.slice(start, end));
597
- }
598
- return {
599
- primitives: [{
600
- type: "group",
601
- children: subPathSlices.map((sub, i) => {
602
- const isFirst = i === 0;
603
- const isLast = i === subPathSlices.length - 1;
604
- return {
605
- type: "path",
606
- d: sub.join(" "),
607
- ...baseProps,
608
- ...isFirst && markers.arrowStart ? { arrowStart: markers.arrowStart } : {},
609
- ...isLast && markers.arrowEnd ? { arrowEnd: markers.arrowEnd } : {}
610
- };
611
- })
612
- }, ...labelPrims],
613
- points
614
- };
615
- };
616
- //#endregion
617
- export { emitPathPrimitive };