@retikz/core 0.1.0 → 0.2.0-alpha.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 (87) hide show
  1. package/dist/es/compile/anchor-cache.d.ts +12 -0
  2. package/dist/es/compile/anchor-cache.d.ts.map +1 -0
  3. package/dist/es/compile/anchor-cache.js +41 -0
  4. package/dist/es/compile/compile.d.ts +2 -2
  5. package/dist/es/compile/compile.d.ts.map +1 -1
  6. package/dist/es/compile/compile.js +148 -40
  7. package/dist/es/compile/index.d.ts +1 -0
  8. package/dist/es/compile/index.d.ts.map +1 -1
  9. package/dist/es/compile/name-stack.d.ts +81 -0
  10. package/dist/es/compile/name-stack.d.ts.map +1 -0
  11. package/dist/es/compile/name-stack.js +104 -0
  12. package/dist/es/compile/node.d.ts +8 -4
  13. package/dist/es/compile/node.d.ts.map +1 -1
  14. package/dist/es/compile/node.js +13 -5
  15. package/dist/es/compile/path/anchor.d.ts +11 -5
  16. package/dist/es/compile/path/anchor.d.ts.map +1 -1
  17. package/dist/es/compile/path/anchor.js +24 -13
  18. package/dist/es/compile/path/index.d.ts +9 -3
  19. package/dist/es/compile/path/index.d.ts.map +1 -1
  20. package/dist/es/compile/path/index.js +17 -16
  21. package/dist/es/compile/path/relative.d.ts +10 -4
  22. package/dist/es/compile/path/relative.d.ts.map +1 -1
  23. package/dist/es/compile/path/relative.js +16 -8
  24. package/dist/es/compile/position.d.ts +19 -3
  25. package/dist/es/compile/position.d.ts.map +1 -1
  26. package/dist/es/compile/position.js +28 -8
  27. package/dist/es/compile/scope.d.ts +66 -0
  28. package/dist/es/compile/scope.d.ts.map +1 -0
  29. package/dist/es/compile/scope.js +256 -0
  30. package/dist/es/index.d.ts +2 -2
  31. package/dist/es/index.d.ts.map +1 -1
  32. package/dist/es/index.js +3 -1
  33. package/dist/es/ir/index.d.ts +2 -0
  34. package/dist/es/ir/index.d.ts.map +1 -1
  35. package/dist/es/ir/scene.d.ts +18 -3542
  36. package/dist/es/ir/scene.d.ts.map +1 -1
  37. package/dist/es/ir/scene.js +11 -3
  38. package/dist/es/ir/scope.d.ts +190 -0
  39. package/dist/es/ir/scope.d.ts.map +1 -0
  40. package/dist/es/ir/scope.js +29 -0
  41. package/dist/es/ir/transform.d.ts +204 -0
  42. package/dist/es/ir/transform.d.ts.map +1 -0
  43. package/dist/es/ir/transform.js +56 -0
  44. package/dist/lib/compile/anchor-cache.cjs +41 -0
  45. package/dist/lib/compile/anchor-cache.d.ts +12 -0
  46. package/dist/lib/compile/anchor-cache.d.ts.map +1 -0
  47. package/dist/lib/compile/compile.cjs +148 -40
  48. package/dist/lib/compile/compile.d.ts +2 -2
  49. package/dist/lib/compile/compile.d.ts.map +1 -1
  50. package/dist/lib/compile/index.d.ts +1 -0
  51. package/dist/lib/compile/index.d.ts.map +1 -1
  52. package/dist/lib/compile/name-stack.cjs +104 -0
  53. package/dist/lib/compile/name-stack.d.ts +81 -0
  54. package/dist/lib/compile/name-stack.d.ts.map +1 -0
  55. package/dist/lib/compile/node.cjs +13 -5
  56. package/dist/lib/compile/node.d.ts +8 -4
  57. package/dist/lib/compile/node.d.ts.map +1 -1
  58. package/dist/lib/compile/path/anchor.cjs +23 -12
  59. package/dist/lib/compile/path/anchor.d.ts +11 -5
  60. package/dist/lib/compile/path/anchor.d.ts.map +1 -1
  61. package/dist/lib/compile/path/index.cjs +17 -16
  62. package/dist/lib/compile/path/index.d.ts +9 -3
  63. package/dist/lib/compile/path/index.d.ts.map +1 -1
  64. package/dist/lib/compile/path/relative.cjs +16 -8
  65. package/dist/lib/compile/path/relative.d.ts +10 -4
  66. package/dist/lib/compile/path/relative.d.ts.map +1 -1
  67. package/dist/lib/compile/position.cjs +28 -8
  68. package/dist/lib/compile/position.d.ts +19 -3
  69. package/dist/lib/compile/position.d.ts.map +1 -1
  70. package/dist/lib/compile/scope.cjs +261 -0
  71. package/dist/lib/compile/scope.d.ts +66 -0
  72. package/dist/lib/compile/scope.d.ts.map +1 -0
  73. package/dist/lib/index.cjs +4 -0
  74. package/dist/lib/index.d.ts +2 -2
  75. package/dist/lib/index.d.ts.map +1 -1
  76. package/dist/lib/ir/index.d.ts +2 -0
  77. package/dist/lib/ir/index.d.ts.map +1 -1
  78. package/dist/lib/ir/scene.cjs +11 -3
  79. package/dist/lib/ir/scene.d.ts +18 -3542
  80. package/dist/lib/ir/scene.d.ts.map +1 -1
  81. package/dist/lib/ir/scope.cjs +30 -0
  82. package/dist/lib/ir/scope.d.ts +190 -0
  83. package/dist/lib/ir/scope.d.ts.map +1 -0
  84. package/dist/lib/ir/transform.cjs +56 -0
  85. package/dist/lib/ir/transform.d.ts +204 -0
  86. package/dist/lib/ir/transform.d.ts.map +1 -0
  87. package/package.json +1 -1
@@ -1,4 +1,6 @@
1
1
  const require_rect = require("../geometry/rect.cjs");
2
+ const require_name_stack = require("./name-stack.cjs");
3
+ const require_scope = require("./scope.cjs");
2
4
  const require_position = require("./position.cjs");
3
5
  const require_node = require("./node.cjs");
4
6
  const require_text_metrics = require("./text-metrics.cjs");
@@ -7,15 +9,16 @@ const require_precision = require("./precision.cjs");
7
9
  const require_layout = require("./layout.cjs");
8
10
  //#region src/compile/compile.ts
