@cardenelabs/cdl 0.47.0 → 0.49.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.
@@ -88,6 +88,7 @@ import {
88
88
  flattenPathSegments,
89
89
  measurePathLabelGapSegs,
90
90
  segmentsCrossRect,
91
+ 札が動けない理由,
91
92
  } from "./layout/collisions";
92
93
  import { measureTextWidth } from "./layout/text-width";
93
94
  import { droppedLineIsHidden, labelPlan } from "./kinds/shape-region";
@@ -276,6 +277,14 @@ export interface Violation {
276
277
  diagramId: string;
277
278
  detail: string;
278
279
  severity: "error" | "warn";
280
+ /**
281
+ * engine がなぜ直せなかったか (#810)。 分かる時だけ付く。
282
+ *
283
+ * `detail` とは別の欄に置く = `detail` は「どの 2 つが何 px か」 を固定の形で持ち、
284
+ * 消費側がそこから id を読み取っている。 理由を `detail` に足すと、 理由の中の id まで
285
+ * 違反の対象として拾われる。
286
+ */
287
+ reason?: string;
279
288
  }
280
289
 
281
290
  export type VisualAxis =
@@ -1447,12 +1456,23 @@ export function visualValidateLaid(
1447
1456
  const counts = emptyCounts();
1448
1457
  const profile = opts?.profile ?? "production";
1449
1458
  const skipped = SKIPPED_AXES[profile];
1450
- const push = (axis: VisualAxis, detail: string, severity: "error" | "warn" = "error") => {
1459
+ const push = (
1460
+ axis: VisualAxis,
1461
+ detail: string,
1462
+ severity: "error" | "warn" = "error",
1463
+ reason?: string,
1464
+ ) => {
1451
1465
  // 用途に合わない軸は数えない。
1452
1466
  if (skipped.has(axis)) return;
1453
1467
  // `diag` は壊れた形でも渡りうる (`#393`)。 報告そのものが例外になると、 壊れている
1454
1468
  // ことを伝える手段が無くなる。
1455
- violations.push({ axis, diagramId: safeDiagramId(diag), detail, severity });
1469
+ violations.push({
1470
+ axis,
1471
+ diagramId: safeDiagramId(diag),
1472
+ detail,
1473
+ severity,
1474
+ ...(reason !== undefined ? { reason } : {}),
1475
+ });
1456
1476
  counts[axis]++;
1457
1477
  };
1458
1478
 
@@ -1479,10 +1499,21 @@ export function visualValidate(diag: CdlDiagram, opts?: ValidateOptions): Visual
1479
1499
  const counts = emptyCounts();
1480
1500
  const profile = opts?.profile ?? "production";
1481
1501
  const skipped = SKIPPED_AXES[profile];
1482
- const push = (axis: VisualAxis, detail: string, severity: "error" | "warn" = "error") => {
1502
+ const push = (
1503
+ axis: VisualAxis,
1504
+ detail: string,
1505
+ severity: "error" | "warn" = "error",
1506
+ reason?: string,
1507
+ ) => {
1483
1508
  // 用途に合わない軸は数えない。 見なかった軸は report の `skippedAxes` に載せる。
1484
1509
  if (skipped.has(axis)) return;
1485
- violations.push({ axis, diagramId: safeDiagramId(diag), detail, severity });
1510
+ violations.push({
1511
+ axis,
1512
+ diagramId: safeDiagramId(diag),
1513
+ detail,
1514
+ severity,
1515
+ ...(reason !== undefined ? { reason } : {}),
1516
+ });
1486
1517
  counts[axis]++;
1487
1518
  };
1488
1519
 
@@ -1852,7 +1883,7 @@ function visualValidateInner(
1852
1883
  diag: CdlDiagram,
1853
1884
  violations: Violation[],
1854
1885
  counts: Record<VisualAxis, number>,
1855
- push: (axis: VisualAxis, detail: string, severity?: "error" | "warn") => void,
1886
+ push: (axis: VisualAxis, detail: string, severity?: "error" | "warn", reason?: string) => void,
1856
1887
  profile: ValidationProfile,
1857
1888
  skippedAxes: VisualAxis[],
1858
1889
  /** 軸 23 が基準にする器。 入口で確かめ済 (`器を確かめる`)。 高さ `undefined` は上限なし。 */
@@ -1909,7 +1940,7 @@ function runAxes(
1909
1940
  diag: CdlDiagram,
1910
1941
  violations: Violation[],
1911
1942
  counts: Record<VisualAxis, number>,
1912
- push: (axis: VisualAxis, detail: string, severity?: "error" | "warn") => void,
1943
+ push: (axis: VisualAxis, detail: string, severity?: "error" | "warn", reason?: string) => void,
1913
1944
  profile: ValidationProfile,
1914
1945
  skippedAxes: VisualAxis[],
1915
1946
  /** 軸 23 が基準にする器。 入口で確かめ済 (`器を確かめる`)。 高さ `undefined` は上限なし。 */
@@ -2046,10 +2077,35 @@ function runAxes(
2046
2077
  // Axis 6: clearance (near collision 0 件、 CLEARANCE_POLICY 違反)
2047
2078
  // ───────────────────────────────────────────────────────────
2048
2079
  const nears = detectNearCollisions(bboxes, laid.edges);
2080
+ /*
2081
+ * 札が絡む違反には、 engine がなぜ動かせなかったかを添える (#810)。
2082
+ *
2083
+ * 動かす側は逃がし pass と同じ = 札どうしなら後に書いた方 (`resolveLabelClearanceAfterPathPasses`
2084
+ * が後の方を動かす)、 札と別の物なら札。 札の絡まない違反 (箱どうし等) には添えない。
2085
+ */
2086
+ const 札の障害物 = bboxes.filter((b) => b.kind === "node" || b.kind === "lane-label");
2087
+ const 辺の順 = new Map(laid.edges.map((e, i) => [e.id, i] as const));
2049
2088
  for (const n of nears) {
2089
+ const aは札 = n.a.kind === "edge-label";
2090
+ const bは札 = n.b.kind === "edge-label";
2091
+ const 動かす =
2092
+ aは札 && bは札
2093
+ ? (辺の順.get(n.a.id) ?? 0) > (辺の順.get(n.b.id) ?? 0)
2094
+ ? n.a
2095
+ : n.b
2096
+ : aは札
2097
+ ? n.a
2098
+ : bは札
2099
+ ? n.b
2100
+ : null;
2101
+ const 相手 = 動かす === n.a ? n.b : n.a;
2102
+ const reason =
2103
+ 動かす !== null ? (札が動けない理由(動かす.id, 相手, laid.edges, 札の障害物) ?? undefined) : undefined;
2050
2104
  push(
2051
2105
  "clearance",
2052
2106
  `${n.a.kind}:${n.a.id} ↔ ${n.b.kind}:${n.b.id} gap=${n.gap.toFixed(1)}px (need ${n.required}px)`,
2107
+ "error",
2108
+ reason,
2053
2109
  );
2054
2110
  }
2055
2111