@retikz/core 0.2.0-alpha.3 → 0.2.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.
Files changed (67) hide show
  1. package/dist/es/compile/compile.d.ts.map +1 -1
  2. package/dist/es/compile/compile.js +74 -12
  3. package/dist/es/compile/node.d.ts +4 -0
  4. package/dist/es/compile/node.d.ts.map +1 -1
  5. package/dist/es/compile/node.js +83 -32
  6. package/dist/es/compile/path/index.d.ts.map +1 -1
  7. package/dist/es/compile/path/index.js +91 -12
  8. package/dist/es/compile/path/relative.d.ts.map +1 -1
  9. package/dist/es/compile/path/relative.js +5 -1
  10. package/dist/es/geometry/arc.d.ts +11 -0
  11. package/dist/es/geometry/arc.d.ts.map +1 -1
  12. package/dist/es/geometry/arc.js +27 -1
  13. package/dist/es/geometry/rect.d.ts +23 -0
  14. package/dist/es/geometry/rect.d.ts.map +1 -1
  15. package/dist/es/geometry/rect.js +84 -1
  16. package/dist/es/geometry/segment.d.ts +2 -0
  17. package/dist/es/geometry/segment.d.ts.map +1 -1
  18. package/dist/es/geometry/segment.js +12 -1
  19. package/dist/es/index.d.ts +2 -2
  20. package/dist/es/index.d.ts.map +1 -1
  21. package/dist/es/index.js +2 -2
  22. package/dist/es/ir/node.d.ts +33 -4
  23. package/dist/es/ir/node.d.ts.map +1 -1
  24. package/dist/es/ir/node.js +9 -2
  25. package/dist/es/ir/path/path.d.ts +230 -5
  26. package/dist/es/ir/path/path.d.ts.map +1 -1
  27. package/dist/es/ir/path/path.js +1 -0
  28. package/dist/es/ir/path/step.d.ts +311 -8
  29. package/dist/es/ir/path/step.d.ts.map +1 -1
  30. package/dist/es/ir/path/step.js +36 -11
  31. package/dist/es/ir/scope.d.ts +348 -16
  32. package/dist/es/ir/scope.d.ts.map +1 -1
  33. package/dist/es/ir/scope.js +5 -2
  34. package/dist/lib/compile/compile.cjs +74 -12
  35. package/dist/lib/compile/compile.d.ts.map +1 -1
  36. package/dist/lib/compile/node.cjs +83 -32
  37. package/dist/lib/compile/node.d.ts +4 -0
  38. package/dist/lib/compile/node.d.ts.map +1 -1
  39. package/dist/lib/compile/path/index.cjs +89 -10
  40. package/dist/lib/compile/path/index.d.ts.map +1 -1
  41. package/dist/lib/compile/path/relative.cjs +5 -1
  42. package/dist/lib/compile/path/relative.d.ts.map +1 -1
  43. package/dist/lib/geometry/arc.cjs +28 -0
  44. package/dist/lib/geometry/arc.d.ts +11 -0
  45. package/dist/lib/geometry/arc.d.ts.map +1 -1
  46. package/dist/lib/geometry/rect.cjs +84 -0
  47. package/dist/lib/geometry/rect.d.ts +23 -0
  48. package/dist/lib/geometry/rect.d.ts.map +1 -1
  49. package/dist/lib/geometry/segment.cjs +12 -0
  50. package/dist/lib/geometry/segment.d.ts +2 -0
  51. package/dist/lib/geometry/segment.d.ts.map +1 -1
  52. package/dist/lib/index.cjs +1 -0
  53. package/dist/lib/index.d.ts +2 -2
  54. package/dist/lib/index.d.ts.map +1 -1
  55. package/dist/lib/ir/node.cjs +9 -2
  56. package/dist/lib/ir/node.d.ts +33 -4
  57. package/dist/lib/ir/node.d.ts.map +1 -1
  58. package/dist/lib/ir/path/path.cjs +1 -0
  59. package/dist/lib/ir/path/path.d.ts +230 -5
  60. package/dist/lib/ir/path/path.d.ts.map +1 -1
  61. package/dist/lib/ir/path/step.cjs +36 -10
  62. package/dist/lib/ir/path/step.d.ts +311 -8
  63. package/dist/lib/ir/path/step.d.ts.map +1 -1
  64. package/dist/lib/ir/scope.cjs +5 -2
  65. package/dist/lib/ir/scope.d.ts +348 -16
  66. package/dist/lib/ir/scope.d.ts.map +1 -1
  67. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../src/compile/compile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAwC,MAAM,OAAO,CAAC;AACtE,OAAO,KAAK,EAAa,KAAK,EAA6B,MAAM,cAAc,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAoBjD,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AA4CrE,uEAAuE;AACvE,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,IAAI,EACA,2BAA2B,GAC3B,gBAAgB,GAChB,0BAA0B,GAC1B,wBAAwB,GACxB,yBAAyB,GACzB,sBAAsB,GACtB,8BAA8B,GAC9B,oBAAoB,GACpB,mBAAmB,GACnB,yBAAyB,GACzB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAClB,iBAAiB;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,2BAA2B;AAC3B,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC1C,CAAC;AAsDF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,EAAE,EAAE,UAAS,cAAmB,KAAG,KAgOrE,CAAC"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../src/compile/compile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAwC,MAAM,OAAO,CAAC;AACtE,OAAO,KAAK,EAAa,KAAK,EAA6B,MAAM,cAAc,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAoBjD,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AA4CrE,uEAAuE;AACvE,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,IAAI,EACA,2BAA2B,GAC3B,gBAAgB,GAChB,0BAA0B,GAC1B,wBAAwB,GACxB,yBAAyB,GACzB,sBAAsB,GACtB,8BAA8B,GAC9B,oBAAoB,GACpB,mBAAmB,GACnB,yBAAyB,GACzB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAClB,iBAAiB;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,2BAA2B;AAC3B,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC1C,CAAC;AA6FF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,EAAE,EAAE,UAAS,cAAmB,KAAG,KA4RrE,CAAC"}
