@ktrysmt/beautiful-mermaid 1.3.0 → 1.4.1

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/dist/index.js CHANGED
@@ -1013,6 +1013,201 @@ function colorizeText(text, hex, mode) {
1013
1013
  return `${code}${text}${RESET}`;
1014
1014
  }
1015
1015
 
1016
+ // src/ascii/cjk.ts
1017
+ var CJK_PAD = "\uE000";
1018
+ function isFullwidthChar(code) {
1019
+ return (
1020
+ // --- CJK ---
1021
+ code >= 4352 && code <= 4447 || // Hangul Jamo
1022
+ code >= 9001 && code <= 9002 || // Angle Brackets (EAW=W)
1023
+ code >= 11904 && code <= 12031 || // CJK Radicals Supplement
1024
+ code >= 12032 && code <= 12255 || // Kangxi Radicals
1025
+ code >= 12288 && code <= 12351 || // CJK Symbols and Punctuation
1026
+ code >= 12352 && code <= 12447 || // Hiragana
1027
+ code >= 12448 && code <= 12543 || // Katakana
1028
+ code >= 12544 && code <= 12591 || // Bopomofo
1029
+ code >= 12592 && code <= 12687 || // Hangul Compatibility Jamo
1030
+ code >= 12688 && code <= 12799 || // Kanbun + extensions
1031
+ code >= 12800 && code <= 13311 || // Enclosed CJK + Compatibility
1032
+ code >= 13312 && code <= 19903 || // CJK Extension A
1033
+ code >= 19968 && code <= 40959 || // CJK Unified Ideographs
1034
+ code >= 44032 && code <= 55215 || // Hangul Syllables
1035
+ code >= 63744 && code <= 64255 || // CJK Compatibility Ideographs
1036
+ code >= 65072 && code <= 65103 || // CJK Compatibility Forms
1037
+ code >= 65280 && code <= 65376 || // Fullwidth ASCII
1038
+ code >= 65504 && code <= 65510 || // Fullwidth symbols
1039
+ // --- Emoji SMP (EAW=W, always 2 columns) ---
1040
+ code === 126980 || code === 127183 || code === 127374 || code >= 127377 && code <= 127386 || code >= 127456 && code <= 127487 || // Regional indicators
1041
+ code >= 127488 && code <= 127490 || // Enclosed Ideographic Supplement
1042
+ code >= 127504 && code <= 127547 || // Squared CJK Unified Ideograph
1043
+ code >= 127552 && code <= 127560 || // Tortoise Shell Bracketed CJK
1044
+ code >= 127568 && code <= 127569 || // Circled Ideograph
1045
+ code >= 127744 && code <= 128591 || // Misc Symbols & Emoticons
1046
+ code >= 128640 && code <= 128767 || // Transport & Map
1047
+ code >= 128992 && code <= 129003 || // Colored circles/squares
1048
+ code >= 129280 && code <= 129535 || // Supplemental Symbols
1049
+ code >= 129536 && code <= 129647 || // Chess Symbols
1050
+ code >= 129648 && code <= 129791 || // Symbols Extended-A
1051
+ // --- BMP emoji (EAW=W only, always 2 columns) ---
1052
+ code === 8986 || code === 8987 || code >= 9193 && code <= 9196 || code === 9200 || code === 9203 || code === 9725 || code === 9726 || code === 9748 || code === 9749 || code >= 9800 && code <= 9811 || // zodiac
1053
+ code === 9855 || code === 9875 || code === 9889 || // ⚡ EAW=W (NOT ⚠ which is EAW=N)
1054
+ code === 9898 || code === 9899 || code === 9917 || code === 9918 || code === 9924 || code === 9925 || code === 9934 || code === 9940 || code === 9962 || code === 9970 || code === 9971 || code === 9973 || code === 9978 || code === 9981 || code === 9989 || // ✅ EAW=W
1055
+ code === 9994 || code === 9995 || // ✊✋ EAW=W
1056
+ code === 10024 || // ✨ EAW=W
1057
+ code === 10060 || code === 10062 || code >= 10067 && code <= 10069 || code === 10071 || code >= 10133 && code <= 10135 || code === 10160 || code === 10175 || code === 11035 || code === 11036 || code === 11088 || code === 11093 || code === 12336 || code === 12349 || code === 12951 || code === 12953 || // --- CJK Extension B through I + Compatibility Supplement ---
1058
+ // FIX: replaced overly broad `>= 0x20000` with precise ranges
1059
+ code >= 131072 && code <= 173791 || // CJK Extension B
1060
+ code >= 173824 && code <= 177983 || // CJK Extension C
1061
+ code >= 177984 && code <= 178207 || // CJK Extension D
1062
+ code >= 178208 && code <= 183983 || // CJK Extension E
1063
+ code >= 183984 && code <= 191471 || // CJK Extension F
1064
+ code >= 194560 && code <= 195103 || // CJK Compatibility Ideographs Supplement
1065
+ code >= 196608 && code <= 201551 || // CJK Extension G
1066
+ code >= 201552 && code <= 205743 || // CJK Extension H
1067
+ code >= 191472 && code <= 194559
1068
+ );
1069
+ }
1070
+ function isEmojiModifiable(code) {
1071
+ return code === 9888 || // ⚠
1072
+ code === 9986 || // ✂
1073
+ code === 9992 || code === 9993 || // ✈✉
1074
+ code === 9996 || code === 9997 || // ✌✍
1075
+ code === 9999 || code === 10002 || // ✏✒
1076
+ code === 10004 || code === 10006 || // ✔✖
1077
+ code === 10013 || code === 10017 || // ✝✡
1078
+ code === 10035 || code === 10036 || // ✳✴
1079
+ code === 10052 || code === 10055 || // ❄❇
1080
+ code === 10083 || code === 10084 || // ❣❤
1081
+ code === 10145 || // ➡
1082
+ code === 10548 || code === 10549 || // ⤴⤵
1083
+ code >= 11013 && code <= 11015 || // ⬅⬆⬇
1084
+ // FIX: added missing EAW=N emoji (©️ ®️ ™️ ℹ️ ↔️ etc.)
1085
+ code === 169 || // © copyright
1086
+ code === 174 || // ® registered
1087
+ code === 8482 || // ™ trade mark
1088
+ code === 8505 || // ℹ information
1089
+ code >= 8596 && code <= 8601 || // ↔↕↖↗↘↙
1090
+ code >= 8617 && code <= 8618 || // ↩↪
1091
+ code === 8986 || code === 8987 || // ⌚⌛ (also EAW=W, but FE0F doesn't hurt)
1092
+ code >= 9193 && code <= 9203 || // ⏩-⏳
1093
+ code >= 9208 && code <= 9210 || // ⏸⏹⏺
1094
+ code === 9642 || code === 9643 || // ▪▫
1095
+ code === 9654 || code === 9664 || // ▶◀
1096
+ code === 9723 || code === 9724 || // ◻◼
1097
+ code === 9728 || code === 9729 || // ☀☁
1098
+ code >= 9730 && code <= 9732 || // ☂☃☄
1099
+ code === 9742 || // ☎
1100
+ code === 9745 || // ☑
1101
+ code >= 9752 && code <= 9757 || // ☘-☝
1102
+ code === 9760 || // ☠
1103
+ code === 9762 || code === 9763 || // ☢☣
1104
+ code === 9766 || code === 9770 || // ☦☪
1105
+ code === 9774 || code === 9775 || // ☮☯
1106
+ code >= 9784 && code <= 9786 || // ☸☹☺
1107
+ code === 9792 || code === 9794 || // ♀♂
1108
+ code >= 9824 && code <= 9832 || // ♠-♨
1109
+ code === 9851 || code === 9854;
1110
+ }
1111
+ function isZeroWidth(code) {
1112
+ return code >= 65024 && code <= 65039 || // Variation Selectors (VS1-VS16)
1113
+ code === 8203 || // Zero Width Space
1114
+ code === 8204 || // Zero Width Non-Joiner
1115
+ code === 8205 || // Zero Width Joiner
1116
+ code === 65279 || // BOM / Zero Width No-Break Space
1117
+ code >= 917760 && code <= 917999 || // Variation Selectors Supplement
1118
+ // FIX: added combining marks (zero display width)
1119
+ code >= 768 && code <= 879 || // Combining Diacritical Marks
1120
+ code >= 1155 && code <= 1161 || // Combining Cyrillic
1121
+ code >= 1425 && code <= 1469 || // Hebrew combining
1122
+ code >= 1552 && code <= 1562 || // Arabic combining
1123
+ code >= 1611 && code <= 1631 || // Arabic combining
1124
+ code >= 8400 && code <= 8447;
1125
+ }
1126
+ function displayWidth(str) {
1127
+ let w = 0;
1128
+ let prevWasNarrowEmoji = false;
1129
+ for (const ch of str) {
1130
+ const code = ch.codePointAt(0);
1131
+ if (code === void 0) continue;
1132
+ if (code === 65039) {
1133
+ if (prevWasNarrowEmoji) {
1134
+ w += 1;
1135
+ prevWasNarrowEmoji = false;
1136
+ }
1137
+ continue;
1138
+ }
1139
+ if (isZeroWidth(code)) continue;
1140
+ if (isFullwidthChar(code)) {
1141
+ w += 2;
1142
+ prevWasNarrowEmoji = false;
1143
+ } else {
1144
+ w += 1;
1145
+ prevWasNarrowEmoji = isEmojiModifiable(code);
1146
+ }
1147
+ }
1148
+ return w;
1149
+ }
1150
+ function drawCJKText(canvas, x, y, text, forceOverwrite = false, roleCanvas, role, maxCols) {
1151
+ let offset = 0;
1152
+ let lastWrittenCx = -1;
1153
+ let lastWasNarrowEmoji = false;
1154
+ let lastCharWritten = false;
1155
+ const h = canvas[0]?.length ?? 0;
1156
+ for (const ch of text) {
1157
+ const code = ch.codePointAt(0);
1158
+ if (code === void 0) continue;
1159
+ if (code === 65039) {
1160
+ if (lastWasNarrowEmoji && lastCharWritten) {
1161
+ if (maxCols === void 0 || offset < maxCols) {
1162
+ if (lastWrittenCx >= 0 && lastWrittenCx < canvas.length && y >= 0 && y < h) {
1163
+ canvas[lastWrittenCx][y] = (canvas[lastWrittenCx][y] ?? "") + ch;
1164
+ }
1165
+ const px = x + offset;
1166
+ if (px >= 0 && px < canvas.length && y >= 0 && y < h) {
1167
+ canvas[px][y] = CJK_PAD;
1168
+ }
1169
+ offset++;
1170
+ }
1171
+ lastWasNarrowEmoji = false;
1172
+ } else if (lastCharWritten && lastWrittenCx >= 0 && lastWrittenCx < canvas.length && y >= 0 && y < h) {
1173
+ canvas[lastWrittenCx][y] = (canvas[lastWrittenCx][y] ?? "") + ch;
1174
+ }
1175
+ continue;
1176
+ }
1177
+ if (isZeroWidth(code)) continue;
1178
+ const charWidth = isFullwidthChar(code) ? 2 : 1;
1179
+ if (maxCols !== void 0 && offset + charWidth > maxCols) break;
1180
+ const cx = x + offset;
1181
+ let written = false;
1182
+ if (cx >= 0 && cx < canvas.length && y >= 0 && y < h) {
1183
+ if (forceOverwrite || canvas[cx][y] === " ") {
1184
+ canvas[cx][y] = ch;
1185
+ if (roleCanvas && role !== void 0 && cx < roleCanvas.length && y < (roleCanvas[0]?.length ?? 0)) {
1186
+ roleCanvas[cx][y] = role;
1187
+ }
1188
+ written = true;
1189
+ }
1190
+ }
1191
+ if (written) {
1192
+ lastWrittenCx = cx;
1193
+ }
1194
+ lastCharWritten = written;
1195
+ offset++;
1196
+ if (isFullwidthChar(code)) {
1197
+ lastWasNarrowEmoji = false;
1198
+ if (written) {
1199
+ const px = x + offset;
1200
+ if (px >= 0 && px < canvas.length && y >= 0 && y < h) {
1201
+ canvas[px][y] = CJK_PAD;
1202
+ }
1203
+ }
1204
+ offset++;
1205
+ } else {
1206
+ lastWasNarrowEmoji = isEmojiModifiable(code);
1207
+ }
1208
+ }
1209
+ }
1210
+
1016
1211
  // src/ascii/canvas.ts
