@retikz/core 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (119) hide show
  1. package/dist/es/compile/compile.d.ts +6 -0
  2. package/dist/es/compile/compile.d.ts.map +1 -1
  3. package/dist/es/compile/compile.js +33 -4
  4. package/dist/es/compile/node.d.ts +23 -2
  5. package/dist/es/compile/node.d.ts.map +1 -1
  6. package/dist/es/compile/node.js +94 -4
  7. package/dist/es/compile/path.d.ts +3 -2
  8. package/dist/es/compile/path.d.ts.map +1 -1
  9. package/dist/es/compile/path.js +333 -14
  10. package/dist/es/compile/position.d.ts +8 -5
  11. package/dist/es/compile/position.d.ts.map +1 -1
  12. package/dist/es/compile/position.js +32 -5
  13. package/dist/es/geometry/arc.d.ts +34 -0
  14. package/dist/es/geometry/arc.d.ts.map +1 -0
  15. package/dist/es/geometry/arc.js +53 -0
  16. package/dist/es/geometry/bend.d.ts +18 -0
  17. package/dist/es/geometry/bend.d.ts.map +1 -0
  18. package/dist/es/geometry/bend.js +29 -0
  19. package/dist/es/geometry/index.d.ts +3 -0
  20. package/dist/es/geometry/index.d.ts.map +1 -1
  21. package/dist/es/geometry/segment.d.ts +38 -0
  22. package/dist/es/geometry/segment.d.ts.map +1 -0
  23. package/dist/es/geometry/segment.js +82 -0
  24. package/dist/es/index.d.ts +4 -4
  25. package/dist/es/index.d.ts.map +1 -1
  26. package/dist/es/index.js +7 -4
  27. package/dist/es/ir/coordinate.d.ts +57 -0
  28. package/dist/es/ir/coordinate.d.ts.map +1 -0
  29. package/dist/es/ir/coordinate.js +27 -0
  30. package/dist/es/ir/index.d.ts +1 -0
  31. package/dist/es/ir/index.d.ts.map +1 -1
  32. package/dist/es/ir/node.d.ts +276 -17
  33. package/dist/es/ir/node.d.ts.map +1 -1
  34. package/dist/es/ir/node.js +28 -3
  35. package/dist/es/ir/path/path.d.ts +625 -15
  36. package/dist/es/ir/path/path.d.ts.map +1 -1
  37. package/dist/es/ir/path/path.js +22 -0
  38. package/dist/es/ir/path/step.d.ts +872 -20
  39. package/dist/es/ir/path/step.d.ts.map +1 -1
  40. package/dist/es/ir/path/step.js +86 -4
  41. package/dist/es/ir/path/target.d.ts +32 -2
  42. package/dist/es/ir/path/target.d.ts.map +1 -1
  43. package/dist/es/ir/path/target.js +7 -3
  44. package/dist/es/ir/position/at-position.d.ts +50 -0
  45. package/dist/es/ir/position/at-position.d.ts.map +1 -0
  46. package/dist/es/ir/position/at-position.js +30 -0
  47. package/dist/es/ir/position/index.d.ts +1 -0
  48. package/dist/es/ir/position/index.d.ts.map +1 -1
  49. package/dist/es/ir/scene.d.ts +2072 -112
  50. package/dist/es/ir/scene.d.ts.map +1 -1
  51. package/dist/es/ir/scene.js +6 -1
  52. package/dist/es/parsers/index.d.ts +1 -0
  53. package/dist/es/parsers/index.d.ts.map +1 -1
  54. package/dist/es/parsers/parseTargetSugar.d.ts +3 -0
  55. package/dist/es/parsers/parseTargetSugar.d.ts.map +1 -0
  56. package/dist/es/parsers/parseTargetSugar.js +31 -0
  57. package/dist/es/parsers/parseWay.d.ts +131 -22
  58. package/dist/es/parsers/parseWay.d.ts.map +1 -1
  59. package/dist/es/parsers/parseWay.js +149 -17
  60. package/dist/lib/compile/compile.cjs +33 -4
  61. package/dist/lib/compile/compile.d.ts +6 -0
  62. package/dist/lib/compile/compile.d.ts.map +1 -1
  63. package/dist/lib/compile/node.cjs +94 -4
  64. package/dist/lib/compile/node.d.ts +23 -2
  65. package/dist/lib/compile/node.d.ts.map +1 -1
  66. package/dist/lib/compile/path.cjs +333 -14
  67. package/dist/lib/compile/path.d.ts +3 -2
  68. package/dist/lib/compile/path.d.ts.map +1 -1
  69. package/dist/lib/compile/position.cjs +32 -5
  70. package/dist/lib/compile/position.d.ts +8 -5
  71. package/dist/lib/compile/position.d.ts.map +1 -1
  72. package/dist/lib/geometry/arc.cjs +55 -0
  73. package/dist/lib/geometry/arc.d.ts +34 -0
  74. package/dist/lib/geometry/arc.d.ts.map +1 -0
  75. package/dist/lib/geometry/bend.cjs +29 -0
  76. package/dist/lib/geometry/bend.d.ts +18 -0
  77. package/dist/lib/geometry/bend.d.ts.map +1 -0
  78. package/dist/lib/geometry/index.d.ts +3 -0
  79. package/dist/lib/geometry/index.d.ts.map +1 -1
  80. package/dist/lib/geometry/segment.cjs +88 -0
  81. package/dist/lib/geometry/segment.d.ts +38 -0
  82. package/dist/lib/geometry/segment.d.ts.map +1 -0
  83. package/dist/lib/index.cjs +18 -0
  84. package/dist/lib/index.d.ts +4 -4
  85. package/dist/lib/index.d.ts.map +1 -1
  86. package/dist/lib/ir/coordinate.cjs +27 -0
  87. package/dist/lib/ir/coordinate.d.ts +57 -0
  88. package/dist/lib/ir/coordinate.d.ts.map +1 -0
  89. package/dist/lib/ir/index.d.ts +1 -0
  90. package/dist/lib/ir/index.d.ts.map +1 -1
  91. package/dist/lib/ir/node.cjs +28 -2
  92. package/dist/lib/ir/node.d.ts +276 -17
  93. package/dist/lib/ir/node.d.ts.map +1 -1
  94. package/dist/lib/ir/path/path.cjs +22 -0
  95. package/dist/lib/ir/path/path.d.ts +625 -15
  96. package/dist/lib/ir/path/path.d.ts.map +1 -1
  97. package/dist/lib/ir/path/step.cjs +93 -3
  98. package/dist/lib/ir/path/step.d.ts +872 -20
  99. package/dist/lib/ir/path/step.d.ts.map +1 -1
  100. package/dist/lib/ir/path/target.cjs +8 -2
  101. package/dist/lib/ir/path/target.d.ts +32 -2
  102. package/dist/lib/ir/path/target.d.ts.map +1 -1
  103. package/dist/lib/ir/position/at-position.cjs +31 -0
  104. package/dist/lib/ir/position/at-position.d.ts +50 -0
  105. package/dist/lib/ir/position/at-position.d.ts.map +1 -0
  106. package/dist/lib/ir/position/index.d.ts +1 -0
  107. package/dist/lib/ir/position/index.d.ts.map +1 -1
  108. package/dist/lib/ir/scene.cjs +6 -1
  109. package/dist/lib/ir/scene.d.ts +2072 -112
  110. package/dist/lib/ir/scene.d.ts.map +1 -1
  111. package/dist/lib/parsers/index.d.ts +1 -0
  112. package/dist/lib/parsers/index.d.ts.map +1 -1
  113. package/dist/lib/parsers/parseTargetSugar.cjs +31 -0
  114. package/dist/lib/parsers/parseTargetSugar.d.ts +3 -0
  115. package/dist/lib/parsers/parseTargetSugar.d.ts.map +1 -0
  116. package/dist/lib/parsers/parseWay.cjs +149 -17
  117. package/dist/lib/parsers/parseWay.d.ts +131 -22
  118. package/dist/lib/parsers/parseWay.d.ts.map +1 -1
  119. package/package.json +1 -1
