@retikz/core 0.1.0-alpha.0 → 0.1.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.
Files changed (121) hide show
  1. package/dist/es/compile/index.d.ts +1 -0
  2. package/dist/es/compile/index.d.ts.map +1 -1
  3. package/dist/es/compile/node.d.ts +73 -22
  4. package/dist/es/compile/node.d.ts.map +1 -1
  5. package/dist/es/compile/node.js +276 -52
  6. package/dist/es/compile/parseTarget.d.ts +25 -0
  7. package/dist/es/compile/parseTarget.d.ts.map +1 -0
  8. package/dist/es/compile/parseTarget.js +48 -0
  9. package/dist/es/compile/path.d.ts +17 -7
  10. package/dist/es/compile/path.d.ts.map +1 -1
  11. package/dist/es/compile/path.js +276 -33
  12. package/dist/es/compile/text-metrics.d.ts +2 -2
  13. package/dist/es/compile/text-metrics.d.ts.map +1 -1
  14. package/dist/es/geometry/circle.d.ts +32 -0
  15. package/dist/es/geometry/circle.d.ts.map +1 -0
  16. package/dist/es/geometry/circle.js +79 -0
  17. package/dist/es/geometry/diamond.d.ts +44 -0
  18. package/dist/es/geometry/diamond.d.ts.map +1 -0
  19. package/dist/es/geometry/diamond.js +87 -0
  20. package/dist/es/geometry/ellipse.d.ts +38 -0
  21. package/dist/es/geometry/ellipse.d.ts.map +1 -0
  22. package/dist/es/geometry/ellipse.js +86 -0
  23. package/dist/es/geometry/index.d.ts +3 -0
  24. package/dist/es/geometry/index.d.ts.map +1 -1
  25. package/dist/es/index.d.ts +9 -7
  26. package/dist/es/index.d.ts.map +1 -1
  27. package/dist/es/index.js +8 -4
  28. package/dist/es/ir/node.d.ts +319 -7
  29. package/dist/es/ir/node.d.ts.map +1 -1
  30. package/dist/es/ir/node.js +96 -8
  31. package/dist/es/ir/path/arrow.d.ts +26 -0
  32. package/dist/es/ir/path/arrow.d.ts.map +1 -0
  33. package/dist/es/ir/path/arrow.js +25 -0
  34. package/dist/es/ir/path/index.d.ts +1 -0
  35. package/dist/es/ir/path/index.d.ts.map +1 -1
  36. package/dist/es/ir/path/path.d.ts +60 -0
  37. package/dist/es/ir/path/path.d.ts.map +1 -1
  38. package/dist/es/ir/path/path.js +10 -0
  39. package/dist/es/ir/path/step.d.ts +56 -2
  40. package/dist/es/ir/path/step.d.ts.map +1 -1
  41. package/dist/es/ir/path/step.js +17 -2
  42. package/dist/es/ir/scene.d.ts +538 -16
  43. package/dist/es/ir/scene.d.ts.map +1 -1
  44. package/dist/es/parsers/parseWay.d.ts +56 -10
  45. package/dist/es/parsers/parseWay.d.ts.map +1 -1
  46. package/dist/es/parsers/parseWay.js +68 -6
  47. package/dist/es/primitive/ellipse.d.ts +34 -0
  48. package/dist/es/primitive/ellipse.d.ts.map +1 -0
  49. package/dist/es/primitive/index.d.ts +1 -0
  50. package/dist/es/primitive/index.d.ts.map +1 -1
  51. package/dist/es/primitive/path.d.ts +11 -0
  52. package/dist/es/primitive/path.d.ts.map +1 -1
  53. package/dist/es/primitive/rect.d.ts +2 -0
  54. package/dist/es/primitive/rect.d.ts.map +1 -1
  55. package/dist/es/primitive/scene.d.ts +2 -1
  56. package/dist/es/primitive/scene.d.ts.map +1 -1
  57. package/dist/es/primitive/text.d.ts +43 -13
  58. package/dist/es/primitive/text.d.ts.map +1 -1
  59. package/dist/es/types.d.ts +3 -0
  60. package/dist/es/types.d.ts.map +1 -0
  61. package/dist/lib/compile/index.d.ts +1 -0
  62. package/dist/lib/compile/index.d.ts.map +1 -1
  63. package/dist/lib/compile/node.cjs +278 -52
  64. package/dist/lib/compile/node.d.ts +73 -22
  65. package/dist/lib/compile/node.d.ts.map +1 -1
  66. package/dist/lib/compile/parseTarget.cjs +48 -0
  67. package/dist/lib/compile/parseTarget.d.ts +25 -0
  68. package/dist/lib/compile/parseTarget.d.ts.map +1 -0
  69. package/dist/lib/compile/path.cjs +275 -32
  70. package/dist/lib/compile/path.d.ts +17 -7
  71. package/dist/lib/compile/path.d.ts.map +1 -1
  72. package/dist/lib/compile/text-metrics.d.ts +2 -2
  73. package/dist/lib/compile/text-metrics.d.ts.map +1 -1
  74. package/dist/lib/geometry/circle.cjs +79 -0
  75. package/dist/lib/geometry/circle.d.ts +32 -0
  76. package/dist/lib/geometry/circle.d.ts.map +1 -0
  77. package/dist/lib/geometry/diamond.cjs +87 -0
  78. package/dist/lib/geometry/diamond.d.ts +44 -0
  79. package/dist/lib/geometry/diamond.d.ts.map +1 -0
  80. package/dist/lib/geometry/ellipse.cjs +86 -0
  81. package/dist/lib/geometry/ellipse.d.ts +38 -0
  82. package/dist/lib/geometry/ellipse.d.ts.map +1 -0
  83. package/dist/lib/geometry/index.d.ts +3 -0
  84. package/dist/lib/geometry/index.d.ts.map +1 -1
  85. package/dist/lib/index.cjs +16 -0
  86. package/dist/lib/index.d.ts +9 -7
  87. package/dist/lib/index.d.ts.map +1 -1
  88. package/dist/lib/ir/node.cjs +100 -7
  89. package/dist/lib/ir/node.d.ts +319 -7
  90. package/dist/lib/ir/node.d.ts.map +1 -1
  91. package/dist/lib/ir/path/arrow.cjs +25 -0
  92. package/dist/lib/ir/path/arrow.d.ts +26 -0
  93. package/dist/lib/ir/path/arrow.d.ts.map +1 -0
  94. package/dist/lib/ir/path/index.d.ts +1 -0
  95. package/dist/lib/ir/path/index.d.ts.map +1 -1
  96. package/dist/lib/ir/path/path.cjs +10 -0
  97. package/dist/lib/ir/path/path.d.ts +60 -0
  98. package/dist/lib/ir/path/path.d.ts.map +1 -1
  99. package/dist/lib/ir/path/step.cjs +18 -1
  100. package/dist/lib/ir/path/step.d.ts +56 -2
  101. package/dist/lib/ir/path/step.d.ts.map +1 -1
  102. package/dist/lib/ir/scene.d.ts +538 -16
  103. package/dist/lib/ir/scene.d.ts.map +1 -1
  104. package/dist/lib/parsers/parseWay.cjs +68 -5
  105. package/dist/lib/parsers/parseWay.d.ts +56 -10
  106. package/dist/lib/parsers/parseWay.d.ts.map +1 -1
  107. package/dist/lib/primitive/ellipse.d.ts +34 -0
  108. package/dist/lib/primitive/ellipse.d.ts.map +1 -0
  109. package/dist/lib/primitive/index.d.ts +1 -0
  110. package/dist/lib/primitive/index.d.ts.map +1 -1
  111. package/dist/lib/primitive/path.d.ts +11 -0
  112. package/dist/lib/primitive/path.d.ts.map +1 -1
  113. package/dist/lib/primitive/rect.d.ts +2 -0
  114. package/dist/lib/primitive/rect.d.ts.map +1 -1
  115. package/dist/lib/primitive/scene.d.ts +2 -1
  116. package/dist/lib/primitive/scene.d.ts.map +1 -1
  117. package/dist/lib/primitive/text.d.ts +43 -13
  118. package/dist/lib/primitive/text.d.ts.map +1 -1
  119. package/dist/lib/types.d.ts +3 -0
  120. package/dist/lib/types.d.ts.map +1 -0
  121. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  export * from './compile';
