@retikz/core 0.2.0-alpha.5 → 0.2.0-alpha.7

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 (181) hide show
  1. package/dist/es/compile/anchor-cache.d.ts +10 -0
  2. package/dist/es/compile/anchor-cache.d.ts.map +1 -1
  3. package/dist/es/compile/anchor-cache.js +25 -1
  4. package/dist/es/compile/compile.d.ts.map +1 -1
  5. package/dist/es/compile/compile.js +10 -4
  6. package/dist/es/compile/index.d.ts +0 -1
  7. package/dist/es/compile/index.d.ts.map +1 -1
  8. package/dist/es/compile/node.d.ts +19 -4
  9. package/dist/es/compile/node.d.ts.map +1 -1
  10. package/dist/es/compile/node.js +159 -29
  11. package/dist/es/compile/paint.d.ts +16 -0
  12. package/dist/es/compile/paint.d.ts.map +1 -0
  13. package/dist/es/compile/paint.js +37 -0
  14. package/dist/es/compile/path/anchor.d.ts.map +1 -1
  15. package/dist/es/compile/path/anchor.js +17 -18
  16. package/dist/es/compile/path/index.d.ts +3 -0
  17. package/dist/es/compile/path/index.d.ts.map +1 -1
  18. package/dist/es/compile/path/index.js +12 -5
  19. package/dist/es/geometry/_edge.d.ts +29 -0
  20. package/dist/es/geometry/_edge.d.ts.map +1 -0
  21. package/dist/es/geometry/_edge.js +35 -0
  22. package/dist/es/geometry/circle.d.ts +3 -0
  23. package/dist/es/geometry/circle.d.ts.map +1 -1
  24. package/dist/es/geometry/circle.js +7 -0
  25. package/dist/es/geometry/diamond.d.ts +3 -0
  26. package/dist/es/geometry/diamond.d.ts.map +1 -1
  27. package/dist/es/geometry/diamond.js +29 -0
  28. package/dist/es/geometry/ellipse.d.ts +3 -0
  29. package/dist/es/geometry/ellipse.d.ts.map +1 -1
  30. package/dist/es/geometry/ellipse.js +7 -0
  31. package/dist/es/geometry/rect.d.ts +3 -0
  32. package/dist/es/geometry/rect.d.ts.map +1 -1
  33. package/dist/es/geometry/rect.js +6 -0
  34. package/dist/es/index.d.ts +4 -4
  35. package/dist/es/index.d.ts.map +1 -1
  36. package/dist/es/index.js +6 -4
  37. package/dist/es/ir/coordinate.d.ts +2 -2
  38. package/dist/es/ir/index.d.ts +1 -0
  39. package/dist/es/ir/index.d.ts.map +1 -1
  40. package/dist/es/ir/node.d.ts +264 -11
  41. package/dist/es/ir/node.d.ts.map +1 -1
  42. package/dist/es/ir/node.js +9 -2
  43. package/dist/es/ir/paint.d.ts +132 -0
  44. package/dist/es/ir/paint.d.ts.map +1 -0
  45. package/dist/es/ir/paint.js +53 -0
  46. package/dist/es/ir/path/arrow.d.ts +24 -24
  47. package/dist/es/ir/path/path.d.ts +860 -204
  48. package/dist/es/ir/path/path.d.ts.map +1 -1
  49. package/dist/es/ir/path/path.js +2 -1
  50. package/dist/es/ir/path/step.d.ts +930 -192
  51. package/dist/es/ir/path/step.d.ts.map +1 -1
  52. package/dist/es/ir/path/target.d.ts +71 -1
  53. package/dist/es/ir/path/target.d.ts.map +1 -1
  54. package/dist/es/ir/path/target.js +24 -3
  55. package/dist/es/ir/scope.d.ts +2095 -335
  56. package/dist/es/ir/scope.d.ts.map +1 -1
  57. package/dist/es/ir/scope.js +2 -1
  58. package/dist/es/parsers/index.d.ts +1 -0
  59. package/dist/es/parsers/index.d.ts.map +1 -1
  60. package/dist/es/parsers/parseNodeTarget.d.ts +4 -0
  61. package/dist/es/parsers/parseNodeTarget.d.ts.map +1 -0
  62. package/dist/es/parsers/parseNodeTarget.js +33 -0
  63. package/dist/es/parsers/parseTargetSugar.d.ts +4 -2
  64. package/dist/es/parsers/parseTargetSugar.d.ts.map +1 -1
  65. package/dist/es/parsers/parseTargetSugar.js +6 -3
  66. package/dist/es/parsers/parseWay.d.ts +1 -1
  67. package/dist/es/parsers/parseWay.d.ts.map +1 -1
  68. package/dist/es/primitive/ellipse.d.ts +3 -5
  69. package/dist/es/primitive/ellipse.d.ts.map +1 -1
  70. package/dist/es/primitive/paint.d.ts +24 -0
  71. package/dist/es/primitive/paint.d.ts.map +1 -0
  72. package/dist/es/primitive/path.d.ts +3 -2
  73. package/dist/es/primitive/path.d.ts.map +1 -1
  74. package/dist/es/primitive/rect.d.ts +3 -2
  75. package/dist/es/primitive/rect.d.ts.map +1 -1
  76. package/dist/es/primitive/scene.d.ts +4 -0
  77. package/dist/es/primitive/scene.d.ts.map +1 -1
  78. package/dist/es/shapes/circle.d.ts.map +1 -1
  79. package/dist/es/shapes/circle.js +1 -0
  80. package/dist/es/shapes/diamond.d.ts.map +1 -1
  81. package/dist/es/shapes/diamond.js +1 -0
  82. package/dist/es/shapes/ellipse.d.ts.map +1 -1
  83. package/dist/es/shapes/ellipse.js +1 -0
  84. package/dist/es/shapes/rectangle.d.ts.map +1 -1
  85. package/dist/es/shapes/rectangle.js +1 -0
  86. package/dist/es/shapes/types.d.ts +8 -2
  87. package/dist/es/shapes/types.d.ts.map +1 -1
  88. package/dist/lib/compile/anchor-cache.cjs +25 -0
  89. package/dist/lib/compile/anchor-cache.d.ts +10 -0
  90. package/dist/lib/compile/anchor-cache.d.ts.map +1 -1
  91. package/dist/lib/compile/compile.cjs +9 -3
  92. package/dist/lib/compile/compile.d.ts.map +1 -1
  93. package/dist/lib/compile/index.d.ts +0 -1
  94. package/dist/lib/compile/index.d.ts.map +1 -1
  95. package/dist/lib/compile/node.cjs +159 -28
  96. package/dist/lib/compile/node.d.ts +19 -4
  97. package/dist/lib/compile/node.d.ts.map +1 -1
  98. package/dist/lib/compile/paint.cjs +37 -0
  99. package/dist/lib/compile/paint.d.ts +16 -0
  100. package/dist/lib/compile/paint.d.ts.map +1 -0
  101. package/dist/lib/compile/path/anchor.cjs +16 -17
  102. package/dist/lib/compile/path/anchor.d.ts.map +1 -1
  103. package/dist/lib/compile/path/index.cjs +12 -5
  104. package/dist/lib/compile/path/index.d.ts +3 -0
  105. package/dist/lib/compile/path/index.d.ts.map +1 -1
  106. package/dist/lib/geometry/_edge.cjs +38 -0
  107. package/dist/lib/geometry/_edge.d.ts +29 -0
  108. package/dist/lib/geometry/_edge.d.ts.map +1 -0
  109. package/dist/lib/geometry/circle.cjs +7 -0
  110. package/dist/lib/geometry/circle.d.ts +3 -0
  111. package/dist/lib/geometry/circle.d.ts.map +1 -1
  112. package/dist/lib/geometry/diamond.cjs +29 -0
  113. package/dist/lib/geometry/diamond.d.ts +3 -0
  114. package/dist/lib/geometry/diamond.d.ts.map +1 -1
  115. package/dist/lib/geometry/ellipse.cjs +7 -0
  116. package/dist/lib/geometry/ellipse.d.ts +3 -0
  117. package/dist/lib/geometry/ellipse.d.ts.map +1 -1
  118. package/dist/lib/geometry/rect.cjs +6 -0
  119. package/dist/lib/geometry/rect.d.ts +3 -0
  120. package/dist/lib/geometry/rect.d.ts.map +1 -1
  121. package/dist/lib/index.cjs +9 -2
  122. package/dist/lib/index.d.ts +4 -4
  123. package/dist/lib/index.d.ts.map +1 -1
  124. package/dist/lib/ir/coordinate.d.ts +2 -2
  125. package/dist/lib/ir/index.d.ts +1 -0
  126. package/dist/lib/ir/index.d.ts.map +1 -1
  127. package/dist/lib/ir/node.cjs +9 -2
  128. package/dist/lib/ir/node.d.ts +264 -11
  129. package/dist/lib/ir/node.d.ts.map +1 -1
  130. package/dist/lib/ir/paint.cjs +54 -0
  131. package/dist/lib/ir/paint.d.ts +132 -0
  132. package/dist/lib/ir/paint.d.ts.map +1 -0
  133. package/dist/lib/ir/path/arrow.d.ts +24 -24
  134. package/dist/lib/ir/path/path.cjs +2 -1
  135. package/dist/lib/ir/path/path.d.ts +860 -204
  136. package/dist/lib/ir/path/path.d.ts.map +1 -1
  137. package/dist/lib/ir/path/step.d.ts +930 -192
  138. package/dist/lib/ir/path/step.d.ts.map +1 -1
  139. package/dist/lib/ir/path/target.cjs +25 -2
  140. package/dist/lib/ir/path/target.d.ts +71 -1
  141. package/dist/lib/ir/path/target.d.ts.map +1 -1
  142. package/dist/lib/ir/scope.cjs +2 -1
  143. package/dist/lib/ir/scope.d.ts +2095 -335
  144. package/dist/lib/ir/scope.d.ts.map +1 -1
  145. package/dist/lib/parsers/index.d.ts +1 -0
  146. package/dist/lib/parsers/index.d.ts.map +1 -1
  147. package/dist/lib/parsers/parseNodeTarget.cjs +33 -0
  148. package/dist/lib/parsers/parseNodeTarget.d.ts +4 -0
  149. package/dist/lib/parsers/parseNodeTarget.d.ts.map +1 -0
  150. package/dist/lib/parsers/parseTargetSugar.cjs +6 -3
  151. package/dist/lib/parsers/parseTargetSugar.d.ts +4 -2
  152. package/dist/lib/parsers/parseTargetSugar.d.ts.map +1 -1
  153. package/dist/lib/parsers/parseWay.d.ts +1 -1
  154. package/dist/lib/parsers/parseWay.d.ts.map +1 -1
  155. package/dist/lib/primitive/ellipse.d.ts +3 -5
  156. package/dist/lib/primitive/ellipse.d.ts.map +1 -1
  157. package/dist/lib/primitive/paint.d.ts +24 -0
  158. package/dist/lib/primitive/paint.d.ts.map +1 -0
  159. package/dist/lib/primitive/path.d.ts +3 -2
  160. package/dist/lib/primitive/path.d.ts.map +1 -1
  161. package/dist/lib/primitive/rect.d.ts +3 -2
  162. package/dist/lib/primitive/rect.d.ts.map +1 -1
  163. package/dist/lib/primitive/scene.d.ts +4 -0
  164. package/dist/lib/primitive/scene.d.ts.map +1 -1
  165. package/dist/lib/shapes/circle.cjs +1 -0
  166. package/dist/lib/shapes/circle.d.ts.map +1 -1
  167. package/dist/lib/shapes/diamond.cjs +1 -0
  168. package/dist/lib/shapes/diamond.d.ts.map +1 -1
  169. package/dist/lib/shapes/ellipse.cjs +1 -0
  170. package/dist/lib/shapes/ellipse.d.ts.map +1 -1
  171. package/dist/lib/shapes/rectangle.cjs +1 -0
  172. package/dist/lib/shapes/rectangle.d.ts.map +1 -1
  173. package/dist/lib/shapes/types.d.ts +8 -2
  174. package/dist/lib/shapes/types.d.ts.map +1 -1
  175. package/package.json +1 -1
  176. package/dist/es/compile/parseTarget.d.ts +0 -20
  177. package/dist/es/compile/parseTarget.d.ts.map +0 -1
  178. package/dist/es/compile/parseTarget.js +0 -36
  179. package/dist/lib/compile/parseTarget.cjs +0 -36
  180. package/dist/lib/compile/parseTarget.d.ts +0 -20
  181. package/dist/lib/compile/parseTarget.d.ts.map +0 -1