@@ -56,6 +56,18 @@ var defaultWarnDispatcher = (warning) => {
56
56
  if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
57
57
  console.warn(`[retikz] ${warning.code} at ${warning.path}: ${warning.message}`);
58
58
  };
59
+ var makePathPlaceholder = () => ({ type: "path-placeholder" });
60
+ /** 把内部 sink 收窄回公开 ScenePrimitive[]:占位已全部回填(compileToScene 末端 placeholderBalance 无条件校验兜底) */
61
+ var sealSink = (sink) => sink;
62
+ /** dev 诊断:递归找出残留占位的 index 路径,供末端无条件校验报错时定位 */
63
+ var collectPlaceholderLocators = (prims, prefix = "primitives") => {
64
+ const locators = [];
65
+ prims.forEach((prim, idx) => {
66
+ if (prim.type === "path-placeholder") locators.push(`${prefix}[${idx}]`);
67
+ else if (prim.type === "group") locators.push(...collectPlaceholderLocators(prim.children, `${prefix}[${idx}].children`));
68
+ });
69
+ return locators;
70
+ };
59
71
  /** scope.transforms 解析失败时根据失败成因映射的 warn code */
60
72
  var scopeTransformWarnCode = (scope) => {
61
73
  for (const t of scope.transforms ?? []) {
@@ -98,11 +110,32 @@ var compileToScene = (ir, options = {}) => {
98
110
  });
99
111
  }
100
112
  const primitives = [];
113
+ /** 已 push 但未回填的占位计数;compileToScene 返回前必须归零(无条件守 Scene 公开契约) */
114
+ let placeholderBalance = 0;
115
+ /**
116
+ * primitive → 显式 zIndex 旁路记录(缺省视为 0);sealSink 后按它稳定排序,不写进 primitive 本体(保 Scene 输出纯净)。
117
+ * key 只会是 real ScenePrimitive——占位 PathPlaceholder 永不进此 Map(占位即将被回填替换)。
118
+ */
119
+ const zIndexOf = /* @__PURE__ */ new Map();
120
+ /**
121
+ * 按 zIndex 升序原地稳定排序:同 zIndex 保持原 IR 顺序(decorate-sort 带原始下标)。全 0 键 = 恒等。
122
+ * 仅在 sealSink(占位已回填、类型已收窄回 ScenePrimitive)之后调用。
123
+ */
124
+ const stableSortByZIndex = (arr) => {
125
+ const decorated = arr.map((prim, index) => ({
126
+ prim,
127
+ index,
128
+ z: zIndexOf.get(prim) ?? 0
129
+ }));
130
+ decorated.sort((a, b) => a.z - b.z || a.index - b.index);
131
+ for (let i = 0; i < arr.length; i++) arr[i] = decorated[i].prim;
132
+ return arr;
133
+ };
101
134
  const nameStack = new NameStack({ onDuplicate: (info) => onWarn(formatDuplicateWarning(info)) });
102
135
  const allPoints = [];
103
136
  /**
104
137
  * 解析一批本层收集的 pending paths(lookup-only 阶段)
105
- * @description path primitive 一律 push 到顶层 `primitives` —— 端点已是全局坐标,不能进 GroupPrim 否则被 scope.transform 二次 apply。NameStack 切到 pass2 守门:path 解析中误调 register 抛 internal error;解析完切回 pass1 让上层 scope 子树继续 register 子节点。
138
+ * @description 两种落点:有 `slot`(scopeChain 为空)→ 原位 splice 回填该 path 在本层 sink 占的位(按引用定位免索引漂移),保住与同层 node 的 IR 声明序;无 `slot`(scopeChain 非空)→ hoist 到顶层 `primitives`,因端点已是全局坐标、进 transformed GroupPrim 会被 scope.transform 二次 apply。NameStack 切到 pass2 守门:path 解析中误调 register 抛 internal error;解析完切回 pass1 让上层 scope 子树继续 register 子节点。
106
139
  * `item.scopeChain` 记录该 path 所属 scope 累积 transform 链——传给 emitPathPrimitive,
107
140
  * 让 step.to 内的 polar/at/offset 字面量按"当前 scope 局部度量 + 末端 apply chain"投影回全局。
108
141
  */
@@ -116,10 +149,18 @@ var compileToScene = (ir, options = {}) => {
116
149
  irPath: item.irPath,
117
150
  scopeChain: item.scopeChain
118
151
  });