2
2
  export * from './node';
3
+ export * from './parseTarget';
3
4
  export * from './path';
4
5
  export * from './position';
5
6
  export * from './precision';
@@ -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,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,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,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC"}
@@ -1,40 +1,90 @@
1
- import { Rect } from '../geometry/rect';
2
- import { IRNode } from '../ir';
3
- import { ScenePrimitive } from '../primitive';
1
+ import { Position } from '../geometry/point';
2
+ import { Rect, RectAnchor } from '../geometry/rect';
3
+ import { IRNode, NodeShape } from '../ir';
4
+ import { ScenePrimitive, TextLine } from '../primitive';
4
5
  import { TextMeasurer } from './text-metrics';
5
6
  export type NodeLayout = {
6
7
  /** 节点 id(如 IR Node 提供);其他位置可通过 id 引用本节点 */
7
8
  id?: string;
8
- /** 节点几何盒(x, y 是几何中心,rotate 是弧度,见 packages/core/AGENTS.md) */
9
+ /** 节点形状——所有几何 / boundaryPoint 计算按 shape 多态 */
10
+ shape: NodeShape;
11
+ /**
12
+ * 节点视觉边界框(所有 shape 共享语义):
13
+ * - rectangle: rect 即矩形本身
14
+ * - circle: rect.width = rect.height = 2 × radius(外接正方形)
15
+ * - ellipse: rect.width = 2 × rx,rect.height = 2 × ry(外接矩形)
16
+ * - diamond: rect.width = 2 × halfA,rect.height = 2 × halfB(外接矩形)
17
+ *
18
+ * x, y 是几何中心;rotate 是弧度(与 packages/core/AGENTS.md 对齐)。
19
+ */
9
20
  rect: Rect;
10
21
  /** IR 中原始的旋转角(度数),保留供 emit 阶段写 SVG transform */
11
22
  rotateDeg: number;
12
- /** 外边距(user units,≥ 0);path 附着到 rect 外扩 margin 的虚拟 attachRect 上 */
23
+ /** 外边距(user units,≥ 0);path 附着到形状外扩 margin 的虚拟边界上 */
13
24
  margin: number;
14
- /** 节点文本内容;空字符串视为无文本(undefined) */
15
- text?: string;
16
- /** 文本宽度(user units),由 TextMeasurer 算出 */
25
+ /**
26
+ * 节点文本行;undefined 表示无文本,否则非空数组。
27
+ * 每行可带覆盖样式(fill / opacity / fontSize / fontFamily / fontWeight / fontStyle);
28
+ * 未覆盖的字段在 emit 阶段不写出,由下游走 TextPrim 块级默认。
29
+ */
30
+ lines?: Array<TextLine>;
31
+ /** 文本块宽度(user units)= max(per-line measureText.width) */
17
32
  textWidth: number;
18
- /** 文本高度(user units),由 TextMeasurer 算出 */
33
+ /** 文本块高度(user units)≈ lines × lineHeight */
19
34
  textHeight: number;
20
- /** 文本字号(user units) */
35
+ /** 文本对齐(已映射到 SVG textAnchor 三态);emit 时透传给 TextPrim.align */
36
+ align: 'start' | 'middle' | 'end';
37
+ /** 行高(user units),已应用默认值;emit 时透传给 TextPrim.lineHeight */
38
+ lineHeight: number;
39
+ /** 文本字号(user units),已应用默认值 */
21
40
  fontSize: number;
22
- /** 节点矩形背景色,CSS 颜色字符串;emit 时用 'transparent' 兜底 */
41
+ /** 字体族;CSS font-family;emit 时透传给 TextPrim */
42
+ fontFamily?: string;
43
+ /** 字重;emit 时透传给 TextPrim */
44
+ fontWeight?: string | number;
45
+ /** 字形:normal / italic / oblique;emit 时透传给 TextPrim */
46
+ fontStyle?: 'normal' | 'italic' | 'oblique';
47
+ /** 节点背景色,CSS 颜色字符串;emit 时用 'transparent' 兜底 */
23
48
  fill?: string;
24
- /** 节点矩形边框色,CSS 颜色字符串;emit 时用 'currentColor' 兜底 */
49
+ /** 节点填充透明度 0~1;透传 shape primitive */
50
+ fillOpacity?: number;
51
+ /** 节点边框色,CSS 颜色字符串;emit 时用 'currentColor' 兜底 */
25
52
  stroke?: string;
26
- /** 节点矩形边框宽度(user units);emit 时用 1 兜底 */
53
+ /** 节点描边透明度 0~1;TikZ `draw opacity`,透传 shape primitive */
54
+ strokeOpacity?: number;
55
+ /** 节点边框宽度(user units);emit 时用 1 兜底 */
27
56
  strokeWidth?: number;
57
+ /** SVG stroke-dasharray 字符串;compile 已把 dashed / dotted 预设解析为具体 pattern */
58
+ strokeDasharray?: string;
59
+ /** rectangle shape 的圆角半径(user units);非 rect shape 该字段无效 */
60
+ roundedCorners?: number;
61
+ /** 文字颜色;emit 时透传给 TextPrim.fill,兜底 'currentColor' */
62
+ textColor?: string;
63
+ /** 整节点透明度 0~1;emit 时同时挂 shape 与 text primitive */
64
+ opacity?: number;
28
65
  };
