@cardenelabs/cdl 0.103.0 → 0.105.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.103.0",
3
+ "version": "0.105.0",
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",
@@ -156,6 +156,122 @@ export function fitsOwnTitleWidth(kind: string | undefined): boolean {
156
156
  return KINDS_FITTING_OWN_TITLE.has(kind ?? "");
157
157
  }
158
158
 
159
+ /**
160
+ * 題を箱の **外** に描く種別 (#946)。
161
+ *
162
+ * 形だけを描く `dyn-*` 5 種は箱の上に名前を置き (`kinds/dyn-shape.tsx`)、図表を自前で描く
163
+ * 15 種も題を箱の上に置く (`drawsFigureTitle`)。 どちらも箱の内側の余白に縛られないので、
164
+ * 幅の見積りに内側の余白を足すと、収まっている題まで超過として挙がる。
165
+ *
166
+ * 一覧は手で持つと描画側とずれるため、`test/title-width-estimate.test.tsx` が `NODE_KINDS` を
167
+ * 全件描き、題の役割名 (箱の中 = `node-label` / 箱の外 = `shape-name` / `figure-title`) と
168
+ * 本述語の返り値の一致を検査する。
169
+ *
170
+ * **集合を書き写さず取り込む** = 図表の種別を足した人が片方だけ直すと、見積りがずれる。
171
+ */
172
+ const KINDS_DRAWING_TITLE_OUTSIDE: ReadonlySet<string> = new Set([
173
+ "dyn-rect",
174
+ "dyn-circle",
175
+ "dyn-arc",
176
+ "dyn-wave",
177
+ "dyn-polygon",
178
+ ...KINDS_DRAWING_OWN_PAYLOAD,
179
+ ]);
180
+
181
+ /** その node が `title` を箱の外に描くか (SSOT) */
182
+ export function drawsTitleOutsideBox(kind: string | undefined): boolean {
183
+ return KINDS_DRAWING_TITLE_OUTSIDE.has(kind ?? "");
184
+ }
185
+
186
+ /**
187
+ * 種別ごとの、題を描く字の大きさ (world)。
188
+ *
189
+ * `text-readability` (軸 3) は「字数 × 1 字の幅 + 余白」 で切れるかを見積もる。
190
+ * 1 字の幅を 22 の定数で持っていたが、**22 で描く種別は 1 つも無い** (#946 で全件実測)。
191
+ * 見積りが実物の 1.2 倍から 1.7 倍になり、収まっている題まで挙がっていた。
192
+ *
193
+ * ここに置くのは **箱が十分に大きい時の大きさ** になる。 `shape-*` の多くは箱の高さに
194
+ * 比例して字を縮めるため (`kinds/shape-region.tsx` の `labelPlan`)、狭い箱では実物のほうが
195
+ * 小さくなる。 見積りが実物を下回らない側に倒してあるので、切れる図を見逃す向きには外れない。
196
+ *
197
+ * 値は小数を切り上げた整数で持つ。 `event` は 26 を 0.92 倍して描くため実効 23.92 で、
198
+ * 24 として持つ。 実測との差は検査が 1 未満に抑える。
199
+ */
200
+ const TITLE_FONT_SIZE_DEFAULT = 18;
201
+ const TITLE_FONT_SIZE: ReadonlyMap<string, number> = new Map<string, number>([
202
+ ...(["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon"] as const).map(
203
+ (k) => [k, 13] as const,
204
+ ),
205
+ ...(
206
+ [
207
+ "shape-code-block",
208
+ "shape-cylinder",
209
+ "shape-diamond",
210
+ "shape-iot-sensor",
211
+ "shape-robot-arm",
212
+ "shape-stack",
213
+ "shape-terminal",
214
+ ] as const
215
+ ).map((k) => [k, 15] as const),
216
+ ...(
217
+ [
218
+ "shape-cloud",
219
+ "shape-file",
220
+ "shape-folder",
221
+ "shape-hexagon",
222
+ "shape-message-bubble",
223
+ "shape-window",
224
+ ] as const
225
+ ).map((k) => [k, 16] as const),
226
+ ["shape-kanban-card", 17],
227
+ ...(
228
+ [
229
+ "admin",
230
+ "api",
231
+ "backend",
232
+ "cache",
233
+ "cdn",
234
+ "cloud",
235
+ "database",
236
+ "decision",
237
+ "developer",
238
+ "external-user",
239
+ "frontend",
240
+ "merkle-tree",
241
+ "message-bus",
242
+ "microservice",
243
+ "oracle",
244
+ "person",
245
+ "queue",
246
+ "service",
247
+ "signer",
248
+ "user-group",
249
+ "webhook",
250
+ ] as const
251
+ ).map((k) => [k, 20] as const),
252
+ ...(["card", "event", "function"] as const).map((k) => [k, 24] as const),
253
+ ["actor", 32],
254
+ ["storage", 34],
255
+ ]);
256
+
257
+ /**
258
+ * その種別が題を描く字の大きさ (SSOT)。
259
+ *
260
+ * 表に無い種別は `shape-*` の多数派と同じ 18 を返す。 種別が増えた時に黙って外れないよう、
261
+ * `test/title-width-estimate.test.tsx` が `NODE_KINDS` を全件描いて実測と突き合わせる。
262
+ */
263
+ export function titleFontSize(kind: string | undefined): number {
264
+ return TITLE_FONT_SIZE.get(kind ?? "") ?? TITLE_FONT_SIZE_DEFAULT;
265
+ }
266
+
267
+ /**
268
+ * 題を箱の中に描く種別で、題が使えない左右の余白 (world)。
269
+ *
270
+ * `text-readability` (軸 3) が字数から出した幅に足す。 箱の外に題を描く種別には足さない
271
+ * (`drawsTitleOutsideBox`)。 値は #946 より前から同じで、本 Issue では動かしていない。
272
+ */
273
+ export const TITLE_BOX_PADDING = 52;
274
+
159
275
  /**
160
276
  * `rows` を文字として描く node の種別。
161
277
  *
@@ -143,6 +143,9 @@ import {
143
143
  TITLE_ROW_MIN_GAP,
144
144
  rendersTitleText,
145
145
  fitsOwnTitleWidth,
146
+ drawsTitleOutsideBox,
147
+ titleFontSize,
148
+ TITLE_BOX_PADDING,
146
149
  rowBaselineY,
147
150
  rowGlyphDepth,
148
151
  laneVerticalRange,
@@ -2000,10 +2003,14 @@ function runAxes(
2000
2003
  if (!isRenderedNode(n)) continue;
2001
2004
  const titleTrimmed = n.title?.trim() ?? "";
2002
2005
  if (!titleTrimmed) continue;
2003
- // 日本語 1 文字 ~22px (kinds/storage.tsx の fontSize 22 を基準) 想定
2004
- // title 長 * 22 + padding 52 > node.w なら切れる
2006
+ // 1 字の幅は **その種別が題を描く大きさ** から出す (#946)。 22 の定数で持っていたが、
2007
+ // 22 で描く種別は 1 つも無く、見積りが実物の 1.2 倍から 1.7 倍になっていた。
2008
+ //
2009
+ // 箱の内側の余白 (52) は、題を箱の中に描く種別にだけ足す。 箱の外に描く種別 (`dyn-*` の
2010
+ // 名前と図表の題) は内側の余白に縛られないので、足すと収まっている題まで挙がる。
2005
2011
  const titleLen = titleTrimmed.length;
2006
- const expectedW = titleLen * 22 + 52;
2012
+ const expectedW =
2013
+ titleLen * titleFontSize(n.kind) + (drawsTitleOutsideBox(n.kind) ? 0 : TITLE_BOX_PADDING);
2007
2014
  if (expectedW > n.w + 8) {
2008
2015
  push(
2009
2016
  "text-readability",
@@ -2363,9 +2370,15 @@ function runAxes(
2363
2370
  // 判定 = viewBox 外に node bbox が飛ぶと clip されて視覚破綻。 node bbox (cx±w/2, cy±h/2) の
2364
2371
  // いずれかの辺が viewBox 外なら error。 Axis 14 は edge-label 対象、 本 axis は node 対象で棲み分け。
2365
2372
  // spacer node (id endsWith "-spacer" or /^s\d+-/) は layout 補助用の非表示 node なので判定対象外。
2373
+ //
2374
+ // **描かない node も対象外** (#945)。 板は `isRenderedNode` で描く node だけに寄せる
2375
+ // (`layout/viewbox.ts`) ので、隠れる node を判定に入れると板の外に在ると数える。
2376
+ // 0.93.0 (#924) で板が中身に寄るようになってから、`visibleIf: "0"` の置き石だけを持つ図
2377
+ // (読み取り盤の見本 17 枚) が全て誤りとして挙がっていた。 判定は板と同じ述語を引く。
2366
2378
  // ───────────────────────────────────────────────────────────
2367
2379
  for (const n of laid.nodes) {
2368
2380
  if (n.id.endsWith("-spacer") || /^s\d+-/.test(n.id)) continue;
2381
+ if (!isRenderedNode(n)) continue;
2369
2382
  const nodeLeft = n.cx - n.w / 2;
2370
2383
  const nodeTop = n.cy - n.h / 2;
2371
2384
  const nodeRight = n.cx + n.w / 2;
@@ -3287,10 +3300,15 @@ function runAxes(
3287
3300
  */