119
- if (result) {
120
- for (const prim of result.primitives) primitives.push(prim);
121
- for (const p of result.points) allPoints.push(p);
152
+ if (item.slot) {
153
+ const idx = item.slot.sink.indexOf(item.slot.placeholder);
154
+ if (idx === -1) throw new Error("internal: path placeholder missing from its sink");
155
+ const real = result?.primitives ?? [];
156
+ item.slot.sink.splice(idx, 1, ...real);
157
+ if (item.zIndex !== void 0) for (const prim of real) zIndexOf.set(prim, item.zIndex);
158
+ placeholderBalance--;
159
+ } else if (result) for (const prim of result.primitives) {
160
+ primitives.push(prim);
161
+ if (item.zIndex !== void 0) zIndexOf.set(prim, item.zIndex);
122
162
  }
163
+ if (result) for (const p of result.points) allPoints.push(p);
123
164
  }
124
165
  } finally {
125
166
  nameStack.exitLookupPhase();
@@ -143,7 +184,10 @@ var compileToScene = (ir, options = {}) => {
143
184
  const layout = layoutNode(resolveNodeStyle(child, styleStack), measureText, nameStack, nodeDistance, chain, resolveLabelDefault(styleStack), effectiveShapes);
144
185
  const globalLayout = chain.length === 0 ? layout : projectLayoutToGlobal(layout, chain);
145
186
  if (child.id) nameStack.register(child.id, globalLayout, `${locatorPrefix}children[${i}].node.id`);
146
- for (const prim of emitNodePrimitives(layout, round)) sink.push(prim);
187
+ for (const prim of emitNodePrimitives(layout, round)) {
188
+ sink.push(prim);
189
+ if (child.zIndex !== void 0) zIndexOf.set(prim, child.zIndex);
190
+ }
147
191
  allPoints.push(rect.anchor(globalLayout.rect, "north-west"), rect.anchor(globalLayout.rect, "north-east"), rect.anchor(globalLayout.rect, "south-west"), rect.anchor(globalLayout.rect, "south-east"));
148
192
  layoutsAccumulator.push(globalLayout);
149
193
  } else if (child.type === "coordinate") {
@@ -196,22 +240,40 @@ var compileToScene = (ir, options = {}) => {
196
240
  if (innerSink.length === 0 && !hasOwnTransforms && child.id === void 0) continue;
197
241
  const group = {
198
242
  type: "group",
199
- children: innerSink
243
+ children: stableSortByZIndex(sealSink(innerSink))
200
244
  };
201
245
  if (hasOwnTransforms) group.transforms = [...ownTransforms];
202
246
  sink.push(group);
203
- } else pathsAccumulator.push({
204
- path: resolveEffectivePath(child, styleStack),
205
- irPath: `${locatorPrefix}children[${i}].path`,
206
- scopeChain: chain
207
- });
247
+ if (child.zIndex !== void 0) zIndexOf.set(group, child.zIndex);
248
+ } else {
249
+ const pending = {
250
+ path: resolveEffectivePath(child, styleStack),
251
+ irPath: `${locatorPrefix}children[${i}].path`,
252
+ scopeChain: chain,
253
+ zIndex: child.zIndex
254
+ };
255
+ if (chain.length === 0) {
256
+ const placeholder = makePathPlaceholder();
257
+ sink.push(placeholder);
258
+ pending.slot = {
259
+ sink,
260
+ placeholder
261
+ };
262
+ placeholderBalance++;
263
+ }
264
+ pathsAccumulator.push(pending);
265
+ }
208
266
  }
209
267
  };
210
268
  const rootPaths = [];
211
269
  processChildren(ir.children, [], primitives, "", [], rootPaths, []);
212
270
  resolvePendingPaths(rootPaths);
271
+ if (placeholderBalance !== 0) {
272
+ const detail = typeof process !== "undefined" && process.env.NODE_ENV !== "production" ? ` at ${collectPlaceholderLocators(primitives).join(", ")}` : "";
273
+ throw new Error(`internal: ${placeholderBalance} unresolved path placeholder(s) leaked into Scene output${detail}`);
274
+ }
213
275
  return {
214
- primitives,
276
+ primitives: stableSortByZIndex(sealSink(primitives)),
215
277
  layout: computeLayout(allPoints, layoutPadding, round)
216
278
  };
217
279
  };
@@ -79,6 +79,10 @@ export type NodeLabelLayout = {
79
79
  fontFamily?: string;
80
80
  fontWeight?: string | number;
81
81
  fontStyle?: 'normal' | 'italic' | 'oblique';
82
+ /** label 文本自旋模式(none / radial / tangent / 数字角度);缺省 = 不旋转 */
83
+ rotate?: 'none' | 'radial' | 'tangent' | number;
84
+ /** 自旋后若文字倒置则翻 180°;缺省 false */
85
+ keepUpright?: boolean;
82
86
  };
83
87
  /**
84
88
  * 取节点 shape 在 toward 方向的附着点(path 端点贴边用)
@@ -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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"}
@@ -78,19 +78,51 @@ var LABEL_DIRECTION_MAP = {
78
78
  }
79
79
  };
80
80
  /**
81
- * 算 label 中心点
82
- * @description 8 方向:节点对应 anchor 出发按单位向量 × distance 外推;数字角度:先取 angleBoundary 边界点再沿 (cos,sin) × distance 外推
81
+ * 算 label 中心点(节点局部坐标系,未旋转)
82
+ * @description 8 方向:节点对应 anchor 出发按单位向量 × distance 外推;数字角度:先取 angleBoundary 边界点再沿 (cos,sin) × distance 外推。
83
+ * 两个分支都在 **axis-aligned rect(rotate=0)** 上算——node 自身 rotate 由外层 GroupPrim 统一施加;
84
+ * 若用带 rotate 的 rect,label 位置会被 anchorOf / angleBoundaryOf 旋转一次、再被外层 group 旋转一次(双重旋转)。
85
+ * anchorOf / angleBoundaryOf 本身不改(path anchor `'A.north'` / `'A.30'` 仍需带 rotate 的 rect)。
83
86
  */