@@ -8,6 +8,8 @@ var DEFAULT_FONT_SIZE = 14;
8
8
  var DEFAULT_PADDING = 8;
9
9
  var DEFAULT_LINE_HEIGHT_FACTOR = 1.2;
10
10
  var DEG_TO_RAD = Math.PI / 180;
11
+ /** Node label 与 node 边界的默认距离(user units);TikZ 默认是 0pt 但视觉上太贴 */
12
+ var DEFAULT_LABEL_DISTANCE = 4;
11
13
  var SQRT2 = Math.SQRT2;
12
14
  /** dashed 预设:SVG stroke-dasharray "4 2"——4 px 实线 + 2 px 间隙循环 */
13
15
  var DASHED_PATTERN = "4 2";
@@ -80,6 +82,59 @@ var anchorOf = (layout, name) => {
80
82
  }
81
83
  };
82
84
  /**
85
+ * 8 方向 label position → (anchorName, 单位向量) 映射。
86
+ * 视觉语义与 `at.direction` 一致——above 是视觉上方(y 减小)。
87
+ */
88
+ var LABEL_DIRECTION_MAP = {
89
+ above: {
90
+ anchor: "north",
91
+ vec: [0, -1]
92
+ },
93
+ below: {
94
+ anchor: "south",
95
+ vec: [0, 1]
96
+ },
97
+ left: {
98
+ anchor: "west",
99
+ vec: [-1, 0]
100
+ },
101
+ right: {
102
+ anchor: "east",
103
+ vec: [1, 0]
104
+ },
105
+ "above-left": {
106
+ anchor: "north-west",
107
+ vec: [-Math.SQRT1_2, -Math.SQRT1_2]
108
+ },
109
+ "above-right": {
110
+ anchor: "north-east",
111
+ vec: [Math.SQRT1_2, -Math.SQRT1_2]
112
+ },
113
+ "below-left": {
114
+ anchor: "south-west",
115
+ vec: [-Math.SQRT1_2, Math.SQRT1_2]
116
+ },
117
+ "below-right": {
118
+ anchor: "south-east",
119
+ vec: [Math.SQRT1_2, Math.SQRT1_2]
120
+ }
121
+ };
122
+ /**
123
+ * 算 label 中心点:
124
+ * - 8 方向:节点对应 anchor 出发,按单位向量 × distance 外推
125
+ * - 数字角度:先取 angleBoundary 边界点,再沿 (cos, sin) 单位向量 × distance 外推
126
+ */
127
+ var labelCenter = (layout, label) => {
128
+ if (typeof label.position === "number") {
129
+ const rad = label.position * Math.PI / 180;
130
+ const [bx, by] = angleBoundaryOf(layout, label.position);
131
+ return [bx + Math.cos(rad) * label.distance, by + Math.sin(rad) * label.distance];
132
+ }
133
+ const { anchor, vec } = LABEL_DIRECTION_MAP[label.position];
134
+ const [bx, by] = anchorOf(layout, anchor);
135
+ return [bx + vec[0] * label.distance, by + vec[1] * label.distance];
136
+ };
137
+ /**
83
138
  * 取节点 shape 在指定角度方向上的边界点。角度约定与 PolarPosition 一致(度数):
84
139
  * 0° = +x(east),90° = +y(screen 下方)。
85
140
  * **不应用 margin**——同 anchorOf。用于 `'A.30'` 这种语法落点。
@@ -102,7 +157,7 @@ var angleBoundaryOf = (layout, angleDeg) => {
102
157
  * - IR 的 rotate(度数)转弧度存进 Rect.rotate
103
158
  * - 透传 margin / 样式属性
104
159
  */
