@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/index.js
CHANGED
|
@@ -829,6 +829,21 @@ var drawLineSchema = z2.object({
|
|
|
829
829
|
opacity: z2.number().min(0).max(1).default(1),
|
|
830
830
|
shadow: drawShadowSchema.optional()
|
|
831
831
|
}).strict();
|
|
832
|
+
var drawArcSchema = z2.object({
|
|
833
|
+
type: z2.literal("arc"),
|
|
834
|
+
center: z2.object({
|
|
835
|
+
x: z2.number(),
|
|
836
|
+
y: z2.number()
|
|
837
|
+
}).strict(),
|
|
838
|
+
radius: z2.number().positive(),
|
|
839
|
+
startAngle: z2.number(),
|
|
840
|
+
endAngle: z2.number(),
|
|
841
|
+
color: colorHexSchema2.default("#FFFFFF"),
|
|
842
|
+
width: z2.number().min(0.5).max(32).default(2),
|
|
843
|
+
dash: z2.array(z2.number()).max(6).optional(),
|
|
844
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
845
|
+
shadow: drawShadowSchema.optional()
|
|
846
|
+
}).strict();
|
|
832
847
|
var drawPointSchema = z2.object({
|
|
833
848
|
x: z2.number(),
|
|
834
849
|
y: z2.number()
|
|
@@ -913,6 +928,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
913
928
|
drawCircleSchema,
|
|
914
929
|
drawTextSchema,
|
|
915
930
|
drawLineSchema,
|
|
931
|
+
drawArcSchema,
|
|
916
932
|
drawBezierSchema,
|
|
917
933
|
drawPathSchema,
|
|
918
934
|
drawBadgeSchema,
|
|
@@ -1044,17 +1060,21 @@ var connectionElementSchema = z2.object({
|
|
|
1044
1060
|
from: z2.string().min(1).max(120),
|
|
1045
1061
|
to: z2.string().min(1).max(120),
|
|
1046
1062
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
1047
|
-
|
|
1063
|
+
/** @deprecated Use `style` instead. */
|
|
1064
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).optional(),
|
|
1048
1065
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
1049
1066
|
label: z2.string().min(1).max(200).optional(),
|
|
1050
1067
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
1051
1068
|
color: colorHexSchema2.optional(),
|
|
1069
|
+
fromColor: colorHexSchema2.optional(),
|
|
1070
|
+
toColor: colorHexSchema2.optional(),
|
|
1052
1071
|
width: z2.number().min(0.5).max(10).optional(),
|
|
1053
1072
|
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
1054
1073
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
1055
1074
|
arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
|
|
1056
1075
|
opacity: z2.number().min(0).max(1).default(1),
|
|
1057
|
-
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
1076
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc", "straight"]).default("auto"),
|
|
1077
|
+
curveMode: z2.enum(["normal", "ellipse"]).default("normal"),
|
|
1058
1078
|
tension: z2.number().min(0.1).max(0.8).default(0.35),
|
|
1059
1079
|
fromAnchor: anchorHintSchema.optional(),
|
|
1060
1080
|
toAnchor: anchorHintSchema.optional()
|
|
@@ -1147,7 +1167,11 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
1147
1167
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
1148
1168
|
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
1149
1169
|
/** Explicit center used by curve/arc connection routing. */
|
|
1150
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
1170
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1171
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1172
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1173
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1174
|
+
ellipseRy: z2.number().positive().optional()
|
|
1151
1175
|
}).strict();
|
|
1152
1176
|
var gridLayoutConfigSchema = z2.object({
|
|
1153
1177
|
mode: z2.literal("grid"),
|
|
@@ -1157,7 +1181,11 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
1157
1181
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
1158
1182
|
equalHeight: z2.boolean().default(false),
|
|
1159
1183
|
/** Explicit center used by curve/arc connection routing. */
|
|
1160
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
1184
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1185
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1186
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1187
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1188
|
+
ellipseRy: z2.number().positive().optional()
|
|
1161
1189
|
}).strict();
|
|
1162
1190
|
var stackLayoutConfigSchema = z2.object({
|
|
1163
1191
|
mode: z2.literal("stack"),
|
|
@@ -1165,7 +1193,11 @@ var stackLayoutConfigSchema = z2.object({
|
|
|
1165
1193
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
1166
1194
|
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
1167
1195
|
/** Explicit center used by curve/arc connection routing. */
|
|
1168
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
1196
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1197
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1198
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1199
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1200
|
+
ellipseRy: z2.number().positive().optional()
|
|
1169
1201
|
}).strict();
|
|
1170
1202
|
var manualPositionSchema = z2.object({
|
|
1171
1203
|
x: z2.number().int(),
|
|
@@ -1177,7 +1209,11 @@ var manualLayoutConfigSchema = z2.object({
|
|
|
1177
1209
|
mode: z2.literal("manual"),
|
|
1178
1210
|
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
1179
1211
|
/** Explicit center used by curve/arc connection routing. */
|
|
1180
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
1212
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1213
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1214
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1215
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1216
|
+
ellipseRy: z2.number().positive().optional()
|
|
1181
1217
|
}).strict();
|
|
1182
1218
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
1183
1219
|
autoLayoutConfigSchema,
|
|
@@ -1242,7 +1278,11 @@ var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
|
1242
1278
|
var diagramLayoutSchema = z2.object({
|
|
1243
1279
|
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
1244
1280
|
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
1245
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
1281
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1282
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1283
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1284
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1285
|
+
ellipseRy: z2.number().positive().optional()
|
|
1246
1286
|
}).strict();
|
|
1247
1287
|
var diagramSpecSchema = z2.object({
|
|
1248
1288
|
version: z2.literal(1),
|
|
@@ -3187,16 +3227,6 @@ function drawBezier(ctx, points, style) {
|
|
|
3187
3227
|
ctx.quadraticCurveTo(penultimate.x, penultimate.y, last.x, last.y);
|
|
3188
3228
|
ctx.stroke();
|
|
3189
3229
|
}
|
|
3190
|
-
function drawOrthogonalPath(ctx, from, to, style) {
|
|
3191
|
-
const midX = (from.x + to.x) / 2;
|
|
3192
|
-
applyLineStyle(ctx, style);
|
|
3193
|
-
ctx.beginPath();
|
|
3194
|
-
ctx.moveTo(from.x, from.y);
|
|
3195
|
-
ctx.lineTo(midX, from.y);
|
|
3196
|
-
ctx.lineTo(midX, to.y);
|
|
3197
|
-
ctx.lineTo(to.x, to.y);
|
|
3198
|
-
ctx.stroke();
|
|
3199
|
-
}
|
|
3200
3230
|
|
|
3201
3231
|
// src/renderers/connection.ts
|
|
3202
3232
|
var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
|
|
@@ -3330,6 +3360,72 @@ function arcRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAn
|
|
|
3330
3360
|
[pMid, cp3, cp4, p3]
|
|
3331
3361
|
];
|
|
3332
3362
|
}
|
|
3363
|
+
function inferEllipseParams(nodeBounds, explicitCenter, explicitRx, explicitRy) {
|
|
3364
|
+
if (nodeBounds.length === 0) {
|
|
3365
|
+
return {
|
|
3366
|
+
cx: explicitCenter?.x ?? 0,
|
|
3367
|
+
cy: explicitCenter?.y ?? 0,
|
|
3368
|
+
rx: explicitRx ?? 1,
|
|
3369
|
+
ry: explicitRy ?? 1
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
const centers = nodeBounds.map(rectCenter);
|
|
3373
|
+
const cx = explicitCenter?.x ?? centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
|
|
3374
|
+
const cy = explicitCenter?.y ?? centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
|
|
3375
|
+
const rx = explicitRx ?? Math.max(1, ...centers.map((c) => Math.abs(c.x - cx)));
|
|
3376
|
+
const ry = explicitRy ?? Math.max(1, ...centers.map((c) => Math.abs(c.y - cy)));
|
|
3377
|
+
return { cx, cy, rx, ry };
|
|
3378
|
+
}
|
|
3379
|
+
function ellipseRoute(fromBounds, toBounds, ellipse, fromAnchor, toAnchor) {
|
|
3380
|
+
const fromCenter = rectCenter(fromBounds);
|
|
3381
|
+
const toCenter = rectCenter(toBounds);
|
|
3382
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toCenter);
|
|
3383
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromCenter);
|
|
3384
|
+
const theta1 = Math.atan2(
|
|
3385
|
+
(fromCenter.y - ellipse.cy) / ellipse.ry,
|
|
3386
|
+
(fromCenter.x - ellipse.cx) / ellipse.rx
|
|
3387
|
+
);
|
|
3388
|
+
const theta2 = Math.atan2(
|
|
3389
|
+
(toCenter.y - ellipse.cy) / ellipse.ry,
|
|
3390
|
+
(toCenter.x - ellipse.cx) / ellipse.rx
|
|
3391
|
+
);
|
|
3392
|
+
let angularSpan = theta2 - theta1;
|
|
3393
|
+
while (angularSpan > Math.PI) angularSpan -= 2 * Math.PI;
|
|
3394
|
+
while (angularSpan <= -Math.PI) angularSpan += 2 * Math.PI;
|
|
3395
|
+
const absSpan = Math.abs(angularSpan);
|
|
3396
|
+
const kappa = absSpan < 1e-6 ? 0 : 4 / 3 * Math.tan(absSpan / 4);
|
|
3397
|
+
const tangent1 = {
|
|
3398
|
+
x: -ellipse.rx * Math.sin(theta1),
|
|
3399
|
+
y: ellipse.ry * Math.cos(theta1)
|
|
3400
|
+
};
|
|
3401
|
+
const tangent2 = {
|
|
3402
|
+
x: -ellipse.rx * Math.sin(theta2),
|
|
3403
|
+
y: ellipse.ry * Math.cos(theta2)
|
|
3404
|
+
};
|
|
3405
|
+
const len1 = Math.hypot(tangent1.x, tangent1.y) || 1;
|
|
3406
|
+
const len2 = Math.hypot(tangent2.x, tangent2.y) || 1;
|
|
3407
|
+
const norm1 = { x: tangent1.x / len1, y: tangent1.y / len1 };
|
|
3408
|
+
const norm2 = { x: tangent2.x / len2, y: tangent2.y / len2 };
|
|
3409
|
+
const chordLength = Math.hypot(p3.x - p0.x, p3.y - p0.y);
|
|
3410
|
+
const cpDistance = chordLength * kappa * 0.5;
|
|
3411
|
+
const sign = angularSpan >= 0 ? 1 : -1;
|
|
3412
|
+
const cp1 = {
|
|
3413
|
+
x: p0.x + norm1.x * cpDistance * sign,
|
|
3414
|
+
y: p0.y + norm1.y * cpDistance * sign
|
|
3415
|
+
};
|
|
3416
|
+
const cp2 = {
|
|
3417
|
+
x: p3.x - norm2.x * cpDistance * sign,
|
|
3418
|
+
y: p3.y - norm2.y * cpDistance * sign
|
|
3419
|
+
};
|
|
3420
|
+
return [p0, cp1, cp2, p3];
|
|
3421
|
+
}
|
|
3422
|
+
function straightRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
3423
|
+
const fromC = rectCenter(fromBounds);
|
|
3424
|
+
const toC = rectCenter(toBounds);
|
|
3425
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toC);
|
|
3426
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromC);
|
|
3427
|
+
return [p0, p3];
|
|
3428
|
+
}
|
|
3333
3429
|
function orthogonalRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
3334
3430
|
const fromC = rectCenter(fromBounds);
|
|
3335
3431
|
const toC = rectCenter(toBounds);
|
|
@@ -3374,15 +3470,6 @@ function findBoundaryIntersection(p0, cp1, cp2, p3, targetRect, searchFromEnd) {
|
|
|
3374
3470
|
}
|
|
3375
3471
|
return void 0;
|
|
3376
3472
|
}
|
|
3377
|
-
function pointAlongArc(route, t) {
|
|
3378
|
-
const [first, second] = route;
|
|
3379
|
-
if (t <= 0.5) {
|
|
3380
|
-
const localT2 = Math.max(0, Math.min(1, t * 2));
|
|
3381
|
-
return bezierPointAt(first[0], first[1], first[2], first[3], localT2);
|
|
3382
|
-
}
|
|
3383
|
-
const localT = Math.max(0, Math.min(1, (t - 0.5) * 2));
|
|
3384
|
-
return bezierPointAt(second[0], second[1], second[2], second[3], localT);
|
|
3385
|
-
}
|
|
3386
3473
|
function computeDiagramCenter(nodeBounds, canvasCenter) {
|
|
3387
3474
|
if (nodeBounds.length === 0) {
|
|
3388
3475
|
return canvasCenter ?? { x: 0, y: 0 };
|
|
@@ -3436,11 +3523,36 @@ function pointAlongPolyline(points, t) {
|
|
|
3436
3523
|
}
|
|
3437
3524
|
return points[points.length - 1];
|
|
3438
3525
|
}
|
|
3439
|
-
function
|
|
3526
|
+
function createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor) {
|
|
3527
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
3528
|
+
gradient.addColorStop(0, fromColor);
|
|
3529
|
+
gradient.addColorStop(0.5, baseColor);
|
|
3530
|
+
gradient.addColorStop(1, toColor);
|
|
3531
|
+
return gradient;
|
|
3532
|
+
}
|
|
3533
|
+
function resolveConnectionStroke(ctx, start, end, fromColor, baseColor, toColor) {
|
|
3534
|
+
if (!fromColor || !toColor) {
|
|
3535
|
+
return baseColor;
|
|
3536
|
+
}
|
|
3537
|
+
return createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor);
|
|
3538
|
+
}
|
|
3539
|
+
function drawOrthogonalPathWithStroke(ctx, from, to, style, stroke) {
|
|
3540
|
+
const midX = (from.x + to.x) / 2;
|
|
3541
|
+
ctx.strokeStyle = stroke;
|
|
3542
|
+
ctx.lineWidth = style.width;
|
|
3543
|
+
ctx.setLineDash(style.dash ?? []);
|
|
3544
|
+
ctx.beginPath();
|
|
3545
|
+
ctx.moveTo(from.x, from.y);
|
|
3546
|
+
ctx.lineTo(midX, from.y);
|
|
3547
|
+
ctx.lineTo(midX, to.y);
|
|
3548
|
+
ctx.lineTo(to.x, to.y);
|
|
3549
|
+
ctx.stroke();
|
|
3550
|
+
}
|
|
3551
|
+
function drawCubicInterpolatedPath(ctx, points, style, stroke) {
|
|
3440
3552
|
if (points.length < 2) {
|
|
3441
3553
|
return;
|
|
3442
3554
|
}
|
|
3443
|
-
ctx.strokeStyle =
|
|
3555
|
+
ctx.strokeStyle = stroke;
|
|
3444
3556
|
ctx.lineWidth = style.width;
|
|
3445
3557
|
ctx.setLineDash(style.dash ?? []);
|
|
3446
3558
|
ctx.beginPath();
|
|
@@ -3480,8 +3592,19 @@ function polylineBounds(points) {
|
|
|
3480
3592
|
};
|
|
3481
3593
|
}
|
|
3482
3594
|
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, options) {
|
|
3483
|
-
|
|
3484
|
-
|
|
3595
|
+
let routing = conn.routing ?? "auto";
|
|
3596
|
+
let curveMode = conn.curveMode ?? "normal";
|
|
3597
|
+
if (conn.strokeStyle !== void 0) {
|
|
3598
|
+
console.warn("connection.strokeStyle is deprecated, use style instead");
|
|
3599
|
+
}
|
|
3600
|
+
if (routing === "arc") {
|
|
3601
|
+
console.warn(
|
|
3602
|
+
"connection routing: 'arc' is deprecated. Use routing: 'curve' with curveMode: 'ellipse' instead."
|
|
3603
|
+
);
|
|
3604
|
+
routing = "curve";
|
|
3605
|
+
curveMode = "ellipse";
|
|
3606
|
+
}
|
|
3607
|
+
const strokeStyle = conn.style ?? conn.strokeStyle ?? "solid";
|
|
3485
3608
|
const strokeWidth = conn.width ?? conn.strokeWidth ?? 2;
|
|
3486
3609
|
const tension = conn.tension ?? 0.35;
|
|
3487
3610
|
const dash = dashFromStyle(strokeStyle);
|
|
@@ -3503,15 +3626,31 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3503
3626
|
ctx.globalAlpha = conn.opacity;
|
|
3504
3627
|
const arrowPlacement = conn.arrowPlacement ?? "endpoint";
|
|
3505
3628
|
if (routing === "curve") {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3629
|
+
let p0;
|
|
3630
|
+
let cp1;
|
|
3631
|
+
let cp2;
|
|
3632
|
+
let p3;
|
|
3633
|
+
if (curveMode === "ellipse") {
|
|
3634
|
+
const ellipse = options?.ellipseParams ?? inferEllipseParams([fromBounds, toBounds]);
|
|
3635
|
+
[p0, cp1, cp2, p3] = ellipseRoute(
|
|
3636
|
+
fromBounds,
|
|
3637
|
+
toBounds,
|
|
3638
|
+
ellipse,
|
|
3639
|
+
conn.fromAnchor,
|
|
3640
|
+
conn.toAnchor
|
|
3641
|
+
);
|
|
3642
|
+
} else {
|
|
3643
|
+
[p0, cp1, cp2, p3] = curveRoute(
|
|
3644
|
+
fromBounds,
|
|
3645
|
+
toBounds,
|
|
3646
|
+
diagramCenter,
|
|
3647
|
+
tension,
|
|
3648
|
+
conn.fromAnchor,
|
|
3649
|
+
conn.toAnchor
|
|
3650
|
+
);
|
|
3651
|
+
}
|
|
3652
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
3653
|
+
ctx.strokeStyle = stroke;
|
|
3515
3654
|
ctx.lineWidth = style.width;
|
|
3516
3655
|
ctx.setLineDash(style.dash ?? []);
|
|
3517
3656
|
ctx.beginPath();
|
|
@@ -3542,50 +3681,22 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3542
3681
|
}
|
|
3543
3682
|
}
|
|
3544
3683
|
}
|
|
3545
|
-
} else if (routing === "
|
|
3546
|
-
const [
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
diagramCenter,
|
|
3550
|
-
tension,
|
|
3551
|
-
conn.fromAnchor,
|
|
3552
|
-
conn.toAnchor
|
|
3553
|
-
);
|
|
3554
|
-
const [p0, cp1, cp2, pMid] = first;
|
|
3555
|
-
const [, cp3, cp4, p3] = second;
|
|
3556
|
-
ctx.strokeStyle = style.color;
|
|
3684
|
+
} else if (routing === "straight") {
|
|
3685
|
+
const [p0, p3] = straightRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor);
|
|
3686
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
3687
|
+
ctx.strokeStyle = stroke;
|
|
3557
3688
|
ctx.lineWidth = style.width;
|
|
3558
3689
|
ctx.setLineDash(style.dash ?? []);
|
|
3559
3690
|
ctx.beginPath();
|
|
3560
3691
|
ctx.moveTo(p0.x, p0.y);
|
|
3561
|
-
ctx.
|
|
3562
|
-
ctx.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, p3.x, p3.y);
|
|
3692
|
+
ctx.lineTo(p3.x, p3.y);
|
|
3563
3693
|
ctx.stroke();
|
|
3564
|
-
linePoints = [p0,
|
|
3694
|
+
linePoints = [p0, p3];
|
|
3565
3695
|
startPoint = p0;
|
|
3566
3696
|
endPoint = p3;
|
|
3567
|
-
startAngle = Math.atan2(p0.y -
|
|
3568
|
-
endAngle = Math.atan2(p3.y -
|
|
3569
|
-
labelPoint =
|
|
3570
|
-
if (arrowPlacement === "boundary") {
|
|
3571
|
-
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
3572
|
-
const [, s_cp3, s_cp4, s_p3] = second;
|
|
3573
|
-
const tEnd = findBoundaryIntersection(pMid, s_cp3, s_cp4, s_p3, toBounds, true);
|
|
3574
|
-
if (tEnd !== void 0) {
|
|
3575
|
-
endPoint = bezierPointAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
3576
|
-
const tangent = bezierTangentAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
3577
|
-
endAngle = Math.atan2(tangent.y, tangent.x);
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
3581
|
-
const tStart = findBoundaryIntersection(p0, cp1, cp2, pMid, fromBounds, false);
|
|
3582
|
-
if (tStart !== void 0) {
|
|
3583
|
-
startPoint = bezierPointAt(p0, cp1, cp2, pMid, tStart);
|
|
3584
|
-
const tangent = bezierTangentAt(p0, cp1, cp2, pMid, tStart);
|
|
3585
|
-
startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3697
|
+
startAngle = Math.atan2(p0.y - p3.y, p0.x - p3.x);
|
|
3698
|
+
endAngle = Math.atan2(p3.y - p0.y, p3.x - p0.x);
|
|
3699
|
+
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
3589
3700
|
} else {
|
|
3590
3701
|
const hasAnchorHints = conn.fromAnchor !== void 0 || conn.toAnchor !== void 0;
|
|
3591
3702
|
const useElkRoute = routing === "auto" && !hasAnchorHints && (edgeRoute?.points.length ?? 0) >= 2;
|
|
@@ -3596,10 +3707,18 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3596
3707
|
endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
|
|
3597
3708
|
startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
|
|
3598
3709
|
endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
|
|
3710
|
+
const stroke = resolveConnectionStroke(
|
|
3711
|
+
ctx,
|
|
3712
|
+
startPoint,
|
|
3713
|
+
endPoint,
|
|
3714
|
+
conn.fromColor,
|
|
3715
|
+
style.color,
|
|
3716
|
+
conn.toColor
|
|
3717
|
+
);
|
|
3599
3718
|
if (useElkRoute) {
|
|
3600
|
-
drawCubicInterpolatedPath(ctx, linePoints, style);
|
|
3719
|
+
drawCubicInterpolatedPath(ctx, linePoints, style, stroke);
|
|
3601
3720
|
} else {
|
|
3602
|
-
|
|
3721
|
+
drawOrthogonalPathWithStroke(ctx, startPoint, endPoint, style, stroke);
|
|
3603
3722
|
}
|
|
3604
3723
|
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
3605
3724
|
}
|
|
@@ -3904,6 +4023,9 @@ function measureTextBounds(ctx, options) {
|
|
|
3904
4023
|
function angleBetween(from, to) {
|
|
3905
4024
|
return Math.atan2(to.y - from.y, to.x - from.x);
|
|
3906
4025
|
}
|
|
4026
|
+
function degreesToRadians(angle) {
|
|
4027
|
+
return angle * Math.PI / 180;
|
|
4028
|
+
}
|
|
3907
4029
|
function pathBounds(operations) {
|
|
3908
4030
|
let minX = Number.POSITIVE_INFINITY;
|
|
3909
4031
|
let minY = Number.POSITIVE_INFINITY;
|
|
@@ -4141,6 +4263,34 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4141
4263
|
});
|
|
4142
4264
|
break;
|
|
4143
4265
|
}
|
|
4266
|
+
case "arc": {
|
|
4267
|
+
const startAngle = degreesToRadians(command.startAngle);
|
|
4268
|
+
const endAngle = degreesToRadians(command.endAngle);
|
|
4269
|
+
withOpacity(ctx, command.opacity, () => {
|
|
4270
|
+
applyDrawShadow(ctx, command.shadow);
|
|
4271
|
+
ctx.beginPath();
|
|
4272
|
+
ctx.setLineDash(command.dash ?? []);
|
|
4273
|
+
ctx.lineWidth = command.width;
|
|
4274
|
+
ctx.strokeStyle = command.color;
|
|
4275
|
+
ctx.arc(command.center.x, command.center.y, command.radius, startAngle, endAngle);
|
|
4276
|
+
ctx.stroke();
|
|
4277
|
+
});
|
|
4278
|
+
rendered.push({
|
|
4279
|
+
id,
|
|
4280
|
+
kind: "draw",
|
|
4281
|
+
bounds: expandRect(
|
|
4282
|
+
{
|
|
4283
|
+
x: command.center.x - command.radius,
|
|
4284
|
+
y: command.center.y - command.radius,
|
|
4285
|
+
width: command.radius * 2,
|
|
4286
|
+
height: command.radius * 2
|
|
4287
|
+
},
|
|
4288
|
+
command.width / 2
|
|
4289
|
+
),
|
|
4290
|
+
foregroundColor: command.color
|
|
4291
|
+
});
|
|
4292
|
+
break;
|
|
4293
|
+
}
|
|
4144
4294
|
case "bezier": {
|
|
4145
4295
|
const points = command.points;
|
|
4146
4296
|
withOpacity(ctx, command.opacity, () => {
|
|
@@ -4784,6 +4934,18 @@ async function renderDesign(input, options = {}) {
|
|
|
4784
4934
|
const specHash = computeSpecHash(spec);
|
|
4785
4935
|
const generatorVersion = options.generatorVersion ?? DEFAULT_GENERATOR_VERSION;
|
|
4786
4936
|
const renderedAt = options.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4937
|
+
const iteration = options.iteration;
|
|
4938
|
+
if (iteration) {
|
|
4939
|
+
if (!Number.isInteger(iteration.iteration) || iteration.iteration <= 0) {
|
|
4940
|
+
throw new Error("Iteration metadata requires iteration to be a positive integer.");
|
|
4941
|
+
}
|
|
4942
|
+
if (iteration.maxIterations != null && (!Number.isInteger(iteration.maxIterations) || iteration.maxIterations <= 0)) {
|
|
4943
|
+
throw new Error("Iteration metadata requires maxIterations to be a positive integer.");
|
|
4944
|
+
}
|
|
4945
|
+
if (iteration.maxIterations != null && iteration.maxIterations < iteration.iteration) {
|
|
4946
|
+
throw new Error("Iteration metadata requires maxIterations to be >= iteration.");
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4787
4949
|
const renderScale = resolveRenderScale(spec);
|
|
4788
4950
|
const canvas = createCanvas(spec.canvas.width * renderScale, spec.canvas.height * renderScale);
|
|
4789
4951
|
const ctx = canvas.getContext("2d");
|
|
@@ -4963,10 +5125,19 @@ async function renderDesign(input, options = {}) {
|
|
|
4963
5125
|
break;
|
|
4964
5126
|
}
|
|
4965
5127
|
}
|
|
4966
|
-
const
|
|
4967
|
-
|
|
4968
|
-
|
|
5128
|
+
const nodeBounds = spec.elements.filter((element) => element.type !== "connection").map((element) => elementRects.get(element.id)).filter((rect) => rect != null);
|
|
5129
|
+
const diagramCenter = spec.layout.diagramCenter ?? computeDiagramCenter(nodeBounds, { x: spec.canvas.width / 2, y: spec.canvas.height / 2 });
|
|
5130
|
+
const layoutEllipseRx = "ellipseRx" in spec.layout ? spec.layout.ellipseRx : void 0;
|
|
5131
|
+
const layoutEllipseRy = "ellipseRy" in spec.layout ? spec.layout.ellipseRy : void 0;
|
|
5132
|
+
const hasEllipseConnections = spec.elements.some(
|
|
5133
|
+
(el) => el.type === "connection" && (el.curveMode === "ellipse" || el.routing === "arc")
|
|
4969
5134
|
);
|
|
5135
|
+
const ellipseParams = hasEllipseConnections ? inferEllipseParams(
|
|
5136
|
+
nodeBounds,
|
|
5137
|
+
spec.layout.diagramCenter ?? diagramCenter,
|
|
5138
|
+
layoutEllipseRx,
|
|
5139
|
+
layoutEllipseRy
|
|
5140
|
+
) : void 0;
|
|
4970
5141
|
for (const element of spec.elements) {
|
|
4971
5142
|
if (element.type !== "connection") {
|
|
4972
5143
|
continue;
|
|
@@ -4980,7 +5151,15 @@ async function renderDesign(input, options = {}) {
|
|
|
4980
5151
|
}
|
|
4981
5152
|
const edgeRoute = edgeRoutes?.get(`${element.from}-${element.to}`);
|
|
4982
5153
|
elements.push(
|
|
4983
|
-
...renderConnection(
|
|
5154
|
+
...renderConnection(
|
|
5155
|
+
ctx,
|
|
5156
|
+
element,
|
|
5157
|
+
fromRect,
|
|
5158
|
+
toRect,
|
|
5159
|
+
theme,
|
|
5160
|
+
edgeRoute,
|
|
5161
|
+
ellipseParams ? { diagramCenter, ellipseParams } : { diagramCenter }
|
|
5162
|
+
)
|
|
4984
5163
|
);
|
|
4985
5164
|
}
|
|
4986
5165
|
if (footerRect && spec.footer) {
|
|
@@ -5024,7 +5203,8 @@ async function renderDesign(input, options = {}) {
|
|
|
5024
5203
|
layout: {
|
|
5025
5204
|
safeFrame,
|
|
5026
5205
|
elements
|
|
5027
|
-
}
|
|
5206
|
+
},
|
|
5207
|
+
...iteration ? { iteration } : {}
|
|
5028
5208
|
};
|
|
5029
5209
|
return {
|
|
5030
5210
|
png: pngBuffer,
|
|
@@ -5331,6 +5511,12 @@ var renderOutputSchema = z3.object({
|
|
|
5331
5511
|
artifactHash: z3.string(),
|
|
5332
5512
|
specHash: z3.string(),
|
|
5333
5513
|
layoutMode: z3.string(),
|
|
5514
|
+
iteration: z3.object({
|
|
5515
|
+
current: z3.number().int().positive(),
|
|
5516
|
+
max: z3.number().int().positive(),
|
|
5517
|
+
isLast: z3.boolean(),
|
|
5518
|
+
notes: z3.string().optional()
|
|
5519
|
+
}).optional(),
|
|
5334
5520
|
qa: z3.object({
|
|
5335
5521
|
pass: z3.boolean(),
|
|
5336
5522
|
issueCount: z3.number(),
|
|
@@ -5417,8 +5603,30 @@ function readCodeRange(code, start, end) {
|
|
|
5417
5603
|
const lines = code.split(/\r?\n/u);
|
|
5418
5604
|
return lines.slice(start - 1, end).join("\n");
|
|
5419
5605
|
}
|
|
5606
|
+
function parseIterationMeta(options) {
|
|
5607
|
+
if (options.iteration == null) {
|
|
5608
|
+
if (options.maxIterations != null || options.iterationNotes || options.previousHash) {
|
|
5609
|
+
throw new Error(
|
|
5610
|
+
"--iteration is required when using --max-iterations, --iteration-notes, or --previous-hash."
|
|
5611
|
+
);
|
|
5612
|
+
}
|
|
5613
|
+
return void 0;
|
|
5614
|
+
}
|
|
5615
|
+
if (options.maxIterations != null && options.maxIterations < options.iteration) {
|
|
5616
|
+
throw new Error("--max-iterations must be greater than or equal to --iteration.");
|
|
5617
|
+
}
|
|
5618
|
+
return {
|
|
5619
|
+
iteration: options.iteration,
|
|
5620
|
+
...options.maxIterations != null ? { maxIterations: options.maxIterations } : {},
|
|
5621
|
+
...options.iterationNotes ? { notes: options.iterationNotes } : {},
|
|
5622
|
+
...options.previousHash ? { previousHash: options.previousHash } : {}
|
|
5623
|
+
};
|
|
5624
|
+
}
|
|
5420
5625
|
async function runRenderPipeline(spec, options) {
|
|
5421
|
-
const renderResult = await renderDesign(spec, {
|
|
5626
|
+
const renderResult = await renderDesign(spec, {
|
|
5627
|
+
generatorVersion: pkg.version,
|
|
5628
|
+
...options.iteration ? { iteration: options.iteration } : {}
|
|
5629
|
+
});
|
|
5422
5630
|
const written = await writeRenderArtifacts(renderResult, options.out);
|
|
5423
5631
|
const specPath = options.specOut ? resolve4(options.specOut) : specPathFor(written.metadataPath);
|
|
5424
5632
|
await mkdir2(dirname3(specPath), { recursive: true });
|
|
@@ -5435,6 +5643,14 @@ async function runRenderPipeline(spec, options) {
|
|
|
5435
5643
|
artifactHash: written.metadata.artifactHash,
|
|
5436
5644
|
specHash: written.metadata.specHash,
|
|
5437
5645
|
layoutMode: spec.layout.mode,
|
|
5646
|
+
...written.metadata.iteration ? {
|
|
5647
|
+
iteration: {
|
|
5648
|
+
current: written.metadata.iteration.iteration,
|
|
5649
|
+
max: written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration,
|
|
5650
|
+
isLast: (written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration) === written.metadata.iteration.iteration,
|
|
5651
|
+
...written.metadata.iteration.notes ? { notes: written.metadata.iteration.notes } : {}
|
|
5652
|
+
}
|
|
5653
|
+
} : {},
|
|
5438
5654
|
qa: {
|
|
5439
5655
|
pass: qa.pass,
|
|
5440
5656
|
issueCount: qa.issues.length,
|
|
@@ -5448,6 +5664,10 @@ cli.command("render", {
|
|
|
5448
5664
|
spec: z3.string().describe('Path to DesignSpec JSON file (or "-" to read JSON from stdin)'),
|
|
5449
5665
|
out: z3.string().describe("Output file path (.png) or output directory"),
|
|
5450
5666
|
specOut: z3.string().optional().describe("Optional explicit output path for normalized spec JSON"),
|
|
5667
|
+
iteration: z3.number().int().positive().optional().describe("Optional iteration number for iterative workflows (1-indexed)"),
|
|
5668
|
+
iterationNotes: z3.string().optional().describe("Optional notes for the current iteration metadata"),
|
|
5669
|
+
maxIterations: z3.number().int().positive().optional().describe("Optional maximum planned iteration count"),
|
|
5670
|
+
previousHash: z3.string().optional().describe("Optional artifact hash from the previous iteration"),
|
|
5451
5671
|
allowQaFail: z3.boolean().default(false).describe("Allow render success even if QA fails")
|
|
5452
5672
|
}),
|
|
5453
5673
|
output: renderOutputSchema,
|
|
@@ -5462,9 +5682,26 @@ cli.command("render", {
|
|
|
5462
5682
|
],
|
|
5463
5683
|
async run(c) {
|
|
5464
5684
|
const spec = parseDesignSpec(await readJson(c.options.spec));
|
|
5685
|
+
let iteration;
|
|
5686
|
+
try {
|
|
5687
|
+
iteration = parseIterationMeta({
|
|
5688
|
+
...c.options.iteration != null ? { iteration: c.options.iteration } : {},
|
|
5689
|
+
...c.options.maxIterations != null ? { maxIterations: c.options.maxIterations } : {},
|
|
5690
|
+
...c.options.iterationNotes ? { iterationNotes: c.options.iterationNotes } : {},
|
|
5691
|
+
...c.options.previousHash ? { previousHash: c.options.previousHash } : {}
|
|
5692
|
+
});
|
|
5693
|
+
} catch (error) {
|
|
5694
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5695
|
+
return c.error({
|
|
5696
|
+
code: "INVALID_ITERATION_OPTIONS",
|
|
5697
|
+
message,
|
|
5698
|
+
retryable: false
|
|
5699
|
+
});
|
|
5700
|
+
}
|
|
5465
5701
|
const runReport = await runRenderPipeline(spec, {
|
|
5466
5702
|
out: c.options.out,
|
|
5467
|
-
...c.options.specOut ? { specOut: c.options.specOut } : {}
|
|
5703
|
+
...c.options.specOut ? { specOut: c.options.specOut } : {},
|
|
5704
|
+
...iteration ? { iteration } : {}
|
|
5468
5705
|
});
|
|
5469
5706
|
if (!runReport.qa.pass && !c.options.allowQaFail) {
|
|
5470
5707
|
return c.error({
|
|
@@ -5984,8 +6221,10 @@ export {
|
|
|
5984
6221
|
drawRainbowRule,
|
|
5985
6222
|
drawVignette,
|
|
5986
6223
|
edgeAnchor,
|
|
6224
|
+
ellipseRoute,
|
|
5987
6225
|
flowNodeElementSchema,
|
|
5988
6226
|
highlightCode,
|
|
6227
|
+
inferEllipseParams,
|
|
5989
6228
|
inferLayout,
|
|
5990
6229
|
inferSidecarPath,
|
|
5991
6230
|
initHighlighter,
|
|
@@ -6004,6 +6243,7 @@ export {
|
|
|
6004
6243
|
resolveShikiTheme,
|
|
6005
6244
|
resolveTheme,
|
|
6006
6245
|
runQa,
|
|
6246
|
+
straightRoute,
|
|
6007
6247
|
themeToShikiMap,
|
|
6008
6248
|
writeRenderArtifacts
|
|
6009
6249
|
};
|
package/dist/qa.d.ts
CHANGED