@@ -5,6 +5,49 @@ var DEFAULT_FONT_SIZE = 14;
5
5
  var DEFAULT_PADDING = 8;
6
6
  var DEFAULT_LINE_HEIGHT_FACTOR = 1.2;
7
7
  var DEG_TO_RAD = Math.PI / 180;
8
+ /** CJK / fullwidth ranges: break per-character (no whitespace needed) */
9
+ var isCjk = (ch) => {
10
+ const c = ch.codePointAt(0) ?? 0;
11
+ return c >= 12288 && c <= 12351 || c >= 12352 && c <= 12543 || c >= 13312 && c <= 19903 || c >= 19968 && c <= 40959 || c >= 63744 && c <= 64255 || c >= 65280 && c <= 65519;
12
+ };
13
+ /**
14
+ * 按 maxWidth 贪心折行:西文按词(空白分割)、CJK 按字;长不可断 token 溢出不硬断
15
+ * @description 用注入的 measureText 度量;连续空白归一为单空格分隔。空文本返回 [''].
16
+ */
17
+ var wrapText = (text, font, maxWidth, measure) => {
18
+ const units = [];
19
+ for (const seg of text.split(/(\s+)/)) {
20
+ if (seg === "") continue;
21
+ if (/^\s+$/.test(seg)) {
22
+ units.push(" ");
23
+ continue;
24
+ }
25
+ let run = "";
26
+ for (const ch of seg) if (isCjk(ch)) {
27
+ if (run) {
28
+ units.push(run);
29
+ run = "";
30
+ }
31
+ units.push(ch);
32
+ } else run += ch;
33
+ if (run) units.push(run);
34
+ }
35
+ const lines = [];
36
+ let cur = "";
37
+ for (const u of units) {
38
+ if (u === " ") {
39
+ if (cur !== "") cur += " ";
40
+ continue;
41
+ }
42
+ const candidate = cur === "" ? u : cur + u;
43
+ if (cur !== "" && measure(candidate, font).width > maxWidth) {
44
+ lines.push(cur.trimEnd());
45
+ cur = u;
46
+ } else cur = candidate;
47
+ }
48
+ if (cur.trimEnd() !== "") lines.push(cur.trimEnd());
49
+ return lines.length > 0 ? lines : [""];
50
+ };
8
51
  /** Node label 与 node 边界默认距离(TikZ 默认 0pt 视觉太贴) */