29
66
  /**
30
- * 取节点的"附着 rect"——用于 path 端点贴边的几何盒。
31
- * 在视觉 rect 基础上每边外扩 margin,保持中心与旋转不变。
32
- * margin = 0 时直接返回视觉 rect 自身(避免无意义复制)。
67
+ * 取节点 shape 在 toward 方向上的"附着点"——path 端点贴边用。
68
+ * shape 多态:rect / circle / ellipse / diamond 各自的 boundaryPoint。
69
+ * margin > 0 时形状先外扩,让 path 在 border 外停 margin 个 user units。
33
70
  */
34
- export declare const attachRectOf: (layout: NodeLayout) => Rect;
71
+ export declare const boundaryPointOf: (layout: NodeLayout, toward: Position) => Position;
72
+ /**
73
+ * 取节点 shape 的命名 anchor(center / north / east / north-east 等 9 个)。
74
+ * **不应用 margin**——TikZ 语义中 explicit anchor 取的是视觉边界点,不涉及 outer sep。
75
+ * 用于 `'A.north'` 这种语法落点。
76
+ */
77
+ export declare const anchorOf: (layout: NodeLayout, name: RectAnchor) => Position;
78
+ /**
79
+ * 取节点 shape 在指定角度方向上的边界点。角度约定与 PolarPosition 一致(度数):
80
+ * 0° = +x(east),90° = +y(screen 下方)。
81
+ * **不应用 margin**——同 anchorOf。用于 `'A.30'` 这种语法落点。
82
+ */
83
+ export declare const angleBoundaryOf: (layout: NodeLayout, angleDeg: number) => Position;
35
84
  /**
36
85
  * 把 IR Node 解析为内部 NodeLayout:
37
- * - 算出文本度量与 padding 推导出的视觉 rect 尺寸
86
+ * - 算出文本度量与 padding 推导出"内框"半轴 (innerHalfW/H)
87
+ * - 按 shape 决定外接边界尺寸(circle 取半对角线、ellipse 各 ×√2、diamond 各 ×2)
38
88
  * - 解析 position(笛卡尔或极坐标)为几何中心
39
89
  * - IR 的 rotate(度数)转弧度存进 Rect.rotate
40
90
  * - 透传 margin / 样式属性
@@ -42,10 +92,11 @@ export declare const attachRectOf: (layout: NodeLayout) => Rect;
42
92
  export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nodeIndex: Map<string, NodeLayout>) => NodeLayout;
43
93
  /**
44
94
  * 把 NodeLayout 翻译为 Scene primitives:
45
- * - rect(背景边框;x/y 转为 SVG 风格的左上角)
46
- * - text(如有内容)
47
- * - 若有旋转,外面套一层 GroupPrim 用 SVG `rotate(deg cx cy)` 实现,
48
- * 内层 RectPrim/TextPrim 自身保持轴对齐
95
+ * - shape 主体:按 shape 分发(rect / ellipse / path)
96
+ * - text(如有内容):始终走 TextPrim
97
+ * - 若有旋转:外面套一层 GroupPrim 用 SVG `rotate(deg cx cy)` 实现
98
+ * (PathPrim diamond 顶点已自带旋转坐标,但 text 需要 group 旋转,
99
+ * 所以仍统一用 group 包裹)
49
100
  */