9
11
  /**
10
- * coordinate 注册成 0×0 NodeLayout
11
- * @description 让后续 path target / `at.of` 引用时 boundaryPoint 命中中心,符合"占位无形状边界"语义
12
+ * 构造一个落在指定全局点的 0×0 rectangle NodeLayout
13
+ * @description coordinate / scope.id 入场临时占位等"无形状只有位置"句柄共享此结构,
14
+ * 让后续 path target / `at.of` / `offset.of` / `polar.origin` 引用时 boundaryPoint 命中中心。
12
15
  */
13
- var coordinateAsLayout = (id, center) => ({
16
+ var zeroSizeRectAt = (id, [cx, cy]) => ({
14
17
  id,
15
18
  shape: "rectangle",
16
19
  rect: {
17
- x: center[0],
18
- y: center[1],
20
+ x: cx,
21
+ y: cy,
19
22
  width: 0,
20
23
  height: 0,
21
24
  rotate: 0
@@ -29,6 +32,20 @@ var coordinateAsLayout = (id, center) => ({
29
32
  fontSize: 0
30
33
  });
31
34
  /**
35
+ * 把 coordinate 注册成 0×0 NodeLayout
36
+ * @description 让后续 path target / `at.of` 引用时 boundaryPoint 命中中心,符合"占位无形状边界"语义
37
+ */
38
+ var coordinateAsLayout = (id, center) => zeroSizeRectAt(id, center);
39
+ /**
40
+ * scope.id 入场时的临时占位 NodeLayout
41
+ * @description scope 子树尚未处理时先放 0×0 占位(落在 scope 局部原点经累积 chain 投到全局的位置),
42
+ * 让 scope 子树内任何 lookup 不返回 undefined(占位语义自洽)。
43
+ * 子树 Pass 1 处理完毕后由 `registerScopeAsLayout` 算出真 bbox layout 覆盖此占位(NameStack.replaceLayout 不发 duplicate warn)
44
+ */
45
+ var scopePlaceholderLayout = (id, chain) => {
46
+ return zeroSizeRectAt(id, chain.length === 0 ? [0, 0] : require_scope.applyTransformChain([0, 0], chain));
47
+ };
48
+ /**
32
49
  * 默认 warn dispatcher:dev 模式 console.warn、生产静默
33
50
  * @description 用户传 onWarn 时使用用户的;不传走此 fallback
34
51
  */
@@ -36,9 +53,29 @@ var defaultWarnDispatcher = (warning) => {
36
53
  if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
37
54
  console.warn(`[retikz] ${warning.code} at ${warning.path}: ${warning.message}`);
38
55
  };
56
+ /** scope.transforms 解析失败时根据失败成因映射的 warn code */
57
+ var scopeTransformWarnCode = (scope) => {
58
+ for (const t of scope.transforms ?? []) {
59
+ if (t.kind === "offset-translate") return "OFFSET_BASE_UNRESOLVED";
60
+ if (t.kind === "at-translate") return "AT_TARGET_UNRESOLVED";
61
+ if (t.kind === "polar-translate") return "POLAR_ORIGIN_UNRESOLVED";
62
+ }
63
+ return "UNRESOLVED_NODE_REFERENCE";
64
+ };
65
+ /** 把 DuplicateRegisterInfo 翻成 CompileWarning(含可读 message + 双 IR locator) */
66
+ var formatDuplicateWarning = (info) => {
67
+ const frameNote = info.frameDepth === 0 ? "frame depth: 0 (root namespace)" : `frame depth: ${info.frameDepth} (under <Scope localNamespace>)`;
68
+ const firstLoc = info.firstIrPath ?? "(unknown earlier location)";
69
+ const secondLoc = info.secondIrPath ?? "(unknown current location)";
70
+ return {
71
+ code: "DUPLICATE_NODE_ID",
72
+ message: `Duplicate id '${info.id}' registered in the same namespace frame (${frameNote}); first defined at ${firstLoc}, redefined at ${secondLoc}. The later definition overrides the earlier one (last-wins).`,
73
+ path: secondLoc
74
+ };
75
+ };
39
76
  /**
40
77
  * IR → Scene 纯函数转换,所有 adapter 共享
41
- * @description Pass 1 处理 Node/coordinate 并注册 nodeIndex、发 primitive、累积 bbox;Pass 2 解析 Path 端点写 d 字符串;末端按 precision 折算 layout
78
+ * @description Pass 1 递归处理 node / coordinate / scope,把 scope 树下沉为嵌套 GroupPrim;scope.transforms 中的 4 种 translate 变体按 lowerScopeTransforms 展平为 Cartesian transform;node 在 Scene primitive 树里是局部坐标 + GroupPrim transform 链、在 NameStack 中存全局坐标供其他节点 / path 引用。NameStack 用栈式 frame 管理命名空间:默认全局扁平、`<Scope localNamespace>` 推入子 framescope.id 始终在父 frame 注册(外部句柄);id lookup 从栈顶向栈底 inside-out 搜索;同 frame 重复 id 触发 DUPLICATE_NODE_ID warn + 后定义覆盖前定义。Pass 2 解析 path 端点写 d 字符串,path primitive 发到 Pass 1 记录的对应容器;末端按 precision 折算 layout
42
79
  */
43
80
  var compileToScene = (ir, options = {}) => {
44
81
  const measureText = options.measureText ?? require_text_metrics.fallbackMeasurer;
@@ -47,46 +84,117 @@ var compileToScene = (ir, options = {}) => {
47
84
  const nodeDistance = options.nodeDistance;
48
85
  const onWarn = options.onWarn ?? defaultWarnDispatcher;
49
86
  const primitives = [];
50
- const nodeIndex = /* @__PURE__ */ new Map();
87
+ const nameStack = new require_name_stack.NameStack({ onDuplicate: (info) => onWarn(formatDuplicateWarning(info)) });
51
88
  const allPoints = [];
52
- const nodeLayouts = /* @__PURE__ */ new Map();
53
- for (let i = 0; i < ir.children.length; i++) {
54
- const child = ir.children[i];
55
- if (child.type === "node") {
56
- const layout = require_node.layoutNode(child, measureText, nodeIndex, nodeDistance);
57
- if (child.id) nodeIndex.set(child.id, layout);
58
- nodeLayouts.set(i, layout);
59
- } else if (child.type === "coordinate") {
60
- const center = require_position.resolvePosition(child.position, nodeIndex, nodeDistance);
61
- if (!center) {
62
- onWarn({
63
- code: "POLAR_ORIGIN_UNRESOLVED",
64
- message: `Cannot resolve position for coordinate '${child.id}'; polar.origin or at.of may reference an undefined node`,
65
- path: `children[${i}].coordinate.position`
89
+ /**
90
+ * 解析一批本层收集的 pending paths(lookup-only 阶段)
91
+ * @description path primitive 一律 push 到顶层 `primitives` —— 端点已是全局坐标,不能进 GroupPrim 否则被 scope.transform 二次 apply。NameStack 切到 pass2 守门:path 解析中误调 register 抛 internal error;解析完切回 pass1 让上层 scope 子树继续 register 子节点。
92
+ * `item.scopeChain` 记录该 path 所属 scope 累积 transform 链——传给 emitPathPrimitive,
93
+ * 让 step.to 内的 polar/at/offset 字面量按"当前 scope 局部度量 + 末端 apply chain"投影回全局。
94
+ */
95
+ const resolvePendingPaths = (pending) => {
96
+ if (pending.length === 0) return;
97
+ nameStack.enterLookupPhase();
98
+ try {
99
+ for (const item of pending) {
100
+ const result = require_index.emitPathPrimitive(item.path, nameStack, round, measureText, {
101
+ onWarn,
102
+ irPath: item.irPath,
103
+ scopeChain: item.scopeChain
66
104
  });
67
- throw new Error(`Cannot resolve position for coordinate ${child.id}; polar.origin or at.of may reference an undefined node`);
105
+ if (result) {
106
+ for (const prim of result.primitives) primitives.push(prim);
107
+ for (const p of result.points) allPoints.push(p);
108
+ }
68
109
  }
69
- nodeIndex.set(child.id, coordinateAsLayout(child.id, center));
110
+ } finally {
111
+ nameStack.exitLookupPhase();
70
112
  }
71
- }
72
- for (let i = 0; i < ir.children.length; i++) {
73
- const child = ir.children[i];
74
- if (child.type === "node") {
75
- const layout = nodeLayouts.get(i);
76
- if (!layout) continue;
77
- for (const prim of require_node.emitNodePrimitives(layout, round)) primitives.push(prim);
78
- allPoints.push(require_rect.rect.anchor(layout.rect, "north-west"), require_rect.rect.anchor(layout.rect, "north-east"), require_rect.rect.anchor(layout.rect, "south-west"), require_rect.rect.anchor(layout.rect, "south-east"));
79
- } else if (child.type === "path") {
80
- const result = require_index.emitPathPrimitive(child, nodeIndex, round, measureText, {
81
- onWarn,
82
- irPath: `children[${i}].path`
113
+ };
114
+ /**
115
+ * 递归处理一组 IR child,把 node / coordinate 发到 sink、把本层 path 收集到 pathsAccumulator、scope 下沉为 GroupPrim
116
+ * @description **不**在内部 resolve pathsAccumulator——调用方负责在合适时机(scope 入口:bbox replaceLayout 之后 / popFrame 之前;顶层:所有处理结束后)调用 resolvePendingPaths。这样 scope.id placeholder→real bbox 替换在本层 path 端点 lookup 之前完成,避免 "scope 内 path 自引用本 scope.id 拿到 placeholder" 的 latent bug,同时保留 ADR-02 的 "本层 path 在本层 frame 还在栈顶时 resolve" inside-out lookup 语义。
117
+ * @param children 当前层级的 IR child 数组
118
+ * @param chain 从根到当前层级累积的 Cartesian-only transform 链
119
+ * @param sink 当前层级 Scene primitive 落点(顶层 = primitives,scope 内 = GroupPrim.children)
120
+ * @param locatorPrefix IR locator 前缀(如 `''` 表示顶层、`children[2].scope.` 表示某 scope 内)
121
+ * @param layoutsAccumulator 当前 scope 子树所有"实体"layout(node / coordinate / 嵌套 scope.id synthetic)累积——专给上层 scope.id bbox 计算用;顶层调用传一个共享数组(用得着就用,丢弃也不影响)
122
+ * @param pathsAccumulator 当前层级收集的 pending paths——由调用方分配并在合适时机 resolve
123
+ */
124
+ const processChildren = (children, chain, sink, locatorPrefix, layoutsAccumulator, pathsAccumulator) => {
125
+ for (let i = 0; i < children.length; i++) {
126
+ const child = children[i];
127
+ if (child.type === "node") {
128
+ const layout = require_node.layoutNode(child, measureText, nameStack, nodeDistance, chain);
129
+ const globalLayout = chain.length === 0 ? layout : require_scope.projectLayoutToGlobal(layout, chain);
130
+ if (child.id) nameStack.register(child.id, globalLayout, `${locatorPrefix}children[${i}].node.id`);
131
+ for (const prim of require_node.emitNodePrimitives(layout, round)) sink.push(prim);
132
+ allPoints.push(require_rect.rect.anchor(globalLayout.rect, "north-west"), require_rect.rect.anchor(globalLayout.rect, "north-east"), require_rect.rect.anchor(globalLayout.rect, "south-west"), require_rect.rect.anchor(globalLayout.rect, "south-east"));
133
+ layoutsAccumulator.push(globalLayout);
134
+ } else if (child.type === "coordinate") {
135
+ const localCenter = require_position.resolvePosition(child.position, nameStack, nodeDistance, chain);
136
+ if (!localCenter) {
137
+ onWarn({
138
+ code: "POLAR_ORIGIN_UNRESOLVED",
139
+ message: `Cannot resolve position for coordinate '${child.id}'; polar.origin or at.of may reference an undefined node`,
140
+ path: `${locatorPrefix}children[${i}].coordinate.position`
141
+ });
142
+ throw new Error(`Cannot resolve position for coordinate ${child.id}; polar.origin or at.of may reference an undefined node`);
143
+ }
144
+ const globalCenter = chain.length === 0 ? localCenter : require_scope.applyTransformChain(localCenter, chain);
145
+ const coordLayout = coordinateAsLayout(child.id, globalCenter);
146
+ nameStack.register(child.id, coordLayout, `${locatorPrefix}children[${i}].coordinate.id`);
147
+ layoutsAccumulator.push(coordLayout);
148
+ } else if (child.type === "scope") {
149
+ const loweredOwn = require_scope.lowerScopeTransforms(child.transforms ?? [], nameStack, nodeDistance);
150
+ if (loweredOwn === null) onWarn({
151
+ code: scopeTransformWarnCode(child),
152
+ message: `Cannot resolve one of scope.transforms; referent (at.of / offset.of / polar.origin) is undefined or defined later in the IR`,
153
+ path: `${locatorPrefix}children[${i}].scope.transforms`
154
+ });
155
+ const ownTransforms = loweredOwn ?? [];
156
+ const innerChain = [...chain, ...ownTransforms];
157
+ const parentFrameDepth = nameStack.depth - 1;
158
+ if (child.id) nameStack.register(child.id, scopePlaceholderLayout(child.id, innerChain), `${locatorPrefix}children[${i}].scope.id`);
159
+ const pushedFrame = child.localNamespace === true;
160
+ if (pushedFrame) nameStack.pushFrame();
161
+ const innerSink = [];
162
+ /** 本 scope 子树的 layouts 累积器;子树结束后用于算 bbox */
163
+ const innerLayouts = [];
164
+ /** 本 scope 子树收集的 pending paths——在 bbox replaceLayout 后 / popFrame 前 resolve,
165
+ * 让 scope 内 path 自引用本 scope.id 端点取真 bbox 而非 placeholder */
166
+ const innerPaths = [];
167
+ try {
168
+ processChildren(child.children, innerChain, innerSink, `${locatorPrefix}children[${i}].scope.`, innerLayouts, innerPaths);
169
+ if (child.id) {
170
+ const bbox = require_scope.computeScopeBoundingBox(innerLayouts);
171
+ const fallbackOrigin = innerChain.length === 0 ? [0, 0] : require_scope.applyTransformChain([0, 0], innerChain);
172
+ const bboxLayout = require_scope.registerScopeAsLayout(child.id, bbox, fallbackOrigin);
173
+ nameStack.replaceLayout(child.id, bboxLayout, parentFrameDepth);
174
+ layoutsAccumulator.push(bboxLayout);
175
+ } else for (const innerLayout of innerLayouts) layoutsAccumulator.push(innerLayout);
176
+ resolvePendingPaths(innerPaths);
177
+ } finally {
178
+ if (pushedFrame) nameStack.popFrame();
179
+ }
180
+ const hasOwnTransforms = ownTransforms.length > 0;
181
+ if (innerSink.length === 0 && !hasOwnTransforms && child.id === void 0) continue;
182
+ const group = {
183
+ type: "group",
184
+ children: innerSink
185
+ };
186
+ if (hasOwnTransforms) group.transforms = [...ownTransforms];
187
+ sink.push(group);
188
+ } else pathsAccumulator.push({
189
+ path: child,
190
+ irPath: `${locatorPrefix}children[${i}].path`,
191
+ scopeChain: chain
83
192
  });
84
- if (result) {
85
- for (const prim of result.primitives) primitives.push(prim);
86
- for (const p of result.points) allPoints.push(p);
87
- }
88
193
  }
89
- }
194
+ };
195
+ const rootPaths = [];
196
+ processChildren(ir.children, [], primitives, "", [], rootPaths);
197
+ resolvePendingPaths(rootPaths);
90
198
  return {
91
199
  primitives,
92
200
  layout: require_layout.computeLayout(allPoints, layoutPadding, round)
@@ -7,7 +7,7 @@ export type CompileWarning = {
7
7
  * 警告类型代码(机器可读)
8
8
  * @description 用户可按 code 分支处理;未来 alpha 加新 code 不破坏调用方
9
9
  */
10
- code: 'UNRESOLVED_NODE_REFERENCE' | 'PATH_TOO_SHORT' | 'ANCHOR_RESOLUTION_FAILED' | 'OFFSET_BASE_UNRESOLVED' | 'POLAR_ORIGIN_UNRESOLVED' | 'AT_TARGET_UNRESOLVED' | 'RELATIVE_INITIAL_NO_PREV_END' | 'BBOX_EXTREME_INPUT' | (string & {});
10
+ code: 'UNRESOLVED_NODE_REFERENCE' | 'PATH_TOO_SHORT' | 'ANCHOR_RESOLUTION_FAILED' | 'OFFSET_BASE_UNRESOLVED' | 'POLAR_ORIGIN_UNRESOLVED' | 'AT_TARGET_UNRESOLVED' | 'RELATIVE_INITIAL_NO_PREV_END' | 'BBOX_EXTREME_INPUT' | 'DUPLICATE_NODE_ID' | (string & {});
11
11
  /** 人类可读消息(英文) */
12
12
  message: string;
13
13
  /** IR locator 路径(jq-like,如 `'children[3].path.children[1].to'`) */
@@ -37,7 +37,7 @@ export type CompileOptions = {
37
37
  };
38
38
  /**
39
39
  * IR → Scene 纯函数转换,所有 adapter 共享
40
- * @description Pass 1 处理 Node/coordinate 并注册 nodeIndex、发 primitive、累积 bbox;Pass 2 解析 Path 端点写 d 字符串;末端按 precision 折算 layout
40
+ * @description Pass 1 递归处理 node / coordinate / scope,把 scope 树下沉为嵌套 GroupPrim;scope.transforms 中的 4 种 translate 变体按 lowerScopeTransforms 展平为 Cartesian transform;node 在 Scene primitive 树里是局部坐标 + GroupPrim transform 链、在 NameStack 中存全局坐标供其他节点 / path 引用。NameStack 用栈式 frame 管理命名空间:默认全局扁平、`<Scope localNamespace>` 推入子 framescope.id 始终在父 frame 注册(外部句柄);id lookup 从栈顶向栈底 inside-out 搜索;同 frame 重复 id 触发 DUPLICATE_NODE_ID warn + 后定义覆盖前定义。Pass 2 解析 path 端点写 d 字符串,path primitive 发到 Pass 1 记录的对应容器;末端按 precision 折算 layout
41
41
  */
42
42
  export declare const compileToScene: (ir: IR, options?: CompileOptions) => Scene;
43
43
  //# sourceMappingURL=compile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../src/compile/compile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAc,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAkB,MAAM,cAAc,CAAC;AAK1D,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAuBrE,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,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;CAC5C,CAAC;AAWF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,EAAE,EAAE,UAAS,cAAmB,KAAG,KAwErE,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;AAahF,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AA2CrE,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,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;CAC5C,CAAC;AAsDF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,IAAI,EAAE,EAAE,UAAS,cAAmB,KAAG,KAoMrE,CAAC"}
@@ -4,6 +4,7 @@ export * from './parseTarget';
4
4
  export * from './path/index';
5
5
  export * from './position';
6
6
  export * from './precision';
7
+ export * from './scope';
7
8
  export * from './text-metrics';
8
9
  export * from './layout';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/compile/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/compile/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC"}
@@ -0,0 +1,104 @@
1
+ //#region src/compile/name-stack.ts
2
+ /**
3
+ * 栈式 namespace frame —— 默认全局扁平、`<Scope localNamespace>` 时 pushFrame 隔离
4
+ * @description 内部维护 `Array<Map<string, NodeLayout>>`,栈底是根 frame;
5
+ * register 写入栈顶 frame;lookup 从栈顶向栈底 inside-out 搜索(内层 shadowing 外层);
6
+ * 同 frame 同 id 触发 onDuplicate + last-wins 覆盖(不抛错);跨 frame 同 id 不算 duplicate;
7
+ * Pass 1 = register-only,Pass 2 = lookup-only,phase 状态守护违规调用
8
+ */
9
+ var NameStack = class {
10
+ /** 栈式 frame 容器;栈底(index 0)= 根 frame,栈顶(last)= 当前 frame */
11
+ frames;
12
+ /** 与每个 frame 对应的"已注册 id → 首次 register 时的 irPath"映射,用于 duplicate warn 复述位置 */
13
+ firstIrPaths;
14
+ onDuplicate;
15
+ /** 当前阶段;compile Pass 1 = 'pass1'(register 合法),Pass 2 = 'pass2'(只能 lookup) */
16
+ currentPhase = "pass1";
17
+ constructor(options = {}) {
18
+ this.frames = [/* @__PURE__ */ new Map()];
19
+ this.firstIrPaths = [/* @__PURE__ */ new Map()];
20
+ this.onDuplicate = options.onDuplicate;
21
+ }
22
+ /** 当前栈深(≥ 1;根 frame 永远存在) */
23
+ get depth() {
24
+ return this.frames.length;
25
+ }
26
+ /** 当前阶段('pass1' / 'pass2') */
27
+ get phase() {
28
+ return this.currentPhase;
29
+ }
30
+ /** 推入新 frame;通常对应 `<Scope localNamespace>` 入场 */
31
+ pushFrame() {
32
+ this.frames.push(/* @__PURE__ */ new Map());
33
+ this.firstIrPaths.push(/* @__PURE__ */ new Map());
34
+ }
35
+ /**
36
+ * 弹出栈顶 frame;通常对应 `<Scope localNamespace>` 出场
37
+ * @description 禁止把栈弹空(根 frame 必须始终存在);意外调用抛 internal error 暴露 bug
38
+ */
39
+ popFrame() {
40
+ if (this.frames.length <= 1) throw new Error("NameStack.popFrame: cannot pop the root frame (internal invariant violated)");
41
+ this.frames.pop();
42
+ this.firstIrPaths.pop();
43
+ }
44
+ /** 切换到 Pass 2(lookup-only)阶段;切换后 register 调用一律抛 internal error */
45
+ enterLookupPhase() {
46
+ this.currentPhase = "pass2";
47
+ }
48
+ /** 切回 Pass 1(register + lookup 均可)阶段;用于嵌套 path-resolve 完成后继续处理上层 scope 子树 */
49
+ exitLookupPhase() {
50
+ this.currentPhase = "pass1";
51
+ }
52
+ /**
53
+ * 注册一条 id → NodeLayout 到栈顶 frame
54
+ * @description 若栈顶 frame 已有同 id:触发 onDuplicate 回调(first register 不触发),用新 layout last-wins 覆盖;返回是否覆盖了已有 entry
55
+ * @param id 要注册的 id(node.id / coordinate.id / scope.id)
56
+ * @param layout 对应的 NodeLayout
57
+ * @param irPath 触发此次 register 的 IR locator(jq-like 路径),用于 duplicate warn
58
+ * @returns true = 当前 frame 已有同 id 被覆盖;false = 新 entry
59
+ */
60
+ register(id, layout, irPath) {
61
+ if (this.currentPhase !== "pass1") throw new Error(`NameStack.register('${id}'): only allowed during pass1; current phase is '${this.currentPhase}'`);
62
+ const topFrame = this.frames[this.frames.length - 1];
63
+ const topFirstPaths = this.firstIrPaths[this.firstIrPaths.length - 1];
64
+ const wasOverwritten = topFrame.has(id);
65
+ if (wasOverwritten) this.onDuplicate?.({
66
+ id,
67
+ frameDepth: this.frames.length - 1,
68
+ firstIrPath: topFirstPaths.get(id),
69
+ secondIrPath: irPath
70
+ });
71
+ else topFirstPaths.set(id, irPath);
72
+ topFrame.set(id, layout);
73
+ return wasOverwritten;
74
+ }
75
+ /**
76
+ * 在指定深度的 frame 上替换已注册 id 对应的 layout,**不触发** onDuplicate 回调
77
+ * @description 专为"scope.id 入场注册临时占位 layout、子树结束后用真 bbox layout 覆盖"流程设计——
78
+ * 同一 scope.id 的两次 register 是预期的 placeholder → real-bbox 接力,不是命名冲突;
79
+ * 此 API 跳过 duplicate 检测但保留 firstIrPath(不刷新 first-register 位置)。
80
+ * 若指定 frame 不存在该 id(说明 id 没被 register 过),抛 internal error 暴露调用方 bug
81
+ * @param id 要替换的 id(必须已在该 frame 注册过)
82
+ * @param layout 新的 NodeLayout(覆盖旧值)
83
+ * @param frameDepth 0 = 根 frame;通常传 scope 入场前的栈深 - 1
84
+ */
85
+ replaceLayout(id, layout, frameDepth) {
86
+ if (this.currentPhase !== "pass1") throw new Error(`NameStack.replaceLayout('${id}'): only allowed during pass1; current phase is '${this.currentPhase}'`);
87
+ if (frameDepth < 0 || frameDepth >= this.frames.length) throw new Error(`NameStack.replaceLayout('${id}'): frameDepth ${frameDepth} out of range (stack depth ${this.frames.length})`);
88
+ const targetFrame = this.frames[frameDepth];
89
+ if (!targetFrame.has(id)) throw new Error(`NameStack.replaceLayout('${id}'): id not previously registered in frame at depth ${frameDepth}`);
90
+ targetFrame.set(id, layout);
91
+ }
92
+ /**
93
+ * inside-out 查找 id 对应的 NodeLayout
94
+ * @description 从栈顶向栈底依次查找;首个命中的 frame 返回;都没命中返回 undefined。内层可见外层(shadowing),外层不可见内层
95
+ */
96
+ lookup(id) {
97
+ for (let i = this.frames.length - 1; i >= 0; i--) {
98
+ const layout = this.frames[i].get(id);
99
+ if (layout !== void 0) return layout;
100
+ }
101
+ }
102
+ };
103
+ //#endregion
104
+ exports.NameStack = NameStack;
@@ -0,0 +1,81 @@
1
+ import { NodeLayout } from './node';
2
+ /**
3
+ * 单条 duplicate warn 由 NameStack 通过 onDuplicate 回调向外发出的载荷
4
+ * @description 由 compile 层把 NameStack 内部 frame depth + 前后两次 IR locator 翻译成 CompileWarning(含可读 message);NameStack 不知道 CompileWarning 的具体形态,避免反向耦合
5
+ */
6
+ export type DuplicateRegisterInfo = {
7
+ /** 同 frame 内重复出现的 id(两次 register 都用此 id) */
8
+ id: string;
9
+ /** 当前 frame 在栈中的深度(0 = 根 frame;每层 pushFrame 自增 1) */
10
+ frameDepth: number;
11
+ /** 先注册的那一条的 IR locator(jq-like 路径),register 时传入;缺失则 undefined */
12
+ firstIrPath?: string;
13
+ /** 后注册(本次触发覆盖)的那一条的 IR locator */
14
+ secondIrPath?: string;
15
+ };
16
+ /** NameStack 构造选项 */
17
+ export type NameStackOptions = {
18
+ /**
19
+ * 同 frame 重复 register 时的回调
20
+ * @description 第 N 次 register(N ≥ 2)发一次;first register 不发;NameStack 不直接发 CompileWarning,由 compile 层翻译
21
+ */
22
+ onDuplicate?: (info: DuplicateRegisterInfo) => void;
23
+ };
24
+ /**
25
+ * 栈式 namespace frame —— 默认全局扁平、`<Scope localNamespace>` 时 pushFrame 隔离
26
+ * @description 内部维护 `Array<Map<string, NodeLayout>>`,栈底是根 frame;
27
+ * register 写入栈顶 frame;lookup 从栈顶向栈底 inside-out 搜索(内层 shadowing 外层);
28
+ * 同 frame 同 id 触发 onDuplicate + last-wins 覆盖(不抛错);跨 frame 同 id 不算 duplicate;
29
+ * Pass 1 = register-only,Pass 2 = lookup-only,phase 状态守护违规调用
30
+ */
31
+ export declare class NameStack {
32
+ /** 栈式 frame 容器;栈底(index 0)= 根 frame,栈顶(last)= 当前 frame */
33
+ private readonly frames;
34
+ /** 与每个 frame 对应的"已注册 id → 首次 register 时的 irPath"映射,用于 duplicate warn 复述位置 */
35
+ private readonly firstIrPaths;
36
+ private readonly onDuplicate?;
37
+ /** 当前阶段;compile Pass 1 = 'pass1'(register 合法),Pass 2 = 'pass2'(只能 lookup) */
38
+ private currentPhase;
39
+ constructor(options?: NameStackOptions);
40
+ /** 当前栈深(≥ 1;根 frame 永远存在) */
41
+ get depth(): number;
42
+ /** 当前阶段('pass1' / 'pass2') */
43
+ get phase(): 'pass1' | 'pass2';
44
+ /** 推入新 frame;通常对应 `<Scope localNamespace>` 入场 */
45
+ pushFrame(): void;
46
+ /**
47
+ * 弹出栈顶 frame;通常对应 `<Scope localNamespace>` 出场
48
+ * @description 禁止把栈弹空(根 frame 必须始终存在);意外调用抛 internal error 暴露 bug
49
+ */
50
+ popFrame(): void;
51
+ /** 切换到 Pass 2(lookup-only)阶段;切换后 register 调用一律抛 internal error */
52
+ enterLookupPhase(): void;
53
+ /** 切回 Pass 1(register + lookup 均可)阶段;用于嵌套 path-resolve 完成后继续处理上层 scope 子树 */
54
+ exitLookupPhase(): void;
55
+ /**
56
+ * 注册一条 id → NodeLayout 到栈顶 frame
57
+ * @description 若栈顶 frame 已有同 id:触发 onDuplicate 回调(first register 不触发),用新 layout last-wins 覆盖;返回是否覆盖了已有 entry
58
+ * @param id 要注册的 id(node.id / coordinate.id / scope.id)
59
+ * @param layout 对应的 NodeLayout
60
+ * @param irPath 触发此次 register 的 IR locator(jq-like 路径),用于 duplicate warn
61
+ * @returns true = 当前 frame 已有同 id 被覆盖;false = 新 entry
62
+ */
63
+ register(id: string, layout: NodeLayout, irPath?: string): boolean;
64
+ /**
65
+ * 在指定深度的 frame 上替换已注册 id 对应的 layout,**不触发** onDuplicate 回调
66
+ * @description 专为"scope.id 入场注册临时占位 layout、子树结束后用真 bbox layout 覆盖"流程设计——
67
+ * 同一 scope.id 的两次 register 是预期的 placeholder → real-bbox 接力,不是命名冲突;
68
+ * 此 API 跳过 duplicate 检测但保留 firstIrPath(不刷新 first-register 位置)。
69
+ * 若指定 frame 不存在该 id(说明 id 没被 register 过),抛 internal error 暴露调用方 bug
70
+ * @param id 要替换的 id(必须已在该 frame 注册过)
71
+ * @param layout 新的 NodeLayout(覆盖旧值)
72
+ * @param frameDepth 0 = 根 frame;通常传 scope 入场前的栈深 - 1
73
+ */
74
+ replaceLayout(id: string, layout: NodeLayout, frameDepth: number): void;
75
+ /**
76
+ * inside-out 查找 id 对应的 NodeLayout
77
+ * @description 从栈顶向栈底依次查找;首个命中的 frame 返回;都没命中返回 undefined。内层可见外层(shadowing),外层不可见内层
78
+ */
79
+ lookup(id: string): NodeLayout | undefined;
80
+ }
81
+ //# sourceMappingURL=name-stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name-stack.d.ts","sourceRoot":"","sources":["../../../src/compile/name-stack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,4CAA4C;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,qBAAqB;AACrB,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,SAAS;IACpB,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAwC;IACrE,6EAA6E;IAC7E,OAAO,CAAC,YAAY,CAA8B;gBAEtC,OAAO,GAAE,gBAAqB;IAM1C,6BAA6B;IAC7B,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,8BAA8B;IAC9B,IAAI,KAAK,IAAI,OAAO,GAAG,OAAO,CAE7B;IAED,iDAAiD;IACjD,SAAS,IAAI,IAAI;IAKjB;;;OAGG;IACH,QAAQ,IAAI,IAAI;IAQhB,kEAAkE;IAClE,gBAAgB,IAAI,IAAI;IAIxB,6EAA6E;IAC7E,eAAe,IAAI,IAAI;IAIvB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO;IAuBlE;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAoBvE;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;CAO3C"}
@@ -130,11 +130,16 @@ var labelCenter = (layout, label) => {
130
130
  };
131
131
  /**
132
132
  * 取节点 shape 在指定角度方向的边界点
133
- * @description 角度约定与 PolarPosition 一致(度数:0°=+x,90°=+y screen 下方);不应用 margin(同 anchorOf);用于 `'A.30'` 落点
133
+ * @description 角度是节点**局部坐标系**下的极角(度数:0°=局部 +x,90°=局部 +y)。layout.rect.rotate 把局部基绕中心旋转,得到世界系下的视觉方向;shape boundaryPoint 内部用 rotate-aware 投影,所以这里把局部 (cos, sin) 经 rect.rotate 旋转后加到中心当作世界系 toward 传入。不应用 margin(同 anchorOf);用于 `'A.30'` 落点
134
134
  */
135
135
  var angleBoundaryOf = (layout, angleDeg) => {
136
136
  const rad = angleDeg * Math.PI / 180;
137
- const toward = [layout.rect.x + Math.cos(rad), layout.rect.y + Math.sin(rad)];
137
+ const lx = Math.cos(rad);
138
+ const ly = Math.sin(rad);
139
+ const rot = layout.rect.rotate ?? 0;
140
+ const cosR = Math.cos(rot);
141
+ const sinR = Math.sin(rot);
142
+ const toward = [layout.rect.x + lx * cosR - ly * sinR, layout.rect.y + lx * sinR + ly * cosR];
138
143
  switch (layout.shape) {
139
144
  case "rectangle": return require_rect.rect.boundaryPoint(rectOf(layout, 0), toward);
140
145
  case "circle": return require_circle.circle.boundaryPoint(circleOf(layout, 0), toward);
@@ -144,9 +149,12 @@ var angleBoundaryOf = (layout, angleDeg) => {
144
149
  };
145
150
  /**
146
151
  * IR Node → 内部 NodeLayout
147
- * @description 文本度量 + padding 推内框半轴;按 shape 算外接边界(circle 取半对角线、ellipse ×√2、diamond ×2);解析 position 为几何中心;rotate 度数转弧度
152
+ * @description 文本度量 + padding 推内框半轴;按 shape 算外接边界(circle 取半对角线、ellipse ×√2、diamond ×2);解析 position 为几何中心;rotate 度数转弧度。
153
+ * `scopeChain` 非空时 `resolvePosition` 返回**当前 scope 局部坐标**(relative position 在当前
154
+ * scope 局部度量),调用方负责后续 `projectLayoutToGlobal` / `applyTransformChain` 投回全局;
155
+ * 笛卡尔字面量 `Position` 已在 scope 局部度量,行为延续 v0.1。
148
156
  */
149
- var layoutNode = (node, measureText, nodeIndex, nodeDistance) => {
157
+ var layoutNode = (node, measureText, nameStack, nodeDistance, scopeChain = []) => {
150
158
  const sx = node.xScale ?? node.scale ?? 1;
151
159
  const sy = node.yScale ?? node.scale ?? 1;
152
160
  const fontScale = Math.min(sx, sy);
@@ -217,7 +225,7 @@ var layoutNode = (node, measureText, nodeIndex, nodeDistance) => {
217
225
  break;
218
226
  }
219
227
  const rotateDeg = node.rotate ?? 0;
220
- const center = require_position.resolvePosition(node.position, nodeIndex, nodeDistance);
228
+ const center = require_position.resolvePosition(node.position, nameStack, nodeDistance, scopeChain);
221
229
  if (!center) throw new Error(`Cannot resolve position for node ${node.id ?? "(unnamed)"}; polar.origin or at.of may reference an undefined node`);
222
230
  const labels = (node.label === void 0 ? void 0 : Array.isArray(node.label) ? node.label : [node.label])?.map((lab) => {
223
231
  const labFont = lab.font;
@@ -1,7 +1,8 @@
1
1
  import { Position } from '../geometry/point';
2
2
  import { Rect, RectAnchor } from '../geometry/rect';
3
3
  import { AtDirection, IRNode, NodeShape } from '../ir';
4
- import { ScenePrimitive, TextLine } from '../primitive';
4
+ import { ScenePrimitive, TextLine, Transform } from '../primitive';
5
+ import { NameStack } from './name-stack';
5
6
  import { TextMeasurer } from './text-metrics';
6
7
  export type NodeLayout = {
7
8
  /** 节点 id(其他位置可引用) */
@@ -88,14 +89,17 @@ export declare const boundaryPointOf: (layout: NodeLayout, toward: Position) =>
88
89
  export declare const anchorOf: (layout: NodeLayout, name: RectAnchor) => Position;
89
90
  /**
90
91
  * 取节点 shape 在指定角度方向的边界点
91
- * @description 角度约定与 PolarPosition 一致(度数:0°=+x,90°=+y screen 下方);不应用 margin(同 anchorOf);用于 `'A.30'` 落点
92
+ * @description 角度是节点**局部坐标系**下的极角(度数:0°=局部 +x,90°=局部 +y)。layout.rect.rotate 把局部基绕中心旋转,得到世界系下的视觉方向;shape boundaryPoint 内部用 rotate-aware 投影,所以这里把局部 (cos, sin) 经 rect.rotate 旋转后加到中心当作世界系 toward 传入。不应用 margin(同 anchorOf);用于 `'A.30'` 落点
92
93
  */
93
94
  export declare const angleBoundaryOf: (layout: NodeLayout, angleDeg: number) => Position;
94
95
  /**
95
96
  * IR Node → 内部 NodeLayout
96
- * @description 文本度量 + padding 推内框半轴;按 shape 算外接边界(circle 取半对角线、ellipse ×√2、diamond ×2);解析 position 为几何中心;rotate 度数转弧度
97
+ * @description 文本度量 + padding 推内框半轴;按 shape 算外接边界(circle 取半对角线、ellipse ×√2、diamond ×2);解析 position 为几何中心;rotate 度数转弧度。
98
+ * `scopeChain` 非空时 `resolvePosition` 返回**当前 scope 局部坐标**(relative position 在当前
99
+ * scope 局部度量),调用方负责后续 `projectLayoutToGlobal` / `applyTransformChain` 投回全局;
100
+ * 笛卡尔字面量 `Position` 已在 scope 局部度量,行为延续 v0.1。
97
101
  */
98
- export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nodeIndex: Map<string, NodeLayout>, nodeDistance?: number) => NodeLayout;
102
+ export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nameStack: NameStack, nodeDistance?: number, scopeChain?: ReadonlyArray<Transform>) => NodeLayout;
99
103
  /**
100
104
  * NodeLayout → Scene primitives
101
105
  * @description shape 主体按 shape 分发(rect/ellipse/path);text 始终走 TextPrim;有旋转时外层 GroupPrim 用 `rotate(deg cx cy)` 统一包裹(text 必须靠 group 旋转)
@@ -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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"}
@@ -1,44 +1,55 @@
1
+ const require_scope = require("../scope.cjs");
1
2
  const require_position = require("../position.cjs");
2
3
  const require_node = require("../node.cjs");
4
+ const require_anchor_cache = require("../anchor-cache.cjs");
3
5
  const require_parseTarget = require("../parseTarget.cjs");
4
6
  //#region src/compile/path/anchor.ts
5
7
  /**
6
8
  * 求 step.to 的参考点(给 boundary clip 算方向 / 折角 corner 用)
7
- * @description 三态:`'A'`(auto) 节点中心;`'A.<anchor>'`/`'A.<deg>'` 显式锚点 refPoint=endpoint 位置不随邻居变。直接坐标/极坐标解析为笛卡尔
9
+ * @description 三态:`'A'`(auto) 节点中心;`'A.<anchor>'`/`'A.<deg>'` 显式锚点 refPoint=endpoint 位置不随邻居变。直接坐标/极坐标解析为笛卡尔。
10
+ * string id lookup 拿到的 layout 已是全局坐标——不走 scopeChain 投影;Position / Polar /
11
+ * At / Offset 字面量经 `resolvePosition(..., scopeChain)` 拿到当前 scope 局部坐标后
12
+ * `applyTransformChain` 投回全局。`scopeChain=[]` 等价 v0.1(恒等)。
8
13
  */
9
- var refPointOfTarget = (target, nodeIndex) => {
14
+ var refPointOfTarget = (target, nameStack, scopeChain = []) => {
10
15
  if (typeof target === "string") {
11
16
  const ref = require_parseTarget.parseNodeRef(target);
12
- const node = nodeIndex.get(ref.id);
17
+ const node = nameStack.lookup(ref.id);
13
18
  if (!node) return null;
14
19
  switch (ref.kind) {
15
20
  case "node": return [node.rect.x, node.rect.y];
16
- case "anchor": return require_node.anchorOf(node, ref.anchor);
17
- case "angle": return require_node.angleBoundaryOf(node, ref.angle);
21
+ case "anchor": return require_anchor_cache.resolveAnchor(node, ref.anchor);
22
+ case "angle": return require_anchor_cache.resolveAnchor(node, String(ref.angle));
18
23
  }
19
24
  }
20
25
  if (typeof target === "object" && !Array.isArray(target) && ("relative" in target || "relativeAccumulate" in target)) return null;
21
- return require_position.resolvePosition(target, nodeIndex);
26
+ const local = require_position.resolvePosition(target, nameStack, void 0, scopeChain);
27
+ if (!local) return null;
28
+ return scopeChain.length === 0 ? local : require_scope.applyTransformChain(local, scopeChain);
22
29
  };
23
30
  /** 折角中间点:`-|` → (curr.x, prev.y);`|-` → (prev.x, curr.y) */
24
31
  var cornerOf = (prev, curr, via) => via === "-|" ? [curr[0], prev[1]] : [prev[0], curr[1]];
25
32
  /**
26
33
  * 在 toward 方向算 step.to 的实际绘制端点
27
- * @description 节点 auto `'A'`:按 shape 走 boundaryPointOf 求中心→toward 射线交点;命名 anchor/角度:位置已定不受 toward 影响;直接坐标/极坐标:解析后返回;失败返回 null
34
+ * @description 节点 auto `'A'`:按 shape 走 boundaryPointOf 求中心→toward 射线交点;命名 anchor/角度:位置已定不受 toward 影响;直接坐标/极坐标:解析后返回;失败返回 null
35
+ * string id lookup 拿到的 layout 已是全局坐标;Position / Polar / At / Offset 字面量经
36
+ * `resolvePosition(..., scopeChain)` 拿到当前 scope 局部坐标后 `applyTransformChain` 投回全局。
28
37
  */
29
- var clipForTarget = (target, toward, nodeIndex) => {
38
+ var clipForTarget = (target, toward, nameStack, scopeChain = []) => {
30
39
  if (typeof target === "string") {
31
40
  const ref = require_parseTarget.parseNodeRef(target);
32
- const node = nodeIndex.get(ref.id);
41
+ const node = nameStack.lookup(ref.id);
33
42
  if (!node) return null;
34
43
  switch (ref.kind) {
35
44
  case "node": return require_node.boundaryPointOf(node, toward);
36
- case "anchor": return require_node.anchorOf(node, ref.anchor);
37
- case "angle": return require_node.angleBoundaryOf(node, ref.angle);
45
+ case "anchor": return require_anchor_cache.resolveAnchor(node, ref.anchor);
46
+ case "angle": return require_anchor_cache.resolveAnchor(node, String(ref.angle));
38
47
  }
39
48
  }
40
49
  if (typeof target === "object" && !Array.isArray(target) && ("relative" in target || "relativeAccumulate" in target)) return null;
41
- return require_position.resolvePosition(target, nodeIndex);
50
+ const local = require_position.resolvePosition(target, nameStack, void 0, scopeChain);
51
+ if (!local) return null;
52
+ return scopeChain.length === 0 ? local : require_scope.applyTransformChain(local, scopeChain);
42
53
  };
43
54
  /** 两个 IRPosition 两分量精确相等(未 round) */
44
55
  var samePoint = (a, b) => !!a && !!b && a[0] === b[0] && a[1] === b[1];