1017
1212
  function mkCanvas(x, y) {
1018
1213
  const canvas = [];
@@ -1173,6 +1368,7 @@ function canvasToString(canvas, options) {
1173
1368
  if (colorMode === "none" || !roleCanvas) {
1174
1369
  let line = "";
1175
1370
  for (let x = 0; x <= maxX; x++) {
1371
+ if (canvas[x][y] === CJK_PAD) continue;
1176
1372
  line += canvas[x][y];
1177
1373
  }
1178
1374
  lines.push(line);
@@ -1180,6 +1376,7 @@ function canvasToString(canvas, options) {
1180
1376
  const chars = [];
1181
1377
  const roles = [];
1182
1378
  for (let x = 0; x <= maxX; x++) {
1379
+ if (canvas[x][y] === CJK_PAD) continue;
1183
1380
  chars.push(canvas[x][y]);
1184
1381
  roles.push(roleCanvas[x]?.[y] ?? null);
1185
1382
  }
@@ -1230,14 +1427,8 @@ function flipRoleCanvasVertically(roleCanvas) {
1230
1427
  return roleCanvas;
1231
1428
  }
1232
1429
  function drawText(canvas, start, text, forceOverwrite = false) {
1233
- increaseSize(canvas, start.x + text.length, start.y);
1234
- for (let i = 0; i < text.length; i++) {
1235
- const x = start.x + i;
1236
- const current = canvas[x][start.y];
1237
- if (forceOverwrite || current === " ") {
1238
- canvas[x][start.y] = text[i];
1239
- }
1240
- }
1430
+ increaseSize(canvas, start.x + displayWidth(text), start.y);
1431
+ drawCJKText(canvas, start.x, start.y, text, forceOverwrite);
1241
1432
  }
1242
1433
  function setCanvasSizeToGrid(canvas, columnWidth, rowHeight) {
1243
1434
  let maxX = 0;
@@ -1415,6 +1606,18 @@ function buildSgMap(mSgs, aSgs, result) {
1415
1606
  }
1416
1607
  }
1417
1608
 
1609
+ // src/ascii/multiline-utils.ts
1610
+ function splitLines(label) {
1611
+ return label.split("\n");
1612
+ }
1613
+ function maxLineWidth(label) {
1614
+ const lines = splitLines(label);
1615
+ return Math.max(...lines.map((l) => displayWidth(l)), 0);
1616
+ }
1617
+ function lineCount(label) {
1618
+ return splitLines(label).length;
1619
+ }
1620
+
1418
1621
  // src/ascii/pathfinder.ts
1419
1622
  var MinHeap = class {
1420
1623
  items = [];
@@ -1703,7 +1906,7 @@ function determinePath(graph, edge) {
1703
1906
  }
1704
1907
  function determineLabelLine(graph, edge) {
1705
1908
  if (edge.text.length === 0) return;
1706
- const lenLabel = edge.text.length;
1909
+ const lenLabel = maxLineWidth(edge.text);
1707
1910
  const pathLen = edge.path.length;
1708
1911
  const isVerticalFlow = graph.config.graphDirection === "TD";
1709
1912
  const segments = [];
@@ -1904,18 +2107,6 @@ function processBundles(graph) {
1904
2107
  }
1905
2108
  }
1906
2109
 
1907
- // src/ascii/multiline-utils.ts
1908
- function splitLines(label) {
1909
- return label.split("\n");
1910
- }
1911
- function maxLineWidth(label) {
1912
- const lines = splitLines(label);
1913
- return Math.max(...lines.map((l) => l.length), 0);
1914
- }
1915
- function lineCount(label) {
1916
- return splitLines(label).length;
1917
- }
1918
-
1919
2110
  // src/ascii/shapes/corners.ts
1920
2111
  var SHAPE_CORNERS = {
1921
2112
  // Standard rectangular shapes
@@ -1994,9 +2185,10 @@ function getCorners(shape, useAscii) {
1994
2185
  // src/ascii/shapes/rectangle.ts
1995
2186
  function getBoxDimensions(label, options) {
1996
2187
  const lines = splitLines(label);
1997
- const maxLineWidth3 = Math.max(...lines.map((l) => l.length), 0);
2188
+ const maxLineWidth3 = Math.max(...lines.map((l) => displayWidth(l)), 0);
1998
2189
  const lineCount3 = lines.length;
1999
- const innerWidth = 2 * options.padding + maxLineWidth3;
2190
+ const rawInnerWidth = 2 * options.padding + maxLineWidth3;
2191
+ const innerWidth = rawInnerWidth % 2 === 0 ? rawInnerWidth + 1 : rawInnerWidth;
2000
2192
  const width = innerWidth + 2;
2001
2193
  const rawInnerHeight = lineCount3 + 2 * options.padding;
2002
2194
  const innerHeight = rawInnerHeight % 2 === 0 ? rawInnerHeight + 1 : rawInnerHeight;
@@ -2041,14 +2233,9 @@ function renderBox(label, dimensions, corners, useAscii) {
2041
2233
  const startY = centerY - Math.floor((lines.length - 1) / 2);
2042
2234
  for (let i = 0; i < lines.length; i++) {
2043
2235
  const line = lines[i];
2044
- const textX = Math.floor(w / 2) - Math.ceil(line.length / 2) + 1;
2045
- for (let j = 0; j < line.length; j++) {
2046
- const x = textX + j;
2047
- const y = startY + i;
2048
- if (x >= 0 && x < canvas.length && y >= 0 && y < canvas[0].length) {
2049
- canvas[x][y] = line[j];
2050
- }
2051
- }
2236
+ const innerW = width - 2;
2237
+ const textX = 1 + Math.floor((innerW - displayWidth(line)) / 2);
2238
+ drawCJKText(canvas, textX, startY + i, line);
2052
2239
  }
2053
2240
  return canvas;
2054
2241
  }
@@ -2227,7 +2414,7 @@ var roundedRenderer = {
2227
2414
  var stadiumRenderer = {
2228
2415
  getDimensions(label, options) {
2229
2416
  const lines = splitLines(label);
2230
- const maxLineWidth3 = Math.max(...lines.map((l) => l.length), 0);
2417
+ const maxLineWidth3 = Math.max(...lines.map((l) => displayWidth(l)), 0);
2231
2418
  const lineCount3 = lines.length;
2232
2419
  const innerWidth = 2 * options.padding + maxLineWidth3;
2233
2420
  const width = innerWidth + 4;
@@ -2279,14 +2466,8 @@ var stadiumRenderer = {
2279
2466
  const startY = centerY - Math.floor((lines.length - 1) / 2);
2280
2467
  for (let i = 0; i < lines.length; i++) {
2281
2468
  const line = lines[i];
2282
- const textX = Math.floor(width / 2) - Math.floor(line.length / 2);
2283
- for (let j = 0; j < line.length; j++) {
2284
- const x = textX + j;
2285
- const y = startY + i;
2286
- if (x > 0 && x < width - 1 && y >= 0 && y < height) {
2287
- canvas[x][y] = line[j];
2288
- }
2289
- }
2469
+ const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2);
2470
+ drawCJKText(canvas, textX, startY + i, line);
2290
2471
  }
2291
2472
  return canvas;
2292
2473
  },
@@ -2307,7 +2488,7 @@ var hexagonRenderer = {
2307
2488
  var subroutineRenderer = {
2308
2489
  getDimensions(label, options) {
2309
2490
  const lines = splitLines(label);
2310
- const maxLineWidth3 = Math.max(...lines.map((l) => l.length), 0);
2491
+ const maxLineWidth3 = Math.max(...lines.map((l) => displayWidth(l)), 0);
2311
2492
  const lineCount3 = lines.length;
2312
2493
  const innerWidth = 2 * options.padding + maxLineWidth3;
2313
2494
  const width = innerWidth + 4;
@@ -2352,14 +2533,8 @@ var subroutineRenderer = {
2352
2533
  const startY = centerY - Math.floor((lines.length - 1) / 2);
2353
2534
  for (let i = 0; i < lines.length; i++) {
2354
2535
  const line = lines[i];
2355
- const textX = Math.floor(width / 2) - Math.floor(line.length / 2);
2356
- for (let j = 0; j < line.length; j++) {
2357
- const x = textX + j;
2358
- const y = startY + i;
2359
- if (x > 1 && x < width - 2 && y > 0 && y < height - 1) {
2360
- canvas[x][y] = line[j];
2361
- }
2362
- }
2536
+ const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2);
2537
+ drawCJKText(canvas, textX, startY + i, line);
2363
2538
  }
2364
2539
  return canvas;
2365
2540
  },
@@ -2376,7 +2551,7 @@ var doublecircleRenderer = {
2376
2551
  var cylinderRenderer = {
2377
2552
  getDimensions(label, options) {
2378
2553
  const lines = splitLines(label);
2379
- const maxLineWidth3 = Math.max(...lines.map((l) => l.length), 0);
2554
+ const maxLineWidth3 = Math.max(...lines.map((l) => displayWidth(l)), 0);
2380
2555
  const lineCount3 = lines.length;
2381
2556
  const innerWidth = 2 * options.padding + maxLineWidth3;
2382
2557
  const width = innerWidth + 2;
@@ -2421,14 +2596,8 @@ var cylinderRenderer = {
2421
2596
  const startY = centerY - Math.floor((lines.length - 1) / 2);
2422
2597
  for (let i = 0; i < lines.length; i++) {
2423
2598
  const line = lines[i];
2424
- const textX = Math.floor(width / 2) - Math.floor(line.length / 2);
2425
- for (let j = 0; j < line.length; j++) {
2426
- const x = textX + j;
2427
- const y = startY + i;
2428
- if (x > 0 && x < width - 1 && y > 1 && y < height - 2) {
2429
- canvas[x][y] = line[j];
2430
- }
2431
- }
2599
+ const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2);
2600
+ drawCJKText(canvas, textX, startY + i, line);
2432
2601
  }
2433
2602
  return canvas;
2434
2603
  },
@@ -2530,12 +2699,9 @@ function drawBoxWithGridDimensions(node, graph) {
2530
2699
  const startY = textCenterY - Math.floor((lines.length - 1) / 2);
2531
2700
  for (let i = 0; i < lines.length; i++) {
2532
2701
  const line = lines[i];
2533
- const textX = from.x + Math.floor(w / 2) - Math.ceil(line.length / 2) + 1;
2534
- for (let j = 0; j < line.length; j++) {
2535
- if (textX + j >= 0 && textX + j < box.length && startY + i >= 0 && startY + i < box[0].length) {
2536
- box[textX + j][startY + i] = line[j];
2537
- }
2538
- }
2702
+ const boxInnerW = to.x - from.x - 1;
2703
+ const textX = from.x + 1 + Math.floor((boxInnerW - displayWidth(line)) / 2);
2704
+ drawCJKText(box, textX, startY + i, line);
2539
2705
  }
2540
2706
  return box;
2541
2707
  }
@@ -2546,7 +2712,7 @@ function drawMultiBox(sections, useAscii, padding = 1) {
2546
2712
  let maxTextWidth = 0;
2547
2713
  for (const section of sections) {
2548
2714
  for (const line of section) {
2549
- maxTextWidth = Math.max(maxTextWidth, line.length);
2715
+ maxTextWidth = Math.max(maxTextWidth, displayWidth(line));
2550
2716
  }
2551
2717
  }
2552
2718
  const innerWidth = maxTextWidth + 2 * padding;
@@ -2582,9 +2748,7 @@ function drawMultiBox(sections, useAscii, padding = 1) {
2582
2748
  const lines = section.length > 0 ? section : [""];
2583
2749
  for (const line of lines) {
2584
2750
  const startX = 1 + padding;
2585
- for (let i = 0; i < line.length; i++) {
2586
- canvas[startX + i][row] = line[i];
2587
- }
2751
+ drawCJKText(canvas, startX, row, line);
2588
2752
  row++;
2589
2753
  }
2590
2754
  if (s < sections.length - 1) {
@@ -2875,7 +3039,7 @@ function drawTextOnLine(canvas, line, label, isUpwardEdge) {
2875
3039
  const startY = middleY - Math.floor((lines.length - 1) / 2);
2876
3040
  for (let i = 0; i < lines.length; i++) {
2877
3041
  const lineText = lines[i];
2878
- const startX = middleX - Math.floor(lineText.length / 2);
3042
+ const startX = middleX - Math.floor(displayWidth(lineText) / 2);
2879
3043
  drawText(canvas, { x: startX, y: startY + i }, lineText);
2880
3044
  }
2881
3045
  }
@@ -3175,13 +3339,9 @@ function drawSubgraphLabel(sg, graph) {
3175
3339
  for (let i = 0; i < lines.length; i++) {
3176
3340
  const line = lines[i];
3177
3341
  const labelY = 1 + i;
3178
- let labelX = Math.floor(width / 2) - Math.floor(line.length / 2);
3342
+ let labelX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2);
3179
3343
  if (labelX < 1) labelX = 1;
3180
- for (let j = 0; j < line.length; j++) {
3181
- if (labelX + j < width && labelY < height) {
3182
- canvas[labelX + j][labelY] = line[j];
3183
- }
3184
- }
3344
+ drawCJKText(canvas, labelX, labelY, line);
3185
3345
  }
3186
3346
  return [canvas, { x: sg.minX, y: sg.minY }];
3187
3347
  }
@@ -3492,18 +3652,37 @@ function ensureSubgraphSpacing(graph) {
3492
3652
  for (let j = i + 1; j < rootSubgraphs.length; j++) {
3493
3653
  const sg1 = rootSubgraphs[i];
3494
3654
  const sg2 = rootSubgraphs[j];
3495
- if (sg1.minX < sg2.maxX && sg1.maxX > sg2.minX) {
3496
- if (sg1.maxY >= sg2.minY - minSpacing && sg1.minY < sg2.minY) {
3497
- sg2.minY = sg1.maxY + minSpacing + 1;
3498
- } else if (sg2.maxY >= sg1.minY - minSpacing && sg2.minY < sg1.minY) {
3499
- sg1.minY = sg2.maxY + minSpacing + 1;
3655
+ const hOverlap = sg1.minX <= sg2.maxX && sg1.maxX >= sg2.minX;
3656
+ const vOverlap = sg1.minY <= sg2.maxY && sg1.maxY >= sg2.minY;
3657
+ if (!hOverlap || !vOverlap) continue;
3658
+ if (hOverlap) {
3659
+ if (sg1.maxY >= sg2.minY - minSpacing && sg1.minY <= sg2.minY) {
3660
+ const newMinY = sg1.maxY + minSpacing + 1;
3661
+ if (newMinY > sg2.maxY) {
3662
+ sg2.maxY = newMinY + (sg2.maxY - sg2.minY);
3663
+ }
3664
+ sg2.minY = newMinY;
3665
+ } else if (sg2.maxY >= sg1.minY - minSpacing && sg2.minY <= sg1.minY) {
3666
+ const newMinY = sg2.maxY + minSpacing + 1;
3667
+ if (newMinY > sg1.maxY) {
3668
+ sg1.maxY = newMinY + (sg1.maxY - sg1.minY);
3669
+ }
3670
+ sg1.minY = newMinY;
3500
3671
  }
3501
3672
  }
3502
- if (sg1.minY < sg2.maxY && sg1.maxY > sg2.minY) {
3503
- if (sg1.maxX >= sg2.minX - minSpacing && sg1.minX < sg2.minX) {
3504
- sg2.minX = sg1.maxX + minSpacing + 1;
3505
- } else if (sg2.maxX >= sg1.minX - minSpacing && sg2.minX < sg1.minX) {
3506
- sg1.minX = sg2.maxX + minSpacing + 1;
3673
+ if (sg1.minY <= sg2.maxY && sg1.maxY >= sg2.minY) {
3674
+ if (sg1.maxX >= sg2.minX - minSpacing && sg1.minX <= sg2.minX) {
3675
+ const newMinX = sg1.maxX + minSpacing + 1;
3676
+ if (newMinX > sg2.maxX) {
3677
+ sg2.maxX = newMinX + (sg2.maxX - sg2.minX);
3678
+ }
3679
+ sg2.minX = newMinX;
3680
+ } else if (sg2.maxX >= sg1.minX - minSpacing && sg2.minX <= sg1.minX) {
3681
+ const newMinX = sg2.maxX + minSpacing + 1;
3682
+ if (newMinX > sg1.maxX) {
3683
+ sg1.maxX = newMinX + (sg1.maxX - sg1.minX);
3684
+ }
3685
+ sg1.minX = newMinX;
3507
3686
  }
3508
3687
  }
3509
3688
  }
@@ -3572,6 +3751,14 @@ function createMapping(graph) {
3572
3751
  }
3573
3752
  }
3574
3753
  const shouldSeparate = dir === "LR" && hasExternalRoots && hasSubgraphRootsWithEdges;
3754
+ const downstreamSgs = /* @__PURE__ */ new Set();
3755
+ for (const edge of graph.edges) {
3756
+ const fromSg = getNodeSubgraph(graph, edge.from);
3757
+ const toSg = getNodeSubgraph(graph, edge.to);
3758
+ if (fromSg && toSg && fromSg !== toSg && !fromSg.parent && !toSg.parent) {
3759
+ downstreamSgs.add(toSg);
3760
+ }
3761
+ }
3575
3762
  let externalRootNodes;
3576
3763
  let subgraphRootNodes = [];
3577
3764
  if (shouldSeparate) {
@@ -3580,8 +3767,18 @@ function createMapping(graph) {
3580
3767
  } else {
3581
3768
  externalRootNodes = rootNodes;
3582
3769
  }
3583
- const rootsByTarget = /* @__PURE__ */ new Map();
3770
+ const deferredRoots = /* @__PURE__ */ new Set();
3771
+ const primaryRoots = [];
3584
3772
  for (const root of externalRootNodes) {
3773
+ const sg = getNodeSubgraph(graph, root);
3774
+ if (sg && downstreamSgs.has(sg)) {
3775
+ deferredRoots.add(root);
3776
+ } else {
3777
+ primaryRoots.push(root);
3778
+ }
3779
+ }
3780
+ const rootsByTarget = /* @__PURE__ */ new Map();
3781
+ for (const root of primaryRoots) {
3585
3782
  const children = getChildren(graph, root);
3586
3783
  const targetName = children.length > 0 ? children[0].name : "__ungrouped__";
3587
3784
  const group = rootsByTarget.get(targetName) ?? [];
@@ -3607,7 +3804,7 @@ function createMapping(graph) {
3607
3804
  for (const edge of graph.edges) {
3608
3805
  inDegree.set(edge.to.name, (inDegree.get(edge.to.name) ?? 0) + 1);
3609
3806
  }
3610
- let placedCount = externalRootNodes.length + subgraphRootNodes.length;
3807
+ let placedCount = primaryRoots.length + subgraphRootNodes.length;
3611
3808
  while (placedCount < graph.nodes.length) {
3612
3809
  const prevCount = placedCount;
3613
3810
  for (const node of graph.nodes) {
@@ -3618,7 +3815,25 @@ function createMapping(graph) {
3618
3815
  const parentSg = getNodeSubgraph(graph, node);
3619
3816
  const childSg = getNodeSubgraph(graph, child);
3620
3817
  const edgeDir = parentSg && parentSg === childSg && parentSg.direction ? parentSg.direction : graph.config.graphDirection;
3621
- const childLevel = edgeDir === "LR" ? gc.x + 4 : gc.y + 4;
3818
+ let childLevel = edgeDir === "LR" ? gc.x + 4 : gc.y + 4;
3819
+ if (childSg && parentSg !== childSg && deferredRoots.size > 0) {
3820
+ let placedDeferred = false;
3821
+ for (const dr of [...deferredRoots]) {
3822
+ if (dr.gridCoord !== null) continue;
3823
+ const drSg = getNodeSubgraph(graph, dr);
3824
+ if (drSg !== childSg) continue;
3825
+ const drPerp = highestPositionPerLevel[childLevel] ?? 0;
3826
+ const drRequested = edgeDir === "LR" ? { x: childLevel, y: drPerp } : { x: drPerp, y: childLevel };
3827
+ reserveSpotInGrid(graph, graph.nodes[dr.index], drRequested, edgeDir);
3828
+ highestPositionPerLevel[childLevel] = drPerp + 4;
3829
+ deferredRoots.delete(dr);
3830
+ placedCount++;
3831
+ placedDeferred = true;
3832
+ }
3833
+ if (placedDeferred) {
3834
+ childLevel += 4;
3835
+ }
3836
+ }
3622
3837
  let highestPosition;
3623
3838
  if (edgeDir !== graph.config.graphDirection) {
3624
3839
  highestPosition = edgeDir === "LR" ? gc.y : gc.x;
@@ -3628,6 +3843,30 @@ function createMapping(graph) {
3628
3843
  } else {
3629
3844
  highestPosition = highestPositionPerLevel[childLevel];
3630
3845
  }
3846
+ if (childSg) {
3847
+ const existingSgNodes = childSg.nodes.filter((n) => n !== child && n.gridCoord !== null);
3848
+ if (existingSgNodes.length > 0) {
3849
+ const perpPositions = existingSgNodes.map(
3850
+ (n) => graph.config.graphDirection === "TD" ? n.gridCoord.x : n.gridCoord.y
3851
+ );
3852
+ const sgMinPerp = Math.min(...perpPositions);
3853
+ highestPosition = Math.max(highestPosition, sgMinPerp);
3854
+ } else if (parentSg && parentSg !== childSg) {
3855
+ const parentSgNodes = parentSg.nodes.filter((n) => n.gridCoord !== null);
3856
+ if (parentSgNodes.length > 0) {
3857
+ const parentLevels = parentSgNodes.map(
3858
+ (n) => graph.config.graphDirection === "TD" ? n.gridCoord.y : n.gridCoord.x
3859
+ );
3860
+ const parentMaxLevel = Math.max(...parentLevels) + 2;
3861
+ if (childLevel <= parentMaxLevel) {
3862
+ const maxPerp = Math.max(...parentSgNodes.map(
3863
+ (n) => graph.config.graphDirection === "TD" ? n.gridCoord.x : n.gridCoord.y
3864
+ ));
3865
+ highestPosition = Math.max(highestPosition, maxPerp + 4);
3866
+ }
3867
+ }
3868
+ }
3869
+ }
3631
3870
  const requested = edgeDir === "LR" ? { x: childLevel, y: highestPosition } : { x: highestPosition, y: childLevel };
3632
3871
  reserveSpotInGrid(graph, graph.nodes[child.index], requested, edgeDir);
3633
3872
  if (edgeDir === graph.config.graphDirection) {
@@ -3912,7 +4151,7 @@ function renderSequenceAscii(text, config, colorMode, theme) {
3912
4151
  curY += 1;
3913
4152
  const note = diagram.notes[n];
3914
4153
  const nLines = splitLines(note.text);
3915
- const nWidth = Math.max(...nLines.map((l) => l.length)) + 4;
4154
+ const nWidth = Math.max(...nLines.map((l) => displayWidth(l))) + 4;
3916
4155
  const nHeight = nLines.length + 2;
3917
4156
  const aIdx = actorIdx.get(note.actorIds[0]) ?? 0;
3918
4157
  let nx;
@@ -3951,7 +4190,7 @@ function renderSequenceAscii(text, config, colorMode, theme) {
3951
4190
  const msg = diagram.messages[m];
3952
4191
  if (msg.from === msg.to) {
3953
4192
  const fi = actorIdx.get(msg.from);
3954
- const selfRight = llX[fi] + 6 + 2 + msg.label.length;
4193
+ const selfRight = llX[fi] + 6 + 2 + displayWidth(msg.label);
3955
4194
  totalW = Math.max(totalW, selfRight + 1);
3956
4195
  }
3957
4196
  }
@@ -3980,10 +4219,8 @@ function renderSequenceAscii(text, config, colorMode, theme) {
3980
4219
  setC(left, row, V, "border");
3981
4220
  setC(left + w - 1, row, V, "border");
3982
4221
  const line = lines2[i];
3983
- const ls = left + 1 + boxPad + Math.floor((maxW - line.length) / 2);
3984
- for (let j = 0; j < line.length; j++) {
3985
- setC(ls + j, row, line[j], "text");
3986
- }
4222
+ const ls = left + 1 + boxPad + Math.floor((maxW - displayWidth(line)) / 2);
4223
+ drawCJKText(canvas, ls, row, line, true, rc, "text");
3987
4224
  }
3988
4225
  const bottomY = topY + h - 1;
3989
4226
  setC(left, bottomY, BL, "border");
@@ -4023,9 +4260,7 @@ function renderSequenceAscii(text, config, colorMode, theme) {
4023
4260
  setC(fromX + loopW, y0, useAscii ? "+" : "\u2510", "corner");
4024
4261
  setC(fromX + loopW, y0 + 1, V, "line");
4025
4262
  const labelX = fromX + loopW + 2;
4026
- for (let i = 0; i < msg.label.length; i++) {
4027
- if (labelX + i < totalW) setC(labelX + i, y0 + 1, msg.label[i], "text");
4028
- }
4263
+ drawCJKText(canvas, labelX, y0 + 1, msg.label, true, rc, "text");
4029
4264
  const arrowChar = isFilled ? useAscii ? "<" : "\u25C0" : useAscii ? "<" : "\u25C1";
4030
4265
  setC(fromX, y0 + 2, arrowChar, "arrow");
4031
4266
  for (let x = fromX + 1; x < fromX + loopW; x++) setC(x, y0 + 2, lineChar, "line");
@@ -4038,12 +4273,9 @@ function renderSequenceAscii(text, config, colorMode, theme) {
4038
4273
  const msgLines = splitLines(msg.label);
4039
4274
  for (let lineIdx = 0; lineIdx < msgLines.length; lineIdx++) {
4040
4275
  const line = msgLines[lineIdx];
4041
- const labelStart = midX - Math.floor(line.length / 2);
4276
+ const labelStart = midX - Math.floor(displayWidth(line) / 2);
4042
4277
  const y = labelY + lineIdx;
4043
- for (let i = 0; i < line.length; i++) {
4044
- const lx = labelStart + i;
4045
- if (lx >= 0 && lx < totalW) setC(lx, y, line[i], "text");
4046
- }
4278
+ drawCJKText(canvas, labelStart, y, line, true, rc, "text");
4047
4279
  }
4048
4280
  if (leftToRight) {
4049
4281
  for (let x = fromX + 1; x < toX; x++) setC(x, arrowY, lineChar, "line");
@@ -4080,9 +4312,7 @@ function renderSequenceAscii(text, config, colorMode, theme) {
4080
4312
  const hdrLines = splitLines(hdrLabel);
4081
4313
  for (let lineIdx = 0; lineIdx < hdrLines.length && topY + lineIdx < botY; lineIdx++) {
4082
4314
  const line = hdrLines[lineIdx];
4083
- for (let i = 0; i < line.length && bLeft + 1 + i < bRight; i++) {
4084
- setC(bLeft + 1 + i, topY + lineIdx, line[i], "text");
4085
- }
4315
+ drawCJKText(canvas, bLeft + 1, topY + lineIdx, line, true, rc, "text", bRight - bLeft - 1);
4086
4316
  }
4087
4317
  setC(bLeft, botY, BL, "border");
4088
4318
  for (let x = bLeft + 1; x < bRight; x++) setC(x, botY, H, "border");
@@ -4101,9 +4331,7 @@ function renderSequenceAscii(text, config, colorMode, theme) {
4101
4331
  const dLabel = block.dividers[d].label;
4102
4332
  if (dLabel) {
4103
4333
  const dStr = `[${dLabel}]`;
4104
- for (let i = 0; i < dStr.length && bLeft + 1 + i < bRight; i++) {
4105
- setC(bLeft + 1 + i, dY, dStr[i], "text");
4106
- }
4334
+ drawCJKText(canvas, bLeft + 1, dY, dStr, true, rc, "text", bRight - bLeft - 1);
4107
4335
  }
4108
4336
  }
4109
4337
  }
@@ -4117,9 +4345,7 @@ function renderSequenceAscii(text, config, colorMode, theme) {
4117
4345
  const ly = np.y + 1 + l;
4118
4346
  setC(np.x, ly, V, "border");
4119
4347
  setC(np.x + np.width - 1, ly, V, "border");
4120
- for (let i = 0; i < np.lines[l].length; i++) {
4121
- setC(np.x + 2 + i, ly, np.lines[l][i], "text");
4122
- }
4348
+ drawCJKText(canvas, np.x + 2, ly, np.lines[l], true, rc, "text");
4123
4349
  }
4124
4350
  const by = np.y + np.height - 1;
4125
4351
  setC(np.x, by, BL, "border");
@@ -4421,7 +4647,7 @@ function renderClassAscii(text, config, colorMode, theme) {
4421
4647
  classSections.set(cls.id, sections);
4422
4648
  let maxTextW = 0;
4423
4649
  for (const section of sections) {
4424
- for (const line of section) maxTextW = Math.max(maxTextW, line.length);
4650
+ for (const line of section) maxTextW = Math.max(maxTextW, displayWidth(line));
4425
4651
  }
4426
4652
  const boxW = maxTextW + 4;
4427
4653
  let totalLines = 0;
@@ -4737,7 +4963,7 @@ function renderClassAscii(text, config, colorMode, theme) {
4737
4963
  }
4738
4964
  if (rel.label) {
4739
4965
  const lines2 = splitLines(rel.label);
4740
- const maxLabelWidth = Math.max(...lines2.map((l) => l.length)) + 2;
4966
+ const maxLabelWidth = Math.max(...lines2.map((l) => displayWidth(l))) + 2;
4741
4967
  let baseMidY;
4742
4968
  let idealMidX;
4743
4969
  if (fromBY < toTY) {
@@ -4788,20 +5014,16 @@ function renderClassAscii(text, config, colorMode, theme) {
4788
5014
  const startY = labelY - halfHeight;
4789
5015
  for (let lineIdx = 0; lineIdx < lines2.length; lineIdx++) {
4790
5016
  const paddedLine = ` ${lines2[lineIdx]} `;
4791
- const idealLabelStart = idealMidX - Math.floor(paddedLine.length / 2);
5017
+ const paddedLineW = displayWidth(paddedLine);
5018
+ const idealLabelStart = idealMidX - Math.floor(paddedLineW / 2);
4792
5019
  const labelStart = Math.max(0, idealLabelStart);
4793
5020
  const y = startY + lineIdx;
4794
- const labelEnd = labelStart + paddedLine.length;
5021
+ const labelEnd = labelStart + paddedLineW;
4795
5022
  if (labelEnd > 0 && y >= 0) {
4796
5023
  increaseSize(canvas, Math.max(labelEnd, 1), Math.max(y + 1, 1));
4797
5024
  increaseRoleCanvasSize(rc, Math.max(labelEnd, 1), Math.max(y + 1, 1));
4798
5025
  }
4799
- for (let i = 0; i < paddedLine.length; i++) {
4800
- const lx = labelStart + i;
4801
- if (lx >= 0 && y >= 0) {
4802
- setC(lx, y, paddedLine[i], "text");
4803
- }
4804
- }
5026
+ drawCJKText(canvas, labelStart, y, paddedLine, true, rc, "text");
4805
5027
  }
4806
5028
  }
4807
5029
  }
@@ -4998,7 +5220,7 @@ function renderErAscii(text, config, colorMode, theme) {
4998
5220
  entitySections.set(ent.id, sections);
4999
5221
  let maxTextW = 0;
5000
5222
  for (const section of sections) {
5001
- for (const line of section) maxTextW = Math.max(maxTextW, line.length);
5223
+ for (const line of section) maxTextW = Math.max(maxTextW, displayWidth(line));
5002
5224
  }
5003
5225
  const boxW = maxTextW + 4;
5004
5226
  let totalLines = 0;
@@ -5108,16 +5330,11 @@ function renderErAscii(text, config, colorMode, theme) {
5108
5330
  const gapMid = Math.floor((startX + endX) / 2);
5109
5331
  for (let lineIdx = 0; lineIdx < lines2.length; lineIdx++) {
5110
5332
  const line = lines2[lineIdx];
5111
- const labelStart = Math.max(startX, gapMid - Math.floor(line.length / 2));
5333
+ const labelStart = Math.max(startX, gapMid - Math.floor(displayWidth(line) / 2));
5112
5334
  const labelY = lineY + 1 + lineIdx;
5113
- increaseSize(canvas, Math.max(labelStart + line.length, 1), Math.max(labelY + 1, 1));
5114
- increaseRoleCanvasSize(rc, Math.max(labelStart + line.length, 1), Math.max(labelY + 1, 1));
5115
- for (let i = 0; i < line.length; i++) {
5116
- const lx = labelStart + i;
5117
- if (lx >= startX && lx <= endX) {
5118
- setC(lx, labelY, line[i], "text");
5119
- }
5120
- }
5335
+ increaseSize(canvas, Math.max(labelStart + displayWidth(line), 1), Math.max(labelY + 1, 1));
5336
+ increaseRoleCanvasSize(rc, Math.max(labelStart + displayWidth(line), 1), Math.max(labelY + 1, 1));
5337
+ drawCJKText(canvas, labelStart, labelY, line, true, rc, "text", endX - labelStart + 1);
5121
5338
  }
5122
5339
  }
5123
5340
  } else {
@@ -5159,14 +5376,9 @@ function renderErAscii(text, config, colorMode, theme) {
5159
5376
  const labelX = lineX + 2;
5160
5377
  const y = startLabelY + lineIdx;
5161
5378
  if (y >= 0) {
5162
- for (let i = 0; i < line.length; i++) {
5163
- const lx = labelX + i;
5164
- if (lx >= 0) {
5165
- increaseSize(canvas, lx + 1, y + 1);
5166
- increaseRoleCanvasSize(rc, lx + 1, y + 1);
5167
- setC(lx, y, line[i], "text");
5168
- }
5169
- }
5379
+ increaseSize(canvas, labelX + displayWidth(line) + 1, y + 1);
5380
+ increaseRoleCanvasSize(rc, labelX + displayWidth(line) + 1, y + 1);
5381
+ drawCJKText(canvas, labelX, y, line, true, rc, "text");
5170
5382
  }
5171
5383
  }
5172
5384
  }
@@ -5391,7 +5603,7 @@ function renderVertical(chart, ch, colorMode, theme) {
5391
5603
  const yRange = chart.yAxis.range;
5392
5604
  const yTicks = niceTickValues(yRange.min, yRange.max);
5393
5605
  const yLabels = yTicks.map((v) => formatTickValue(v));
5394
- const yGutter = Math.max(...yLabels.map((l) => l.length)) + 1;
5606
+ const yGutter = Math.max(...yLabels.map((l) => displayWidth(l))) + 1;
5395
5607
  const plotW = Math.max(PLOT_WIDTH, dataCount * 6);
5396
5608
  const plotH = PLOT_HEIGHT;
5397
5609
  const bandW = Math.floor(plotW / dataCount);
@@ -5417,7 +5629,7 @@ function renderVertical(chart, ch, colorMode, theme) {
5417
5629
  };
5418
5630
  const bandCenter = (i) => plotLeft + Math.floor(bandW * (i + 0.5));
5419
5631
  if (hasTitle && titleRow >= 0) {
5420
- writeText(canvas, roles, titleRow, Math.floor(totalW / 2 - chart.title.length / 2), chart.title, "text");
5632
+ writeText(canvas, roles, titleRow, Math.floor(totalW / 2 - displayWidth(chart.title) / 2), chart.title, "text");
5421
5633
  }
5422
5634
  if (hasLegend) {
5423
5635
  const legendRow = hasTitle ? 1 : 0;
@@ -5434,7 +5646,7 @@ function renderVertical(chart, ch, colorMode, theme) {
5434
5646
  const displayRow = plotTop + (plotH - 1 - row);
5435
5647
  const label = formatTickValue(tick);
5436
5648
  set(canvas, roles, displayRow, plotLeft - 1, row === 0 ? ch.origin : ch.yTick, "border");
5437
- const labelStart = yGutter - label.length;
5649
+ const labelStart = yGutter - displayWidth(label);
5438
5650
  writeText(canvas, roles, displayRow, Math.max(0, labelStart), label, "text");
5439
5651
  }
5440
5652
  for (let c = plotLeft; c < plotLeft + bandW * dataCount; c++) {
@@ -5444,12 +5656,12 @@ function renderVertical(chart, ch, colorMode, theme) {
5444
5656
  const cx = bandCenter(i);
5445
5657
  set(canvas, roles, xAxisRow, cx, ch.xTick, "border");
5446
5658
  const label = catLabels[i];
5447
- const labelStart = cx - Math.floor(label.length / 2);
5659
+ const labelStart = cx - Math.floor(displayWidth(label) / 2);
5448
5660
  writeText(canvas, roles, xLabelRow, Math.max(0, labelStart), label, "text");
5449
5661
  }
5450
5662
  if (hasXTitle && xTitleRow >= 0) {
5451
5663
  const title = chart.xAxis.title;
5452
- writeText(canvas, roles, xTitleRow, Math.floor(totalW / 2 - title.length / 2), title, "text");
5664
+ writeText(canvas, roles, xTitleRow, Math.floor(totalW / 2 - displayWidth(title) / 2), title, "text");
5453
5665
  }
5454
5666
  for (const tick of yTicks) {
5455
5667
  const row = valueToRow(tick);
@@ -5507,7 +5719,7 @@ function renderHorizontal(chart, ch, colorMode, theme) {
5507
5719
  const yRange = chart.yAxis.range;
5508
5720
  const valueTicks = niceTickValues(yRange.min, yRange.max);
5509
5721
  const catLabels = getCategoryLabels(chart, dataCount);
5510
- const catGutter = Math.max(...catLabels.map((l) => l.length)) + 1;
5722
+ const catGutter = Math.max(...catLabels.map((l) => displayWidth(l))) + 1;
5511
5723
  const plotW = Math.max(PLOT_WIDTH, 40);
5512
5724
  const bandH = Math.max(2, Math.floor(PLOT_HEIGHT / dataCount));
5513
5725
  const plotH = bandH * dataCount;
@@ -5529,7 +5741,7 @@ function renderHorizontal(chart, ch, colorMode, theme) {
5529
5741
  };
5530
5742
  const bandMid = (i) => plotTop + Math.floor(bandH * (i + 0.5));
5531
5743
  if (hasTitle) {
5532
- writeText(canvas, roles, 0, Math.floor(totalW / 2 - chart.title.length / 2), chart.title, "text");
5744
+ writeText(canvas, roles, 0, Math.floor(totalW / 2 - displayWidth(chart.title) / 2), chart.title, "text");
5533
5745
  }
5534
5746
  if (hasLegend) {
5535
5747
  const legendRow = hasTitle ? 1 : 0;
@@ -5542,7 +5754,7 @@ function renderHorizontal(chart, ch, colorMode, theme) {
5542
5754
  for (let i = 0; i < dataCount; i++) {
5543
5755
  const my = bandMid(i);
5544
5756
  const label = catLabels[i];
5545
- const labelStart = catGutter - label.length;
5757
+ const labelStart = catGutter - displayWidth(label);
5546
5758
  writeText(canvas, roles, my, Math.max(0, labelStart), label, "text");
5547
5759
  }
5548
5760
  for (let c = plotLeft; c < plotLeft + plotW; c++) {
@@ -5553,11 +5765,11 @@ function renderHorizontal(chart, ch, colorMode, theme) {
5553
5765
  if (cx < plotLeft || cx >= plotLeft + plotW) continue;
5554
5766
  set(canvas, roles, xAxisRow, cx, ch.xTick, "border");
5555
5767
  const label = formatTickValue(tick);
5556
- writeText(canvas, roles, xAxisRow + 1, cx - Math.floor(label.length / 2), label, "text");
5768
+ writeText(canvas, roles, xAxisRow + 1, cx - Math.floor(displayWidth(label) / 2), label, "text");
5557
5769
  }
5558
5770
  if (hasYTitle) {
5559
5771
  const title = chart.yAxis.title;
5560
- writeText(canvas, roles, totalH - 1, Math.floor(totalW / 2 - title.length / 2), title, "text");
5772
+ writeText(canvas, roles, totalH - 1, Math.floor(totalW / 2 - displayWidth(title) / 2), title, "text");
5561
5773
  }
5562
5774
  for (const tick of valueTicks) {
5563
5775
  const cx = valueToCol(tick);
@@ -5733,7 +5945,7 @@ function drawLegend(canvas, roles, hexCanvas, chart, row, totalW, ch, seriesColo
5733
5945
  let totalLen = 0;
5734
5946
  for (let i = 0; i < items.length; i++) {
5735
5947
  if (i > 0) totalLen += 2;
5736
- totalLen += 1 + 1 + items[i].label.length;
5948
+ totalLen += 1 + 1 + displayWidth(items[i].label);
5737
5949
  }
5738
5950
  const startCol = Math.max(0, Math.floor(totalW / 2 - totalLen / 2));
5739
5951
  let col = startCol;
@@ -5744,7 +5956,7 @@ function drawLegend(canvas, roles, hexCanvas, chart, row, totalW, ch, seriesColo
5744
5956
  col += 1;
5745
5957
  col += 1;
5746
5958
  writeText(canvas, roles, row, col, item.label, "text");
5747
- col += item.label.length;
5959
+ col += displayWidth(item.label);
5748
5960
  }
5749
5961
  }
5750
5962
  function createCanvas(width, height) {
@@ -5770,9 +5982,7 @@ function get(canvas, row, col) {
5770
5982
  return " ";
5771
5983
  }
5772
5984
  function writeText(canvas, roles, row, startCol, text, role) {
5773
- for (let i = 0; i < text.length; i++) {
5774
- set(canvas, roles, row, startCol + i, text[i], role);
5775
- }
5985
+ drawCJKText(canvas, startCol, row, text, true, roles, role);
5776
5986
  }
5777
5987
  function canvasToString2(canvas, roles, hexCanvas, colorMode, theme) {
5778
5988
  if (canvas.length === 0) return "";
@@ -5784,6 +5994,7 @@ function canvasToString2(canvas, roles, hexCanvas, colorMode, theme) {
5784
5994
  const rowRoles = [];
5785
5995
  const rowHex = [];
5786
5996
  for (let col = 0; col < width; col++) {
5997
+ if (canvas[col][row] === CJK_PAD) continue;
5787
5998
  chars.push(canvas[col][row]);
5788
5999
  rowRoles.push(roles[col][row]);
5789
6000
  rowHex.push(hexCanvas[col][row]);