@cardenelabs/cdl 0.73.0 → 0.74.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.73.0",
3
+ "version": "0.74.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",
@@ -1,8 +1,16 @@
1
1
  import type { JSX } from "react";
2
2
  import type { LaidNode } from "../types";
3
- import { fitArt, labelPlan, shapeRegion, textWidth, type ArtExtent } from "./shape-region";
3
+ import {
4
+ fitArt,
5
+ labelPlan,
6
+ shapeRegion,
7
+ textWidth,
8
+ 打ち消しの倍率,
9
+ 絵の中の字の幅,
10
+ type ArtExtent,
11
+ type 打ち消す字,
12
+ } from "./shape-region";
4
13
  import { 幅で切る } from "./box-lines";
5
- import { MONO_ADVANCE_RATIO, displayWidth } from "./row-align";
6
14
  import { 字引きを作る, type CdlLang, type CdlTextKey } from "../lang";
7
15
 
8
16
  /**
@@ -36,28 +44,6 @@ const 値の字 = 11;
36
44
  /** 5 行を並べる間隔と、見出しが乗る帯の高さ。 打ち消しの上限をここから出す (#871) */
37
45
  const 行の間隔 = 22;
38
46
  const 見出しの帯 = 32;
39
- /** 打ち消した字が帯のどこまでを占めてよいか。 超えると上下の行や帯の縁に触れる */
40
- const 帯に対する上限 = 0.8;
41
-
42
- /**
43
- * 縮んだ分を字の指定で打ち消す倍率 (#871)。
44
- *
45
- * この絵は 240 x 220 で組んでから、箱に収まる倍率で丸ごと縮む。 字も一緒に縮むので、
46
- * 指定 10.5 が `k = 0.719` の図で 7.55 になり、画面で読める下限 (8px) を割っていた。
47
- *
48
- * **上限は絵の中の帯から出す** = 固定値にすると、絵の寸法を変えた日に効き方だけが変わる。
49
- * 5 行は 22 ずつ空けて並べ、見出しは高さ 32 の帯に乗る。
50
- *
51
- * 1 を下回らない = 縮んでいない図 (`k = 1`) で字を小さくしない。
52
- */
53
- function 打ち消しの倍率(k: number): number {
54
- const 戻し = Number.isFinite(k) && k > 0 ? 1 / k : 1;
55
- const 上限 = Math.min(
56
- (行の間隔 * 帯に対する上限) / 値の字,
57
- (見出しの帯 * 帯に対する上限) / 見出しの字,
58
- );
59
- return Math.max(1, Math.min(戻し, 上限));
60
- }
61
47
  const BLOCK_SHADOW = 6;
62
48
 
63
49
  const NFT_W = 220;
@@ -281,8 +267,7 @@ export function ShapeSmartContractNode({ node, active, lang }: ShapeProps): JSX.
281
267
  * どちらの書体で描かれても実物以上に出る側へ倒す = 下回ると「収まっていない物を収まっていると
282
268
  * 判定」 する向きに外れる (`row-align.ts` の `rowColumnWidth` と同じ考え方)。
283
269
  */
284
- const 字の幅 = (text: string, size: number): number =>
285
- Math.max(displayWidth(text) * size * MONO_ADVANCE_RATIO, textWidth(text, size));
270
+ const 字の幅 = (text: string, size: number): number => 絵の中の字の幅(text, size);
286
271
 
287
272
  /**
288
273
  * 絵の中の 5 行の既定値。 書き手が渡さなかった欄はこれで描く (#469)。
@@ -356,8 +341,28 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
356
341
  * **幅の予算も打ち消した後の大きさで計算する** = 字だけ大きくして切る位置を元の大きさの
357
342
  * まま置くと、切った字が枠から出る。
358
343
  */