84
87
  var labelCenter = (layout, label) => {
88
+ const aaLayout = {
89
+ ...layout,
90
+ rect: {
91
+ ...layout.rect,
92
+ rotate: 0
93
+ }
94
+ };
85
95
  if (typeof label.position === "number") {
86
96
  const rad = label.position * Math.PI / 180;
87
- const [bx, by] = angleBoundaryOf(layout, label.position);
97
+ const [bx, by] = angleBoundaryOf(aaLayout, label.position);
88
98
  return [bx + Math.cos(rad) * label.distance, by + Math.sin(rad) * label.distance];
89
99
  }
90
100
  const { anchor, vec } = LABEL_DIRECTION_MAP[label.position];
91
- const [bx, by] = anchorOf(layout, anchor);
101
+ const [bx, by] = anchorOf(aaLayout, anchor);
92
102
  return [bx + vec[0] * label.distance, by + vec[1] * label.distance];
93
103
  };
104
+ /** 角度换算常量(弧度 → 度) */
105
+ var RAD_TO_DEG = 180 / Math.PI;
106
+ /**
107
+ * 算 label 文本自旋角度(度,屏幕 y-down,节点局部系)
108
+ * @description radial = atan2(label中心 − node中心);tangent = radial + 90;number = 原值;none / 缺省 = 0。
109
+ * keepUpright 时把"偏离正立 > 90°"的角度翻 180° 保阅读方向。方向向量在局部坐标算,node 自身 rotate 由外层 group 叠加。
110
+ */
111
+ var resolveLabelRotateDeg = (label, lx, ly, cx, cy) => {
112
+ const mode = label.rotate;
113
+ if (mode === void 0 || mode === "none") return 0;
114
+ let deg;
115
+ if (typeof mode === "number") deg = mode;
116
+ else {
117
+ const radial = Math.atan2(ly - cy, lx - cx) * RAD_TO_DEG;
118
+ deg = mode === "tangent" ? radial + 90 : radial;
119
+ }
120
+ if (label.keepUpright) {
121
+ const norm = (deg % 360 + 360) % 360;
122
+ if (norm > 90 && norm < 270) deg += 180;
123
+ }
124
+ return deg;
125
+ };
94
126
  /**
95
127
  * 取节点 shape 在指定角度方向的边界点
96
128
  * @description 角度是节点**局部坐标系**下的极角(度数:0°=局部 +x,90°=局部 +y)。layout.rect.rotate 把局部基绕中心旋转,得到世界系下的视觉方向;shape boundaryPoint 内部用 rotate-aware 投影,所以这里把局部 (cos, sin) 经 rect.rotate 旋转后加到中心当作世界系 toward 传入。不应用 margin(同 anchorOf);用于 `'A.30'` 落点
@@ -177,7 +209,9 @@ var layoutNode = (node, measureText, nameStack, nodeDistance, scopeChain = [], l
177
209
  fontSize: (labFont?.size ?? labelDefault?.font?.size ?? baseFontSize) * fontScale,
178
210
  fontFamily: labFont?.family ?? labelDefault?.font?.family ?? fontFamily,
179
211
  fontWeight: labFont?.weight ?? labelDefault?.font?.weight ?? fontWeight,
180
- fontStyle: labFont?.style ?? labelDefault?.font?.style ?? fontStyle
212
+ fontStyle: labFont?.style ?? labelDefault?.font?.style ?? fontStyle,
213
+ rotate: lab.rotate,
214
+ keepUpright: lab.keepUpright
181
215
  };
182
216
  });
183
217
  return {
@@ -257,37 +291,54 @@ var emitNodePrimitives = (layout, round) => {
257
291
  measuredHeight: round(layout.textHeight)
258
292
  });
259
293
  }
260
- if (layout.labels) for (const lab of layout.labels) {
261
- const [lx, ly] = labelCenter(layout, lab);
262
- inner.push({
263
- type: "text",
264
- x: round(lx),
265
- y: round(ly),
266
- lines: [{ text: lab.text }],
267
- fontSize: lab.fontSize,
268
- fontFamily: lab.fontFamily,
269
- fontWeight: lab.fontWeight,
270
- fontStyle: lab.fontStyle,
271
- align: "middle",
272
- baseline: "middle",
273
- lineHeight: round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR),
274
- fill: lab.textColor ?? "currentColor",
275
- opacity: lab.opacity ?? layout.opacity,
276
- measuredWidth: 0,
277
- measuredHeight: round(lab.fontSize)
278
- });
294
+ if (layout.labels) {
295
+ const cx = layout.rect.x;
296
+ const cy = layout.rect.y;
297
+ for (const lab of layout.labels) {
298
+ const [lx, ly] = labelCenter(layout, lab);
299
+ const textPrim = {
300
+ type: "text",
301
+ x: round(lx),
302
+ y: round(ly),
303
+ lines: [{ text: lab.text }],
304
+ fontSize: lab.fontSize,
305
+ fontFamily: lab.fontFamily,
306
+ fontWeight: lab.fontWeight,
307
+ fontStyle: lab.fontStyle,
308
+ align: "middle",
309
+ baseline: "middle",
310
+ lineHeight: round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR),
311
+ fill: lab.textColor ?? "currentColor",
312
+ opacity: lab.opacity ?? layout.opacity,
313
+ measuredWidth: 0,
314
+ measuredHeight: round(lab.fontSize)
315
+ };
316
+ const deg = resolveLabelRotateDeg(lab, lx, ly, cx, cy);
317
+ if (deg === 0) inner.push(textPrim);
318
+ else inner.push({
319
+ type: "group",
320
+ transforms: [{
321
+ kind: "rotate",
322
+ degrees: round(deg),
323
+ cx: round(lx),
324
+ cy: round(ly)
325
+ }],
326
+ children: [textPrim]
327
+ });
328
+ }
279
329
  }
280
- if (layout.rotateDeg === 0) return inner;
281
- return [{
330
+ if (!(layout.rotateDeg !== 0 || layout.lines !== void 0)) return inner;
331
+ const group = {
282
332
  type: "group",
283
- transforms: [{
284
- kind: "rotate",
285
- degrees: round(layout.rotateDeg),
286
- cx: round(layout.rect.x),
287
- cy: round(layout.rect.y)
288
- }],
289
333
  children: inner
334
+ };
335
+ if (layout.rotateDeg !== 0) group.transforms = [{
336
+ kind: "rotate",
337
+ degrees: round(layout.rotateDeg),
338
+ cx: round(layout.rect.x),
339
+ cy: round(layout.rect.y)
290
340
  }];
341
+ return [group];
291
342
  };
292
343
  //#endregion
293
344
  export { anchorOf, angleBoundaryOf, boundaryPointOf, emitNodePrimitives, layoutNode };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/index.ts"],"names":[],"mappings":"AAYA,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,IA2XrE,CAAC"}
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,6 +1,7 @@
1
- import { arcBoundingPoints, arcEndPoint } from "../../geometry/arc.js";
1
+ import { rectOutline } from "../../geometry/rect.js";
2
+ import { arcBoundingPoints, arcEndPoint, ellipseArcBoundingPoints, ellipseArcPoint } from "../../geometry/arc.js";
2
3
  import { bendControlPoints } from "../../geometry/bend.js";
3
- import { arcSegmentSample, circleSegmentSample, cubicSegmentSample, ellipseSegmentSample, foldSegmentSample, lineSegmentSample, quadSegmentSample } from "../../geometry/segment.js";
4
+ import { arcSegmentSample, circleSegmentSample, cubicSegmentSample, ellipseArcSegmentSample, ellipseSegmentSample, foldSegmentSample, lineSegmentSample, quadSegmentSample } from "../../geometry/segment.js";
4
5
  import { fallbackMeasurer } from "../text-metrics.js";
5
6
  import { clipForTarget, cornerOf, refPointOfTarget, samePoint } from "./anchor.js";
6
7
  import { emitLabelPrimitive, tForLabelPosition } from "./label.js";
@@ -51,7 +52,7 @@ var emitPathPrimitive = (path, nameStack, round, measureText = fallbackMeasurer,
51
52
  labelPrims.push(r.primitive);
52
53
  for (const p of r.points) points.push(p);
53
54
  };
54
- const hasTo = (s) => s.kind !== "cycle" && s.kind !== "arc" && s.kind !== "circlePath" && s.kind !== "ellipsePath";
55
+ const hasTo = (s) => s.kind !== "cycle" && s.kind !== "arc" && s.kind !== "circlePath" && s.kind !== "ellipsePath" && s.kind !== "rectangle";
55
56
  const anchors = steps.map((s, idx) => {
56
57
  if (!hasTo(s)) return null;
57
58
  const ref = refPointOfTarget(s.to, nameStack, scopeChain);
@@ -168,6 +169,12 @@ var emitPathPrimitive = (path, nameStack, round, measureText = fallbackMeasurer,
168
169
  if (samePoint(p, lastEnd)) return;
169
170
  emitMove(p);
170
171
  };
172
+ /** 部分圆/椭圆的闭合模式:'open' 直接返回;缺省 / 误给 'closed' 回退 'chord' */
173
+ const resolvePartialClosed = (closed, idx) => {
174
+ if (closed === "open") return "open";
175
+ if (closed === "closed") warn("PARTIAL_ARC_CLOSED_INVALID", "Partial circle/ellipse (with angles) cannot use closed:'closed'; falling back to 'chord'", `children[${idx}]`);
176
+ return "chord";
177
+ };
171
178
  for (let i = 0; i < steps.length; i++) {
172
179
  if (i > 0) {
173
180
  const prevStep = steps[i - 1];
@@ -193,22 +200,80 @@ var emitPathPrimitive = (path, nameStack, round, measureText = fallbackMeasurer,
193
200
  emitLine(toClip);
194
201
  continue;
195
202
  }
203
+ if (step.kind === "rectangle") {
204
+ const fromPt = refPointOfTarget(step.from, nameStack, scopeChain);
205
+ const toPt = refPointOfTarget(step.to, nameStack, scopeChain);
206
+ 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`);
209
+ return null;
210
+ }
211
+ let rectStart = null;
212
+ for (const op of rectOutline(fromPt, toPt, step.roundedCorners)) if (op.kind === "move") {
213
+ emitMove(op.to);
214
+ rectStart = op.to;
215
+ } else if (op.kind === "line") emitLine(op.to);
216
+ else if (op.kind === "arc") emitArc(op.center, op.radius, op.startAngle, op.endAngle);
217
+ else emitClose();
218
+ const rx0 = Math.min(fromPt[0], toPt[0]);
219
+ const rx1 = Math.max(fromPt[0], toPt[0]);
220
+ const ry0 = Math.min(fromPt[1], toPt[1]);
221
+ const ry1 = Math.max(fromPt[1], toPt[1]);
222
+ points.push([rx0, ry0], [rx1, ry0], [rx1, ry1], [rx0, ry1]);
223
+ if (rectStart) penOverride = rectStart;
224
+ continue;
225
+ }
196
226
  const prev = findPrev();
197
227
  if (!prev) return null;
198
228
  if (step.kind === "arc") {
199
- const center = prev.anchor;
200
- const startPt = arcEndPoint(center, step.radius, step.startAngle);
201
- const endPt = arcEndPoint(center, step.radius, step.endAngle);
202
- startSegment(startPt);
203
- emitArc(center, step.radius, step.startAngle, step.endAngle);
204
- for (const p of arcBoundingPoints(center, step.radius, step.startAngle, step.endAngle)) points.push(p);
205
- collectLabel(step, (t) => arcSegmentSample(center, step.radius, step.startAngle, step.endAngle, t));
206
- penOverride = endPt;
207
- continue;
229
+ let center;
230
+ if (step.center !== void 0) {
231
+ const c = refPointOfTarget(step.center, nameStack, scopeChain);
232
+ 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`);
234
+ return null;
235
+ }
236
+ center = c;
237
+ } else center = prev.anchor;
238
+ if (step.radiusX !== void 0 && step.radiusY !== void 0) {
239
+ const rx = step.radiusX;
240
+ const ry = step.radiusY;
241
+ startSegment(ellipseArcPoint(center, rx, ry, step.startAngle));
242
+ emitEllipseArc(center, rx, ry, step.startAngle, step.endAngle);
243
+ for (const p of ellipseArcBoundingPoints(center, rx, ry, step.startAngle, step.endAngle)) points.push(p);
244
+ collectLabel(step, (t) => ellipseArcSegmentSample(center, rx, ry, step.startAngle, step.endAngle, t));
245
+ penOverride = ellipseArcPoint(center, rx, ry, step.endAngle);
246
+ continue;
247
+ }
248
+ if (step.radius !== void 0) {
249
+ const r = step.radius;
250
+ startSegment(arcEndPoint(center, r, step.startAngle));
251
+ emitArc(center, r, step.startAngle, step.endAngle);
252
+ for (const p of arcBoundingPoints(center, r, step.startAngle, step.endAngle)) points.push(p);
253
+ collectLabel(step, (t) => arcSegmentSample(center, r, step.startAngle, step.endAngle, t));
254
+ penOverride = arcEndPoint(center, r, step.endAngle);
255
+ continue;
256
+ }
257
+ warn("ARC_MISSING_RADIUS", "Arc step requires radius (circular) or both radiusX and radiusY (elliptical); the entire path is skipped", `children[${i}]`);
258
+ return null;
208
259
  }
