@spectratools/graphic-designer-cli 0.6.0 → 0.7.1

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 CHANGED
@@ -752,9 +752,19 @@ var linearGradientSchema = z2.object({
752
752
  }).strict();
753
753
  var radialGradientSchema = z2.object({
754
754
  type: z2.literal("radial"),
755
+ cx: z2.number().optional(),
756
+ cy: z2.number().optional(),
757
+ innerRadius: z2.number().min(0).optional(),
758
+ outerRadius: z2.number().min(0).optional(),
755
759
  stops: z2.array(gradientStopSchema).min(2)
756
760
  }).strict();
757
761
  var gradientSchema = z2.discriminatedUnion("type", [linearGradientSchema, radialGradientSchema]);
762
+ var drawShadowSchema = z2.object({
763
+ color: colorHexSchema2.default("rgba(0,0,0,0.5)"),
764
+ blur: z2.number().min(0).max(64).default(10),
765
+ offsetX: z2.number().default(0),
766
+ offsetY: z2.number().default(4)
767
+ }).strict();
758
768
  var drawFontFamilySchema = z2.enum(["heading", "body", "mono"]);
759
769
  var drawRectSchema = z2.object({
760
770
  type: z2.literal("rect"),
@@ -766,7 +776,8 @@ var drawRectSchema = z2.object({
766
776
  stroke: colorHexSchema2.optional(),
767
777
  strokeWidth: z2.number().min(0).max(32).default(0),
768
778
  radius: z2.number().min(0).max(256).default(0),
769
- opacity: z2.number().min(0).max(1).default(1)
779
+ opacity: z2.number().min(0).max(1).default(1),
780
+ shadow: drawShadowSchema.optional()
770
781
  }).strict();
771
782
  var drawCircleSchema = z2.object({
772
783
  type: z2.literal("circle"),
@@ -776,7 +787,8 @@ var drawCircleSchema = z2.object({
776
787
  fill: colorHexSchema2.optional(),
777
788
  stroke: colorHexSchema2.optional(),
778
789
  strokeWidth: z2.number().min(0).max(32).default(0),
779
- opacity: z2.number().min(0).max(1).default(1)
790
+ opacity: z2.number().min(0).max(1).default(1),
791
+ shadow: drawShadowSchema.optional()
780
792
  }).strict();
781
793
  var drawTextSchema = z2.object({
782
794
  type: z2.literal("text"),
@@ -791,7 +803,8 @@ var drawTextSchema = z2.object({
791
803
  baseline: z2.enum(["top", "middle", "alphabetic", "bottom"]).default("alphabetic"),
792
804
  letterSpacing: z2.number().min(-10).max(50).default(0),
793
805
  maxWidth: z2.number().positive().optional(),
794
- opacity: z2.number().min(0).max(1).default(1)
806
+ opacity: z2.number().min(0).max(1).default(1),
807
+ shadow: drawShadowSchema.optional()
795
808
  }).strict();
796
809
  var drawLineSchema = z2.object({
797
810
  type: z2.literal("line"),
@@ -804,7 +817,8 @@ var drawLineSchema = z2.object({
804
817
  dash: z2.array(z2.number()).max(6).optional(),
805
818
  arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
806
819
  arrowSize: z2.number().min(4).max(32).default(10),
807
- opacity: z2.number().min(0).max(1).default(1)
820
+ opacity: z2.number().min(0).max(1).default(1),
821
+ shadow: drawShadowSchema.optional()
808
822
  }).strict();
809
823
  var drawPointSchema = z2.object({
810
824
  x: z2.number(),
@@ -818,7 +832,8 @@ var drawBezierSchema = z2.object({
818
832
  dash: z2.array(z2.number()).max(6).optional(),
819
833
  arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
820
834
  arrowSize: z2.number().min(4).max(32).default(10),
821
- opacity: z2.number().min(0).max(1).default(1)
835
+ opacity: z2.number().min(0).max(1).default(1),
836
+ shadow: drawShadowSchema.optional()
822
837
  }).strict();
823
838
  var drawPathSchema = z2.object({
824
839
  type: z2.literal("path"),
@@ -826,7 +841,8 @@ var drawPathSchema = z2.object({
826
841
  fill: colorHexSchema2.optional(),
827
842
  stroke: colorHexSchema2.optional(),
828
843
  strokeWidth: z2.number().min(0).max(32).default(0),
829
- opacity: z2.number().min(0).max(1).default(1)
844
+ opacity: z2.number().min(0).max(1).default(1),
845
+ shadow: drawShadowSchema.optional()
830
846
  }).strict();
831
847
  var drawBadgeSchema = z2.object({
832
848
  type: z2.literal("badge"),
@@ -840,7 +856,8 @@ var drawBadgeSchema = z2.object({
840
856
  paddingX: z2.number().min(0).max(64).default(10),
841
857
  paddingY: z2.number().min(0).max(32).default(4),
842
858
  borderRadius: z2.number().min(0).max(64).default(12),
843
- opacity: z2.number().min(0).max(1).default(1)
859
+ opacity: z2.number().min(0).max(1).default(1),
860
+ shadow: drawShadowSchema.optional()
844
861
  }).strict();
845
862
  var drawGradientRectSchema = z2.object({
846
863
  type: z2.literal("gradient-rect"),
@@ -850,7 +867,17 @@ var drawGradientRectSchema = z2.object({
850
867
  height: z2.number().positive(),
851
868
  gradient: gradientSchema,
852
869
  radius: z2.number().min(0).max(256).default(0),
853
- opacity: z2.number().min(0).max(1).default(1)
870
+ opacity: z2.number().min(0).max(1).default(1),
871
+ shadow: drawShadowSchema.optional()
872
+ }).strict();
873
+ var drawGridSchema = z2.object({
874
+ type: z2.literal("grid"),
875
+ spacing: z2.number().min(5).max(200).default(40),
876
+ color: colorHexSchema2.default("#1E2D4A"),
877
+ width: z2.number().min(0.1).max(4).default(0.5),
878
+ opacity: z2.number().min(0).max(1).default(0.2),
879
+ offsetX: z2.number().default(0),
880
+ offsetY: z2.number().default(0)
854
881
  }).strict();
855
882
  var drawCommandSchema = z2.discriminatedUnion("type", [
856
883
  drawRectSchema,
@@ -860,7 +887,8 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
860
887
  drawBezierSchema,
861
888
  drawPathSchema,
862
889
  drawBadgeSchema,
863
- drawGradientRectSchema
890
+ drawGradientRectSchema,
891
+ drawGridSchema
864
892
  ]);
865
893
  var defaultCanvas = {
866
894
  width: 1200,
@@ -966,6 +994,13 @@ var flowNodeElementSchema = z2.object({
966
994
  badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
967
995
  shadow: flowNodeShadowSchema.optional()
968
996
  }).strict();
997
+ var anchorHintSchema = z2.union([
998
+ z2.enum(["top", "bottom", "left", "right", "center"]),
999
+ z2.object({
1000
+ x: z2.number().min(-1).max(1),
1001
+ y: z2.number().min(-1).max(1)
1002
+ }).strict()
1003
+ ]);
969
1004
  var connectionElementSchema = z2.object({
970
1005
  type: z2.literal("connection"),
971
1006
  from: z2.string().min(1).max(120),
@@ -979,9 +1014,12 @@ var connectionElementSchema = z2.object({
979
1014
  width: z2.number().min(0.5).max(10).optional(),
980
1015
  strokeWidth: z2.number().min(0.5).max(10).default(2),
981
1016
  arrowSize: z2.number().min(4).max(32).optional(),
1017
+ arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
982
1018
  opacity: z2.number().min(0).max(1).default(1),
983
1019
  routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
984
- tension: z2.number().min(0.1).max(0.8).default(0.35)
1020
+ tension: z2.number().min(0.1).max(0.8).default(0.35),
1021
+ fromAnchor: anchorHintSchema.optional(),
1022
+ toAnchor: anchorHintSchema.optional()
985
1023
  }).strict();
986
1024
  var codeBlockStyleSchema = z2.object({
987
1025
  paddingVertical: z2.number().min(0).max(128).default(56),
@@ -2479,12 +2517,12 @@ function withAlpha2(color, alpha) {
2479
2517
  }
2480
2518
  function drawGradientRect(ctx, rect, gradient, borderRadius = 0) {
2481
2519
  const fill = gradient.type === "linear" ? createLinearRectGradient(ctx, rect, gradient.angle ?? 180) : ctx.createRadialGradient(
2482
- rect.x + rect.width / 2,
2483
- rect.y + rect.height / 2,
2484
- 0,
2485
- rect.x + rect.width / 2,
2486
- rect.y + rect.height / 2,
2487
- Math.max(rect.width, rect.height) / 2
2520
+ gradient.cx ?? rect.x + rect.width / 2,
2521
+ gradient.cy ?? rect.y + rect.height / 2,
2522
+ gradient.innerRadius ?? 0,
2523
+ gradient.cx ?? rect.x + rect.width / 2,
2524
+ gradient.cy ?? rect.y + rect.height / 2,
2525
+ gradient.outerRadius ?? Math.max(rect.width, rect.height) / 2
2488
2526
  );
2489
2527
  addGradientStops(fill, gradient.stops);
2490
2528
  ctx.save();
@@ -3105,21 +3143,61 @@ function edgeAnchor(bounds, target) {
3105
3143
  const t = absDx * hh > absDy * hw ? hw / absDx : hh / absDy;
3106
3144
  return { x: c.x + dx * t, y: c.y + dy * t };
3107
3145
  }
3146
+ function resolveAnchor(bounds, anchor, fallbackTarget) {
3147
+ if (!anchor) return edgeAnchor(bounds, fallbackTarget);
3148
+ if (typeof anchor === "string") {
3149
+ const c2 = rectCenter(bounds);
3150
+ switch (anchor) {
3151
+ case "top":
3152
+ return { x: c2.x, y: bounds.y };
3153
+ case "bottom":
3154
+ return { x: c2.x, y: bounds.y + bounds.height };
3155
+ case "left":
3156
+ return { x: bounds.x, y: c2.y };
3157
+ case "right":
3158
+ return { x: bounds.x + bounds.width, y: c2.y };
3159
+ case "center":
3160
+ return c2;
3161
+ }
3162
+ }
3163
+ const c = rectCenter(bounds);
3164
+ return {
3165
+ x: c.x + anchor.x * (bounds.width / 2),
3166
+ y: c.y + anchor.y * (bounds.height / 2)
3167
+ };
3168
+ }
3169
+ function anchorNormal(anchor, point, diagramCenter) {
3170
+ if (typeof anchor === "string") {
3171
+ switch (anchor) {
3172
+ case "top":
3173
+ return { x: 0, y: -1 };
3174
+ case "bottom":
3175
+ return { x: 0, y: 1 };
3176
+ case "left":
3177
+ return { x: -1, y: 0 };
3178
+ case "right":
3179
+ return { x: 1, y: 0 };
3180
+ case "center":
3181
+ return outwardNormal(point, diagramCenter);
3182
+ }
3183
+ }
3184
+ return outwardNormal(point, diagramCenter);
3185
+ }
3108
3186
  function outwardNormal(point, diagramCenter) {
3109
3187
  const dx = point.x - diagramCenter.x;
3110
3188
  const dy = point.y - diagramCenter.y;
3111
3189
  const len = Math.hypot(dx, dy) || 1;
3112
3190
  return { x: dx / len, y: dy / len };
3113
3191
  }
3114
- function curveRoute(fromBounds, toBounds, diagramCenter, tension) {
3192
+ function curveRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAnchor) {
3115
3193
  const fromCenter = rectCenter(fromBounds);
3116
3194
  const toCenter = rectCenter(toBounds);
3117
- const p0 = edgeAnchor(fromBounds, toCenter);
3118
- const p3 = edgeAnchor(toBounds, fromCenter);
3195
+ const p0 = resolveAnchor(fromBounds, fromAnchor, toCenter);
3196
+ const p3 = resolveAnchor(toBounds, toAnchor, fromCenter);
3119
3197
  const dist = Math.hypot(p3.x - p0.x, p3.y - p0.y);
3120
3198
  const offset = dist * tension;
3121
- const n0 = outwardNormal(p0, diagramCenter);
3122
- const n3 = outwardNormal(p3, diagramCenter);
3199
+ const n0 = anchorNormal(fromAnchor, p0, diagramCenter);
3200
+ const n3 = anchorNormal(toAnchor, p3, diagramCenter);
3123
3201
  const cp1 = { x: p0.x + n0.x * offset, y: p0.y + n0.y * offset };
3124
3202
  const cp2 = { x: p3.x + n3.x * offset, y: p3.y + n3.y * offset };
3125
3203
  return [p0, cp1, cp2, p3];
@@ -3133,11 +3211,11 @@ function localToWorld(origin, axisX, axisY, local) {
3133
3211
  y: origin.y + axisX.y * local.x + axisY.y * local.y
3134
3212
  };
3135
3213
  }
3136
- function arcRoute(fromBounds, toBounds, diagramCenter, tension) {
3214
+ function arcRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAnchor) {
3137
3215
  const fromCenter = rectCenter(fromBounds);
3138
3216
  const toCenter = rectCenter(toBounds);
3139
- const start = edgeAnchor(fromBounds, toCenter);
3140
- const end = edgeAnchor(toBounds, fromCenter);
3217
+ const start = resolveAnchor(fromBounds, fromAnchor, toCenter);
3218
+ const end = resolveAnchor(toBounds, toAnchor, fromCenter);
3141
3219
  const chord = { x: end.x - start.x, y: end.y - start.y };
3142
3220
  const chordLength = Math.hypot(chord.x, chord.y);
3143
3221
  if (chordLength < 1e-6) {
@@ -3175,11 +3253,11 @@ function arcRoute(fromBounds, toBounds, diagramCenter, tension) {
3175
3253
  [pMid, cp3, cp4, p3]
3176
3254
  ];
3177
3255
  }
3178
- function orthogonalRoute(fromBounds, toBounds) {
3256
+ function orthogonalRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
3179
3257
  const fromC = rectCenter(fromBounds);
3180
3258
  const toC = rectCenter(toBounds);
3181
- const p0 = edgeAnchor(fromBounds, toC);
3182
- const p3 = edgeAnchor(toBounds, fromC);
3259
+ const p0 = resolveAnchor(fromBounds, fromAnchor, toC);
3260
+ const p3 = resolveAnchor(toBounds, toAnchor, fromC);
3183
3261
  const midX = (p0.x + p3.x) / 2;
3184
3262
  return [p0, { x: midX, y: p0.y }, { x: midX, y: p3.y }, p3];
3185
3263
  }
@@ -3190,6 +3268,35 @@ function bezierPointAt(p0, cp1, cp2, p3, t) {
3190
3268
  y: mt * mt * mt * p0.y + 3 * mt * mt * t * cp1.y + 3 * mt * t * t * cp2.y + t * t * t * p3.y
3191
3269
  };
3192
3270
  }
3271
+ function bezierTangentAt(p0, cp1, cp2, p3, t) {
3272
+ const mt = 1 - t;
3273
+ return {
3274
+ x: 3 * mt * mt * (cp1.x - p0.x) + 6 * mt * t * (cp2.x - cp1.x) + 3 * t * t * (p3.x - cp2.x),
3275
+ y: 3 * mt * mt * (cp1.y - p0.y) + 6 * mt * t * (cp2.y - cp1.y) + 3 * t * t * (p3.y - cp2.y)
3276
+ };
3277
+ }
3278
+ function isInsideRect(point, rect) {
3279
+ return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
3280
+ }
3281
+ function findBoundaryIntersection(p0, cp1, cp2, p3, targetRect, searchFromEnd) {
3282
+ const step = 5e-3;
3283
+ if (searchFromEnd) {
3284
+ for (let t = 0.95; t >= 0.5; t -= step) {
3285
+ const pt = bezierPointAt(p0, cp1, cp2, p3, t);
3286
+ if (!isInsideRect(pt, targetRect)) {
3287
+ return t;
3288
+ }
3289
+ }
3290
+ } else {
3291
+ for (let t = 0.05; t <= 0.5; t += step) {
3292
+ const pt = bezierPointAt(p0, cp1, cp2, p3, t);
3293
+ if (!isInsideRect(pt, targetRect)) {
3294
+ return t;
3295
+ }
3296
+ }
3297
+ }
3298
+ return void 0;
3299
+ }
3193
3300
  function pointAlongArc(route, t) {
3194
3301
  const [first, second] = route;
3195
3302
  if (t <= 0.5) {
@@ -3317,8 +3424,16 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3317
3424
  let labelPoint;
3318
3425
  ctx.save();
3319
3426
  ctx.globalAlpha = conn.opacity;
3427
+ const arrowPlacement = conn.arrowPlacement ?? "endpoint";
3320
3428
  if (routing === "curve") {
3321
- const [p0, cp1, cp2, p3] = curveRoute(fromBounds, toBounds, diagramCenter, tension);
3429
+ const [p0, cp1, cp2, p3] = curveRoute(
3430
+ fromBounds,
3431
+ toBounds,
3432
+ diagramCenter,
3433
+ tension,
3434
+ conn.fromAnchor,
3435
+ conn.toAnchor
3436
+ );
3322
3437
  ctx.strokeStyle = style.color;
3323
3438
  ctx.lineWidth = style.width;
3324
3439
  ctx.setLineDash(style.dash ?? []);
@@ -3332,8 +3447,33 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3332
3447
  startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
3333
3448
  endAngle = Math.atan2(p3.y - cp2.y, p3.x - cp2.x);
3334
3449
  labelPoint = bezierPointAt(p0, cp1, cp2, p3, labelT);
3450
+ if (arrowPlacement === "boundary") {
3451
+ if (conn.arrow === "end" || conn.arrow === "both") {
3452
+ const tEnd = findBoundaryIntersection(p0, cp1, cp2, p3, toBounds, true);
3453
+ if (tEnd !== void 0) {
3454
+ endPoint = bezierPointAt(p0, cp1, cp2, p3, tEnd);
3455
+ const tangent = bezierTangentAt(p0, cp1, cp2, p3, tEnd);
3456
+ endAngle = Math.atan2(tangent.y, tangent.x);
3457
+ }
3458
+ }
3459
+ if (conn.arrow === "start" || conn.arrow === "both") {
3460
+ const tStart = findBoundaryIntersection(p0, cp1, cp2, p3, fromBounds, false);
3461
+ if (tStart !== void 0) {
3462
+ startPoint = bezierPointAt(p0, cp1, cp2, p3, tStart);
3463
+ const tangent = bezierTangentAt(p0, cp1, cp2, p3, tStart);
3464
+ startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
3465
+ }
3466
+ }
3467
+ }
3335
3468
  } else if (routing === "arc") {
3336
- const [first, second] = arcRoute(fromBounds, toBounds, diagramCenter, tension);
3469
+ const [first, second] = arcRoute(
3470
+ fromBounds,
3471
+ toBounds,
3472
+ diagramCenter,
3473
+ tension,
3474
+ conn.fromAnchor,
3475
+ conn.toAnchor
3476
+ );
3337
3477
  const [p0, cp1, cp2, pMid] = first;
3338
3478
  const [, cp3, cp4, p3] = second;
3339
3479
  ctx.strokeStyle = style.color;
@@ -3350,9 +3490,29 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
3350
3490
  startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
3351
3491
  endAngle = Math.atan2(p3.y - cp4.y, p3.x - cp4.x);
3352
3492
  labelPoint = pointAlongArc([first, second], labelT);
3493
+ if (arrowPlacement === "boundary") {
3494
+ if (conn.arrow === "end" || conn.arrow === "both") {
3495
+ const [, s_cp3, s_cp4, s_p3] = second;
3496
+ const tEnd = findBoundaryIntersection(pMid, s_cp3, s_cp4, s_p3, toBounds, true);
3497
+ if (tEnd !== void 0) {
3498
+ endPoint = bezierPointAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
3499
+ const tangent = bezierTangentAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
3500
+ endAngle = Math.atan2(tangent.y, tangent.x);
3501
+ }
3502
+ }
3503
+ if (conn.arrow === "start" || conn.arrow === "both") {
3504
+ const tStart = findBoundaryIntersection(p0, cp1, cp2, pMid, fromBounds, false);
3505
+ if (tStart !== void 0) {
3506
+ startPoint = bezierPointAt(p0, cp1, cp2, pMid, tStart);
3507
+ const tangent = bezierTangentAt(p0, cp1, cp2, pMid, tStart);
3508
+ startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
3509
+ }
3510
+ }
3511
+ }
3353
3512
  } else {
3354
- const useElkRoute = routing === "auto" && (edgeRoute?.points.length ?? 0) >= 2;
3355
- linePoints = useElkRoute ? edgeRoute?.points ?? orthogonalRoute(fromBounds, toBounds) : orthogonalRoute(fromBounds, toBounds);
3513
+ const hasAnchorHints = conn.fromAnchor !== void 0 || conn.toAnchor !== void 0;
3514
+ const useElkRoute = routing === "auto" && !hasAnchorHints && (edgeRoute?.points.length ?? 0) >= 2;
3515
+ linePoints = useElkRoute ? edgeRoute?.points ?? orthogonalRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor) : orthogonalRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor);
3356
3516
  startPoint = linePoints[0];
3357
3517
  const startSegment = linePoints[1] ?? linePoints[0];
3358
3518
  const endStart = linePoints[linePoints.length - 2] ?? linePoints[0];
@@ -3584,6 +3744,13 @@ function expandRect(rect, amount) {
3584
3744
  height: rect.height + amount * 2
3585
3745
  };
3586
3746
  }
3747
+ function applyDrawShadow(ctx, shadow) {
3748
+ if (!shadow) return;
3749
+ ctx.shadowColor = shadow.color;
3750
+ ctx.shadowBlur = shadow.blur;
3751
+ ctx.shadowOffsetX = shadow.offsetX;
3752
+ ctx.shadowOffsetY = shadow.offsetY;
3753
+ }
3587
3754
  function fromPoints(points) {
3588
3755
  const minX = Math.min(...points.map((point) => point.x));
3589
3756
  const minY = Math.min(...points.map((point) => point.y));
@@ -3765,6 +3932,7 @@ function renderDrawCommands(ctx, commands, theme) {
3765
3932
  height: command.height
3766
3933
  };
3767
3934
  withOpacity(ctx, command.opacity, () => {
3935
+ applyDrawShadow(ctx, command.shadow);
3768
3936
  roundRectPath(ctx, rect, command.radius);
3769
3937
  if (command.fill) {
3770
3938
  ctx.fillStyle = command.fill;
@@ -3788,6 +3956,7 @@ function renderDrawCommands(ctx, commands, theme) {
3788
3956
  }
3789
3957
  case "circle": {
3790
3958
  withOpacity(ctx, command.opacity, () => {
3959
+ applyDrawShadow(ctx, command.shadow);
3791
3960
  ctx.beginPath();
3792
3961
  ctx.arc(command.cx, command.cy, command.radius, 0, Math.PI * 2);
3793
3962
  ctx.closePath();
@@ -3822,6 +3991,7 @@ function renderDrawCommands(ctx, commands, theme) {
3822
3991
  case "text": {
3823
3992
  const fontFamily = resolveDrawFont(theme, command.fontFamily);
3824
3993
  withOpacity(ctx, command.opacity, () => {
3994
+ applyDrawShadow(ctx, command.shadow);
3825
3995
  applyFont(ctx, {
3826
3996
  size: command.fontSize,
3827
3997
  weight: command.fontWeight,
@@ -3872,6 +4042,7 @@ function renderDrawCommands(ctx, commands, theme) {
3872
4042
  const to = { x: command.x2, y: command.y2 };
3873
4043
  const lineAngle = angleBetween(from, to);
3874
4044
  withOpacity(ctx, command.opacity, () => {
4045
+ applyDrawShadow(ctx, command.shadow);
3875
4046
  drawLine(ctx, from, to, {
3876
4047
  color: command.color,
3877
4048
  width: command.width,
@@ -3896,6 +4067,7 @@ function renderDrawCommands(ctx, commands, theme) {
3896
4067
  case "bezier": {
3897
4068
  const points = command.points;
3898
4069
  withOpacity(ctx, command.opacity, () => {
4070
+ applyDrawShadow(ctx, command.shadow);
3899
4071
  drawBezier(ctx, points, {
3900
4072
  color: command.color,
3901
4073
  width: command.width,
@@ -3929,6 +4101,7 @@ function renderDrawCommands(ctx, commands, theme) {
3929
4101
  const operations = parseSvgPath(command.d);
3930
4102
  const baseBounds = pathBounds(operations);
3931
4103
  withOpacity(ctx, command.opacity, () => {
4104
+ applyDrawShadow(ctx, command.shadow);
3932
4105
  applySvgOperations(ctx, operations);
3933
4106
  if (command.fill) {
3934
4107
  ctx.fillStyle = command.fill;
@@ -3969,9 +4142,16 @@ function renderDrawCommands(ctx, commands, theme) {
3969
4142
  height: textHeight + command.paddingY * 2
3970
4143
  };
3971
4144
  withOpacity(ctx, command.opacity, () => {
4145
+ applyDrawShadow(ctx, command.shadow);
3972
4146
  roundRectPath(ctx, rect, command.borderRadius);
3973
4147
  ctx.fillStyle = command.background;
3974
4148
  ctx.fill();
4149
+ if (command.shadow) {
4150
+ ctx.shadowColor = "transparent";
4151
+ ctx.shadowBlur = 0;
4152
+ ctx.shadowOffsetX = 0;
4153
+ ctx.shadowOffsetY = 0;
4154
+ }
3975
4155
  applyFont(ctx, {
3976
4156
  size: command.fontSize,
3977
4157
  weight: 600,
@@ -3999,6 +4179,7 @@ function renderDrawCommands(ctx, commands, theme) {
3999
4179
  height: command.height
4000
4180
  };
4001
4181
  withOpacity(ctx, command.opacity, () => {
4182
+ applyDrawShadow(ctx, command.shadow);
4002
4183
  drawGradientRect(ctx, rect, command.gradient, command.radius);
4003
4184
  });
4004
4185
  rendered.push({
@@ -4009,6 +4190,36 @@ function renderDrawCommands(ctx, commands, theme) {
4009
4190
  });
4010
4191
  break;
4011
4192
  }
4193
+ case "grid": {
4194
+ const canvasWidth = ctx.canvas.width;
4195
+ const canvasHeight = ctx.canvas.height;
4196
+ withOpacity(ctx, command.opacity, () => {
4197
+ ctx.strokeStyle = command.color;
4198
+ ctx.lineWidth = command.width;
4199
+ const startX = command.offsetX % command.spacing;
4200
+ for (let x = startX; x <= canvasWidth; x += command.spacing) {
4201
+ ctx.beginPath();
4202
+ ctx.moveTo(x, 0);
4203
+ ctx.lineTo(x, canvasHeight);
4204
+ ctx.stroke();
4205
+ }
4206
+ const startY = command.offsetY % command.spacing;
4207
+ for (let y = startY; y <= canvasHeight; y += command.spacing) {
4208
+ ctx.beginPath();
4209
+ ctx.moveTo(0, y);
4210
+ ctx.lineTo(canvasWidth, y);
4211
+ ctx.stroke();
4212
+ }
4213
+ });
4214
+ rendered.push({
4215
+ id,
4216
+ kind: "draw",
4217
+ bounds: { x: 0, y: 0, width: canvasWidth, height: canvasHeight },
4218
+ foregroundColor: command.color,
4219
+ allowOverlap: true
4220
+ });
4221
+ break;
4222
+ }
4012
4223
  }
4013
4224
  }
4014
4225
  return rendered;
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, C as ConnectionElement } from './spec.schema-Dm_wOLTd.js';
3
- export { A as AutoLayoutConfig, B as BuiltInTheme, e as CardElement, f as CodeBlockElement, g as ConstraintSpec, h as DEFAULT_GENERATOR_VERSION, i as DEFAULT_RAINBOW_COLORS, j as Decorator, k as DesignCardSpec, l as DesignSafeFrame, m as DesignTheme, n as DiagramElement, o as DiagramLayout, p as DiagramSpec, q as DrawBadge, r as DrawBezier, s as DrawCircle, t as DrawFontFamily, u as DrawGradientRect, v as DrawLine, w as DrawPath, x as DrawPoint, y as DrawRect, z as DrawText, E as Element, F as FlowNodeElement, G as Gradient, H as GradientOverlayDecorator, I as GradientSpec, J as GradientStop, K as GridLayoutConfig, L as ImageElement, M as LayoutConfig, N as LayoutSnapshot, O as ManualLayoutConfig, P as RainbowRuleDecorator, R as RenderMetadata, Q as RenderResult, S as ShapeElement, U as StackLayoutConfig, V as TerminalElement, W as TextElement, X as ThemeInput, Y as VignetteDecorator, Z as WrittenArtifacts, _ as builtInThemeBackgrounds, $ as builtInThemes, a0 as computeSpecHash, a1 as connectionElementSchema, a2 as defaultAutoLayout, a3 as defaultCanvas, a4 as defaultConstraints, a5 as defaultGridLayout, a6 as defaultLayout, a7 as defaultStackLayout, a8 as defaultTheme, a9 as deriveSafeFrame, aa as designSpecSchema, ab as diagramElementSchema, ac as diagramLayoutSchema, ad as diagramSpecSchema, ae as drawGradientRect, af as drawRainbowRule, ag as drawVignette, ah as flowNodeElementSchema, ai as inferLayout, aj as inferSidecarPath, ak as parseDesignSpec, al as parseDiagramSpec, am as renderDesign, an as resolveTheme, ao as writeRenderArtifacts } from './spec.schema-Dm_wOLTd.js';
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-BDvtn_mJ.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 DrawBadge, s as DrawBezier, t as DrawCircle, u as DrawFontFamily, v as DrawGradientRect, w as DrawLine, x as DrawPath, y as DrawPoint, z as DrawRect, E as DrawShadow, F as DrawText, G as Element, H as FlowNodeElement, I as Gradient, J as GradientOverlayDecorator, K as GradientSpec, L as GradientStop, M as GridLayoutConfig, N as ImageElement, O as LayoutConfig, P as LayoutSnapshot, Q as ManualLayoutConfig, S as RainbowRuleDecorator, R as RenderMetadata, U as RenderResult, V as ShapeElement, W as StackLayoutConfig, X as TerminalElement, Y as TextElement, Z as ThemeInput, _ as VignetteDecorator, $ as WrittenArtifacts, a0 as builtInThemeBackgrounds, a1 as builtInThemes, a2 as computeSpecHash, a3 as connectionElementSchema, a4 as defaultAutoLayout, a5 as defaultCanvas, a6 as defaultConstraints, a7 as defaultGridLayout, a8 as defaultLayout, a9 as defaultStackLayout, aa as defaultTheme, ab as deriveSafeFrame, ac as designSpecSchema, ad as diagramElementSchema, ae as diagramLayoutSchema, af as diagramSpecSchema, ag as drawGradientRect, ah as drawRainbowRule, ai as drawVignette, aj as flowNodeElementSchema, ak as inferLayout, al as inferSidecarPath, am as parseDesignSpec, an as parseDiagramSpec, ao as renderDesign, ap as resolveTheme, aq as writeRenderArtifacts } from './spec.schema-BDvtn_mJ.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';
@@ -275,21 +275,31 @@ declare function outwardNormal(point: Point, diagramCenter: Point): Point;
275
275
  /**
276
276
  * Compute a cubic bezier curve that bows outward from the diagram center.
277
277
  * Returns `[startPoint, controlPoint1, controlPoint2, endPoint]`.
278
+ *
279
+ * When `fromAnchor` or `toAnchor` hints are provided, they override the
280
+ * automatic edge anchor calculation and the outward normal is derived from
281
+ * the hint direction instead of from the diagram center.
278
282
  */
279
- declare function curveRoute(fromBounds: Rect, toBounds: Rect, diagramCenter: Point, tension: number): [Point, Point, Point, Point];
283
+ declare function curveRoute(fromBounds: Rect, toBounds: Rect, diagramCenter: Point, tension: number, fromAnchor?: AnchorHint, toAnchor?: AnchorHint): [Point, Point, Point, Point];
280
284
  /**
281
285
  * Approximate an outward-bowing half-ellipse with two cubic bezier segments.
282
286
  *
283
287
  * Uses the classic kappa constant (`4 * (sqrt(2) - 1) / 3`) for quarter-ellipse
284
288
  * control points, producing a stable arc from source edge anchor to target edge
285
289
  * anchor.
290
+ *
291
+ * When `fromAnchor` or `toAnchor` hints are provided, they override the
292
+ * automatic edge anchor calculation.
286
293
  */
287
- declare function arcRoute(fromBounds: Rect, toBounds: Rect, diagramCenter: Point, tension: number): [CubicBezierSegment, CubicBezierSegment];
294
+ declare function arcRoute(fromBounds: Rect, toBounds: Rect, diagramCenter: Point, tension: number, fromAnchor?: AnchorHint, toAnchor?: AnchorHint): [CubicBezierSegment, CubicBezierSegment];
288
295
  /**
289
296
  * Compute an orthogonal (right-angle) path between two rectangles.
290
297
  * Returns an array of waypoints forming a 3-segment path.
298
+ *
299
+ * When `fromAnchor` or `toAnchor` hints are provided, they override the
300
+ * automatic edge anchor calculation.
291
301
  */
292
- declare function orthogonalRoute(fromBounds: Rect, toBounds: Rect): Point[];
302
+ declare function orthogonalRoute(fromBounds: Rect, toBounds: Rect, fromAnchor?: AnchorHint, toAnchor?: AnchorHint): Point[];
293
303
  /** Evaluate cubic bezier at parameter `t`. */
294
304
  declare function bezierPointAt(p0: Point, cp1: Point, cp2: Point, p3: Point, t: number): Point;
295
305
  /**