@spectratools/graphic-designer-cli 0.9.0 → 0.11.0
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/cli.js +321 -135
- package/dist/index.d.ts +48 -8
- package/dist/index.js +325 -85
- package/dist/qa.d.ts +1 -1
- package/dist/qa.js +47 -7
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +262 -133
- package/dist/{spec.schema-B_Z-KNqt.d.ts → spec.schema-CYlOLxmK.d.ts} +573 -49
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +47 -7
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -1366,12 +1366,12 @@ var MACOS_DOTS = [
|
|
|
1366
1366
|
{ fill: "#27C93F", stroke: "#1AAB29" }
|
|
1367
1367
|
];
|
|
1368
1368
|
function drawMacosDots(ctx, x, y) {
|
|
1369
|
-
for (const [index,
|
|
1369
|
+
for (const [index, dot] of MACOS_DOTS.entries()) {
|
|
1370
1370
|
ctx.beginPath();
|
|
1371
1371
|
ctx.arc(x + index * DOT_SPACING, y, DOT_RADIUS, 0, Math.PI * 2);
|
|
1372
1372
|
ctx.closePath();
|
|
1373
|
-
ctx.fillStyle =
|
|
1374
|
-
ctx.strokeStyle =
|
|
1373
|
+
ctx.fillStyle = dot.fill;
|
|
1374
|
+
ctx.strokeStyle = dot.stroke;
|
|
1375
1375
|
ctx.lineWidth = DOT_STROKE_WIDTH;
|
|
1376
1376
|
ctx.fill();
|
|
1377
1377
|
ctx.stroke();
|
|
@@ -2008,16 +2008,6 @@ function drawBezier(ctx, points, style) {
|
|
|
2008
2008
|
ctx.quadraticCurveTo(penultimate.x, penultimate.y, last.x, last.y);
|
|
2009
2009
|
ctx.stroke();
|
|
2010
2010
|
}
|
|
2011
|
-
function drawOrthogonalPath(ctx, from, to, style) {
|
|
2012
|
-
const midX = (from.x + to.x) / 2;
|
|
2013
|
-
applyLineStyle(ctx, style);
|
|
2014
|
-
ctx.beginPath();
|
|
2015
|
-
ctx.moveTo(from.x, from.y);
|
|
2016
|
-
ctx.lineTo(midX, from.y);
|
|
2017
|
-
ctx.lineTo(midX, to.y);
|
|
2018
|
-
ctx.lineTo(to.x, to.y);
|
|
2019
|
-
ctx.stroke();
|
|
2020
|
-
}
|
|
2021
2011
|
|
|
2022
2012
|
// src/renderers/connection.ts
|
|
2023
2013
|
var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
|
|
@@ -2100,56 +2090,71 @@ function curveRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, to
|
|
|
2100
2090
|
const cp2 = { x: p3.x + n3.x * offset, y: p3.y + n3.y * offset };
|
|
2101
2091
|
return [p0, cp1, cp2, p3];
|
|
2102
2092
|
}
|
|
2103
|
-
function
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2093
|
+
function inferEllipseParams(nodeBounds, explicitCenter, explicitRx, explicitRy) {
|
|
2094
|
+
if (nodeBounds.length === 0) {
|
|
2095
|
+
return {
|
|
2096
|
+
cx: explicitCenter?.x ?? 0,
|
|
2097
|
+
cy: explicitCenter?.y ?? 0,
|
|
2098
|
+
rx: explicitRx ?? 1,
|
|
2099
|
+
ry: explicitRy ?? 1
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
const centers = nodeBounds.map(rectCenter);
|
|
2103
|
+
const cx = explicitCenter?.x ?? centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
|
|
2104
|
+
const cy = explicitCenter?.y ?? centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
|
|
2105
|
+
const rx = explicitRx ?? Math.max(1, ...centers.map((c) => Math.abs(c.x - cx)));
|
|
2106
|
+
const ry = explicitRy ?? Math.max(1, ...centers.map((c) => Math.abs(c.y - cy)));
|
|
2107
|
+
return { cx, cy, rx, ry };
|
|
2111
2108
|
}
|
|
2112
|
-
function
|
|
2109
|
+
function ellipseRoute(fromBounds, toBounds, ellipse, fromAnchor, toAnchor) {
|
|
2113
2110
|
const fromCenter = rectCenter(fromBounds);
|
|
2114
2111
|
const toCenter = rectCenter(toBounds);
|
|
2115
|
-
const
|
|
2116
|
-
const
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const
|
|
2129
|
-
const
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
const
|
|
2139
|
-
const
|
|
2140
|
-
const
|
|
2141
|
-
const
|
|
2142
|
-
const
|
|
2143
|
-
const
|
|
2144
|
-
const
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2112
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toCenter);
|
|
2113
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromCenter);
|
|
2114
|
+
const theta1 = Math.atan2(
|
|
2115
|
+
(fromCenter.y - ellipse.cy) / ellipse.ry,
|
|
2116
|
+
(fromCenter.x - ellipse.cx) / ellipse.rx
|
|
2117
|
+
);
|
|
2118
|
+
const theta2 = Math.atan2(
|
|
2119
|
+
(toCenter.y - ellipse.cy) / ellipse.ry,
|
|
2120
|
+
(toCenter.x - ellipse.cx) / ellipse.rx
|
|
2121
|
+
);
|
|
2122
|
+
let angularSpan = theta2 - theta1;
|
|
2123
|
+
while (angularSpan > Math.PI) angularSpan -= 2 * Math.PI;
|
|
2124
|
+
while (angularSpan <= -Math.PI) angularSpan += 2 * Math.PI;
|
|
2125
|
+
const absSpan = Math.abs(angularSpan);
|
|
2126
|
+
const kappa = absSpan < 1e-6 ? 0 : 4 / 3 * Math.tan(absSpan / 4);
|
|
2127
|
+
const tangent1 = {
|
|
2128
|
+
x: -ellipse.rx * Math.sin(theta1),
|
|
2129
|
+
y: ellipse.ry * Math.cos(theta1)
|
|
2130
|
+
};
|
|
2131
|
+
const tangent2 = {
|
|
2132
|
+
x: -ellipse.rx * Math.sin(theta2),
|
|
2133
|
+
y: ellipse.ry * Math.cos(theta2)
|
|
2134
|
+
};
|
|
2135
|
+
const len1 = Math.hypot(tangent1.x, tangent1.y) || 1;
|
|
2136
|
+
const len2 = Math.hypot(tangent2.x, tangent2.y) || 1;
|
|
2137
|
+
const norm1 = { x: tangent1.x / len1, y: tangent1.y / len1 };
|
|
2138
|
+
const norm2 = { x: tangent2.x / len2, y: tangent2.y / len2 };
|
|
2139
|
+
const chordLength = Math.hypot(p3.x - p0.x, p3.y - p0.y);
|
|
2140
|
+
const cpDistance = chordLength * kappa * 0.5;
|
|
2141
|
+
const sign = angularSpan >= 0 ? 1 : -1;
|
|
2142
|
+
const cp1 = {
|
|
2143
|
+
x: p0.x + norm1.x * cpDistance * sign,
|
|
2144
|
+
y: p0.y + norm1.y * cpDistance * sign
|
|
2145
|
+
};
|
|
2146
|
+
const cp2 = {
|
|
2147
|
+
x: p3.x - norm2.x * cpDistance * sign,
|
|
2148
|
+
y: p3.y - norm2.y * cpDistance * sign
|
|
2149
|
+
};
|
|
2150
|
+
return [p0, cp1, cp2, p3];
|
|
2151
|
+
}
|
|
2152
|
+
function straightRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
2153
|
+
const fromC = rectCenter(fromBounds);
|
|
2154
|
+
const toC = rectCenter(toBounds);
|
|
2155
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toC);
|
|
2156
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromC);
|
|
2157
|
+
return [p0, p3];
|
|
2153
2158
|
}
|
|
2154
2159
|
function orthogonalRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
2155
2160
|
const fromC = rectCenter(fromBounds);
|
|
@@ -2195,15 +2200,6 @@ function findBoundaryIntersection(p0, cp1, cp2, p3, targetRect, searchFromEnd) {
|
|
|
2195
2200
|
}
|
|
2196
2201
|
return void 0;
|
|
2197
2202
|
}
|
|
2198
|
-
function pointAlongArc(route, t) {
|
|
2199
|
-
const [first, second] = route;
|
|
2200
|
-
if (t <= 0.5) {
|
|
2201
|
-
const localT2 = Math.max(0, Math.min(1, t * 2));
|
|
2202
|
-
return bezierPointAt(first[0], first[1], first[2], first[3], localT2);
|
|
2203
|
-
}
|
|
2204
|
-
const localT = Math.max(0, Math.min(1, (t - 0.5) * 2));
|
|
2205
|
-
return bezierPointAt(second[0], second[1], second[2], second[3], localT);
|
|
2206
|
-
}
|
|
2207
2203
|
function computeDiagramCenter(nodeBounds, canvasCenter) {
|
|
2208
2204
|
if (nodeBounds.length === 0) {
|
|
2209
2205
|
return canvasCenter ?? { x: 0, y: 0 };
|
|
@@ -2257,11 +2253,36 @@ function pointAlongPolyline(points, t) {
|
|
|
2257
2253
|
}
|
|
2258
2254
|
return points[points.length - 1];
|
|
2259
2255
|
}
|
|
2260
|
-
function
|
|
2256
|
+
function createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor) {
|
|
2257
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
2258
|
+
gradient.addColorStop(0, fromColor);
|
|
2259
|
+
gradient.addColorStop(0.5, baseColor);
|
|
2260
|
+
gradient.addColorStop(1, toColor);
|
|
2261
|
+
return gradient;
|
|
2262
|
+
}
|
|
2263
|
+
function resolveConnectionStroke(ctx, start, end, fromColor, baseColor, toColor) {
|
|
2264
|
+
if (!fromColor || !toColor) {
|
|
2265
|
+
return baseColor;
|
|
2266
|
+
}
|
|
2267
|
+
return createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor);
|
|
2268
|
+
}
|
|
2269
|
+
function drawOrthogonalPathWithStroke(ctx, from, to, style, stroke) {
|
|
2270
|
+
const midX = (from.x + to.x) / 2;
|
|
2271
|
+
ctx.strokeStyle = stroke;
|
|
2272
|
+
ctx.lineWidth = style.width;
|
|
2273
|
+
ctx.setLineDash(style.dash ?? []);
|
|
2274
|
+
ctx.beginPath();
|
|
2275
|
+
ctx.moveTo(from.x, from.y);
|
|
2276
|
+
ctx.lineTo(midX, from.y);
|
|
2277
|
+
ctx.lineTo(midX, to.y);
|
|
2278
|
+
ctx.lineTo(to.x, to.y);
|
|
2279
|
+
ctx.stroke();
|
|
2280
|
+
}
|
|
2281
|
+
function drawCubicInterpolatedPath(ctx, points, style, stroke) {
|
|
2261
2282
|
if (points.length < 2) {
|
|
2262
2283
|
return;
|
|
2263
2284
|
}
|
|
2264
|
-
ctx.strokeStyle =
|
|
2285
|
+
ctx.strokeStyle = stroke;
|
|
2265
2286
|
ctx.lineWidth = style.width;
|
|
2266
2287
|
ctx.setLineDash(style.dash ?? []);
|
|
2267
2288
|
ctx.beginPath();
|
|
@@ -2301,8 +2322,19 @@ function polylineBounds(points) {
|
|
|
2301
2322
|
};
|
|
2302
2323
|
}
|
|
2303
2324
|
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, options) {
|
|
2304
|
-
|
|
2305
|
-
|
|
2325
|
+
let routing = conn.routing ?? "auto";
|
|
2326
|
+
let curveMode = conn.curveMode ?? "normal";
|
|
2327
|
+
if (conn.strokeStyle !== void 0) {
|
|
2328
|
+
console.warn("connection.strokeStyle is deprecated, use style instead");
|
|
2329
|
+
}
|
|
2330
|
+
if (routing === "arc") {
|
|
2331
|
+
console.warn(
|
|
2332
|
+
"connection routing: 'arc' is deprecated. Use routing: 'curve' with curveMode: 'ellipse' instead."
|
|
2333
|
+
);
|
|
2334
|
+
routing = "curve";
|
|
2335
|
+
curveMode = "ellipse";
|
|
2336
|
+
}
|
|
2337
|
+
const strokeStyle = conn.style ?? conn.strokeStyle ?? "solid";
|
|
2306
2338
|
const strokeWidth = conn.width ?? conn.strokeWidth ?? 2;
|
|
2307
2339
|
const tension = conn.tension ?? 0.35;
|
|
2308
2340
|
const dash = dashFromStyle(strokeStyle);
|
|
@@ -2324,15 +2356,31 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
2324
2356
|
ctx.globalAlpha = conn.opacity;
|
|
2325
2357
|
const arrowPlacement = conn.arrowPlacement ?? "endpoint";
|
|
2326
2358
|
if (routing === "curve") {
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2359
|
+
let p0;
|
|
2360
|
+
let cp1;
|
|
2361
|
+
let cp2;
|
|
2362
|
+
let p3;
|
|
2363
|
+
if (curveMode === "ellipse") {
|
|
2364
|
+
const ellipse = options?.ellipseParams ?? inferEllipseParams([fromBounds, toBounds]);
|
|
2365
|
+
[p0, cp1, cp2, p3] = ellipseRoute(
|
|
2366
|
+
fromBounds,
|
|
2367
|
+
toBounds,
|
|
2368
|
+
ellipse,
|
|
2369
|
+
conn.fromAnchor,
|
|
2370
|
+
conn.toAnchor
|
|
2371
|
+
);
|
|
2372
|
+
} else {
|
|
2373
|
+
[p0, cp1, cp2, p3] = curveRoute(
|
|
2374
|
+
fromBounds,
|
|
2375
|
+
toBounds,
|
|
2376
|
+
diagramCenter,
|
|
2377
|
+
tension,
|
|
2378
|
+
conn.fromAnchor,
|
|
2379
|
+
conn.toAnchor
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
2383
|
+
ctx.strokeStyle = stroke;
|
|
2336
2384
|
ctx.lineWidth = style.width;
|
|
2337
2385
|
ctx.setLineDash(style.dash ?? []);
|
|
2338
2386
|
ctx.beginPath();
|
|
@@ -2363,50 +2411,22 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
2363
2411
|
}
|
|
2364
2412
|
}
|
|
2365
2413
|
}
|
|
2366
|
-
} else if (routing === "
|
|
2367
|
-
const [
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
diagramCenter,
|
|
2371
|
-
tension,
|
|
2372
|
-
conn.fromAnchor,
|
|
2373
|
-
conn.toAnchor
|
|
2374
|
-
);
|
|
2375
|
-
const [p0, cp1, cp2, pMid] = first;
|
|
2376
|
-
const [, cp3, cp4, p3] = second;
|
|
2377
|
-
ctx.strokeStyle = style.color;
|
|
2414
|
+
} else if (routing === "straight") {
|
|
2415
|
+
const [p0, p3] = straightRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor);
|
|
2416
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
2417
|
+
ctx.strokeStyle = stroke;
|
|
2378
2418
|
ctx.lineWidth = style.width;
|
|
2379
2419
|
ctx.setLineDash(style.dash ?? []);
|
|
2380
2420
|
ctx.beginPath();
|
|
2381
2421
|
ctx.moveTo(p0.x, p0.y);
|
|
2382
|
-
ctx.
|
|
2383
|
-
ctx.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, p3.x, p3.y);
|
|
2422
|
+
ctx.lineTo(p3.x, p3.y);
|
|
2384
2423
|
ctx.stroke();
|
|
2385
|
-
linePoints = [p0,
|
|
2424
|
+
linePoints = [p0, p3];
|
|
2386
2425
|
startPoint = p0;
|
|
2387
2426
|
endPoint = p3;
|
|
2388
|
-
startAngle = Math.atan2(p0.y -
|
|
2389
|
-
endAngle = Math.atan2(p3.y -
|
|
2390
|
-
labelPoint =
|
|
2391
|
-
if (arrowPlacement === "boundary") {
|
|
2392
|
-
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
2393
|
-
const [, s_cp3, s_cp4, s_p3] = second;
|
|
2394
|
-
const tEnd = findBoundaryIntersection(pMid, s_cp3, s_cp4, s_p3, toBounds, true);
|
|
2395
|
-
if (tEnd !== void 0) {
|
|
2396
|
-
endPoint = bezierPointAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
2397
|
-
const tangent = bezierTangentAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
2398
|
-
endAngle = Math.atan2(tangent.y, tangent.x);
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
2402
|
-
const tStart = findBoundaryIntersection(p0, cp1, cp2, pMid, fromBounds, false);
|
|
2403
|
-
if (tStart !== void 0) {
|
|
2404
|
-
startPoint = bezierPointAt(p0, cp1, cp2, pMid, tStart);
|
|
2405
|
-
const tangent = bezierTangentAt(p0, cp1, cp2, pMid, tStart);
|
|
2406
|
-
startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2427
|
+
startAngle = Math.atan2(p0.y - p3.y, p0.x - p3.x);
|
|
2428
|
+
endAngle = Math.atan2(p3.y - p0.y, p3.x - p0.x);
|
|
2429
|
+
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
2410
2430
|
} else {
|
|
2411
2431
|
const hasAnchorHints = conn.fromAnchor !== void 0 || conn.toAnchor !== void 0;
|
|
2412
2432
|
const useElkRoute = routing === "auto" && !hasAnchorHints && (edgeRoute?.points.length ?? 0) >= 2;
|
|
@@ -2417,10 +2437,18 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
2417
2437
|
endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
|
|
2418
2438
|
startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
|
|
2419
2439
|
endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
|
|
2440
|
+
const stroke = resolveConnectionStroke(
|
|
2441
|
+
ctx,
|
|
2442
|
+
startPoint,
|
|
2443
|
+
endPoint,
|
|
2444
|
+
conn.fromColor,
|
|
2445
|
+
style.color,
|
|
2446
|
+
conn.toColor
|
|
2447
|
+
);
|
|
2420
2448
|
if (useElkRoute) {
|
|
2421
|
-
drawCubicInterpolatedPath(ctx, linePoints, style);
|
|
2449
|
+
drawCubicInterpolatedPath(ctx, linePoints, style, stroke);
|
|
2422
2450
|
} else {
|
|
2423
|
-
|
|
2451
|
+
drawOrthogonalPathWithStroke(ctx, startPoint, endPoint, style, stroke);
|
|
2424
2452
|
}
|
|
2425
2453
|
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
2426
2454
|
}
|
|
@@ -2725,6 +2753,9 @@ function measureTextBounds(ctx, options) {
|
|
|
2725
2753
|
function angleBetween(from, to) {
|
|
2726
2754
|
return Math.atan2(to.y - from.y, to.x - from.x);
|
|
2727
2755
|
}
|
|
2756
|
+
function degreesToRadians(angle) {
|
|
2757
|
+
return angle * Math.PI / 180;
|
|
2758
|
+
}
|
|
2728
2759
|
function pathBounds(operations) {
|
|
2729
2760
|
let minX = Number.POSITIVE_INFINITY;
|
|
2730
2761
|
let minY = Number.POSITIVE_INFINITY;
|
|
@@ -2962,6 +2993,34 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
2962
2993
|
});
|
|
2963
2994
|
break;
|
|
2964
2995
|
}
|
|
2996
|
+
case "arc": {
|
|
2997
|
+
const startAngle = degreesToRadians(command.startAngle);
|
|
2998
|
+
const endAngle = degreesToRadians(command.endAngle);
|
|
2999
|
+
withOpacity(ctx, command.opacity, () => {
|
|
3000
|
+
applyDrawShadow(ctx, command.shadow);
|
|
3001
|
+
ctx.beginPath();
|
|
3002
|
+
ctx.setLineDash(command.dash ?? []);
|
|
3003
|
+
ctx.lineWidth = command.width;
|
|
3004
|
+
ctx.strokeStyle = command.color;
|
|
3005
|
+
ctx.arc(command.center.x, command.center.y, command.radius, startAngle, endAngle);
|
|
3006
|
+
ctx.stroke();
|
|
3007
|
+
});
|
|
3008
|
+
rendered.push({
|
|
3009
|
+
id,
|
|
3010
|
+
kind: "draw",
|
|
3011
|
+
bounds: expandRect(
|
|
3012
|
+
{
|
|
3013
|
+
x: command.center.x - command.radius,
|
|
3014
|
+
y: command.center.y - command.radius,
|
|
3015
|
+
width: command.radius * 2,
|
|
3016
|
+
height: command.radius * 2
|
|
3017
|
+
},
|
|
3018
|
+
command.width / 2
|
|
3019
|
+
),
|
|
3020
|
+
foregroundColor: command.color
|
|
3021
|
+
});
|
|
3022
|
+
break;
|
|
3023
|
+
}
|
|
2965
3024
|
case "bezier": {
|
|
2966
3025
|
const points = command.points;
|
|
2967
3026
|
withOpacity(ctx, command.opacity, () => {
|
|
@@ -3545,6 +3604,21 @@ var drawLineSchema = z2.object({
|
|
|
3545
3604
|
opacity: z2.number().min(0).max(1).default(1),
|
|
3546
3605
|
shadow: drawShadowSchema.optional()
|
|
3547
3606
|
}).strict();
|
|
3607
|
+
var drawArcSchema = z2.object({
|
|
3608
|
+
type: z2.literal("arc"),
|
|
3609
|
+
center: z2.object({
|
|
3610
|
+
x: z2.number(),
|
|
3611
|
+
y: z2.number()
|
|
3612
|
+
}).strict(),
|
|
3613
|
+
radius: z2.number().positive(),
|
|
3614
|
+
startAngle: z2.number(),
|
|
3615
|
+
endAngle: z2.number(),
|
|
3616
|
+
color: colorHexSchema2.default("#FFFFFF"),
|
|
3617
|
+
width: z2.number().min(0.5).max(32).default(2),
|
|
3618
|
+
dash: z2.array(z2.number()).max(6).optional(),
|
|
3619
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
3620
|
+
shadow: drawShadowSchema.optional()
|
|
3621
|
+
}).strict();
|
|
3548
3622
|
var drawPointSchema = z2.object({
|
|
3549
3623
|
x: z2.number(),
|
|
3550
3624
|
y: z2.number()
|
|
@@ -3629,6 +3703,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
3629
3703
|
drawCircleSchema,
|
|
3630
3704
|
drawTextSchema,
|
|
3631
3705
|
drawLineSchema,
|
|
3706
|
+
drawArcSchema,
|
|
3632
3707
|
drawBezierSchema,
|
|
3633
3708
|
drawPathSchema,
|
|
3634
3709
|
drawBadgeSchema,
|
|
@@ -3759,17 +3834,21 @@ var connectionElementSchema = z2.object({
|
|
|
3759
3834
|
from: z2.string().min(1).max(120),
|
|
3760
3835
|
to: z2.string().min(1).max(120),
|
|
3761
3836
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
3762
|
-
|
|
3837
|
+
/** @deprecated Use `style` instead. */
|
|
3838
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).optional(),
|
|
3763
3839
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
3764
3840
|
label: z2.string().min(1).max(200).optional(),
|
|
3765
3841
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
3766
3842
|
color: colorHexSchema2.optional(),
|
|
3843
|
+
fromColor: colorHexSchema2.optional(),
|
|
3844
|
+
toColor: colorHexSchema2.optional(),
|
|
3767
3845
|
width: z2.number().min(0.5).max(10).optional(),
|
|
3768
3846
|
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
3769
3847
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
3770
3848
|
arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
|
|
3771
3849
|
opacity: z2.number().min(0).max(1).default(1),
|
|
3772
|
-
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
3850
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc", "straight"]).default("auto"),
|
|
3851
|
+
curveMode: z2.enum(["normal", "ellipse"]).default("normal"),
|
|
3773
3852
|
tension: z2.number().min(0.1).max(0.8).default(0.35),
|
|
3774
3853
|
fromAnchor: anchorHintSchema.optional(),
|
|
3775
3854
|
toAnchor: anchorHintSchema.optional()
|
|
@@ -3862,7 +3941,11 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
3862
3941
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
3863
3942
|
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
3864
3943
|
/** Explicit center used by curve/arc connection routing. */
|
|
3865
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
3944
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
3945
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3946
|
+
ellipseRx: z2.number().positive().optional(),
|
|
3947
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3948
|
+
ellipseRy: z2.number().positive().optional()
|
|
3866
3949
|
}).strict();
|
|
3867
3950
|
var gridLayoutConfigSchema = z2.object({
|
|
3868
3951
|
mode: z2.literal("grid"),
|
|
@@ -3872,7 +3955,11 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
3872
3955
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
3873
3956
|
equalHeight: z2.boolean().default(false),
|
|
3874
3957
|
/** Explicit center used by curve/arc connection routing. */
|
|
3875
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
3958
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
3959
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3960
|
+
ellipseRx: z2.number().positive().optional(),
|
|
3961
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3962
|
+
ellipseRy: z2.number().positive().optional()
|
|
3876
3963
|
}).strict();
|
|
3877
3964
|
var stackLayoutConfigSchema = z2.object({
|
|
3878
3965
|
mode: z2.literal("stack"),
|
|
@@ -3880,7 +3967,11 @@ var stackLayoutConfigSchema = z2.object({
|
|
|
3880
3967
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
3881
3968
|
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
3882
3969
|
/** Explicit center used by curve/arc connection routing. */
|
|
3883
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
3970
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
3971
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3972
|
+
ellipseRx: z2.number().positive().optional(),
|
|
3973
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3974
|
+
ellipseRy: z2.number().positive().optional()
|
|
3884
3975
|
}).strict();
|
|
3885
3976
|
var manualPositionSchema = z2.object({
|
|
3886
3977
|
x: z2.number().int(),
|
|
@@ -3892,7 +3983,11 @@ var manualLayoutConfigSchema = z2.object({
|
|
|
3892
3983
|
mode: z2.literal("manual"),
|
|
3893
3984
|
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
3894
3985
|
/** Explicit center used by curve/arc connection routing. */
|
|
3895
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
3986
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
3987
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3988
|
+
ellipseRx: z2.number().positive().optional(),
|
|
3989
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
3990
|
+
ellipseRy: z2.number().positive().optional()
|
|
3896
3991
|
}).strict();
|
|
3897
3992
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
3898
3993
|
autoLayoutConfigSchema,
|
|
@@ -3957,7 +4052,11 @@ var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
|
3957
4052
|
var diagramLayoutSchema = z2.object({
|
|
3958
4053
|
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
3959
4054
|
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
3960
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
4055
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4056
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4057
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4058
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4059
|
+
ellipseRy: z2.number().positive().optional()
|
|
3961
4060
|
}).strict();
|
|
3962
4061
|
var diagramSpecSchema = z2.object({
|
|
3963
4062
|
version: z2.literal(1),
|
|
@@ -4149,6 +4248,18 @@ async function renderDesign(input, options = {}) {
|
|
|
4149
4248
|
const specHash = computeSpecHash(spec);
|
|
4150
4249
|
const generatorVersion = options.generatorVersion ?? DEFAULT_GENERATOR_VERSION;
|
|
4151
4250
|
const renderedAt = options.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4251
|
+
const iteration = options.iteration;
|
|
4252
|
+
if (iteration) {
|
|
4253
|
+
if (!Number.isInteger(iteration.iteration) || iteration.iteration <= 0) {
|
|
4254
|
+
throw new Error("Iteration metadata requires iteration to be a positive integer.");
|
|
4255
|
+
}
|
|
4256
|
+
if (iteration.maxIterations != null && (!Number.isInteger(iteration.maxIterations) || iteration.maxIterations <= 0)) {
|
|
4257
|
+
throw new Error("Iteration metadata requires maxIterations to be a positive integer.");
|
|
4258
|
+
}
|
|
4259
|
+
if (iteration.maxIterations != null && iteration.maxIterations < iteration.iteration) {
|
|
4260
|
+
throw new Error("Iteration metadata requires maxIterations to be >= iteration.");
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4152
4263
|
const renderScale = resolveRenderScale(spec);
|
|
4153
4264
|
const canvas = createCanvas(spec.canvas.width * renderScale, spec.canvas.height * renderScale);
|
|
4154
4265
|
const ctx = canvas.getContext("2d");
|
|
@@ -4328,10 +4439,19 @@ async function renderDesign(input, options = {}) {
|
|
|
4328
4439
|
break;
|
|
4329
4440
|
}
|
|
4330
4441
|
}
|
|
4331
|
-
const
|
|
4332
|
-
|
|
4333
|
-
|
|
4442
|
+
const nodeBounds = spec.elements.filter((element) => element.type !== "connection").map((element) => elementRects.get(element.id)).filter((rect) => rect != null);
|
|
4443
|
+
const diagramCenter = spec.layout.diagramCenter ?? computeDiagramCenter(nodeBounds, { x: spec.canvas.width / 2, y: spec.canvas.height / 2 });
|
|
4444
|
+
const layoutEllipseRx = "ellipseRx" in spec.layout ? spec.layout.ellipseRx : void 0;
|
|
4445
|
+
const layoutEllipseRy = "ellipseRy" in spec.layout ? spec.layout.ellipseRy : void 0;
|
|
4446
|
+
const hasEllipseConnections = spec.elements.some(
|
|
4447
|
+
(el) => el.type === "connection" && (el.curveMode === "ellipse" || el.routing === "arc")
|
|
4334
4448
|
);
|
|
4449
|
+
const ellipseParams = hasEllipseConnections ? inferEllipseParams(
|
|
4450
|
+
nodeBounds,
|
|
4451
|
+
spec.layout.diagramCenter ?? diagramCenter,
|
|
4452
|
+
layoutEllipseRx,
|
|
4453
|
+
layoutEllipseRy
|
|
4454
|
+
) : void 0;
|
|
4335
4455
|
for (const element of spec.elements) {
|
|
4336
4456
|
if (element.type !== "connection") {
|
|
4337
4457
|
continue;
|
|
@@ -4345,7 +4465,15 @@ async function renderDesign(input, options = {}) {
|
|
|
4345
4465
|
}
|
|
4346
4466
|
const edgeRoute = edgeRoutes?.get(`${element.from}-${element.to}`);
|
|
4347
4467
|
elements.push(
|
|
4348
|
-
...renderConnection(
|
|
4468
|
+
...renderConnection(
|
|
4469
|
+
ctx,
|
|
4470
|
+
element,
|
|
4471
|
+
fromRect,
|
|
4472
|
+
toRect,
|
|
4473
|
+
theme,
|
|
4474
|
+
edgeRoute,
|
|
4475
|
+
ellipseParams ? { diagramCenter, ellipseParams } : { diagramCenter }
|
|
4476
|
+
)
|
|
4349
4477
|
);
|
|
4350
4478
|
}
|
|
4351
4479
|
if (footerRect && spec.footer) {
|
|
@@ -4389,7 +4517,8 @@ async function renderDesign(input, options = {}) {
|
|
|
4389
4517
|
layout: {
|
|
4390
4518
|
safeFrame,
|
|
4391
4519
|
elements
|
|
4392
|
-
}
|
|
4520
|
+
},
|
|
4521
|
+
...iteration ? { iteration } : {}
|
|
4393
4522
|
};
|
|
4394
4523
|
return {
|
|
4395
4524
|
png: pngBuffer,
|