@retikz/core 0.1.0 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/compile/anchor-cache.d.ts +12 -0
- package/dist/es/compile/anchor-cache.d.ts.map +1 -0
- package/dist/es/compile/anchor-cache.js +41 -0
- package/dist/es/compile/compile.d.ts +2 -2
- package/dist/es/compile/compile.d.ts.map +1 -1
- package/dist/es/compile/compile.js +150 -40
- package/dist/es/compile/index.d.ts +1 -0
- package/dist/es/compile/index.d.ts.map +1 -1
- package/dist/es/compile/name-stack.d.ts +81 -0
- package/dist/es/compile/name-stack.d.ts.map +1 -0
- package/dist/es/compile/name-stack.js +104 -0
- package/dist/es/compile/node.d.ts +9 -5
- package/dist/es/compile/node.d.ts.map +1 -1
- package/dist/es/compile/node.js +19 -11
- package/dist/es/compile/path/anchor.d.ts +11 -5
- package/dist/es/compile/path/anchor.d.ts.map +1 -1
- package/dist/es/compile/path/anchor.js +24 -13
- package/dist/es/compile/path/index.d.ts +9 -3
- package/dist/es/compile/path/index.d.ts.map +1 -1
- package/dist/es/compile/path/index.js +18 -17
- package/dist/es/compile/path/label.d.ts +1 -1
- package/dist/es/compile/path/label.d.ts.map +1 -1
- package/dist/es/compile/path/label.js +17 -4
- package/dist/es/compile/path/relative.d.ts +10 -4
- package/dist/es/compile/path/relative.d.ts.map +1 -1
- package/dist/es/compile/path/relative.js +16 -8
- package/dist/es/compile/position.d.ts +19 -3
- package/dist/es/compile/position.d.ts.map +1 -1
- package/dist/es/compile/position.js +28 -8
- package/dist/es/compile/scope.d.ts +66 -0
- package/dist/es/compile/scope.d.ts.map +1 -0
- package/dist/es/compile/scope.js +256 -0
- package/dist/es/compile/style.d.ts +46 -0
- package/dist/es/compile/style.d.ts.map +1 -0
- package/dist/es/compile/style.js +259 -0
- package/dist/es/index.d.ts +2 -2
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +3 -1
- package/dist/es/ir/index.d.ts +2 -0
- package/dist/es/ir/index.d.ts.map +1 -1
- package/dist/es/ir/node.d.ts +5 -2
- package/dist/es/ir/node.d.ts.map +1 -1
- package/dist/es/ir/node.js +1 -0
- package/dist/es/ir/path/path.d.ts +531 -0
- package/dist/es/ir/path/path.d.ts.map +1 -1
- package/dist/es/ir/path/path.js +1 -0
- package/dist/es/ir/path/step.d.ts +834 -0
- package/dist/es/ir/path/step.d.ts.map +1 -1
- package/dist/es/ir/path/step.js +5 -1
- package/dist/es/ir/scene.d.ts +18 -3542
- package/dist/es/ir/scene.d.ts.map +1 -1
- package/dist/es/ir/scene.js +11 -3
- package/dist/es/ir/scope.d.ts +3690 -0
- package/dist/es/ir/scope.d.ts.map +1 -0
- package/dist/es/ir/scope.js +89 -0
- package/dist/es/ir/transform.d.ts +204 -0
- package/dist/es/ir/transform.d.ts.map +1 -0
- package/dist/es/ir/transform.js +56 -0
- package/dist/lib/compile/anchor-cache.cjs +41 -0
- package/dist/lib/compile/anchor-cache.d.ts +12 -0
- package/dist/lib/compile/anchor-cache.d.ts.map +1 -0
- package/dist/lib/compile/compile.cjs +150 -40
- package/dist/lib/compile/compile.d.ts +2 -2
- package/dist/lib/compile/compile.d.ts.map +1 -1
- package/dist/lib/compile/index.d.ts +1 -0
- package/dist/lib/compile/index.d.ts.map +1 -1
- package/dist/lib/compile/name-stack.cjs +104 -0
- package/dist/lib/compile/name-stack.d.ts +81 -0
- package/dist/lib/compile/name-stack.d.ts.map +1 -0
- package/dist/lib/compile/node.cjs +19 -11
- package/dist/lib/compile/node.d.ts +9 -5
- package/dist/lib/compile/node.d.ts.map +1 -1
- package/dist/lib/compile/path/anchor.cjs +23 -12
- package/dist/lib/compile/path/anchor.d.ts +11 -5
- package/dist/lib/compile/path/anchor.d.ts.map +1 -1
- package/dist/lib/compile/path/index.cjs +18 -17
- package/dist/lib/compile/path/index.d.ts +9 -3
- package/dist/lib/compile/path/index.d.ts.map +1 -1
- package/dist/lib/compile/path/label.cjs +17 -4
- package/dist/lib/compile/path/label.d.ts +1 -1
- package/dist/lib/compile/path/label.d.ts.map +1 -1
- package/dist/lib/compile/path/relative.cjs +16 -8
- package/dist/lib/compile/path/relative.d.ts +10 -4
- package/dist/lib/compile/path/relative.d.ts.map +1 -1
- package/dist/lib/compile/position.cjs +28 -8
- package/dist/lib/compile/position.d.ts +19 -3
- package/dist/lib/compile/position.d.ts.map +1 -1
- package/dist/lib/compile/scope.cjs +261 -0
- package/dist/lib/compile/scope.d.ts +66 -0
- package/dist/lib/compile/scope.d.ts.map +1 -0
- package/dist/lib/compile/style.cjs +262 -0
- package/dist/lib/compile/style.d.ts +46 -0
- package/dist/lib/compile/style.d.ts.map +1 -0
- package/dist/lib/index.cjs +8 -0
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/ir/index.d.ts +2 -0
- package/dist/lib/ir/index.d.ts.map +1 -1
- package/dist/lib/ir/node.cjs +1 -0
- package/dist/lib/ir/node.d.ts +5 -2
- package/dist/lib/ir/node.d.ts.map +1 -1
- package/dist/lib/ir/path/path.cjs +1 -0
- package/dist/lib/ir/path/path.d.ts +531 -0
- package/dist/lib/ir/path/path.d.ts.map +1 -1
- package/dist/lib/ir/path/step.cjs +5 -1
- package/dist/lib/ir/path/step.d.ts +834 -0
- package/dist/lib/ir/path/step.d.ts.map +1 -1
- package/dist/lib/ir/scene.cjs +11 -3
- package/dist/lib/ir/scene.d.ts +18 -3542
- package/dist/lib/ir/scene.d.ts.map +1 -1
- package/dist/lib/ir/scope.cjs +94 -0
- package/dist/lib/ir/scope.d.ts +3690 -0
- package/dist/lib/ir/scope.d.ts.map +1 -0
- package/dist/lib/ir/transform.cjs +56 -0
- package/dist/lib/ir/transform.d.ts +204 -0
- package/dist/lib/ir/transform.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IRPosition } from '../ir';
|
|
2
|
+
import { NodeLayout } from './node';
|
|
3
|
+
/**
|
|
4
|
+
* 取节点 anchor 的全局坐标,带 per-layout 缓存
|
|
5
|
+
* @description name 接受 RectAnchor 关键字(如 `'north'` / `'south-west'`)或数字角度字符串(如 `'30'` / `'-45'`);
|
|
6
|
+
* 同一 (layout, name) 第二次起返回首调用结果的**同一引用**——上游可用 `===` 判定 cache 命中
|
|
7
|
+
* @param layout 已 Pass 1 完成的 NodeLayout(rect 已是全局坐标)
|
|
8
|
+
* @param anchorName 关键字或数字角度字符串
|
|
9
|
+
* @returns 全局坐标系下的 IRPosition `[x, y]`
|
|
10
|
+
*/
|
|
11
|
+
export declare const resolveAnchor: (layout: NodeLayout, anchorName: string) => IRPosition;
|
|
12
|
+
//# sourceMappingURL=anchor-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-cache.d.ts","sourceRoot":"","sources":["../../../src/compile/anchor-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AA4BzC;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,UAAU,EAClB,YAAY,MAAM,KACjB,UAWF,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { anchorOf, angleBoundaryOf } from "./node.js";
|
|
2
|
+
//#region src/compile/anchor-cache.ts
|
|
3
|
+
/**
|
|
4
|
+
* (layout, anchorName) → IRPosition 缓存
|
|
5
|
+
* @description WeakMap 让 NodeLayout 引用一旦失效(compile 结束、NameStack 回收),对应 Map 自动 GC,无需手动 invalidate
|
|
6
|
+
*/
|
|
7
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
/** 角度字符串识别:可选负号 + 数字 + 可选小数;与 parseTarget.ts 的 ANGLE_RE 同语义 */
|
|
9
|
+
var ANGLE_RE = /^-?\d+(\.\d+)?$/;
|
|
10
|
+
/**
|
|
11
|
+
* 把 anchorName 解析到对应 shape 的 anchor / boundaryPoint 上
|
|
12
|
+
* @description 数字字符串走 angleBoundaryOf;其余按 RectAnchor 走 anchorOf
|
|
13
|
+
*/
|
|
14
|
+
var computeAnchor = (layout, anchorName) => {
|
|
15
|
+
if (ANGLE_RE.test(anchorName)) return positionToIR(angleBoundaryOf(layout, Number(anchorName)));
|
|
16
|
+
return positionToIR(anchorOf(layout, anchorName));
|
|
17
|
+
};
|
|
18
|
+
/** geometry Position(含 readonly 形态)转 IRPosition 元组(IRPosition === [number, number]) */
|
|
19
|
+
var positionToIR = (p) => [p[0], p[1]];
|
|
20
|
+
/**
|
|
21
|
+
* 取节点 anchor 的全局坐标,带 per-layout 缓存
|
|
22
|
+
* @description name 接受 RectAnchor 关键字(如 `'north'` / `'south-west'`)或数字角度字符串(如 `'30'` / `'-45'`);
|
|
23
|
+
* 同一 (layout, name) 第二次起返回首调用结果的**同一引用**——上游可用 `===` 判定 cache 命中
|
|
24
|
+
* @param layout 已 Pass 1 完成的 NodeLayout(rect 已是全局坐标)
|
|
25
|
+
* @param anchorName 关键字或数字角度字符串
|
|
26
|
+
* @returns 全局坐标系下的 IRPosition `[x, y]`
|
|
27
|
+
*/
|
|
28
|
+
var resolveAnchor = (layout, anchorName) => {
|
|
29
|
+
let layoutCache = cache.get(layout);
|
|
30
|
+
if (!layoutCache) {
|
|
31
|
+
layoutCache = /* @__PURE__ */ new Map();
|
|
32
|
+
cache.set(layout, layoutCache);
|
|
33
|
+
}
|
|
34
|
+
const cached = layoutCache.get(anchorName);
|
|
35
|
+
if (cached !== void 0) return cached;
|
|
36
|
+
const result = computeAnchor(layout, anchorName);
|
|
37
|
+
layoutCache.set(anchorName, result);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { resolveAnchor };
|
|
@@ -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
|
|
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>` 推入子 frame;scope.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,
|
|
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;AAoBhF,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,KA+MrE,CAAC"}
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { rect } from "../geometry/rect.js";
|
|
2
|
+
import { NameStack } from "./name-stack.js";
|
|
3
|
+
import { applyTransformChain, computeScopeBoundingBox, lowerScopeTransforms, projectLayoutToGlobal, registerScopeAsLayout } from "./scope.js";
|
|
2
4
|
import { resolvePosition } from "./position.js";
|
|
3
5
|
import { emitNodePrimitives, layoutNode } from "./node.js";
|
|
4
6
|
import { fallbackMeasurer } from "./text-metrics.js";
|
|
5
7
|
import { emitPathPrimitive } from "./path/index.js";
|
|
6
8
|
import { makeRound } from "./precision.js";
|
|
9
|
+
import { buildStyleFrame, resolveEffectivePath, resolveLabelDefault, resolveNodeStyle } from "./style.js";
|
|
7
10
|
import { computeLayout } from "./layout.js";
|
|
8
11
|
//#region src/compile/compile.ts
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @description
|
|
13
|
+
* 构造一个落在指定全局点的 0×0 rectangle NodeLayout
|
|
14
|
+
* @description coordinate / scope.id 入场临时占位等"无形状只有位置"句柄共享此结构,
|
|
15
|
+
* 让后续 path target / `at.of` / `offset.of` / `polar.origin` 引用时 boundaryPoint 命中中心。
|
|
12
16
|
*/
|
|
13
|
-
var
|
|
17
|
+
var zeroSizeRectAt = (id, [cx, cy]) => ({
|
|
14
18
|
id,
|
|
15
19
|
shape: "rectangle",
|
|
16
20
|
rect: {
|
|
17
|
-
x:
|
|
18
|
-
y:
|
|
21
|
+
x: cx,
|
|
22
|
+
y: cy,
|
|
19
23
|
width: 0,
|
|
20
24
|
height: 0,
|
|
21
25
|
rotate: 0
|
|
@@ -29,6 +33,20 @@ var coordinateAsLayout = (id, center) => ({
|
|
|
29
33
|
fontSize: 0
|
|
30
34
|
});
|
|
31
35
|
/**
|
|
36
|
+
* 把 coordinate 注册成 0×0 NodeLayout
|
|
37
|
+
* @description 让后续 path target / `at.of` 引用时 boundaryPoint 命中中心,符合"占位无形状边界"语义
|
|
38
|
+
*/
|
|
39
|
+
var coordinateAsLayout = (id, center) => zeroSizeRectAt(id, center);
|
|
40
|
+
/**
|
|
41
|
+
* scope.id 入场时的临时占位 NodeLayout
|
|
42
|
+
* @description scope 子树尚未处理时先放 0×0 占位(落在 scope 局部原点经累积 chain 投到全局的位置),
|
|
43
|
+
* 让 scope 子树内任何 lookup 不返回 undefined(占位语义自洽)。
|
|
44
|
+
* 子树 Pass 1 处理完毕后由 `registerScopeAsLayout` 算出真 bbox layout 覆盖此占位(NameStack.replaceLayout 不发 duplicate warn)
|
|
45
|
+
*/
|
|
46
|
+
var scopePlaceholderLayout = (id, chain) => {
|
|
47
|
+
return zeroSizeRectAt(id, chain.length === 0 ? [0, 0] : applyTransformChain([0, 0], chain));
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
32
50
|
* 默认 warn dispatcher:dev 模式 console.warn、生产静默
|
|
33
51
|
* @description 用户传 onWarn 时使用用户的;不传走此 fallback
|
|
34
52
|
*/
|
|
@@ -36,9 +54,29 @@ var defaultWarnDispatcher = (warning) => {
|
|
|
36
54
|
if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
|
|
37
55
|
console.warn(`[retikz] ${warning.code} at ${warning.path}: ${warning.message}`);
|
|
38
56
|
};
|
|
57
|
+
/** scope.transforms 解析失败时根据失败成因映射的 warn code */
|
|
58
|
+
var scopeTransformWarnCode = (scope) => {
|
|
59
|
+
for (const t of scope.transforms ?? []) {
|
|
60
|
+
if (t.kind === "offset-translate") return "OFFSET_BASE_UNRESOLVED";
|
|
61
|
+
if (t.kind === "at-translate") return "AT_TARGET_UNRESOLVED";
|
|
62
|
+
if (t.kind === "polar-translate") return "POLAR_ORIGIN_UNRESOLVED";
|
|
63
|
+
}
|
|
64
|
+
return "UNRESOLVED_NODE_REFERENCE";
|
|
65
|
+
};
|
|
66
|
+
/** 把 DuplicateRegisterInfo 翻成 CompileWarning(含可读 message + 双 IR locator) */
|
|
67
|
+
var formatDuplicateWarning = (info) => {
|
|
68
|
+
const frameNote = info.frameDepth === 0 ? "frame depth: 0 (root namespace)" : `frame depth: ${info.frameDepth} (under <Scope localNamespace>)`;
|
|
69
|
+
const firstLoc = info.firstIrPath ?? "(unknown earlier location)";
|
|
70
|
+
const secondLoc = info.secondIrPath ?? "(unknown current location)";
|
|
71
|
+
return {
|
|
72
|
+
code: "DUPLICATE_NODE_ID",
|
|
73
|
+
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).`,
|
|
74
|
+
path: secondLoc
|
|
75
|
+
};
|
|
76
|
+
};
|
|
39
77
|
/**
|
|
40
78
|
* IR → Scene 纯函数转换,所有 adapter 共享
|
|
41
|
-
* @description Pass 1
|
|
79
|
+
* @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>` 推入子 frame;scope.id 始终在父 frame 注册(外部句柄);id lookup 从栈顶向栈底 inside-out 搜索;同 frame 重复 id 触发 DUPLICATE_NODE_ID warn + 后定义覆盖前定义。Pass 2 解析 path 端点写 d 字符串,path primitive 发到 Pass 1 记录的对应容器;末端按 precision 折算 layout
|
|
42
80
|
*/
|
|
43
81
|
var compileToScene = (ir, options = {}) => {
|
|
44
82
|
const measureText = options.measureText ?? fallbackMeasurer;
|
|
@@ -47,46 +85,118 @@ var compileToScene = (ir, options = {}) => {
|
|
|
47
85
|
const nodeDistance = options.nodeDistance;
|
|
48
86
|
const onWarn = options.onWarn ?? defaultWarnDispatcher;
|
|
49
87
|
const primitives = [];
|
|
50
|
-
const
|
|
88
|
+
const nameStack = new NameStack({ onDuplicate: (info) => onWarn(formatDuplicateWarning(info)) });
|
|
51
89
|
const allPoints = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
/**
|
|
91
|
+
* 解析一批本层收集的 pending paths(lookup-only 阶段)
|
|
92
|
+
* @description path primitive 一律 push 到顶层 `primitives` —— 端点已是全局坐标,不能进 GroupPrim 否则被 scope.transform 二次 apply。NameStack 切到 pass2 守门:path 解析中误调 register 抛 internal error;解析完切回 pass1 让上层 scope 子树继续 register 子节点。
|
|
93
|
+
* `item.scopeChain` 记录该 path 所属 scope 累积 transform 链——传给 emitPathPrimitive,
|
|
94
|
+
* 让 step.to 内的 polar/at/offset 字面量按"当前 scope 局部度量 + 末端 apply chain"投影回全局。
|
|
95
|
+
*/
|
|
96
|
+
const resolvePendingPaths = (pending) => {
|
|
97
|
+
if (pending.length === 0) return;
|
|
98
|
+
nameStack.enterLookupPhase();
|
|
99
|
+
try {
|
|
100
|
+
for (const item of pending) {
|
|
101
|
+
const result = emitPathPrimitive(item.path, nameStack, round, measureText, {
|
|
102
|
+
onWarn,
|
|
103
|
+
irPath: item.irPath,
|
|
104
|
+
scopeChain: item.scopeChain
|
|
66
105
|
});
|
|
67
|
-
|
|
106
|
+
if (result) {
|
|
107
|
+
for (const prim of result.primitives) primitives.push(prim);
|
|
108
|
+
for (const p of result.points) allPoints.push(p);
|
|
109
|
+
}
|
|
68
110
|
}
|
|
69
|
-
|
|
111
|
+
} finally {
|
|
112
|
+
nameStack.exitLookupPhase();
|
|
70
113
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* 递归处理一组 IR child,把 node / coordinate 发到 sink、把本层 path 收集到 pathsAccumulator、scope 下沉为 GroupPrim
|
|
117
|
+
* @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 语义。
|
|
118
|
+
* @param children 当前层级的 IR child 数组
|
|
119
|
+
* @param chain 从根到当前层级累积的 Cartesian-only transform 链
|
|
120
|
+
* @param sink 当前层级 Scene primitive 落点(顶层 = primitives,scope 内 = GroupPrim.children)
|
|
121
|
+
* @param locatorPrefix IR locator 前缀(如 `''` 表示顶层、`children[2].scope.` 表示某 scope 内)
|
|
122
|
+
* @param layoutsAccumulator 当前 scope 子树所有"实体"layout(node / coordinate / 嵌套 scope.id synthetic)累积——专给上层 scope.id bbox 计算用;顶层调用传一个共享数组(用得着就用,丢弃也不影响)
|
|
123
|
+
* @param pathsAccumulator 当前层级收集的 pending paths——由调用方分配并在合适时机 resolve
|
|
124
|
+
* @param styleStack 从根到当前层级累积的样式 frame 栈(scope 级联 graphic state + 四通道 every-X + resetStyle);node / path 进入时按 inside-out per-field 解析 effective 样式
|
|
125
|
+
*/
|
|
126
|
+
const processChildren = (children, chain, sink, locatorPrefix, layoutsAccumulator, pathsAccumulator, styleStack) => {
|
|
127
|
+
for (let i = 0; i < children.length; i++) {
|
|
128
|
+
const child = children[i];
|
|
129
|
+
if (child.type === "node") {
|
|
130
|
+
const layout = layoutNode(resolveNodeStyle(child, styleStack), measureText, nameStack, nodeDistance, chain, resolveLabelDefault(styleStack));
|
|
131
|
+
const globalLayout = chain.length === 0 ? layout : projectLayoutToGlobal(layout, chain);
|
|
132
|
+
if (child.id) nameStack.register(child.id, globalLayout, `${locatorPrefix}children[${i}].node.id`);
|
|
133
|
+
for (const prim of emitNodePrimitives(layout, round)) sink.push(prim);
|
|
134
|
+
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"));
|
|
135
|
+
layoutsAccumulator.push(globalLayout);
|
|
136
|
+
} else if (child.type === "coordinate") {
|
|
137
|
+
const localCenter = resolvePosition(child.position, nameStack, nodeDistance, chain);
|
|
138
|
+
if (!localCenter) {
|
|
139
|
+
onWarn({
|
|
140
|
+
code: "POLAR_ORIGIN_UNRESOLVED",
|
|
141
|
+
message: `Cannot resolve position for coordinate '${child.id}'; polar.origin or at.of may reference an undefined node`,
|
|
142
|
+
path: `${locatorPrefix}children[${i}].coordinate.position`
|
|
143
|
+
});
|
|
144
|
+
throw new Error(`Cannot resolve position for coordinate ${child.id}; polar.origin or at.of may reference an undefined node`);
|
|
145
|
+
}
|
|
146
|
+
const globalCenter = chain.length === 0 ? localCenter : applyTransformChain(localCenter, chain);
|
|
147
|
+
const coordLayout = coordinateAsLayout(child.id, globalCenter);
|
|
148
|
+
nameStack.register(child.id, coordLayout, `${locatorPrefix}children[${i}].coordinate.id`);
|
|
149
|
+
layoutsAccumulator.push(coordLayout);
|
|
150
|
+
} else if (child.type === "scope") {
|
|
151
|
+
const loweredOwn = lowerScopeTransforms(child.transforms ?? [], nameStack, nodeDistance);
|
|
152
|
+
if (loweredOwn === null) onWarn({
|
|
153
|
+
code: scopeTransformWarnCode(child),
|
|
154
|
+
message: `Cannot resolve one of scope.transforms; referent (at.of / offset.of / polar.origin) is undefined or defined later in the IR`,
|
|
155
|
+
path: `${locatorPrefix}children[${i}].scope.transforms`
|
|
156
|
+
});
|
|
157
|
+
const ownTransforms = loweredOwn ?? [];
|
|
158
|
+
const innerChain = [...chain, ...ownTransforms];
|
|
159
|
+
const parentFrameDepth = nameStack.depth - 1;
|
|
160
|
+
if (child.id) nameStack.register(child.id, scopePlaceholderLayout(child.id, innerChain), `${locatorPrefix}children[${i}].scope.id`);
|
|
161
|
+
const pushedFrame = child.localNamespace === true;
|
|
162
|
+
if (pushedFrame) nameStack.pushFrame();
|
|
163
|
+
const innerSink = [];
|
|
164
|
+
/** 本 scope 子树的 layouts 累积器;子树结束后用于算 bbox */
|
|
165
|
+
const innerLayouts = [];
|
|
166
|
+
/** 本 scope 子树收集的 pending paths——在 bbox replaceLayout 后 / popFrame 前 resolve,
|
|
167
|
+
* 让 scope 内 path 自引用本 scope.id 端点取真 bbox 而非 placeholder */
|
|
168
|
+
const innerPaths = [];
|
|
169
|
+
try {
|
|
170
|
+
processChildren(child.children, innerChain, innerSink, `${locatorPrefix}children[${i}].scope.`, innerLayouts, innerPaths, [...styleStack, buildStyleFrame(child)]);
|
|
171
|
+
if (child.id) {
|
|
172
|
+
const bbox = computeScopeBoundingBox(innerLayouts);
|
|
173
|
+
const fallbackOrigin = innerChain.length === 0 ? [0, 0] : applyTransformChain([0, 0], innerChain);
|
|
174
|
+
const bboxLayout = registerScopeAsLayout(child.id, bbox, fallbackOrigin);
|
|
175
|
+
nameStack.replaceLayout(child.id, bboxLayout, parentFrameDepth);
|
|
176
|
+
layoutsAccumulator.push(bboxLayout);
|
|
177
|
+
} else for (const innerLayout of innerLayouts) layoutsAccumulator.push(innerLayout);
|
|
178
|
+
resolvePendingPaths(innerPaths);
|
|
179
|
+
} finally {
|
|
180
|
+
if (pushedFrame) nameStack.popFrame();
|
|
181
|
+
}
|
|
182
|
+
const hasOwnTransforms = ownTransforms.length > 0;
|
|
183
|
+
if (innerSink.length === 0 && !hasOwnTransforms && child.id === void 0) continue;
|
|
184
|
+
const group = {
|
|
185
|
+
type: "group",
|
|
186
|
+
children: innerSink
|
|
187
|
+
};
|
|
188
|
+
if (hasOwnTransforms) group.transforms = [...ownTransforms];
|
|
189
|
+
sink.push(group);
|
|
190
|
+
} else pathsAccumulator.push({
|
|
191
|
+
path: resolveEffectivePath(child, styleStack),
|
|
192
|
+
irPath: `${locatorPrefix}children[${i}].path`,
|
|
193
|
+
scopeChain: chain
|
|
83
194
|
});
|
|
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
195
|
}
|
|
89
|
-
}
|
|
196
|
+
};
|
|
197
|
+
const rootPaths = [];
|
|
198
|
+
processChildren(ir.children, [], primitives, "", [], rootPaths, []);
|
|
199
|
+
resolvePendingPaths(rootPaths);
|
|
90
200
|
return {
|
|
91
201
|
primitives,
|
|
92
202
|
layout: computeLayout(allPoints, layoutPadding, round)
|
|
@@ -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,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"}
|
|
@@ -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
|
+
export { NameStack };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Position } from '../geometry/point';
|
|
2
2
|
import { Rect, RectAnchor } from '../geometry/rect';
|
|
3
|
-
import { AtDirection, IRNode, NodeShape } from '../ir';
|
|
4
|
-
import { ScenePrimitive, TextLine } from '../primitive';
|
|
3
|
+
import { AtDirection, IRLabelDefault, IRNode, NodeShape } from '../ir';
|
|
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
|
|
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,
|
|
102
|
+
export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nameStack: NameStack, nodeDistance?: number, scopeChain?: ReadonlyArray<Transform>, labelDefault?: IRLabelDefault) => 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":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,EAAe,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
|