@cardenelabs/cdl 0.76.0 → 0.77.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.76.0",
3
+ "version": "0.77.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",
@@ -5,7 +5,22 @@ import { resolveChartData, type ResolvedChartDatum } from "../render/payload-bin
5
5
  import { 進みを畳む } from "./draw-progress";
6
6
  import { 名札の段をほどく } from "./label-stack";
7
7
  import { 扇形の切り抜き } from "./draw-arc";
8
- import { textWidth } from "./text-width";
8
+ import { textWidth, 縮んだ絵の字の下限 } from "./text-width";
9
+
10
+ /** 字の上下に残す余白の割合。 帯に字を収める時と同じ値を使う */
11
+ const 穴に対する上限 = 0.8;
12
+
13
+ /**
14
+ * 輪の中央に置く字の大きさ。
15
+ *
16
+ * 半径に比例させると小さい図で字が読めなくなる (実測 = 120x110 の箱で 3.00 world)。
17
+ * 下限まで戻し、**穴に入らない時だけ下限を割る** = 入らない大きさで描くと数字が輪へ
18
+ * 重なる (#891)。
19
+ */
20
+ function 中央の字(比例した大きさ: number, 穴の半径: number, 文: string): number {
21
+ const 入る大きさ = (穴の半径 * 2 * 穴に対する上限) / Math.max(textWidth(文, 1), 1e-6);
22
+ return Math.min(Math.max(比例した大きさ, 縮んだ絵の字の下限), 入る大きさ);
23
+ }
9
24
  import { 実値表記, 割合表記 } from "./chart-number";
10
25
 
11
26
  /**
@@ -182,6 +197,15 @@ export function ChartPieNode({
182
197
  const cy = PAD_TOP + chartAreaH / 2;
183
198
 
184
199
  const 前のslices = 前あり ? 輪を組む(data, cx, cy, 前の外半径, 前の内半径, 前の割合) : [];
200
+ /*
201
+ * 中央の 2 行。 字は穴に入る範囲で下限まで戻し、2 行目の位置は **字の大きさから出す**
202
+ * (#891)。 半径に比例させたままだと、下限まで戻した 1 行目と 2 行目が重なる。
203
+ */
204
+ const 穴 = 前あり ? 前の内半径 : 外の内半径;
205
+ const 合計の文 = 実値表記(Math.round(前あり ? 今の合計 : total));
206
+ const 合計の字 = 中央の字(Math.round(radius * 0.26), 穴, 合計の文);
207
+ const 見出しの字 = 中央の字(Math.round(radius * 0.12), 穴, "合計");
208
+ const 合計の下 = Math.max(Math.round(radius * 0.2), 合計の字 * 0.25 + 見出しの字 * 0.8);
185
209
  const slices = 輪を組む(data, cx, cy, radius, 外の内半径, 今の割合);
186
210
 
187
211
  /** 円がどこまで開いたか (0..1、 cdl#516)。 畳み方は `draw-progress.ts` が持つ */