50
101
  export declare const emitNodePrimitives: (layout: NodeLayout, round: (n: number) => number) => Array<ScenePrimitive>;
51
102
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAMnD,MAAM,MAAM,UAAU,GAAG;IACvB,2CAA2C;IAC3C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,IAAI,EAAE,IAAI,CAAC;IACX,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,UAAU,KAAG,IASjD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,aAAa,YAAY,EACzB,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,KACjC,UAqCF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAC3B,KAAK,CAAC,cAAc,CAuCtB,CAAC"}
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,EAAc,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8BnD,MAAM,MAAM,UAAU,GAAG;IACvB,2CAA2C;IAC3C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;;;;OAQG;IACH,IAAI,EAAE,IAAI,CAAC;IACX,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAClC,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,sDAAsD;IACtD,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC5C,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAsCF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,KAAG,QAYtE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,UAAU,EAAE,MAAM,UAAU,KAAG,QAW/D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,UAAU,EAAE,UAAU,MAAM,KAAG,QActE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,aAAa,YAAY,EACzB,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,KACjC,UAgJF,CAAC;AAsEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAC3B,KAAK,CAAC,cAAc,CAoDtB,CAAC"}
@@ -1,94 +1,310 @@
1
+ import { rect } from "../geometry/rect.js";
2
+ import { circle } from "../geometry/circle.js";
3
+ import { diamond } from "../geometry/diamond.js";
4
+ import { ellipse } from "../geometry/ellipse.js";
1
5
  import { resolvePosition } from "./position.js";
2
6
  //#region src/compile/node.ts
3
7
  var DEFAULT_FONT_SIZE = 14;
4
8
  var DEFAULT_PADDING = 8;
9
+ var DEFAULT_LINE_HEIGHT_FACTOR = 1.2;
5
10
  var DEG_TO_RAD = Math.PI / 180;
11
+ var SQRT2 = Math.SQRT2;
12
+ /** dashed 预设:SVG stroke-dasharray "4 2"——4 px 实线 + 2 px 间隙循环 */
13
+ var DASHED_PATTERN = "4 2";
14
+ /** dotted 预设:SVG stroke-dasharray "1 2"——1 px 圆点 + 2 px 间隙 */
15
+ var DOTTED_PATTERN = "1 2";
16
+ /** 解析 dashed / dotted / dashArray 优先级:dashArray > dashed > dotted */
17
+ var resolveDashArray = (dashArray, dashed, dotted) => {
18
+ if (dashArray !== void 0) return dashArray;
19
+ if (dashed) return DASHED_PATTERN;
20
+ if (dotted) return DOTTED_PATTERN;
21
+ };
22
+ /** IR `align` ('left' | 'center' | 'right') → SVG textAnchor ('start' | 'middle' | 'end') */
23
+ var alignToTextAnchor = (a) => a === "left" ? "start" : a === "right" ? "end" : "middle";
24
+ /** 由 layout 构造的 Rect(带 margin 扩张) */
25
+ var rectOf = (layout, marginAdd) => ({
26
+ x: layout.rect.x,
27
+ y: layout.rect.y,
28
+ width: layout.rect.width + 2 * marginAdd,
29
+ height: layout.rect.height + 2 * marginAdd,
30
+ rotate: layout.rect.rotate
31
+ });
32
+ /** 由 layout 构造的 Circle(圆心 + 半径,半径=外接框边长/2 + margin) */
33
+ var circleOf = (layout, marginAdd) => ({
34
+ x: layout.rect.x,
35
+ y: layout.rect.y,
36
+ radius: layout.rect.width / 2 + marginAdd,
37
+ rotate: layout.rect.rotate
38
+ });
39
+ /** 由 layout 构造的 Ellipse(rx/ry 各加 margin) */
40
+ var ellipseOf = (layout, marginAdd) => ({
41
+ x: layout.rect.x,
42
+ y: layout.rect.y,
43
+ rx: layout.rect.width / 2 + marginAdd,
44
+ ry: layout.rect.height / 2 + marginAdd,
45
+ rotate: layout.rect.rotate
46
+ });
47
+ /** 由 layout 构造的 Diamond(halfA/halfB 各加 margin) */
48
+ var diamondOf = (layout, marginAdd) => ({
49
+ x: layout.rect.x,
50
+ y: layout.rect.y,
51
+ halfA: layout.rect.width / 2 + marginAdd,
52
+ halfB: layout.rect.height / 2 + marginAdd,
53
+ rotate: layout.rect.rotate
54
+ });
6
55
  /**
7
- * 取节点的"附着 rect"——用于 path 端点贴边的几何盒。
8
- * 在视觉 rect 基础上每边外扩 margin,保持中心与旋转不变。
9
- * margin = 0 时直接返回视觉 rect 自身(避免无意义复制)。
56
+ * 取节点 shape 在 toward 方向上的"附着点"——path 端点贴边用。
57
+ * shape 多态:rect / circle / ellipse / diamond 各自的 boundaryPoint。
58
+ * margin > 0 时形状先外扩,让 path 在 border 外停 margin 个 user units。
10
59
  */
