@cardenelabs/cdl 0.30.2 → 0.30.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.30.2",
3
+ "version": "0.30.3",
4
4
  "description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
5
5
  "license": "MIT",
6
6
  "author": "cardene777",
@@ -3328,8 +3328,116 @@ function 末尾を辺に沿ってずらす(
3328
3328
  }
3329
3329
  // 助走の折れは丸めない。 短い 2 折れなので、 丸めると切り口が区間からはみ出す。
3330
3330
  const 折れ1は直前の折れ = Math.abs(折れ1.x - last.x1) <= 0.5 && Math.abs(折れ1.y - last.y1) <= 0.5;
3331
+ /*
3332
+ * 助走が最後の区間いっぱいに達すると、 1 つ目の折れが直前の折れと重なる (#663)。
3333
+ * その形で 2 つ目の折れを足すと、 直前の折れからずらし量ぶん (実測 28) の直線ができ、
3334
+ * 前後を角丸に挟まれて潰れた角に見える。
3335
+ *
3336
+ * 折れを足す代わりに **直前の折れごと** ずらすと、 手前の区間が伸び縮みするだけで
3337
+ * 短い直線ができない。 手前を辿ってずらす向きと平行な区間を探し、 そこで吸収する。
3338
+ */
3339
+ if (折れ1は直前の折れ) {
3340
+ const 折り返さない = 直前の折れごとずらす(segs, 横, d, nodes, e);
3341
+ if (折り返さない) return 折り返さない;
3342
+ }
3331
3343
  if (!折れ1は直前の折れ) parts.push(`L ${round1(折れ1.x)} ${round1(折れ1.y)}`);
3332
3344
  parts.push(`L ${round1(折れ2.x)} ${round1(折れ2.y)}`);
3333
3345
  parts.push(`L ${round1(終点.x)} ${round1(終点.y)}`);
3334
3346
  return parts.join(" ");
3335
3347
  }
3348
+
3349
+ /**
3350
+ * 折れを足さずに、 最後の区間の手前を丸ごとずらす (#663)。
3351
+ *
3352
+ * ずらす向きと **平行な** 区間まで手前へ辿り、 その区間の終わりだけを動かす。
3353
+ * 平行な区間は伸び縮みするだけなので折れが増えない。 その先 (最後の区間まで) は
3354
+ * 形を保ったまま平行移動する。
3355
+ *
3356
+ * 見つからない / 吸収する区間が短くなりすぎる / 箱に当たる 場合は `null` を返し、
3357
+ * 呼出側の従来の形 (折れを 2 つ足す) に任せる。
3358
+ */
3359
+ function 直前の折れごとずらす(
3360
+ segs: ReturnType<typeof extractPathSegments>,
3361
+ 横: boolean,
3362
+ d: number,
3363
+ nodes: readonly LaidNode[],
3364
+ e: LaidEdge,
3365
+ ): string | null {
3366
+ // 吸収する区間は、 ずらす向きと平行 = 横にずらすなら縦の区間。
3367
+ const 平行か = (s: (typeof segs)[number]): boolean =>
3368
+ 横 ? Math.abs(s.x2 - s.x1) < 0.5 && Math.abs(s.y2 - s.y1) > 0.5
3369
+ : Math.abs(s.y2 - s.y1) < 0.5 && Math.abs(s.x2 - s.x1) > 0.5;
3370
+
3371
+ let 吸収 = -1;
3372
+ for (let i = segs.length - 2; i >= 0; i -= 1) {
3373
+ if (平行か(segs[i]!)) {
3374
+ 吸収 = i;
3375
+ break;
3376
+ }
3377
+ }
3378
+ /*
3379
+ * **平行な区間が 1 つも無い形は検査で作れなかった**。 呼出側は「最後の区間が辺に垂直に
3380
+ * 入る」 かつ「助走が最後の区間いっぱいに達する」 形でしか ここへ来ないため、 その手前には
3381
+ * 必ず向きの変わる区間がある。 型の上では起こりうるので、 落ちる代わりに従来の形へ返す。
3382
+ */
3383
+ if (吸収 < 0) return null;
3384
+
3385
+ // 吸収する区間が伸び縮みした後も、 折れとして読める長さを残す。
3386
+ const 元の長さ = 横
3387
+ ? Math.abs(segs[吸収]!.y2 - segs[吸収]!.y1)
3388
+ : Math.abs(segs[吸収]!.x2 - segs[吸収]!.x1);
3389
+ const 向き = 横
3390
+ ? Math.sign(segs[吸収]!.y2 - segs[吸収]!.y1)
3391
+ : Math.sign(segs[吸収]!.x2 - segs[吸収]!.x1);
3392
+ const 後の長さ = 元の長さ + 向き * d;
3393
+ if (後の長さ < ENDPOINT_FAN_RUNUP) return null;
3394
+
3395
+ const ずらす = <T extends { x: number; y: number }>(p: T): T =>
3396
+ (横 ? { ...p, y: p.y + d } : { ...p, x: p.x + d });
3397
+
3398
+ // 吸収する区間の終わりから後ろを全て平行移動する。
3399
+ const 新segs = segs.map((s, i) => {
3400
+ // 吸収する区間は終わりだけを動かす (下で置き直す)。 それより前は触らない。
3401
+ if (i <= 吸収) return s;
3402
+ return {
3403
+ ...s,
3404
+ x1: ずらす({ x: s.x1, y: s.y1 }).x,
3405
+ y1: ずらす({ x: s.x1, y: s.y1 }).y,
3406
+ x2: ずらす({ x: s.x2, y: s.y2 }).x,
3407
+ y2: ずらす({ x: s.x2, y: s.y2 }).y,
3408
+ ...(s.ctrl ? { ctrl: s.ctrl.map((c) => ずらす(c)) } : {}),
3409
+ };
3410
+ });
3411
+ // 吸収する区間の終わりは、 上の map では x2/y2 を個別に扱えないので置き直す。
3412
+ 新segs[吸収] = {
3413
+ ...segs[吸収]!,
3414
+ x2: 横 ? segs[吸収]!.x2 : segs[吸収]!.x2 + d,
3415
+ y2: 横 ? segs[吸収]!.y2 + d : segs[吸収]!.y2,
3416
+ };
3417
+
3418
+ if (
3419
+ nodes.some(
3420
+ (n) =>
3421
+ n.id !== e.from &&
3422
+ n.id !== e.to &&
3423
+ segmentsCrossRect(
3424
+ 新segs.slice(吸収).map((s) => ({ x1: s.x1, y1: s.y1, x2: s.x2, y2: s.y2 })),
3425
+ nodeRect(n),
3426
+ ),
3427
+ )
3428
+ ) {
3429
+ return null;
3430
+ }
3431
+
3432
+ const head = 新segs[0]!;
3433
+ const parts: string[] = [`M ${round1(head.x1)} ${round1(head.y1)}`];
3434
+ for (const s of 新segs) {
3435
+ if (s.cmd === "Q") {
3436
+ const c = s.ctrl?.[0];
3437
+ parts.push(c ? `Q ${round1(c.x)} ${round1(c.y)}, ${round1(s.x2)} ${round1(s.y2)}` : `L ${round1(s.x2)} ${round1(s.y2)}`);
3438
+ continue;
3439
+ }
3440
+ parts.push(`L ${round1(s.x2)} ${round1(s.y2)}`);
3441
+ }
3442
+ return parts.join(" ");
3443
+ }