@spectratools/graphic-designer-cli 0.4.0 → 0.6.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/README.md +32 -2
- package/dist/cli.js +555 -60
- package/dist/index.d.ts +105 -5
- package/dist/index.js +578 -60
- package/dist/qa.d.ts +14 -3
- package/dist/qa.js +242 -11
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +293 -53
- package/dist/{spec.schema-BUTof436.d.ts → spec.schema-Dm_wOLTd.d.ts} +1375 -114
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +75 -8
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -115,9 +115,9 @@ function drawRoundedRect(ctx, rect, radius, fill, stroke) {
|
|
|
115
115
|
roundRectPath(ctx, rect, radius);
|
|
116
116
|
fillAndStroke(ctx, fill, stroke);
|
|
117
117
|
}
|
|
118
|
-
function drawCircle(ctx,
|
|
118
|
+
function drawCircle(ctx, center, radius, fill, stroke) {
|
|
119
119
|
ctx.beginPath();
|
|
120
|
-
ctx.arc(
|
|
120
|
+
ctx.arc(center.x, center.y, Math.max(0, radius), 0, Math.PI * 2);
|
|
121
121
|
ctx.closePath();
|
|
122
122
|
fillAndStroke(ctx, fill, stroke);
|
|
123
123
|
}
|
|
@@ -339,6 +339,10 @@ function relativeLuminance(hexColor) {
|
|
|
339
339
|
const b = srgbToLinear(rgb.b);
|
|
340
340
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
341
341
|
}
|
|
342
|
+
function withAlpha(hexColor, opacity) {
|
|
343
|
+
const rgb = parseHexColor(hexColor);
|
|
344
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
|
|
345
|
+
}
|
|
342
346
|
function blendColorWithOpacity(foreground, background, opacity) {
|
|
343
347
|
const fg = parseHexColor(foreground);
|
|
344
348
|
const bg = parseHexColor(background);
|
|
@@ -441,15 +445,34 @@ function renderFlowNode(ctx, node, bounds, theme) {
|
|
|
441
445
|
const badgeBackground = node.badgeBackground ?? borderColor ?? theme.accent;
|
|
442
446
|
ctx.save();
|
|
443
447
|
ctx.lineWidth = borderWidth;
|
|
448
|
+
if (node.shadow) {
|
|
449
|
+
const shadowColor = node.shadow.color ?? borderColor ?? theme.accent;
|
|
450
|
+
ctx.shadowColor = withAlpha(shadowColor, node.shadow.opacity);
|
|
451
|
+
ctx.shadowBlur = node.shadow.blur;
|
|
452
|
+
ctx.shadowOffsetX = node.shadow.offsetX;
|
|
453
|
+
ctx.shadowOffsetY = node.shadow.offsetY;
|
|
454
|
+
}
|
|
444
455
|
if (fillOpacity < 1) {
|
|
445
456
|
ctx.globalAlpha = node.opacity * fillOpacity;
|
|
446
457
|
drawNodeShape(ctx, node.shape, bounds, fillColor, void 0, cornerRadius);
|
|
458
|
+
if (node.shadow) {
|
|
459
|
+
ctx.shadowColor = "transparent";
|
|
460
|
+
ctx.shadowBlur = 0;
|
|
461
|
+
ctx.shadowOffsetX = 0;
|
|
462
|
+
ctx.shadowOffsetY = 0;
|
|
463
|
+
}
|
|
447
464
|
ctx.globalAlpha = node.opacity;
|
|
448
465
|
drawNodeShape(ctx, node.shape, bounds, "rgba(0,0,0,0)", borderColor, cornerRadius);
|
|
449
466
|
} else {
|
|
450
467
|
ctx.globalAlpha = node.opacity;
|
|
451
468
|
drawNodeShape(ctx, node.shape, bounds, fillColor, borderColor, cornerRadius);
|
|
452
469
|
}
|
|
470
|
+
if (node.shadow) {
|
|
471
|
+
ctx.shadowColor = "transparent";
|
|
472
|
+
ctx.shadowBlur = 0;
|
|
473
|
+
ctx.shadowOffsetX = 0;
|
|
474
|
+
ctx.shadowOffsetY = 0;
|
|
475
|
+
}
|
|
453
476
|
const headingFont = resolveFont(theme.fonts.heading, "heading");
|
|
454
477
|
const bodyFont = resolveFont(theme.fonts.body, "body");
|
|
455
478
|
const monoFont = resolveFont(theme.fonts.mono, "mono");
|
|
@@ -1121,7 +1144,7 @@ function parseHexColor2(color) {
|
|
|
1121
1144
|
a: normalized.length === 8 ? parseChannel2(6) / 255 : 1
|
|
1122
1145
|
};
|
|
1123
1146
|
}
|
|
1124
|
-
function
|
|
1147
|
+
function withAlpha2(color, alpha) {
|
|
1125
1148
|
const parsed = parseHexColor2(color);
|
|
1126
1149
|
const effectiveAlpha = clamp01(parsed.a * alpha);
|
|
1127
1150
|
return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${effectiveAlpha})`;
|
|
@@ -1178,9 +1201,9 @@ function drawVignette(ctx, width, height, intensity = 0.3, color = "#000000") {
|
|
|
1178
1201
|
centerY,
|
|
1179
1202
|
outerRadius
|
|
1180
1203
|
);
|
|
1181
|
-
vignette.addColorStop(0,
|
|
1182
|
-
vignette.addColorStop(0.6,
|
|
1183
|
-
vignette.addColorStop(1,
|
|
1204
|
+
vignette.addColorStop(0, withAlpha2(color, 0));
|
|
1205
|
+
vignette.addColorStop(0.6, withAlpha2(color, 0));
|
|
1206
|
+
vignette.addColorStop(1, withAlpha2(color, clamp01(intensity)));
|
|
1184
1207
|
ctx.save();
|
|
1185
1208
|
ctx.fillStyle = vignette;
|
|
1186
1209
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -1311,12 +1334,12 @@ var MACOS_DOTS = [
|
|
|
1311
1334
|
{ fill: "#27C93F", stroke: "#1AAB29" }
|
|
1312
1335
|
];
|
|
1313
1336
|
function drawMacosDots(ctx, x, y) {
|
|
1314
|
-
for (const [index,
|
|
1337
|
+
for (const [index, dot2] of MACOS_DOTS.entries()) {
|
|
1315
1338
|
ctx.beginPath();
|
|
1316
1339
|
ctx.arc(x + index * DOT_SPACING, y, DOT_RADIUS, 0, Math.PI * 2);
|
|
1317
1340
|
ctx.closePath();
|
|
1318
|
-
ctx.fillStyle =
|
|
1319
|
-
ctx.strokeStyle =
|
|
1341
|
+
ctx.fillStyle = dot2.fill;
|
|
1342
|
+
ctx.strokeStyle = dot2.stroke;
|
|
1320
1343
|
ctx.lineWidth = DOT_STROKE_WIDTH;
|
|
1321
1344
|
ctx.fill();
|
|
1322
1345
|
ctx.stroke();
|
|
@@ -1965,25 +1988,134 @@ function drawOrthogonalPath(ctx, from, to, style) {
|
|
|
1965
1988
|
}
|
|
1966
1989
|
|
|
1967
1990
|
// src/renderers/connection.ts
|
|
1968
|
-
|
|
1991
|
+
var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
|
|
1992
|
+
function rectCenter(rect) {
|
|
1969
1993
|
return {
|
|
1970
1994
|
x: rect.x + rect.width / 2,
|
|
1971
1995
|
y: rect.y + rect.height / 2
|
|
1972
1996
|
};
|
|
1973
1997
|
}
|
|
1974
|
-
function edgeAnchor(
|
|
1975
|
-
const c =
|
|
1998
|
+
function edgeAnchor(bounds, target) {
|
|
1999
|
+
const c = rectCenter(bounds);
|
|
1976
2000
|
const dx = target.x - c.x;
|
|
1977
2001
|
const dy = target.y - c.y;
|
|
1978
|
-
if (
|
|
1979
|
-
return {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2002
|
+
if (dx === 0 && dy === 0) {
|
|
2003
|
+
return { x: c.x, y: c.y - bounds.height / 2 };
|
|
2004
|
+
}
|
|
2005
|
+
const hw = bounds.width / 2;
|
|
2006
|
+
const hh = bounds.height / 2;
|
|
2007
|
+
const absDx = Math.abs(dx);
|
|
2008
|
+
const absDy = Math.abs(dy);
|
|
2009
|
+
const t = absDx * hh > absDy * hw ? hw / absDx : hh / absDy;
|
|
2010
|
+
return { x: c.x + dx * t, y: c.y + dy * t };
|
|
2011
|
+
}
|
|
2012
|
+
function outwardNormal(point, diagramCenter) {
|
|
2013
|
+
const dx = point.x - diagramCenter.x;
|
|
2014
|
+
const dy = point.y - diagramCenter.y;
|
|
2015
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
2016
|
+
return { x: dx / len, y: dy / len };
|
|
2017
|
+
}
|
|
2018
|
+
function curveRoute(fromBounds, toBounds, diagramCenter, tension) {
|
|
2019
|
+
const fromCenter = rectCenter(fromBounds);
|
|
2020
|
+
const toCenter = rectCenter(toBounds);
|
|
2021
|
+
const p0 = edgeAnchor(fromBounds, toCenter);
|
|
2022
|
+
const p3 = edgeAnchor(toBounds, fromCenter);
|
|
2023
|
+
const dist = Math.hypot(p3.x - p0.x, p3.y - p0.y);
|
|
2024
|
+
const offset = dist * tension;
|
|
2025
|
+
const n0 = outwardNormal(p0, diagramCenter);
|
|
2026
|
+
const n3 = outwardNormal(p3, diagramCenter);
|
|
2027
|
+
const cp1 = { x: p0.x + n0.x * offset, y: p0.y + n0.y * offset };
|
|
2028
|
+
const cp2 = { x: p3.x + n3.x * offset, y: p3.y + n3.y * offset };
|
|
2029
|
+
return [p0, cp1, cp2, p3];
|
|
2030
|
+
}
|
|
2031
|
+
function dot(a, b) {
|
|
2032
|
+
return a.x * b.x + a.y * b.y;
|
|
2033
|
+
}
|
|
2034
|
+
function localToWorld(origin, axisX, axisY, local) {
|
|
2035
|
+
return {
|
|
2036
|
+
x: origin.x + axisX.x * local.x + axisY.x * local.y,
|
|
2037
|
+
y: origin.y + axisX.y * local.x + axisY.y * local.y
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
function arcRoute(fromBounds, toBounds, diagramCenter, tension) {
|
|
2041
|
+
const fromCenter = rectCenter(fromBounds);
|
|
2042
|
+
const toCenter = rectCenter(toBounds);
|
|
2043
|
+
const start = edgeAnchor(fromBounds, toCenter);
|
|
2044
|
+
const end = edgeAnchor(toBounds, fromCenter);
|
|
2045
|
+
const chord = { x: end.x - start.x, y: end.y - start.y };
|
|
2046
|
+
const chordLength = Math.hypot(chord.x, chord.y);
|
|
2047
|
+
if (chordLength < 1e-6) {
|
|
2048
|
+
const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
|
2049
|
+
return [
|
|
2050
|
+
[start, start, mid, mid],
|
|
2051
|
+
[mid, mid, end, end]
|
|
2052
|
+
];
|
|
1983
2053
|
}
|
|
2054
|
+
const axisX = { x: chord.x / chordLength, y: chord.y / chordLength };
|
|
2055
|
+
let axisY = { x: -axisX.y, y: axisX.x };
|
|
2056
|
+
const midpoint = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
|
2057
|
+
const outwardHint = outwardNormal(midpoint, diagramCenter);
|
|
2058
|
+
if (dot(axisY, outwardHint) < 0) {
|
|
2059
|
+
axisY = { x: -axisY.x, y: -axisY.y };
|
|
2060
|
+
}
|
|
2061
|
+
const semiMajor = chordLength / 2;
|
|
2062
|
+
const semiMinor = Math.max(12, chordLength * tension * 0.75);
|
|
2063
|
+
const p0Local = { x: -semiMajor, y: 0 };
|
|
2064
|
+
const cp1Local = { x: -semiMajor, y: ELLIPSE_KAPPA * semiMinor };
|
|
2065
|
+
const cp2Local = { x: -ELLIPSE_KAPPA * semiMajor, y: semiMinor };
|
|
2066
|
+
const pMidLocal = { x: 0, y: semiMinor };
|
|
2067
|
+
const cp3Local = { x: ELLIPSE_KAPPA * semiMajor, y: semiMinor };
|
|
2068
|
+
const cp4Local = { x: semiMajor, y: ELLIPSE_KAPPA * semiMinor };
|
|
2069
|
+
const p3Local = { x: semiMajor, y: 0 };
|
|
2070
|
+
const p0 = localToWorld(midpoint, axisX, axisY, p0Local);
|
|
2071
|
+
const cp1 = localToWorld(midpoint, axisX, axisY, cp1Local);
|
|
2072
|
+
const cp2 = localToWorld(midpoint, axisX, axisY, cp2Local);
|
|
2073
|
+
const pMid = localToWorld(midpoint, axisX, axisY, pMidLocal);
|
|
2074
|
+
const cp3 = localToWorld(midpoint, axisX, axisY, cp3Local);
|
|
2075
|
+
const cp4 = localToWorld(midpoint, axisX, axisY, cp4Local);
|
|
2076
|
+
const p3 = localToWorld(midpoint, axisX, axisY, p3Local);
|
|
2077
|
+
return [
|
|
2078
|
+
[p0, cp1, cp2, pMid],
|
|
2079
|
+
[pMid, cp3, cp4, p3]
|
|
2080
|
+
];
|
|
2081
|
+
}
|
|
2082
|
+
function orthogonalRoute(fromBounds, toBounds) {
|
|
2083
|
+
const fromC = rectCenter(fromBounds);
|
|
2084
|
+
const toC = rectCenter(toBounds);
|
|
2085
|
+
const p0 = edgeAnchor(fromBounds, toC);
|
|
2086
|
+
const p3 = edgeAnchor(toBounds, fromC);
|
|
2087
|
+
const midX = (p0.x + p3.x) / 2;
|
|
2088
|
+
return [p0, { x: midX, y: p0.y }, { x: midX, y: p3.y }, p3];
|
|
2089
|
+
}
|
|
2090
|
+
function bezierPointAt(p0, cp1, cp2, p3, t) {
|
|
2091
|
+
const mt = 1 - t;
|
|
1984
2092
|
return {
|
|
1985
|
-
x:
|
|
1986
|
-
y:
|
|
2093
|
+
x: mt * mt * mt * p0.x + 3 * mt * mt * t * cp1.x + 3 * mt * t * t * cp2.x + t * t * t * p3.x,
|
|
2094
|
+
y: mt * mt * mt * p0.y + 3 * mt * mt * t * cp1.y + 3 * mt * t * t * cp2.y + t * t * t * p3.y
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function pointAlongArc(route, t) {
|
|
2098
|
+
const [first, second] = route;
|
|
2099
|
+
if (t <= 0.5) {
|
|
2100
|
+
const localT2 = Math.max(0, Math.min(1, t * 2));
|
|
2101
|
+
return bezierPointAt(first[0], first[1], first[2], first[3], localT2);
|
|
2102
|
+
}
|
|
2103
|
+
const localT = Math.max(0, Math.min(1, (t - 0.5) * 2));
|
|
2104
|
+
return bezierPointAt(second[0], second[1], second[2], second[3], localT);
|
|
2105
|
+
}
|
|
2106
|
+
function computeDiagramCenter(nodeBounds, canvasCenter) {
|
|
2107
|
+
if (nodeBounds.length === 0) {
|
|
2108
|
+
return canvasCenter ?? { x: 0, y: 0 };
|
|
2109
|
+
}
|
|
2110
|
+
let totalX = 0;
|
|
2111
|
+
let totalY = 0;
|
|
2112
|
+
for (const bounds of nodeBounds) {
|
|
2113
|
+
totalX += bounds.x + bounds.width / 2;
|
|
2114
|
+
totalY += bounds.y + bounds.height / 2;
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
x: totalX / nodeBounds.length,
|
|
2118
|
+
y: totalY / nodeBounds.length
|
|
1987
2119
|
};
|
|
1988
2120
|
}
|
|
1989
2121
|
function dashFromStyle(style) {
|
|
@@ -2067,51 +2199,95 @@ function polylineBounds(points) {
|
|
|
2067
2199
|
height: Math.max(1, maxY - minY)
|
|
2068
2200
|
};
|
|
2069
2201
|
}
|
|
2070
|
-
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute) {
|
|
2071
|
-
const
|
|
2072
|
-
const
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
const dash = dashFromStyle(
|
|
2202
|
+
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, options) {
|
|
2203
|
+
const routing = conn.routing ?? "auto";
|
|
2204
|
+
const strokeStyle = conn.strokeStyle ?? conn.style ?? "solid";
|
|
2205
|
+
const strokeWidth = conn.width ?? conn.strokeWidth ?? 2;
|
|
2206
|
+
const tension = conn.tension ?? 0.35;
|
|
2207
|
+
const dash = dashFromStyle(strokeStyle);
|
|
2076
2208
|
const style = {
|
|
2077
2209
|
color: conn.color ?? theme.borderMuted,
|
|
2078
|
-
width:
|
|
2210
|
+
width: strokeWidth,
|
|
2079
2211
|
headSize: conn.arrowSize ?? 10,
|
|
2080
2212
|
...dash ? { dash } : {}
|
|
2081
2213
|
};
|
|
2082
|
-
const
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
let
|
|
2087
|
-
let
|
|
2214
|
+
const labelT = conn.labelPosition === "start" ? 0.2 : conn.labelPosition === "end" ? 0.8 : 0.5;
|
|
2215
|
+
const diagramCenter = options?.diagramCenter ?? computeDiagramCenter([fromBounds, toBounds]);
|
|
2216
|
+
let linePoints;
|
|
2217
|
+
let startPoint;
|
|
2218
|
+
let endPoint;
|
|
2219
|
+
let startAngle;
|
|
2220
|
+
let endAngle;
|
|
2221
|
+
let labelPoint;
|
|
2222
|
+
ctx.save();
|
|
2223
|
+
ctx.globalAlpha = conn.opacity;
|
|
2224
|
+
if (routing === "curve") {
|
|
2225
|
+
const [p0, cp1, cp2, p3] = curveRoute(fromBounds, toBounds, diagramCenter, tension);
|
|
2226
|
+
ctx.strokeStyle = style.color;
|
|
2227
|
+
ctx.lineWidth = style.width;
|
|
2228
|
+
ctx.setLineDash(style.dash ?? []);
|
|
2229
|
+
ctx.beginPath();
|
|
2230
|
+
ctx.moveTo(p0.x, p0.y);
|
|
2231
|
+
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, p3.x, p3.y);
|
|
2232
|
+
ctx.stroke();
|
|
2233
|
+
linePoints = [p0, cp1, cp2, p3];
|
|
2234
|
+
startPoint = p0;
|
|
2235
|
+
endPoint = p3;
|
|
2236
|
+
startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
|
|
2237
|
+
endAngle = Math.atan2(p3.y - cp2.y, p3.x - cp2.x);
|
|
2238
|
+
labelPoint = bezierPointAt(p0, cp1, cp2, p3, labelT);
|
|
2239
|
+
} else if (routing === "arc") {
|
|
2240
|
+
const [first, second] = arcRoute(fromBounds, toBounds, diagramCenter, tension);
|
|
2241
|
+
const [p0, cp1, cp2, pMid] = first;
|
|
2242
|
+
const [, cp3, cp4, p3] = second;
|
|
2243
|
+
ctx.strokeStyle = style.color;
|
|
2244
|
+
ctx.lineWidth = style.width;
|
|
2245
|
+
ctx.setLineDash(style.dash ?? []);
|
|
2246
|
+
ctx.beginPath();
|
|
2247
|
+
ctx.moveTo(p0.x, p0.y);
|
|
2248
|
+
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, pMid.x, pMid.y);
|
|
2249
|
+
ctx.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, p3.x, p3.y);
|
|
2250
|
+
ctx.stroke();
|
|
2251
|
+
linePoints = [p0, cp1, cp2, pMid, cp3, cp4, p3];
|
|
2252
|
+
startPoint = p0;
|
|
2253
|
+
endPoint = p3;
|
|
2254
|
+
startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
|
|
2255
|
+
endAngle = Math.atan2(p3.y - cp4.y, p3.x - cp4.x);
|
|
2256
|
+
labelPoint = pointAlongArc([first, second], labelT);
|
|
2257
|
+
} else {
|
|
2258
|
+
const useElkRoute = routing === "auto" && (edgeRoute?.points.length ?? 0) >= 2;
|
|
2259
|
+
linePoints = useElkRoute ? edgeRoute?.points ?? orthogonalRoute(fromBounds, toBounds) : orthogonalRoute(fromBounds, toBounds);
|
|
2260
|
+
startPoint = linePoints[0];
|
|
2261
|
+
const startSegment = linePoints[1] ?? linePoints[0];
|
|
2262
|
+
const endStart = linePoints[linePoints.length - 2] ?? linePoints[0];
|
|
2263
|
+
endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
|
|
2264
|
+
startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
|
|
2265
|
+
endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
|
|
2266
|
+
if (useElkRoute) {
|
|
2267
|
+
drawCubicInterpolatedPath(ctx, linePoints, style);
|
|
2268
|
+
} else {
|
|
2269
|
+
drawOrthogonalPath(ctx, startPoint, endPoint, style);
|
|
2270
|
+
}
|
|
2271
|
+
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
2272
|
+
}
|
|
2088
2273
|
if (!Number.isFinite(startAngle)) {
|
|
2089
2274
|
startAngle = 0;
|
|
2090
2275
|
}
|
|
2091
2276
|
if (!Number.isFinite(endAngle)) {
|
|
2092
2277
|
endAngle = 0;
|
|
2093
2278
|
}
|
|
2094
|
-
const t = conn.labelPosition === "start" ? 0.2 : conn.labelPosition === "end" ? 0.8 : 0.5;
|
|
2095
|
-
const labelPoint = pointAlongPolyline(points, t);
|
|
2096
|
-
ctx.save();
|
|
2097
|
-
ctx.globalAlpha = conn.opacity;
|
|
2098
|
-
if (edgeRoute && edgeRoute.points.length >= 2) {
|
|
2099
|
-
drawCubicInterpolatedPath(ctx, points, style);
|
|
2100
|
-
} else {
|
|
2101
|
-
drawOrthogonalPath(ctx, points[0], points[points.length - 1], style);
|
|
2102
|
-
}
|
|
2103
2279
|
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
2104
|
-
drawArrowhead(ctx,
|
|
2280
|
+
drawArrowhead(ctx, startPoint, startAngle, style.headSize, style.color);
|
|
2105
2281
|
}
|
|
2106
2282
|
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
2107
|
-
drawArrowhead(ctx,
|
|
2283
|
+
drawArrowhead(ctx, endPoint, endAngle, style.headSize, style.color);
|
|
2108
2284
|
}
|
|
2109
2285
|
ctx.restore();
|
|
2110
2286
|
const elements = [
|
|
2111
2287
|
{
|
|
2112
2288
|
id: `connection-${conn.from}-${conn.to}`,
|
|
2113
2289
|
kind: "connection",
|
|
2114
|
-
bounds: polylineBounds(
|
|
2290
|
+
bounds: polylineBounds(linePoints),
|
|
2115
2291
|
foregroundColor: style.color
|
|
2116
2292
|
}
|
|
2117
2293
|
];
|
|
@@ -3190,10 +3366,26 @@ var cardElementSchema = z2.object({
|
|
|
3190
3366
|
tone: z2.enum(["neutral", "accent", "success", "warning", "error"]).default("neutral"),
|
|
3191
3367
|
icon: z2.string().min(1).max(64).optional()
|
|
3192
3368
|
}).strict();
|
|
3369
|
+
var flowNodeShadowSchema = z2.object({
|
|
3370
|
+
color: colorHexSchema2.optional(),
|
|
3371
|
+
blur: z2.number().min(0).max(64).default(8),
|
|
3372
|
+
offsetX: z2.number().min(-32).max(32).default(0),
|
|
3373
|
+
offsetY: z2.number().min(-32).max(32).default(0),
|
|
3374
|
+
opacity: z2.number().min(0).max(1).default(0.3)
|
|
3375
|
+
}).strict();
|
|
3193
3376
|
var flowNodeElementSchema = z2.object({
|
|
3194
3377
|
type: z2.literal("flow-node"),
|
|
3195
3378
|
id: z2.string().min(1).max(120),
|
|
3196
|
-
shape: z2.enum([
|
|
3379
|
+
shape: z2.enum([
|
|
3380
|
+
"box",
|
|
3381
|
+
"rounded-box",
|
|
3382
|
+
"diamond",
|
|
3383
|
+
"circle",
|
|
3384
|
+
"pill",
|
|
3385
|
+
"cylinder",
|
|
3386
|
+
"parallelogram",
|
|
3387
|
+
"hexagon"
|
|
3388
|
+
]).default("rounded-box"),
|
|
3197
3389
|
label: z2.string().min(1).max(200),
|
|
3198
3390
|
sublabel: z2.string().min(1).max(300).optional(),
|
|
3199
3391
|
sublabelColor: colorHexSchema2.optional(),
|
|
@@ -3213,20 +3405,25 @@ var flowNodeElementSchema = z2.object({
|
|
|
3213
3405
|
badgeText: z2.string().min(1).max(32).optional(),
|
|
3214
3406
|
badgeColor: colorHexSchema2.optional(),
|
|
3215
3407
|
badgeBackground: colorHexSchema2.optional(),
|
|
3216
|
-
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top")
|
|
3408
|
+
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
|
|
3409
|
+
shadow: flowNodeShadowSchema.optional()
|
|
3217
3410
|
}).strict();
|
|
3218
3411
|
var connectionElementSchema = z2.object({
|
|
3219
3412
|
type: z2.literal("connection"),
|
|
3220
3413
|
from: z2.string().min(1).max(120),
|
|
3221
3414
|
to: z2.string().min(1).max(120),
|
|
3222
3415
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
3416
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
3223
3417
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
3224
3418
|
label: z2.string().min(1).max(200).optional(),
|
|
3225
3419
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
3226
3420
|
color: colorHexSchema2.optional(),
|
|
3227
|
-
width: z2.number().min(0.5).max(
|
|
3421
|
+
width: z2.number().min(0.5).max(10).optional(),
|
|
3422
|
+
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
3228
3423
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
3229
|
-
opacity: z2.number().min(0).max(1).default(1)
|
|
3424
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
3425
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
3426
|
+
tension: z2.number().min(0.1).max(0.8).default(0.35)
|
|
3230
3427
|
}).strict();
|
|
3231
3428
|
var codeBlockStyleSchema = z2.object({
|
|
3232
3429
|
paddingVertical: z2.number().min(0).max(128).default(56),
|
|
@@ -3295,6 +3492,10 @@ var elementSchema = z2.discriminatedUnion("type", [
|
|
|
3295
3492
|
shapeElementSchema,
|
|
3296
3493
|
imageElementSchema
|
|
3297
3494
|
]);
|
|
3495
|
+
var diagramCenterSchema = z2.object({
|
|
3496
|
+
x: z2.number(),
|
|
3497
|
+
y: z2.number()
|
|
3498
|
+
}).strict();
|
|
3298
3499
|
var autoLayoutConfigSchema = z2.object({
|
|
3299
3500
|
mode: z2.literal("auto"),
|
|
3300
3501
|
algorithm: z2.enum(["layered", "stress", "force", "radial", "box"]).default("layered"),
|
|
@@ -3310,7 +3511,9 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
3310
3511
|
/** Compaction strategy for radial layout. Only relevant when algorithm is 'radial'. */
|
|
3311
3512
|
radialCompaction: z2.enum(["none", "radial", "wedge"]).optional(),
|
|
3312
3513
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
3313
|
-
radialSortBy: z2.enum(["id", "connections"]).optional()
|
|
3514
|
+
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
3515
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3516
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3314
3517
|
}).strict();
|
|
3315
3518
|
var gridLayoutConfigSchema = z2.object({
|
|
3316
3519
|
mode: z2.literal("grid"),
|
|
@@ -3318,13 +3521,17 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
3318
3521
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
3319
3522
|
cardMinHeight: z2.number().int().min(32).max(4096).optional(),
|
|
3320
3523
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
3321
|
-
equalHeight: z2.boolean().default(false)
|
|
3524
|
+
equalHeight: z2.boolean().default(false),
|
|
3525
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3526
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3322
3527
|
}).strict();
|
|
3323
3528
|
var stackLayoutConfigSchema = z2.object({
|
|
3324
3529
|
mode: z2.literal("stack"),
|
|
3325
3530
|
direction: z2.enum(["vertical", "horizontal"]).default("vertical"),
|
|
3326
3531
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
3327
|
-
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch")
|
|
3532
|
+
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
3533
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3534
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3328
3535
|
}).strict();
|
|
3329
3536
|
var manualPositionSchema = z2.object({
|
|
3330
3537
|
x: z2.number().int(),
|
|
@@ -3334,7 +3541,9 @@ var manualPositionSchema = z2.object({
|
|
|
3334
3541
|
}).strict();
|
|
3335
3542
|
var manualLayoutConfigSchema = z2.object({
|
|
3336
3543
|
mode: z2.literal("manual"),
|
|
3337
|
-
positions: z2.record(z2.string().min(1), manualPositionSchema).default({})
|
|
3544
|
+
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
3545
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3546
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3338
3547
|
}).strict();
|
|
3339
3548
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
3340
3549
|
autoLayoutConfigSchema,
|
|
@@ -3386,6 +3595,31 @@ var canvasSchema = z2.object({
|
|
|
3386
3595
|
padding: z2.number().int().min(0).max(256).default(defaultCanvas.padding)
|
|
3387
3596
|
}).strict();
|
|
3388
3597
|
var themeInputSchema = z2.union([builtInThemeSchema, themeSchema]);
|
|
3598
|
+
var diagramPositionSchema = z2.object({
|
|
3599
|
+
x: z2.number(),
|
|
3600
|
+
y: z2.number(),
|
|
3601
|
+
width: z2.number().positive(),
|
|
3602
|
+
height: z2.number().positive()
|
|
3603
|
+
}).strict();
|
|
3604
|
+
var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
3605
|
+
flowNodeElementSchema,
|
|
3606
|
+
connectionElementSchema
|
|
3607
|
+
]);
|
|
3608
|
+
var diagramLayoutSchema = z2.object({
|
|
3609
|
+
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
3610
|
+
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
3611
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3612
|
+
}).strict();
|
|
3613
|
+
var diagramSpecSchema = z2.object({
|
|
3614
|
+
version: z2.literal(1),
|
|
3615
|
+
canvas: z2.object({
|
|
3616
|
+
width: z2.number().int().min(320).max(4096).default(1200),
|
|
3617
|
+
height: z2.number().int().min(180).max(4096).default(675)
|
|
3618
|
+
}).default({ width: 1200, height: 675 }),
|
|
3619
|
+
theme: themeSchema.optional(),
|
|
3620
|
+
elements: z2.array(diagramElementSchema).min(1),
|
|
3621
|
+
layout: diagramLayoutSchema.default({ mode: "manual" })
|
|
3622
|
+
}).strict();
|
|
3389
3623
|
var designSpecSchema = z2.object({
|
|
3390
3624
|
version: z2.literal(2).default(2),
|
|
3391
3625
|
canvas: canvasSchema.default(defaultCanvas),
|
|
@@ -3745,6 +3979,10 @@ async function renderDesign(input, options = {}) {
|
|
|
3745
3979
|
break;
|
|
3746
3980
|
}
|
|
3747
3981
|
}
|
|
3982
|
+
const diagramCenter = spec.layout.diagramCenter ?? computeDiagramCenter(
|
|
3983
|
+
spec.elements.filter((element) => element.type !== "connection").map((element) => elementRects.get(element.id)).filter((rect) => rect != null),
|
|
3984
|
+
{ x: spec.canvas.width / 2, y: spec.canvas.height / 2 }
|
|
3985
|
+
);
|
|
3748
3986
|
for (const element of spec.elements) {
|
|
3749
3987
|
if (element.type !== "connection") {
|
|
3750
3988
|
continue;
|
|
@@ -3757,7 +3995,9 @@ async function renderDesign(input, options = {}) {
|
|
|
3757
3995
|
);
|
|
3758
3996
|
}
|
|
3759
3997
|
const edgeRoute = edgeRoutes?.get(`${element.from}-${element.to}`);
|
|
3760
|
-
elements.push(
|
|
3998
|
+
elements.push(
|
|
3999
|
+
...renderConnection(ctx, element, fromRect, toRect, theme, edgeRoute, { diagramCenter })
|
|
4000
|
+
);
|
|
3761
4001
|
}
|
|
3762
4002
|
if (footerRect && spec.footer) {
|
|
3763
4003
|
const footerText = spec.footer.tagline ? `${spec.footer.text} \u2022 ${spec.footer.tagline}` : spec.footer.text;
|