for-humanity 0.1.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 +14 -0
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/cli.css +962 -0
- package/dist/cli.js +1241 -0
- package/dist/client.js +5 -0
- package/package.json +92 -0
- package/src/app.tsx +57 -0
- package/src/asset/favicon.svg +5 -0
- package/src/asset/font/architects-daughter/LICENSE.txt +93 -0
- package/src/asset/font/architects-daughter/architects-daughter-latin-400-normal.woff2 +0 -0
- package/src/asset/font/architects-daughter/architects-daughter.css +10 -0
- package/src/asset/font/jetbrains-mono/LICENSE +93 -0
- package/src/asset/font/jetbrains-mono/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- package/src/asset/font/jetbrains-mono/jetbrains-mono.css +11 -0
- package/src/asset/font/pretendard/LICENSE.txt +94 -0
- package/src/asset/font/pretendard/pretendardvariable-dynamic-subset.css +1008 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.0.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.1.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.10.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.11.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.12.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.13.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.14.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.15.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.16.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.17.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.18.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.19.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.2.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.20.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.21.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.22.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.23.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.24.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.25.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.26.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.27.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.28.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.29.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.3.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.30.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.31.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.32.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.33.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.34.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.35.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.36.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.37.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.38.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.39.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.4.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.40.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.41.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.42.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.43.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.44.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.45.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.46.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.47.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.48.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.49.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.5.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.50.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.51.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.52.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.53.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.54.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.55.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.56.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.57.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.58.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.59.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.6.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.60.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.61.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.62.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.63.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.64.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.65.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.66.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.67.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.68.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.69.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.7.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.70.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.71.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.72.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.73.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.74.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.75.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.76.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.77.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.78.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.79.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.8.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.80.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.81.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.82.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.83.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.84.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.85.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.86.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.87.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.88.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.89.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.9.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.90.woff2 +0 -0
- package/src/asset/font/pretendard/woff2-dynamic-subset/PretendardVariable.subset.91.woff2 +0 -0
- package/src/cli.ts +162 -0
- package/src/client.ts +322 -0
- package/src/component/widget/prose/_constant/heading-highlight.ts +4 -0
- package/src/component/widget/prose/_constant/section.ts +7 -0
- package/src/component/widget/prose/_constant/status.ts +9 -0
- package/src/component/widget/prose/_function/has-block-content.ts +30 -0
- package/src/component/widget/prose/_function/rehype-head.ts +31 -0
- package/src/component/widget/prose/_function/rehype-sections/_to-section-headings.ts +20 -0
- package/src/component/widget/prose/_function/rehype-sections/rehype-sections.ts +21 -0
- package/src/component/widget/prose/_function/rehype-tables.ts +22 -0
- package/src/component/widget/prose/_function/remark-blocks.ts +112 -0
- package/src/component/widget/prose/_function/remark-flow.ts +26 -0
- package/src/component/widget/prose/_function/remark-links.ts +33 -0
- package/src/component/widget/prose/_function/remark-parts.ts +26 -0
- package/src/component/widget/prose/_function/remark-status.ts +59 -0
- package/src/component/widget/prose/_function/remark-swatch.ts +16 -0
- package/src/component/widget/prose/_function/remark-unknown-directives.ts +25 -0
- package/src/component/widget/prose/_type/doc-section.ts +9 -0
- package/src/component/widget/prose/wg-prose.css +381 -0
- package/src/component/widget/prose/wg-prose.tsx +43 -0
- package/src/component/widget/shell/_constant/cursor-face.ts +9 -0
- package/src/component/widget/shell/_constant/navigation.ts +4 -0
- package/src/component/widget/shell/_constant/reading-line.ts +4 -0
- package/src/component/widget/shell/_function/bind-mobile-navigation.ts +140 -0
- package/src/component/widget/shell/_function/to-toc-groups.ts +38 -0
- package/src/component/widget/shell/_type/doc-outline.ts +16 -0
- package/src/component/widget/shell/_type/toc-group.ts +39 -0
- package/src/component/widget/shell/_wg-shell-nav.css +134 -0
- package/src/component/widget/shell/_wg-shell-nav.tsx +75 -0
- package/src/component/widget/shell/_wg-shell-toc.css +268 -0
- package/src/component/widget/shell/_wg-shell-toc.tsx +57 -0
- package/src/component/widget/shell/wg-shell.css +261 -0
- package/src/component/widget/shell/wg-shell.tsx +147 -0
- package/src/constant/asset.ts +48 -0
- package/src/constant/cli.ts +19 -0
- package/src/constant/copy.ts +108 -0
- package/src/constant/doc.ts +13 -0
- package/src/constant/font.ts +39 -0
- package/src/constant/locale.ts +9 -0
- package/src/constant/site.ts +9 -0
- package/src/constant/status.ts +15 -0
- package/src/content/create-processor.ts +59 -0
- package/src/content/read-docs.test.ts +242 -0
- package/src/content/read-docs.ts +75 -0
- package/src/content/read-home.ts +32 -0
- package/src/content/rehype-heading-ids.ts +34 -0
- package/src/content/remark-report.ts +15 -0
- package/src/content/to-doc-groups.ts +18 -0
- package/src/dev.ts +79 -0
- package/src/env.d.ts +6 -0
- package/src/page/doc/pg-doc.tsx +35 -0
- package/src/page/home/pg-home.tsx +40 -0
- package/src/style/base.css +28 -0
- package/src/style/token.css +162 -0
- package/src/type/doc-content.ts +15 -0
- package/src/type/doc-data.ts +33 -0
- package/src/type/doc-file-data.ts +21 -0
- package/src/type/doc-heading.ts +17 -0
- package/src/type/doc.ts +16 -0
- package/src/type/site-assets.ts +17 -0
- package/src/type/site-config.ts +55 -0
- package/src/type/vfile-data.d.ts +10 -0
- package/src/util/dom/find-hash-target.ts +18 -0
- package/src/util/dom/reveal-hash-target.ts +15 -0
- package/src/util/error/to-error-message.ts +10 -0
- package/src/util/font/to-font-css.test.ts +41 -0
- package/src/util/font/to-font-css.ts +28 -0
- package/src/util/mermaid/render-flow.js +321 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ASCII 격자의 빌드 시 SVG 렌더링 · CSS 변수 테마
|
|
3
|
+
* 전각 문자의 폭 0 문자 삽입으로 2칸 확보
|
|
4
|
+
* 공유 blueprint 알고리즘 · Biome 제외 · 실행 로직 변경 시 원본 대조
|
|
5
|
+
*/
|
|
6
|
+
import {renderMermaidASCII} from "beautiful-mermaid";
|
|
7
|
+
|
|
8
|
+
const esc = (s) => String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
9
|
+
|
|
10
|
+
const ASCII_OPT = {paddingX: 3, paddingY: 2, boxBorderPadding: 1, colorMode: "none"};
|
|
11
|
+
const WIDE = /[ᄀ-ᇿ -〿-가-一-鿿-ヿ-⦆]/;
|
|
12
|
+
const ZW = "";
|
|
13
|
+
const widenCjk = (src) => src.replace(new RegExp(WIDE.source, "g"), (c) => c + ZW);
|
|
14
|
+
// 선 문자의 연결 방향 · L R U D · r 둥근 모서리
|
|
15
|
+
const LINES = {"─": "LR", "│": "UD", "┌": "RD", "┐": "LD", "└": "RU", "┘": "LU", "├": "UDR", "┤": "UDL", "┬": "LRD", "┴": "LRU", "┼": "LRUD", "╭": "RDr", "╮": "LDr", "╰": "RUr", "╯": "LUr",
|
|
16
|
+
"═": "LRb", "║": "UDb", "╔": "RDb", "╗": "LDb", "╚": "RUb", "╝": "LUb", "╟": "UDRb", "╢": "UDLb", "╌": "LRd", "╎": "UDd"};
|
|
17
|
+
const ARROWS = {"►": "R", "◄": "L", "▼": "D", "▲": "U", "▶": "R", "◀": "L"};
|
|
18
|
+
// 선·화살촉의 접점: 상자 벽 선의 가운데
|
|
19
|
+
const isWall = (chr) => chr === "│" || chr === "◇" || chr === "├" || chr === "┤";
|
|
20
|
+
const isHBorder = (chr) => chr !== undefined && chr !== " " && chr !== ZW && (LINES[chr] !== undefined || chr === "◇");
|
|
21
|
+
|
|
22
|
+
// ASCII 라벨 구간의 3칸 여백 보정
|
|
23
|
+
// 1) 글자를 가르지 않는 열 삽입 · 라벨 양옆 최소 6칸 확보
|
|
24
|
+
// 2) 라벨 중앙 정렬 · 줄 길이 유지로 세로선 정렬 보존
|
|
25
|
+
function widenLabelGaps(rows) {
|
|
26
|
+
const width = Math.max.apply(null, rows.map((r) => r.length));
|
|
27
|
+
let grid = rows.map((r) => r.padEnd(width));
|
|
28
|
+
const H = new Set("─┬┴├┤┼◇┌┐└┘╭╮╰╯═╌►◄▶◀╔╗╚╝╟╢".split(""));
|
|
29
|
+
const ANCHOR = new Set("├┤┬┴┼└┘┌┐╭╮╰╯".split(""));
|
|
30
|
+
const END = new Set("►▶◄◀┤├┬┴┼┐┘┌└╮╯╭╰│".split(""));
|
|
31
|
+
const isTxt = (chr) => chr !== undefined && chr !== " " && chr !== ZW && chr !== "│" && !H.has(chr);
|
|
32
|
+
const txtish = (chr) => isTxt(chr) || chr === ZW;
|
|
33
|
+
const splittable = (col) => grid.every((r) => !(txtish(r[col - 1]) && txtish(r[col])));
|
|
34
|
+
const filler = (r, col) => {
|
|
35
|
+
const l = r[col - 1], rt = r[col];
|
|
36
|
+
if (l === undefined || rt === undefined) return " ";
|
|
37
|
+
if ((H.has(l) || txtish(l)) && (H.has(rt) || rt === "│" || txtish(rt)) && !(txtish(l) && txtish(rt))) {
|
|
38
|
+
if (!(H.has(l) || H.has(rt))) return " ";
|
|
39
|
+
return l === "╌" || rt === "╌" ? "╌" : "─";
|
|
40
|
+
}
|
|
41
|
+
return " ";
|
|
42
|
+
};
|
|
43
|
+
// 선 위 라벨 · 양쪽 선 또는 모서리·화살표에 닿은 글자 묶음
|
|
44
|
+
const runs = (row) => {
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const m of row.matchAll(/(─*)((?:[가-힣A-Za-z0-9]?)+)(─*)/g)) {
|
|
47
|
+
if (m[2].length === 0) continue;
|
|
48
|
+
const before = row[m.index - 1], after = row[m.index + m[0].length];
|
|
49
|
+
const leftOk = m[1].length > 0 || ANCHOR.has(before);
|
|
50
|
+
const rightOk = m[3].length > 0 || END.has(after);
|
|
51
|
+
if (leftOk && rightOk && (m[1].length + m[3].length > 0 || (ANCHOR.has(before) && END.has(after)))) {
|
|
52
|
+
out.push({start: m.index, label: m[2], left: m[1].length, right: m[3].length, end: m.index + m[0].length});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
};
|
|
57
|
+
const want = 6;
|
|
58
|
+
// 1) 오른쪽부터 열 삽입 · 기존 인덱스 보존
|
|
59
|
+
const inserts = [];
|
|
60
|
+
grid.forEach((row) => {
|
|
61
|
+
for (const run of runs(row)) {
|
|
62
|
+
const missing = want - (run.left + run.right);
|
|
63
|
+
if (missing > 0) inserts.push({at: run.end, alt: run.start + run.left, n: missing});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
inserts.sort((x, y) => y.at - x.at);
|
|
67
|
+
for (const ins of inserts) {
|
|
68
|
+
const candidates = [ins.at, ins.at + 1, ins.at + 2, ins.alt, ins.alt - 1];
|
|
69
|
+
const col = candidates.find((c) => c > 0 && c < width + 40 && splittable(c));
|
|
70
|
+
if (col === undefined) continue;
|
|
71
|
+
grid = grid.map((r) => r.slice(0, col) + filler(r, col).repeat(ins.n) + r.slice(col));
|
|
72
|
+
}
|
|
73
|
+
// 2) 라벨 중앙 정렬
|
|
74
|
+
return grid.map((row) => {
|
|
75
|
+
let out = row;
|
|
76
|
+
for (const run of runs(row).reverse()) {
|
|
77
|
+
const total = run.left + run.right, l = Math.floor(total / 2), rgt = total - l;
|
|
78
|
+
out = out.slice(0, run.start) + "─".repeat(l) + run.label + "─".repeat(rgt) + out.slice(run.end);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function gridToSvg(ascii) {
|
|
85
|
+
const cw = 7.2, ch = 17, fs = 12;
|
|
86
|
+
const rows = widenLabelGaps(ascii.replace(/\s+$/, "").split("\n"));
|
|
87
|
+
const cols = Math.max.apply(null, rows.map((r) => r.length));
|
|
88
|
+
const f = (n) => n.toFixed(1);
|
|
89
|
+
let path = "", bold = "", dashed = "", arcs = "", tris = "", marks = "", dots = "", texts = "";
|
|
90
|
+
|
|
91
|
+
const isText = (chr) => chr !== undefined && chr !== " " && chr !== ZW && LINES[chr] === undefined && ARROWS[chr] === undefined && chr !== "◇";
|
|
92
|
+
const center = (c) => c * cw + cw / 2;
|
|
93
|
+
const textWidth = (chars) => chars.reduce((w, chr) => w + (WIDE.test(chr) ? fs : cw), 0);
|
|
94
|
+
|
|
95
|
+
rows.forEach((row, r) => {
|
|
96
|
+
const cy = r * ch + ch / 2, y0 = r * ch, y1 = y0 + ch;
|
|
97
|
+
let run = null;
|
|
98
|
+
// 전각 선 라벨의 1칸 오차 보정
|
|
99
|
+
// 벽부터 화살촉까지 중앙 정렬 · 라벨 폭만큼 선 비움
|
|
100
|
+
const skip = new Set();
|
|
101
|
+
for (let c = 0; c < row.length; c++) {
|
|
102
|
+
if (!isText(row[c]) || skip.has(c)) continue;
|
|
103
|
+
let e = c;
|
|
104
|
+
while (e < row.length && (isText(row[e]) || row[e] === ZW || (row[e] === " " && isText(row[e + 1])))) e++;
|
|
105
|
+
// 테두리 라벨로 사라진 분기점 ┬ 복원
|
|
106
|
+
// 테두리·세로선 연결 · 라벨은 다음 줄의 세로선 옆
|
|
107
|
+
if (row[c - 1] === "─" && row[e] === "─") {
|
|
108
|
+
const below = rows[r + 1] || "", above = rows[r - 1] || "";
|
|
109
|
+
let exit = null;
|
|
110
|
+
for (let k = c - 2; k < e + 2 && exit === null; k++) {
|
|
111
|
+
if (below[k] === "│" || below[k] === "▼") exit = {k: k, dir: 1};
|
|
112
|
+
else if (above[k] === "│" || above[k] === "▲") exit = {k: k, dir: -1};
|
|
113
|
+
}
|
|
114
|
+
if (exit) {
|
|
115
|
+
const kx = center(exit.k), chars = row.slice(c, e).split("").filter((chr) => chr !== ZW);
|
|
116
|
+
path += "M" + f(c * cw) + " " + f(cy) + "H" + f(e * cw) + " ";
|
|
117
|
+
path += "M" + f(kx) + " " + f(cy) + "V" + f(exit.dir > 0 ? y1 : y0) + " ";
|
|
118
|
+
texts += '<text x="' + f(kx + cw * 0.8 + textWidth(chars) / 2) + '" y="' + f(cy + exit.dir * ch + fs * 0.35) + '">' + esc(chars.join("")) + "</text>";
|
|
119
|
+
for (let k = c; k < e; k++) skip.add(k);
|
|
120
|
+
c = e - 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let l = c - 1, rr = e;
|
|
126
|
+
while (l >= 0 && row[l] === " ") l--;
|
|
127
|
+
while (rr < row.length && row[rr] === " ") rr++;
|
|
128
|
+
const onLine = (row[l] === "─" || row[l] === "├") && (row[rr] === "─" || ARROWS[row[rr]] !== undefined || row[rr] === "┤");
|
|
129
|
+
if (!onLine) { c = e - 1; continue; }
|
|
130
|
+
let L = l;
|
|
131
|
+
while (L - 1 >= 0 && row[L - 1] === "─") L--;
|
|
132
|
+
// 출발점 ├ 앞의 ASCII 여백을 건너 상자 벽까지 연결
|
|
133
|
+
let segStart = L * cw, drawStart = L * cw;
|
|
134
|
+
if (row[L] === "├" || row[L - 1] === "├") {
|
|
135
|
+
const j = row[L] === "├" ? L : L - 1;
|
|
136
|
+
const above = rows[r - 1] ? rows[r - 1][j] : " ", below = rows[r + 1] ? rows[r + 1][j] : " ";
|
|
137
|
+
const junction = (above !== undefined && LINES[above] !== undefined && /[UD]/.test(LINES[above])) ||
|
|
138
|
+
(below !== undefined && LINES[below] !== undefined && /[UD]/.test(LINES[below]));
|
|
139
|
+
if (junction) {
|
|
140
|
+
// 상자 벽인 ├는 기본 루프에서 유지 · 연결선만 벽 가운데부터 시작
|
|
141
|
+
segStart = center(j);
|
|
142
|
+
drawStart = center(j);
|
|
143
|
+
for (let m = j + 1; m < L; m++) skip.add(m);
|
|
144
|
+
} else {
|
|
145
|
+
// 벽과 떨어진 ├는 별도 표식 없이 연결선으로 대체
|
|
146
|
+
let k = j - 1;
|
|
147
|
+
while (k >= 0 && row[k] === " ") k--;
|
|
148
|
+
segStart = isWall(row[k]) ? center(k) : center(j);
|
|
149
|
+
drawStart = segStart;
|
|
150
|
+
for (let m = j; m < L; m++) skip.add(m);
|
|
151
|
+
}
|
|
152
|
+
} else if (LINES[row[L - 1]] !== undefined || isWall(row[L - 1])) {
|
|
153
|
+
segStart = center(L - 1);
|
|
154
|
+
}
|
|
155
|
+
let R = rr;
|
|
156
|
+
while (R + 1 < row.length && row[R + 1] === "─") R++;
|
|
157
|
+
let segEnd = (R + 1) * cw, drawEnd = (R + 1) * cw;
|
|
158
|
+
if (ARROWS[row[R]] !== undefined) {
|
|
159
|
+
drawEnd = R * cw;
|
|
160
|
+
segEnd = isWall(row[R + 1]) ? center(R + 1) : center(R) + cw * 0.45;
|
|
161
|
+
R--;
|
|
162
|
+
} else if (ARROWS[row[R + 1]] !== undefined) {
|
|
163
|
+
drawEnd = (R + 1) * cw;
|
|
164
|
+
segEnd = isWall(row[R + 2]) ? center(R + 2) : center(R + 1) + cw * 0.45;
|
|
165
|
+
} else if (LINES[row[R + 1]] !== undefined || isWall(row[R + 1])) {
|
|
166
|
+
segEnd = center(R + 1);
|
|
167
|
+
}
|
|
168
|
+
const chars = row.slice(c, e).split("").filter((chr) => chr !== ZW);
|
|
169
|
+
const mid = (segStart + segEnd) / 2, half = textWidth(chars) / 2 + 5;
|
|
170
|
+
path += "M" + f(drawStart) + " " + f(cy) + "H" + f(mid - half) + " ";
|
|
171
|
+
path += "M" + f(mid + half) + " " + f(cy) + "H" + f(drawEnd) + " ";
|
|
172
|
+
texts += '<text x="' + f(mid) + '" y="' + f(cy + fs * 0.35) + '">' + esc(chars.join("")) + "</text>";
|
|
173
|
+
for (let k = L; k <= R; k++) skip.add(k);
|
|
174
|
+
c = e - 1;
|
|
175
|
+
}
|
|
176
|
+
// 영문은 격자 칸별 배치 · 한글 혼합은 예약 칸 중앙의 글자 묶음
|
|
177
|
+
// 상자 내부 라벨은 실제 테두리 중앙 · ASCII의 반 칸 오차 보정
|
|
178
|
+
// 테두리 없는 라벨은 예약 칸 기준 정렬
|
|
179
|
+
const wall = (chr) => chr === "│" || chr === "◇" || chr === "├" || chr === "┤";
|
|
180
|
+
const walls = (from, to) => {
|
|
181
|
+
let l = from - 1, rgt = to;
|
|
182
|
+
while (l >= 0 && (row[l] === " " || row[l] === ZW)) l--;
|
|
183
|
+
while (rgt < row.length && (row[rgt] === " " || row[rgt] === ZW)) rgt++;
|
|
184
|
+
return wall(row[l] || "") && wall(row[rgt] || "") ? [l, rgt] : null;
|
|
185
|
+
};
|
|
186
|
+
// 상자 내부와 라벨의 줄 수 홀짝 차이 · 반 줄 처짐 보정
|
|
187
|
+
const lift = (box) => {
|
|
188
|
+
const col = run.start;
|
|
189
|
+
let top = r - 1, bottom = r + 1;
|
|
190
|
+
while (top >= 0 && !isHBorder((rows[top] || "")[col])) top--;
|
|
191
|
+
while (bottom < rows.length && !isHBorder((rows[bottom] || "")[col])) bottom++;
|
|
192
|
+
// 실제 상자 모서리만 보정 · 시퀀스 생명선 제외
|
|
193
|
+
const corner = (chr) => chr !== undefined && chr !== "─" && chr !== "═" && chr !== "╌" && (LINES[chr] !== undefined || chr === "◇");
|
|
194
|
+
if (top < 0 || bottom >= rows.length || !corner((rows[top] || "")[box[0]]) || !corner((rows[bottom] || "")[box[0]])) return 0;
|
|
195
|
+
let labelRows = 0;
|
|
196
|
+
for (let k = top + 1; k < bottom; k++) {
|
|
197
|
+
const inner = (rows[k] || "").slice(box[0] + 1, box[1]).split(ZW).join("").trim();
|
|
198
|
+
if (inner.length > 0) labelRows++;
|
|
199
|
+
}
|
|
200
|
+
return (bottom - top - 1 - labelRows) % 2 === 1 ? -ch / 2 : 0;
|
|
201
|
+
};
|
|
202
|
+
const flush = () => {
|
|
203
|
+
if (!run) return;
|
|
204
|
+
const box = walls(run.start, run.end);
|
|
205
|
+
const y = f(cy + fs * 0.35 + (box ? lift(box) : 0));
|
|
206
|
+
const x = box ? f(((box[0] + 1) * cw + box[1] * cw) / 2) : run.wide ? f((run.start * cw + run.end * cw) / 2) : run.xs.join(" ");
|
|
207
|
+
texts += '<text x="' + x + '" y="' + y + '">' + esc(run.chars.join("")) + "</text>";
|
|
208
|
+
run = null;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
for (let c = 0; c < row.length; c++) {
|
|
212
|
+
const chr = row[c];
|
|
213
|
+
const cx = c * cw + cw / 2, x0 = c * cw, x1 = x0 + cw;
|
|
214
|
+
|
|
215
|
+
if (skip.has(c)) { flush(); continue; }
|
|
216
|
+
if (chr === ZW) { if (run) run.end = c + 1; continue; }
|
|
217
|
+
|
|
218
|
+
// 라벨 내부 단일 공백은 묶음 유지 · 연속 공백은 묶음 종료
|
|
219
|
+
if (chr === " ") {
|
|
220
|
+
const next = row[c + 1];
|
|
221
|
+
const joins = run && next !== undefined && next !== " " && next !== ZW && !LINES[next] && !ARROWS[next] && next !== "◇";
|
|
222
|
+
if (!joins) { flush(); continue; }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 세로선 없는 ├·┤는 ASCII 가로선의 출발·도착점
|
|
226
|
+
// 상자와의 여백까지 연결
|
|
227
|
+
const above = rows[r - 1] ? rows[r - 1][c] : " ", below = rows[r + 1] ? rows[r + 1][c] : " ";
|
|
228
|
+
const vertical = (v) => v !== undefined && LINES[v] !== undefined && /[UD]/.test(LINES[v]) || v === "◇";
|
|
229
|
+
if ((chr === "├" || chr === "┤") && !vertical(above) && !vertical(below)) {
|
|
230
|
+
flush();
|
|
231
|
+
let l = c - 1, rgt = c + 1;
|
|
232
|
+
while (l >= 0 && row[l] === " ") l--;
|
|
233
|
+
while (rgt < row.length && row[rgt] === " ") rgt++;
|
|
234
|
+
const gapL = isWall(row[l]) ? l * cw + cw / 2 : x0;
|
|
235
|
+
const gapR = isWall(row[rgt]) ? rgt * cw + cw / 2 : x1;
|
|
236
|
+
path += "M" + f(gapL) + " " + f(cy) + "H" + f(gapR) + " ";
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const ln = LINES[chr];
|
|
241
|
+
if (ln) {
|
|
242
|
+
flush();
|
|
243
|
+
if (ln.indexOf("r") >= 0) {
|
|
244
|
+
const ax = ln.indexOf("L") >= 0 ? x0 : x1, by = ln.indexOf("U") >= 0 ? y0 : y1;
|
|
245
|
+
arcs += "M" + f(ax) + " " + f(cy) + "Q" + f(cx) + " " + f(cy) + " " + f(cx) + " " + f(by) + " ";
|
|
246
|
+
} else {
|
|
247
|
+
// 벽 접점까지 선 연장 · b 이중선, d 점선
|
|
248
|
+
let seg = "";
|
|
249
|
+
if (ln.indexOf("L") >= 0) seg += "M" + f(isWall(row[c - 1]) ? x0 - cw / 2 : x0) + " " + f(cy) + "H" + f(cx) + " ";
|
|
250
|
+
if (ln.indexOf("R") >= 0) seg += "M" + f(cx) + " " + f(cy) + "H" + f(isWall(row[c + 1]) ? x1 + cw / 2 : x1) + " ";
|
|
251
|
+
if (ln.indexOf("U") >= 0) seg += "M" + f(cx) + " " + f(y0) + "V" + f(cy) + " ";
|
|
252
|
+
if (ln.indexOf("D") >= 0) seg += "M" + f(cx) + " " + f(cy) + "V" + f(y1) + " ";
|
|
253
|
+
if (ln.indexOf("b") >= 0) bold += seg; else if (ln.indexOf("d") >= 0) dashed += seg; else path += seg;
|
|
254
|
+
}
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const ar = ARROWS[chr];
|
|
259
|
+
if (ar) {
|
|
260
|
+
flush();
|
|
261
|
+
// 화살촉의 벽 접점 보정
|
|
262
|
+
const w = cw * 0.9, h = ch * 0.42;
|
|
263
|
+
const tipR = isWall(row[c + 1]) ? x1 + cw / 2 : cx + w / 2, tipL = isWall(row[c - 1]) ? x0 - cw / 2 : cx - w / 2;
|
|
264
|
+
const tipD = isHBorder(below) ? y1 + ch / 2 : cy + h / 2, tipU = isHBorder(above) ? y0 - ch / 2 : cy - h / 2;
|
|
265
|
+
if (ar === "R") { path += "M" + f(x0) + " " + f(cy) + "H" + f(tipR - w) + " "; tris += "M" + f(tipR - w) + " " + f(cy - h / 2) + "L" + f(tipR) + " " + f(cy) + "L" + f(tipR - w) + " " + f(cy + h / 2) + "Z "; }
|
|
266
|
+
if (ar === "L") { path += "M" + f(tipL + w) + " " + f(cy) + "H" + f(x1) + " "; tris += "M" + f(tipL + w) + " " + f(cy - h / 2) + "L" + f(tipL) + " " + f(cy) + "L" + f(tipL + w) + " " + f(cy + h / 2) + "Z "; }
|
|
267
|
+
if (ar === "D") { path += "M" + f(cx) + " " + f(y0) + "V" + f(tipD - h) + " "; tris += "M" + f(cx - w / 2) + " " + f(tipD - h) + "L" + f(cx + w / 2) + " " + f(tipD - h) + "L" + f(cx) + " " + f(tipD) + "Z "; }
|
|
268
|
+
if (ar === "U") { path += "M" + f(cx) + " " + f(tipU + h) + "V" + f(y1) + " "; tris += "M" + f(cx - w / 2) + " " + f(tipU + h) + "L" + f(cx + w / 2) + " " + f(tipU + h) + "L" + f(cx) + " " + f(tipU) + "Z "; }
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (chr === "◇") {
|
|
273
|
+
flush();
|
|
274
|
+
const w = cw * 0.8, h = ch * 0.4;
|
|
275
|
+
marks += "M" + f(cx) + " " + f(cy - h / 2) + "L" + f(cx + w / 2) + " " + f(cy) + "L" + f(cx) + " " + f(cy + h / 2) + "L" + f(cx - w / 2) + " " + f(cy) + "Z ";
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 상태도 시작 ● · 클래스 상속 △
|
|
280
|
+
if (chr === "●") {
|
|
281
|
+
flush();
|
|
282
|
+
const rr = cw * 0.45;
|
|
283
|
+
dots += "M" + f(cx - rr) + " " + f(cy) + "a" + f(rr) + " " + f(rr) + " 0 1 0 " + f(rr * 2) + " 0a" + f(rr) + " " + f(rr) + " 0 1 0 " + f(-rr * 2) + " 0Z ";
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (chr === "△") {
|
|
288
|
+
flush();
|
|
289
|
+
const w = cw * 0.9, h = ch * 0.42;
|
|
290
|
+
marks += "M" + f(cx) + " " + f(cy - h / 2) + "L" + f(cx + w / 2) + " " + f(cy + h / 2) + "L" + f(cx - w / 2) + " " + f(cy + h / 2) + "Z ";
|
|
291
|
+
path += "M" + f(cx) + " " + f(cy + h / 2) + "V" + f(y1) + " ";
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!run) run = {start: c, end: c + 1, xs: [], chars: [], wide: false};
|
|
296
|
+
run.end = c + 1;
|
|
297
|
+
run.xs.push(f(cx));
|
|
298
|
+
run.chars.push(chr);
|
|
299
|
+
if (WIDE.test(chr)) run.wide = true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
flush();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const W = f(cols * cw), H = f(rows.length * ch);
|
|
306
|
+
|
|
307
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" class="ascii-flow" viewBox="0 0 ' + W + " " + H + '" width="' + W + '" height="' + H + '">' +
|
|
308
|
+
'<path d="' + path + '" fill="none" stroke="var(--app-color-text-muted)" stroke-width="1"/>' +
|
|
309
|
+
'<path d="' + bold + '" fill="none" stroke="var(--app-color-text-muted)" stroke-width="2"/>' +
|
|
310
|
+
'<path d="' + dashed + '" fill="none" stroke="var(--app-color-text-muted)" stroke-width="1" stroke-dasharray="3 3"/>' +
|
|
311
|
+
'<path d="' + arcs + '" fill="none" stroke="var(--app-color-text-muted)" stroke-width="1"/>' +
|
|
312
|
+
'<path d="' + tris + '" fill="var(--app-color-text-muted)"/>' +
|
|
313
|
+
'<path d="' + dots + '" fill="var(--app-color-text-muted)"/>' +
|
|
314
|
+
'<path d="' + marks + '" fill="var(--app-color-surface)" stroke="var(--app-color-text-muted)" stroke-width="1"/>' +
|
|
315
|
+
'<g font-family="var(--app-font-mono)" font-size="' + fs + '" text-anchor="middle" fill="var(--app-color-text)">' + texts + "</g></svg>";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Mermaid 원문의 격자 SVG 문자열
|
|
320
|
+
*/
|
|
321
|
+
export const renderFlow = (source) => gridToSvg(renderMermaidASCII(widenCjk(source), ASCII_OPT));
|