@spectratools/graphic-designer-cli 0.6.0 → 0.7.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/qa.js CHANGED
@@ -595,6 +595,15 @@ var drawGradientRectSchema = z2.object({
595
595
  radius: z2.number().min(0).max(256).default(0),
596
596
  opacity: z2.number().min(0).max(1).default(1)
597
597
  }).strict();
598
+ var drawGridSchema = z2.object({
599
+ type: z2.literal("grid"),
600
+ spacing: z2.number().min(5).max(200).default(40),
601
+ color: colorHexSchema2.default("#1E2D4A"),
602
+ width: z2.number().min(0.1).max(4).default(0.5),
603
+ opacity: z2.number().min(0).max(1).default(0.2),
604
+ offsetX: z2.number().default(0),
605
+ offsetY: z2.number().default(0)
606
+ }).strict();
598
607
  var drawCommandSchema = z2.discriminatedUnion("type", [
599
608
  drawRectSchema,
600
609
  drawCircleSchema,
@@ -603,7 +612,8 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
603
612
  drawBezierSchema,
604
613
  drawPathSchema,
605
614
  drawBadgeSchema,
606
- drawGradientRectSchema
615
+ drawGradientRectSchema,
616
+ drawGridSchema
607
617
  ]);
608
618
  var defaultCanvas = {
609
619
  width: 1200,
@@ -709,6 +719,13 @@ var flowNodeElementSchema = z2.object({
709
719
  badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
710
720
  shadow: flowNodeShadowSchema.optional()
711
721
  }).strict();
722
+ var anchorHintSchema = z2.union([
723
+ z2.enum(["top", "bottom", "left", "right", "center"]),
724
+ z2.object({
725
+ x: z2.number().min(-1).max(1),
726
+ y: z2.number().min(-1).max(1)
727
+ }).strict()
728
+ ]);
712
729
  var connectionElementSchema = z2.object({
713
730
  type: z2.literal("connection"),
714
731
  from: z2.string().min(1).max(120),
@@ -722,9 +739,12 @@ var connectionElementSchema = z2.object({
722
739
  width: z2.number().min(0.5).max(10).optional(),
723
740
  strokeWidth: z2.number().min(0.5).max(10).default(2),
724
741
  arrowSize: z2.number().min(4).max(32).optional(),
742
+ arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
725
743
  opacity: z2.number().min(0).max(1).default(1),
726
744
  routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
727
- tension: z2.number().min(0.1).max(0.8).default(0.35)
745
+ tension: z2.number().min(0.1).max(0.8).default(0.35),
746
+ fromAnchor: anchorHintSchema.optional(),
747
+ toAnchor: anchorHintSchema.optional()
728
748
  }).strict();
729
749
  var codeBlockStyleSchema = z2.object({
730
750
  paddingVertical: z2.number().min(0).max(128).default(56),
@@ -1,3 +1,3 @@
1
- export { h as DEFAULT_GENERATOR_VERSION, N as LayoutSnapshot, a as Rect, R as RenderMetadata, Q as RenderResult, d as RenderedElement, Z as WrittenArtifacts, a0 as computeSpecHash, aj as inferSidecarPath, am as renderDesign, ao as writeRenderArtifacts } from './spec.schema-Dm_wOLTd.js';
1
+ export { i as DEFAULT_GENERATOR_VERSION, O as LayoutSnapshot, a as Rect, R as RenderMetadata, S as RenderResult, d as RenderedElement, _ as WrittenArtifacts, a1 as computeSpecHash, ak as inferSidecarPath, an as renderDesign, ap as writeRenderArtifacts } from './spec.schema-BeFz_nk1.js';
2
2
  import 'zod';
3
3
  import '@napi-rs/canvas';
package/dist/renderer.js CHANGED
@@ -2009,21 +2009,61 @@ function edgeAnchor(bounds, target) {
2009
2009
  const t = absDx * hh > absDy * hw ? hw / absDx : hh / absDy;
2010
2010
  return { x: c.x + dx * t, y: c.y + dy * t };
2011
2011
  }
2012
+ function resolveAnchor(bounds, anchor, fallbackTarget) {
2013
+ if (!anchor) return edgeAnchor(bounds, fallbackTarget);
2014
+ if (typeof anchor === "string") {
2015
+ const c2 = rectCenter(bounds);
2016
+ switch (anchor) {
2017
+ case "top":
2018
+ return { x: c2.x, y: bounds.y };
2019
+ case "bottom":
2020
+ return { x: c2.x, y: bounds.y + bounds.height };
2021
+ case "left":
2022
+ return { x: bounds.x, y: c2.y };
2023
+ case "right":
2024
+ return { x: bounds.x + bounds.width, y: c2.y };
2025
+ case "center":
2026
+ return c2;
2027
+ }
2028
+ }
2029
+ const c = rectCenter(bounds);
2030
+ return {
2031
+ x: c.x + anchor.x * (bounds.width / 2),
2032
+ y: c.y + anchor.y * (bounds.height / 2)
2033
+ };
2034
+ }
2035
+ function anchorNormal(anchor, point, diagramCenter) {
2036
+ if (typeof anchor === "string") {
2037
+ switch (anchor) {
2038
+ case "top":
2039
+ return { x: 0, y: -1 };
2040
+ case "bottom":
2041
+ return { x: 0, y: 1 };
2042
+ case "left":
2043
+ return { x: -1, y: 0 };
2044
+ case "right":
2045
+ return { x: 1, y: 0 };
2046
+ case "center":
2047
+ return outwardNormal(point, diagramCenter);
2048
+ }
2049
+ }
2050
+ return outwardNormal(point, diagramCenter);
2051
+ }
2012
2052
  function outwardNormal(point, diagramCenter) {
2013
2053
  const dx = point.x - diagramCenter.x;
2014
2054
  const dy = point.y - diagramCenter.y;
2015
2055
  const len = Math.hypot(dx, dy) || 1;
2016
2056
  return { x: dx / len, y: dy / len };
2017
2057
  }
2018
- function curveRoute(fromBounds, toBounds, diagramCenter, tension) {
2058
+ function curveRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAnchor) {
2019
2059
  const fromCenter = rectCenter(fromBounds);
2020
2060
  const toCenter = rectCenter(toBounds);
2021
- const p0 = edgeAnchor(fromBounds, toCenter);
2022
- const p3 = edgeAnchor(toBounds, fromCenter);
2061
+ const p0 = resolveAnchor(fromBounds, fromAnchor, toCenter);
2062
+ const p3 = resolveAnchor(toBounds, toAnchor, fromCenter);
2023
2063
  const dist = Math.hypot(p3.x - p0.x, p3.y - p0.y);
2024
2064
  const offset = dist * tension;
2025
- const n0 = outwardNormal(p0, diagramCenter);
2026
- const n3 = outwardNormal(p3, diagramCenter);
2065
+ const n0 = anchorNormal(fromAnchor, p0, diagramCenter);
2066
+ const n3 = anchorNormal(toAnchor, p3, diagramCenter);
2027
2067
  const cp1 = { x: p0.x + n0.x * offset, y: p0.y + n0.y * offset };
2028
2068
  const cp2 = { x: p3.x + n3.x * offset, y: p3.y + n3.y * offset };
2029
2069
  return [p0, cp1, cp2, p3];
@@ -2037,11 +2077,11 @@ function localToWorld(origin, axisX, axisY, local) {
2037
2077
  y: origin.y + axisX.y * local.x + axisY.y * local.y
2038
2078
  };
2039
2079
  }
2040
- function arcRoute(fromBounds, toBounds, diagramCenter, tension) {
2080
+ function arcRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAnchor) {
2041
2081
  const fromCenter = rectCenter(fromBounds);
2042
2082
  const toCenter = rectCenter(toBounds);
2043
- const start = edgeAnchor(fromBounds, toCenter);
2044
- const end = edgeAnchor(toBounds, fromCenter);
2083
+ const start = resolveAnchor(fromBounds, fromAnchor, toCenter);
2084
+ const end = resolveAnchor(toBounds, toAnchor, fromCenter);
2045
2085
  const chord = { x: end.x - start.x, y: end.y - start.y };
2046
2086
  const chordLength = Math.hypot(chord.x, chord.y);
2047
2087
  if (chordLength < 1e-6) {
@@ -2079,11 +2119,11 @@ function arcRoute(fromBounds, toBounds, diagramCenter, tension) {
2079
2119
  [pMid, cp3, cp4, p3]
2080
2120
  ];
2081
2121
  }
2082
- function orthogonalRoute(fromBounds, toBounds) {
2122
+ function orthogonalRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
2083
2123
  const fromC = rectCenter(fromBounds);
2084
2124
  const toC = rectCenter(toBounds);
2085
- const p0 = edgeAnchor(fromBounds, toC);
2086
- const p3 = edgeAnchor(toBounds, fromC);
2125
+ const p0 = resolveAnchor(fromBounds, fromAnchor, toC);
2126
+ const p3 = resolveAnchor(toBounds, toAnchor, fromC);
2087
2127
  const midX = (p0.x + p3.x) / 2;
2088
2128
  return [p0, { x: midX, y: p0.y }, { x: midX, y: p3.y }, p3];
2089
2129
  }
@@ -2094,6 +2134,35 @@ function bezierPointAt(p0, cp1, cp2, p3, t) {
2094
2134
  y: mt * mt * mt * p0.y + 3 * mt * mt * t * cp1.y + 3 * mt * t * t * cp2.y + t * t * t * p3.y
2095
2135
  };
2096
2136
  }
2137
+ function bezierTangentAt(p0, cp1, cp2, p3, t) {
2138
+ const mt = 1 - t;
2139
+ return {
2140
+ x: 3 * mt * mt * (cp1.x - p0.x) + 6 * mt * t * (cp2.x - cp1.x) + 3 * t * t * (p3.x - cp2.x),
2141
+ y: 3 * mt * mt * (cp1.y - p0.y) + 6 * mt * t * (cp2.y - cp1.y) + 3 * t * t * (p3.y - cp2.y)
2142
+ };
2143
+ }
2144
+ function isInsideRect(point, rect) {
2145
+ return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
2146
+ }
2147
+ function findBoundaryIntersection(p0, cp1, cp2, p3, targetRect, searchFromEnd) {
2148
+ const step = 5e-3;
2149
+ if (searchFromEnd) {
2150
+ for (let t = 0.95; t >= 0.5; t -= step) {
2151
+ const pt = bezierPointAt(p0, cp1, cp2, p3, t);
2152
+ if (!isInsideRect(pt, targetRect)) {
2153
+ return t;
2154
+ }
2155
+ }
2156
+ } else {
2157
+ for (let t = 0.05; t <= 0.5; t += step) {
2158
+ const pt = bezierPointAt(p0, cp1, cp2, p3, t);
2159
+ if (!isInsideRect(pt, targetRect)) {
2160
+ return t;
2161
+ }
2162
+ }
2163
+ }
2164
+ return void 0;
2165
+ }
2097
2166
  function pointAlongArc(route, t) {
2098
2167
  const [first, second] = route;
2099
2168
  if (t <= 0.5) {
@@ -2221,8 +2290,16 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
2221
2290
  let labelPoint;
2222
2291
  ctx.save();
2223
2292
  ctx.globalAlpha = conn.opacity;
2293
+ const arrowPlacement = conn.arrowPlacement ?? "endpoint";
2224
2294
  if (routing === "curve") {
2225
- const [p0, cp1, cp2, p3] = curveRoute(fromBounds, toBounds, diagramCenter, tension);
2295
+ const [p0, cp1, cp2, p3] = curveRoute(
2296
+ fromBounds,
2297
+ toBounds,
2298
+ diagramCenter,
2299
+ tension,
2300
+ conn.fromAnchor,
2301
+ conn.toAnchor
2302
+ );
2226
2303
  ctx.strokeStyle = style.color;
2227
2304
  ctx.lineWidth = style.width;
2228
2305
  ctx.setLineDash(style.dash ?? []);
@@ -2236,8 +2313,33 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
2236
2313
  startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
2237
2314
  endAngle = Math.atan2(p3.y - cp2.y, p3.x - cp2.x);
2238
2315
  labelPoint = bezierPointAt(p0, cp1, cp2, p3, labelT);
2316
+ if (arrowPlacement === "boundary") {
2317
+ if (conn.arrow === "end" || conn.arrow === "both") {
2318
+ const tEnd = findBoundaryIntersection(p0, cp1, cp2, p3, toBounds, true);
2319
+ if (tEnd !== void 0) {
2320
+ endPoint = bezierPointAt(p0, cp1, cp2, p3, tEnd);
2321
+ const tangent = bezierTangentAt(p0, cp1, cp2, p3, tEnd);
2322
+ endAngle = Math.atan2(tangent.y, tangent.x);
2323
+ }
2324
+ }
2325
+ if (conn.arrow === "start" || conn.arrow === "both") {
2326
+ const tStart = findBoundaryIntersection(p0, cp1, cp2, p3, fromBounds, false);
2327
+ if (tStart !== void 0) {
2328
+ startPoint = bezierPointAt(p0, cp1, cp2, p3, tStart);
2329
+ const tangent = bezierTangentAt(p0, cp1, cp2, p3, tStart);
2330
+ startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
2331
+ }
2332
+ }
2333
+ }
2239
2334
  } else if (routing === "arc") {
2240
- const [first, second] = arcRoute(fromBounds, toBounds, diagramCenter, tension);
2335
+ const [first, second] = arcRoute(
2336
+ fromBounds,
2337
+ toBounds,
2338
+ diagramCenter,
2339
+ tension,
2340
+ conn.fromAnchor,
2341
+ conn.toAnchor
2342
+ );
2241
2343
  const [p0, cp1, cp2, pMid] = first;
2242
2344
  const [, cp3, cp4, p3] = second;
2243
2345
  ctx.strokeStyle = style.color;
@@ -2254,9 +2356,29 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
2254
2356
  startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
2255
2357
  endAngle = Math.atan2(p3.y - cp4.y, p3.x - cp4.x);
2256
2358
  labelPoint = pointAlongArc([first, second], labelT);
2359
+ if (arrowPlacement === "boundary") {
2360
+ if (conn.arrow === "end" || conn.arrow === "both") {
2361
+ const [, s_cp3, s_cp4, s_p3] = second;
2362
+ const tEnd = findBoundaryIntersection(pMid, s_cp3, s_cp4, s_p3, toBounds, true);
2363
+ if (tEnd !== void 0) {
2364
+ endPoint = bezierPointAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
2365
+ const tangent = bezierTangentAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
2366
+ endAngle = Math.atan2(tangent.y, tangent.x);
2367
+ }
2368
+ }
2369
+ if (conn.arrow === "start" || conn.arrow === "both") {
2370
+ const tStart = findBoundaryIntersection(p0, cp1, cp2, pMid, fromBounds, false);
2371
+ if (tStart !== void 0) {
2372
+ startPoint = bezierPointAt(p0, cp1, cp2, pMid, tStart);
2373
+ const tangent = bezierTangentAt(p0, cp1, cp2, pMid, tStart);
2374
+ startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
2375
+ }
2376
+ }
2377
+ }
2257
2378
  } else {
2258
- const useElkRoute = routing === "auto" && (edgeRoute?.points.length ?? 0) >= 2;
2259
- linePoints = useElkRoute ? edgeRoute?.points ?? orthogonalRoute(fromBounds, toBounds) : orthogonalRoute(fromBounds, toBounds);
2379
+ const hasAnchorHints = conn.fromAnchor !== void 0 || conn.toAnchor !== void 0;
2380
+ const useElkRoute = routing === "auto" && !hasAnchorHints && (edgeRoute?.points.length ?? 0) >= 2;
2381
+ linePoints = useElkRoute ? edgeRoute?.points ?? orthogonalRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor) : orthogonalRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor);
2260
2382
  startPoint = linePoints[0];
2261
2383
  const startSegment = linePoints[1] ?? linePoints[0];
2262
2384
  const endStart = linePoints[linePoints.length - 2] ?? linePoints[0];
@@ -2913,6 +3035,36 @@ function renderDrawCommands(ctx, commands, theme) {
2913
3035
  });
2914
3036
  break;
2915
3037
  }
3038
+ case "grid": {
3039
+ const canvasWidth = ctx.canvas.width;
3040
+ const canvasHeight = ctx.canvas.height;
3041
+ withOpacity(ctx, command.opacity, () => {
3042
+ ctx.strokeStyle = command.color;
3043
+ ctx.lineWidth = command.width;
3044
+ const startX = command.offsetX % command.spacing;
3045
+ for (let x = startX; x <= canvasWidth; x += command.spacing) {
3046
+ ctx.beginPath();
3047
+ ctx.moveTo(x, 0);
3048
+ ctx.lineTo(x, canvasHeight);
3049
+ ctx.stroke();
3050
+ }
3051
+ const startY = command.offsetY % command.spacing;
3052
+ for (let y = startY; y <= canvasHeight; y += command.spacing) {
3053
+ ctx.beginPath();
3054
+ ctx.moveTo(0, y);
3055
+ ctx.lineTo(canvasWidth, y);
3056
+ ctx.stroke();
3057
+ }
3058
+ });
3059
+ rendered.push({
3060
+ id,
3061
+ kind: "draw",
3062
+ bounds: { x: 0, y: 0, width: canvasWidth, height: canvasHeight },
3063
+ foregroundColor: command.color,
3064
+ allowOverlap: true
3065
+ });
3066
+ break;
3067
+ }
2916
3068
  }
2917
3069
  }
2918
3070
  return rendered;
@@ -3294,6 +3446,15 @@ var drawGradientRectSchema = z2.object({
3294
3446
  radius: z2.number().min(0).max(256).default(0),
3295
3447
  opacity: z2.number().min(0).max(1).default(1)
3296
3448
  }).strict();
3449
+ var drawGridSchema = z2.object({
3450
+ type: z2.literal("grid"),
3451
+ spacing: z2.number().min(5).max(200).default(40),
3452
+ color: colorHexSchema2.default("#1E2D4A"),
3453
+ width: z2.number().min(0.1).max(4).default(0.5),
3454
+ opacity: z2.number().min(0).max(1).default(0.2),
3455
+ offsetX: z2.number().default(0),
3456
+ offsetY: z2.number().default(0)
3457
+ }).strict();
3297
3458
  var drawCommandSchema = z2.discriminatedUnion("type", [
3298
3459
  drawRectSchema,
3299
3460
  drawCircleSchema,
@@ -3302,7 +3463,8 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
3302
3463
  drawBezierSchema,
3303
3464
  drawPathSchema,
3304
3465
  drawBadgeSchema,
3305
- drawGradientRectSchema
3466
+ drawGradientRectSchema,
3467
+ drawGridSchema
3306
3468
  ]);
3307
3469
  var defaultCanvas = {
3308
3470
  width: 1200,
@@ -3408,6 +3570,13 @@ var flowNodeElementSchema = z2.object({
3408
3570
  badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
3409
3571
  shadow: flowNodeShadowSchema.optional()
3410
3572
  }).strict();
3573
+ var anchorHintSchema = z2.union([
3574
+ z2.enum(["top", "bottom", "left", "right", "center"]),
3575
+ z2.object({
3576
+ x: z2.number().min(-1).max(1),
3577
+ y: z2.number().min(-1).max(1)
3578
+ }).strict()
3579
+ ]);
3411
3580
  var connectionElementSchema = z2.object({
3412
3581
  type: z2.literal("connection"),
3413
3582
  from: z2.string().min(1).max(120),
@@ -3421,9 +3590,12 @@ var connectionElementSchema = z2.object({
3421
3590
  width: z2.number().min(0.5).max(10).optional(),
3422
3591
  strokeWidth: z2.number().min(0.5).max(10).default(2),
3423
3592
  arrowSize: z2.number().min(4).max(32).optional(),
3593
+ arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
3424
3594
  opacity: z2.number().min(0).max(1).default(1),
3425
3595
  routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
3426
- tension: z2.number().min(0.1).max(0.8).default(0.35)
3596
+ tension: z2.number().min(0.1).max(0.8).default(0.35),
3597
+ fromAnchor: anchorHintSchema.optional(),
3598
+ toAnchor: anchorHintSchema.optional()
3427
3599
  }).strict();
3428
3600
  var codeBlockStyleSchema = z2.object({
3429
3601
  paddingVertical: z2.number().min(0).max(128).default(56),