9
52
  var DEFAULT_LABEL_DISTANCE = 12;
10
53
  /** dashed 预设:4 px 实线 + 2 px 间隙循环 */
@@ -84,7 +127,8 @@ var LABEL_DIRECTION_MAP = {
84
127
  * 若用带 rotate 的 rect,label 位置会被 anchorOf / angleBoundaryOf 旋转一次、再被外层 group 旋转一次(双重旋转)。
85
128
  * anchorOf / angleBoundaryOf 本身不改(path anchor `'A.north'` / `'A.30'` 仍需带 rotate 的 rect)。
86
129
  */
87
- var labelCenter = (layout, label) => {
130
+ /** label node 边界上的附着点(未旋转局部系;pin 引线起点 = 此点) */
131
+ var labelBorderPoint = (layout, label) => {
88
132
  const aaLayout = {
89
133
  ...layout,
90
134
  rect: {
@@ -92,15 +136,32 @@ var labelCenter = (layout, label) => {
92
136
  rotate: 0
93
137
  }
94
138
  };
139
+ if (typeof label.position === "number") return angleBoundaryOf(aaLayout, label.position);
140
+ const { anchor } = LABEL_DIRECTION_MAP[label.position];
141
+ return anchorOf(aaLayout, anchor);
142
+ };
143
+ var labelCenter = (layout, label) => {
144
+ const [bx, by] = labelBorderPoint(layout, label);
95
145
  if (typeof label.position === "number") {
96
146
  const rad = label.position * Math.PI / 180;
97
- const [bx, by] = angleBoundaryOf(aaLayout, label.position);
98
147
  return [bx + Math.cos(rad) * label.distance, by + Math.sin(rad) * label.distance];
99
148
  }
100
- const { anchor, vec } = LABEL_DIRECTION_MAP[label.position];
101
- const [bx, by] = anchorOf(aaLayout, anchor);
149
+ const { vec } = LABEL_DIRECTION_MAP[label.position];
102
150
  return [bx + vec[0] * label.distance, by + vec[1] * label.distance];
103
151
  };
152
+ /** 从 label 中心朝 border 方向,求 label 框(halfW×halfH)边界交点(pin 引线终点 = label 框近 node 边) */
153
+ var labelBoxEdgeToward = (center, border, halfW, halfH) => {
154
+ const dx = border[0] - center[0];
155
+ const dy = border[1] - center[1];
156
+ const len = Math.hypot(dx, dy);
157
+ if (len < 1e-9) return center;
158
+ const ux = dx / len;
159
+ const uy = dy / len;
160
+ const sx = Math.abs(ux) > 1e-9 ? halfW / Math.abs(ux) : Number.POSITIVE_INFINITY;
161
+ const sy = Math.abs(uy) > 1e-9 ? halfH / Math.abs(uy) : Number.POSITIVE_INFINITY;
162
+ const s = Math.min(sx, sy, len);
163
+ return [center[0] + ux * s, center[1] + uy * s];
164
+ };
104
165
  /** 角度换算常量(弧度 → 度) */
105
166
  var RAD_TO_DEG = 180 / Math.PI;
106
167
  /**
@@ -162,32 +223,38 @@ var layoutNode = (node, measureText, nameStack, nodeDistance, scopeChain = [], l
162
223
  const lineHeight = (node.lineHeight ?? baseFontSize * DEFAULT_LINE_HEIGHT_FACTOR) * sy;
163
224
  const align = alignToTextAnchor(node.align ?? "center");
164
225
  const rawLines = node.text === void 0 ? void 0 : typeof node.text === "string" ? [node.text] : node.text;
226
+ const maxTextWidth = node.maxTextWidth !== void 0 ? node.maxTextWidth * sx : void 0;
165
227
  let textWidth = 0;
166
228
  let textHeight = 0;
167
229
  let lines;
168
230
  if (rawLines) {
169
- lines = rawLines.map((spec) => {
231
+ lines = [];
232
+ for (const spec of rawLines) {
170
233
  const isObj = typeof spec !== "string";
171
234
  const text = isObj ? spec.text : spec;
172
235
  const lineFont = isObj ? spec.font : void 0;
173
- const m = measureText(text, {
236
+ const font = {
174
237
  size: lineFont?.size ?? fontSize,
175
238
  family: lineFont?.family ?? fontFamily,
176
239
  weight: lineFont?.weight ?? fontWeight,
177
240
  style: lineFont?.style ?? fontStyle
178
- });
179
- if (m.width > textWidth) textWidth = m.width;
180
- const out = { text };
181
- if (isObj) {
182
- if (spec.fill !== void 0) out.fill = spec.fill;
183
- if (spec.opacity !== void 0) out.opacity = spec.opacity;
184
- if (lineFont?.size !== void 0) out.fontSize = lineFont.size;
185
- if (lineFont?.family !== void 0) out.fontFamily = lineFont.family;
186
- if (lineFont?.weight !== void 0) out.fontWeight = lineFont.weight;
187
- if (lineFont?.style !== void 0) out.fontStyle = lineFont.style;
241
+ };
242
+ const physical = maxTextWidth !== void 0 ? wrapText(text, font, maxTextWidth, measureText) : [text];
243
+ for (const ptext of physical) {
244
+ const m = measureText(ptext, font);
245
+ if (m.width > textWidth) textWidth = m.width;
246
+ const out = { text: ptext };
247
+ if (isObj) {
248
+ if (spec.fill !== void 0) out.fill = spec.fill;
249
+ if (spec.opacity !== void 0) out.opacity = spec.opacity;
250
+ if (lineFont?.size !== void 0) out.fontSize = lineFont.size;
251
+ if (lineFont?.family !== void 0) out.fontFamily = lineFont.family;
252
+ if (lineFont?.weight !== void 0) out.fontWeight = lineFont.weight;
253
+ if (lineFont?.style !== void 0) out.fontStyle = lineFont.style;
254
+ }
255
+ lines.push(out);
188
256
  }
189
- return out;
190
- });
257
+ }
191
258
  textHeight = lines.length * lineHeight;
192
259
  }
193
260
  const minW = node.minimumWidth ?? node.minimumSize ?? 0;
@@ -200,18 +267,29 @@ var layoutNode = (node, measureText, nameStack, nodeDistance, scopeChain = [], l
200
267
  if (!center) throw new Error(`Cannot resolve position for node ${node.id ?? "(unnamed)"}; polar.origin or at.of may reference an undefined node`);
201
268
  const labels = (node.label === void 0 ? void 0 : Array.isArray(node.label) ? node.label : [node.label])?.map((lab) => {
202
269
  const labFont = lab.font;
270
+ const labFontSize = (labFont?.size ?? labelDefault?.font?.size ?? baseFontSize) * fontScale;
271
+ const labFamily = labFont?.family ?? labelDefault?.font?.family ?? fontFamily;
272
+ const labWeight = labFont?.weight ?? labelDefault?.font?.weight ?? fontWeight;
273
+ const labStyle = labFont?.style ?? labelDefault?.font?.style ?? fontStyle;
203
274
  return {
204
275
  text: lab.text,
205
276
  position: lab.position ?? "above",
206
277
  distance: lab.distance ?? DEFAULT_LABEL_DISTANCE,
207
278
  textColor: lab.textColor ?? labelDefault?.textColor ?? labelDefault?.color ?? node.textColor,
208
279
  opacity: lab.opacity ?? labelDefault?.opacity,
209
- fontSize: (labFont?.size ?? labelDefault?.font?.size ?? baseFontSize) * fontScale,
210
- fontFamily: labFont?.family ?? labelDefault?.font?.family ?? fontFamily,
211
- fontWeight: labFont?.weight ?? labelDefault?.font?.weight ?? fontWeight,
212
- fontStyle: labFont?.style ?? labelDefault?.font?.style ?? fontStyle,
280
+ fontSize: labFontSize,
281
+ fontFamily: labFamily,
282
+ fontWeight: labWeight,
283
+ fontStyle: labStyle,
213
284
  rotate: lab.rotate,
214
- keepUpright: lab.keepUpright
285
+ keepUpright: lab.keepUpright,
286
+ measuredWidth: measureText(lab.text, {
287
+ size: labFontSize,
288
+ family: labFamily,
289
+ weight: labWeight,
290
+ style: labStyle
291
+ }).width,
292
+ pin: lab.pin
215
293
  };
216
294
  });
217
295
  return {
@@ -248,9 +326,9 @@ var layoutNode = (node, measureText, nameStack, nodeDistance, scopeChain = [], l
248
326
  labels
249
327
  };
250
328
  };
251
- /** 从 NodeLayout 收敛 emit 所需的视觉样式子集(ShapeStyle,不含几何 / 文本) */
252
- var toShapeStyle = (layout) => ({
253
- fill: layout.fill,
329
+ /** 从 NodeLayout 收敛 emit 所需的视觉样式子集(ShapeStyle,不含几何 / 文本);fill 经 resolveFill 转 PaintValue */
330
+ var toShapeStyle = (layout, resolveFill) => ({
331
+ fill: resolveFill(layout.fill),
254
332
  fillOpacity: layout.fillOpacity,
255
333
  stroke: layout.stroke,
256
334
  strokeOpacity: layout.strokeOpacity,
@@ -264,12 +342,44 @@ var toShapeStyle = (layout) => ({
264
342
  * @description shape 主体走 `shapeDef.emit`(收轴对齐 rect、可出多 primitive);text 始终走 TextPrim;
265
343
  * 有旋转时外层 GroupPrim 用 `rotate(deg cx cy)` 统一包裹 shape + text(diamond 顶点 / text 都靠 group 旋转)
266
344
  */
267
- var emitNodePrimitives = (layout, round) => {
345
+ /**
346
+ * 节点 label 的外接点(供顶层 bbox / viewBox 计算,让 label 不被裁——与 step.label 进 bbox 一致)
347
+ * @description 每个 label 取其文本框四角;label 中心走 labelCenter(轴对齐系),node 自身 rotate 时绕 node 中心旋转
348
+ * (与 emit 的 group rotate 同步)。pin 引线起点在 node 边界内、已被 node 四角覆盖,无需额外。
349
+ */
350
+ var labelExtentPoints = (layout) => {
351
+ if (!layout.labels || layout.labels.length === 0) return [];
352
+ const cx = layout.rect.x;
353
+ const cy = layout.rect.y;
354
+ const rad = layout.rotateDeg * Math.PI / 180;
355
+ const cos = Math.cos(rad);
356
+ const sin = Math.sin(rad);
357
+ const pts = [];
358
+ for (const lab of layout.labels) {
359
+ const [lx, ly] = labelCenter(layout, lab);
360
+ const halfW = lab.measuredWidth / 2;
361
+ const halfH = lab.fontSize / 2;
362
+ const corners = [
363
+ [lx - halfW, ly - halfH],
364
+ [lx + halfW, ly - halfH],
365
+ [lx - halfW, ly + halfH],
366
+ [lx + halfW, ly + halfH]
367
+ ];
368
+ for (const [px, py] of corners) if (layout.rotateDeg === 0) pts.push([px, py]);
369
+ else {
370
+ const dx = px - cx;
371
+ const dy = py - cy;
372
+ pts.push([cx + dx * cos - dy * sin, cy + dx * sin + dy * cos]);
373
+ }
374
+ }
375
+ return pts;
376
+ };
377
+ var emitNodePrimitives = (layout, round, resolveFill) => {
268
378
  const axisAlignedRect = {
269
379
  ...layout.rect,
270
380
  rotate: 0
271
381
  };
272
- const inner = [...layout.shapeDef.emit(axisAlignedRect, toShapeStyle(layout), round)];
382
+ const inner = [...layout.shapeDef.emit(axisAlignedRect, toShapeStyle(layout, resolveFill), round)];
273
383
  if (layout.lines) {
274
384
  const halfBlockW = layout.textWidth / 2;
275
385
  const xOffset = layout.align === "start" ? -halfBlockW : layout.align === "end" ? halfBlockW : 0;
@@ -296,6 +406,26 @@ var emitNodePrimitives = (layout, round) => {
296
406
  const cy = layout.rect.y;
297
407
  for (const lab of layout.labels) {
298
408
  const [lx, ly] = labelCenter(layout, lab);
409
+ if (lab.pin) {
410
+ const style = typeof lab.pin === "object" ? lab.pin : void 0;
411
+ const [bx, by] = labelBorderPoint(layout, lab);
412
+ const pad = 2;
413
+ const [nx, ny] = labelBoxEdgeToward([lx, ly], [bx, by], lab.measuredWidth / 2 + pad, lab.fontSize / 2 + pad);
414
+ inner.push({
415
+ type: "path",
416
+ commands: [{
417
+ kind: "move",
418
+ to: [round(bx), round(by)]
419
+ }, {
420
+ kind: "line",
421
+ to: [round(nx), round(ny)]
422
+ }],
423
+ stroke: style?.stroke ?? lab.textColor ?? "currentColor",
424
+ strokeWidth: style?.strokeWidth ?? 1,
425
+ dashPattern: style?.dashPattern,
426
+ opacity: lab.opacity ?? layout.opacity
427
+ });
428
+ }
299
429
  const textPrim = {
300
430
  type: "text",
301
431
  x: round(lx),
@@ -345,4 +475,5 @@ exports.anchorOf = anchorOf;
345
475
  exports.angleBoundaryOf = angleBoundaryOf;
346
476
  exports.boundaryPointOf = boundaryPointOf;
347
477
  exports.emitNodePrimitives = emitNodePrimitives;
478
+ exports.labelExtentPoints = labelExtentPoints;
348
479
  exports.layoutNode = layoutNode;
@@ -1,6 +1,7 @@
1
1
  import { Position } from '../geometry/point';
2
2
  import { Rect } from '../geometry/rect';
3
- import { AtDirection, IRLabelDefault, IRNode } from '../ir';
3
+ import { AtDirection, IRLabelDefault, IRNode, IRPaintSpec } from '../ir';
4
+ import { PaintResolver } from './paint';
4
5
  import { ScenePrimitive, TextLine, Transform } from '../primitive';
5
6
  import { ShapeDefinition } from '../shapes';
6
7
  import { NameStack } from './name-stack';
@@ -42,8 +43,8 @@ export type NodeLayout = {
42
43
  fontWeight?: string | number;
43
44
  /** 字形 */
44
45
  fontStyle?: 'normal' | 'italic' | 'oblique';
45
- /** 节点背景色,emit 'transparent' 兜底 */
46
- fill?: string;
46
+ /** 节点背景填充(纯色 / PaintSpec gradient),emit 时经 resolveFill → PaintValue、'transparent' 兜底 */
47
+ fill?: string | IRPaintSpec;
47
48
  /** 填充透明度 0~1 */
48
49
  fillOpacity?: number;
49
50
  /** 节点边框色,emit 时 'currentColor' 兜底 */
@@ -83,6 +84,14 @@ export type NodeLabelLayout = {
83
84
  rotate?: 'none' | 'radial' | 'tangent' | number;
84
85
  /** 自旋后若文字倒置则翻 180°;缺省 false */
85
86
  keepUpright?: boolean;
87
+ /** label 文本测量宽度(pin leader 算 label 框近边用) */
88
+ measuredWidth: number;
89
+ /** pin:true = 默认引线;对象 = 带样式引线(stroke / strokeWidth / dashPattern);缺省 / false = 无引线 */
90
+ pin?: boolean | {
91
+ stroke?: string;
92
+ strokeWidth?: number;
93
+ dashPattern?: Array<number>;
94
+ };
86
95
  };
87
96
  /**
88
97
  * 取节点 shape 在 toward 方向的附着点(path 端点贴边用)
@@ -113,5 +122,11 @@ export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nameS
113
122
  * @description shape 主体走 `shapeDef.emit`(收轴对齐 rect、可出多 primitive);text 始终走 TextPrim;
114
123
  * 有旋转时外层 GroupPrim 用 `rotate(deg cx cy)` 统一包裹 shape + text(diamond 顶点 / text 都靠 group 旋转)
115
124
  */
116
- export declare const emitNodePrimitives: (layout: NodeLayout, round: (n: number) => number) => Array<ScenePrimitive>;
125
+ /**
126
+ * 节点 label 的外接点(供顶层 bbox / viewBox 计算,让 label 不被裁——与 step.label 进 bbox 一致)
127
+ * @description 每个 label 取其文本框四角;label 中心走 labelCenter(轴对齐系),node 自身 rotate 时绕 node 中心旋转
128
+ * (与 emit 的 group rotate 同步)。pin 引线起点在 node 边界内、已被 node 四角覆盖,无需额外。
129
+ */
130
+ export declare const labelExtentPoints: (layout: NodeLayout) => Array<Position>;
131
+ export declare const emitNodePrimitives: (layout: NodeLayout, round: (n: number) => number, resolveFill: PaintResolver) => Array<ScenePrimitive>;
117
132
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ //#region src/compile/paint.ts
2
+ /**
3
+ * 建一个 paint 登记表
4
+ * @description resolve 对相同 PaintSpec(结构化 JSON 深比较)合并为一个资源、派稳定 id(`paint-1` / `paint-2`…,首见序)。
5
+ * 同一份 IR 编译两次 → 同 id(快照稳定、SSR / CSR 一致)。SVG id 跨实例唯一性由 react adapter 加 useId 前缀解决。
6
+ */
7
+ var createPaintRegistry = () => {
8
+ const idByKey = /* @__PURE__ */ new Map();
9
+ const list = [];
10
+ let counter = 0;
11
+ const resolve = (fill) => {
12
+ if (fill === void 0) return void 0;
13
+ if (typeof fill === "string") return fill;
14
+ const key = JSON.stringify(fill);
15
+ let id = idByKey.get(key);
16
+ if (id === void 0) {
17
+ counter += 1;
18
+ id = `paint-${counter}`;
19
+ idByKey.set(key, id);
20
+ list.push({
21
+ kind: "paint",
22
+ id,
23
+ spec: fill
24
+ });
25
+ }
26
+ return {
27
+ kind: "resourceRef",
28
+ id
29
+ };
30
+ };
31
+ return {
32
+ resolve,
33
+ resources: () => list
34
+ };
35
+ };
36
+ //#endregion
37
+ exports.createPaintRegistry = createPaintRegistry;
@@ -0,0 +1,16 @@
1
+ import { IRPaintSpec } from '../ir';
2
+ import { PaintValue, SceneResource } from '../primitive';
3
+ /** fill 解析器:纯色 string 原样返回;PaintSpec 去重 + 派稳定 id → `{ kind:'resourceRef', id }`;undefined 透传 */
4
+ export type PaintResolver = (fill: string | IRPaintSpec | undefined) => PaintValue | undefined;
5
+ /** paint 资源登记表:编译期收集 PaintSpec、去重派 id,最后产出 Scene.resources */
6
+ export type PaintRegistry = {
7
+ resolve: PaintResolver;
8
+ resources: () => Array<SceneResource>;
9
+ };
10
+ /**
11
+ * 建一个 paint 登记表
12
+ * @description resolve 对相同 PaintSpec(结构化 JSON 深比较)合并为一个资源、派稳定 id(`paint-1` / `paint-2`…,首见序)。
13
+ * 同一份 IR 编译两次 → 同 id(快照稳定、SSR / CSR 一致)。SVG id 跨实例唯一性由 react adapter 加 useId 前缀解决。
14
+ */
15
+ export declare const createPaintRegistry: () => PaintRegistry;
16
+ //# sourceMappingURL=paint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paint.d.ts","sourceRoot":"","sources":["../../../src/compile/paint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE9D,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,KAAK,UAAU,GAAG,SAAS,CAAC;AAE/F,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,aAAa,CAAC;IACvB,SAAS,EAAE,MAAM,KAAK,CAAC,aAAa,CAAC,CAAC;CACvC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAAO,aAkBtC,CAAC"}
@@ -2,8 +2,17 @@ const require_scope = require("../scope.cjs");
2
2
  const require_position = require("../position.cjs");
3
3
  const require_node = require("../node.cjs");
4
4
  const require_anchor_cache = require("../anchor-cache.cjs");
5
- const require_parseTarget = require("../parseTarget.cjs");
6
5
  //#region src/compile/path/anchor.ts
6
+ /** target 是否对象形态 NodeTarget(`{ id, anchor?, offset? }`);与 Position(array) / Polar / Offset(of) / Relative 区分(独有 `id`) */
7
+ var isNodeTarget = (t) => typeof t === "object" && !Array.isArray(t) && "id" in t;
8
+ /** 解析 NodeTarget 的 anchor(非 undefined)到世界坐标:命名 / 角度走 resolveAnchor,`{ side, t }` 走 resolveEdgePoint */
9
+ var resolveAnchorRef = (node, anchor) => {
10
+ if (typeof anchor === "number") return require_anchor_cache.resolveAnchor(node, String(anchor));
11
+ if (typeof anchor === "string") return require_anchor_cache.resolveAnchor(node, anchor);
12
+ return require_anchor_cache.resolveEdgePoint(node, anchor.side, anchor.t);
13
+ };
14
+ /** anchor/边点解析后叠加世界系 offset(不随节点 rotate 旋转) */
15
+ var addOffset = (base, offset) => offset ? [base[0] + offset[0], base[1] + offset[1]] : base;
7
16
  /**
8
17
  * 求 step.to 的参考点(给 boundary clip 算方向 / 折角 corner 用)
9
18
  * @description 三态:`'A'`(auto) 节点中心;`'A.<anchor>'`/`'A.<deg>'` 显式锚点 refPoint=endpoint 位置不随邻居变。直接坐标/极坐标解析为笛卡尔。
@@ -12,15 +21,10 @@ const require_parseTarget = require("../parseTarget.cjs");
12
21
  * `applyTransformChain` 投回全局。`scopeChain=[]` 等价 v0.1(恒等)。
13
22
  */
14
23
  var refPointOfTarget = (target, nameStack, scopeChain = []) => {
15
- if (typeof target === "string") {
16
- const ref = require_parseTarget.parseNodeRef(target);
17
- const node = nameStack.lookup(ref.id);
24
+ if (isNodeTarget(target)) {
25
+ const node = nameStack.lookup(target.id);
18
26
  if (!node) return null;
19
- switch (ref.kind) {
20
- case "node": return [node.rect.x, node.rect.y];
21
- case "anchor": return require_anchor_cache.resolveAnchor(node, ref.anchor);
22
- case "angle": return require_anchor_cache.resolveAnchor(node, String(ref.angle));
23
- }
27
+ return addOffset(target.anchor === void 0 ? [node.rect.x, node.rect.y] : resolveAnchorRef(node, target.anchor), target.offset);
24
28
  }
25
29
  if (typeof target === "object" && !Array.isArray(target) && ("relative" in target || "relativeAccumulate" in target)) return null;
26
30
  const local = require_position.resolvePosition(target, nameStack, void 0, scopeChain);
@@ -36,15 +40,10 @@ var cornerOf = (prev, curr, via) => via === "-|" ? [curr[0], prev[1]] : [prev[0]
36
40
  * `resolvePosition(..., scopeChain)` 拿到当前 scope 局部坐标后 `applyTransformChain` 投回全局。
37
41
  */
38
42
  var clipForTarget = (target, toward, nameStack, scopeChain = []) => {
39
- if (typeof target === "string") {
40
- const ref = require_parseTarget.parseNodeRef(target);
41
- const node = nameStack.lookup(ref.id);
43
+ if (isNodeTarget(target)) {
44
+ const node = nameStack.lookup(target.id);
42
45
  if (!node) return null;
43
- switch (ref.kind) {
44
- case "node": return require_node.boundaryPointOf(node, toward);
45
- case "anchor": return require_anchor_cache.resolveAnchor(node, ref.anchor);
46
- case "angle": return require_anchor_cache.resolveAnchor(node, String(ref.angle));
47
- }
46
+ return addOffset(target.anchor === void 0 ? require_node.boundaryPointOf(node, toward) : resolveAnchorRef(node, target.anchor), target.offset);
48
47
  }
49
48
  if (typeof target === "object" && !Array.isArray(target) && ("relative" in target || "relativeAccumulate" in target)) return null;
50
49
  const local = require_position.resolvePosition(target, nameStack, void 0, scopeChain);
@@ -1 +1 @@
1
- {"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/anchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM/C;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,QAAQ,EAChB,WAAW,SAAS,EACpB,aAAY,aAAa,CAAC,SAAS,CAAM,KACxC,UAAU,GAAG,IAqBf,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,QAAQ,GACnB,MAAM,UAAU,EAChB,MAAM,UAAU,EAChB,KAAK,IAAI,GAAG,IAAI,KACf,UACqD,CAAC;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,QAAQ,EAChB,QAAQ,UAAU,EAClB,WAAW,SAAS,EACpB,aAAY,aAAa,CAAC,SAAS,CAAM,KACxC,UAAU,GAAG,IAqBf,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,SAAS,GAAI,GAAG,UAAU,GAAG,IAAI,EAAE,GAAG,UAAU,GAAG,IAAI,KAAG,OACzB,CAAC;AAE/C,6BAA6B;AAC7B,eAAO,MAAM,WAAW,GAAI,GAAG,UAAU,EAAE,QAAQ,UAAU,EAAE,MAAM,MAAM,KAAG,UAM7E,CAAC"}
1
+ {"version":3,"file":"anchor.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/anchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,UAAU,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAqB/C;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,QAAQ,EAChB,WAAW,SAAS,EACpB,aAAY,aAAa,CAAC,SAAS,CAAM,KACxC,UAAU,GAAG,IAkBf,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,QAAQ,GACnB,MAAM,UAAU,EAChB,MAAM,UAAU,EAChB,KAAK,IAAI,GAAG,IAAI,KACf,UACqD,CAAC;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,QAAQ,EAChB,QAAQ,UAAU,EAClB,WAAW,SAAS,EACpB,aAAY,aAAa,CAAC,SAAS,CAAM,KACxC,UAAU,GAAG,IAkBf,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,SAAS,GAAI,GAAG,UAAU,GAAG,IAAI,EAAE,GAAG,UAAU,GAAG,IAAI,KAAG,OACzB,CAAC;AAE/C,6BAA6B;AAC7B,eAAO,MAAM,WAAW,GAAI,GAAG,UAAU,EAAE,QAAQ,UAAU,EAAE,MAAM,MAAM,KAAG,UAM7E,CAAC"}
@@ -9,6 +9,8 @@ const require_relative = require("./relative.cjs");
9
9
  const require_shrink = require("./shrink.cjs");
10
10
  const require_split = require("./split.cjs");
11
11
  //#region src/compile/path/index.ts
12
+ /** 目标若是对象 NodeTarget(`{ id, ... }`)返回其 id,否则 undefined——给 UNRESOLVED_NODE_REFERENCE 诊断用 */
13
+ var nodeRefId = (t) => typeof t === "object" && !Array.isArray(t) && "id" in t ? t.id : void 0;
12
14
  /**
13
15
  * 语义 stroke 档位 → 数值(user units)
14
16
  * @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。
@@ -37,6 +39,7 @@ var emitPathPrimitive = (path, nameStack, round, measureText = require_text_metr
37
39
  });
38
40
  };
39
41
  const scopeChain = warnHook.scopeChain ?? [];
42
+ const resolveFill = warnHook.resolveFill ?? ((f) => typeof f === "string" || f === void 0 ? f : void 0);
40
43
  const steps = require_relative.normalizeRelativeTargets(path.children, nameStack, scopeChain);
41
44
  if (steps.length < 2) {
42
45
  warn("PATH_TOO_SHORT", `Path requires at least 2 steps (got ${steps.length}); the entire path is skipped`, "children");
@@ -56,7 +59,8 @@ var emitPathPrimitive = (path, nameStack, round, measureText = require_text_metr
56
59
  const anchors = steps.map((s, idx) => {
57
60
  if (!hasTo(s)) return null;
58
61
  const ref = require_anchor.refPointOfTarget(s.to, nameStack, scopeChain);
59
- if (!ref && typeof s.to === "string") warn("UNRESOLVED_NODE_REFERENCE", `Step.to references undefined node id '${s.to}'; the entire path is skipped`, `children[${idx}].to`);
62
+ const toId = nodeRefId(s.to);
63
+ if (!ref && toId !== void 0) warn("UNRESOLVED_NODE_REFERENCE", `Step.to references undefined node id '${toId}'; the entire path is skipped`, `children[${idx}].to`);
60
64
  return ref;
61
65
  });
62
66
  /**
@@ -204,8 +208,10 @@ var emitPathPrimitive = (path, nameStack, round, measureText = require_text_metr
204
208
  const fromPt = require_anchor.refPointOfTarget(step.from, nameStack, scopeChain);
205
209
  const toPt = require_anchor.refPointOfTarget(step.to, nameStack, scopeChain);
206
210
  if (!fromPt || !toPt) {
207
- if (!fromPt && typeof step.from === "string") warn("UNRESOLVED_NODE_REFERENCE", `Rectangle from references undefined node id '${step.from}'; the entire path is skipped`, `children[${i}].from`);
208
- if (!toPt && typeof step.to === "string") warn("UNRESOLVED_NODE_REFERENCE", `Rectangle to references undefined node id '${step.to}'; the entire path is skipped`, `children[${i}].to`);
211
+ const fromId = nodeRefId(step.from);
212
+ const rectToId = nodeRefId(step.to);
213
+ if (!fromPt && fromId !== void 0) warn("UNRESOLVED_NODE_REFERENCE", `Rectangle from references undefined node id '${fromId}'; the entire path is skipped`, `children[${i}].from`);
214
+ if (!toPt && rectToId !== void 0) warn("UNRESOLVED_NODE_REFERENCE", `Rectangle to references undefined node id '${rectToId}'; the entire path is skipped`, `children[${i}].to`);
209
215
  return null;
210
216
  }
211
217
  let rectStart = null;
@@ -230,7 +236,8 @@ var emitPathPrimitive = (path, nameStack, round, measureText = require_text_metr
230
236
  if (step.center !== void 0) {
231
237
  const c = require_anchor.refPointOfTarget(step.center, nameStack, scopeChain);
232
238
  if (!c) {
233
- if (typeof step.center === "string") warn("UNRESOLVED_NODE_REFERENCE", `Arc step center references undefined node id '${step.center}'; the entire path is skipped`, `children[${i}].center`);
239
+ const centerId = nodeRefId(step.center);
240
+ if (centerId !== void 0) warn("UNRESOLVED_NODE_REFERENCE", `Arc step center references undefined node id '${centerId}'; the entire path is skipped`, `children[${i}].center`);
234
241
  return null;
235
242
  }
236
243
  center = c;
@@ -367,7 +374,7 @@ var emitPathPrimitive = (path, nameStack, round, measureText = require_text_metr
367
374
  const baseProps = {
368
375
  stroke: path.stroke ?? "currentColor",
369
376
  strokeWidth,
370
- fill: path.fill ?? "none",
377
+ fill: resolveFill(path.fill) ?? "none",
371
378
  fillRule: path.fillRule,
372
379
  dashPattern: path.dashPattern,
373
380
  strokeLinecap: path.lineCap,
@@ -1,3 +1,4 @@
1
+ import { PaintResolver } from '../paint';
1
2
  import { IRPath, IRPosition } from '../../ir';
2
3
  import { ScenePrimitive, Transform } from '../../primitive';
3
4
  import { NameStack } from '../name-stack';
@@ -18,6 +19,8 @@ export type EmitPathWarnHook = {
18
19
  * 投影回全局;顶层 path / 无 scope chain 时为 `[]`(恒等,等价 v0.1 行为)
19
20
  */
20
21
  scopeChain?: ReadonlyArray<Transform>;
22
+ /** fill 解析器(PaintSpec → resourceRef + 登记资源);缺省时纯色透传、PaintSpec 退化为无填充 */
23
+ resolveFill?: PaintResolver;
21
24
  };
22
25
  /**
23
26
  * IR Path → PathPrim
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,MAAM,EACN,UAAU,EAGX,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAEV,cAAc,EACd,SAAS,EACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,iBAAiB,CAAC;AA8BtE,mCAAmC;AACnC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iCAAiC;IACjC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;IACX,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,WAAW,SAAS,EACpB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAa,YAA+B,EAC5C,WAAU,gBAAqB,KAC9B;IAAE,UAAU,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IA8gBrE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAY9C,OAAO,KAAK,EACV,MAAM,EACN,UAAU,EAGX,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAEV,cAAc,EACd,SAAS,EACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,iBAAiB,CAAC;AAkCtE,mCAAmC;AACnC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iCAAiC;IACjC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,CAAC;IACX,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACtC,wEAAwE;IACxE,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,WAAW,SAAS,EACpB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAa,YAA+B,EAC5C,WAAU,gBAAqB,KAC9B;IAAE,UAAU,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IAqhBrE,CAAC"}
@@ -0,0 +1,38 @@
1
+ //#region src/geometry/_edge.ts
2
+ /**
3
+ * rect 四直边 t=0 / t=1 端点对应的角 anchor
4
+ * @description 方向约定单一真源:north/south = 西→东(t=0 在 west 端),east/west = 北→南(t=0 在 north 端)。
5
+ * 仅 rect 直边用两角端点;circle/ellipse 用 `edgeAngleDeg` 角度表、diamond 用过顶点折线。
6
+ */
7
+ var EDGE_ENDS = {
8
+ north: ["north-west", "north-east"],
9
+ south: ["south-west", "south-east"],
10
+ east: ["north-east", "south-east"],
11
+ west: ["north-west", "south-west"]
12
+ };
13
+ /** 线性插值 a + (b − a)·t */
14
+ var lerpPoint = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
15
+ /**
16
+ * circle / ellipse 周长弧段:side 的局部参数角 θ(t),单位度
17
+ * @description 约定同 geometry 既有 `(cosθ, sinθ)` + y 轴向下 ⇒ east=0° / south=90° / west=180° / north=270°,
18
+ * 顺时针为正。每条 side 是一段 90° 弧(等角插值);三点(t=0/0.5/1)与 9-anchor 重合。
19
+ */
20
+ var edgeAngleDeg = (side, t) => {
21
+ switch (side) {
22
+ case "north": return 225 + 90 * t;
23
+ case "south": return 135 - 90 * t;
24
+ case "east": return -45 + 90 * t;
25
+ case "west": return 225 - 90 * t;
26
+ }
27
+ };
28
+ /**
29
+ * diamond 过 cardinal 顶点的两段折线
30
+ * @description t∈[0,0.5] 走 p0→vertex、t∈[0.5,1] 走 vertex→p1;t=0.5 恰落 vertex。
31
+ * p0/p1 为相邻边中点 anchor、vertex 为 cardinal 顶点 anchor——全落真实斜边
32
+ */
33
+ var polylineViaVertex = (p0, vertex, p1, t) => t <= .5 ? lerpPoint(p0, vertex, t * 2) : lerpPoint(vertex, p1, (t - .5) * 2);
34
+ //#endregion
35
+ exports.EDGE_ENDS = EDGE_ENDS;
36
+ exports.edgeAngleDeg = edgeAngleDeg;
37
+ exports.lerpPoint = lerpPoint;
38
+ exports.polylineViaVertex = polylineViaVertex;
@@ -0,0 +1,29 @@
1
+ import { Position } from './point';
2
+ /** 边上比例点 `{ side, t }` 的四个 side(north/south/east/west) */
3
+ export type Side = 'north' | 'south' | 'east' | 'west';
4
+ /**
5
+ * rect 四直边 t=0 / t=1 端点对应的角 anchor
6
+ * @description 方向约定单一真源:north/south = 西→东(t=0 在 west 端),east/west = 北→南(t=0 在 north 端)。
7
+ * 仅 rect 直边用两角端点;circle/ellipse 用 `edgeAngleDeg` 角度表、diamond 用过顶点折线。
8
+ */
9
+ export declare const EDGE_ENDS: {
10
+ readonly north: readonly ["north-west", "north-east"];
11
+ readonly south: readonly ["south-west", "south-east"];
12
+ readonly east: readonly ["north-east", "south-east"];
13
+ readonly west: readonly ["north-west", "south-west"];
14
+ };
15
+ /** 线性插值 a + (b − a)·t */
16
+ export declare const lerpPoint: (a: Position, b: Position, t: number) => Position;
17
+ /**
18
+ * circle / ellipse 周长弧段:side 的局部参数角 θ(t),单位度
19
+ * @description 约定同 geometry 既有 `(cosθ, sinθ)` + y 轴向下 ⇒ east=0° / south=90° / west=180° / north=270°,
20
+ * 顺时针为正。每条 side 是一段 90° 弧(等角插值);三点(t=0/0.5/1)与 9-anchor 重合。
21
+ */
22
+ export declare const edgeAngleDeg: (side: Side, t: number) => number;
23
+ /**
24
+ * diamond 过 cardinal 顶点的两段折线
25
+ * @description t∈[0,0.5] 走 p0→vertex、t∈[0.5,1] 走 vertex→p1;t=0.5 恰落 vertex。
26
+ * p0/p1 为相邻边中点 anchor、vertex 为 cardinal 顶点 anchor——全落真实斜边
27
+ */
28
+ export declare const polylineViaVertex: (p0: Position, vertex: Position, p1: Position, t: number) => Position;
29
+ //# sourceMappingURL=_edge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_edge.d.ts","sourceRoot":"","sources":["../../../src/geometry/_edge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,0DAA0D;AAC1D,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;CAK8C,CAAC;AAErE,yBAAyB;AACzB,eAAO,MAAM,SAAS,GAAI,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,KAAG,QAG/D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,IAAI,EAAE,GAAG,MAAM,KAAG,MAWpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,IAAI,QAAQ,EACZ,QAAQ,QAAQ,EAChB,IAAI,QAAQ,EACZ,GAAG,MAAM,KACR,QAA4F,CAAC"}
@@ -1,5 +1,7 @@
1
1
  const require__transform = require("./_transform.cjs");
2
+ const require__edge = require("./_edge.cjs");
2
3
  //#region src/geometry/circle.ts
4
+ var DEG_TO_RAD = Math.PI / 180;
3
5
  var SQRT_HALF = Math.SQRT1_2;
4
6
  /** 圆形相关基础工具 */
5
7
  var circle = {
@@ -55,6 +57,11 @@ var circle = {
55
57
  if (len === 0) return [c.x, c.y];
56
58
  const t = c.radius / len;
57
59
  return require__transform.localToWorld(c, [lx * t, ly * t]);
60
+ },
61
+ /** 边上比例点:side 的 90° 周长弧段 t∈[0,1] 处(等角,落真实圆周;含旋转) */
62
+ edgePoint: (c, side, t) => {
63
+ const rad = require__edge.edgeAngleDeg(side, t) * DEG_TO_RAD;
64
+ return require__transform.localToWorld(c, [c.radius * Math.cos(rad), c.radius * Math.sin(rad)]);
58
65
  }
59
66
  };
60
67
  //#endregion