@spectratools/graphic-designer-cli 0.12.2 → 0.14.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 +316 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +316 -5
- package/dist/qa.d.ts +1 -1
- package/dist/qa.js +49 -3
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +316 -5
- package/dist/{spec.schema-BkbcnVcm.d.ts → spec.schema-De-IkUjn.d.ts} +584 -26
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +49 -3
- package/package.json +2 -2
package/dist/renderer.js
CHANGED
|
@@ -614,6 +614,8 @@ function estimateElementHeight(element) {
|
|
|
614
614
|
return 130;
|
|
615
615
|
case "image":
|
|
616
616
|
return 220;
|
|
617
|
+
case "ring":
|
|
618
|
+
return element.radius * 2 + element.glowRadius * 2 + 16;
|
|
617
619
|
case "connection":
|
|
618
620
|
return 0;
|
|
619
621
|
}
|
|
@@ -634,6 +636,8 @@ function estimateElementWidth(element) {
|
|
|
634
636
|
return 280;
|
|
635
637
|
case "image":
|
|
636
638
|
return 320;
|
|
639
|
+
case "ring":
|
|
640
|
+
return element.radius * 2 + element.glowRadius * 2 + 16;
|
|
637
641
|
case "connection":
|
|
638
642
|
return 0;
|
|
639
643
|
}
|
|
@@ -1248,6 +1252,30 @@ function drawRainbowRule(ctx, x, y, width, thickness = 2, colors = [...DEFAULT_R
|
|
|
1248
1252
|
ctx.fill();
|
|
1249
1253
|
ctx.restore();
|
|
1250
1254
|
}
|
|
1255
|
+
function drawEdgeVignette(ctx, width, height, color = "#000000", topHeight = 35, bottomHeight = 55, topOpacity = 0.3, bottomOpacity = 0.4) {
|
|
1256
|
+
if (width <= 0 || height <= 0) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
if (topHeight > 0 && topOpacity > 0) {
|
|
1260
|
+
const topGradient = ctx.createLinearGradient(0, 0, 0, topHeight);
|
|
1261
|
+
topGradient.addColorStop(0, withAlpha2(color, clamp01(topOpacity)));
|
|
1262
|
+
topGradient.addColorStop(1, withAlpha2(color, 0));
|
|
1263
|
+
ctx.save();
|
|
1264
|
+
ctx.fillStyle = topGradient;
|
|
1265
|
+
ctx.fillRect(0, 0, width, topHeight);
|
|
1266
|
+
ctx.restore();
|
|
1267
|
+
}
|
|
1268
|
+
if (bottomHeight > 0 && bottomOpacity > 0) {
|
|
1269
|
+
const bottomY = height - bottomHeight;
|
|
1270
|
+
const bottomGradient = ctx.createLinearGradient(0, bottomY, 0, height);
|
|
1271
|
+
bottomGradient.addColorStop(0, withAlpha2(color, 0));
|
|
1272
|
+
bottomGradient.addColorStop(1, withAlpha2(color, clamp01(bottomOpacity)));
|
|
1273
|
+
ctx.save();
|
|
1274
|
+
ctx.fillStyle = bottomGradient;
|
|
1275
|
+
ctx.fillRect(0, bottomY, width, bottomHeight);
|
|
1276
|
+
ctx.restore();
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1251
1279
|
function drawVignette(ctx, width, height, intensity = 0.3, color = "#000000") {
|
|
1252
1280
|
if (width <= 0 || height <= 0 || intensity <= 0) {
|
|
1253
1281
|
return;
|
|
@@ -3330,10 +3358,118 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
3330
3358
|
});
|
|
3331
3359
|
break;
|
|
3332
3360
|
}
|
|
3361
|
+
case "stats-bar": {
|
|
3362
|
+
const barRect = renderStatsBar(ctx, command, theme);
|
|
3363
|
+
rendered.push({
|
|
3364
|
+
id,
|
|
3365
|
+
kind: "draw",
|
|
3366
|
+
bounds: barRect,
|
|
3367
|
+
foregroundColor: command.valueColor,
|
|
3368
|
+
backgroundColor: theme.background
|
|
3369
|
+
});
|
|
3370
|
+
break;
|
|
3371
|
+
}
|
|
3333
3372
|
}
|
|
3334
3373
|
}
|
|
3335
3374
|
return rendered;
|
|
3336
3375
|
}
|
|
3376
|
+
function renderStatsBar(ctx, command, theme) {
|
|
3377
|
+
const canvasWidth = ctx.canvas.width;
|
|
3378
|
+
const valueFontFamily = resolveDrawFont(theme, command.valueFontFamily);
|
|
3379
|
+
const labelFontFamily = resolveDrawFont(theme, command.labelFontFamily);
|
|
3380
|
+
const spaceWidth = 4;
|
|
3381
|
+
const measuredItems = [];
|
|
3382
|
+
for (const item of command.items) {
|
|
3383
|
+
applyFont(ctx, {
|
|
3384
|
+
size: command.valueFontSize,
|
|
3385
|
+
weight: command.valueFontWeight,
|
|
3386
|
+
family: valueFontFamily
|
|
3387
|
+
});
|
|
3388
|
+
const valueWidth = ctx.measureText(item.value).width;
|
|
3389
|
+
applyFont(ctx, {
|
|
3390
|
+
size: command.labelFontSize,
|
|
3391
|
+
weight: command.labelFontWeight,
|
|
3392
|
+
family: labelFontFamily
|
|
3393
|
+
});
|
|
3394
|
+
const labelWidth = ctx.measureText(item.label).width;
|
|
3395
|
+
measuredItems.push({
|
|
3396
|
+
valueWidth,
|
|
3397
|
+
labelWidth,
|
|
3398
|
+
totalWidth: valueWidth + spaceWidth + labelWidth
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
let separatorWidth = 0;
|
|
3402
|
+
let separatorText = "";
|
|
3403
|
+
if (command.separator !== "none" && command.items.length > 1) {
|
|
3404
|
+
separatorText = command.separator === "dot" ? "\xB7" : "|";
|
|
3405
|
+
const sepFontSize = Math.max(command.valueFontSize, command.labelFontSize);
|
|
3406
|
+
applyFont(ctx, { size: sepFontSize, weight: 400, family: labelFontFamily });
|
|
3407
|
+
separatorWidth = ctx.measureText(separatorText).width;
|
|
3408
|
+
}
|
|
3409
|
+
const itemsWidth = measuredItems.reduce((sum, m) => sum + m.totalWidth, 0);
|
|
3410
|
+
const gapCount = command.items.length - 1;
|
|
3411
|
+
const totalWidth = itemsWidth + gapCount * (command.gap + separatorWidth);
|
|
3412
|
+
let cursorX = (canvasWidth - totalWidth) / 2;
|
|
3413
|
+
const startX = cursorX;
|
|
3414
|
+
applyFont(ctx, {
|
|
3415
|
+
size: command.valueFontSize,
|
|
3416
|
+
weight: command.valueFontWeight,
|
|
3417
|
+
family: valueFontFamily
|
|
3418
|
+
});
|
|
3419
|
+
const valueMetrics = ctx.measureText("M");
|
|
3420
|
+
const valueAscent = valueMetrics.actualBoundingBoxAscent || command.valueFontSize * 0.75;
|
|
3421
|
+
const valueDescent = valueMetrics.actualBoundingBoxDescent || command.valueFontSize * 0.25;
|
|
3422
|
+
applyFont(ctx, {
|
|
3423
|
+
size: command.labelFontSize,
|
|
3424
|
+
weight: command.labelFontWeight,
|
|
3425
|
+
family: labelFontFamily
|
|
3426
|
+
});
|
|
3427
|
+
const labelMetrics = ctx.measureText("M");
|
|
3428
|
+
const labelAscent = labelMetrics.actualBoundingBoxAscent || command.labelFontSize * 0.75;
|
|
3429
|
+
const labelDescent = labelMetrics.actualBoundingBoxDescent || command.labelFontSize * 0.25;
|
|
3430
|
+
const maxAscent = Math.max(valueAscent, labelAscent);
|
|
3431
|
+
const maxDescent = Math.max(valueDescent, labelDescent);
|
|
3432
|
+
withOpacity(ctx, command.opacity, () => {
|
|
3433
|
+
for (let i = 0; i < command.items.length; i++) {
|
|
3434
|
+
const item = command.items[i];
|
|
3435
|
+
const measured = measuredItems[i];
|
|
3436
|
+
applyFont(ctx, {
|
|
3437
|
+
size: command.valueFontSize,
|
|
3438
|
+
weight: command.valueFontWeight,
|
|
3439
|
+
family: valueFontFamily
|
|
3440
|
+
});
|
|
3441
|
+
ctx.fillStyle = command.valueColor;
|
|
3442
|
+
ctx.textAlign = "left";
|
|
3443
|
+
ctx.textBaseline = "alphabetic";
|
|
3444
|
+
ctx.fillText(item.value, cursorX, command.y);
|
|
3445
|
+
cursorX += measured.valueWidth + spaceWidth;
|
|
3446
|
+
applyFont(ctx, {
|
|
3447
|
+
size: command.labelFontSize,
|
|
3448
|
+
weight: command.labelFontWeight,
|
|
3449
|
+
family: labelFontFamily
|
|
3450
|
+
});
|
|
3451
|
+
ctx.fillStyle = command.labelColor;
|
|
3452
|
+
ctx.fillText(item.label, cursorX, command.y);
|
|
3453
|
+
cursorX += measured.labelWidth;
|
|
3454
|
+
if (i < command.items.length - 1 && command.separator !== "none") {
|
|
3455
|
+
const sepFontSize = Math.max(command.valueFontSize, command.labelFontSize);
|
|
3456
|
+
applyFont(ctx, { size: sepFontSize, weight: 400, family: labelFontFamily });
|
|
3457
|
+
ctx.fillStyle = command.separatorColor;
|
|
3458
|
+
ctx.textAlign = "center";
|
|
3459
|
+
ctx.fillText(separatorText, cursorX + command.gap / 2 + separatorWidth / 2, command.y);
|
|
3460
|
+
ctx.textAlign = "left";
|
|
3461
|
+
cursorX += command.gap + separatorWidth;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
});
|
|
3465
|
+
const height = Math.max(1, maxAscent + maxDescent);
|
|
3466
|
+
return {
|
|
3467
|
+
x: startX,
|
|
3468
|
+
y: command.y - maxAscent,
|
|
3469
|
+
width: Math.max(1, totalWidth),
|
|
3470
|
+
height
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3337
3473
|
|
|
3338
3474
|
// src/renderers/image.ts
|
|
3339
3475
|
import { loadImage } from "@napi-rs/canvas";
|
|
@@ -3420,6 +3556,110 @@ async function renderImageElement(ctx, image, bounds, theme) {
|
|
|
3420
3556
|
}
|
|
3421
3557
|
}
|
|
3422
3558
|
|
|
3559
|
+
// src/renderers/ring.ts
|
|
3560
|
+
function renderRingElement(ctx, ring, bounds, theme) {
|
|
3561
|
+
const cx = bounds.x + bounds.width / 2;
|
|
3562
|
+
const cy = bounds.y + bounds.height / 2;
|
|
3563
|
+
const { radius, strokeWidth, segments } = ring;
|
|
3564
|
+
if (ring.glowRadius > 0) {
|
|
3565
|
+
const glowColor = ring.glowColor ?? segments[0].color;
|
|
3566
|
+
ctx.save();
|
|
3567
|
+
ctx.globalAlpha = 0.15;
|
|
3568
|
+
ctx.beginPath();
|
|
3569
|
+
ctx.arc(cx, cy, radius + ring.glowRadius, 0, Math.PI * 2);
|
|
3570
|
+
ctx.fillStyle = glowColor;
|
|
3571
|
+
ctx.fill();
|
|
3572
|
+
ctx.restore();
|
|
3573
|
+
}
|
|
3574
|
+
if (ring.fill || ring.fillOpacity > 0) {
|
|
3575
|
+
const fillColor = ring.fill ?? segments[0].color;
|
|
3576
|
+
ctx.save();
|
|
3577
|
+
ctx.globalAlpha = ring.fillOpacity;
|
|
3578
|
+
ctx.beginPath();
|
|
3579
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
3580
|
+
ctx.fillStyle = fillColor;
|
|
3581
|
+
ctx.fill();
|
|
3582
|
+
ctx.restore();
|
|
3583
|
+
}
|
|
3584
|
+
ctx.save();
|
|
3585
|
+
ctx.globalAlpha = 0.2;
|
|
3586
|
+
ctx.beginPath();
|
|
3587
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
3588
|
+
ctx.strokeStyle = theme.border;
|
|
3589
|
+
ctx.lineWidth = strokeWidth;
|
|
3590
|
+
ctx.stroke();
|
|
3591
|
+
ctx.restore();
|
|
3592
|
+
const segmentAngle = 2 * Math.PI / segments.length;
|
|
3593
|
+
for (let i = 0; i < segments.length; i++) {
|
|
3594
|
+
const startAngle = i * segmentAngle - Math.PI / 2;
|
|
3595
|
+
ctx.beginPath();
|
|
3596
|
+
ctx.arc(cx, cy, radius, startAngle, startAngle + segmentAngle);
|
|
3597
|
+
ctx.strokeStyle = segments[i].color;
|
|
3598
|
+
ctx.lineWidth = strokeWidth;
|
|
3599
|
+
ctx.stroke();
|
|
3600
|
+
}
|
|
3601
|
+
if (ring.showCycleArrows) {
|
|
3602
|
+
const arrowArcRadius = radius + strokeWidth + 4;
|
|
3603
|
+
const arrowColor = segments[0].color;
|
|
3604
|
+
const numArrows = Math.min(segments.length, 4);
|
|
3605
|
+
const arrowSpacing = 2 * Math.PI / numArrows;
|
|
3606
|
+
for (let i = 0; i < numArrows; i++) {
|
|
3607
|
+
const baseAngle = i * arrowSpacing - Math.PI / 2;
|
|
3608
|
+
const arcStart = baseAngle + 0.15;
|
|
3609
|
+
const arcEnd = baseAngle + arrowSpacing - 0.15;
|
|
3610
|
+
ctx.beginPath();
|
|
3611
|
+
ctx.arc(cx, cy, arrowArcRadius, arcStart, arcEnd);
|
|
3612
|
+
ctx.strokeStyle = arrowColor;
|
|
3613
|
+
ctx.lineWidth = Math.max(1, strokeWidth * 0.6);
|
|
3614
|
+
ctx.stroke();
|
|
3615
|
+
const headAngle = arcEnd;
|
|
3616
|
+
const headX = cx + arrowArcRadius * Math.cos(headAngle);
|
|
3617
|
+
const headY = cy + arrowArcRadius * Math.sin(headAngle);
|
|
3618
|
+
const tangentAngle = headAngle + Math.PI / 2;
|
|
3619
|
+
const headSize = Math.max(4, strokeWidth * 2);
|
|
3620
|
+
ctx.beginPath();
|
|
3621
|
+
ctx.moveTo(headX, headY);
|
|
3622
|
+
ctx.lineTo(
|
|
3623
|
+
headX - headSize * Math.cos(tangentAngle - 0.4),
|
|
3624
|
+
headY - headSize * Math.sin(tangentAngle - 0.4)
|
|
3625
|
+
);
|
|
3626
|
+
ctx.lineTo(
|
|
3627
|
+
headX - headSize * Math.cos(tangentAngle + 0.4),
|
|
3628
|
+
headY - headSize * Math.sin(tangentAngle + 0.4)
|
|
3629
|
+
);
|
|
3630
|
+
ctx.closePath();
|
|
3631
|
+
ctx.fillStyle = arrowColor;
|
|
3632
|
+
ctx.fill();
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
if (ring.label) {
|
|
3636
|
+
const labelColor = ring.labelColor ?? theme.text;
|
|
3637
|
+
const labelSize = ring.labelSize;
|
|
3638
|
+
const bodyFont = resolveFont(theme.fonts.body, "body");
|
|
3639
|
+
applyFont(ctx, { size: labelSize, weight: 500, family: bodyFont });
|
|
3640
|
+
ctx.fillStyle = labelColor;
|
|
3641
|
+
ctx.textAlign = "center";
|
|
3642
|
+
ctx.textBaseline = "middle";
|
|
3643
|
+
const lines = ring.label.split("\\n");
|
|
3644
|
+
const lineHeight = labelSize * 1.3;
|
|
3645
|
+
const totalHeight = lines.length * lineHeight;
|
|
3646
|
+
const startY = cy - totalHeight / 2 + lineHeight / 2;
|
|
3647
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3648
|
+
ctx.fillText(lines[i], cx, startY + i * lineHeight);
|
|
3649
|
+
}
|
|
3650
|
+
ctx.textAlign = "left";
|
|
3651
|
+
ctx.textBaseline = "alphabetic";
|
|
3652
|
+
}
|
|
3653
|
+
return [
|
|
3654
|
+
{
|
|
3655
|
+
id: `ring-${ring.id}`,
|
|
3656
|
+
kind: "shape",
|
|
3657
|
+
bounds,
|
|
3658
|
+
foregroundColor: segments[0].color
|
|
3659
|
+
}
|
|
3660
|
+
];
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3423
3663
|
// src/renderers/shape.ts
|
|
3424
3664
|
function renderShapeElement(ctx, shape, bounds, theme) {
|
|
3425
3665
|
const fill = shape.fill ?? theme.surfaceMuted;
|
|
@@ -3779,6 +4019,27 @@ var drawTextRowSchema = z2.object({
|
|
|
3779
4019
|
defaultColor: colorHexSchema2.default("#FFFFFF"),
|
|
3780
4020
|
opacity: z2.number().min(0).max(1).default(1)
|
|
3781
4021
|
}).strict();
|
|
4022
|
+
var drawStatsBarItemSchema = z2.object({
|
|
4023
|
+
value: z2.string().min(1).max(50),
|
|
4024
|
+
label: z2.string().min(1).max(100)
|
|
4025
|
+
}).strict();
|
|
4026
|
+
var drawStatsBarSchema = z2.object({
|
|
4027
|
+
type: z2.literal("stats-bar"),
|
|
4028
|
+
y: z2.number().describe("Vertical position of the stats bar"),
|
|
4029
|
+
items: z2.array(drawStatsBarItemSchema).min(1).max(8),
|
|
4030
|
+
separator: z2.enum(["dot", "pipe", "none"]).default("dot"),
|
|
4031
|
+
valueColor: colorHexSchema2.default("#FFFFFF"),
|
|
4032
|
+
valueFontSize: z2.number().min(8).max(72).default(18),
|
|
4033
|
+
valueFontWeight: z2.number().int().min(100).max(900).default(700),
|
|
4034
|
+
valueFontFamily: drawFontFamilySchema.default("mono"),
|
|
4035
|
+
labelColor: colorHexSchema2.default("#AAAAAA"),
|
|
4036
|
+
labelFontSize: z2.number().min(8).max(72).default(14),
|
|
4037
|
+
labelFontWeight: z2.number().int().min(100).max(900).default(400),
|
|
4038
|
+
labelFontFamily: drawFontFamilySchema.default("body"),
|
|
4039
|
+
separatorColor: colorHexSchema2.default("#666666"),
|
|
4040
|
+
gap: z2.number().min(0).max(100).default(24),
|
|
4041
|
+
opacity: z2.number().min(0).max(1).default(1)
|
|
4042
|
+
}).strict();
|
|
3782
4043
|
var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
3783
4044
|
drawRectSchema,
|
|
3784
4045
|
drawCircleSchema,
|
|
@@ -3790,7 +4051,8 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
3790
4051
|
drawBadgeSchema,
|
|
3791
4052
|
drawGradientRectSchema,
|
|
3792
4053
|
drawGridSchema,
|
|
3793
|
-
drawTextRowSchema
|
|
4054
|
+
drawTextRowSchema,
|
|
4055
|
+
drawStatsBarSchema
|
|
3794
4056
|
]);
|
|
3795
4057
|
var defaultCanvas = {
|
|
3796
4058
|
width: 1200,
|
|
@@ -3991,6 +4253,24 @@ var imageElementSchema = z2.object({
|
|
|
3991
4253
|
fit: z2.enum(["contain", "cover", "fill", "none"]).default("contain"),
|
|
3992
4254
|
borderRadius: z2.number().min(0).default(0)
|
|
3993
4255
|
}).strict();
|
|
4256
|
+
var ringSegmentSchema = z2.object({
|
|
4257
|
+
color: colorHexSchema2
|
|
4258
|
+
}).strict();
|
|
4259
|
+
var ringElementSchema = z2.object({
|
|
4260
|
+
type: z2.literal("ring"),
|
|
4261
|
+
id: z2.string().min(1).max(120),
|
|
4262
|
+
radius: z2.number().min(8).max(512).default(48),
|
|
4263
|
+
strokeWidth: z2.number().min(1).max(32).default(2),
|
|
4264
|
+
label: z2.string().max(100).optional(),
|
|
4265
|
+
labelColor: colorHexSchema2.optional(),
|
|
4266
|
+
labelSize: z2.number().min(8).max(48).default(12),
|
|
4267
|
+
segments: z2.array(ringSegmentSchema).min(1).max(24).default([{ color: "#4A7BF7" }]),
|
|
4268
|
+
glowRadius: z2.number().min(0).max(64).default(0),
|
|
4269
|
+
glowColor: colorHexSchema2.optional(),
|
|
4270
|
+
showCycleArrows: z2.boolean().default(false),
|
|
4271
|
+
fill: colorHexSchema2.optional(),
|
|
4272
|
+
fillOpacity: z2.number().min(0).max(1).default(0.05)
|
|
4273
|
+
}).strict();
|
|
3994
4274
|
var elementSchema = z2.discriminatedUnion("type", [
|
|
3995
4275
|
cardElementSchema,
|
|
3996
4276
|
flowNodeElementSchema,
|
|
@@ -3999,7 +4279,8 @@ var elementSchema = z2.discriminatedUnion("type", [
|
|
|
3999
4279
|
terminalElementSchema,
|
|
4000
4280
|
textElementSchema,
|
|
4001
4281
|
shapeElementSchema,
|
|
4002
|
-
imageElementSchema
|
|
4282
|
+
imageElementSchema,
|
|
4283
|
+
ringElementSchema
|
|
4003
4284
|
]);
|
|
4004
4285
|
var diagramCenterSchema = z2.object({
|
|
4005
4286
|
x: z2.number(),
|
|
@@ -4120,8 +4401,13 @@ var decoratorSchema = z2.discriminatedUnion("type", [
|
|
|
4120
4401
|
}).strict(),
|
|
4121
4402
|
z2.object({
|
|
4122
4403
|
type: z2.literal("vignette"),
|
|
4404
|
+
mode: z2.enum(["radial", "edge"]).default("radial"),
|
|
4123
4405
|
intensity: z2.number().min(0).max(1).default(0.3),
|
|
4124
|
-
color: colorHexSchema2.default("#000000")
|
|
4406
|
+
color: colorHexSchema2.default("#000000"),
|
|
4407
|
+
edgeTopHeight: z2.number().min(0).max(200).default(35),
|
|
4408
|
+
edgeBottomHeight: z2.number().min(0).max(200).default(55),
|
|
4409
|
+
edgeTopOpacity: z2.number().min(0).max(1).default(0.3),
|
|
4410
|
+
edgeBottomOpacity: z2.number().min(0).max(1).default(0.4)
|
|
4125
4411
|
}).strict(),
|
|
4126
4412
|
z2.object({
|
|
4127
4413
|
type: z2.literal("gradient-overlay"),
|
|
@@ -4460,7 +4746,16 @@ async function renderDesign(input, options = {}) {
|
|
|
4460
4746
|
const deferredVignettes = [];
|
|
4461
4747
|
for (const [index, decorator] of spec.decorators.entries()) {
|
|
4462
4748
|
if (decorator.type === "vignette") {
|
|
4463
|
-
deferredVignettes.push({
|
|
4749
|
+
deferredVignettes.push({
|
|
4750
|
+
index,
|
|
4751
|
+
mode: decorator.mode,
|
|
4752
|
+
intensity: decorator.intensity,
|
|
4753
|
+
color: decorator.color,
|
|
4754
|
+
edgeTopHeight: decorator.edgeTopHeight,
|
|
4755
|
+
edgeBottomHeight: decorator.edgeBottomHeight,
|
|
4756
|
+
edgeTopOpacity: decorator.edgeTopOpacity,
|
|
4757
|
+
edgeBottomOpacity: decorator.edgeBottomOpacity
|
|
4758
|
+
});
|
|
4464
4759
|
continue;
|
|
4465
4760
|
}
|
|
4466
4761
|
if (decorator.type === "gradient-overlay") {
|
|
@@ -4533,6 +4828,9 @@ async function renderDesign(input, options = {}) {
|
|
|
4533
4828
|
case "image":
|
|
4534
4829
|
elements.push(...await renderImageElement(ctx, element, rect, theme));
|
|
4535
4830
|
break;
|
|
4831
|
+
case "ring":
|
|
4832
|
+
elements.push(...renderRingElement(ctx, element, rect, theme));
|
|
4833
|
+
break;
|
|
4536
4834
|
}
|
|
4537
4835
|
}
|
|
4538
4836
|
const nodeBounds = spec.elements.filter((element) => element.type !== "connection").map((element) => elementRects.get(element.id)).filter((rect) => rect != null);
|
|
@@ -4587,7 +4885,20 @@ async function renderDesign(input, options = {}) {
|
|
|
4587
4885
|
}
|
|
4588
4886
|
elements.push(...renderDrawCommands(ctx, spec.draw, theme));
|
|
4589
4887
|
for (const vignette of deferredVignettes) {
|
|
4590
|
-
|
|
4888
|
+
if (vignette.mode === "edge") {
|
|
4889
|
+
drawEdgeVignette(
|
|
4890
|
+
ctx,
|
|
4891
|
+
spec.canvas.width,
|
|
4892
|
+
spec.canvas.height,
|
|
4893
|
+
vignette.color,
|
|
4894
|
+
vignette.edgeTopHeight,
|
|
4895
|
+
vignette.edgeBottomHeight,
|
|
4896
|
+
vignette.edgeTopOpacity,
|
|
4897
|
+
vignette.edgeBottomOpacity
|
|
4898
|
+
);
|
|
4899
|
+
} else {
|
|
4900
|
+
drawVignette(ctx, spec.canvas.width, spec.canvas.height, vignette.intensity, vignette.color);
|
|
4901
|
+
}
|
|
4591
4902
|
elements.push({
|
|
4592
4903
|
id: `decorator-vignette-${vignette.index}`,
|
|
4593
4904
|
kind: "vignette",
|