@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.
- package/dist/es/compile/compile.d.ts +6 -0
- package/dist/es/compile/compile.d.ts.map +1 -1
- package/dist/es/compile/compile.js +33 -4
- package/dist/es/compile/node.d.ts +23 -2
- package/dist/es/compile/node.d.ts.map +1 -1
- package/dist/es/compile/node.js +94 -4
- package/dist/es/compile/path.d.ts +3 -2
- package/dist/es/compile/path.d.ts.map +1 -1
- package/dist/es/compile/path.js +333 -14
- package/dist/es/compile/position.d.ts +8 -5
- package/dist/es/compile/position.d.ts.map +1 -1
- package/dist/es/compile/position.js +32 -5
- package/dist/es/geometry/arc.d.ts +34 -0
- package/dist/es/geometry/arc.d.ts.map +1 -0
- package/dist/es/geometry/arc.js +53 -0
- package/dist/es/geometry/bend.d.ts +18 -0
- package/dist/es/geometry/bend.d.ts.map +1 -0
- package/dist/es/geometry/bend.js +29 -0
- package/dist/es/geometry/index.d.ts +3 -0
- package/dist/es/geometry/index.d.ts.map +1 -1
- package/dist/es/geometry/segment.d.ts +38 -0
- package/dist/es/geometry/segment.d.ts.map +1 -0
- package/dist/es/geometry/segment.js +82 -0
- package/dist/es/index.d.ts +4 -4
- package/dist/es/index.d.ts.map +1 -1
- package/dist/es/index.js +7 -4
- package/dist/es/ir/coordinate.d.ts +57 -0
- package/dist/es/ir/coordinate.d.ts.map +1 -0
- package/dist/es/ir/coordinate.js +27 -0
- package/dist/es/ir/index.d.ts +1 -0
- package/dist/es/ir/index.d.ts.map +1 -1
- package/dist/es/ir/node.d.ts +276 -17
- package/dist/es/ir/node.d.ts.map +1 -1
- package/dist/es/ir/node.js +28 -3
- package/dist/es/ir/path/path.d.ts +625 -15
- package/dist/es/ir/path/path.d.ts.map +1 -1
- package/dist/es/ir/path/path.js +22 -0
- package/dist/es/ir/path/step.d.ts +872 -20
- package/dist/es/ir/path/step.d.ts.map +1 -1
- package/dist/es/ir/path/step.js +86 -4
- package/dist/es/ir/path/target.d.ts +32 -2
- package/dist/es/ir/path/target.d.ts.map +1 -1
- package/dist/es/ir/path/target.js +7 -3
- package/dist/es/ir/position/at-position.d.ts +50 -0
- package/dist/es/ir/position/at-position.d.ts.map +1 -0
- package/dist/es/ir/position/at-position.js +30 -0
- package/dist/es/ir/position/index.d.ts +1 -0
- package/dist/es/ir/position/index.d.ts.map +1 -1
- package/dist/es/ir/scene.d.ts +2072 -112
- package/dist/es/ir/scene.d.ts.map +1 -1
- package/dist/es/ir/scene.js +6 -1
- package/dist/es/parsers/index.d.ts +1 -0
- package/dist/es/parsers/index.d.ts.map +1 -1
- package/dist/es/parsers/parseTargetSugar.d.ts +3 -0
- package/dist/es/parsers/parseTargetSugar.d.ts.map +1 -0
- package/dist/es/parsers/parseTargetSugar.js +31 -0
- package/dist/es/parsers/parseWay.d.ts +131 -22
- package/dist/es/parsers/parseWay.d.ts.map +1 -1
- package/dist/es/parsers/parseWay.js +149 -17
- package/dist/lib/compile/compile.cjs +33 -4
- package/dist/lib/compile/compile.d.ts +6 -0
- package/dist/lib/compile/compile.d.ts.map +1 -1
- package/dist/lib/compile/node.cjs +94 -4
- package/dist/lib/compile/node.d.ts +23 -2
- package/dist/lib/compile/node.d.ts.map +1 -1
- package/dist/lib/compile/path.cjs +333 -14
- package/dist/lib/compile/path.d.ts +3 -2
- package/dist/lib/compile/path.d.ts.map +1 -1
- package/dist/lib/compile/position.cjs +32 -5
- package/dist/lib/compile/position.d.ts +8 -5
- package/dist/lib/compile/position.d.ts.map +1 -1
- package/dist/lib/geometry/arc.cjs +55 -0
- package/dist/lib/geometry/arc.d.ts +34 -0
- package/dist/lib/geometry/arc.d.ts.map +1 -0
- package/dist/lib/geometry/bend.cjs +29 -0
- package/dist/lib/geometry/bend.d.ts +18 -0
- package/dist/lib/geometry/bend.d.ts.map +1 -0
- package/dist/lib/geometry/index.d.ts +3 -0
- package/dist/lib/geometry/index.d.ts.map +1 -1
- package/dist/lib/geometry/segment.cjs +88 -0
- package/dist/lib/geometry/segment.d.ts +38 -0
- package/dist/lib/geometry/segment.d.ts.map +1 -0
- package/dist/lib/index.cjs +18 -0
- package/dist/lib/index.d.ts +4 -4
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/ir/coordinate.cjs +27 -0
- package/dist/lib/ir/coordinate.d.ts +57 -0
- package/dist/lib/ir/coordinate.d.ts.map +1 -0
- package/dist/lib/ir/index.d.ts +1 -0
- package/dist/lib/ir/index.d.ts.map +1 -1
- package/dist/lib/ir/node.cjs +28 -2
- package/dist/lib/ir/node.d.ts +276 -17
- package/dist/lib/ir/node.d.ts.map +1 -1
- package/dist/lib/ir/path/path.cjs +22 -0
- package/dist/lib/ir/path/path.d.ts +625 -15
- package/dist/lib/ir/path/path.d.ts.map +1 -1
- package/dist/lib/ir/path/step.cjs +93 -3
- package/dist/lib/ir/path/step.d.ts +872 -20
- package/dist/lib/ir/path/step.d.ts.map +1 -1
- package/dist/lib/ir/path/target.cjs +8 -2
- package/dist/lib/ir/path/target.d.ts +32 -2
- package/dist/lib/ir/path/target.d.ts.map +1 -1
- package/dist/lib/ir/position/at-position.cjs +31 -0
- package/dist/lib/ir/position/at-position.d.ts +50 -0
- package/dist/lib/ir/position/at-position.d.ts.map +1 -0
- package/dist/lib/ir/position/index.d.ts +1 -0
- package/dist/lib/ir/position/index.d.ts.map +1 -1
- package/dist/lib/ir/scene.cjs +6 -1
- package/dist/lib/ir/scene.d.ts +2072 -112
- package/dist/lib/ir/scene.d.ts.map +1 -1
- package/dist/lib/parsers/index.d.ts +1 -0
- package/dist/lib/parsers/index.d.ts.map +1 -1
- package/dist/lib/parsers/parseTargetSugar.cjs +31 -0
- package/dist/lib/parsers/parseTargetSugar.d.ts +3 -0
- package/dist/lib/parsers/parseTargetSugar.d.ts.map +1 -0
- package/dist/lib/parsers/parseWay.cjs +149 -17
- package/dist/lib/parsers/parseWay.d.ts +131 -22
- package/dist/lib/parsers/parseWay.d.ts.map +1 -1
- 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
|
|
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,
|
|
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;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgCnD,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;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACjC,CAAC;AAEF,gDAAgD;AAChD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,QAAQ,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC7C,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;AAoCF;;;;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,EAClC,eAAe,MAAM,KACpB,UAuKF,CAAC;AAsEF;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,UAAU,EAClB,OAAO,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,KAC3B,KAAK,CAAC,cAAc,CA2EtB,CAAC"}
|
|
@@ -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
|
-
|
|
131
|
-
|
|
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
|
|
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--)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|