@cardenelabs/cdl 0.105.0 → 0.107.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/CHANGELOG.md +67 -1
- package/dist/index.cjs +71 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +71 -8
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +71 -8
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +71 -8
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/layout/spec.ts +81 -0
- package/src/layout/text-width.ts +52 -4
- package/src/visual-validate.ts +31 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.107.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",
|
package/src/layout/spec.ts
CHANGED
|
@@ -272,6 +272,87 @@ export function titleFontSize(kind: string | undefined): number {
|
|
|
272
272
|
*/
|
|
273
273
|
export const TITLE_BOX_PADDING = 52;
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* 低い箱の時だけ題を箱に収まるまで小さくする種別。
|
|
277
|
+
*
|
|
278
|
+
* この 4 種は `kinds/compact-title.ts` の `titlePlacement` を通す。 低い箱 (`h` が
|
|
279
|
+
* `COMPACT_TITLE_MAX_H` 未満) では `fitTextSize` が字を 1 ずつ落として収めるが、
|
|
280
|
+
* それ以外の高さでは種別ごとの大きさをそのまま描くため、長い題は箱を超える。
|
|
281
|
+
*/
|
|
282
|
+
const KINDS_SCALING_TITLE_WHEN_LOW: ReadonlySet<string> = new Set(["actor", "event", "function", "storage"]);
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 高さに関わらず題を箱に収まるまで小さくする種別。
|
|
286
|
+
*
|
|
287
|
+
* `card` は `fitTextSize` を直に呼び、残る 34 種は絵の中の字として箱の幅に収まる倍率を掛ける。
|
|
288
|
+
* 一覧は `test/title-fit-kinds.test.tsx` が `NODE_KINDS` を全件描いて突き合わせる。
|
|
289
|
+
*/
|
|
290
|
+
const KINDS_SCALING_TITLE_ALWAYS: ReadonlySet<string> = new Set([
|
|
291
|
+
"card",
|
|
292
|
+
"shape-file",
|
|
293
|
+
"shape-folder",
|
|
294
|
+
"shape-cloud",
|
|
295
|
+
"shape-cylinder",
|
|
296
|
+
"shape-hexagon",
|
|
297
|
+
"shape-diamond",
|
|
298
|
+
"shape-stack",
|
|
299
|
+
"shape-person",
|
|
300
|
+
"shape-server-rack",
|
|
301
|
+
"shape-network-node",
|
|
302
|
+
"shape-mobile-device",
|
|
303
|
+
"shape-satellite",
|
|
304
|
+
"shape-smart-contract",
|
|
305
|
+
"shape-rpc-node",
|
|
306
|
+
"shape-wallet",
|
|
307
|
+
"shape-nft",
|
|
308
|
+
"shape-token",
|
|
309
|
+
"shape-blockchain",
|
|
310
|
+
"shape-bitcoin-chain",
|
|
311
|
+
"shape-ethereum-chain",
|
|
312
|
+
"shape-blockchain-node",
|
|
313
|
+
"shape-bank",
|
|
314
|
+
"shape-trust-bank",
|
|
315
|
+
"shape-payment-provider",
|
|
316
|
+
"shape-credit-card",
|
|
317
|
+
"shape-brokerage",
|
|
318
|
+
"shape-exchange",
|
|
319
|
+
"shape-atm",
|
|
320
|
+
"shape-website",
|
|
321
|
+
"shape-storefront",
|
|
322
|
+
"shape-warehouse",
|
|
323
|
+
"shape-online-shop",
|
|
324
|
+
"shape-cdn-edge",
|
|
325
|
+
"shape-api-gateway",
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* これより低い箱を「低い箱」 とみなす。 `kinds/compact-title.ts` の `COMPACT_MAX_H` と同じ値。
|
|
330
|
+
*
|
|
331
|
+
* 写しているのは向きが逆だから = `compact-title.ts` は描く側の module で、`layout/` から
|
|
332
|
+
* 引くと組み方が描画に依存する。 2 つがずれないことは `test/title-fit-kinds.test.tsx` が見る。
|
|
333
|
+
*/
|
|
334
|
+
export const COMPACT_TITLE_MAX_H = 100;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* その箱が題を収まるまで小さくするか (SSOT)。
|
|
338
|
+
*
|
|
339
|
+
* 小さくする種別は題がはみ出さない代わりに、字が読めない大きさまで落ちうる。
|
|
340
|
+
* 軸 3 はこの 2 群で見るものを変える = 小さくしない種別は「箱の幅を超えるか」、
|
|
341
|
+
* 小さくする種別は「読める下限 (`MIN_TITLE_FONT_SIZE`) を割るか」 を見る。
|
|
342
|
+
*/
|
|
343
|
+
export function titleScalesToFit(kind: string | undefined, h: number): boolean {
|
|
344
|
+
const k = kind ?? "";
|
|
345
|
+
if (KINDS_SCALING_TITLE_ALWAYS.has(k)) return true;
|
|
346
|
+
return KINDS_SCALING_TITLE_WHEN_LOW.has(k) && h < COMPACT_TITLE_MAX_H;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 題を小さくする時の下限 (world)。 `kinds/compact-title.ts` の `MIN_TEXT_SIZE` と同じ値。
|
|
351
|
+
*
|
|
352
|
+
* 写している理由は `COMPACT_TITLE_MAX_H` と同じ。
|
|
353
|
+
*/
|
|
354
|
+
export const MIN_TITLE_FONT_SIZE = 11;
|
|
355
|
+
|
|
275
356
|
/**
|
|
276
357
|
* `rows` を文字として描く node の種別。
|
|
277
358
|
*
|
package/src/layout/text-width.ts
CHANGED
|
@@ -21,11 +21,24 @@
|
|
|
21
21
|
|
|
22
22
|
type FontFamily = "sans" | "mono";
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 見積りをどちらへ外すか。
|
|
26
|
+
*
|
|
27
|
+
* `layout` (既定) は **広い側** へ外す。 箱の大きさと線の置き場を決める側は、狭く見積もると
|
|
28
|
+
* 字が枠から出るため、外すなら広い側でなければならない。 表の値はこの向きに寄せてある。
|
|
29
|
+
*
|
|
30
|
+
* `drawn` は **実描画に合わせる**。 「はみ出すか」 を判定する側は、広く見積もると収まっている
|
|
31
|
+
* 字を挙げてしまう。 見本帳の題 407 か所を `getComputedTextLength()` で測り、和文は 1.08 倍 /
|
|
32
|
+
* 欧文は 1.16 倍の上振れだと判った (`DRAWN_*` の説明)。 その分を割り戻す。
|
|
33
|
+
*/
|
|
34
|
+
type WidthBound = "layout" | "drawn";
|
|
35
|
+
|
|
24
36
|
interface MeasureOptions {
|
|
25
37
|
fontSize?: number;
|
|
26
38
|
fontWeight?: number;
|
|
27
39
|
letterSpacing?: number;
|
|
28
40
|
fontFamily?: FontFamily;
|
|
41
|
+
bound?: WidthBound;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
/**
|
|
@@ -91,6 +104,28 @@ const EMOJI_ADVANCE_EM = 1.05;
|
|
|
91
104
|
/** mono text から fallback する絵文字の advance (em)。 fontSize 19 の実測 22.0 px。 */
|
|
92
105
|
const MONO_EMOJI_ADVANCE_EM = 1.16;
|
|
93
106
|
|
|
107
|
+
/**
|
|
108
|
+
* 和文の上振れ幅。 `bound: "drawn"` で割り戻す。
|
|
109
|
+
*
|
|
110
|
+
* 実測 = 見本帳の題のうち和文だけの 247 件が **例外なく** 見積りの 1/1.08 で描かれていた
|
|
111
|
+
* (最小 1.080 / 中央 1.080)。 和文の字は全角 1.0 em で、表の 1.08 はその上振れぶんになる。
|
|
112
|
+
*/
|
|
113
|
+
const DRAWN_CJK_RATIO = 1.08;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 欧文の上振れ幅。 `bound: "drawn"` で割り戻す。
|
|
117
|
+
*
|
|
118
|
+
* 実測 = 欧文だけの 119 件で見積りが実描画の 1.16 から 1.82 倍 (中央 1.27 倍) だった。
|
|
119
|
+
* **最小の 1.16 を採る** = 割り戻した値が実描画を下回ると、はみ出す題を見落とす。
|
|
120
|
+
* 407 件すべてで下回らないことを `title-drawn-width.test.ts` が突き合わせる。
|
|
121
|
+
*/
|
|
122
|
+
const DRAWN_ASCII_RATIO = 1.16;
|
|
123
|
+
|
|
124
|
+
/** ASCII か。 `bound: "drawn"` で割り戻す係数を選ぶためだけに使う。 */
|
|
125
|
+
function isAscii(code: number): boolean {
|
|
126
|
+
return code < 0x80;
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
/**
|
|
95
130
|
* 絵文字ではない非 ASCII の記号 1 つ分の advance (em)。 実ブラウザで測った値 (`#481`)。
|
|
96
131
|
*
|
|
@@ -189,6 +224,7 @@ export function measureTextWidth(text: string, opts: MeasureOptions = {}): numbe
|
|
|
189
224
|
const fontSize = opts.fontSize ?? 22;
|
|
190
225
|
const letterSpacing = opts.letterSpacing ?? 0;
|
|
191
226
|
const fontFamily = opts.fontFamily ?? "sans";
|
|
227
|
+
const bound = opts.bound ?? "layout";
|
|
192
228
|
let advanceEmSum = 0;
|
|
193
229
|
let charCount = 0;
|
|
194
230
|
// 1 つの絵に畳まれる並びがある時だけ書記素で数える (`CLUSTER_JOINER_RE` の説明)
|
|
@@ -196,13 +232,23 @@ export function measureTextWidth(text: string, opts: MeasureOptions = {}): numbe
|
|
|
196
232
|
for (const ch of 文字列) {
|
|
197
233
|
charCount++;
|
|
198
234
|
const code = ch.codePointAt(0) ?? 0;
|
|
235
|
+
// 実描画に合わせる時は、字種ごとの上振れぶんを割り戻す。 絵文字と記号は実測が無いので
|
|
236
|
+
// 割り戻さない = 測っていない字を狭く見積もると、はみ出す題を見落とす
|
|
237
|
+
const 割り戻し =
|
|
238
|
+
bound === "drawn" && !isEmojiGrapheme(ch, code)
|
|
239
|
+
? isAscii(code)
|
|
240
|
+
? DRAWN_ASCII_RATIO
|
|
241
|
+
: isCJK(code)
|
|
242
|
+
? DRAWN_CJK_RATIO
|
|
243
|
+
: 1
|
|
244
|
+
: 1;
|
|
199
245
|
if (isEmojiGrapheme(ch, code)) {
|
|
200
246
|
// mono を指定しても絵文字は JetBrains Mono ではなく emoji font へ fallback する。
|
|
201
247
|
advanceEmSum += fontFamily === "mono" ? MONO_EMOJI_ADVANCE_EM : EMOJI_ADVANCE_EM;
|
|
202
248
|
continue;
|
|
203
249
|
}
|
|
204
250
|
if (fontFamily === "mono") {
|
|
205
|
-
advanceEmSum += isCJK(code) ? 1.08 : JETBRAINS_MONO_ADVANCE_EM
|
|
251
|
+
advanceEmSum += (isCJK(code) ? 1.08 : JETBRAINS_MONO_ADVANCE_EM) / 割り戻し;
|
|
206
252
|
continue;
|
|
207
253
|
}
|
|
208
254
|
if (isCJK(code)) {
|
|
@@ -210,14 +256,16 @@ export function measureTextWidth(text: string, opts: MeasureOptions = {}): numbe
|
|
|
210
256
|
// Inter fontSize 22 baseline に対し 1.08 em 前後で描画される (実測係数化)。
|
|
211
257
|
// 1.08 が cookbook / presets / text-dsl の合計 G5 warn 最小 (10 件)、
|
|
212
258
|
// 1.15 以上に上げると flow-demo が過大化して +2-6 件増える tradeoff。
|
|
213
|
-
advanceEmSum += 1.08
|
|
259
|
+
advanceEmSum += 1.08 / 割り戻し;
|
|
214
260
|
continue;
|
|
215
261
|
}
|
|
216
262
|
const table = ch.length === 1 ? INTER_BOLD_ADVANCE_EM[ch] : undefined;
|
|
217
|
-
advanceEmSum += table !== undefined ? table : defaultAdvanceEm(ch, code)
|
|
263
|
+
advanceEmSum += (table !== undefined ? table : defaultAdvanceEm(ch, code)) / 割り戻し;
|
|
218
264
|
}
|
|
219
265
|
const w = advanceEmSum * fontSize + letterSpacing * charCount;
|
|
220
|
-
|
|
266
|
+
// 最低幅は組み方のための下駄 (短い札で bbox が細くなり判定が揺れるのを防ぐ)。
|
|
267
|
+
// 実描画に合わせる時は当てない = 1 字の題が 1.2 em で描かれることは無い
|
|
268
|
+
return bound === "drawn" ? w : Math.max(w, fontSize * 1.2);
|
|
221
269
|
}
|
|
222
270
|
|
|
223
271
|
/**
|
package/src/visual-validate.ts
CHANGED
|
@@ -144,6 +144,8 @@ import {
|
|
|
144
144
|
rendersTitleText,
|
|
145
145
|
fitsOwnTitleWidth,
|
|
146
146
|
drawsTitleOutsideBox,
|
|
147
|
+
titleScalesToFit,
|
|
148
|
+
MIN_TITLE_FONT_SIZE,
|
|
147
149
|
titleFontSize,
|
|
148
150
|
TITLE_BOX_PADDING,
|
|
149
151
|
rowBaselineY,
|
|
@@ -2003,18 +2005,41 @@ function runAxes(
|
|
|
2003
2005
|
if (!isRenderedNode(n)) continue;
|
|
2004
2006
|
const titleTrimmed = n.title?.trim() ?? "";
|
|
2005
2007
|
if (!titleTrimmed) continue;
|
|
2006
|
-
//
|
|
2008
|
+
// 幅は **字ごとの実幅** で出す (#949)。 字数に 1 字の幅を掛ける近似は、英数字の題で
|
|
2009
|
+
// 過大に見積もる (英数字は全角の半分ほどしか取らない)。 34 で描く `storage` に
|
|
2010
|
+
// `order_items` を書くと、近似は 426 と数えるが実際は 250 ほどしか使わない。
|
|
2011
|
+
//
|
|
2012
|
+
// 1 字の大きさは **その種別が題を描く大きさ** から渡す (#946)。 22 の定数で持っていたが、
|
|
2007
2013
|
// 22 で描く種別は 1 つも無く、見積りが実物の 1.2 倍から 1.7 倍になっていた。
|
|
2008
2014
|
//
|
|
2015
|
+
// 見積りは **実描画に合わせた向き** で取る (#951)。 既定の値は組み方のために広い側へ
|
|
2016
|
+
// 寄せてあり (和文 1.08 倍 / 欧文 1.16 倍以上)、そのまま「はみ出すか」 を判定すると
|
|
2017
|
+
// 収まっている題を挙げる。 見本帳の題 407 か所の実測で、広い側の値では 9 枚が挙がり、
|
|
2018
|
+
// 実測ではその 9 枚とも箱の中に 20px 以上の余白を残していた。
|
|
2019
|
+
//
|
|
2009
2020
|
// 箱の内側の余白 (52) は、題を箱の中に描く種別にだけ足す。 箱の外に描く種別 (`dyn-*` の
|
|
2010
2021
|
// 名前と図表の題) は内側の余白に縛られないので、足すと収まっている題まで挙がる。
|
|
2011
|
-
const
|
|
2012
|
-
const
|
|
2013
|
-
|
|
2014
|
-
if (
|
|
2022
|
+
const 余白 = drawsTitleOutsideBox(n.kind) ? 0 : TITLE_BOX_PADDING;
|
|
2023
|
+
const 使える幅 = n.w - 余白;
|
|
2024
|
+
const 実幅 = measureTextWidth(titleTrimmed, { fontSize: titleFontSize(n.kind), bound: "drawn" });
|
|
2025
|
+
if (titleScalesToFit(n.kind, n.h)) {
|
|
2026
|
+
// 収まるまで縮める種別は、題がはみ出すことが起きない。 代わりに字が読めない大きさまで
|
|
2027
|
+
// 落ちるので、**読める下限を割るか** を見る (#951)。 下限まで縮めても収まらない時
|
|
2028
|
+
// (`actor` 等の 4 種) と、下限を無視して縮め続ける時 (絵の中の字) の両方がここに入る
|
|
2029
|
+
const 縮めた大きさ =
|
|
2030
|
+
実幅 > 0 ? titleFontSize(n.kind) * Math.min(1, 使える幅 / 実幅) : titleFontSize(n.kind);
|
|
2031
|
+
if (縮めた大きさ < MIN_TITLE_FONT_SIZE) {
|
|
2032
|
+
push(
|
|
2033
|
+
"text-readability",
|
|
2034
|
+
`node "${n.id}" title "${n.title}" が node 幅 ${n.w}px に収まらず、読める下限 ${MIN_TITLE_FONT_SIZE}px を割る (縮めて ${縮めた大きさ.toFixed(1)}px)`,
|
|
2035
|
+
"warn",
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
} else if (実幅 > 使える幅 + 8) {
|
|
2015
2039
|
push(
|
|
2016
2040
|
"text-readability",
|
|
2017
|
-
|
|
2041
|
+
// 字ごとの実幅は小数になるので、読む人に出す時だけ丸める (判定は丸めない)
|
|
2042
|
+
`node "${n.id}" title "${n.title}" が node 幅 ${n.w}px を超過する可能性 (期待 ${Math.round(実幅 + 余白)}px)`,
|
|
2018
2043
|
"warn",
|
|
2019
2044
|
);
|
|
2020
2045
|
}
|