344
+ /*
345
+ * 打ち消しの上限を出す帯 (#886)。 **絵の定数から渡す**。
346
+ *
347
+ * 見出しは高さ 32 の帯に乗り、5 行は 22 ずつ空けて並ぶ。 幅は行の内側 (`bw - 28`) で、
348
+ * 値は名前の分を引いた残り = 名前と値が同じ行に並ぶため。
349
+ *
350
+ * 値の幅は **打ち消す前の名前の幅** で引く。 打ち消した後の幅で引くと、上限を出すのに
351
+ * 上限が要る形になる。 名前は打ち消しで広がるので、その分は帯に対する上限 (8 割) が吸う。
352
+ */
353
+ const 打ち消す字ら: 打ち消す字[] = [
354
+ { text: 見出し, size: 見出しの字, 帯: { 幅: 見出しの幅, 高さ: 見出しの帯 } },
355
+ ...fields.flatMap((f): 打ち消す字[] => [
356
+ { text: f.label, size: 名の字, 帯: { 幅: bw - 28, 高さ: 行の間隔 } },
357
+ {
358
+ text: f.value,
359
+ size: 値の字,
360
+ 帯: { 幅: bw - 28 - 字の幅(f.label, 名の字) - 8, 高さ: 行の間隔 },
361
+ },
362
+ ]),
363
+ ];
359
364
  const art = (倍率: number): JSX.Element => {
360
- const 倍 = 打ち消しの倍率(倍率);
365
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
361
366
  const 題の大きさ = 見出しの字 * 倍;
362
367
  const 名の大きさ = 名の字 * 倍;
363
368
  const 値の大きさ = 値の字 * 倍;
@@ -368,17 +373,17 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
368
373
  <rect data-cdl-role="node-body" x={x} y={y} width={bw} height={bh} rx={8} {...frame} />
369
374
  {/* header bar */}
370
375
  <path d={`M ${x} ${y + 32} L ${x} ${y + 8} Q ${x} ${y}, ${x + 8} ${y} L ${x + bw - 8} ${y} Q ${x + bw} ${y}, ${x + bw} ${y + 8} L ${x + bw} ${y + 32} Z`} fill="var(--cdl-tone-accent, #2d6a8f)" fillOpacity={0.9} stroke="none" />
371
- <text data-cdl-role="blockchain-block-title" x={x + 14} y={y + 22} fontSize={題の大きさ} fontWeight={700} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="#ffffff">
376
+ <text data-cdl-role="blockchain-block-title" data-cdl-counter-scaled="1" x={x + 14} y={y + 22} fontSize={題の大きさ} fontWeight={700} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="#ffffff">
372
377
  {幅で切る(見出し, 題の大きさ, 見出しの幅, 字の幅)}
373
378
  </text>
374
379
  <circle cx={x + bw - 16} cy={y + 16} r={5.5} fill="#7cd88f" stroke="#ffffff" strokeWidth={1.5} />
375
380
  {/* fields */}
376
381
  {fields.map((f, i) => (
377
382
  <g key={i}>
378
- <text x={x + 14} y={y + 54 + i * 行の間隔} fontSize={名の大きさ} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="var(--cdl-text-dim, #5a6270)">
383
+ <text data-cdl-counter-scaled="1" x={x + 14} y={y + 54 + i * 行の間隔} fontSize={名の大きさ} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="var(--cdl-text-dim, #5a6270)">
379
384
  {f.label}
380
385
  </text>
381
- <text data-cdl-role={`blockchain-block-${f.key}`} x={x + bw - 14} y={y + 54 + i * 行の間隔} fontSize={値の大きさ} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={600} textAnchor="end" fill="var(--cdl-text, #1a1f2a)">
386
+ <text data-cdl-role={`blockchain-block-${f.key}`} data-cdl-counter-scaled="1" x={x + bw - 14} y={y + 54 + i * 行の間隔} fontSize={値の大きさ} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={600} textAnchor="end" fill="var(--cdl-text, #1a1f2a)">
382
387
  {幅で切る(f.value, 値の大きさ, bw - 28 - 字の幅(f.label, 名の大きさ) - 8, 字の幅)}
383
388
  </text>
384
389
  <line x1={x + 14} y1={y + 60 + i * 行の間隔} x2={x + bw - 14} y2={y + 60 + i * 行の間隔} stroke="var(--cdl-divider, #d4d4d8)" strokeWidth={0.5} opacity={0.5} />
@@ -21,6 +21,8 @@
21
21
  */
22
22
  import type { JSX } from "react";
23
23
  import type { LaidNode, NodeKind } from "../types";
24
+ import { MONO_ADVANCE_RATIO, displayWidth } from "./row-align";
25
+ import { textWidth } from "./text-width";
24
26
 
25
27
  // 既存の呼出 (検査を含む) がここから取っているため、 そのまま出し直す。
26
28
  export { textWidth } from "./text-width";
@@ -508,8 +510,98 @@ export function fitArt(
508
510
  // 書くと変換の形が 2 通りになり、 読む側が両方を扱う必要が出る (実測 = 検査の変換 parser が
509
511
  // `translate(0 1)` を読めず 8 件落ちた)。 `scale(1)` は無害なので形を 1 つに保つ
510
512
  return (
511
- <g transform={`translate(${dx} ${dy}) scale(${s})`}>
513
+ /*
514
+ * 包みに印を付ける (#886)。 **`scale(` を持つ変換は他にもある** = `event` は目立たせる
515
+ * ための脈動を `scale(0.78)` から `scale(1)` で描き、絵を箱に収める縮小ではない。
516
+ * 祖先の変換を正規表現で見る形だと、その脈動まで「絵の縮小」 として数える。
517
+ */
518
+ <g data-cdl-art-fit="1" transform={`translate(${dx} ${dy}) scale(${s})`}>
512
519
  {組む(s)}
513
520
  </g>
514
521
  );
515
522
  }
523
+
524
+ /**
525
+ * 打ち消しを掛ける字 1 つ (#886)。
526
+ *
527
+ * `帯` はその字が座る場所の広さ (絵の座標)。 行なら行の間隔と行の内側の幅、
528
+ * 見出しなら見出しの帯の高さと、見出しが使える幅。
529
+ *
530
+ * **絵が自分の定数から渡す**。 表に書き写すと、絵の寸法を変えた日に片方だけ古くなる
531
+ * (`ArtExtent` と同じ考え方)。
532
+ */
533
+ export type 打ち消す字 = {
534
+ /** 描く字。 幅の上限を出すのに使う */
535
+ text: string;
536
+ /** 打ち消す前の指定 */
537
+ size: number;
538
+ /** 字間。 渡さない呼び方は 0 */
539
+ letterSpacing?: number;
540
+ /** その字が座る場所の広さ */
541
+ 帯: { 幅: number; 高さ: number };
542
+ };
543
+
544
+ /**
545
+ * 打ち消した字が帯の高さのどこまでを占めてよいか。 超えると隣の行や帯の縁に触れる。
546
+ *
547
+ * **高さにだけ掛ける**。 帯の高さは行の間隔 = 次の行までの距離なので、字がそのまま使うと
548
+ * 上下が触れる。 帯の幅は絵が余白を引いた内側の幅を渡すので、そのまま使ってよい
549
+ * (8 割で止めると、字を切る予算より狭い所で打ち消しが止まって 2 割を捨てる)。
550
+ */
551
+ const 帯の高さに対する上限 = 0.8;
552
+
553
+ /**
554
+ * 絵の中の字の幅。 **実物以上に出る側へ倒す**。
555
+ *
556
+ * 等幅の字送りで数えた値と、字形から数えた値の大きい方を採る = 下回ると
557
+ * 「収まっていない物を収まっていると判定」 する向きに外れる (`row-align.ts` の
558
+ * `rowColumnWidth` と同じ考え方)。
559
+ */
560
+ export function 絵の中の字の幅(text: string, size: number, letterSpacing = 0): number {
561
+ return Math.max(
562
+ displayWidth(text) * size * MONO_ADVANCE_RATIO,
563
+ textWidth(text, size, letterSpacing),
564
+ );
565
+ }
566
+
567
+ /**
568
+ * 縮んだ分を字の指定で打ち消す倍率 (#871 で連鎖の箱に入れ、#886 で共通化した)。
569
+ *
570
+ * 入れ子の絵は決まった大きさで組んでから `fitArt` が箱に収まる倍率で丸ごと縮めるため、
571
+ * 字も一緒に縮む (実測 = 指定 10.5 が倍率 0.719 の図で 7.55)。 字の指定にこの倍率を掛けると
572
+ * 画面に出る大きさが指定へ戻る。
573
+ *
574
+ * ## 上限は絵の中の帯から出す
575
+ *
576
+ * 固定値にすると、絵の寸法を変えた日に効き方だけが変わる。
577
+ * **幅と高さの両方から出して小さい方を採る** (#886) = 高さだけで止めると、中央揃えの看板の
578
+ * ように幅が字数に比例して伸びる字が横へ出る。
579
+ *
580
+ * ## 帯に収まっていない字では打ち消さない
581
+ *
582
+ * 上限が 1 を下回る字がある絵では 1 を返す (実測 = 判子の中の `印` は半径 8 の丸に収まる
583
+ * 四角の 1 辺 11.3 を指定 10 の 1 字でほぼ使い切っており、幅から出る上限は 1 を下回る)。
584
+ * 打ち消す側に倒すと、その字が確実に入れ物から出る。
585
+ *
586
+ * ## 測れる字が 1 つも無い時も打ち消さない
587
+ *
588
+ * 帯の値が有限の正の値でない絵は、収まるかの判定ができない。
589
+ * `fitArt` が判定できない入力で手を触れないのと同じ向きに倒す。
590
+ */
591
+ export function 打ち消しの倍率(倍率: number, 字ら: readonly 打ち消す字[]): number {
592
+ const 戻し = Number.isFinite(倍率) && 倍率 > 0 ? 1 / 倍率 : 1;
593
+ let 上限 = Number.POSITIVE_INFINITY;
594
+ for (const 字 of 字ら) {
595
+ if (!(Number.isFinite(字.size) && 字.size > 0)) continue;
596
+ const { 幅, 高さ } = 字.帯;
597
+ if (Number.isFinite(高さ) && 高さ > 0) {
598
+ 上限 = Math.min(上限, (高さ * 帯の高さに対する上限) / 字.size);
599
+ }
600
+ const 実幅 = 絵の中の字の幅(字.text, 字.size, 字.letterSpacing ?? 0);
601
+ if (Number.isFinite(幅) && 幅 > 0 && 実幅 > 0) {
602
+ 上限 = Math.min(上限, 幅 / 実幅);
603
+ }
604
+ }
605
+ if (!Number.isFinite(上限)) return 1;
606
+ return Math.max(1, Math.min(戻し, 上限));
607
+ }