11
- var attachRectOf = (layout) => {
12
- if (layout.margin === 0) return layout.rect;
13
- return {
14
- x: layout.rect.x,
15
- y: layout.rect.y,
16
- width: layout.rect.width + 2 * layout.margin,
17
- height: layout.rect.height + 2 * layout.margin,
18
- rotate: layout.rect.rotate
19
- };
60
+ var boundaryPointOf = (layout, toward) => {
61
+ const m = layout.margin;
62
+ switch (layout.shape) {
63
+ case "rectangle": return rect.boundaryPoint(rectOf(layout, m), toward);
64
+ case "circle": return circle.boundaryPoint(circleOf(layout, m), toward);
65
+ case "ellipse": return ellipse.boundaryPoint(ellipseOf(layout, m), toward);
66
+ case "diamond": return diamond.boundaryPoint(diamondOf(layout, m), toward);
67
+ }
68
+ };
69
+ /**
70
+ * 取节点 shape 的命名 anchor(center / north / east / north-east 等 9 个)。
71
+ * **不应用 margin**——TikZ 语义中 explicit anchor 取的是视觉边界点,不涉及 outer sep。
72
+ * 用于 `'A.north'` 这种语法落点。
73
+ */
74
+ var anchorOf = (layout, name) => {
75
+ switch (layout.shape) {
76
+ case "rectangle": return rect.anchor(rectOf(layout, 0), name);
77
+ case "circle": return circle.anchor(circleOf(layout, 0), name);
78
+ case "ellipse": return ellipse.anchor(ellipseOf(layout, 0), name);
79
+ case "diamond": return diamond.anchor(diamondOf(layout, 0), name);
80
+ }
81
+ };
82
+ /**
83
+ * 取节点 shape 在指定角度方向上的边界点。角度约定与 PolarPosition 一致(度数):
84
+ * 0° = +x(east),90° = +y(screen 下方)。
85
+ * **不应用 margin**——同 anchorOf。用于 `'A.30'` 这种语法落点。
86
+ */
87
+ var angleBoundaryOf = (layout, angleDeg) => {
88
+ const rad = angleDeg * Math.PI / 180;
89
+ const toward = [layout.rect.x + Math.cos(rad), layout.rect.y + Math.sin(rad)];
90
+ switch (layout.shape) {
91
+ case "rectangle": return rect.boundaryPoint(rectOf(layout, 0), toward);
92
+ case "circle": return circle.boundaryPoint(circleOf(layout, 0), toward);
93
+ case "ellipse": return ellipse.boundaryPoint(ellipseOf(layout, 0), toward);
94
+ case "diamond": return diamond.boundaryPoint(diamondOf(layout, 0), toward);
95
+ }
20
96
  };
21
97
  /**
22
98
  * 把 IR Node 解析为内部 NodeLayout:
23
- * - 算出文本度量与 padding 推导出的视觉 rect 尺寸
99
+ * - 算出文本度量与 padding 推导出"内框"半轴 (innerHalfW/H)
100
+ * - 按 shape 决定外接边界尺寸(circle 取半对角线、ellipse 各 ×√2、diamond 各 ×2)
24
101
  * - 解析 position(笛卡尔或极坐标)为几何中心
25
102
  * - IR 的 rotate(度数)转弧度存进 Rect.rotate
26
103
  * - 透传 margin / 样式属性
27
104
  */
28
105
  var layoutNode = (node, measureText, nodeIndex) => {
29
- const fontSize = node.fontSize ?? DEFAULT_FONT_SIZE;
30
- const padding = node.padding ?? DEFAULT_PADDING;
31
- const metrics = node.text ? measureText(node.text, { size: fontSize }) : {
32
- width: 0,
33
- height: 0
34
- };
35
- const width = Math.max(metrics.width + padding * 2, padding * 2);
36
- const height = Math.max(metrics.height + padding * 2, padding * 2);
106
+ const sx = node.xScale ?? node.scale ?? 1;
107
+ const sy = node.yScale ?? node.scale ?? 1;
108
+ const fontScale = Math.min(sx, sy);
109
+ const baseFontSize = node.font?.size ?? DEFAULT_FONT_SIZE;
110
+ const fontSize = baseFontSize * fontScale;
111
+ const fontFamily = node.font?.family;
112
+ const fontWeight = node.font?.weight;
113
+ const fontStyle = node.font?.style;
114
+ const xSep = (node.innerXSep ?? node.padding ?? DEFAULT_PADDING) * sx;
115
+ const ySep = (node.innerYSep ?? node.padding ?? DEFAULT_PADDING) * sy;
116
+ const outerSep = (node.outerSep ?? node.margin ?? 0) * Math.max(sx, sy);
117
+ const lineHeight = (node.lineHeight ?? baseFontSize * DEFAULT_LINE_HEIGHT_FACTOR) * sy;
118
+ const align = alignToTextAnchor(node.align ?? "center");
119
+ const rawLines = node.text === void 0 ? void 0 : typeof node.text === "string" ? [node.text] : node.text;
120
+ let textWidth = 0;
121
+ let textHeight = 0;
122
+ let lines;
123
+ if (rawLines) {
124
+ lines = rawLines.map((spec) => {
125
+ const isObj = typeof spec !== "string";
126
+ const text = isObj ? spec.text : spec;
127
+ const lineFont = isObj ? spec.font : void 0;
128
+ const m = measureText(text, {
129
+ size: lineFont?.size ?? fontSize,
130
+ family: lineFont?.family ?? fontFamily,
131
+ weight: lineFont?.weight ?? fontWeight,
132
+ style: lineFont?.style ?? fontStyle
133
+ });
134
+ if (m.width > textWidth) textWidth = m.width;
135
+ const out = { text };
136
+ if (isObj) {
137
+ if (spec.fill !== void 0) out.fill = spec.fill;
138
+ if (spec.opacity !== void 0) out.opacity = spec.opacity;
139
+ if (lineFont?.size !== void 0) out.fontSize = lineFont.size;
140
+ if (lineFont?.family !== void 0) out.fontFamily = lineFont.family;
141
+ if (lineFont?.weight !== void 0) out.fontWeight = lineFont.weight;
142
+ if (lineFont?.style !== void 0) out.fontStyle = lineFont.style;
143
+ }
144
+ return out;
145
+ });
146
+ textHeight = lines.length * lineHeight;
147
+ }
148
+ const minW = node.minimumWidth ?? node.minimumSize ?? 0;
149
+ const minH = node.minimumHeight ?? node.minimumSize ?? 0;
150
+ const innerHalfW = Math.max(textWidth / 2 + xSep, xSep, minW / 2);
151
+ const innerHalfH = Math.max(textHeight / 2 + ySep, ySep, minH / 2);
152
+ const shape = node.shape ?? "rectangle";
153
+ let boundsHalfW;
154
+ let boundsHalfH;
155
+ switch (shape) {
156
+ case "rectangle":
157
+ boundsHalfW = innerHalfW;
158
+ boundsHalfH = innerHalfH;
159
+ break;
160
+ case "circle": {
161
+ const r = Math.sqrt(innerHalfW * innerHalfW + innerHalfH * innerHalfH);
162
+ boundsHalfW = r;
163
+ boundsHalfH = r;
164
+ break;
165
+ }
166
+ case "ellipse":
167
+ boundsHalfW = innerHalfW * SQRT2;
168
+ boundsHalfH = innerHalfH * SQRT2;
169
+ break;
170
+ case "diamond":
171
+ boundsHalfW = innerHalfW * 2;
172
+ boundsHalfH = innerHalfH * 2;
173
+ break;
174
+ }
37
175
  const rotateDeg = node.rotate ?? 0;
38
176
  const center = resolvePosition(node.position, nodeIndex);
39
177
  if (!center) throw new Error(`Cannot resolve position for node ${node.id ?? "(unnamed)"}; polar.origin may reference an undefined node`);
40
178
  return {
41
179
  id: node.id,
180
+ shape,
42
181
  rect: {
43
182
  x: center[0],
44
183
  y: center[1],
45
- width,
46
- height,
184
+ width: 2 * boundsHalfW,
185
+ height: 2 * boundsHalfH,
47
186
  rotate: rotateDeg * DEG_TO_RAD
48
187
  },
49
188
  rotateDeg,
50
- margin: node.margin ?? 0,
51
- text: node.text,
52
- textWidth: metrics.width,
53
- textHeight: metrics.height,
189
+ margin: outerSep,
190
+ lines,
191
+ textWidth,
192
+ textHeight,
193
+ align,
194
+ lineHeight,
54
195
  fontSize,
196
+ fontFamily,
197
+ fontWeight,
198
+ fontStyle,
55
199
  fill: node.fill,
200
+ fillOpacity: node.fillOpacity,
56
201
  stroke: node.stroke,
57
- strokeWidth: node.strokeWidth
202
+ strokeOpacity: node.drawOpacity,
203
+ strokeWidth: node.strokeWidth,
204
+ strokeDasharray: resolveDashArray(node.dashArray, node.dashed, node.dotted),
205
+ roundedCorners: node.roundedCorners,
206
+ textColor: node.textColor,
207
+ opacity: node.opacity
58
208
  };
59
209
  };
60
- /**
61
- * NodeLayout 翻译为 Scene primitives:
62
- * - rect(背景边框;x/y 转为 SVG 风格的左上角)
63
- * - text(如有内容)
64
- * - 若有旋转,外面套一层 GroupPrim 用 SVG `rotate(deg cx cy)` 实现,
65
- * 内层 RectPrim/TextPrim 自身保持轴对齐
66
- */
67
- var emitNodePrimitives = (layout, round) => {
210
+ /** rectangle shape 的 RectPrim */
211
+ var emitRectShape = (layout, round) => {
68
212
  const halfW = layout.rect.width / 2;
69
213
  const halfH = layout.rect.height / 2;
70
- const inner = [{
214
+ return {
71
215
  type: "rect",
72
216
  x: round(layout.rect.x - halfW),
73
217
  y: round(layout.rect.y - halfH),
74
218
  width: round(layout.rect.width),
75
219
  height: round(layout.rect.height),
76
220
  fill: layout.fill ?? "transparent",
221
+ fillOpacity: layout.fillOpacity,
77
222
  stroke: layout.stroke ?? "currentColor",
78
- strokeWidth: layout.strokeWidth ?? 1
79
- }];
80
- if (layout.text) inner.push({
81
- type: "text",
82
- x: round(layout.rect.x),
83
- y: round(layout.rect.y),
84
- content: layout.text,
85
- fontSize: layout.fontSize,
86
- align: "middle",
87
- baseline: "middle",
88
- fill: "currentColor",
89
- measuredWidth: round(layout.textWidth),
90
- measuredHeight: round(layout.textHeight)
91
- });
223
+ strokeOpacity: layout.strokeOpacity,
224
+ strokeWidth: layout.strokeWidth ?? 1,
225
+ strokeDasharray: layout.strokeDasharray,
226
+ cornerRadius: layout.roundedCorners,
227
+ opacity: layout.opacity
228
+ };
229
+ };
230
+ /** circle / ellipse shape 的 EllipsePrim(圆形 rx=ry) */
231
+ var emitEllipseShape = (layout, round) => ({
232
+ type: "ellipse",
233
+ cx: round(layout.rect.x),
234
+ cy: round(layout.rect.y),
235
+ rx: round(layout.rect.width / 2),
236
+ ry: round(layout.rect.height / 2),
237
+ fill: layout.fill ?? "transparent",
238
+ fillOpacity: layout.fillOpacity,
239
+ stroke: layout.stroke ?? "currentColor",
240
+ strokeOpacity: layout.strokeOpacity,
241
+ strokeWidth: layout.strokeWidth ?? 1,
242
+ strokeDasharray: layout.strokeDasharray,
243
+ opacity: layout.opacity
244
+ });
245
+ /** diamond shape 的 PathPrim(4 顶点 + Z 闭合) */
246
+ var emitDiamondShape = (layout, round) => {
247
+ const diam = diamondOf(layout, 0);
248
+ const e = diamond.anchor(diam, "east");
249
+ const n = diamond.anchor(diam, "north");
250
+ const w = diamond.anchor(diam, "west");
251
+ const s = diamond.anchor(diam, "south");
252
+ return {
253
+ type: "path",
254
+ d: `M ${round(e[0])} ${round(e[1])} L ${round(n[0])} ${round(n[1])} L ${round(w[0])} ${round(w[1])} L ${round(s[0])} ${round(s[1])} Z`,
255
+ fill: layout.fill ?? "transparent",
256
+ fillOpacity: layout.fillOpacity,
257
+ stroke: layout.stroke ?? "currentColor",
258
+ strokeOpacity: layout.strokeOpacity,
259
+ strokeWidth: layout.strokeWidth ?? 1,
260
+ strokeDasharray: layout.strokeDasharray,
261
+ opacity: layout.opacity
262
+ };
263
+ };
264
+ /**
265
+ * 把 NodeLayout 翻译为 Scene primitives:
266
+ * - shape 主体:按 shape 分发(rect / ellipse / path)
267
+ * - text(如有内容):始终走 TextPrim
268
+ * - 若有旋转:外面套一层 GroupPrim 用 SVG `rotate(deg cx cy)` 实现
269
+ * (PathPrim 的 diamond 顶点已自带旋转坐标,但 text 需要 group 旋转,
270
+ * 所以仍统一用 group 包裹)
271
+ */
272
+ var emitNodePrimitives = (layout, round) => {
273
+ let shapePrim;
274
+ switch (layout.shape) {
275
+ case "rectangle":
276
+ shapePrim = emitRectShape(layout, round);
277
+ break;
278
+ case "circle":
279
+ case "ellipse":
280
+ shapePrim = emitEllipseShape(layout, round);
281
+ break;
282
+ case "diamond":
283
+ shapePrim = emitDiamondShape(unrotated(layout), round);
284
+ break;
285
+ }
286
+ const inner = [shapePrim];
287
+ if (layout.lines) {
288
+ const halfBlockW = layout.textWidth / 2;
289
+ const xOffset = layout.align === "start" ? -halfBlockW : layout.align === "end" ? halfBlockW : 0;
290
+ inner.push({
291
+ type: "text",
292
+ x: round(layout.rect.x + xOffset),
293
+ y: round(layout.rect.y),
294
+ lines: layout.lines,
295
+ fontSize: layout.fontSize,
296
+ fontFamily: layout.fontFamily,
297
+ fontWeight: layout.fontWeight,
298
+ fontStyle: layout.fontStyle,
299
+ align: layout.align,
300
+ baseline: "middle",
301
+ lineHeight: round(layout.lineHeight),
302
+ fill: layout.textColor ?? "currentColor",
303
+ opacity: layout.opacity,
304
+ measuredWidth: round(layout.textWidth),
305
+ measuredHeight: round(layout.textHeight)
306
+ });
307
+ }
92
308
  if (layout.rotateDeg === 0) return inner;
93
309
  return [{
94
310
  type: "group",
@@ -96,5 +312,13 @@ var emitNodePrimitives = (layout, round) => {
96
312
  children: inner
97
313
  }];
98
314
  };
315
+ /** 返回 layout 的"未旋转"副本——用于先把 diamond 顶点按未旋转算,再由外层 group 统一旋转 */
316
+ var unrotated = (layout) => ({
317
+ ...layout,
318
+ rect: {
319
+ ...layout.rect,
320
+ rotate: 0
321
+ }
322
+ });
99
323
  //#endregion
100
- export { attachRectOf, emitNodePrimitives, layoutNode };
324
+ export { anchorOf, angleBoundaryOf, boundaryPointOf, emitNodePrimitives, layoutNode };
@@ -0,0 +1,25 @@
1
+ import { RectAnchor } from '../geometry/rect';
2
+ /** 解析后的节点 ref 三态 */
3
+ export type ParsedNodeRef = {
4
+ kind: 'node';
5
+ id: string;
6
+ } | {
7
+ kind: 'anchor';
8
+ id: string;
9
+ anchor: RectAnchor;
10
+ } | {
11
+ kind: 'angle';
12
+ id: string;
13
+ angle: number;
14
+ };
15
+ /**
16
+ * 解析节点 ref 字符串。
17
+ *
18
+ * - 不带 `.`:node(auto-clip)
19
+ * - 带 `.` 后纯数字:angle(toward 方向上的边界点,与 PolarPosition 同角度约定)
20
+ * - 带 `.` 后字母 / 连字符:anchor(命中 ANCHOR_NAMES 才合法,否则抛错)
21
+ *
22
+ * 抛错路径:未知 anchor 名(避免静默吞掉拼写错误)。
23
+ */
24
+ export declare const parseNodeRef: (s: string) => ParsedNodeRef;
25
+ //# sourceMappingURL=parseTarget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseTarget.d.ts","sourceRoot":"","sources":["../../../src/compile/parseTarget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQjE,oBAAoB;AACpB,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,aAcxC,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { RECT_ANCHORS } from "../geometry/rect.js";
2
+ //#region src/compile/parseTarget.ts
3
+ /**
4
+ * 节点 ref 字符串扩展语法(ADR-0004):
5
+ *
6
+ * `'A'` → 节点 A,由 path 自动 boundary clip(auto 模式)
7
+ * `'A.<name>'` → 节点 A 的命名 anchor(center/north/south/east/west/north-east/...)
8
+ * `'A.<deg>'` → 节点 A 在 degree 角度方向上的边界点(同 PolarPosition 角度约定)
9
+ *
10
+ * id 约束:`[A-Za-z_][\w-]*`——禁数字开头、禁含 `.`(与 anchor 分隔符冲突);
11
+ * anchor 名只接受 alpha.1 首批 9 个 RECT_ANCHORS(其余如 'text' / 'base' / 'mid'
12
+ * 留 alpha.2 + 字体改造时再支持);角度纯数字(含可选 `.`/`-`)。
13
+ */
14
+ /** alpha.1 支持的 anchor 名集合(RECT_ANCHORS 的 9 个值) */
15
+ var ANCHOR_NAMES = new Set(Object.values(RECT_ANCHORS));
16
+ /** 纯数字(可选小数 / 负号),用于识别 `A.30` / `A.-45` / `A.180.5` */
17
+ var ANGLE_RE = /^-?\d+(\.\d+)?$/;
18
+ /**
19
+ * 解析节点 ref 字符串。
20
+ *
21
+ * - 不带 `.`:node(auto-clip)
22
+ * - 带 `.` 后纯数字:angle(toward 方向上的边界点,与 PolarPosition 同角度约定)
23
+ * - 带 `.` 后字母 / 连字符:anchor(命中 ANCHOR_NAMES 才合法,否则抛错)
24
+ *
25
+ * 抛错路径:未知 anchor 名(避免静默吞掉拼写错误)。
26
+ */
27
+ var parseNodeRef = (s) => {
28
+ const dot = s.indexOf(".");
29
+ if (dot < 0) return {
30
+ kind: "node",
31
+ id: s
32
+ };
33
+ const id = s.slice(0, dot);
34
+ const tail = s.slice(dot + 1);
35
+ if (ANGLE_RE.test(tail)) return {
36
+ kind: "angle",
37
+ id,
38
+ angle: Number(tail)
39
+ };
40
+ if (!ANCHOR_NAMES.has(tail)) throw new Error(`parseNodeRef: unknown anchor '${tail}' in '${s}' (alpha.1 supports: ${[...ANCHOR_NAMES].join(", ")})`);
41
+ return {
42
+ kind: "anchor",
43
+ id,
44
+ anchor: tail
45
+ };
46
+ };
47
+ //#endregion
48
+ export { parseNodeRef };
@@ -3,14 +3,24 @@ import { ScenePrimitive } from '../primitive';
3
3
  import { NodeLayout } from './node';
4
4
  /**
5
5
  * 把 IR Path 翻译为单个 PathPrim。
6
- * 算法:
7
- * 1. 第一遍:把每个 step.to 解析为"参考点"(节点中心 / 直接坐标 / 极坐标解算后),
8
- * 用于给邻居 step 算节点边界点的方向;
9
- * 2. 第二遍:算每个 step 的实际终点——节点引用调用 boundaryPoint 贴 attachRect;
10
- * 其他形态直接用解析后的笛卡尔。
11
- * 3. 把点序列写成 SVG path d 字符串。
12
6
  *
13
- * 引用未定义节点时返回 null(path 整体跳过)。
7
+ * 关键算法(v0.1.0-alpha.1):每个绘制段(line / fold)**独立**地用节点中心
8
+ * 算两端 boundary clip——一个节点在路径中段时,"入边"和"出边" boundary 点
9
+ * 通常不同,路径会在该节点处可见地"断开"。这与 TikZ 原生语义一致:
10
+ *
11
+ * `\draw (A) -- (B) -- (C);`
12
+ * 段 1:A.center → B.center 决定 A 出口、B 入口的 boundary 交点
13
+ * 段 2:B.center → C.center 决定 B 出口、C 入口的 boundary 交点
14
+ * B 在两段里 clip 出来的点不同——视觉上看到两条独立线段。
15
+ *
16
+ * 实现上仍只产一个 PathPrim:d 字符串里以多组 `M ... L ...` 表达多个 sub-path。
17
+ * 当某段起点恰好等于上一段终点(例如直接坐标连续,或未触发 clip 差异)时,
18
+ * 复用 cursor,省掉冗余 M。
19
+ *
20
+ * cycle 段:闭回最近一次 move 起点。若 cycle 起点 == lastEnd 且终点 == subPathStart,
21
+ * 输出 `Z`(最优雅);否则显式画一段 line(与"段独立 clip"一致)。
22
+ *
23
+ * 引用未定义节点 / 解析失败时返回 null(path 整体跳过)。
14
24
  */
15
25
  export declare const emitPathPrimitive: (path: IRPath, nodeIndex: Map<string, NodeLayout>, round: (n: number) => number) => {
16
26
  primitive: ScenePrimitive;
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/compile/path.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAY,MAAM,OAAO,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,QAAQ,CAAC;AAcvD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAClC,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAC3B;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IA6C7D,CAAC"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/compile/path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,MAAM,EAAE,UAAU,EAAoB,MAAM,OAAO,CAAC;AAC9E,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,KAAK,UAAU,EAA8C,MAAM,QAAQ,CAAC;AAuIrF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAClC,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAC3B;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IAsN7D,CAAC"}