@cardenelabs/cdl 0.73.0 → 0.75.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.75.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;
@@ -70,6 +56,19 @@ const CHAIN_BLOCK_H = 66;
70
56
  const CHAIN_LINK_W = 12;
71
57
  const CHAIN_SHADOW = 3;
72
58
  const CHAIN_BLOCK_SW = 2.5;
59
+ /** 連鎖の箱の頭の帯の高さ。 番号がここに乗る (#887 で打ち消しの帯に使うため定数へ出した) */
60
+ const CHAIN_HEAD_H = 16;
61
+ /** 箱の中で字が使える幅。 左右に 4 ずつ余白を置く */
62
+ const CHAIN_TEXT_W = CHAIN_BLOCK_W - 8;
63
+ /** 箱の中の 2 行 (中央の記号と下の字) の baseline の間隔 */
64
+ const CHAIN_ROW_GAP = 14;
65
+ /** 連鎖の箱の中の字の指定 (#887 で打ち消しの計算に使うため定数へ出した) */
66
+ const CHAIN_NO_SIZE = 10;
67
+ const CONTRACT_CALL_SIZE = 11;
68
+ const NFT_ID_SIZE = 15;
69
+ const CHAIN_HASH_SIZE = 7;
70
+ const BTC_NO_SIZE = 9;
71
+ const ETH_NO_SIZE = 9;
73
72
 
74
73
  const NODE_HEX_R = 44;
75
74
  const NODE_PEER_R = 12;
@@ -237,7 +236,23 @@ export function ShapeSmartContractNode({ node, active, lang }: ShapeProps): JSX.
237
236
  gearPts.push(`${gearCx + Math.cos(a3) * gearTipR},${gearCy + Math.sin(a3) * gearTipR}`);
238
237
  gearPts.push(`${gearCx + Math.cos(a4) * gearBodyR},${gearCy + Math.sin(a4) * gearBodyR}`);
239
238
  }
240
- const art = (
239
+ /*
240
+ * 打ち消しの上限を出す帯 (#887)。 呼び出し行は書類の下寄りに 1 行だけ置くので、
241
+ * 幅は書類の内側 (左右 16 ずつ)、高さは行を並べる間隔 18 を使う。
242
+ *
243
+ * 字引きで日英が切り替わる (`function execute()` 18 字 と 自動で実行する 7 字) ため、
244
+ * 描く字そのものを渡して幅を数える。
245
+ */
246
+ const 打ち消す字ら: 打ち消す字[] = [
247
+ {
248
+ text: 字引("smartContract.call"),
249
+ size: CONTRACT_CALL_SIZE,
250
+ 帯: { 幅: docW - 32, 高さ: 18 },
251
+ },
252
+ ];
253
+ const art = (倍率: number): JSX.Element => {
254
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
255
+ return (
241
256
  <g>
242
257
  <path data-cdl-role="node-body" d={path} {...frame} strokeLinejoin="round" />
243
258
  <path d={foldPath} fill="none" stroke={frame.stroke} strokeWidth={frame.strokeWidth} strokeLinejoin="round" />
@@ -246,7 +261,7 @@ export function ShapeSmartContractNode({ node, active, lang }: ShapeProps): JSX.
246
261
  <rect key={i} x={x + 16} y={ly - 3} width={docW - 32 - i * 22} height={5} rx={1} fill="var(--cdl-divider, #d4d4d8)" fillOpacity={0.7} stroke="none" />
247
262
  ))}
248
263
  {/* function signature */}