@@ -301,17 +325,17 @@ export function ChartPieNode({
301
325
  x={cx}
302
326
  y={cy - 2}
303
327
  textAnchor="middle"
304
- fontSize={Math.round(radius * 0.26)}
328
+ fontSize={合計の字}
305
329
  fontWeight={600}
306
330
  fill="var(--cdl-text, #1a1f2a)"
307
331
  >
308
- {実値表記(Math.round(前あり ? 今の合計 : total))}
332
+ {合計の文}
309
333
  </text>
310
334
  <text
311
335
  x={cx}
312
- y={cy + Math.round(radius * 0.2)}
336
+ y={cy + 合計の下}
313
337
  textAnchor="middle"
314
- fontSize={Math.round(radius * 0.12)}
338
+ fontSize={見出しの字}
315
339
  fill="var(--cdl-text-dim, #5a6270)"
316
340
  >
317
341
  合計
@@ -593,6 +617,11 @@ function 表の形({ node, active, data, 割合, x0, y0, drawing, progress, inst
593
617
  const 内半径 = R * 0.58;
594
618
  const cx = PAD + R;
595
619
  const cy = node.h / 2;
620
+ // 中央の 2 行。 輪の layout と同じ決め方 (#891)
621
+ const 合計の文 = 実値表記(Math.round(data.reduce((acc, d) => acc + d.value, 0)));
622
+ const 合計の字 = 中央の字(Math.round(R * 0.26), 内半径, 合計の文);
623
+ const 見出しの字 = 中央の字(Math.round(R * 0.12), 内半径, "合計");
624
+ const 合計の下 = Math.max(Math.round(R * 0.2), 合計の字 * 0.25 + 見出しの字 * 0.8);
596
625
 
597
626
  const 表左 = node.w - PAD - 表幅;
598
627
  const 右端 = node.w - PAD;
@@ -643,17 +672,17 @@ function 表の形({ node, active, data, 割合, x0, y0, drawing, progress, inst
643
672
  x={cx}
644
673
  y={cy - 2}
645
674
  textAnchor="middle"
646
- fontSize={Math.round(R * 0.26)}
675
+ fontSize={合計の字}
647
676
  fontWeight={600}
648
677
  fill="var(--cdl-text, #1a1f2a)"
649
678
  >
650
- {実値表記(Math.round(data.reduce((acc, d) => acc + d.value, 0)))}
679
+ {合計の文}
651
680
  </text>
652
681
  <text
653
682
  x={cx}
654
- y={cy + Math.round(R * 0.2)}
683
+ y={cy + 合計の下}
655
684
  textAnchor="middle"
656
- fontSize={Math.round(R * 0.12)}
685
+ fontSize={見出しの字}
657
686
  fill="var(--cdl-text-dim, #5a6270)"
658
687
  >
659
688
  合計
@@ -1,6 +1,7 @@
1
1
  import type { JSX } from "react";
2
2
  import type { LaidNode } from "../types";
3
3
  import { 字引きを作る, type CdlLang } from "../lang";
4
+ import { 打ち消しの倍率, type 打ち消す字 } from "./shape-region";
4
5
 
5
6
  /**
6
7
  * Shape-driven software 6 kinds (CAR-1111 Phase 2-B)。
@@ -217,6 +218,8 @@ export function ShapeTerminalNode({ node, active, lang }: ShapeProps): JSX.Eleme
217
218
  const lineFont = "ui-monospace, SFMono-Regular, Menlo, monospace";
218
219
  const 余白 = 16;
219
220
  const 帯 = 28;
221
+ // 帯の名札の指定。 中の行と揃える = 1 つだけ小さいと縮んだ箱で真っ先に読めなくなる (#891)
222
+ const 帯の字 = 12.5;
220
223
  const 角 = 10;
221
224
  const 先頭 = 26;
222
225
  const 行間 = 22;
@@ -226,7 +229,7 @@ export function ShapeTerminalNode({ node, active, lang }: ShapeProps): JSX.Eleme
226
229
  { 左: 16, 字: 12.5, 色: "#7cd88f", 文: "$" },
227
230
  { 左: 30, 字: 12.5, 色: "#e6edf3", 文: "pnpm build" },
228
231
  ],
229
- [{ 左: 16, 字: 12, 色: "#8b95a5", 文: 字引("terminal.output") }],
232
+ [{ 左: 16, 字: 12.5, 色: "#8b95a5", 文: 字引("terminal.output") }],
230
233
  [{ 左: 16, 字: 12.5, 色: "#7cd88f", 文: "$" }],
231
234
  ];
232
235
  // 中身は自分の座標で描くため、 本体に入る倍率を出せるよう最長行から自然幅を求める。
@@ -266,7 +269,7 @@ export function ShapeTerminalNode({ node, active, lang }: ShapeProps): JSX.Eleme
266
269
  <text
267
270
  x={x + w / 2}
268
271
  y={y + barH / 2 + 4 * 倍}
269
- fontSize={11 * 倍}
272
+ fontSize={帯の字 * 倍}
270
273
  fontFamily={lineFont}
271
274
  textAnchor="middle"
272
275
  fill="#7a8290"
@@ -355,6 +358,8 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
355
358
  const 太さ = 9;
356
359
  const 隙間 = 6;
357
360
  const 持ち上げ = 8;
361
+ // 行番号の指定。 溝 (幅 24) に 9.46、行の間隔 (20) に 10.7 で収まる (#891 で 10.5 から)
362
+ const 行番号の字 = 11;
358
363
  const lines = [
359
364
  [
360
365
  { c: "#c678dd", w: 40 },
@@ -377,20 +382,30 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
377
382
  { c: "#e6edf3", w: 40 },
378
383
  ],
379
384
  ];
380
- const 最長 = Math.max(
381
- ...lines.map(
382
- (tokens) => tokens.reduce((sum, token) => sum + token.w, 0) + 隙間 * (tokens.length - 1),
383
- ),
384
- );
385
- // 中身は自分の座標で描くため、 本体に入る倍率を出せるよう最長行から自然幅を求める。
386
- const 自然幅 = 溝 + 字下げ + 最長 + 余白;
387
385
  // 最終行の token と行番号まで入る自然高を使い、 縦にも同じ倍率を適用できるようにする。
388
386
  const 自然高 =
389
- タブ高 + 行頭 + 行間 * (lines.length - 1) + Math.max(太さ - 持ち上げ, 10.5 * DESCENT);
390
- // 右 132 のはみ出しは、 中の行を本体へ縦横同率で収めて直す。
391
- // fitArt で絵ごと縮めると tab の字と行番号が読めなくなるため使わない。
392
- // 幅 300・高さ 200 では倍率が 1 のままなので、 従来値を保つ。
393
- const 倍 = Math.min(1, w / 自然幅, bodyH / 自然高);
387
+ タブ高 + 行頭 + 行間 * (lines.length - 1) + Math.max(太さ - 持ち上げ, 行番号の字 * DESCENT);
388
+ /*
389
+ * 倍率は **高さだけから出す** (#891)。
390
+ *
391
+ * 行の帯は code の見た目を作る飾りで、 いちばん長い行は 228 ある。 幅も倍率に入れると
392
+ * 飾りの長さが字の大きさを決めてしまい、 120x110 の箱で行番号が 4.33 world まで落ちた。
393
+ * 狭い箱では帯を縮めず **本体の右端で切る** = 実際の editor も長い行を切って見せる。
394
+ *
395
+ * 幅 300・高さ 200 では倍率が 1 のままなので、 従来値を保つ。
396
+ */
397
+ const 倍 = Math.min(1, bodyH / 自然高);
398
+ /*
399
+ * 縮んだ分を字の指定で打ち消す (#891、 仕組みは #886 と同じ)。
400
+ *
401
+ * 帯は tab の札 (幅 `タブ幅` / 高さ `タブ高`) と、行番号が乗る溝 (幅 `溝 - 持ち上げ` /
402
+ * 高さ `行間`)。 絵に 1 つの倍率なので、狭い方が全体の上限になる。
403
+ */
404
+ const 打ち消す字ら: 打ち消す字[] = [
405
+ { text: 字引("codeBlock.fileName"), size: 11, 帯: { 幅: タブ幅, 高さ: タブ高 } },
406
+ { text: "1", size: 行番号の字, 帯: { 幅: 溝 - 持ち上げ, 高さ: 行間 } },
407
+ ];
408
+ const 倍返し = 打ち消しの倍率(倍, 打ち消す字ら);
394
409
  const tabW = タブ幅 * 倍;
395
410
  const tabH = タブ高 * 倍;
396
411
  const 丸 = 角 * 倍;
@@ -421,9 +436,10 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
421
436
  />
422
437
  <path d={`M ${x} ${y + tabH} L ${x + w} ${y + tabH}`} stroke="#2a3140" strokeWidth={1} />
423
438
  <text
439
+ data-cdl-counter-scaled="1"
424
440
  x={x + tabW / 2}
425
441
  y={y + tabH / 2 + 4 * 倍}
426
- fontSize={11 * 倍}
442
+ fontSize={11 * 倍 * 倍返し}
427
443
  fontFamily={lineFont}
428
444
  textAnchor="middle"
429
445
  fill="#e6edf3"
@@ -441,9 +457,10 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
441
457
  return (
442
458
  <g key={i}>
443
459
  <text
460
+ data-cdl-counter-scaled="1"
444
461
  x={x + gutterW - 持ち上げ * 倍}
445
462
  y={codeY0 + i * lineH}
446
- fontSize={10.5 * 倍}
463
+ fontSize={行番号の字 * 倍 * 倍返し}
447
464
  fontFamily={lineFont}
448
465
  textAnchor="end"
449
466
  fill="#4a5266"
@@ -451,19 +468,22 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
451
468
  {i + 1}
452
469
  </text>
453
470
  {toks.map((t, j) => {
454
- const rect = (
455
- <rect
456
- key={j}
457
- x={cursor}
458
- y={codeY0 + i * lineH - 持ち上げ * 倍}
459
- width={t.w * 倍}
460
- height={太さ * 倍}
461
- rx={2 * 倍}
462
- fill={t.c}
463
- fillOpacity={0.85}
464
- stroke="none"
465
- />
466
- );
471
+ // 本体の右端で切る。 端から出る帯は幅を詰め、端の外から始まる帯は描かない
472
+ const 描く幅 = Math.min(t.w * 倍, x + w - 余白 * 倍 - cursor);
473
+ const rect =
474
+ 描く幅 > 0 ? (
475
+ <rect
476
+ key={j}
477
+ x={cursor}
478
+ y={codeY0 + i * lineH - 持ち上げ * 倍}
479
+ width={描く幅}
480
+ height={太さ * 倍}
481
+ rx={2 * 倍}
482
+ fill={t.c}
483
+ fillOpacity={0.85}
484
+ stroke="none"
485
+ />
486
+ ) : null;
467
487
  cursor += (t.w + 隙間) * 倍;
468
488
  return rect;
469
489
  })}
@@ -512,22 +532,38 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
512
532
  const w = Math.max(0, node.w - 線 * 2 - 影);
513
533
  const h = Math.max(0, node.h - 線 * 2 - 影);
514
534
  const 帯間余白 = 4;
515
- const 左右余白 = 14;
516
- const 上段隙間 = 8;
535
+ /*
536
+ * 上段 (`ID` と状態の札) の寸法は **字の下限から逆算する** (#891)。
537
+ *
538
+ * 札の中身は縦横まとめて `倍` で縮むので、字の大きさは `指定 x 倍` になる。
539
+ * 120x110 の箱では描ける幅が 112.8 しか無く、指定 11 の字を 8 world に保つには
540
+ * 上段の自然幅を 155 以下に収める必要がある (112.8 / (8 / 11) = 155.1)。
541
+ *
542
+ * 変更前は 166.8 で、字が 7.44 world まで落ちていた。
543
+ */
544
+ const 左右余白 = 12;
545
+ const 上段隙間 = 6;
517
546
  const 優先帯高 = 10;
518
547
  const ID文 = 字引("kanbanCard.id");
519
548
  const ID基準 = 30;
520
549
  const ID字 = 11;
521
550
  const ID字間 = 1;
522
551
  const PILL上 = 20;
523
- // 札の幅は状態の字から決める = 指定 11 の `IN PROG` は字間 1 で幅 62.4、丸い端に触れない余白を足す (#888)
524
- const PILL幅 = 70;
552
+ // 札の幅は状態の字から決める = 指定 11 の `IN PROG` は幅 55.4、丸い端に触れない余白を足す (#888)
553
+ const PILL幅 = 62;
525
554
  const PILL高 = 16;
526
555
  const PILL字 = 11;
556
+ /*
557
+ * 状態の字に字間を置かない (#891)。 字間は幅を字数に比例して伸ばすため、札を 8 広げないと
558
+ * 収まらない。 その 8 は上段の自然幅に乗り、120x110 の箱で全部の字を 0.4 world 下げる。
559
+ */
560
+ const PILL字間 = 0;
527
561
  const FOOTER下 = 22;
528
562
  const AVATAR中心Y = 4;
529
563
  const AVATAR半径 = 9;
530
564
  const AVATAR枠 = 1.5;
565
+ // 頭文字の指定。 半径 9 の丸の内側 (一辺 12.7) に 9.46 x 10.7 で収まる (#891 で 10 から)
566
+ const AVATAR字 = 11;
531
567
  const 名前字 = 17;
532
568
  const 説明基準比 = 20 / 17;
533
569
  const 説明字比 = 12 / 17;
@@ -609,7 +645,7 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
609
645
  y={pillY + 12 * 倍}
610
646
  fontSize={PILL字 * 倍}
611
647
  fontWeight={700}
612
- letterSpacing={1 * 倍}
648
+ letterSpacing={PILL字間 * 倍}
613
649
  textAnchor="middle"
614
650
  fill="#5a4a1a"
615
651
  fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
@@ -651,7 +687,7 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
651
687
  <text
652
688
  x={9 * 倍}
653
689
  y={8 * 倍}
654
- fontSize={10 * 倍}
690
+ fontSize={AVATAR字 * 倍}
655
691
  fontWeight={700}
656
692
  textAnchor="middle"
657
693
  fill="#ffffff"
@@ -670,7 +706,7 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
670
706
  <text
671
707
  x={23 * 倍}
672
708
  y={8 * 倍}
673
- fontSize={10 * 倍}
709
+ fontSize={AVATAR字 * 倍}
674
710
  fontWeight={700}
675
711
  textAnchor="middle"
676
712
  fill="#ffffff"
@@ -71,3 +71,15 @@ export function textWidth(s: string, size: number, 字送り = 0): number {
71
71
  }
72
72
  return 幅 * size + 字送り * 字数;
73
73
  }
74
+
75
+ /**
76
+ * 縮んだ絵の中の字の下限 (world 座標、 #891)。
77
+ *
78
+ * 箱に合わせて中身を縮める種別は、倍率をそのまま字へ掛けると読めない大きさまで落ちる
79
+ * (実測 = 120x110 の箱で 3.00 world)。 **入れ物に入る限りはここまでで止める**。
80
+ *
81
+ * 絵が縮まない箱の下限 (11) とは別の値で、そちらは検査側
82
+ * (`test/art-text-declarations.ts` の `絵の中の字の下限`) が持つ。 縮んだ箱では帯が
83
+ * 上限を決めるため 11 に届かないことがあり、2 つを同じ値にはできない。
84
+ */
85
+ export const 縮んだ絵の字の下限 = 8;
package/src/lang.ts CHANGED
@@ -33,7 +33,14 @@ export function resolveLang(value: unknown): CdlLang {
33
33
  */
34
34
  const en = {
35
35
  /** `shape-terminal` の 2 行目 (命令の出力) */
36
- "terminal.output": "→ 12 chunks in 1.8s",
36
+ /*
37
+ * `shape-terminal` の出力の行。
38
+ *
39
+ * **いちばん長い行が絵の倍率を決める** (#891)。 端末の中身は自分の座標で組んでから箱へ
40
+ * 収めるので、この行が長いほど全部の字が小さくなる。 120x110 の箱では 20 字の版で
41
+ * 7.71 world まで落ちていた。
42
+ */
43
+ "terminal.output": "→ 12 chunks",
37
44
  /** `shape-code-block` の札の file 名 */
38
45
  "codeBlock.fileName": "utils.ts",
39
46
  /** `shape-kanban-card` の課題の番号 */
@@ -272,7 +279,7 @@ export type CdlTextKey = keyof typeof en;
272
279
  * 日本語の文でもこの綴りで書くため。
273
280
  */
274
281
  const ja: Record<CdlTextKey, string> = {
275
- "terminal.output": "→ 1.8 秒で完了",
282
+ "terminal.output": "→ 12 個を結合",
276
283
  "codeBlock.fileName": "共通.ts",
277
284
  "kanbanCard.id": "#1111",
278
285
  "kanbanCard.status": "進行中",