@spectratools/graphic-designer-cli 0.8.0 → 0.9.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 +110 -0
- package/dist/index.js +110 -0
- package/dist/renderer.js +110 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1867,6 +1867,38 @@ function renderFlowNode(ctx, node, bounds, theme) {
|
|
|
1867
1867
|
ctx.shadowOffsetX = 0;
|
|
1868
1868
|
ctx.shadowOffsetY = 0;
|
|
1869
1869
|
}
|
|
1870
|
+
if (node.accentColor) {
|
|
1871
|
+
const barWidth = node.accentBarWidth ?? 3;
|
|
1872
|
+
const effectiveRadius = node.shape === "box" ? 0 : cornerRadius;
|
|
1873
|
+
ctx.save();
|
|
1874
|
+
ctx.beginPath();
|
|
1875
|
+
ctx.roundRect(bounds.x, bounds.y, bounds.width, bounds.height, effectiveRadius);
|
|
1876
|
+
ctx.clip();
|
|
1877
|
+
ctx.fillStyle = node.accentColor;
|
|
1878
|
+
ctx.fillRect(bounds.x, bounds.y, barWidth, bounds.height);
|
|
1879
|
+
ctx.restore();
|
|
1880
|
+
}
|
|
1881
|
+
if (node.glowColor) {
|
|
1882
|
+
const glowW = node.glowWidth ?? 16;
|
|
1883
|
+
const glowOp = node.glowOpacity ?? 0.15;
|
|
1884
|
+
ctx.save();
|
|
1885
|
+
ctx.beginPath();
|
|
1886
|
+
ctx.roundRect(bounds.x, bounds.y, bounds.width, bounds.height, cornerRadius);
|
|
1887
|
+
ctx.clip();
|
|
1888
|
+
const barOffset = node.accentColor ? node.accentBarWidth ?? 3 : 0;
|
|
1889
|
+
const gradient = ctx.createLinearGradient(
|
|
1890
|
+
bounds.x + barOffset,
|
|
1891
|
+
bounds.y,
|
|
1892
|
+
bounds.x + barOffset + glowW,
|
|
1893
|
+
bounds.y
|
|
1894
|
+
);
|
|
1895
|
+
gradient.addColorStop(0, node.glowColor);
|
|
1896
|
+
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
|
1897
|
+
ctx.globalAlpha = glowOp;
|
|
1898
|
+
ctx.fillStyle = gradient;
|
|
1899
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1900
|
+
ctx.restore();
|
|
1901
|
+
}
|
|
1870
1902
|
const headingFont = resolveFont(theme.fonts.heading, "heading");
|
|
1871
1903
|
const bodyFont = resolveFont(theme.fonts.body, "body");
|
|
1872
1904
|
const monoFont = resolveFont(theme.fonts.mono, "mono");
|
|
@@ -4248,6 +4280,84 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4248
4280
|
});
|
|
4249
4281
|
break;
|
|
4250
4282
|
}
|
|
4283
|
+
case "text-row": {
|
|
4284
|
+
const segments = command.segments;
|
|
4285
|
+
if (segments.length === 0) break;
|
|
4286
|
+
const resolveSegment = (seg) => ({
|
|
4287
|
+
text: seg.text,
|
|
4288
|
+
fontSize: seg.fontSize ?? command.defaultFontSize,
|
|
4289
|
+
fontWeight: seg.fontWeight ?? command.defaultFontWeight,
|
|
4290
|
+
fontFamily: resolveDrawFont(theme, seg.fontFamily ?? command.defaultFontFamily),
|
|
4291
|
+
color: seg.color ?? command.defaultColor
|
|
4292
|
+
});
|
|
4293
|
+
const measured = [];
|
|
4294
|
+
let totalWidth = 0;
|
|
4295
|
+
let maxAscent = 0;
|
|
4296
|
+
let maxDescent = 0;
|
|
4297
|
+
for (const seg of segments) {
|
|
4298
|
+
const resolved = resolveSegment(seg);
|
|
4299
|
+
applyFont(ctx, {
|
|
4300
|
+
size: resolved.fontSize,
|
|
4301
|
+
weight: resolved.fontWeight,
|
|
4302
|
+
family: resolved.fontFamily
|
|
4303
|
+
});
|
|
4304
|
+
const metrics = ctx.measureText(resolved.text);
|
|
4305
|
+
const width = metrics.width;
|
|
4306
|
+
const ascent = metrics.actualBoundingBoxAscent || 0;
|
|
4307
|
+
const descent = metrics.actualBoundingBoxDescent || 0;
|
|
4308
|
+
totalWidth += width;
|
|
4309
|
+
maxAscent = Math.max(maxAscent, ascent);
|
|
4310
|
+
maxDescent = Math.max(maxDescent, descent);
|
|
4311
|
+
measured.push({ width, resolved });
|
|
4312
|
+
}
|
|
4313
|
+
let cursorX;
|
|
4314
|
+
if (command.align === "center") {
|
|
4315
|
+
cursorX = command.x - totalWidth / 2;
|
|
4316
|
+
} else if (command.align === "right") {
|
|
4317
|
+
cursorX = command.x - totalWidth;
|
|
4318
|
+
} else {
|
|
4319
|
+
cursorX = command.x;
|
|
4320
|
+
}
|
|
4321
|
+
const startX = cursorX;
|
|
4322
|
+
withOpacity(ctx, command.opacity, () => {
|
|
4323
|
+
ctx.textBaseline = command.baseline;
|
|
4324
|
+
for (const { width, resolved } of measured) {
|
|
4325
|
+
applyFont(ctx, {
|
|
4326
|
+
size: resolved.fontSize,
|
|
4327
|
+
weight: resolved.fontWeight,
|
|
4328
|
+
family: resolved.fontFamily
|
|
4329
|
+
});
|
|
4330
|
+
ctx.fillStyle = resolved.color;
|
|
4331
|
+
ctx.textAlign = "left";
|
|
4332
|
+
ctx.fillText(resolved.text, cursorX, command.y);
|
|
4333
|
+
cursorX += width;
|
|
4334
|
+
}
|
|
4335
|
+
});
|
|
4336
|
+
const height = Math.max(1, maxAscent + maxDescent);
|
|
4337
|
+
let topY;
|
|
4338
|
+
if (command.baseline === "top") {
|
|
4339
|
+
topY = command.y;
|
|
4340
|
+
} else if (command.baseline === "middle") {
|
|
4341
|
+
topY = command.y - height / 2;
|
|
4342
|
+
} else if (command.baseline === "bottom") {
|
|
4343
|
+
topY = command.y - height;
|
|
4344
|
+
} else {
|
|
4345
|
+
topY = command.y - maxAscent;
|
|
4346
|
+
}
|
|
4347
|
+
rendered.push({
|
|
4348
|
+
id,
|
|
4349
|
+
kind: "draw",
|
|
4350
|
+
bounds: {
|
|
4351
|
+
x: startX,
|
|
4352
|
+
y: topY,
|
|
4353
|
+
width: Math.max(1, totalWidth),
|
|
4354
|
+
height
|
|
4355
|
+
},
|
|
4356
|
+
foregroundColor: command.defaultColor,
|
|
4357
|
+
backgroundColor: theme.background
|
|
4358
|
+
});
|
|
4359
|
+
break;
|
|
4360
|
+
}
|
|
4251
4361
|
}
|
|
4252
4362
|
}
|
|
4253
4363
|
return rendered;
|
package/dist/index.js
CHANGED
|
@@ -1880,6 +1880,38 @@ function renderFlowNode(ctx, node, bounds, theme) {
|
|
|
1880
1880
|
ctx.shadowOffsetX = 0;
|
|
1881
1881
|
ctx.shadowOffsetY = 0;
|
|
1882
1882
|
}
|
|
1883
|
+
if (node.accentColor) {
|
|
1884
|
+
const barWidth = node.accentBarWidth ?? 3;
|
|
1885
|
+
const effectiveRadius = node.shape === "box" ? 0 : cornerRadius;
|
|
1886
|
+
ctx.save();
|
|
1887
|
+
ctx.beginPath();
|
|
1888
|
+
ctx.roundRect(bounds.x, bounds.y, bounds.width, bounds.height, effectiveRadius);
|
|
1889
|
+
ctx.clip();
|
|
1890
|
+
ctx.fillStyle = node.accentColor;
|
|
1891
|
+
ctx.fillRect(bounds.x, bounds.y, barWidth, bounds.height);
|
|
1892
|
+
ctx.restore();
|
|
1893
|
+
}
|
|
1894
|
+
if (node.glowColor) {
|
|
1895
|
+
const glowW = node.glowWidth ?? 16;
|
|
1896
|
+
const glowOp = node.glowOpacity ?? 0.15;
|
|
1897
|
+
ctx.save();
|
|
1898
|
+
ctx.beginPath();
|
|
1899
|
+
ctx.roundRect(bounds.x, bounds.y, bounds.width, bounds.height, cornerRadius);
|
|
1900
|
+
ctx.clip();
|
|
1901
|
+
const barOffset = node.accentColor ? node.accentBarWidth ?? 3 : 0;
|
|
1902
|
+
const gradient = ctx.createLinearGradient(
|
|
1903
|
+
bounds.x + barOffset,
|
|
1904
|
+
bounds.y,
|
|
1905
|
+
bounds.x + barOffset + glowW,
|
|
1906
|
+
bounds.y
|
|
1907
|
+
);
|
|
1908
|
+
gradient.addColorStop(0, node.glowColor);
|
|
1909
|
+
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
|
1910
|
+
ctx.globalAlpha = glowOp;
|
|
1911
|
+
ctx.fillStyle = gradient;
|
|
1912
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
1913
|
+
ctx.restore();
|
|
1914
|
+
}
|
|
1883
1915
|
const headingFont = resolveFont(theme.fonts.heading, "heading");
|
|
1884
1916
|
const bodyFont = resolveFont(theme.fonts.body, "body");
|
|
1885
1917
|
const monoFont = resolveFont(theme.fonts.mono, "mono");
|
|
@@ -4265,6 +4297,84 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4265
4297
|
});
|
|
4266
4298
|
break;
|
|
4267
4299
|
}
|
|
4300
|
+
case "text-row": {
|
|
4301
|
+
const segments = command.segments;
|
|
4302
|
+
if (segments.length === 0) break;
|
|
4303
|
+
const resolveSegment = (seg) => ({
|
|
4304
|
+
text: seg.text,
|
|
4305
|
+
fontSize: seg.fontSize ?? command.defaultFontSize,
|
|
4306
|
+
fontWeight: seg.fontWeight ?? command.defaultFontWeight,
|
|
4307
|
+
fontFamily: resolveDrawFont(theme, seg.fontFamily ?? command.defaultFontFamily),
|
|
4308
|
+
color: seg.color ?? command.defaultColor
|
|
4309
|
+
});
|
|
4310
|
+
const measured = [];
|
|
4311
|
+
let totalWidth = 0;
|
|
4312
|
+
let maxAscent = 0;
|
|
4313
|
+
let maxDescent = 0;
|
|
4314
|
+
for (const seg of segments) {
|
|
4315
|
+
const resolved = resolveSegment(seg);
|
|
4316
|
+
applyFont(ctx, {
|
|
4317
|
+
size: resolved.fontSize,
|
|
4318
|
+
weight: resolved.fontWeight,
|
|
4319
|
+
family: resolved.fontFamily
|
|
4320
|
+
});
|
|
4321
|
+
const metrics = ctx.measureText(resolved.text);
|
|
4322
|
+
const width = metrics.width;
|
|
4323
|
+
const ascent = metrics.actualBoundingBoxAscent || 0;
|
|
4324
|
+
const descent = metrics.actualBoundingBoxDescent || 0;
|
|
4325
|
+
totalWidth += width;
|
|
4326
|
+
maxAscent = Math.max(maxAscent, ascent);
|
|
4327
|
+
maxDescent = Math.max(maxDescent, descent);
|
|
4328
|
+
measured.push({ width, resolved });
|
|
4329
|
+
}
|
|
4330
|
+
let cursorX;
|
|
4331
|
+
if (command.align === "center") {
|
|
4332
|
+
cursorX = command.x - totalWidth / 2;
|
|
4333
|
+
} else if (command.align === "right") {
|
|
4334
|
+
cursorX = command.x - totalWidth;
|
|
4335
|
+
} else {
|
|
4336
|
+
cursorX = command.x;
|
|
4337
|
+
}
|
|
4338
|
+
const startX = cursorX;
|
|
4339
|
+
withOpacity(ctx, command.opacity, () => {
|
|
4340
|
+
ctx.textBaseline = command.baseline;
|
|
4341
|
+
for (const { width, resolved } of measured) {
|
|
4342
|
+
applyFont(ctx, {
|
|
4343
|
+
size: resolved.fontSize,
|
|
4344
|
+
weight: resolved.fontWeight,
|
|
4345
|
+
family: resolved.fontFamily
|
|
4346
|
+
});
|
|
4347
|
+
ctx.fillStyle = resolved.color;
|
|
4348
|
+
ctx.textAlign = "left";
|
|
4349
|
+
ctx.fillText(resolved.text, cursorX, command.y);
|
|
4350
|
+
cursorX += width;
|
|
4351
|
+
}
|
|
4352
|
+
});
|
|
4353
|
+
const height = Math.max(1, maxAscent + maxDescent);
|
|
4354
|
+
let topY;
|
|
4355
|
+
if (command.baseline === "top") {
|
|
4356
|
+
topY = command.y;
|
|
4357
|
+
} else if (command.baseline === "middle") {
|
|
4358
|
+
topY = command.y - height / 2;
|
|
4359
|
+
} else if (command.baseline === "bottom") {
|
|
4360
|
+
topY = command.y - height;
|
|
4361
|
+
} else {
|
|
4362
|
+
topY = command.y - maxAscent;
|
|
4363
|
+
}
|
|
4364
|
+
rendered.push({
|
|
4365
|
+
id,
|
|
4366
|
+
kind: "draw",
|
|
4367
|
+
bounds: {
|
|
4368
|
+
x: startX,
|
|
4369
|
+
y: topY,
|
|
4370
|
+
width: Math.max(1, totalWidth),
|
|
4371
|
+
height
|
|
4372
|
+
},
|
|
4373
|
+
foregroundColor: command.defaultColor,
|
|
4374
|
+
backgroundColor: theme.background
|
|
4375
|
+
});
|
|
4376
|
+
break;
|
|
4377
|
+
}
|
|
4268
4378
|
}
|
|
4269
4379
|
}
|
|
4270
4380
|
return rendered;
|
package/dist/renderer.js
CHANGED
|
@@ -473,6 +473,38 @@ function renderFlowNode(ctx, node, bounds, theme) {
|
|
|
473
473
|
ctx.shadowOffsetX = 0;
|
|
474
474
|
ctx.shadowOffsetY = 0;
|
|
475
475
|
}
|
|
476
|
+
if (node.accentColor) {
|
|
477
|
+
const barWidth = node.accentBarWidth ?? 3;
|
|
478
|
+
const effectiveRadius = node.shape === "box" ? 0 : cornerRadius;
|
|
479
|
+
ctx.save();
|
|
480
|
+
ctx.beginPath();
|
|
481
|
+
ctx.roundRect(bounds.x, bounds.y, bounds.width, bounds.height, effectiveRadius);
|
|
482
|
+
ctx.clip();
|
|
483
|
+
ctx.fillStyle = node.accentColor;
|
|
484
|
+
ctx.fillRect(bounds.x, bounds.y, barWidth, bounds.height);
|
|
485
|
+
ctx.restore();
|
|
486
|
+
}
|
|
487
|
+
if (node.glowColor) {
|
|
488
|
+
const glowW = node.glowWidth ?? 16;
|
|
489
|
+
const glowOp = node.glowOpacity ?? 0.15;
|
|
490
|
+
ctx.save();
|
|
491
|
+
ctx.beginPath();
|
|
492
|
+
ctx.roundRect(bounds.x, bounds.y, bounds.width, bounds.height, cornerRadius);
|
|
493
|
+
ctx.clip();
|
|
494
|
+
const barOffset = node.accentColor ? node.accentBarWidth ?? 3 : 0;
|
|
495
|
+
const gradient = ctx.createLinearGradient(
|
|
496
|
+
bounds.x + barOffset,
|
|
497
|
+
bounds.y,
|
|
498
|
+
bounds.x + barOffset + glowW,
|
|
499
|
+
bounds.y
|
|
500
|
+
);
|
|
501
|
+
gradient.addColorStop(0, node.glowColor);
|
|
502
|
+
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
|
503
|
+
ctx.globalAlpha = glowOp;
|
|
504
|
+
ctx.fillStyle = gradient;
|
|
505
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
506
|
+
ctx.restore();
|
|
507
|
+
}
|
|
476
508
|
const headingFont = resolveFont(theme.fonts.heading, "heading");
|
|
477
509
|
const bodyFont = resolveFont(theme.fonts.body, "body");
|
|
478
510
|
const monoFont = resolveFont(theme.fonts.mono, "mono");
|
|
@@ -3086,6 +3118,84 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
3086
3118
|
});
|
|
3087
3119
|
break;
|
|
3088
3120
|
}
|
|
3121
|
+
case "text-row": {
|
|
3122
|
+
const segments = command.segments;
|
|
3123
|
+
if (segments.length === 0) break;
|
|
3124
|
+
const resolveSegment = (seg) => ({
|
|
3125
|
+
text: seg.text,
|
|
3126
|
+
fontSize: seg.fontSize ?? command.defaultFontSize,
|
|
3127
|
+
fontWeight: seg.fontWeight ?? command.defaultFontWeight,
|
|
3128
|
+
fontFamily: resolveDrawFont(theme, seg.fontFamily ?? command.defaultFontFamily),
|
|
3129
|
+
color: seg.color ?? command.defaultColor
|
|
3130
|
+
});
|
|
3131
|
+
const measured = [];
|
|
3132
|
+
let totalWidth = 0;
|
|
3133
|
+
let maxAscent = 0;
|
|
3134
|
+
let maxDescent = 0;
|
|
3135
|
+
for (const seg of segments) {
|
|
3136
|
+
const resolved = resolveSegment(seg);
|
|
3137
|
+
applyFont(ctx, {
|
|
3138
|
+
size: resolved.fontSize,
|
|
3139
|
+
weight: resolved.fontWeight,
|
|
3140
|
+
family: resolved.fontFamily
|
|
3141
|
+
});
|
|
3142
|
+
const metrics = ctx.measureText(resolved.text);
|
|
3143
|
+
const width = metrics.width;
|
|
3144
|
+
const ascent = metrics.actualBoundingBoxAscent || 0;
|
|
3145
|
+
const descent = metrics.actualBoundingBoxDescent || 0;
|
|
3146
|
+
totalWidth += width;
|
|
3147
|
+
maxAscent = Math.max(maxAscent, ascent);
|
|
3148
|
+
maxDescent = Math.max(maxDescent, descent);
|
|
3149
|
+
measured.push({ width, resolved });
|
|
3150
|
+
}
|
|
3151
|
+
let cursorX;
|
|
3152
|
+
if (command.align === "center") {
|
|
3153
|
+
cursorX = command.x - totalWidth / 2;
|
|
3154
|
+
} else if (command.align === "right") {
|
|
3155
|
+
cursorX = command.x - totalWidth;
|
|
3156
|
+
} else {
|
|
3157
|
+
cursorX = command.x;
|
|
3158
|
+
}
|
|
3159
|
+
const startX = cursorX;
|
|
3160
|
+
withOpacity(ctx, command.opacity, () => {
|
|
3161
|
+
ctx.textBaseline = command.baseline;
|
|
3162
|
+
for (const { width, resolved } of measured) {
|
|
3163
|
+
applyFont(ctx, {
|
|
3164
|
+
size: resolved.fontSize,
|
|
3165
|
+
weight: resolved.fontWeight,
|
|
3166
|
+
family: resolved.fontFamily
|
|
3167
|
+
});
|
|
3168
|
+
ctx.fillStyle = resolved.color;
|
|
3169
|
+
ctx.textAlign = "left";
|
|
3170
|
+
ctx.fillText(resolved.text, cursorX, command.y);
|
|
3171
|
+
cursorX += width;
|
|
3172
|
+
}
|
|
3173
|
+
});
|
|
3174
|
+
const height = Math.max(1, maxAscent + maxDescent);
|
|
3175
|
+
let topY;
|
|
3176
|
+
if (command.baseline === "top") {
|
|
3177
|
+
topY = command.y;
|
|
3178
|
+
} else if (command.baseline === "middle") {
|
|
3179
|
+
topY = command.y - height / 2;
|
|
3180
|
+
} else if (command.baseline === "bottom") {
|
|
3181
|
+
topY = command.y - height;
|
|
3182
|
+
} else {
|
|
3183
|
+
topY = command.y - maxAscent;
|
|
3184
|
+
}
|
|
3185
|
+
rendered.push({
|
|
3186
|
+
id,
|
|
3187
|
+
kind: "draw",
|
|
3188
|
+
bounds: {
|
|
3189
|
+
x: startX,
|
|
3190
|
+
y: topY,
|
|
3191
|
+
width: Math.max(1, totalWidth),
|
|
3192
|
+
height
|
|
3193
|
+
},
|
|
3194
|
+
foregroundColor: command.defaultColor,
|
|
3195
|
+
backgroundColor: theme.background
|
|
3196
|
+
});
|
|
3197
|
+
break;
|
|
3198
|
+
}
|
|
3089
3199
|
}
|
|
3090
3200
|
}
|
|
3091
3201
|
return rendered;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectratools/graphic-designer-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Deterministic visual content generator — code screenshots, terminal shots, flowcharts, and infographics. No browser dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|