105
- var layoutNode = (node, measureText, nodeIndex) => {
160
+ var layoutNode = (node, measureText, nodeIndex, nodeDistance) => {
106
161
  const sx = node.xScale ?? node.scale ?? 1;
107
162
  const sy = node.yScale ?? node.scale ?? 1;
108
163
  const fontScale = Math.min(sx, sy);
@@ -173,8 +228,22 @@ var layoutNode = (node, measureText, nodeIndex) => {
173
228
  break;
174
229
  }
175
230
  const rotateDeg = node.rotate ?? 0;
176
- const center = require_position.resolvePosition(node.position, nodeIndex);
177
- if (!center) throw new Error(`Cannot resolve position for node ${node.id ?? "(unnamed)"}; polar.origin may reference an undefined node`);
231
+ const center = require_position.resolvePosition(node.position, nodeIndex, nodeDistance);
232
+ if (!center) throw new Error(`Cannot resolve position for node ${node.id ?? "(unnamed)"}; polar.origin or at.of may reference an undefined node`);
233
+ const labels = (node.label === void 0 ? void 0 : Array.isArray(node.label) ? node.label : [node.label])?.map((lab) => {
234
+ const labFont = lab.font;
235
+ return {
236
+ text: lab.text,
237
+ position: lab.position ?? "above",
238
+ distance: lab.distance ?? DEFAULT_LABEL_DISTANCE,
239
+ textColor: lab.textColor ?? node.textColor,
240
+ opacity: lab.opacity,
241
+ fontSize: (labFont?.size ?? baseFontSize) * fontScale,
242
+ fontFamily: labFont?.family ?? fontFamily,
243
+ fontWeight: labFont?.weight ?? fontWeight,
244
+ fontStyle: labFont?.style ?? fontStyle
245
+ };
246
+ });
178
247
  return {
179
248
  id: node.id,
180
249
  shape,
@@ -204,7 +273,8 @@ var layoutNode = (node, measureText, nodeIndex) => {
204
273
  strokeDasharray: resolveDashArray(node.dashArray, node.dashed, node.dotted),
205
274
  roundedCorners: node.roundedCorners,
206
275
  textColor: node.textColor,
207
- opacity: node.opacity
276
+ opacity: node.opacity,
277
+ labels
208
278
  };
209
279
  };
210
280
  /** rectangle shape 的 RectPrim */
@@ -305,6 +375,26 @@ var emitNodePrimitives = (layout, round) => {
305
375
  measuredHeight: round(layout.textHeight)
306
376
  });
307
377
  }
378
+ if (layout.labels) for (const lab of layout.labels) {
379
+ const [lx, ly] = labelCenter(layout, lab);
380
+ inner.push({
381
+ type: "text",
382
+ x: round(lx),
383
+ y: round(ly),
384
+ lines: [{ text: lab.text }],
385
+ fontSize: lab.fontSize,
386
+ fontFamily: lab.fontFamily,
387
+ fontWeight: lab.fontWeight,
388
+ fontStyle: lab.fontStyle,
389
+ align: "middle",
390
+ baseline: "middle",
391
+ lineHeight: round(lab.fontSize * DEFAULT_LINE_HEIGHT_FACTOR),
392
+ fill: lab.textColor ?? "currentColor",
393
+ opacity: lab.opacity ?? layout.opacity,
394
+ measuredWidth: 0,
395
+ measuredHeight: round(lab.fontSize)
396
+ });
397
+ }
308
398
  if (layout.rotateDeg === 0) return inner;