249
- <text x={x + 16} y={y + docH - 46} fontSize={11} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="var(--cdl-tone-accent, #2d6a8f)">
264
+ <text data-cdl-counter-scaled="1" x={x + 16} y={y + docH - 46} fontSize={CONTRACT_CALL_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="var(--cdl-tone-accent, #2d6a8f)">
250
265
  {字引("smartContract.call")}
251
266
  </text>
252
267
  {/* gear (auto-execution 記号) */}
@@ -261,7 +276,8 @@ export function ShapeSmartContractNode({ node, active, lang }: ShapeProps): JSX.
261
276
  </polygon>
262
277
  <circle cx={gearCx} cy={gearCy} r={gearBodyR * 0.4} fill="var(--cdl-node-fill, #ffffff)" stroke="none" />
263
278
  </g>
264
- );
279
+ );
280
+ };
265
281
  return (
266
282
  <g>
267
283
  {fitArt(r, smartContractExtent(frame.strokeWidth), art)}
@@ -281,8 +297,7 @@ export function ShapeSmartContractNode({ node, active, lang }: ShapeProps): JSX.
281
297
  * どちらの書体で描かれても実物以上に出る側へ倒す = 下回ると「収まっていない物を収まっていると
282
298
  * 判定」 する向きに外れる (`row-align.ts` の `rowColumnWidth` と同じ考え方)。
283
299
  */
284
- const 字の幅 = (text: string, size: number): number =>
285
- Math.max(displayWidth(text) * size * MONO_ADVANCE_RATIO, textWidth(text, size));
300
+ const 字の幅 = (text: string, size: number): number => 絵の中の字の幅(text, size);
286
301
 
287
302
  /**
288
303
  * 絵の中の 5 行の既定値。 書き手が渡さなかった欄はこれで描く (#469)。
@@ -356,8 +371,28 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
356
371
  * **幅の予算も打ち消した後の大きさで計算する** = 字だけ大きくして切る位置を元の大きさの
357
372
  * まま置くと、切った字が枠から出る。
358
373
  */
374
+ /*
375
+ * 打ち消しの上限を出す帯 (#886)。 **絵の定数から渡す**。
376
+ *
377
+ * 見出しは高さ 32 の帯に乗り、5 行は 22 ずつ空けて並ぶ。 幅は行の内側 (`bw - 28`) で、
378
+ * 値は名前の分を引いた残り = 名前と値が同じ行に並ぶため。
379
+ *
380
+ * 値の幅は **打ち消す前の名前の幅** で引く。 打ち消した後の幅で引くと、上限を出すのに
381
+ * 上限が要る形になる。 名前は打ち消しで広がるので、その分は帯に対する上限 (8 割) が吸う。
382
+ */
383
+ const 打ち消す字ら: 打ち消す字[] = [
384
+ { text: 見出し, size: 見出しの字, 帯: { 幅: 見出しの幅, 高さ: 見出しの帯 } },
385
+ ...fields.flatMap((f): 打ち消す字[] => [
386
+ { text: f.label, size: 名の字, 帯: { 幅: bw - 28, 高さ: 行の間隔 } },
387
+ {
388
+ text: f.value,
389
+ size: 値の字,
390
+ 帯: { 幅: bw - 28 - 字の幅(f.label, 名の字) - 8, 高さ: 行の間隔 },
391
+ },
392
+ ]),
393
+ ];
359
394
  const art = (倍率: number): JSX.Element => {
360
- const 倍 = 打ち消しの倍率(倍率);
395
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
361
396
  const 題の大きさ = 見出しの字 * 倍;
362
397
  const 名の大きさ = 名の字 * 倍;
363
398
  const 値の大きさ = 値の字 * 倍;
@@ -368,17 +403,17 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
368
403
  <rect data-cdl-role="node-body" x={x} y={y} width={bw} height={bh} rx={8} {...frame} />
369
404
  {/* header bar */}
370
405
  <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">
406
+ <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
407
  {幅で切る(見出し, 題の大きさ, 見出しの幅, 字の幅)}
373
408
  </text>
374
409
  <circle cx={x + bw - 16} cy={y + 16} r={5.5} fill="#7cd88f" stroke="#ffffff" strokeWidth={1.5} />
375
410
  {/* fields */}
376
411
  {fields.map((f, i) => (
377
412
  <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)">
413
+ <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
414
  {f.label}
380
415
  </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)">
416
+ <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
417
  {幅で切る(f.value, 値の大きさ, bw - 28 - 字の幅(f.label, 名の大きさ) - 8, 字の幅)}
383
418
  </text>
384
419
  <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} />
@@ -408,7 +443,19 @@ export function ShapeBlockchainNode({ node, active }: ShapeProps): JSX.Element {
408
443
  const linkW = CHAIN_LINK_W;
409
444
  const totalW = blockCount * blockW + (blockCount - 1) * linkW;
410
445
  const startX = cx - totalW / 2;
411
- const art = (
446
+ /*
447
+ * 打ち消しの上限を出す帯 (#887)。 番号は頭の帯 (高さ 16) に乗り、下の字は中央の記号との
448
+ * 間隔 14 に収まる。 幅はどちらも箱の内側。
449
+ *
450
+ * **中央の記号 (`#`) は打ち消さない**。 箱の中央に大きく置いた飾りで、戻すと箱から出る。
451
+ */
452
+ const 打ち消す字ら: 打ち消す字[] = [
453
+ { text: "#420", size: CHAIN_NO_SIZE, 帯: { 幅: CHAIN_TEXT_W, 高さ: CHAIN_HEAD_H } },
454
+ { text: "0x…", size: CHAIN_HASH_SIZE, 帯: { 幅: CHAIN_TEXT_W, 高さ: CHAIN_ROW_GAP } },
455
+ ];
456
+ const art = (倍率: number): JSX.Element => {
457
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
458
+ return (
412
459
  <g>
413
460
  {Array.from({ length: blockCount }, (_, i) => i).map((i) => {
414
461
  const bx = startX + i * (blockW + linkW);
@@ -435,21 +482,22 @@ export function ShapeBlockchainNode({ node, active }: ShapeProps): JSX.Element {
435
482
  stroke={frame.stroke}
436
483
  strokeWidth={isCurrent ? 2.5 : 1.5}
437
484
  />
438
- <rect x={bx} y={cy - blockH / 2} width={blockW} height={16} rx={4} fill={isCurrent ? "#1a4a68" : "var(--cdl-divider, #d4d4d8)"} fillOpacity={isCurrent ? 1 : 0.4} stroke="none" />
439
- <text x={bx + blockW / 2} y={cy - blockH / 2 + 12} fontSize={10} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
485
+ <rect x={bx} y={cy - blockH / 2} width={blockW} height={CHAIN_HEAD_H} rx={4} fill={isCurrent ? "#1a4a68" : "var(--cdl-divider, #d4d4d8)"} fillOpacity={isCurrent ? 1 : 0.4} stroke="none" />
486
+ <text data-cdl-counter-scaled="1" x={bx + blockW / 2} y={cy - blockH / 2 + 12} fontSize={CHAIN_NO_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
440
487
  #{420 + i}
441
488
  </text>
442
- <text x={bx + blockW / 2} y={cy + 8} fontSize={14} fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-tone-accent, #2d6a8f)"} fontFamily="Georgia, serif">
489
+ <text data-cdl-counter-scaled="0" x={bx + blockW / 2} y={cy + 8} fontSize={14} fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-tone-accent, #2d6a8f)"} fontFamily="Georgia, serif">
443
490
  #
444
491
  </text>
445
- <text x={bx + blockW / 2} y={cy + 22} fontSize={7} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"} opacity={0.9}>
492
+ <text data-cdl-counter-scaled="1" x={bx + blockW / 2} y={cy + 22} fontSize={CHAIN_HASH_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"} opacity={0.9}>
446
493
  0x…
447
494
  </text>
448
495
  </g>
449
496
  );
450
497
  })}
451
498
  </g>
452
- );
499
+ );
500
+ };
453
501
  return (
454
502
  <g>
455
503
  {fitArt(r, chainExtent(), art)}
@@ -471,7 +519,17 @@ export function ShapeBitcoinChainNode({ node, active }: ShapeProps): JSX.Element
471
519
  const linkW = CHAIN_LINK_W;
472
520
  const totalW = blockCount * blockW + (blockCount - 1) * linkW;
473
521
  const startX = cx - totalW / 2;
474
- const art = (
522
+ /*
523
+ * 打ち消しの上限を出す帯 (#887)。 番号は箱の下半分に置き、中央の `₿` との間隔 20 に収まる。
524
+ *
525
+ * **`₿` は打ち消さない** (指定 30 の飾りで、箱の高さ 66 に対して既に大きい)。
526
+ */
527
+ const 打ち消す字ら: 打ち消す字[] = [
528
+ { text: "#820", size: BTC_NO_SIZE, 帯: { 幅: CHAIN_TEXT_W, 高さ: 20 } },
529
+ ];
530
+ const art = (倍率: number): JSX.Element => {
531
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
532
+ return (
475
533
  <g>
476
534
  {Array.from({ length: blockCount }, (_, i) => i).map((i) => {
477
535
  const bx = startX + i * (blockW + linkW);
@@ -491,17 +549,18 @@ export function ShapeBitcoinChainNode({ node, active }: ShapeProps): JSX.Element
491
549
  stroke={btcOrange}
492
550
  strokeWidth={isCurrent ? 2.5 : 1.5}
493
551
  />
494
- <text x={bx + blockW / 2} y={cy - 4} fontSize={30} fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : btcOrange} fontFamily="Georgia, serif">
552
+ <text data-cdl-counter-scaled="0" x={bx + blockW / 2} y={cy - 4} fontSize={30} fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : btcOrange} fontFamily="Georgia, serif">
495
553
  ₿
496
554
  </text>
497
- <text x={bx + blockW / 2} y={cy + 16} fontSize={9} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
555
+ <text data-cdl-counter-scaled="1" x={bx + blockW / 2} y={cy + 16} fontSize={BTC_NO_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
498
556
  #{820 + i}
499
557
  </text>
500
558
  </g>
501
559
  );
502
560
  })}
503
561
  </g>
504
- );
562
+ );
563
+ };
505
564
  return (
506
565
  <g>
507
566
  {fitArt(r, chainExtent(), art)}
@@ -523,6 +582,15 @@ export function ShapeEthereumChainNode({ node, active }: ShapeProps): JSX.Elemen
523
582
  const linkW = CHAIN_LINK_W;
524
583
  const totalW = blockCount * blockW + (blockCount - 1) * linkW;
525
584
  const startX = cx - totalW / 2;
585
+ /*
586
+ * **番号は打ち消さない** (#887)。 6 桁の番号は指定 9 でも幅の見積りが 46 になり、
587
+ * 箱の中で字が使える幅 42 (`CHAIN_TEXT_W`) を縮む前から超えている = 打ち消す余地が無い
588
+ * (`textWidth` は等幅の指定を無視して比例の書体で測る。 dragon の `cdl-theme.css` が
589
+ * `--d-body` を `!important` で当てるため、描かれるのは比例の書体になる)。
590
+ *
591
+ * 4 桁の `shape-bitcoin-chain` は同じ帯に収まるので打ち消しに乗っている。
592
+ * 縮む前から帯を超えていること自体は別の課題 (cardene777/cdl#885 と同じ形)。
593
+ */
526
594
  const art = (
527
595
  <g>
528
596
  {Array.from({ length: blockCount }, (_, i) => i).map((i) => {
@@ -548,7 +616,7 @@ export function ShapeEthereumChainNode({ node, active }: ShapeProps): JSX.Elemen
548
616
  />
549
617
  {/* Ethereum Diamond ロゴ */}
550
618
  <EthereumDiamond cx={bx + blockW / 2} cy={cy - 6} h={16} colors={iconColors} />
551
- <text x={bx + blockW / 2} y={cy + 22} fontSize={9} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
619
+ <text data-cdl-counter-scaled="0" x={bx + blockW / 2} y={cy + 22} fontSize={ETH_NO_SIZE} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"}>
552
620
  #{21_400 + i}
553
621
  </text>
554
622
  </g>
@@ -771,7 +839,19 @@ export function ShapeNftNode({ node, active }: ShapeProps): JSX.Element {
771
839
  const artH = cardH * 0.72;
772
840
  const artCx = x + cardW / 2;
773
841
  const artCy = y + artH / 2;
774
- const art = (
842
+ /*
843
+ * 打ち消しの上限を出す帯 (#887)。 番号は下の帯に置く。 高さは帯 (cardH - artH = 67)、
844
+ * 幅は左の余白 16 と右の丸 (半径 10、中心が cardW - 20) を避けた分。
845
+ *
846
+ * **絵の中央の `NFT` (指定 68) は打ち消さない**。 上の帯いっぱいに置いた飾りで、
847
+ * 戻すと帯から出る (実測 = 余り 0.92 で既に使い切っている)。
848
+ */
849
+ const 打ち消す字ら: 打ち消す字[] = [
850
+ { text: "#1024", size: NFT_ID_SIZE, 帯: { 幅: cardW - 16 - 34, 高さ: cardH - artH } },
851
+ ];
852
+ const art = (倍率: number): JSX.Element => {
853
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
854
+ return (
775
855
  <g>
776
856
  {/* shadow */}
777
857
  <rect x={x + 4} y={y + 4} width={cardW} height={cardH} rx={14} fill={frame.stroke} opacity={0.2} stroke="none" />
@@ -786,6 +866,7 @@ export function ShapeNftNode({ node, active }: ShapeProps): JSX.Element {
786
866
  />
787
867
  {/* 巨大 NFT 文字 (art 中央、 accent 色) */}
788
868
  <text
869
+ data-cdl-counter-scaled="0"
789
870
  x={artCx}
790
871
  y={artCy + 24}
791
872
  fontSize={68}
@@ -800,7 +881,7 @@ export function ShapeNftNode({ node, active }: ShapeProps): JSX.Element {
800
881
  {/* 下部 info bar */}
801
882
  <line x1={x} y1={y + artH} x2={x + cardW} y2={y + artH} stroke={frame.stroke} strokeWidth={1} opacity={0.5} />
802
883
  {/* token id (左) */}
803
- <text x={x + 16} y={y + artH + 30} fontSize={15} fontWeight={700} fill="var(--cdl-text, #1a1f2a)" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
884
+ <text data-cdl-counter-scaled="1" x={x + 16} y={y + artH + 30} fontSize={NFT_ID_SIZE * 倍} fontWeight={700} fill="var(--cdl-text, #1a1f2a)" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
804
885
  #1024
805
886
  </text>
806
887
  {/* verified check (右) */}
@@ -809,7 +890,8 @@ export function ShapeNftNode({ node, active }: ShapeProps): JSX.Element {
809
890
  <path d="M -4 0 L -1 3 L 4 -3" fill="none" stroke="#ffffff" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" />
810
891
  </g>
811
892
  </g>
812
- );
893
+ );
894
+ };
813
895
  return (
814
896
  <g>
815
897
  {fitArt(r, nftExtent(frame.strokeWidth), art)}
@@ -1,5 +1,12 @@
1
1
  import type { JSX } from "react";
2
- import { shapeRegion, labelPlan, fitArt, textWidth } from "./shape-region";
2
+ import {
3
+ shapeRegion,
4
+ labelPlan,
5
+ fitArt,
6
+ textWidth,
7
+ 打ち消しの倍率,
8
+ type 打ち消す字,
9
+ } from "./shape-region";
3
10
  import { MONO_ADVANCE_RATIO, displayWidth } from "./row-align";
4
11
  import type { LaidNode } from "../types";
5
12
  import { 字引きを作る, type CdlLang } from "../lang";
@@ -304,7 +311,7 @@ export function ShapeWarehouseNode({ node, active, lang }: ShapeProps): JSX.Elem
304
311
  <rect data-cdl-role="node-body" x={x} y={y + roofH} width={bw} height={bh - roofH - 12} fill="#a89d8f" stroke="#5a6270" strokeWidth={frame.strokeWidth} />
305
312
  {/* WAREHOUSE 看板 (赤帯) */}
306
313
  <rect x={x} y={y + roofH} width={bw} height={signH} fill="#c15a4a" stroke="#8b3f2f" strokeWidth={1.5} />
307
- <text x={cx} y={y + roofH + 17} fontSize={12} fontWeight={900} letterSpacing={6} textAnchor="middle" fill="#ffffff">{字引("warehouse.sign")}</text>
314
+ <text data-cdl-counter-scaled="0" x={cx} y={y + roofH + 17} fontSize={12} fontWeight={900} letterSpacing={6} textAnchor="middle" fill="#ffffff">{字引("warehouse.sign")}</text>
308
315
  {/* 中央大 shutter (rib 縞) */}
309
316
  <rect x={shutterX} y={shutterY} width={shutterW} height={shutterH} fill="#8b949e" stroke={accentDarker} strokeWidth={2} />
310
317
  {/* shutter 上部 shade (立体感) */}
@@ -501,6 +508,12 @@ const API_ROWS = 3;
501
508
  /** 板の大きさ。 高さの下限は行が最小の間隔で 3 本入る高さ。 */
502
509
  const API_H_MIN =
503
510
  API_ROW_TOP + (API_ROWS - 1) * API_ROW_PITCH_MIN + API_ROW_H + API_ROW_BOTTOM_PAD;
511
+ /** 要求の道と天井看板の字の指定 (#887 で打ち消しの計算に使うため定数へ出した) */
512
+ const API_PATH_SIZE = 12;
513
+ const API_SIGN_SIZE = 14;
514
+ /** 種類の箱の幅と、その中で字が使える幅 (左右 3 ずつ空ける) */
515
+ const API_METHOD_BOX_W = 54;
516
+ const API_METHOD_TEXT_W = 48;
504
517
  const API_W_MIN = 200;
505
518
  const API_W_MAX = 300;
506
519
 
@@ -522,31 +535,48 @@ export function ShapeApiGatewayNode({ node, active, lang }: ShapeProps): JSX.Ele
522
535
  // 11 のまま入り、長い綴り (日本語の辞書の `DELETE`) だけを縮める。 見積りは等幅と比例の大きい方
523
536
  // (`shape-blockchain.tsx` の `字の幅` と同じ考え方で、どちらの書体で描かれても箱からはみ出さない)
524
537
  const 種類の字 = (s: string): number =>
525
- Math.min(11, (11 * 48) / Math.max(displayWidth(s) * 11 * MONO_ADVANCE_RATIO, textWidth(s, 11)));
538
+ Math.min(11, (11 * API_METHOD_TEXT_W) / Math.max(displayWidth(s) * 11 * MONO_ADVANCE_RATIO, textWidth(s, 11)));
526
539
  const rowPitch = Math.max(
527
540
  API_ROW_PITCH_MIN,
528
541
  (bh - API_ROW_TOP - API_ROW_H - API_ROW_BOTTOM_PAD) / (API_ROWS - 1),
529
542
  );
530
- const 絵 = (
543
+ const 行ら = [
544
+ { method: "GET", path: 字引("apiGateway.users"), color: "#27c93f" },
545
+ { method: "POST", path: 字引("apiGateway.orders"), color: "#e5c07b" },
546
+ { method: 字引("apiGateway.delete"), path: 字引("apiGateway.session"), color: "#c9302c" },
547
+ ];
548
+ /*
549
+ * 打ち消しの上限を出す帯 (#887)。 要求の道の字が使える幅は、行の箱 (bw - 32) から
550
+ * 種類の箱までの 64 と、右端で点滅する印の丸の 16 を除いた残り。
551
+ *
552
+ * **種類の字 (`GET` / `POST`) は打ち消さない**。 `種類の字` が箱の幅 48 に合わせて指定を
553
+ * 決めているので余白が 0 で、打ち消す余地が無い (長い綴りでは上限がちょうど 1.0 になる)。
554
+ * 打ち消しは絵ごとに 1 つの倍率で、最も狭い字が全体を縛る = これを入れると要求の道の字も
555
+ * 戻せなくなる。
556
+ */
557
+ const 打ち消す字ら: 打ち消す字[] = 行ら.map((e) => ({
558
+ text: e.path,
559
+ size: API_PATH_SIZE,
560
+ 帯: { 幅: bw - 32 - 64 - 16, 高さ: API_ROW_H },
561
+ }));
562
+ const 絵 = (倍率: number): JSX.Element => {
563
+ const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
564
+ return (
531
565
  <g>
532
566
  <rect x={x + DROP_SHADOW_D} y={y + DROP_SHADOW_D} width={bw} height={bh} rx={10} fill={frame.stroke} opacity={0.2} stroke="none" />
533
567
  <rect data-cdl-role="node-body" x={x} y={y} width={bw} height={bh} rx={10} fill="#1a2332" stroke={frame.stroke} strokeWidth={frame.strokeWidth} />
534
568
  <rect x={x} y={y} width={bw} height={36} rx={10} fill="var(--cdl-tone-accent, #2d6a8f)" stroke="none" />
535
- <text x={cx} y={y + 24} fontSize={14} fontWeight={700} letterSpacing={3} textAnchor="middle" fill="#ffffff" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
569
+ <text data-cdl-counter-scaled="0" x={cx} y={y + 24} fontSize={API_SIGN_SIZE} fontWeight={700} letterSpacing={3} textAnchor="middle" fill="#ffffff" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
536
570
  {字引("apiGateway.sign")}
537
571
  </text>
538
- {[
539
- { method: "GET", path: 字引("apiGateway.users"), color: "#27c93f" },
540
- { method: "POST", path: 字引("apiGateway.orders"), color: "#e5c07b" },
541
- { method: 字引("apiGateway.delete"), path: 字引("apiGateway.session"), color: "#c9302c" },
542
- ].map((e, i) => (
572
+ {行ら.map((e, i) => (
543
573
  <g key={i} transform={`translate(${x + 16}, ${y + API_ROW_TOP + i * rowPitch})`}>
544
574
  <rect x={0} y={0} width={bw - 32} height={API_ROW_H} rx={4} fill="#0f1419" stroke="none" />
545
- <rect x={0} y={0} width={54} height={API_ROW_H} rx={4} fill={e.color} stroke="none" />
546
- <text x={27} y={16} fontSize={種類の字(e.method)} fontWeight={700} textAnchor="middle" fill="#ffffff" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
575
+ <rect x={0} y={0} width={API_METHOD_BOX_W} height={API_ROW_H} rx={4} fill={e.color} stroke="none" />
576
+ <text data-cdl-counter-scaled="0" x={27} y={16} fontSize={種類の字(e.method)} fontWeight={700} textAnchor="middle" fill="#ffffff" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">
547
577
  {e.method}
548
578
  </text>
549
- <text x={64} y={16} fontSize={12} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="#e6edf3">
579
+ <text data-cdl-counter-scaled="1" x={64} y={16} fontSize={API_PATH_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="#e6edf3">
550
580
  {e.path}
551
581
  </text>
552
582
  <circle cx={bw - 44} cy={12} r={4} fill="#27c93f" stroke="none">
@@ -555,7 +585,8 @@ export function ShapeApiGatewayNode({ node, active, lang }: ShapeProps): JSX.Ele
555
585
  </g>
556
586
  ))}
557
587
  </g>
558
- );
588
+ );
589
+ };
559
590
  return (
560
591
  <g>
561
592
  {fitArt(r, 広がり, 絵)}