209
260
  if (step.kind === "circlePath") {
210
261
  const center = prev.anchor;
211
262
  const r = step.radius;
263
+ if (step.startAngle !== void 0 && step.endAngle !== void 0) {
264
+ const startA = step.startAngle;
265
+ const endA = step.endAngle;
266
+ startSegment(ellipseArcPoint(center, r, r, startA));
267
+ emitEllipseArc(center, r, r, startA, endA);
268
+ for (const p of ellipseArcBoundingPoints(center, r, r, startA, endA)) points.push(p);
269
+ collectLabel(step, (t) => ellipseArcSegmentSample(center, r, r, startA, endA, t));
270
+ if (resolvePartialClosed(step.closed, i) === "chord") {
271
+ emitClose();
272
+ penOverride = ellipseArcPoint(center, r, r, startA);
273
+ } else penOverride = ellipseArcPoint(center, r, r, endA);
274
+ continue;
275
+ }
276
+ if (step.startAngle !== void 0 || step.endAngle !== void 0) warn("PARTIAL_ARC_NEEDS_BOTH_ANGLES", "circlePath needs both startAngle and endAngle for a partial circle; treated as a full circle", `children[${i}]`);
212
277
  startSegment([center[0] + r, center[1]]);
213
278
  emitEllipseArc(center, r, r, 0, 360);
214
279
  points.push([center[0] + r, center[1]]);
@@ -223,6 +288,20 @@ var emitPathPrimitive = (path, nameStack, round, measureText = fallbackMeasurer,
223
288
  const center = prev.anchor;
224
289
  const rx = step.radiusX;
225
290
  const ry = step.radiusY;
291
+ if (step.startAngle !== void 0 && step.endAngle !== void 0) {
292
+ const startA = step.startAngle;
293
+ const endA = step.endAngle;
294
+ startSegment(ellipseArcPoint(center, rx, ry, startA));
295
+ emitEllipseArc(center, rx, ry, startA, endA);
296
+ for (const p of ellipseArcBoundingPoints(center, rx, ry, startA, endA)) points.push(p);
297
+ collectLabel(step, (t) => ellipseArcSegmentSample(center, rx, ry, startA, endA, t));
298
+ if (resolvePartialClosed(step.closed, i) === "chord") {
299
+ emitClose();
300
+ penOverride = ellipseArcPoint(center, rx, ry, startA);
301
+ } else penOverride = ellipseArcPoint(center, rx, ry, endA);
302
+ continue;
303
+ }
304
+ if (step.startAngle !== void 0 || step.endAngle !== void 0) warn("PARTIAL_ARC_NEEDS_BOTH_ANGLES", "ellipsePath needs both startAngle and endAngle for a partial ellipse; treated as a full ellipse", `children[${i}]`);
226
305
  startSegment([center[0] + rx, center[1]]);
227
306
  emitEllipseArc(center, rx, ry, 0, 360);
228
307
  points.push([center[0] + rx, center[1]]);
@@ -1 +1 @@
1
- {"version":3,"file":"relative.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/relative.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,MAAM,EAAY,MAAM,UAAU,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,aAAa,CAAC,MAAM,CAAC,EAC5B,WAAW,SAAS,EACpB,aAAY,aAAa,CAAC,SAAS,CAAM,KACxC,KAAK,CAAC,MAAM,CAuEd,CAAC"}
1
+ {"version":3,"file":"relative.d.ts","sourceRoot":"","sources":["../../../../src/compile/path/relative.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,MAAM,EAAY,MAAM,UAAU,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,aAAa,CAAC,MAAM,CAAC,EAC5B,WAAW,SAAS,EACpB,aAAY,aAAa,CAAC,SAAS,CAAM,KACxC,KAAK,CAAC,MAAM,CA6Ed,CAAC"}
@@ -25,7 +25,11 @@ var normalizeRelativeTargets = (steps, nameStack, scopeChain = []) => {
25
25
  }
26
26
  if (step.kind === "arc") {
27
27
  out.push(step);
28
- if (prevEnd) prevEnd = arcEndPoint(prevEnd, step.radius, step.endAngle);
28
+ if (prevEnd && step.radius !== void 0 && step.center === void 0) prevEnd = arcEndPoint(prevEnd, step.radius, step.endAngle);
29
+ continue;
30
+ }
31
+ if (step.kind === "rectangle") {
32
+ out.push(step);
29
33
  continue;
30
34
  }
31
35
  const original = step.to;
@@ -6,4 +6,15 @@ export declare const arcEndPoint: (center: Position, radius: number, angleDeg: n
6
6
  * @description 弧投影到 x/y 轴的极值只可能在弧端点或圆周轴向四点出现。endAngle < startAngle 时按 min..max 扫描;跨 360°(270→450)按数值区间正确处理;不去重——端角恰在 90°·k 上时调用方处理
7
7
  */
8
8
  export declare const arcBoundingPoints: (center: Position, radius: number, startAngleDeg: number, endAngleDeg: number) => Array<Position>;
9
+ /**
10
+ * 椭圆弧参数点:中心 + 半轴 rx/ry + 参数角(度)→ 椭圆周上点
11
+ * @description 与 arcEndPoint 同角度约定(SVG y-down);endpoint = [cx + rx·cosθ, cy + ry·sinθ]。
12
+ * θ 是参数角(非真实极角,rx≠ry 时两者不等)
13
+ */
14
+ export declare const ellipseArcPoint: (center: Position, radiusX: number, radiusY: number, angleDeg: number) => Position;
15
+ /**
16
+ * 椭圆弧 bbox 极值候选:起点、终点,加 [start,end] 区间内所有 90°·k 参数角处的椭圆周点
17
+ * @description 轴对齐椭圆的 x 极值在 θ=0/180、y 极值在 θ=90/270,与正圆同结构(仅半轴用 rx/ry)
18
+ */
19
+ export declare const ellipseArcBoundingPoints: (center: Position, radiusX: number, radiusY: number, startAngleDeg: number, endAngleDeg: number) => Array<Position>;
9
20
  //# sourceMappingURL=arc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"arc.d.ts","sourceRoot":"","sources":["../../../src/geometry/arc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAWxC,iDAAiD;AACjD,eAAO,MAAM,WAAW,GACtB,QAAQ,QAAQ,EAChB,QAAQ,MAAM,EACd,UAAU,MAAM,KACf,QAMF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,QAAQ,EAChB,QAAQ,MAAM,EACd,eAAe,MAAM,EACrB,aAAa,MAAM,KAClB,KAAK,CAAC,QAAQ,CAiBhB,CAAC"}
1
+ {"version":3,"file":"arc.d.ts","sourceRoot":"","sources":["../../../src/geometry/arc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAWxC,iDAAiD;AACjD,eAAO,MAAM,WAAW,GACtB,QAAQ,QAAQ,EAChB,QAAQ,MAAM,EACd,UAAU,MAAM,KACf,QAMF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,QAAQ,EAChB,QAAQ,MAAM,EACd,eAAe,MAAM,EACrB,aAAa,MAAM,KAClB,KAAK,CAAC,QAAQ,CAiBhB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,QAAQ,EAChB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,UAAU,MAAM,KACf,QAMF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,QAAQ,EAChB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,eAAe,MAAM,EACrB,aAAa,MAAM,KAClB,KAAK,CAAC,QAAQ,CAehB,CAAC"}
@@ -22,5 +22,31 @@ var arcBoundingPoints = (center, radius, startAngleDeg, endAngleDeg) => {
22
22
  }
23
23
  return points;
24
24
  };
25
+ /**
26
+ * 椭圆弧参数点:中心 + 半轴 rx/ry + 参数角(度)→ 椭圆周上点
27
+ * @description 与 arcEndPoint 同角度约定(SVG y-down);endpoint = [cx + rx·cosθ, cy + ry·sinθ]。
28
+ * θ 是参数角(非真实极角,rx≠ry 时两者不等)
29
+ */
30
+ var ellipseArcPoint = (center, radiusX, radiusY, angleDeg) => {
31
+ const rad = angleDeg * DEG_TO_RAD;
32
+ return [center[0] + Math.cos(rad) * radiusX, center[1] + Math.sin(rad) * radiusY];
33
+ };
34
+ /**
35
+ * 椭圆弧 bbox 极值候选:起点、终点,加 [start,end] 区间内所有 90°·k 参数角处的椭圆周点
36
+ * @description 轴对齐椭圆的 x 极值在 θ=0/180、y 极值在 θ=90/270,与正圆同结构(仅半轴用 rx/ry)
37
+ */
38
+ var ellipseArcBoundingPoints = (center, radiusX, radiusY, startAngleDeg, endAngleDeg) => {
39
+ const points = [ellipseArcPoint(center, radiusX, radiusY, startAngleDeg), ellipseArcPoint(center, radiusX, radiusY, endAngleDeg)];
40
+ const lo = Math.min(startAngleDeg, endAngleDeg);
41
+ const hi = Math.max(startAngleDeg, endAngleDeg);
42
+ const kStart = Math.ceil(lo / 90);
43
+ const kEnd = Math.floor(hi / 90);
44
+ for (let k = kStart; k <= kEnd; k++) {
45
+ const angle = k * 90;
46
+ if (angle === startAngleDeg || angle === endAngleDeg) continue;
47
+ points.push(ellipseArcPoint(center, radiusX, radiusY, angle));
48
+ }
49
+ return points;
50
+ };
25
51
  //#endregion
26
- export { arcBoundingPoints, arcEndPoint };
52
+ export { arcBoundingPoints, arcEndPoint, ellipseArcBoundingPoints, ellipseArcPoint };
@@ -32,4 +32,27 @@ export declare const rect: {
32
32
  /** 从中心向 toward 方向射线与矩形边界交点(含旋转),Path 端点贴 Node 边界用 */
33
33
  boundaryPoint: (r: Rect, toward: Position) => Position;
34
34
  };
35
+ /** rectOutline 的命令算子(供 compile 翻译为 PathCommand;几何在 core 下沉,便于未来 rectangle node shape 复用) */
36
+ export type RectOutlineOp = {
37
+ kind: 'move';
38
+ to: Position;
39
+ } | {
40
+ kind: 'line';
41
+ to: Position;
42
+ } | {
43
+ kind: 'arc';
44
+ center: Position;
45
+ radius: number;
46
+ startAngle: number;
47
+ endAngle: number;
48
+ } | {
49
+ kind: 'close';
50
+ };
51
+ /**
52
+ * 矩形 outline:两对角 → 顺时针 path 算子序列
53
+ * @description from/to 任意顺序,归一化 (x0,y0)=min、(x1,y1)=max。直角 = 4 line + close(起点左上 (x0,y0));
54
+ * 圆角 = 4 line + 4 quarter-arc + close(起点 (x0+r, y0))。roundedCorners clamp 到 min(w,h)/2。
55
+ * 角度约定同 geometry/arc(y-down:0=+x, 90=+y/下, 180=-x, 270=-y/上)。
56
+ */
57
+ export declare const rectOutline: (from: Position, to: Position, roundedCorners?: number) => Array<RectOutlineOp>;
35
58
  //# sourceMappingURL=rect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rect.d.ts","sourceRoot":"","sources":["../../../src/geometry/rect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,gCAAgC;AAChC,MAAM,MAAM,IAAI,GAAG;IACjB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,YAAY;;;;;;;;;;CAUf,CAAC;AAEX,+BAA+B;AAC/B,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE1E,eAAO,MAAM,IAAI;IACf,WAAW;gBACC,IAAI,KAAG,QAAQ;IAC3B,uBAAuB;kBACT,IAAI,KAAK,QAAQ,KAAG,OAAO;IAMzC,sCAAsC;gBAC1B,IAAI,QAAQ,UAAU,KAAG,QAAQ;IAuC7C,qDAAqD;uBAClC,IAAI,UAAU,QAAQ,KAAG,QAAQ;CAUrD,CAAC"}
1
+ {"version":3,"file":"rect.d.ts","sourceRoot":"","sources":["../../../src/geometry/rect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,gCAAgC;AAChC,MAAM,MAAM,IAAI,GAAG;IACjB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,YAAY;;;;;;;;;;CAUf,CAAC;AAEX,+BAA+B;AAC/B,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE1E,eAAO,MAAM,IAAI;IACf,WAAW;gBACC,IAAI,KAAG,QAAQ;IAC3B,uBAAuB;kBACT,IAAI,KAAK,QAAQ,KAAG,OAAO;IAMzC,sCAAsC;gBAC1B,IAAI,QAAQ,UAAU,KAAG,QAAQ;IAuC7C,qDAAqD;uBAClC,IAAI,UAAU,QAAQ,KAAG,QAAQ;CAUrD,CAAC;AAEF,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvF;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GACtB,MAAM,QAAQ,EACd,IAAI,QAAQ,EACZ,iBAAiB,MAAM,KACtB,KAAK,CAAC,aAAa,CAgCrB,CAAC"}