309
399
  return [{
310
400
  type: "group",
@@ -1,6 +1,6 @@
1
1
  import { Position } from '../geometry/point';
2
2
  import { Rect, RectAnchor } from '../geometry/rect';
3
- import { IRNode, NodeShape } from '../ir';
3
+ import { AtDirection, IRNode, NodeShape } from '../ir';
4
4
  import { ScenePrimitive, TextLine } from '../primitive';
5
5
  import { TextMeasurer } from './text-metrics';
6
6
  export type NodeLayout = {
@@ -62,6 +62,27 @@ export type NodeLayout = {
62
62
  textColor?: string;
63
63
  /** 整节点透明度 0~1;emit 时同时挂 shape 与 text primitive */
64
64
  opacity?: number;
65
+ /**
66
+ * 已解析的 label 列表(IR 层 `Node.label` 标准化后)。每条 label 已合并:
67
+ * - position 默认 'above'
68
+ * - distance 默认 DEFAULT_LABEL_DISTANCE
69
+ * - font 字段从 Node 继承(family / size / weight / style 任一未填则取 Node 块级值)
70
+ */
71
+ labels?: Array<NodeLabelLayout>;
72
+ };
73
+ /** 节点附属标签的 layout——layoutNode 阶段已合并好默认值与样式继承 */
74
+ export type NodeLabelLayout = {
75
+ text: string;
76
+ /** 8 方向枚举或数字角度(与 IR 同形态) */
77
+ position: AtDirection | number;
78
+ /** 已应用默认值的距离 */
79
+ distance: number;
80
+ textColor?: string;
81
+ opacity?: number;
82
+ fontSize: number;
83
+ fontFamily?: string;
84
+ fontWeight?: string | number;
85
+ fontStyle?: 'normal' | 'italic' | 'oblique';
65
86
  };
66
87
  /**
67
88
  * 取节点 shape 在 toward 方向上的"附着点"——path 端点贴边用。
@@ -89,7 +110,7 @@ export declare const angleBoundaryOf: (layout: NodeLayout, angleDeg: number) =>
89
110
  * - IR 的 rotate(度数)转弧度存进 Rect.rotate
90
111
  * - 透传 margin / 样式属性
91
112
  */
92
- export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nodeIndex: Map<string, NodeLayout>) => NodeLayout;
113
+ export declare const layoutNode: (node: IRNode, measureText: TextMeasurer, nodeIndex: Map<string, NodeLayout>, nodeDistance?: number) => NodeLayout;
93
114
  /**
94
115
  * 把 NodeLayout 翻译为 Scene primitives:
95
116
  * - shape 主体:按 shape 分发(rect / ellipse / path)
@@ -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,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
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/compile/node.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,10 @@
1
1
  const require_position = require("./position.cjs");
2
2
  const require_node = require("./node.cjs");
3
+ const require_arc = require("../geometry/arc.cjs");
4
+ const require_bend = require("../geometry/bend.cjs");
5
+ const require_segment = require("../geometry/segment.cjs");
3
6
  const require_parseTarget = require("./parseTarget.cjs");
7
+ const require_text_metrics = require("./text-metrics.cjs");
4
8
  //#region src/compile/path.ts
5
9
  /**
6
10
  * IR 的 path-level `arrow` + `arrowShape` 映射到 PathPrim 的
@@ -72,6 +76,7 @@ var refPointOfTarget = (target, nodeIndex) => {
72
76
  case "angle": return require_node.angleBoundaryOf(node, ref.angle);
73
77
  }
74
78
  }
79
+ if (typeof target === "object" && !Array.isArray(target) && ("rel" in target || "relAccumulate" in target)) return null;
75
80
  return require_position.resolvePosition(target, nodeIndex);
76
81
  };
77
82
  /**
@@ -99,11 +104,179 @@ var clipForTarget = (target, toward, nodeIndex) => {
99
104
  case "angle": return require_node.angleBoundaryOf(node, ref.angle);
100
105
  }
101
106
  }
107
+ if (typeof target === "object" && !Array.isArray(target) && ("rel" in target || "relAccumulate" in target)) return null;
102
108
  return require_position.resolvePosition(target, nodeIndex);
103
109
  };
104
110
  /** 浅相等:两个 IRPosition 的两个分量都精确相等(未 round) */
105
111
  var samePoint = (a, b) => !!a && !!b && a[0] === b[0] && a[1] === b[1];
106
112
  /**
113
+ * 语义 stroke 档位 → 数值映射(user units)。
114
+ *
115
+ * 对齐 TikZ 比例(thin = 默认 0.4pt,retikz 默认 strokeWidth = 1,所以 thin → 1):
116
+ * ultra thin 0.1pt → 0.25
117
+ * very thin 0.2pt → 0.5
118
+ * thin 0.4pt → 1 (= 默认 strokeWidth)
119
+ * semithick 0.6pt → 1.5
120
+ * thick 0.8pt → 2
121
+ * very thick 1.2pt → 3
122
+ * ultra thick 1.6pt → 4
123
+ *
124
+ * 显式 `strokeWidth` 始终覆盖 `thickness`——thickness 仅在 strokeWidth 缺省时生效。
125
+ */
126
+ var THICKNESS_TO_WIDTH = {
127
+ ultraThin: .25,
128
+ veryThin: .5,
129
+ thin: 1,
130
+ semithick: 1.5,
131
+ thick: 2,
132
+ veryThick: 3,
133
+ ultraThick: 4
134
+ };
135
+ /** ADR-0004:边标注的默认字号 / 偏移量(user units) */
136
+ var LABEL_FONT_SIZE = 14;
137
+ var LABEL_LINE_HEIGHT_FACTOR = 1.2;
138
+ var LABEL_SIDE_OFFSET = 4;
139
+ var RAD_TO_DEG = 180 / Math.PI;
140
+ /** label.position → 段参数 t */
141
+ var tForLabelPosition = (pos) => pos === "near-start" ? .25 : pos === "near-end" ? .75 : .5;
142
+ /**
143
+ * 把 step.label + 段采样结果翻成 TextPrim(sloped 时再裹一层 group 旋转)。
144
+ *
145
+ * 几何(默认 side='above',position='midway'):
146
+ * - 'above': 锚点 (x, y - LABEL_SIDE_OFFSET),align=middle, baseline=bottom
147
+ * - 'below': 锚点 (x, y + LABEL_SIDE_OFFSET),align=middle, baseline=top
148
+ * - 'left' : 锚点 (x - LABEL_SIDE_OFFSET, y),align=end, baseline=middle
149
+ * - 'right': 锚点 (x + LABEL_SIDE_OFFSET, y),align=start, baseline=middle
150
+ * - 'sloped': 锚点 (x, y) 不偏移,align=middle baseline=bottom,外裹 group
151
+ * `transform="rotate(angle x y)"`,angle 由切线 atan2 算出(SVG y-down,CW 正向)
152
+ *
153
+ * 文本宽高交给 measureText;fallbackMeasurer 时只是估算,但不影响渲染坐标。
154
+ * 返回 primitive + 用于 viewBox 的若干外接点(sloped 时按"近似最大半径"四角扩张)。
155
+ */
156
+ var emitLabelPrimitive = (label, sample, measureText, round) => {
157
+ const fontSize = LABEL_FONT_SIZE;
158
+ const lineHeight = fontSize * LABEL_LINE_HEIGHT_FACTOR;
159
+ const m = measureText(label.text, { size: fontSize });
160
+ const measuredWidth = m.width;
161
+ const measuredHeight = m.height || lineHeight;
162
+ const side = label.side ?? "above";
163
+ let x = sample.point[0];
164
+ let y = sample.point[1];
165
+ let align = "middle";
166
+ let baseline = "middle";
167
+ if (side === "above") {
168
+ y -= LABEL_SIDE_OFFSET;
169
+ baseline = "bottom";
170
+ } else if (side === "below") {
171
+ y += LABEL_SIDE_OFFSET;
172
+ baseline = "top";
173
+ } else if (side === "left") {
174
+ x -= LABEL_SIDE_OFFSET;
175
+ align = "end";
176
+ } else if (side === "right") {
177
+ x += LABEL_SIDE_OFFSET;
178
+ align = "start";
179
+ } else baseline = "bottom";
180
+ const text = {
181
+ type: "text",
182
+ x: round(x),
183
+ y: round(y),
184
+ lines: [{ text: label.text }],
185
+ fontSize,
186
+ align,
187
+ baseline,
188
+ lineHeight: round(lineHeight),
189
+ measuredWidth: round(measuredWidth),
190
+ measuredHeight: round(measuredHeight),
191
+ fill: "currentColor"
192
+ };
193
+ if (side === "sloped") {
194
+ const groupPrim = {
195
+ type: "group",
196
+ transform: `rotate(${round(Math.atan2(sample.tangent[1], sample.tangent[0]) * RAD_TO_DEG)} ${round(x)} ${round(y)})`,
197
+ children: [text]
198
+ };
199
+ const r = Math.max(measuredWidth / 2, measuredHeight / 2);
200
+ return {
201
+ primitive: groupPrim,
202
+ points: [
203
+ [x - r, y - r],
204
+ [x + r, y - r],
205
+ [x - r, y + r],
206
+ [x + r, y + r]
207
+ ]
208
+ };
209
+ }
210
+ const halfW = measuredWidth / 2;
211
+ const halfH = measuredHeight / 2;
212
+ return {
213
+ primitive: text,
214
+ points: [
215
+ [x - halfW, y - halfH],
216
+ [x + halfW, y - halfH],
217
+ [x - halfW, y + halfH],
218
+ [x + halfW, y + halfH]
219
+ ]
220
+ };
221
+ };
222
+ /**
223
+ * 把 rel / relAccumulate 目标解析为绝对 Position,产出 step kind 不变但
224
+ * `to` 字段全为绝对坐标的步序列。
225
+ *
226
+ * 决策(ADR-0003 §影响 与 §背景 文本有矛盾):
227
+ * 两者都相对 prevEnd 解析,区别仅在是否更新 prevEnd——
228
+ * rel 不更新(保持 TikZ `+` 语义),relAccumulate 更新(TikZ `++` 累积)。
229
+ * 选这个语义因为:(1) 与 TikZ `+`/`++` 一致;(2) 与字段名"Accumulate"
230
+ * 语义匹配;(3) 与 ADR 背景段一致;§影响 段写"pathStart + offset"是 typo。
231
+ *
232
+ * 跨 step kind 的 prevEnd 推进:
233
+ * - 有 to 的 kind(move/line/step/curve/cubic/bend):prevEnd = refPointOfTarget(to)
234
+ * - arc:prevEnd = arcEndPoint(prevEnd, radius, endAngle)
235
+ * - circlePath / ellipsePath:prevEnd 不变(画完留圆心,即 prevEnd 本身)
236
+ * - cycle:prevEnd 不变(不重置到 pathStart,保持简单;后续如有需要再扩)
237
+ *
238
+ * prevEnd 为 null(首步是 rel)时回退到 [0, 0] 当锚点;解析失败保持原 step。
239
+ */
240
+ var normalizeRelativeTargets = (steps, nodeIndex) => {
241
+ let prevEnd = null;
242
+ const out = [];
243
+ for (const step of steps) {
244
+ if (step.kind === "cycle") {
245
+ out.push(step);
246
+ continue;
247
+ }
248
+ if (step.kind === "circlePath" || step.kind === "ellipsePath") {
249
+ out.push(step);
250
+ continue;
251
+ }
252
+ if (step.kind === "arc") {
253
+ out.push(step);
254
+ if (prevEnd) prevEnd = require_arc.arcEndPoint(prevEnd, step.radius, step.endAngle);
255
+ continue;
256
+ }
257
+ const original = step.to;
258
+ let resolvedTo = original;
259
+ let updatePrevEnd = true;
260
+ if (typeof original === "object" && !Array.isArray(original) && "rel" in original) {
261
+ const ref = prevEnd ?? [0, 0];
262
+ resolvedTo = [ref[0] + original.rel[0], ref[1] + original.rel[1]];
263
+ updatePrevEnd = false;
264
+ } else if (typeof original === "object" && !Array.isArray(original) && "relAccumulate" in original) {
265
+ const ref = prevEnd ?? [0, 0];
266
+ resolvedTo = [ref[0] + original.relAccumulate[0], ref[1] + original.relAccumulate[1]];
267
+ }
268
+ out.push({
269
+ ...step,
270
+ to: resolvedTo
271
+ });
272
+ if (updatePrevEnd) {
273
+ const pos = refPointOfTarget(resolvedTo, nodeIndex);
274
+ if (pos) prevEnd = pos;
275
+ }
276
+ }
277
+ return out;
278
+ };
279
+ /**
107
280
  * 把 IR Path 翻译为单个 PathPrim。
108
281
  *
109
282
  * 关键算法(v0.1.0-alpha.1):每个绘制段(line / fold)**独立**地用节点中心
@@ -124,15 +297,27 @@ var samePoint = (a, b) => !!a && !!b && a[0] === b[0] && a[1] === b[1];
124
297
  *
125
298
  * 引用未定义节点 / 解析失败时返回 null(path 整体跳过)。
126
299
  */
127
- var emitPathPrimitive = (path, nodeIndex, round) => {
128
- const steps = path.children;
300
+ var emitPathPrimitive = (path, nodeIndex, round, measureText = require_text_metrics.fallbackMeasurer) => {
301
+ const steps = normalizeRelativeTargets(path.children, nodeIndex);
129
302
  if (steps.length < 2) return null;
130
- const anchors = steps.map((s) => s.kind === "cycle" ? null : refPointOfTarget(s.to, nodeIndex));
131
- /** i 之前最近的"有 to 字段的 step"(跳过 cycle) + 它的 anchor */
303
+ /** ADR-0004:每段 step.label 翻译出的 TextPrim(或裹 sloped 旋转的 group),
304
+ * 与 path 主体 primitive 同级返回;调用方 push scene.primitives */
305
+ const labelPrims = [];
306
+ /** 为当前 step 收 label:算 sample 后调用 emitLabelPrimitive,结果累积到 labelPrims / points */
307
+ const collectLabel = (step, sampleAt) => {
308
+ if (step.kind === "move" || step.kind === "cycle" || !("label" in step) || !step.label) return;
309
+ const sample = sampleAt(tForLabelPosition(step.label.position));
310
+ const r = emitLabelPrimitive(step.label, sample, measureText, round);
311
+ labelPrims.push(r.primitive);
312
+ for (const p of r.points) points.push(p);
313
+ };
314
+ const hasTo = (s) => s.kind !== "cycle" && s.kind !== "arc" && s.kind !== "circlePath" && s.kind !== "ellipsePath";
315
+ const anchors = steps.map((s) => hasTo(s) ? refPointOfTarget(s.to, nodeIndex) : null);
316
+ /** 找 i 之前最近的"有 to 字段的 step" + 它的 anchor */
132
317
  const findPrev = (i) => {
133
318
  for (let j = i - 1; j >= 0; j--) {
134
319
  const s = steps[j];
135
- if (s.kind === "cycle") continue;
320
+ if (!hasTo(s)) continue;
136
321
  const a = anchors[j];
137
322
  if (!a) return null;
138
323
  return {
@@ -144,13 +329,27 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
144
329
  };
145
330
  /** 找 i 之前最近的 move 的 to——cycle 闭合的目标 */
146
331
  const findRecentMoveTo = (i) => {
147
- for (let j = i - 1; j >= 0; j--) if (steps[j].kind === "move") return steps[j].to;
332
+ for (let j = i - 1; j >= 0; j--) {
333
+ const s = steps[j];
334
+ if (s.kind === "move") return s.to;
335
+ }
148
336
  return null;
149
337
  };
150
338
  const ops = [];
151
339
  const points = [];
152
340
  let lastEnd = null;
153
341
  let subPathStart = null;
342
+ /**
343
+ * "笔位覆盖"——arc / circlePath / ellipsePath 这种没有 `to` 字段的 step
344
+ * 不能通过 `prev.step.to` 重算下一段的起点。它们设置 penOverride,下一个
345
+ * 绘制段(line/curve/cubic/bend/step)直接用这个点当 fromClip,之后清空。
346
+ *
347
+ * - arc:endpoint(弧终点)—— 与 SVG 实际 cursor 一致
348
+ * - circlePath/ellipsePath:center(圆心)—— ADR-0002 决策"画完留在圆心",
349
+ * 注意 SVG 实际 cursor 在弧端点而不在 center,必须靠 startSegment 发 M
350
+ * teleport 回中心
351
+ */
352
+ let penOverride = null;
154
353
  const emitM = (p) => {
155
354
  ops.push({
156
355
  cmd: "M",
@@ -172,6 +371,40 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
172
371
  ops.push({ cmd: "Z" });
173
372
  lastEnd = subPathStart;
174
373
  };
374
+ const emitQ = (control, p) => {
375
+ ops.push({
376
+ cmd: "Q",
377
+ control,
378
+ point: p
379
+ });
380
+ points.push(control);
381
+ points.push(p);
382
+ lastEnd = p;
383
+ };
384
+ const emitC = (c1, c2, p) => {
385
+ ops.push({
386
+ cmd: "C",
387
+ control1: c1,
388
+ control2: c2,
389
+ point: p
390
+ });
391
+ points.push(c1);
392
+ points.push(c2);
393
+ points.push(p);
394
+ lastEnd = p;
395
+ };
396
+ const emitA = (rx, ry, largeArc, sweep, p) => {
397
+ ops.push({
398
+ cmd: "A",
399
+ rx,
400
+ ry,
401
+ largeArc,
402
+ sweep,
403
+ point: p
404
+ });
405
+ points.push(p);
406
+ lastEnd = p;
407
+ };
175
408
  /** 段起点:与 lastEnd 相同就复用 cursor(省掉冗余 M),否则发 M */
176
409
  const startSegment = (p) => {
177
410
  if (samePoint(p, lastEnd)) return;
@@ -199,31 +432,114 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
199
432
  }
200
433
  const prev = findPrev(i);
201
434
  if (!prev) return null;
435
+ if (step.kind === "arc") {
436
+ const center = prev.anchor;
437
+ const startPt = require_arc.arcEndPoint(center, step.radius, step.startAngle);
438
+ const endPt = require_arc.arcEndPoint(center, step.radius, step.endAngle);
439
+ const flags = require_arc.arcSvgFlags(step.startAngle, step.endAngle);
440
+ startSegment(startPt);
441
+ emitA(step.radius, step.radius, flags.largeArc, flags.sweep, endPt);
442
+ for (const p of require_arc.arcBoundingPoints(center, step.radius, step.startAngle, step.endAngle)) points.push(p);
443
+ collectLabel(step, (t) => require_segment.arcSegmentSample(center, step.radius, step.startAngle, step.endAngle, t));
444
+ penOverride = endPt;
445
+ continue;
446
+ }
447
+ if (step.kind === "circlePath") {
448
+ const center = prev.anchor;
449
+ const r = step.radius;
450
+ const right = [center[0] + r, center[1]];
451
+ const left = [center[0] - r, center[1]];
452
+ startSegment(right);
453
+ emitA(r, r, 0, 1, left);
454
+ emitA(r, r, 0, 1, right);
455
+ points.push([center[0] + r, center[1]]);
456
+ points.push([center[0] - r, center[1]]);
457
+ points.push([center[0], center[1] + r]);
458
+ points.push([center[0], center[1] - r]);
459
+ collectLabel(step, (t) => require_segment.circleSegmentSample(center, r, t));
460
+ penOverride = center;
461
+ continue;
462
+ }
463
+ if (step.kind === "ellipsePath") {
464
+ const center = prev.anchor;
465
+ const rx = step.radiusX;
466
+ const ry = step.radiusY;
467
+ const right = [center[0] + rx, center[1]];
468
+ const left = [center[0] - rx, center[1]];
469
+ startSegment(right);
470
+ emitA(rx, ry, 0, 1, left);
471
+ emitA(rx, ry, 0, 1, right);
472
+ points.push([center[0] + rx, center[1]]);
473
+ points.push([center[0] - rx, center[1]]);
474
+ points.push([center[0], center[1] + ry]);
475
+ points.push([center[0], center[1] - ry]);
476
+ collectLabel(step, (t) => require_segment.ellipseSegmentSample(center, rx, ry, t));
477
+ penOverride = center;
478
+ continue;
479
+ }
202
480
  const currAnchor = anchors[i];
203
481
  if (!currAnchor) return null;
482
+ const usedOverride = penOverride;
483
+ penOverride = null;
204
484
  if (step.kind === "line") {
205
- const fromClip = clipForTarget(prev.step.to, currAnchor, nodeIndex);
485
+ const fromClip = usedOverride ?? clipForTarget(prev.step.to, currAnchor, nodeIndex);
206
486
  const toClip = clipForTarget(step.to, prev.anchor, nodeIndex);
207
487
  if (!fromClip || !toClip) return null;
208
488
  startSegment(fromClip);
209
489
  emitL(toClip);
490
+ collectLabel(step, (t) => require_segment.lineSegmentSample(fromClip, toClip, t));
491
+ continue;
492
+ }
493
+ if (step.kind === "curve") {
494
+ const fromClip = usedOverride ?? clipForTarget(prev.step.to, step.control, nodeIndex);
495
+ const toClip = clipForTarget(step.to, step.control, nodeIndex);
496
+ if (!fromClip || !toClip) return null;
497
+ startSegment(fromClip);
498
+ emitQ(step.control, toClip);
499
+ collectLabel(step, (t) => require_segment.quadSegmentSample(fromClip, step.control, toClip, t));
500
+ continue;
501
+ }
502
+ if (step.kind === "cubic") {
503
+ const fromClip = usedOverride ?? clipForTarget(prev.step.to, step.control1, nodeIndex);
504
+ const toClip = clipForTarget(step.to, step.control2, nodeIndex);
505
+ if (!fromClip || !toClip) return null;
506
+ startSegment(fromClip);
507
+ emitC(step.control1, step.control2, toClip);
508
+ collectLabel(step, (t) => require_segment.cubicSegmentSample(fromClip, step.control1, step.control2, toClip, t));
509
+ continue;
510
+ }
511
+ if (step.kind === "bend") {
512
+ const angle = step.bendAngle ?? 30;
513
+ const [c1, c2] = require_bend.bendControlPoints(prev.anchor, currAnchor, step.bendDirection, angle);
514
+ const fromClip = usedOverride ?? clipForTarget(prev.step.to, c1, nodeIndex);
515
+ const toClip = clipForTarget(step.to, c2, nodeIndex);
516
+ if (!fromClip || !toClip) return null;
517
+ startSegment(fromClip);
518
+ emitC(c1, c2, toClip);
519
+ collectLabel(step, (t) => require_segment.cubicSegmentSample(fromClip, c1, c2, toClip, t));
210
520
  continue;
211
521
  }
212
522
  const corner = cornerOf(prev.anchor, currAnchor, step.via);
213
- const fromClip = clipForTarget(prev.step.to, corner, nodeIndex);
523
+ const fromClip = usedOverride ?? clipForTarget(prev.step.to, corner, nodeIndex);
214
524
  const toClip = clipForTarget(step.to, corner, nodeIndex);
215
525
  if (!fromClip || !toClip) return null;
216
526
  startSegment(fromClip);
217
527
  emitL(corner);
218
528
  emitL(toClip);
529
+ collectLabel(step, (t) => require_segment.foldSegmentSample(fromClip, corner, toClip, t));
219
530
  }
220
- const strokeWidth = path.strokeWidth ?? 1;
531
+ const strokeWidth = path.strokeWidth ?? (path.thickness ? THICKNESS_TO_WIDTH[path.thickness] : 1);
221
532
  const baseProps = {
222
533
  stroke: path.stroke ?? "currentColor",
223
534
  strokeWidth,
224
535
  fill: path.fill ?? "none",
225
536
  fillRule: path.fillRule,
226
- strokeDasharray: path.strokeDasharray
537
+ strokeDasharray: path.strokeDasharray,
538
+ strokeLinecap: path.lineCap,
539
+ strokeLinejoin: path.lineJoin,
540
+ opacity: path.opacity,
541
+ fillOpacity: path.fillOpacity,
542
+ strokeOpacity: path.drawOpacity
227
543
  };
228
544
  const markers = arrowMarkers(path.arrow, path.arrowShape);
229
545
  const hasArrows = !!markers.arrowStart || !!markers.arrowEnd;
@@ -255,6 +571,9 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
255
571
  }
256
572
  const tokens = ops.map((op) => {
257
573
  if (op.cmd === "Z") return "Z";
574
+ if (op.cmd === "Q") return `Q ${round(op.control[0])} ${round(op.control[1])} ${round(op.point[0])} ${round(op.point[1])}`;
575
+ if (op.cmd === "C") return `C ${round(op.control1[0])} ${round(op.control1[1])} ${round(op.control2[0])} ${round(op.control2[1])} ${round(op.point[0])} ${round(op.point[1])}`;
576
+ if (op.cmd === "A") return `A ${round(op.rx)} ${round(op.ry)} 0 ${op.largeArc} ${op.sweep} ${round(op.point[0])} ${round(op.point[1])}`;
258
577
  return `${op.cmd} ${round(op.point[0])} ${round(op.point[1])}`;
259
578
  });
260
579
  const subPathStarts = [];
@@ -262,12 +581,12 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
262
581
  if (tok.startsWith("M ")) subPathStarts.push(idx);
263
582
  });
264
583
  if (!hasArrows || subPathStarts.length <= 1) return {
265
- primitive: {
584
+ primitives: [{
266
585
  type: "path",
267
586
  d: tokens.join(" "),
268
587
  ...baseProps,
269
588
  ...markers
270
- },
589
+ }, ...labelPrims],
271
590
  points
272
591
  };
273
592
  const subPathSlices = [];
@@ -277,7 +596,7 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
277
596
  subPathSlices.push(tokens.slice(start, end));
278
597
  }
279
598
  return {
280
- primitive: {
599
+ primitives: [{
281
600
  type: "group",
282
601
  children: subPathSlices.map((sub, i) => {
283
602
  const isFirst = i === 0;
@@ -290,7 +609,7 @@ var emitPathPrimitive = (path, nodeIndex, round) => {
290
609
  ...isLast && markers.arrowEnd ? { arrowEnd: markers.arrowEnd } : {}
291
610
  };
292
611
  })
293
- },
612
+ }, ...labelPrims],
294
613
  points
295
614
  };
296
615
  };
@@ -1,6 +1,7 @@
1
1
  import { IRPath, IRPosition } from '../ir';
2
2
  import { ScenePrimitive } from '../primitive';
3
3
  import { NodeLayout } from './node';
4
+ import { TextMeasurer } from './text-metrics';
4
5
  /**
5
6
  * 把 IR Path 翻译为单个 PathPrim。
6
7
  *
@@ -22,8 +23,8 @@ import { NodeLayout } from './node';
22
23
  *
23
24
  * 引用未定义节点 / 解析失败时返回 null(path 整体跳过)。
24
25
  */
25
- export declare const emitPathPrimitive: (path: IRPath, nodeIndex: Map<string, NodeLayout>, round: (n: number) => number) => {
26
- primitive: ScenePrimitive;
26
+ export declare const emitPathPrimitive: (path: IRPath, nodeIndex: Map<string, NodeLayout>, round: (n: number) => number, measureText?: TextMeasurer) => {
27
+ primitives: Array<ScenePrimitive>;
27
28
  points: Array<IRPosition>;
28
29
  } | null;
29
30
  //# sourceMappingURL=path.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/compile/path.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAEV,MAAM,EACN,UAAU,EAIX,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAY,cAAc,EAAY,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,KAAK,UAAU,EAA8C,MAAM,QAAQ,CAAC;AAGrF,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAoWrE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EAClC,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAC5B,cAAa,YAA+B,KAC3C;IAAE,UAAU,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;CAAE,GAAG,IA8arE,CAAC"}