3288
3301
  const RESPONSIVE_MIN_ON_SCREEN_FONT_PX = 12;
3289
3302
  /**
3290
- * 判定の基準にする文字の world 寸法。 箱の題の大きさ (`kinds/storage.tsx` の fontSize 22)。
3303
+ * 判定の基準にする文字の world 寸法。
3291
3304
  *
3292
3305
  * 図の中には これより小さい文字 (辺の名札等) もあるが、 基準を最小の文字に取ると
3293
- * ほぼ全ての図が境を割る。 最も多く出る文字で見る。
3306
+ * ほぼ全ての図が境を割る。 よく出る大きさで見る。
3307
+ *
3308
+ * **22 で題を描く種別は 1 つも無い** (#946 で `NODE_KINDS` を全件実測)。 元は
3309
+ * `kinds/storage.tsx` の値と書いてあったが、 実物の storage は 34 だった。
3310
+ * ただし本軸は図の中の文字全体を見る目安で、 題の幅を見る軸 3 とは別の判定になる。
3311
+ * 値を動かすと全図の判定が動くため、 #946 では基準の出どころだけを直して値は据え置く。
3294
3312
  */
3295
3313
  const RESPONSIVE_BASE_FONT_WORLD = 22;
3296
3314
  const vbResp = laid.viewBox;