@spectratools/graphic-designer-cli 0.9.0 → 0.10.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 +163 -19
- package/dist/index.d.ts +6 -6
- package/dist/index.js +163 -19
- package/dist/qa.d.ts +1 -1
- package/dist/qa.js +18 -0
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +104 -17
- package/dist/{spec.schema-B_Z-KNqt.d.ts → spec.schema-B6sXTTou.d.ts} +1664 -1314
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +18 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -820,6 +820,21 @@ var drawLineSchema = z2.object({
|
|
|
820
820
|
opacity: z2.number().min(0).max(1).default(1),
|
|
821
821
|
shadow: drawShadowSchema.optional()
|
|
822
822
|
}).strict();
|
|
823
|
+
var drawArcSchema = z2.object({
|
|
824
|
+
type: z2.literal("arc"),
|
|
825
|
+
center: z2.object({
|
|
826
|
+
x: z2.number(),
|
|
827
|
+
y: z2.number()
|
|
828
|
+
}).strict(),
|
|
829
|
+
radius: z2.number().positive(),
|
|
830
|
+
startAngle: z2.number(),
|
|
831
|
+
endAngle: z2.number(),
|
|
832
|
+
color: colorHexSchema2.default("#FFFFFF"),
|
|
833
|
+
width: z2.number().min(0.5).max(32).default(2),
|
|
834
|
+
dash: z2.array(z2.number()).max(6).optional(),
|
|
835
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
836
|
+
shadow: drawShadowSchema.optional()
|
|
837
|
+
}).strict();
|
|
823
838
|
var drawPointSchema = z2.object({
|
|
824
839
|
x: z2.number(),
|
|
825
840
|
y: z2.number()
|
|
@@ -904,6 +919,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
904
919
|
drawCircleSchema,
|
|
905
920
|
drawTextSchema,
|
|
906
921
|
drawLineSchema,
|
|
922
|
+
drawArcSchema,
|
|
907
923
|
drawBezierSchema,
|
|
908
924
|
drawPathSchema,
|
|
909
925
|
drawBadgeSchema,
|
|
@@ -1039,6 +1055,8 @@ var connectionElementSchema = z2.object({
|
|
|
1039
1055
|
label: z2.string().min(1).max(200).optional(),
|
|
1040
1056
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
1041
1057
|
color: colorHexSchema2.optional(),
|
|
1058
|
+
fromColor: colorHexSchema2.optional(),
|
|
1059
|
+
toColor: colorHexSchema2.optional(),
|
|
1042
1060
|
width: z2.number().min(0.5).max(10).optional(),
|
|
1043
1061
|
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
1044
1062
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
@@ -3170,16 +3188,6 @@ function drawBezier(ctx, points, style) {
|
|
|
3170
3188
|
ctx.quadraticCurveTo(penultimate.x, penultimate.y, last.x, last.y);
|
|
3171
3189
|
ctx.stroke();
|
|
3172
3190
|
}
|
|
3173
|
-
function drawOrthogonalPath(ctx, from, to, style) {
|
|
3174
|
-
const midX = (from.x + to.x) / 2;
|
|
3175
|
-
applyLineStyle(ctx, style);
|
|
3176
|
-
ctx.beginPath();
|
|
3177
|
-
ctx.moveTo(from.x, from.y);
|
|
3178
|
-
ctx.lineTo(midX, from.y);
|
|
3179
|
-
ctx.lineTo(midX, to.y);
|
|
3180
|
-
ctx.lineTo(to.x, to.y);
|
|
3181
|
-
ctx.stroke();
|
|
3182
|
-
}
|
|
3183
3191
|
|
|
3184
3192
|
// src/renderers/connection.ts
|
|
3185
3193
|
var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
|
|
@@ -3419,11 +3427,36 @@ function pointAlongPolyline(points, t) {
|
|
|
3419
3427
|
}
|
|
3420
3428
|
return points[points.length - 1];
|
|
3421
3429
|
}
|
|
3422
|
-
function
|
|
3430
|
+
function createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor) {
|
|
3431
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
3432
|
+
gradient.addColorStop(0, fromColor);
|
|
3433
|
+
gradient.addColorStop(0.5, baseColor);
|
|
3434
|
+
gradient.addColorStop(1, toColor);
|
|
3435
|
+
return gradient;
|
|
3436
|
+
}
|
|
3437
|
+
function resolveConnectionStroke(ctx, start, end, fromColor, baseColor, toColor) {
|
|
3438
|
+
if (!fromColor || !toColor) {
|
|
3439
|
+
return baseColor;
|
|
3440
|
+
}
|
|
3441
|
+
return createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor);
|
|
3442
|
+
}
|
|
3443
|
+
function drawOrthogonalPathWithStroke(ctx, from, to, style, stroke) {
|
|
3444
|
+
const midX = (from.x + to.x) / 2;
|
|
3445
|
+
ctx.strokeStyle = stroke;
|
|
3446
|
+
ctx.lineWidth = style.width;
|
|
3447
|
+
ctx.setLineDash(style.dash ?? []);
|
|
3448
|
+
ctx.beginPath();
|
|
3449
|
+
ctx.moveTo(from.x, from.y);
|
|
3450
|
+
ctx.lineTo(midX, from.y);
|
|
3451
|
+
ctx.lineTo(midX, to.y);
|
|
3452
|
+
ctx.lineTo(to.x, to.y);
|
|
3453
|
+
ctx.stroke();
|
|
3454
|
+
}
|
|
3455
|
+
function drawCubicInterpolatedPath(ctx, points, style, stroke) {
|
|
3423
3456
|
if (points.length < 2) {
|
|
3424
3457
|
return;
|
|
3425
3458
|
}
|
|
3426
|
-
ctx.strokeStyle =
|
|
3459
|
+
ctx.strokeStyle = stroke;
|
|
3427
3460
|
ctx.lineWidth = style.width;
|
|
3428
3461
|
ctx.setLineDash(style.dash ?? []);
|
|
3429
3462
|
ctx.beginPath();
|
|
@@ -3494,7 +3527,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3494
3527
|
conn.fromAnchor,
|
|
3495
3528
|
conn.toAnchor
|
|
3496
3529
|
);
|
|
3497
|
-
ctx.
|
|
3530
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
3531
|
+
ctx.strokeStyle = stroke;
|
|
3498
3532
|
ctx.lineWidth = style.width;
|
|
3499
3533
|
ctx.setLineDash(style.dash ?? []);
|
|
3500
3534
|
ctx.beginPath();
|
|
@@ -3536,7 +3570,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3536
3570
|
);
|
|
3537
3571
|
const [p0, cp1, cp2, pMid] = first;
|
|
3538
3572
|
const [, cp3, cp4, p3] = second;
|
|
3539
|
-
ctx.
|
|
3573
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
3574
|
+
ctx.strokeStyle = stroke;
|
|
3540
3575
|
ctx.lineWidth = style.width;
|
|
3541
3576
|
ctx.setLineDash(style.dash ?? []);
|
|
3542
3577
|
ctx.beginPath();
|
|
@@ -3579,10 +3614,18 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3579
3614
|
endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
|
|
3580
3615
|
startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
|
|
3581
3616
|
endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
|
|
3617
|
+
const stroke = resolveConnectionStroke(
|
|
3618
|
+
ctx,
|
|
3619
|
+
startPoint,
|
|
3620
|
+
endPoint,
|
|
3621
|
+
conn.fromColor,
|
|
3622
|
+
style.color,
|
|
3623
|
+
conn.toColor
|
|
3624
|
+
);
|
|
3582
3625
|
if (useElkRoute) {
|
|
3583
|
-
drawCubicInterpolatedPath(ctx, linePoints, style);
|
|
3626
|
+
drawCubicInterpolatedPath(ctx, linePoints, style, stroke);
|
|
3584
3627
|
} else {
|
|
3585
|
-
|
|
3628
|
+
drawOrthogonalPathWithStroke(ctx, startPoint, endPoint, style, stroke);
|
|
3586
3629
|
}
|
|
3587
3630
|
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
3588
3631
|
}
|
|
@@ -3887,6 +3930,9 @@ function measureTextBounds(ctx, options) {
|
|
|
3887
3930
|
function angleBetween(from, to) {
|
|
3888
3931
|
return Math.atan2(to.y - from.y, to.x - from.x);
|
|
3889
3932
|
}
|
|
3933
|
+
function degreesToRadians(angle) {
|
|
3934
|
+
return angle * Math.PI / 180;
|
|
3935
|
+
}
|
|
3890
3936
|
function pathBounds(operations) {
|
|
3891
3937
|
let minX = Number.POSITIVE_INFINITY;
|
|
3892
3938
|
let minY = Number.POSITIVE_INFINITY;
|
|
@@ -4124,6 +4170,34 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4124
4170
|
});
|
|
4125
4171
|
break;
|
|
4126
4172
|
}
|
|
4173
|
+
case "arc": {
|
|
4174
|
+
const startAngle = degreesToRadians(command.startAngle);
|
|
4175
|
+
const endAngle = degreesToRadians(command.endAngle);
|
|
4176
|
+
withOpacity(ctx, command.opacity, () => {
|
|
4177
|
+
applyDrawShadow(ctx, command.shadow);
|
|
4178
|
+
ctx.beginPath();
|
|
4179
|
+
ctx.setLineDash(command.dash ?? []);
|
|
4180
|
+
ctx.lineWidth = command.width;
|
|
4181
|
+
ctx.strokeStyle = command.color;
|
|
4182
|
+
ctx.arc(command.center.x, command.center.y, command.radius, startAngle, endAngle);
|
|
4183
|
+
ctx.stroke();
|
|
4184
|
+
});
|
|
4185
|
+
rendered.push({
|
|
4186
|
+
id,
|
|
4187
|
+
kind: "draw",
|
|
4188
|
+
bounds: expandRect(
|
|
4189
|
+
{
|
|
4190
|
+
x: command.center.x - command.radius,
|
|
4191
|
+
y: command.center.y - command.radius,
|
|
4192
|
+
width: command.radius * 2,
|
|
4193
|
+
height: command.radius * 2
|
|
4194
|
+
},
|
|
4195
|
+
command.width / 2
|
|
4196
|
+
),
|
|
4197
|
+
foregroundColor: command.color
|
|
4198
|
+
});
|
|
4199
|
+
break;
|
|
4200
|
+
}
|
|
4127
4201
|
case "bezier": {
|
|
4128
4202
|
const points = command.points;
|
|
4129
4203
|
withOpacity(ctx, command.opacity, () => {
|
|
@@ -4767,6 +4841,18 @@ async function renderDesign(input, options = {}) {
|
|
|
4767
4841
|
const specHash = computeSpecHash(spec);
|
|
4768
4842
|
const generatorVersion = options.generatorVersion ?? DEFAULT_GENERATOR_VERSION;
|
|
4769
4843
|
const renderedAt = options.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4844
|
+
const iteration = options.iteration;
|
|
4845
|
+
if (iteration) {
|
|
4846
|
+
if (!Number.isInteger(iteration.iteration) || iteration.iteration <= 0) {
|
|
4847
|
+
throw new Error("Iteration metadata requires iteration to be a positive integer.");
|
|
4848
|
+
}
|
|
4849
|
+
if (iteration.maxIterations != null && (!Number.isInteger(iteration.maxIterations) || iteration.maxIterations <= 0)) {
|
|
4850
|
+
throw new Error("Iteration metadata requires maxIterations to be a positive integer.");
|
|
4851
|
+
}
|
|
4852
|
+
if (iteration.maxIterations != null && iteration.maxIterations < iteration.iteration) {
|
|
4853
|
+
throw new Error("Iteration metadata requires maxIterations to be >= iteration.");
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4770
4856
|
const renderScale = resolveRenderScale(spec);
|
|
4771
4857
|
const canvas = createCanvas(spec.canvas.width * renderScale, spec.canvas.height * renderScale);
|
|
4772
4858
|
const ctx = canvas.getContext("2d");
|
|
@@ -5007,7 +5093,8 @@ async function renderDesign(input, options = {}) {
|
|
|
5007
5093
|
layout: {
|
|
5008
5094
|
safeFrame,
|
|
5009
5095
|
elements
|
|
5010
|
-
}
|
|
5096
|
+
},
|
|
5097
|
+
...iteration ? { iteration } : {}
|
|
5011
5098
|
};
|
|
5012
5099
|
return {
|
|
5013
5100
|
png: pngBuffer,
|
|
@@ -5314,6 +5401,12 @@ var renderOutputSchema = z3.object({
|
|
|
5314
5401
|
artifactHash: z3.string(),
|
|
5315
5402
|
specHash: z3.string(),
|
|
5316
5403
|
layoutMode: z3.string(),
|
|
5404
|
+
iteration: z3.object({
|
|
5405
|
+
current: z3.number().int().positive(),
|
|
5406
|
+
max: z3.number().int().positive(),
|
|
5407
|
+
isLast: z3.boolean(),
|
|
5408
|
+
notes: z3.string().optional()
|
|
5409
|
+
}).optional(),
|
|
5317
5410
|
qa: z3.object({
|
|
5318
5411
|
pass: z3.boolean(),
|
|
5319
5412
|
issueCount: z3.number(),
|
|
@@ -5400,8 +5493,30 @@ function readCodeRange(code, start, end) {
|
|
|
5400
5493
|
const lines = code.split(/\r?\n/u);
|
|
5401
5494
|
return lines.slice(start - 1, end).join("\n");
|
|
5402
5495
|
}
|
|
5496
|
+
function parseIterationMeta(options) {
|
|
5497
|
+
if (options.iteration == null) {
|
|
5498
|
+
if (options.maxIterations != null || options.iterationNotes || options.previousHash) {
|
|
5499
|
+
throw new Error(
|
|
5500
|
+
"--iteration is required when using --max-iterations, --iteration-notes, or --previous-hash."
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
return void 0;
|
|
5504
|
+
}
|
|
5505
|
+
if (options.maxIterations != null && options.maxIterations < options.iteration) {
|
|
5506
|
+
throw new Error("--max-iterations must be greater than or equal to --iteration.");
|
|
5507
|
+
}
|
|
5508
|
+
return {
|
|
5509
|
+
iteration: options.iteration,
|
|
5510
|
+
...options.maxIterations != null ? { maxIterations: options.maxIterations } : {},
|
|
5511
|
+
...options.iterationNotes ? { notes: options.iterationNotes } : {},
|
|
5512
|
+
...options.previousHash ? { previousHash: options.previousHash } : {}
|
|
5513
|
+
};
|
|
5514
|
+
}
|
|
5403
5515
|
async function runRenderPipeline(spec, options) {
|
|
5404
|
-
const renderResult = await renderDesign(spec, {
|
|
5516
|
+
const renderResult = await renderDesign(spec, {
|
|
5517
|
+
generatorVersion: pkg.version,
|
|
5518
|
+
...options.iteration ? { iteration: options.iteration } : {}
|
|
5519
|
+
});
|
|
5405
5520
|
const written = await writeRenderArtifacts(renderResult, options.out);
|
|
5406
5521
|
const specPath = options.specOut ? resolve4(options.specOut) : specPathFor(written.metadataPath);
|
|
5407
5522
|
await mkdir2(dirname3(specPath), { recursive: true });
|
|
@@ -5418,6 +5533,14 @@ async function runRenderPipeline(spec, options) {
|
|
|
5418
5533
|
artifactHash: written.metadata.artifactHash,
|
|
5419
5534
|
specHash: written.metadata.specHash,
|
|
5420
5535
|
layoutMode: spec.layout.mode,
|
|
5536
|
+
...written.metadata.iteration ? {
|
|
5537
|
+
iteration: {
|
|
5538
|
+
current: written.metadata.iteration.iteration,
|
|
5539
|
+
max: written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration,
|
|
5540
|
+
isLast: (written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration) === written.metadata.iteration.iteration,
|
|
5541
|
+
...written.metadata.iteration.notes ? { notes: written.metadata.iteration.notes } : {}
|
|
5542
|
+
}
|
|
5543
|
+
} : {},
|
|
5421
5544
|
qa: {
|
|
5422
5545
|
pass: qa.pass,
|
|
5423
5546
|
issueCount: qa.issues.length,
|
|
@@ -5431,6 +5554,10 @@ cli.command("render", {
|
|
|
5431
5554
|
spec: z3.string().describe('Path to DesignSpec JSON file (or "-" to read JSON from stdin)'),
|
|
5432
5555
|
out: z3.string().describe("Output file path (.png) or output directory"),
|
|
5433
5556
|
specOut: z3.string().optional().describe("Optional explicit output path for normalized spec JSON"),
|
|
5557
|
+
iteration: z3.number().int().positive().optional().describe("Optional iteration number for iterative workflows (1-indexed)"),
|
|
5558
|
+
iterationNotes: z3.string().optional().describe("Optional notes for the current iteration metadata"),
|
|
5559
|
+
maxIterations: z3.number().int().positive().optional().describe("Optional maximum planned iteration count"),
|
|
5560
|
+
previousHash: z3.string().optional().describe("Optional artifact hash from the previous iteration"),
|
|
5434
5561
|
allowQaFail: z3.boolean().default(false).describe("Allow render success even if QA fails")
|
|
5435
5562
|
}),
|
|
5436
5563
|
output: renderOutputSchema,
|
|
@@ -5445,9 +5572,26 @@ cli.command("render", {
|
|
|
5445
5572
|
],
|
|
5446
5573
|
async run(c) {
|
|
5447
5574
|
const spec = parseDesignSpec(await readJson(c.options.spec));
|
|
5575
|
+
let iteration;
|
|
5576
|
+
try {
|
|
5577
|
+
iteration = parseIterationMeta({
|
|
5578
|
+
...c.options.iteration != null ? { iteration: c.options.iteration } : {},
|
|
5579
|
+
...c.options.maxIterations != null ? { maxIterations: c.options.maxIterations } : {},
|
|
5580
|
+
...c.options.iterationNotes ? { iterationNotes: c.options.iterationNotes } : {},
|
|
5581
|
+
...c.options.previousHash ? { previousHash: c.options.previousHash } : {}
|
|
5582
|
+
});
|
|
5583
|
+
} catch (error) {
|
|
5584
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5585
|
+
return c.error({
|
|
5586
|
+
code: "INVALID_ITERATION_OPTIONS",
|
|
5587
|
+
message,
|
|
5588
|
+
retryable: false
|
|
5589
|
+
});
|
|
5590
|
+
}
|
|
5448
5591
|
const runReport = await runRenderPipeline(spec, {
|
|
5449
5592
|
out: c.options.out,
|
|
5450
|
-
...c.options.specOut ? { specOut: c.options.specOut } : {}
|
|
5593
|
+
...c.options.specOut ? { specOut: c.options.specOut } : {},
|
|
5594
|
+
...iteration ? { iteration } : {}
|
|
5451
5595
|
});
|
|
5452
5596
|
if (!runReport.qa.pass && !c.options.allowQaFail) {
|
|
5453
5597
|
return c.error({
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Cli } from 'incur';
|
|
2
|
-
import { T as ThemeInput, D as DesignSpec, a as Rect$1, b as DrawCommand, c as Theme, d as RenderedElement, A as AnchorHint, C as ConnectionElement } from './spec.schema-
|
|
3
|
-
export { e as AutoLayoutConfig, B as BuiltInTheme, f as CardElement, g as CodeBlockElement, h as ConstraintSpec, i as DEFAULT_GENERATOR_VERSION, j as DEFAULT_RAINBOW_COLORS, k as Decorator, l as DesignCardSpec, m as DesignSafeFrame, n as DesignTheme, o as DiagramElement, p as DiagramLayout, q as DiagramSpec, r as
|
|
2
|
+
import { T as ThemeInput, D as DesignSpec, a as Rect$1, b as DrawCommand, c as Theme, d as RenderedElement, A as AnchorHint, C as ConnectionElement } from './spec.schema-B6sXTTou.js';
|
|
3
|
+
export { e as AutoLayoutConfig, B as BuiltInTheme, f as CardElement, g as CodeBlockElement, h as ConstraintSpec, i as DEFAULT_GENERATOR_VERSION, j as DEFAULT_RAINBOW_COLORS, k as Decorator, l as DesignCardSpec, m as DesignSafeFrame, n as DesignTheme, o as DiagramElement, p as DiagramLayout, q as DiagramSpec, r as DrawArc, s as DrawBadge, t as DrawBezier, u as DrawCircle, v as DrawFontFamily, w as DrawGradientRect, x as DrawLine, y as DrawPath, z as DrawPoint, E as DrawRect, F as DrawShadow, G as DrawText, H as DrawTextRow, I as DrawTextRowSegment, J as Element, K as FlowNodeElement, L as Gradient, M as GradientOverlayDecorator, N as GradientSpec, O as GradientStop, P as GridLayoutConfig, Q as ImageElement, S as IterationMeta, U as LayoutConfig, V as LayoutSnapshot, W as ManualLayoutConfig, X as RainbowRuleDecorator, Y as RenderDesignOptions, R as RenderMetadata, Z as RenderResult, _ as ShapeElement, $ as StackLayoutConfig, a0 as TerminalElement, a1 as TextElement, a2 as ThemeInput, a3 as VignetteDecorator, a4 as WrittenArtifacts, a5 as builtInThemeBackgrounds, a6 as builtInThemes, a7 as computeSpecHash, a8 as connectionElementSchema, a9 as defaultAutoLayout, aa as defaultCanvas, ab as defaultConstraints, ac as defaultGridLayout, ad as defaultLayout, ae as defaultStackLayout, af as defaultTheme, ag as deriveSafeFrame, ah as designSpecSchema, ai as diagramElementSchema, aj as diagramLayoutSchema, ak as diagramSpecSchema, al as drawGradientRect, am as drawRainbowRule, an as drawVignette, ao as flowNodeElementSchema, ap as inferLayout, aq as inferSidecarPath, ar as parseDesignSpec, as as parseDiagramSpec, at as renderDesign, au as resolveTheme, av as writeRenderArtifacts } from './spec.schema-B6sXTTou.js';
|
|
4
4
|
import { SKRSContext2D } from '@napi-rs/canvas';
|
|
5
5
|
export { QaIssue, QaReferenceResult, QaReport, QaSeverity, readMetadata, runQa } from './qa.js';
|
|
6
6
|
import { Highlighter } from 'shiki';
|
|
@@ -223,10 +223,10 @@ type ElkLayoutResult = LayoutResult & {
|
|
|
223
223
|
/**
|
|
224
224
|
* Render an array of freestyle draw commands onto a canvas context.
|
|
225
225
|
*
|
|
226
|
-
* Supports
|
|
227
|
-
* `path`, `badge`,
|
|
228
|
-
*
|
|
229
|
-
* downstream QA checks.
|
|
226
|
+
* Supports draw command types including `rect`, `circle`, `text`, `line`,
|
|
227
|
+
* `arc`, `bezier`, `path`, `badge`, `gradient-rect`, `grid`, and `text-row`.
|
|
228
|
+
* Each command is rendered in order and produces a corresponding
|
|
229
|
+
* {@link RenderedElement} with computed bounds for downstream QA checks.
|
|
230
230
|
*
|
|
231
231
|
* @param ctx - The `@napi-rs/canvas` 2D rendering context to draw into.
|
|
232
232
|
* @param commands - Array of {@link DrawCommand} objects from the design spec's
|
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,
|
|
@@ -1049,6 +1065,8 @@ var connectionElementSchema = z2.object({
|
|
|
1049
1065
|
label: z2.string().min(1).max(200).optional(),
|
|
1050
1066
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
1051
1067
|
color: colorHexSchema2.optional(),
|
|
1068
|
+
fromColor: colorHexSchema2.optional(),
|
|
1069
|
+
toColor: colorHexSchema2.optional(),
|
|
1052
1070
|
width: z2.number().min(0.5).max(10).optional(),
|
|
1053
1071
|
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
1054
1072
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
@@ -3187,16 +3205,6 @@ function drawBezier(ctx, points, style) {
|
|
|
3187
3205
|
ctx.quadraticCurveTo(penultimate.x, penultimate.y, last.x, last.y);
|
|
3188
3206
|
ctx.stroke();
|
|
3189
3207
|
}
|
|
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
3208
|
|
|
3201
3209
|
// src/renderers/connection.ts
|
|
3202
3210
|
var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
|
|
@@ -3436,11 +3444,36 @@ function pointAlongPolyline(points, t) {
|
|
|
3436
3444
|
}
|
|
3437
3445
|
return points[points.length - 1];
|
|
3438
3446
|
}
|
|
3439
|
-
function
|
|
3447
|
+
function createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor) {
|
|
3448
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
3449
|
+
gradient.addColorStop(0, fromColor);
|
|
3450
|
+
gradient.addColorStop(0.5, baseColor);
|
|
3451
|
+
gradient.addColorStop(1, toColor);
|
|
3452
|
+
return gradient;
|
|
3453
|
+
}
|
|
3454
|
+
function resolveConnectionStroke(ctx, start, end, fromColor, baseColor, toColor) {
|
|
3455
|
+
if (!fromColor || !toColor) {
|
|
3456
|
+
return baseColor;
|
|
3457
|
+
}
|
|
3458
|
+
return createConnectionGradient(ctx, start, end, fromColor, baseColor, toColor);
|
|
3459
|
+
}
|
|
3460
|
+
function drawOrthogonalPathWithStroke(ctx, from, to, style, stroke) {
|
|
3461
|
+
const midX = (from.x + to.x) / 2;
|
|
3462
|
+
ctx.strokeStyle = stroke;
|
|
3463
|
+
ctx.lineWidth = style.width;
|
|
3464
|
+
ctx.setLineDash(style.dash ?? []);
|
|
3465
|
+
ctx.beginPath();
|
|
3466
|
+
ctx.moveTo(from.x, from.y);
|
|
3467
|
+
ctx.lineTo(midX, from.y);
|
|
3468
|
+
ctx.lineTo(midX, to.y);
|
|
3469
|
+
ctx.lineTo(to.x, to.y);
|
|
3470
|
+
ctx.stroke();
|
|
3471
|
+
}
|
|
3472
|
+
function drawCubicInterpolatedPath(ctx, points, style, stroke) {
|
|
3440
3473
|
if (points.length < 2) {
|
|
3441
3474
|
return;
|
|
3442
3475
|
}
|
|
3443
|
-
ctx.strokeStyle =
|
|
3476
|
+
ctx.strokeStyle = stroke;
|
|
3444
3477
|
ctx.lineWidth = style.width;
|
|
3445
3478
|
ctx.setLineDash(style.dash ?? []);
|
|
3446
3479
|
ctx.beginPath();
|
|
@@ -3511,7 +3544,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3511
3544
|
conn.fromAnchor,
|
|
3512
3545
|
conn.toAnchor
|
|
3513
3546
|
);
|
|
3514
|
-
ctx.
|
|
3547
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
3548
|
+
ctx.strokeStyle = stroke;
|
|
3515
3549
|
ctx.lineWidth = style.width;
|
|
3516
3550
|
ctx.setLineDash(style.dash ?? []);
|
|
3517
3551
|
ctx.beginPath();
|
|
@@ -3553,7 +3587,8 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3553
3587
|
);
|
|
3554
3588
|
const [p0, cp1, cp2, pMid] = first;
|
|
3555
3589
|
const [, cp3, cp4, p3] = second;
|
|
3556
|
-
ctx.
|
|
3590
|
+
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
3591
|
+
ctx.strokeStyle = stroke;
|
|
3557
3592
|
ctx.lineWidth = style.width;
|
|
3558
3593
|
ctx.setLineDash(style.dash ?? []);
|
|
3559
3594
|
ctx.beginPath();
|
|
@@ -3596,10 +3631,18 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
3596
3631
|
endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
|
|
3597
3632
|
startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
|
|
3598
3633
|
endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
|
|
3634
|
+
const stroke = resolveConnectionStroke(
|
|
3635
|
+
ctx,
|
|
3636
|
+
startPoint,
|
|
3637
|
+
endPoint,
|
|
3638
|
+
conn.fromColor,
|
|
3639
|
+
style.color,
|
|
3640
|
+
conn.toColor
|
|
3641
|
+
);
|
|
3599
3642
|
if (useElkRoute) {
|
|
3600
|
-
drawCubicInterpolatedPath(ctx, linePoints, style);
|
|
3643
|
+
drawCubicInterpolatedPath(ctx, linePoints, style, stroke);
|
|
3601
3644
|
} else {
|
|
3602
|
-
|
|
3645
|
+
drawOrthogonalPathWithStroke(ctx, startPoint, endPoint, style, stroke);
|
|
3603
3646
|
}
|
|
3604
3647
|
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
3605
3648
|
}
|
|
@@ -3904,6 +3947,9 @@ function measureTextBounds(ctx, options) {
|
|
|
3904
3947
|
function angleBetween(from, to) {
|
|
3905
3948
|
return Math.atan2(to.y - from.y, to.x - from.x);
|
|
3906
3949
|
}
|
|
3950
|
+
function degreesToRadians(angle) {
|
|
3951
|
+
return angle * Math.PI / 180;
|
|
3952
|
+
}
|
|
3907
3953
|
function pathBounds(operations) {
|
|
3908
3954
|
let minX = Number.POSITIVE_INFINITY;
|
|
3909
3955
|
let minY = Number.POSITIVE_INFINITY;
|
|
@@ -4141,6 +4187,34 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4141
4187
|
});
|
|
4142
4188
|
break;
|
|
4143
4189
|
}
|
|
4190
|
+
case "arc": {
|
|
4191
|
+
const startAngle = degreesToRadians(command.startAngle);
|
|
4192
|
+
const endAngle = degreesToRadians(command.endAngle);
|
|
4193
|
+
withOpacity(ctx, command.opacity, () => {
|
|
4194
|
+
applyDrawShadow(ctx, command.shadow);
|
|
4195
|
+
ctx.beginPath();
|
|
4196
|
+
ctx.setLineDash(command.dash ?? []);
|
|
4197
|
+
ctx.lineWidth = command.width;
|
|
4198
|
+
ctx.strokeStyle = command.color;
|
|
4199
|
+
ctx.arc(command.center.x, command.center.y, command.radius, startAngle, endAngle);
|
|
4200
|
+
ctx.stroke();
|
|
4201
|
+
});
|
|
4202
|
+
rendered.push({
|
|
4203
|
+
id,
|
|
4204
|
+
kind: "draw",
|
|
4205
|
+
bounds: expandRect(
|
|
4206
|
+
{
|
|
4207
|
+
x: command.center.x - command.radius,
|
|
4208
|
+
y: command.center.y - command.radius,
|
|
4209
|
+
width: command.radius * 2,
|
|
4210
|
+
height: command.radius * 2
|
|
4211
|
+
},
|
|
4212
|
+
command.width / 2
|
|
4213
|
+
),
|
|
4214
|
+
foregroundColor: command.color
|
|
4215
|
+
});
|
|
4216
|
+
break;
|
|
4217
|
+
}
|
|
4144
4218
|
case "bezier": {
|
|
4145
4219
|
const points = command.points;
|
|
4146
4220
|
withOpacity(ctx, command.opacity, () => {
|
|
@@ -4784,6 +4858,18 @@ async function renderDesign(input, options = {}) {
|
|
|
4784
4858
|
const specHash = computeSpecHash(spec);
|
|
4785
4859
|
const generatorVersion = options.generatorVersion ?? DEFAULT_GENERATOR_VERSION;
|
|
4786
4860
|
const renderedAt = options.renderedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4861
|
+
const iteration = options.iteration;
|
|
4862
|
+
if (iteration) {
|
|
4863
|
+
if (!Number.isInteger(iteration.iteration) || iteration.iteration <= 0) {
|
|
4864
|
+
throw new Error("Iteration metadata requires iteration to be a positive integer.");
|
|
4865
|
+
}
|
|
4866
|
+
if (iteration.maxIterations != null && (!Number.isInteger(iteration.maxIterations) || iteration.maxIterations <= 0)) {
|
|
4867
|
+
throw new Error("Iteration metadata requires maxIterations to be a positive integer.");
|
|
4868
|
+
}
|
|
4869
|
+
if (iteration.maxIterations != null && iteration.maxIterations < iteration.iteration) {
|
|
4870
|
+
throw new Error("Iteration metadata requires maxIterations to be >= iteration.");
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4787
4873
|
const renderScale = resolveRenderScale(spec);
|
|
4788
4874
|
const canvas = createCanvas(spec.canvas.width * renderScale, spec.canvas.height * renderScale);
|
|
4789
4875
|
const ctx = canvas.getContext("2d");
|
|
@@ -5024,7 +5110,8 @@ async function renderDesign(input, options = {}) {
|
|
|
5024
5110
|
layout: {
|
|
5025
5111
|
safeFrame,
|
|
5026
5112
|
elements
|
|
5027
|
-
}
|
|
5113
|
+
},
|
|
5114
|
+
...iteration ? { iteration } : {}
|
|
5028
5115
|
};
|
|
5029
5116
|
return {
|
|
5030
5117
|
png: pngBuffer,
|
|
@@ -5331,6 +5418,12 @@ var renderOutputSchema = z3.object({
|
|
|
5331
5418
|
artifactHash: z3.string(),
|
|
5332
5419
|
specHash: z3.string(),
|
|
5333
5420
|
layoutMode: z3.string(),
|
|
5421
|
+
iteration: z3.object({
|
|
5422
|
+
current: z3.number().int().positive(),
|
|
5423
|
+
max: z3.number().int().positive(),
|
|
5424
|
+
isLast: z3.boolean(),
|
|
5425
|
+
notes: z3.string().optional()
|
|
5426
|
+
}).optional(),
|
|
5334
5427
|
qa: z3.object({
|
|
5335
5428
|
pass: z3.boolean(),
|
|
5336
5429
|
issueCount: z3.number(),
|
|
@@ -5417,8 +5510,30 @@ function readCodeRange(code, start, end) {
|
|
|
5417
5510
|
const lines = code.split(/\r?\n/u);
|
|
5418
5511
|
return lines.slice(start - 1, end).join("\n");
|
|
5419
5512
|
}
|
|
5513
|
+
function parseIterationMeta(options) {
|
|
5514
|
+
if (options.iteration == null) {
|
|
5515
|
+
if (options.maxIterations != null || options.iterationNotes || options.previousHash) {
|
|
5516
|
+
throw new Error(
|
|
5517
|
+
"--iteration is required when using --max-iterations, --iteration-notes, or --previous-hash."
|
|
5518
|
+
);
|
|
5519
|
+
}
|
|
5520
|
+
return void 0;
|
|
5521
|
+
}
|
|
5522
|
+
if (options.maxIterations != null && options.maxIterations < options.iteration) {
|
|
5523
|
+
throw new Error("--max-iterations must be greater than or equal to --iteration.");
|
|
5524
|
+
}
|
|
5525
|
+
return {
|
|
5526
|
+
iteration: options.iteration,
|
|
5527
|
+
...options.maxIterations != null ? { maxIterations: options.maxIterations } : {},
|
|
5528
|
+
...options.iterationNotes ? { notes: options.iterationNotes } : {},
|
|
5529
|
+
...options.previousHash ? { previousHash: options.previousHash } : {}
|
|
5530
|
+
};
|
|
5531
|
+
}
|
|
5420
5532
|
async function runRenderPipeline(spec, options) {
|
|
5421
|
-
const renderResult = await renderDesign(spec, {
|
|
5533
|
+
const renderResult = await renderDesign(spec, {
|
|
5534
|
+
generatorVersion: pkg.version,
|
|
5535
|
+
...options.iteration ? { iteration: options.iteration } : {}
|
|
5536
|
+
});
|
|
5422
5537
|
const written = await writeRenderArtifacts(renderResult, options.out);
|
|
5423
5538
|
const specPath = options.specOut ? resolve4(options.specOut) : specPathFor(written.metadataPath);
|
|
5424
5539
|
await mkdir2(dirname3(specPath), { recursive: true });
|
|
@@ -5435,6 +5550,14 @@ async function runRenderPipeline(spec, options) {
|
|
|
5435
5550
|
artifactHash: written.metadata.artifactHash,
|
|
5436
5551
|
specHash: written.metadata.specHash,
|
|
5437
5552
|
layoutMode: spec.layout.mode,
|
|
5553
|
+
...written.metadata.iteration ? {
|
|
5554
|
+
iteration: {
|
|
5555
|
+
current: written.metadata.iteration.iteration,
|
|
5556
|
+
max: written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration,
|
|
5557
|
+
isLast: (written.metadata.iteration.maxIterations ?? written.metadata.iteration.iteration) === written.metadata.iteration.iteration,
|
|
5558
|
+
...written.metadata.iteration.notes ? { notes: written.metadata.iteration.notes } : {}
|
|
5559
|
+
}
|
|
5560
|
+
} : {},
|
|
5438
5561
|
qa: {
|
|
5439
5562
|
pass: qa.pass,
|
|
5440
5563
|
issueCount: qa.issues.length,
|
|
@@ -5448,6 +5571,10 @@ cli.command("render", {
|
|
|
5448
5571
|
spec: z3.string().describe('Path to DesignSpec JSON file (or "-" to read JSON from stdin)'),
|
|
5449
5572
|
out: z3.string().describe("Output file path (.png) or output directory"),
|
|
5450
5573
|
specOut: z3.string().optional().describe("Optional explicit output path for normalized spec JSON"),
|
|
5574
|
+
iteration: z3.number().int().positive().optional().describe("Optional iteration number for iterative workflows (1-indexed)"),
|
|
5575
|
+
iterationNotes: z3.string().optional().describe("Optional notes for the current iteration metadata"),
|
|
5576
|
+
maxIterations: z3.number().int().positive().optional().describe("Optional maximum planned iteration count"),
|
|
5577
|
+
previousHash: z3.string().optional().describe("Optional artifact hash from the previous iteration"),
|
|
5451
5578
|
allowQaFail: z3.boolean().default(false).describe("Allow render success even if QA fails")
|
|
5452
5579
|
}),
|
|
5453
5580
|
output: renderOutputSchema,
|
|
@@ -5462,9 +5589,26 @@ cli.command("render", {
|
|
|
5462
5589
|
],
|
|
5463
5590
|
async run(c) {
|
|
5464
5591
|
const spec = parseDesignSpec(await readJson(c.options.spec));
|
|
5592
|
+
let iteration;
|
|
5593
|
+
try {
|
|
5594
|
+
iteration = parseIterationMeta({
|
|
5595
|
+
...c.options.iteration != null ? { iteration: c.options.iteration } : {},
|
|
5596
|
+
...c.options.maxIterations != null ? { maxIterations: c.options.maxIterations } : {},
|
|
5597
|
+
...c.options.iterationNotes ? { iterationNotes: c.options.iterationNotes } : {},
|
|
5598
|
+
...c.options.previousHash ? { previousHash: c.options.previousHash } : {}
|
|
5599
|
+
});
|
|
5600
|
+
} catch (error) {
|
|
5601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5602
|
+
return c.error({
|
|
5603
|
+
code: "INVALID_ITERATION_OPTIONS",
|
|
5604
|
+
message,
|
|
5605
|
+
retryable: false
|
|
5606
|
+
});
|
|
5607
|
+
}
|
|
5465
5608
|
const runReport = await runRenderPipeline(spec, {
|
|
5466
5609
|
out: c.options.out,
|
|
5467
|
-
...c.options.specOut ? { specOut: c.options.specOut } : {}
|
|
5610
|
+
...c.options.specOut ? { specOut: c.options.specOut } : {},
|
|
5611
|
+
...iteration ? { iteration } : {}
|
|
5468
5612
|
});
|
|
5469
5613
|
if (!runReport.qa.pass && !c.options.allowQaFail) {
|
|
5470
5614
|
return c.error({
|
package/dist/qa.d.ts
CHANGED
package/dist/qa.js
CHANGED
|
@@ -563,6 +563,21 @@ var drawLineSchema = z2.object({
|
|
|
563
563
|
opacity: z2.number().min(0).max(1).default(1),
|
|
564
564
|
shadow: drawShadowSchema.optional()
|
|
565
565
|
}).strict();
|
|
566
|
+
var drawArcSchema = z2.object({
|
|
567
|
+
type: z2.literal("arc"),
|
|
568
|
+
center: z2.object({
|
|
569
|
+
x: z2.number(),
|
|
570
|
+
y: z2.number()
|
|
571
|
+
}).strict(),
|
|
572
|
+
radius: z2.number().positive(),
|
|
573
|
+
startAngle: z2.number(),
|
|
574
|
+
endAngle: z2.number(),
|
|
575
|
+
color: colorHexSchema2.default("#FFFFFF"),
|
|
576
|
+
width: z2.number().min(0.5).max(32).default(2),
|
|
577
|
+
dash: z2.array(z2.number()).max(6).optional(),
|
|
578
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
579
|
+
shadow: drawShadowSchema.optional()
|
|
580
|
+
}).strict();
|
|
566
581
|
var drawPointSchema = z2.object({
|
|
567
582
|
x: z2.number(),
|
|
568
583
|
y: z2.number()
|
|
@@ -647,6 +662,7 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
647
662
|
drawCircleSchema,
|
|
648
663
|
drawTextSchema,
|
|
649
664
|
drawLineSchema,
|
|
665
|
+
drawArcSchema,
|
|
650
666
|
drawBezierSchema,
|
|
651
667
|
drawPathSchema,
|
|
652
668
|
drawBadgeSchema,
|
|
@@ -782,6 +798,8 @@ var connectionElementSchema = z2.object({
|
|
|
782
798
|
label: z2.string().min(1).max(200).optional(),
|
|
783
799
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
784
800
|
color: colorHexSchema2.optional(),
|
|
801
|
+
fromColor: colorHexSchema2.optional(),
|
|
802
|
+
toColor: colorHexSchema2.optional(),
|
|
785
803
|
width: z2.number().min(0.5).max(10).optional(),
|
|
786
804
|
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
787
805
|
arrowSize: z2.number().min(4).max(32).optional(),
|