text-input-guard 0.1.3 → 0.1.5

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.
@@ -330,10 +330,25 @@ class SwapState {
330
330
  * @property {boolean} warn - warnログを出すかどうか
331
331
  * @property {string} invalidClass - エラー時に付与するclass名
332
332
  * @property {boolean} composing - IME変換中かどうか
333
+ * @property {string|null} inputType - 直前の入力操作種別(insertText / insertFromPaste / insertCompositionText 等)
334
+ * @property {string} beforeText - 挿入前の全文字列(置換範囲は除去済み)
335
+ * @property {number} replaceStart - 挿入位置/置換開始位置(selectionStart)
336
+ * @property {number} replaceEnd - 置換終了位置(selectionEnd)
337
+ * @property {string} insertedText - 挿入された文字列
338
+ * @property {string} afterText - 挿入後の全文字列(後で代入する)
333
339
  * @property {(e: TigError) => void} pushError - エラーを登録する関数
334
340
  * @property {(req: RevertRequest) => void} requestRevert - 入力を直前の受理値へ巻き戻す要求
335
341
  */
336
342
 
343
+ /**
344
+ * beforeinput で採取する「入力直前スナップショット」
345
+ * - 入力反映前の状態を保持し、差分判定や挿入位置特定に利用する
346
+ * @typedef {Object} BeforeInputSnapshot
347
+ * @property {SelectionState} selection - 入力反映前の選択範囲(挿入/置換位置の判定に使用)
348
+ * @property {string|null} inputType - 入力種別(insertText / insertFromPaste / deleteContentBackward 等)
349
+ * @property {string|null} insertedText - 挿入された文字列(通常入力時に取得できる場合あり)
350
+ */
351
+
337
352
  /**
338
353
  * 1つの入力制御ルール定義
339
354
  * - 各フェーズの処理を必要に応じて実装する
@@ -590,6 +605,11 @@ class InputGuard {
590
605
  */
591
606
  this.onInput = this.onInput.bind(this);
592
607
 
608
+ /**
609
+ * beforeinputイベントハンドラ(this固定)
610
+ */
611
+ this.onBeforeInput = this.onBeforeInput.bind(this);
612
+
593
613
  /**
594
614
  * blurイベントハンドラ(this固定)
595
615
  */
@@ -619,17 +639,24 @@ class InputGuard {
619
639
  this.pendingCompositionCommit = false;
620
640
 
621
641
  /**
622
- * 直前に受理した表示値(block時の戻し先)
642
+ * 直前に受理した表示値、正しい情報のスナップショットのような情報(block時の戻し先)
623
643
  * @type {string}
624
644
  */
625
645
  this.lastAcceptedValue = "";
626
646
 
627
647
  /**
628
- * 直前に受理したselectionblock時の戻し先)
648
+ * 直前に受理したselection、正しい情報のスナップショットのような情報(block時の戻し先)
629
649
  * @type {SelectionState}
630
650
  */
631
651
  this.lastAcceptedSelection = { start: null, end: null, direction: null };
632
652
 
653
+ /**
654
+ * 入力直前スナップショット(beforeinputで更新)
655
+ * length等の「挿入位置優先」ロジックで使用する
656
+ * @type {BeforeInputSnapshot|null}
657
+ */
658
+ this.beforeInputSnapshot = null;
659
+
633
660
  /**
634
661
  * ルールからのrevert要求
635
662
  * @type {RevertRequest|null}
@@ -691,7 +718,7 @@ class InputGuard {
691
718
  applySeparateValue() {
692
719
  const userMode = this.options.separateValue?.mode ?? "auto";
693
720
 
694
- // autoの場合:format系ルールがあるときだけswap
721
+ // autoの場合:format系ルールがあるときだけswap (つまり input を作成する)
695
722
  const mode =
696
723
  userMode === "auto"
697
724
  ? (this.formatRules.length > 0 ? "swap" : "off")
@@ -830,6 +857,7 @@ class InputGuard {
830
857
  this.displayElement.addEventListener("compositionstart", this.onCompositionStart);
831
858
  this.displayElement.addEventListener("compositionend", this.onCompositionEnd);
832
859
  this.displayElement.addEventListener("input", this.onInput);
860
+ this.displayElement.addEventListener("beforeinput", this.onBeforeInput);
833
861
  this.displayElement.addEventListener("blur", this.onBlur);
834
862
 
835
863
  // フォーカスで編集用に戻す
@@ -850,6 +878,7 @@ class InputGuard {
850
878
  this.displayElement.removeEventListener("compositionstart", this.onCompositionStart);
851
879
  this.displayElement.removeEventListener("compositionend", this.onCompositionEnd);
852
880
  this.displayElement.removeEventListener("input", this.onInput);
881
+ this.displayElement.removeEventListener("beforeinput", this.onBeforeInput);
853
882
  this.displayElement.removeEventListener("blur", this.onBlur);
854
883
  this.displayElement.removeEventListener("focus", this.onFocus);
855
884
  this.displayElement.removeEventListener("keyup", this.onSelectionChange);
@@ -891,6 +920,70 @@ class InputGuard {
891
920
  * @returns {GuardContext}
892
921
  */
893
922
  createCtx() {
923
+ const snap = this.beforeInputSnapshot;
924
+ const inputType = snap?.inputType ?? "";
925
+ const insertedText = snap?.insertedText ?? "";
926
+
927
+ // 受理済み(正規化済み)の全文を「今回の編集の基準」として使う
928
+ // display.value はブラウザ側の編集結果が混ざるので、差分再構成の基準にはしない
929
+ const beforeText = this.lastAcceptedValue ?? "";
930
+
931
+ // selection は2系統ある:
932
+ // - snapSel: beforeinput 時点で取得した selection(今回の編集の基準点になり得る)
933
+ // - lastSel: ユーザー操作(keyup/mouseup/select 等)で追跡している selection(常にUIの見た目に近い)
934
+ //
935
+ // 基本は snapSel を優先するが、IME が絡むと snapSel が「変換中の範囲(composition range)」を指して
936
+ // “本当のキャレット位置” と一致しないことがあるため、その場合は lastSel を採用する。
937
+ const snapSel = snap?.selection ?? null;
938
+ const lastSel = this.lastAcceptedSelection;
939
+
940
+ // 通常は beforeinput の selection(snapSel)を使うのが一番正確
941
+ let baseSel = snapSel ?? lastSel;
942
+
943
+ // IME由来の入力(変換中の確定/更新など)は、beforeinput の selection が
944
+ // 「IMEが管理する範囲」になってしまうことがある。
945
+ // この場合 snapSel を使うと “勝手に上書き” が起きやすいので lastSel に寄せる。
946
+ const isCompositionInput =
947
+ this.composing ||
948
+ inputType === "insertCompositionText" ||
949
+ inputType === "deleteCompositionText" ||
950
+ inputType === "insertFromComposition";
951
+
952
+ // もう一つの検知:snapSel が「範囲選択」なのに lastSel が「キャレットのみ」なら、
953
+ // その範囲はユーザーが選択したのではなく、IMEが作っている範囲である可能性が高い。
954
+ // (例:12|34 に 5 を入れたいのに、IME範囲を置換して 1254 になる、など)
955
+ const looksLikeImeRange =
956
+ snapSel &&
957
+ (snapSel.start !== snapSel.end) &&
958
+ (lastSel.start === lastSel.end) &&
959
+ (inputType === "insertText" || inputType === "insertCompositionText");
960
+
961
+ if (isCompositionInput || looksLikeImeRange) {
962
+ baseSel = lastSel;
963
+ }
964
+
965
+ let replaceStart = baseSel.start ?? 0;
966
+ let replaceEnd = baseSel.end ?? 0;
967
+
968
+ // Backspace / Delete は「挿入文字がない(dataがnull)」ことが多い。
969
+ // そのままだと差分再構成で “何も変わらない” 扱いになって削除が効かなくなるため、
970
+ // 選択範囲が無い場合は「削除される1文字ぶん」の置換範囲をここで作る。
971
+ //
972
+ // ※ 選択範囲がある削除は replaceStart!=replaceEnd なので補正不要(その範囲を消すだけでよい)
973
+ if (replaceStart === replaceEnd) {
974
+ if (inputType === "deleteContentBackward") {
975
+ // Backspace: キャレットの左側1文字を削除
976
+ replaceStart = Math.max(0, replaceStart - 1);
977
+ replaceEnd = snapSel.start ?? replaceEnd;
978
+ } else if (inputType === "deleteContentForward") {
979
+ // Delete: キャレットの右側1文字を削除
980
+ replaceStart = snapSel.start ?? replaceStart;
981
+ replaceEnd = Math.min(beforeText.length, replaceEnd + 1);
982
+ }
983
+ // 追加で拾うならここ:
984
+ // deleteWordBackward / deleteWordForward / deleteByCut / deleteSoftLineBackward ... etc
985
+ }
986
+
894
987
  return {
895
988
  hostElement: this.hostElement,
896
989
  displayElement: this.displayElement,
@@ -899,6 +992,12 @@ class InputGuard {
899
992
  warn: this.warn,
900
993
  invalidClass: this.invalidClass,
901
994
  composing: this.composing,
995
+ inputType,
996
+ beforeText,
997
+ replaceStart,
998
+ replaceEnd,
999
+ insertedText,
1000
+ afterText: null, // 後で代入する
902
1001
  pushError: (e) => this.errors.push(e),
903
1002
  requestRevert: (req) => {
904
1003
  // 1回でもrevert要求が出たら採用(最初の理由を保持)
@@ -1063,6 +1162,23 @@ class InputGuard {
1063
1162
  this.evaluateInput();
1064
1163
  }
1065
1164
 
1165
+ /**
1166
+ * beforeinput:入力が反映される直前に呼ばれる
1167
+ * - ここでの value/selection が「今回の編集の基準点」になる
1168
+ * @param {InputEvent} e
1169
+ * @returns {void}
1170
+ */
1171
+ onBeforeInput(e) {
1172
+ const el = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1173
+ // 現時点(反映前)の選択範囲
1174
+ const selection = this.readSelection(el);
1175
+ /** @type {string|null} */
1176
+ const inputType = typeof e.inputType === "string" ? e.inputType : null;
1177
+ /** @type {string|null} */
1178
+ const insertedText = typeof e.data === "string" ? e.data : null;
1179
+ this.beforeInputSnapshot = { selection, inputType, insertedText };
1180
+ }
1181
+
1066
1182
  /**
1067
1183
  * blurイベント:確定時評価(normalize → validate → fix → format、同期、class更新)
1068
1184
  * @returns {void}
@@ -1073,7 +1189,7 @@ class InputGuard {
1073
1189
  }
1074
1190
 
1075
1191
  /**
1076
- * focusイベント:表示整形(カンマ等)を剥がして編集しやすい状態にする
1192
+ * focusイベント:表示整形を剥がして編集しやすい状態にする
1077
1193
  * - validate は走らせない(触っただけで赤くしたくないため)
1078
1194
  * @returns {void}
1079
1195
  */
@@ -1084,9 +1200,10 @@ class InputGuard {
1084
1200
  const current = display.value;
1085
1201
 
1086
1202
  const ctx = this.createCtx();
1203
+ ctx.afterText = current;
1087
1204
 
1088
1205
  let v = current;
1089
- v = this.runNormalizeChar(v, ctx); // カンマ除去が効く
1206
+ v = this.runNormalizeChar(v, ctx);
1090
1207
  v = this.runNormalizeStructure(v, ctx);
1091
1208
 
1092
1209
  if (v !== current) {
@@ -1153,6 +1270,83 @@ class InputGuard {
1153
1270
  }
1154
1271
  }
1155
1272
 
1273
+ /**
1274
+ * evaluateInput専用createCtx(ルール実行に渡すコンテキストを作り、正規箇所も実行する)
1275
+ *
1276
+ * - CTX を作成する中で、文字の正規化と構造の正規化を行い、CTXのその情報に合わせる
1277
+ * - runNormalizeChar に対して入力した文字のみを入れることで、処理の高速化とキャレットズレが起きないように制御する
1278
+ * - runNormalizeStructure は全体の文字を入れるため、ここでの処理はキャレットズレが起きる可能性がある
1279
+ * @returns {GuardContext}
1280
+ */
1281
+ createCtxAndNormalize() {
1282
+ const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1283
+ const current = display.value;
1284
+ const ctx = this.createCtx();
1285
+ ctx.afterText = current;
1286
+
1287
+ // 元のテキスト
1288
+ const beforeText = ctx.beforeText;
1289
+
1290
+ // 追加入力したテキスト
1291
+ let insertedText = ctx.insertedText;
1292
+
1293
+ // 左端の挿入箇所
1294
+ const replaceStart = ctx.replaceStart;
1295
+
1296
+ // 現状のテキスト
1297
+ const tempText = current;
1298
+
1299
+ // 作成する全体のテキスト
1300
+ let newText = beforeText;
1301
+
1302
+ if (ctx.replaceStart !== ctx.replaceEnd) {
1303
+ // 選択範囲の前までと、選択範囲の後ろを結合して、間(選択部分)を削除する
1304
+ newText = beforeText.slice(0, ctx.replaceStart) + beforeText.slice(ctx.replaceEnd);
1305
+ }
1306
+
1307
+ // CTX の情報を最新の情報へ更新する
1308
+ ctx.beforeText = newText;
1309
+
1310
+ // 挿入するテキストのみ文字チェックを行う
1311
+ const normalizeCharText = this.runNormalizeChar(insertedText, ctx);
1312
+ insertedText = normalizeCharText;
1313
+
1314
+ // 作成したテキストを挿入する
1315
+ newText = newText.slice(0, replaceStart) + insertedText + newText.slice(replaceStart);
1316
+
1317
+ // 挿入したテキストの右側にカーソル位置をずらす
1318
+ // insertedText は UTF-16 code unit 長なので、
1319
+ // Selection は JS の index(UTF-16)前提で計算
1320
+ /**
1321
+ * @type {SelectionState}
1322
+ */
1323
+ let newSelection = { start: replaceStart + insertedText.length, end: replaceStart + insertedText.length, direction: "forward" };
1324
+
1325
+ // 挿入後文章全体に構造チェックを行う
1326
+ const normalizeStructureText = this.runNormalizeStructure(newText, ctx);
1327
+
1328
+ // 構成した文章がずれていた場合、カーソル位置の見直しを行う
1329
+ if (newText !== normalizeStructureText) {
1330
+ newText = normalizeStructureText;
1331
+ // 入力した実際のテキスト(tempText)から、現在位置から左側のみ切り出して、左側のみ再チェックする
1332
+ // 文章の長さに依存した変更があった場合は厳しいが、それ以外は以下の方法で切り抜けられる可能性が高い
1333
+ let leftText = tempText.slice(0, replaceStart);
1334
+ leftText = this.runNormalizeChar(leftText, ctx);
1335
+ leftText = this.runNormalizeStructure(leftText, ctx);
1336
+ const newPos = Math.min(leftText.length, newText.length);
1337
+ newSelection = { start: newPos, end: newPos, direction: "forward" };
1338
+ }
1339
+
1340
+ // 画面を更新
1341
+ this.syncDisplay(newText);
1342
+ this.writeSelection(display, newSelection);
1343
+
1344
+ // CTX の情報を最新の情報へ更新する
1345
+ ctx.afterText = newText;
1346
+
1347
+ return ctx;
1348
+ }
1349
+
1156
1350
  /**
1157
1351
  * 入力中の評価(IME中は何もしない)
1158
1352
  * - 固定順:normalize.char → normalize.structure → validate
@@ -1168,26 +1362,15 @@ class InputGuard {
1168
1362
  this.revertRequest = null;
1169
1363
 
1170
1364
  const display = /** @type {HTMLInputElement|HTMLTextAreaElement} */ (this.displayElement);
1171
- const current = display.value;
1172
1365
 
1173
- const ctx = this.createCtx();
1174
-
1175
- // raw候補(入力中は表示値=rawとして扱う)
1176
- let raw = current;
1177
-
1178
- raw = this.runNormalizeChar(raw, ctx);
1179
- raw = this.runNormalizeStructure(raw, ctx);
1180
-
1181
- // normalizeで変わったら反映(selection補正)
1182
- if (raw !== current) {
1183
- this.setDisplayValuePreserveCaret(display, raw, ctx);
1184
- }
1366
+ const ctx = this.createCtxAndNormalize();
1367
+ const raw = ctx.afterText;
1185
1368
 
1186
1369
  // validate(入力中:エラー出すだけ)
1187
1370
  this.runValidate(raw, ctx);
1188
1371
 
1189
- // revert要求が出たら巻き戻して終了
1190
1372
  if (this.revertRequest) {
1373
+ // revert要求が出たら巻き戻して終了
1191
1374
  this.revertDisplay(this.revertRequest);
1192
1375
  return;
1193
1376
  }
@@ -1221,6 +1404,7 @@ class InputGuard {
1221
1404
 
1222
1405
  // 1) raw候補(displayから取得)
1223
1406
  let raw = display.value;
1407
+ ctx.afterText = raw;
1224
1408
 
1225
1409
  // 2) 正規化(rawとして扱う形に揃える)
1226
1410
  raw = this.runNormalizeChar(raw, ctx);
@@ -1354,59 +1538,122 @@ class InputGuard {
1354
1538
  }
1355
1539
 
1356
1540
  /**
1357
- * The script is part of TextInputGuard.
1541
+ * dataset/option boolean 値を解釈する
1542
+ * - 未指定(null/undefined)の場合は defaultValue を返す
1543
+ * - 空文字 "" は常に true(HTML属性文化)
1544
+ * - 指定があるが解釈できない場合は undefined
1358
1545
  *
1359
- * AUTHOR:
1360
- * natade-jp (https://github.com/natade-jp)
1546
+ * true : true / 1 / "true" / "1" / "yes" / "on" / ""
1547
+ * false : false / 0 / "false" / "0" / "no" / "off"
1361
1548
  *
1362
- * LICENSE:
1363
- * The MIT license https://opensource.org/licenses/MIT
1364
- */
1365
-
1366
- /**
1367
- * datasetのboolean値を解釈する
1368
- * - 未指定なら undefined
1369
- * - "" / "true" / "1" / "yes" / "on" は true
1370
- * - "false" / "0" / "no" / "off" は false
1371
- * @param {string|undefined} v
1549
+ * @param {string|number|boolean|undefined|null} v
1550
+ * @param {boolean} [defaultValue]
1372
1551
  * @returns {boolean|undefined}
1373
1552
  */
1374
- function parseDatasetBool(v) {
1375
- if (v == null) { return; }
1553
+ function parseDatasetBool(v, defaultValue) {
1554
+ if (v === null || v === undefined) { return defaultValue; }
1555
+
1556
+ if (typeof v === "boolean") { return v; }
1557
+
1558
+ if (typeof v === "number") {
1559
+ if (v === 1) { return true; }
1560
+ if (v === 0) { return false; }
1561
+ return;
1562
+ }
1563
+
1376
1564
  const s = String(v).trim().toLowerCase();
1377
- if (s === "" || s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1565
+
1566
+ // dataset の属性存在を true とみなす(例: data-xxx="")
1567
+ if (s === "") { return true; }
1568
+
1569
+ if (s === "true" || s === "1" || s === "yes" || s === "on") { return true; }
1378
1570
  if (s === "false" || s === "0" || s === "no" || s === "off") { return false; }
1571
+
1379
1572
  return;
1380
1573
  }
1381
1574
 
1382
1575
  /**
1383
- * datasetのnumber値を解釈する(整数想定)
1384
- * - 未指定/空なら undefined
1576
+ * dataset/option number 値を解釈する
1577
+ * - 未指定(null/undefined/空文字)の場合は defaultValue を返す
1385
1578
  * - 数値でなければ undefined
1386
- * @param {string|undefined} v
1579
+ * @param {string|number|undefined|null} v
1580
+ * @param {number} [defaultValue]
1387
1581
  * @returns {number|undefined}
1388
1582
  */
1389
- function parseDatasetNumber(v) {
1390
- if (v == null) { return; }
1583
+ function parseDatasetNumber(v, defaultValue) {
1584
+ if (v === null || v === undefined) { return defaultValue; }
1585
+
1586
+ if (typeof v === "number") {
1587
+ return Number.isFinite(v) ? v : undefined;
1588
+ }
1589
+
1391
1590
  const s = String(v).trim();
1392
- if (s === "") { return; }
1591
+ if (s === "") { return defaultValue; }
1592
+
1393
1593
  const n = Number(s);
1394
1594
  return Number.isFinite(n) ? n : undefined;
1395
1595
  }
1396
1596
 
1397
1597
  /**
1398
- * enumを解釈する(未指定なら undefined)
1598
+ * enumを解釈する
1599
+ * - 未指定(null/undefined/空文字)の場合は defaultValue を返す
1600
+ * - 値が指定されているが allowed に含まれない場合は undefined を返す
1601
+ *
1399
1602
  * @template {string} T
1400
- * @param {string|undefined} v
1603
+ * @param {string|undefined|null} v
1401
1604
  * @param {readonly T[]} allowed
1605
+ * @param {T} [defaultValue]
1402
1606
  * @returns {T|undefined}
1403
1607
  */
1404
- function parseDatasetEnum(v, allowed) {
1405
- if (v == null) { return; }
1608
+ function parseDatasetEnum(v, allowed, defaultValue) {
1609
+ if (v === null || v === undefined) { return defaultValue; }
1610
+
1611
+ const s = String(v).trim();
1612
+ if (s === "") { return defaultValue; }
1613
+
1614
+ return /** @type {T|undefined} */ (
1615
+ allowed.includes(/** @type {any} */ (s)) ? s : undefined
1616
+ );
1617
+ }
1618
+
1619
+ /**
1620
+ * enum のカンマ区切り複数指定を解釈する
1621
+ * - 未指定(null/undefined/空文字)の場合は defaultValue を返す
1622
+ * - 空要素は無視
1623
+ * - allowed に含まれないものは除外
1624
+ *
1625
+ * @template {string} T
1626
+ * @param {string|T[]|undefined|null} v
1627
+ * @param {readonly T[]} allowed
1628
+ * @param {T[]} [defaultValue]
1629
+ * @returns {T[]|undefined}
1630
+ */
1631
+ function parseDatasetEnumList(v, allowed, defaultValue) {
1632
+ if (v === null || v === undefined) { return defaultValue; }
1633
+
1634
+ // JSオプションで配列直渡しも許可
1635
+ if (Array.isArray(v)) {
1636
+ const result = v.filter(
1637
+ /** @returns {x is T} */
1638
+ (x) => allowed.includes(/** @type {any} */ (x))
1639
+ );
1640
+ return result;
1641
+ }
1642
+
1406
1643
  const s = String(v).trim();
1407
- if (s === "") { return; }
1408
- // 大文字小文字を区別したいならここを変える(今は厳密一致)
1409
- return /** @type {T|undefined} */ (allowed.includes(/** @type {any} */ (s)) ? s : undefined);
1644
+ if (s === "") { return defaultValue; }
1645
+
1646
+ const list = s
1647
+ .split(",")
1648
+ .map((x) => x.trim())
1649
+ .filter(Boolean);
1650
+
1651
+ const result = list.filter(
1652
+ /** @returns {x is T} */
1653
+ (x) => allowed.includes(/** @type {any} */ (x))
1654
+ );
1655
+
1656
+ return /** @type {T[]} */ (result);
1410
1657
  }
1411
1658
 
1412
1659
  /**
@@ -1613,6 +1860,7 @@ class InputGuardAutoAttach {
1613
1860
  * @returns {Rule}
1614
1861
  */
1615
1862
  function numeric(options = {}) {
1863
+ /** @type {NumericRuleOptions} */
1616
1864
  const opt = {
1617
1865
  allowFullWidth: options.allowFullWidth ?? true,
1618
1866
  allowMinus: options.allowMinus ?? false,
@@ -2051,6 +2299,7 @@ function roundFraction(intPart, fracPart, fracLimit) {
2051
2299
  * @returns {Rule}
2052
2300
  */
2053
2301
  function digits(options = {}) {
2302
+ /** @type {DigitsRuleOptions} */
2054
2303
  const opt = {
2055
2304
  int: typeof options.int === "number" ? options.int : undefined,
2056
2305
  frac: typeof options.frac === "number" ? options.frac : undefined,
@@ -2376,6 +2625,4188 @@ comma.fromDataset = function fromDataset(dataset, _el) {
2376
2625
  return comma();
2377
2626
  };
2378
2627
 
2628
+ /**
2629
+ * The script is part of Mojix for TextInputGuard.
2630
+ *
2631
+ * AUTHOR:
2632
+ * natade-jp (https://github.com/natade-jp)
2633
+ *
2634
+ * LICENSE:
2635
+ * The MIT license https://opensource.org/licenses/MIT
2636
+ */
2637
+
2638
+ /**
2639
+ * 制御文字マップ
2640
+ * @type {Record<number, string>}
2641
+ * @ignore
2642
+ */
2643
+ let control_charcter_map = null;
2644
+
2645
+ /**
2646
+ * コードポイントからUnicodeのブロック名に変換する
2647
+ * @type {(codepoint: number) => (string)}
2648
+ * @ignore
2649
+ */
2650
+ let toBlockNameFromUnicode = null;
2651
+
2652
+ /**
2653
+ * コードポイントから異体字セレクタの判定をする
2654
+ * @type {(codepoint: number, annotate?: boolean) => (string|null)}
2655
+ * @ignore
2656
+ */
2657
+ let getVariationSelectorsnumberFromCodePoint = null;
2658
+
2659
+ /**
2660
+ * コードポイントからタグ文字の判定をする
2661
+ * @type {(codepoint: number) => (string|null)}
2662
+ * @ignore
2663
+ */
2664
+ let getTagCharacterFromCodePoint = null;
2665
+
2666
+ /**
2667
+ * Unicode を扱うクラス
2668
+ * @ignore
2669
+ */
2670
+ class Unicode {
2671
+ /**
2672
+ * 初期化
2673
+ */
2674
+ static init() {
2675
+ if (Unicode.is_initmap) {
2676
+ return;
2677
+ }
2678
+ Unicode.is_initmap = true;
2679
+
2680
+ /**
2681
+ * 制御文字、VS、タグ文字は多いため含めていない
2682
+ */
2683
+ // prettier-ignore
2684
+ control_charcter_map = {
2685
+ // --- C0 control characters (ASCII 0x00–0x1F) ---
2686
+ 0: "NUL", // Null
2687
+ 1: "SOH", // Start of Heading
2688
+ 2: "STX", // Start of Text
2689
+ 3: "ETX", // End of Text
2690
+ 4: "EOT", // End of Transmission
2691
+ 5: "ENQ", // Enquiry
2692
+ 6: "ACK", // Acknowledge
2693
+ 7: "BEL", // Bell (beep)
2694
+
2695
+ 8: "BS", // Backspace
2696
+ 9: "HT", // Horizontal Tab
2697
+ 10: "LF", // Line Feed
2698
+ 11: "VT", // Vertical Tab
2699
+ 12: "FF", // Form Feed
2700
+ 13: "CR", // Carriage Return
2701
+ 14: "SO", // Shift Out
2702
+ 15: "SI", // Shift In
2703
+
2704
+ 16: "DLE", // Data Link Escape
2705
+ 17: "DC1", // Device Control 1 (XON)
2706
+ 18: "DC2", // Device Control 2
2707
+ 19: "DC3", // Device Control 3 (XOFF)
2708
+ 20: "DC4", // Device Control 4
2709
+ 21: "NAK", // Negative Acknowledge
2710
+ 22: "SYN", // Synchronous Idle
2711
+ 23: "ETB", // End of Transmission Block
2712
+
2713
+ 24: "CAN", // Cancel
2714
+ 25: "EM", // End of Medium
2715
+ 26: "SUB", // Substitute
2716
+ 27: "ESC", // Escape
2717
+ 28: "FS", // File Separator
2718
+ 29: "GS", // Group Separator
2719
+ 30: "RS", // Record Separator
2720
+ 31: "US", // Unit Separator
2721
+
2722
+ // --- DEL ---
2723
+ 127: "DEL", // Delete
2724
+
2725
+ // --- C1 control characters (ISO/IEC 6429, 0x80–0x9F) ---
2726
+ 128: "PAD", // Padding Character
2727
+ 129: "HOP", // High Octet Preset
2728
+ 130: "BPH", // Break Permitted Here
2729
+ 131: "NBH", // No Break Here
2730
+ 132: "IND", // Index
2731
+ 133: "NEL", // Next Line
2732
+ 134: "SSA", // Start of Selected Area
2733
+ 135: "ESA", // End of Selected Area
2734
+ 136: "HTS", // Horizontal Tab Set
2735
+ 137: "HTJ", // Horizontal Tab with Justification
2736
+ 138: "VTS", // Vertical Tab Set
2737
+ 139: "PLD", // Partial Line Down
2738
+ 140: "PLU", // Partial Line Up
2739
+ 141: "RI", // Reverse Index
2740
+ 142: "SS2", // Single Shift 2
2741
+ 143: "SS3", // Single Shift 3
2742
+ 144: "DCS", // Device Control String
2743
+ 145: "PU1", // Private Use 1
2744
+ 146: "PU2", // Private Use 2
2745
+ 147: "STS", // Set Transmit State
2746
+ 148: "CCH", // Cancel Character
2747
+ 149: "MW", // Message Waiting
2748
+ 150: "SPA", // Start of Protected Area
2749
+ 151: "EPA", // End of Protected Area
2750
+ 152: "SOS", // Start of String
2751
+ 153: "SGCI", // Single Graphic Character Introducer
2752
+ 154: "SCI", // Single Character Introducer
2753
+ 155: "CSI", // Control Sequence Introducer
2754
+ 156: "ST", // String Terminator
2755
+ 157: "OSC", // Operating System Command
2756
+ 158: "PM", // Privacy Message
2757
+ 159: "APC", // Application Program Command
2758
+
2759
+ // --- Unicode but制御的に扱われる文字 ---
2760
+ 160: "NBSP", // No-Break Space(表示は空白だが改行不可)
2761
+ 173: "SHY", // Soft Hyphen(通常は表示されない)
2762
+
2763
+ // --- Unicode Interlinear Annotation ---
2764
+ 65529: "IAA", // Interlinear Annotation Anchor
2765
+ 65530: "IAS", // Interlinear Annotation Separator
2766
+ 65531: "IAT", // Interlinear Annotation Terminator
2767
+
2768
+ // Zero Width / Joiner 系(Cf)
2769
+ 0x200B: "ZWSP", // ZERO WIDTH SPACE ゼロ幅スペース
2770
+ 0x200C: "ZWNJ", // ZERO WIDTH NON-JOINER ゼロ幅非接合子
2771
+ 0x200D: "ZWJ", // ZERO WIDTH JOINER ゼロ幅接合子
2772
+ 0x2060: "WJ", // WORD JOINER 単語結合子
2773
+ 0xFEFF: "BOM", // BYTE ORDER MARK / ZERO WIDTH NO-BREAK SPACE
2774
+
2775
+ // 双方向(BiDi)制御文字
2776
+ 0x202A: "LRE", // LEFT-TO-RIGHT EMBEDDING
2777
+ 0x202B: "RLE", // RIGHT-TO-LEFT EMBEDDING
2778
+ 0x202C: "PDF", // POP DIRECTIONAL FORMATTING
2779
+ 0x202D: "LRO", // LEFT-TO-RIGHT OVERRIDE
2780
+ 0x202E: "RLO", // RIGHT-TO-LEFT OVERRIDE
2781
+
2782
+ 0x2066: "LRI", // LEFT-TO-RIGHT ISOLATE
2783
+ 0x2067: "RLI", // RIGHT-TO-LEFT ISOLATE
2784
+ 0x2068: "FSI", // FIRST STRONG ISOLATE
2785
+ 0x2069: "PDI", // POP DIRECTIONAL ISOLATE
2786
+
2787
+ // Unicode Noncharacter(検証・防御用途)
2788
+ 0xFFFE: "NONCHAR_FFFE",
2789
+ 0xFFFF: "NONCHAR_FFFF"
2790
+ };
2791
+
2792
+ // prettier-ignore
2793
+ const unicode_blockname_array = [
2794
+ "Basic Latin", "Latin-1 Supplement", "Latin Extended-A", "Latin Extended-B", "IPA Extensions", "Spacing Modifier Letters", "Combining Diacritical Marks", "Greek and Coptic",
2795
+ "Cyrillic", "Cyrillic Supplement", "Armenian", "Hebrew", "Arabic", "Syriac", "Arabic Supplement", "Thaana",
2796
+ "NKo", "Samaritan", "Mandaic", "Syriac Supplement", "Arabic Extended-B", "Arabic Extended-A", "Devanagari", "Bengali",
2797
+ "Gurmukhi", "Gujarati", "Oriya", "Tamil", "Telugu", "Kannada", "Malayalam", "Sinhala",
2798
+ "Thai", "Lao", "Tibetan", "Myanmar", "Georgian", "Hangul Jamo", "Ethiopic", "Ethiopic Supplement",
2799
+ "Cherokee", "Unified Canadian Aboriginal Syllabics", "Ogham", "Runic", "Tagalog", "Hanunoo", "Buhid", "Tagbanwa",
2800
+ "Khmer", "Mongolian", "Unified Canadian Aboriginal Syllabics Extended", "Limbu", "Tai Le", "New Tai Lue", "Khmer Symbols", "Buginese",
2801
+ "Tai Tham", "Combining Diacritical Marks Extended", "Balinese", "Sundanese", "Batak", "Lepcha", "Ol Chiki", "Cyrillic Extended-C",
2802
+ "Georgian Extended", "Sundanese Supplement", "Vedic Extensions", "Phonetic Extensions", "Phonetic Extensions Supplement", "Combining Diacritical Marks Supplement", "Latin Extended Additional", "Greek Extended",
2803
+ "General Punctuation", "Superscripts and Subscripts", "Currency Symbols", "Combining Diacritical Marks for Symbols", "Letterlike Symbols", "Number Forms", "Arrows", "Mathematical Operators",
2804
+ "Miscellaneous Technical", "Control Pictures", "Optical Character Recognition", "Enclosed Alphanumerics", "Box Drawing", "Block Elements", "Geometric Shapes", "Miscellaneous Symbols",
2805
+ "Dingbats", "Miscellaneous Mathematical Symbols-A", "Supplemental Arrows-A", "Braille Patterns", "Supplemental Arrows-B", "Miscellaneous Mathematical Symbols-B", "Supplemental Mathematical Operators", "Miscellaneous Symbols and Arrows",
2806
+ "Glagolitic", "Latin Extended-C", "Coptic", "Georgian Supplement", "Tifinagh", "Ethiopic Extended", "Cyrillic Extended-A", "Supplemental Punctuation",
2807
+ "CJK Radicals Supplement", "Kangxi Radicals", "Ideographic Description Characters", "CJK Symbols and Punctuation", "Hiragana", "Katakana", "Bopomofo", "Hangul Compatibility Jamo",
2808
+ "Kanbun", "Bopomofo Extended", "CJK Strokes", "Katakana Phonetic Extensions", "Enclosed CJK Letters and Months", "CJK Compatibility", "CJK Unified Ideographs Extension A", "Yijing Hexagram Symbols",
2809
+ "CJK Unified Ideographs", "Yi Syllables", "Yi Radicals", "Lisu", "Vai", "Cyrillic Extended-B", "Bamum", "Modifier Tone Letters",
2810
+ "Latin Extended-D", "Syloti Nagri", "Common Indic Number Forms", "Phags-pa", "Saurashtra", "Devanagari Extended", "Kayah Li", "Rejang",
2811
+ "Hangul Jamo Extended-A", "Javanese", "Myanmar Extended-B", "Cham", "Myanmar Extended-A", "Tai Viet", "Meetei Mayek Extensions", "Ethiopic Extended-A",
2812
+ "Latin Extended-E", "Cherokee Supplement", "Meetei Mayek", "Hangul Syllables", "Hangul Jamo Extended-B", "High Surrogates", "High Private Use Surrogates", "Low Surrogates",
2813
+ "Private Use Area", "CJK Compatibility Ideographs", "Alphabetic Presentation Forms", "Arabic Presentation Forms-A", "Variation Selectors", "Vertical Forms", "Combining Half Marks", "CJK Compatibility Forms",
2814
+ "Small Form Variants", "Arabic Presentation Forms-B", "Halfwidth and Fullwidth Forms", "Specials", "Linear B Syllabary", "Linear B Ideograms", "Aegean Numbers", "Ancient Greek Numbers",
2815
+ "Ancient Symbols", "Phaistos Disc", "Lycian", "Carian", "Coptic Epact Numbers", "Old Italic", "Gothic", "Old Permic",
2816
+ "Ugaritic", "Old Persian", "Deseret", "Shavian", "Osmanya", "Osage", "Elbasan", "Caucasian Albanian",
2817
+ "Vithkuqi", "Linear A", "Latin Extended-F", "Cypriot Syllabary", "Imperial Aramaic", "Palmyrene", "Nabataean", "Hatran",
2818
+ "Phoenician", "Lydian", "Meroitic Hieroglyphs", "Meroitic Cursive", "Kharoshthi", "Old South Arabian", "Old North Arabian", "Manichaean",
2819
+ "Avestan", "Inscriptional Parthian", "Inscriptional Pahlavi", "Psalter Pahlavi", "Old Turkic", "Old Hungarian", "Hanifi Rohingya", "Rumi Numeral Symbols",
2820
+ "Yezidi", "Arabic Extended-C", "Old Sogdian", "Sogdian", "Old Uyghur", "Chorasmian", "Elymaic", "Brahmi",
2821
+ "Kaithi", "Sora Sompeng", "Chakma", "Mahajani", "Sharada", "Sinhala Archaic Numbers", "Khojki", "Multani",
2822
+ "Khudawadi", "Grantha", "Newa", "Tirhuta", "Siddham", "Modi", "Mongolian Supplement", "Takri",
2823
+ "Ahom", "Dogra", "Warang Citi", "Dives Akuru", "Nandinagari", "Zanabazar Square", "Soyombo", "Unified Canadian Aboriginal Syllabics Extended-A",
2824
+ "Pau Cin Hau", "Devanagari Extended-A", "Bhaiksuki", "Marchen", "Masaram Gondi", "Gunjala Gondi", "Makasar", "Kawi",
2825
+ "Lisu Supplement", "Tamil Supplement", "Cuneiform", "Cuneiform Numbers and Punctuation", "Early Dynastic Cuneiform", "Cypro-Minoan", "Egyptian Hieroglyphs", "Egyptian Hieroglyph Format Controls",
2826
+ "Anatolian Hieroglyphs", "Bamum Supplement", "Mro", "Tangsa", "Bassa Vah", "Pahawh Hmong", "Medefaidrin", "Miao",
2827
+ "Ideographic Symbols and Punctuation", "Tangut", "Tangut Components", "Khitan Small Script", "Tangut Supplement", "Kana Extended-B", "Kana Supplement", "Kana Extended-A",
2828
+ "Small Kana Extension", "Nushu", "Duployan", "Shorthand Format Controls", "Znamenny Musical Notation", "Byzantine Musical Symbols", "Musical Symbols", "Ancient Greek Musical Notation",
2829
+ "Kaktovik Numerals", "Mayan Numerals", "Tai Xuan Jing Symbols", "Counting Rod Numerals", "Mathematical Alphanumeric Symbols", "Sutton SignWriting", "Latin Extended-G", "Glagolitic Supplement",
2830
+ "Cyrillic Extended-D", "Nyiakeng Puachue Hmong", "Toto", "Wancho", "Nag Mundari", "Ethiopic Extended-B", "Mende Kikakui", "Adlam",
2831
+ "Indic Siyaq Numbers", "Ottoman Siyaq Numbers", "Arabic Mathematical Alphabetic Symbols", "Mahjong Tiles", "Domino Tiles", "Playing Cards", "Enclosed Alphanumeric Supplement", "Enclosed Ideographic Supplement",
2832
+ "Miscellaneous Symbols and Pictographs", "Emoticons", "Ornamental Dingbats", "Transport and Map Symbols", "Alchemical Symbols", "Geometric Shapes Extended", "Supplemental Arrows-C", "Supplemental Symbols and Pictographs",
2833
+ "Chess Symbols", "Symbols and Pictographs Extended-A", "Symbols for Legacy Computing", "CJK Unified Ideographs Extension B", "CJK Unified Ideographs Extension C", "CJK Unified Ideographs Extension D", "CJK Unified Ideographs Extension E", "CJK Unified Ideographs Extension F", "CJK Unified Ideographs Extension I",
2834
+ "CJK Compatibility Ideographs Supplement", "CJK Unified Ideographs Extension G", "CJK Unified Ideographs Extension H", "CJK Unified Ideographs Extension J", "Tags", "Variation Selectors Supplement", "Supplementary Private Use Area-A", "Supplementary Private Use Area-B"
2835
+ ];
2836
+
2837
+ /* eslint-disable max-len */
2838
+ // prettier-ignore
2839
+ const unicode_blockaddress_array = [
2840
+ 0x007F, 0x00FF, 0x017F, 0x024F, 0x02AF, 0x02FF, 0x036F, 0x03FF, 0x04FF, 0x052F, 0x058F, 0x05FF, 0x06FF, 0x074F, 0x077F, 0x07BF,
2841
+ 0x07FF, 0x083F, 0x085F, 0x086F, 0x089F, 0x08FF, 0x097F, 0x09FF, 0x0A7F, 0x0AFF, 0x0B7F, 0x0BFF, 0x0C7F, 0x0CFF, 0x0D7F, 0x0DFF,
2842
+ 0x0E7F, 0x0EFF, 0x0FFF, 0x109F, 0x10FF, 0x11FF, 0x137F, 0x139F, 0x13FF, 0x167F, 0x169F, 0x16FF, 0x171F, 0x173F, 0x175F, 0x177F,
2843
+ 0x17FF, 0x18AF, 0x18FF, 0x194F, 0x197F, 0x19DF, 0x19FF, 0x1A1F, 0x1AAF, 0x1AFF, 0x1B7F, 0x1BBF, 0x1BFF, 0x1C4F, 0x1C7F, 0x1C8F,
2844
+ 0x1CBF, 0x1CCF, 0x1CFF, 0x1D7F, 0x1DBF, 0x1DFF, 0x1EFF, 0x1FFF, 0x206F, 0x209F, 0x20CF, 0x20FF, 0x214F, 0x218F, 0x21FF, 0x22FF,
2845
+ 0x23FF, 0x243F, 0x245F, 0x24FF, 0x257F, 0x259F, 0x25FF, 0x26FF, 0x27BF, 0x27EF, 0x27FF, 0x28FF, 0x297F, 0x29FF, 0x2AFF, 0x2BFF,
2846
+ 0x2C5F, 0x2C7F, 0x2CFF, 0x2D2F, 0x2D7F, 0x2DDF, 0x2DFF, 0x2E7F, 0x2EFF, 0x2FDF, 0x2FFF, 0x303F, 0x309F, 0x30FF, 0x312F, 0x318F,
2847
+ 0x319F, 0x31BF, 0x31EF, 0x31FF, 0x32FF, 0x33FF, 0x4DBF, 0x4DFF, 0x9FFF, 0xA48F, 0xA4CF, 0xA4FF, 0xA63F, 0xA69F, 0xA6FF, 0xA71F,
2848
+ 0xA7FF, 0xA82F, 0xA83F, 0xA87F, 0xA8DF, 0xA8FF, 0xA92F, 0xA95F, 0xA97F, 0xA9DF, 0xA9FF, 0xAA5F, 0xAA7F, 0xAADF, 0xAAFF, 0xAB2F,
2849
+ 0xAB6F, 0xABBF, 0xABFF, 0xD7AF, 0xD7FF, 0xDB7F, 0xDBFF, 0xDFFF, 0xF8FF, 0xFAFF, 0xFB4F, 0xFDFF, 0xFE0F, 0xFE1F, 0xFE2F, 0xFE4F,
2850
+ 0xFE6F, 0xFEFF, 0xFFEF, 0xFFFF, 0x1007F, 0x100FF, 0x1013F, 0x1018F, 0x101CF, 0x101FF, 0x1029F, 0x102DF, 0x102FF, 0x1032F, 0x1034F, 0x1037F,
2851
+ 0x1039F, 0x103DF, 0x1044F, 0x1047F, 0x104AF, 0x104FF, 0x1052F, 0x1056F, 0x105BF, 0x1077F, 0x107BF, 0x1083F, 0x1085F, 0x1087F, 0x108AF, 0x108FF,
2852
+ 0x1091F, 0x1093F, 0x1099F, 0x109FF, 0x10A5F, 0x10A7F, 0x10A9F, 0x10AFF, 0x10B3F, 0x10B5F, 0x10B7F, 0x10BAF, 0x10C4F, 0x10CFF, 0x10D3F, 0x10E7F,
2853
+ 0x10EBF, 0x10EFF, 0x10F2F, 0x10F6F, 0x10FAF, 0x10FDF, 0x10FFF, 0x1107F, 0x110CF, 0x110FF, 0x1114F, 0x1117F, 0x111DF, 0x111FF, 0x1124F, 0x112AF,
2854
+ 0x112FF, 0x1137F, 0x1147F, 0x114DF, 0x115FF, 0x1165F, 0x1167F, 0x116CF, 0x1174F, 0x1184F, 0x118FF, 0x1195F, 0x119FF, 0x11A4F, 0x11AAF, 0x11ABF,
2855
+ 0x11AFF, 0x11B5F, 0x11C6F, 0x11CBF, 0x11D5F, 0x11DAF, 0x11EFF, 0x11F5F, 0x11FBF, 0x11FFF, 0x123FF, 0x1247F, 0x1254F, 0x12FFF, 0x1342F, 0x1345F,
2856
+ 0x1467F, 0x16A3F, 0x16A6F, 0x16ACF, 0x16AFF, 0x16B8F, 0x16E9F, 0x16F9F, 0x16FFF, 0x187FF, 0x18AFF, 0x18CFF, 0x18D7F, 0x1AFFF, 0x1B0FF, 0x1B12F,
2857
+ 0x1B16F, 0x1B2FF, 0x1BC9F, 0x1BCAF, 0x1CFCF, 0x1D0FF, 0x1D1FF, 0x1D24F, 0x1D2DF, 0x1D2FF, 0x1D35F, 0x1D37F, 0x1D7FF, 0x1DAAF, 0x1DFFF, 0x1E02F,
2858
+ 0x1E08F, 0x1E14F, 0x1E2BF, 0x1E2FF, 0x1E4FF, 0x1E7FF, 0x1E8DF, 0x1E95F, 0x1ECBF, 0x1ED4F, 0x1EEFF, 0x1F02F, 0x1F09F, 0x1F0FF, 0x1F1FF, 0x1F2FF,
2859
+ 0x1F5FF, 0x1F64F, 0x1F67F, 0x1F6FF, 0x1F77F, 0x1F7FF, 0x1F8FF, 0x1F9FF, 0x1FA6F, 0x1FAFF, 0x1FBFF, 0x2A6DF, 0x2B73F, 0x2B81F, 0x2CEAF, 0x2EBEF, 0x2EE5F,
2860
+ 0x2FA1F, 0x3134F, 0x323AF, 0x3347F, 0xE007F, 0xE01EF, 0xFFFFF, 0x10FFFF
2861
+ ];
2862
+ /* eslint-enable max-len */
2863
+
2864
+ /**
2865
+ * コードポイントからUnicodeのブロック名に変換する
2866
+ * 変換できない場合は "-" を返す
2867
+ * @param {number} codepoint - コードポイント
2868
+ * @returns {string}
2869
+ */
2870
+ toBlockNameFromUnicode = function (codepoint) {
2871
+ for (let i = 0; i < unicode_blockname_array.length; i++) {
2872
+ if (codepoint <= unicode_blockaddress_array[i]) {
2873
+ return unicode_blockname_array[i];
2874
+ }
2875
+ }
2876
+ return "-";
2877
+ };
2878
+
2879
+ /**
2880
+ * コードポイントから異体字セレクタの判定
2881
+ * @param {number} codepoint - コードポイント
2882
+ * @param {boolean} [annotate = false] - 注釈をつけるか否か
2883
+ * @returns {string|null} 確認結果(異体字セレクタではない場合はNULLを返す)
2884
+ */
2885
+ getVariationSelectorsnumberFromCodePoint = function (codepoint, annotate) {
2886
+ // prettier-ignore
2887
+ if (0x180B <= codepoint && codepoint <= 0x180D) {
2888
+ // モンゴル自由字形選択子 U+180B〜U+180D (3個)
2889
+ // prettier-ignore
2890
+ return "FVS" + (codepoint - 0x180B + 1);
2891
+ }
2892
+ // prettier-ignore
2893
+ if (0xFE00 <= codepoint && codepoint <= 0xFE0F) {
2894
+ // SVSで利用される異体字セレクタ U+FE00〜U+FE0F (VS1~VS16) (16個)
2895
+ // prettier-ignore
2896
+ const n = codepoint - 0xFE00 + 1;
2897
+ if (!annotate) { return "VS" + n; }
2898
+ // prettier-ignore
2899
+ if (codepoint === 0xFE0E) { return "VS15 (text)"; }
2900
+ // prettier-ignore
2901
+ if (codepoint === 0xFE0F) { return "VS16 (emoji)"; }
2902
+ return "VS" + n;
2903
+ // prettier-ignore
2904
+ } else if (0xE0100 <= codepoint && codepoint <= 0xE01EF) {
2905
+ // IVSで利用される異体字セレクタ U+E0100〜U+E01EF (VS17~VS256) (240個)
2906
+ // prettier-ignore
2907
+ return "VS" + (codepoint - 0xE0100 + 17);
2908
+ }
2909
+ return null;
2910
+ };
2911
+
2912
+ /**
2913
+ * コードポイントからタグ文字の判定
2914
+ * @param {number} codepoint - コードポイント
2915
+ * @returns {string|null} 確認結果(タグ文字ではない場合はNULLを返す)
2916
+ */
2917
+ getTagCharacterFromCodePoint = function (codepoint) {
2918
+ // TAG characters U+E0020..U+E007F
2919
+ // prettier-ignore
2920
+ if (0xE0020 <= codepoint && codepoint <= 0xE007F) {
2921
+ // CANCEL TAG
2922
+ // prettier-ignore
2923
+ if (codepoint === 0xE007F) {
2924
+ return "CANCEL_TAG";
2925
+ }
2926
+ // TAG_20..TAG_7E のように返す
2927
+ // prettier-ignore
2928
+ const ascii = codepoint - 0xE0000; // 0x20..0x7E
2929
+ return "TAG_" + ascii.toString(16).toUpperCase().padStart(2, "0");
2930
+ }
2931
+ return null;
2932
+ };
2933
+ }
2934
+
2935
+ /**
2936
+ * 上位のサロゲートペアの判定
2937
+ * @param {string} text - 対象テキスト
2938
+ * @param {number} index - インデックス
2939
+ * @returns {boolean} 確認結果
2940
+ */
2941
+ static isHighSurrogateAt(text, index) {
2942
+ const ch = text.charCodeAt(index);
2943
+ // prettier-ignore
2944
+ return 0xD800 <= ch && ch <= 0xDBFF;
2945
+ }
2946
+
2947
+ /**
2948
+ * 下位のサロゲートペアの判定
2949
+ * @param {string} text - 対象テキスト
2950
+ * @param {number} index - インデックス
2951
+ * @returns {boolean} 確認結果
2952
+ */
2953
+ static isLowSurrogateAt(text, index) {
2954
+ const ch = text.charCodeAt(index);
2955
+ // prettier-ignore
2956
+ return 0xDC00 <= ch && ch <= 0xDFFF;
2957
+ }
2958
+
2959
+ /**
2960
+ * サロゲートペアの判定
2961
+ * @param {string} text - 対象テキスト
2962
+ * @param {number} index - インデックス
2963
+ * @returns {boolean} 確認結果
2964
+ */
2965
+ static isSurrogatePairAt(text, index) {
2966
+ const ch = text.charCodeAt(index);
2967
+ // prettier-ignore
2968
+ return 0xD800 <= ch && ch <= 0xDFFF;
2969
+ }
2970
+
2971
+ /**
2972
+ * サロゲートペア対応のコードポイント取得
2973
+ * @param {string} text - 対象テキスト
2974
+ * @param {number} [index = 0] - インデックス
2975
+ * @returns {number} コードポイント
2976
+ */
2977
+ static codePointAt(text, index) {
2978
+ const index_ = index !== undefined ? index : 0;
2979
+ if (Unicode.isHighSurrogateAt(text, index_)) {
2980
+ const high = text.charCodeAt(index_);
2981
+ const low = text.charCodeAt(index_ + 1);
2982
+ // prettier-ignore
2983
+ return (((high - 0xD800) << 10) | (low - 0xDC00)) + 0x10000;
2984
+ } else {
2985
+ return text.charCodeAt(index_);
2986
+ }
2987
+ }
2988
+
2989
+ /**
2990
+ * インデックスの前にあるコードポイント
2991
+ * @param {string} text - 対象テキスト
2992
+ * @param {number} index - インデックス
2993
+ * @returns {number} コードポイント
2994
+ */
2995
+ static codePointBefore(text, index) {
2996
+ if (!Unicode.isLowSurrogateAt(text, index - 1)) {
2997
+ return text.charCodeAt(index - 1);
2998
+ } else {
2999
+ return text.codePointAt(index - 2);
3000
+ }
3001
+ }
3002
+
3003
+ /**
3004
+ * コードポイント換算で文字列数をカウント
3005
+ * @param {string} text - 対象テキスト
3006
+ * @param {number} [beginIndex=0] - 最初のインデックス(省略可)
3007
+ * @param {number} [endIndex] - 最後のインデックス(ここは含めない)(省略可)
3008
+ * @returns {number} 文字数
3009
+ */
3010
+ static codePointCount(text, beginIndex, endIndex) {
3011
+ if (beginIndex === undefined) {
3012
+ beginIndex = 0;
3013
+ }
3014
+ if (endIndex === undefined) {
3015
+ endIndex = text.length;
3016
+ }
3017
+ let count = 0;
3018
+ for (; beginIndex < endIndex; beginIndex++) {
3019
+ count++;
3020
+ if (Unicode.isSurrogatePairAt(text, beginIndex)) {
3021
+ beginIndex++;
3022
+ }
3023
+ }
3024
+ return count;
3025
+ }
3026
+
3027
+ /**
3028
+ * コードポイント換算で文字列配列の位置を計算
3029
+ * @param {string} text - 対象テキスト
3030
+ * @param {number} index - オフセット
3031
+ * @param {number} codePointOffset - ずらすコードポイント数
3032
+ * @returns {number} ずらしたインデックス
3033
+ */
3034
+ static offsetByCodePoints(text, index, codePointOffset) {
3035
+ let count = 0;
3036
+ if (codePointOffset === 0) {
3037
+ return index;
3038
+ }
3039
+ if (codePointOffset > 0) {
3040
+ for (; index < text.length; index++) {
3041
+ count++;
3042
+ if (Unicode.isHighSurrogateAt(text, index)) {
3043
+ index++;
3044
+ }
3045
+ if (count === codePointOffset) {
3046
+ return index + 1;
3047
+ }
3048
+ }
3049
+ } else {
3050
+ codePointOffset = -codePointOffset;
3051
+ for (; index >= 0; index--) {
3052
+ count++;
3053
+ if (Unicode.isLowSurrogateAt(text, index - 1)) {
3054
+ index--;
3055
+ }
3056
+ if (count === codePointOffset) {
3057
+ return index - 1;
3058
+ }
3059
+ }
3060
+ }
3061
+ throw "error offsetByCodePoints";
3062
+ }
3063
+
3064
+ /**
3065
+ * コードポイントの数値データをUTF16の配列に変換
3066
+ * @param {...(number|number[])} codepoint - 変換したいUTF-32の配列、又はコードポイントを並べた可変引数
3067
+ * @returns {number[]} 変換後のテキスト
3068
+ */
3069
+ static toUTF16ArrayFromCodePoint() {
3070
+ /**
3071
+ * @type {number[]}
3072
+ */
3073
+ const utf16_array = [];
3074
+ /**
3075
+ * @type {number[]}
3076
+ */
3077
+ let codepoint_array = [];
3078
+ if (arguments[0].length) {
3079
+ codepoint_array = arguments[0];
3080
+ } else {
3081
+ for (let i = 0; i < arguments.length; i++) {
3082
+ codepoint_array[i] = arguments[i];
3083
+ }
3084
+ }
3085
+ for (let i = 0; i < codepoint_array.length; i++) {
3086
+ const codepoint = codepoint_array[i];
3087
+ if (0x10000 <= codepoint) {
3088
+ // prettier-ignore
3089
+ const high = ((codepoint - 0x10000) >> 10) + 0xD800;
3090
+ // prettier-ignore
3091
+ const low = (codepoint & 0x3FF) + 0xDC00;
3092
+ utf16_array.push(high);
3093
+ utf16_array.push(low);
3094
+ } else {
3095
+ utf16_array.push(codepoint);
3096
+ }
3097
+ }
3098
+ return utf16_array;
3099
+ }
3100
+
3101
+ /**
3102
+ * コードポイントの数値データを文字列に変換
3103
+ * @param {...(number|number[])} codepoint - 変換したいコードポイントの数値配列、又は数値を並べた可変引数
3104
+ * @returns {string} 変換後のテキスト
3105
+ */
3106
+ static fromCodePoint(codepoint) {
3107
+ /** @type {number[]} */
3108
+ let utf16_array;
3109
+ if (Array.isArray(codepoint)) {
3110
+ utf16_array = Unicode.toUTF16ArrayFromCodePoint(codepoint);
3111
+ } else {
3112
+ const codepoint_array = [];
3113
+ for (let i = 0; i < arguments.length; i++) {
3114
+ codepoint_array[i] = arguments[i];
3115
+ }
3116
+ utf16_array = Unicode.toUTF16ArrayFromCodePoint(codepoint_array);
3117
+ }
3118
+ const text = [];
3119
+ for (let i = 0; i < utf16_array.length; i++) {
3120
+ text[text.length] = String.fromCharCode(utf16_array[i]);
3121
+ }
3122
+ return text.join("");
3123
+ }
3124
+
3125
+ /**
3126
+ * 文字列をUTF32(コードポイント)の配列に変換
3127
+ * @param {string} text - 変換したいテキスト
3128
+ * @returns {number[]} UTF32(コードポイント)のデータが入った配列
3129
+ */
3130
+ static toUTF32Array(text) {
3131
+ const utf32 = [];
3132
+ for (let i = 0; i < text.length; i = Unicode.offsetByCodePoints(text, i, 1)) {
3133
+ utf32.push(Unicode.codePointAt(text, i));
3134
+ }
3135
+ return utf32;
3136
+ }
3137
+
3138
+ /**
3139
+ * UTF32の配列から文字列に変換
3140
+ * @param {number[]} utf32 - 変換したいテキスト
3141
+ * @returns {string} 変換後のテキスト
3142
+ */
3143
+ static fromUTF32Array(utf32) {
3144
+ return Unicode.fromCodePoint(utf32);
3145
+ }
3146
+
3147
+ /**
3148
+ * 文字列をUTF16の配列に変換
3149
+ * @param {string} text - 変換したいテキスト
3150
+ * @returns {number[]} UTF16のデータが入った配列
3151
+ */
3152
+ static toUTF16Array(text) {
3153
+ const utf16 = [];
3154
+ for (let i = 0; i < text.length; i++) {
3155
+ utf16[i] = text.charCodeAt(i);
3156
+ }
3157
+ return utf16;
3158
+ }
3159
+
3160
+ /**
3161
+ * UTF16の配列から文字列に変換
3162
+ * @param {number[]} utf16 - 変換したいテキスト
3163
+ * @returns {string} 変換後のテキスト
3164
+ */
3165
+ static fromUTF16Array(utf16) {
3166
+ const text = [];
3167
+ for (let i = 0; i < utf16.length; i++) {
3168
+ text[i] = String.fromCharCode(utf16[i]);
3169
+ }
3170
+ return text.join("");
3171
+ }
3172
+
3173
+ /**
3174
+ * 文字列をUTF8の配列に変換
3175
+ * @param {string} text - 変換したいテキスト
3176
+ * @returns {number[]} UTF8のデータが入った配列
3177
+ */
3178
+ static toUTF8Array(text) {
3179
+ return Unicode.toUTFBinaryFromCodePoint(Unicode.toUTF32Array(text), "utf-8", false);
3180
+ }
3181
+
3182
+ /**
3183
+ * UTF8の配列から文字列に変換
3184
+ * @param {number[]} utf8 - 変換したいテキスト
3185
+ * @returns {string} 変換後のテキスト
3186
+ */
3187
+ static fromUTF8Array(utf8) {
3188
+ return Unicode.fromCodePoint(Unicode.toCodePointFromUTFBinary(utf8, "utf-8"));
3189
+ }
3190
+
3191
+ /**
3192
+ * 指定したテキストを切り出す
3193
+ * - 単位は文字数
3194
+ * @param {string} text - 切り出したいテキスト
3195
+ * @param {number} offset - 切り出し位置
3196
+ * @param {number} size - 切り出す長さ
3197
+ * @returns {string} 切り出したテキスト
3198
+ */
3199
+ static cutTextForCodePoint(text, offset, size) {
3200
+ const utf32 = Unicode.toUTF32Array(text);
3201
+ const cut = [];
3202
+ for (let i = 0, point = offset; i < size && point < utf32.length; i++, point++) {
3203
+ cut.push(utf32[point]);
3204
+ }
3205
+ return Unicode.fromUTF32Array(cut);
3206
+ }
3207
+
3208
+ /**
3209
+ * UTFのバイナリ配列からバイトオーダーマーク(BOM)を調査する
3210
+ * @param {number[]} utfbinary - 調査するバイナリ配列
3211
+ * @returns {string} 符号化形式(不明時はnull)
3212
+ */
3213
+ static getCharsetFromBOM(utfbinary) {
3214
+ if (utfbinary.length >= 4) {
3215
+ // prettier-ignore
3216
+ if (utfbinary[0] === 0x00 && utfbinary[1] === 0x00 && utfbinary[2] === 0xFE && utfbinary[3] === 0xFF) {
3217
+ return "UTF-32BE";
3218
+ }
3219
+ // prettier-ignore
3220
+ if (utfbinary[0] === 0xFF && utfbinary[1] === 0xFE && utfbinary[2] === 0x00 && utfbinary[3] === 0x00) {
3221
+ return "UTF-32LE";
3222
+ }
3223
+ }
3224
+ if (utfbinary.length >= 3) {
3225
+ // prettier-ignore
3226
+ if (utfbinary[0] === 0xEF && utfbinary[1] === 0xBB && utfbinary[2] === 0xBF) {
3227
+ return "UTF-8";
3228
+ }
3229
+ }
3230
+ if (utfbinary.length >= 2) {
3231
+ // prettier-ignore
3232
+ if (utfbinary[0] === 0xFE && utfbinary[1] === 0xFF) {
3233
+ return "UTF-16BE";
3234
+ }
3235
+ // prettier-ignore
3236
+ if (utfbinary[0] === 0xFF && utfbinary[1] === 0xFE) {
3237
+ return "UTF-16LE";
3238
+ }
3239
+ }
3240
+ return null;
3241
+ }
3242
+
3243
+ /**
3244
+ * UTFのバイナリ配列からコードポイントに変換
3245
+ * @param {number[]} binary - 変換したいバイナリ配列
3246
+ * @param {string} [charset] - UTFの種類(省略した場合はBOM付きを期待する)
3247
+ * @returns {number[]} コードポイントの配列(失敗時はnull)
3248
+ */
3249
+ static toCodePointFromUTFBinary(binary, charset) {
3250
+ const utf32_array = [];
3251
+ let check_charset = charset;
3252
+ let offset = 0;
3253
+ // バイトオーダーマーク(BOM)がある場合は BOM を優先
3254
+ const charset_for_bom = Unicode.getCharsetFromBOM(binary);
3255
+ if (charset_for_bom) {
3256
+ check_charset = charset_for_bom;
3257
+ if (/utf-?8/i.test(charset_for_bom)) {
3258
+ offset = 3;
3259
+ } else if (/utf-?16/i.test(charset_for_bom)) {
3260
+ offset = 2;
3261
+ } else if (/utf-?32/i.test(charset_for_bom)) {
3262
+ offset = 4;
3263
+ }
3264
+ }
3265
+ // BOM付きではない+指定もしていないので変換失敗
3266
+ if (!charset_for_bom && !charset) {
3267
+ return null;
3268
+ }
3269
+ if (/utf-?8n?/i.test(check_charset)) {
3270
+ // UTF-8
3271
+ let size = 0;
3272
+ let write = 0;
3273
+ for (let i = offset; i < binary.length; i++) {
3274
+ const bin = binary[i];
3275
+ if (size === 0) {
3276
+ if (bin < 0x80) {
3277
+ utf32_array.push(bin);
3278
+ // prettier-ignore
3279
+ } else if (bin < 0xE0) {
3280
+ size = 1;
3281
+ // prettier-ignore
3282
+ write = bin & 0x1F; // 0001 1111
3283
+ // prettier-ignore
3284
+ } else if (bin < 0xF0) {
3285
+ size = 2;
3286
+ // prettier-ignore
3287
+ write = bin & 0xF; // 0000 1111
3288
+ } else {
3289
+ size = 3;
3290
+ // prettier-ignore
3291
+ write = bin & 0x7; // 0000 0111
3292
+ }
3293
+ } else {
3294
+ write <<= 6;
3295
+ // prettier-ignore
3296
+ write |= bin & 0x3F; // 0011 1111
3297
+ size--;
3298
+ if (size === 0) {
3299
+ utf32_array.push(write);
3300
+ }
3301
+ }
3302
+ }
3303
+ return utf32_array;
3304
+ } else if (/utf-?16/i.test(check_charset)) {
3305
+ // UTF-16
3306
+ // UTF-16 につめる
3307
+ const utf16 = [];
3308
+ if (/utf-?16(be)/i.test(check_charset)) {
3309
+ // UTF-16BE
3310
+ for (let i = offset; i < binary.length; i += 2) {
3311
+ utf16.push((binary[i] << 8) | binary[i + 1]);
3312
+ }
3313
+ } else if (/utf-?16(le)?/i.test(check_charset)) {
3314
+ // UTF-16LE
3315
+ for (let i = offset; i < binary.length; i += 2) {
3316
+ utf16.push(binary[i] | (binary[i + 1] << 8));
3317
+ }
3318
+ }
3319
+ // UTF-32 につめる
3320
+ for (let i = 0; i < utf16.length; i++) {
3321
+ // prettier-ignore
3322
+ if (0xD800 <= utf16[i] && utf16[i] <= 0xDBFF) {
3323
+ if (i + 2 <= utf16.length) {
3324
+ const high = utf16[i];
3325
+ const low = utf16[i + 1];
3326
+ // prettier-ignore
3327
+ utf32_array.push((((high - 0xD800) << 10) | (low - 0xDC00)) + 0x10000);
3328
+ }
3329
+ i++;
3330
+ } else {
3331
+ utf32_array.push(utf16[i]);
3332
+ }
3333
+ }
3334
+ return utf32_array;
3335
+ } else {
3336
+ // UTF-32
3337
+ if (/utf-?32(be)/i.test(check_charset)) {
3338
+ // UTF-32BE
3339
+ for (let i = offset; i < binary.length; i += 4) {
3340
+ utf32_array.push((binary[i] << 24) | (binary[i + 1] << 16) | (binary[i + 2] << 8) | binary[i + 3]);
3341
+ }
3342
+ return utf32_array;
3343
+ } else if (/utf-?32(le)?/i.test(check_charset)) {
3344
+ // UTF-32LE
3345
+ for (let i = offset; i < binary.length; i += 4) {
3346
+ utf32_array.push(binary[i] | (binary[i + 1] << 8) | (binary[i + 2] << 16) | (binary[i + 3] << 24));
3347
+ }
3348
+ return utf32_array;
3349
+ }
3350
+ }
3351
+ return null;
3352
+ }
3353
+
3354
+ /**
3355
+ * UTF32配列からバイナリ配列に変換
3356
+ * @param {number[]} utf32_array - 変換したいUTF-32配列
3357
+ * @param {string} charset - UTFの種類
3358
+ * @param {boolean} [is_with_bom=true] - BOMをつけるかどうか
3359
+ * @returns {number[]} バイナリ配列(失敗時はnull)
3360
+ */
3361
+ static toUTFBinaryFromCodePoint(utf32_array, charset, is_with_bom) {
3362
+ let is_with_bom_ = is_with_bom !== undefined ? is_with_bom : true;
3363
+ // charset に" with BOM" が入っている場合はBOM付きとする
3364
+ if (/\s+with\s+bom$/i.test(charset)) {
3365
+ is_with_bom_ = true;
3366
+ }
3367
+ /**
3368
+ * @type {number[]}
3369
+ */
3370
+ const binary = [];
3371
+ // UTF-8
3372
+ if (/utf-?8n?/i.test(charset)) {
3373
+ // bom をつける
3374
+ if (is_with_bom_) {
3375
+ // prettier-ignore
3376
+ binary.push(0xEF);
3377
+ // prettier-ignore
3378
+ binary.push(0xBB);
3379
+ // prettier-ignore
3380
+ binary.push(0xBF);
3381
+ }
3382
+ for (let i = 0; i < utf32_array.length; i++) {
3383
+ let codepoint = utf32_array[i];
3384
+ // 1バイト文字
3385
+ if (codepoint <= 0x7F) {
3386
+ binary.push(codepoint);
3387
+ continue;
3388
+ }
3389
+ const buffer = [];
3390
+ /** @type {number} */
3391
+ let size;
3392
+ // 2バイト以上
3393
+ if (codepoint < 0x800) {
3394
+ size = 2;
3395
+ } else if (codepoint < 0x10000) {
3396
+ size = 3;
3397
+ } else {
3398
+ size = 4;
3399
+ }
3400
+ for (let j = 0; j < size; j++) {
3401
+ let write = codepoint & ((1 << 6) - 1);
3402
+ if (j === size - 1) {
3403
+ if (size === 2) {
3404
+ // prettier-ignore
3405
+ write |= 0xC0; // 1100 0000
3406
+ } else if (size === 3) {
3407
+ // prettier-ignore
3408
+ write |= 0xE0; // 1110 0000
3409
+ } else {
3410
+ // prettier-ignore
3411
+ write |= 0xF0; // 1111 0000
3412
+ }
3413
+ buffer.push(write);
3414
+ break;
3415
+ }
3416
+ buffer.push(write | 0x80); // 1000 0000
3417
+ codepoint = codepoint >> 6;
3418
+ }
3419
+ // 反転
3420
+ for (let j = buffer.length - 1; j >= 0; j--) {
3421
+ binary.push(buffer[j]);
3422
+ }
3423
+ }
3424
+ return binary;
3425
+ } else if (/utf-?16/i.test(charset)) {
3426
+ // UTF-16
3427
+ // UTF-16 に詰め替える
3428
+ const utf16_array = Unicode.toUTF16ArrayFromCodePoint(utf32_array);
3429
+ if (/utf-?16(be)/i.test(charset)) {
3430
+ // UTF-16BE
3431
+ // bom をつける
3432
+ if (is_with_bom_) {
3433
+ binary.push(0xFE);
3434
+ binary.push(0xFF);
3435
+ }
3436
+ for (let i = 0; i < utf16_array.length; i++) {
3437
+ binary.push(utf16_array[i] >> 8);
3438
+ binary.push(utf16_array[i] & 0xFF);
3439
+ }
3440
+ } else if (/utf-?16(le)?/i.test(charset)) {
3441
+ // UTF-16LE
3442
+ // bom をつける
3443
+ if (is_with_bom_) {
3444
+ binary.push(0xFF);
3445
+ binary.push(0xFE);
3446
+ }
3447
+ for (let i = 0; i < utf16_array.length; i++) {
3448
+ binary.push(utf16_array[i] & 0xFF);
3449
+ binary.push(utf16_array[i] >> 8);
3450
+ }
3451
+ }
3452
+ return binary;
3453
+ } else if (/utf-?32/i.test(charset)) {
3454
+ // UTF-32
3455
+ if (/utf-?32(be)/i.test(charset)) {
3456
+ // UTF-32BE
3457
+ // bom をつける
3458
+ if (is_with_bom_) {
3459
+ binary.push(0x00);
3460
+ binary.push(0x00);
3461
+ binary.push(0xFE);
3462
+ binary.push(0xFF);
3463
+ }
3464
+ for (let i = 0; i < utf32_array.length; i++) {
3465
+ binary.push((utf32_array[i] >> 24) & 0xFF);
3466
+ binary.push((utf32_array[i] >> 16) & 0xFF);
3467
+ binary.push((utf32_array[i] >> 8) & 0xFF);
3468
+ binary.push(utf32_array[i] & 0xFF);
3469
+ }
3470
+ } else if (/utf-?32(le)?/i.test(charset)) {
3471
+ // UTF-32LE
3472
+ // bom をつける
3473
+ if (is_with_bom_) {
3474
+ binary.push(0xFF);
3475
+ binary.push(0xFE);
3476
+ binary.push(0x00);
3477
+ binary.push(0x00);
3478
+ }
3479
+ for (let i = 0; i < utf32_array.length; i++) {
3480
+ binary.push(utf32_array[i] & 0xFF);
3481
+ binary.push((utf32_array[i] >> 8) & 0xFF);
3482
+ binary.push((utf32_array[i] >> 16) & 0xFF);
3483
+ binary.push((utf32_array[i] >> 24) & 0xFF);
3484
+ }
3485
+ }
3486
+ return binary;
3487
+ }
3488
+ return null;
3489
+ }
3490
+
3491
+ /**
3492
+ * コードポイントからUnicodeのブロック名に変換する
3493
+ * 変換できない場合は "-" を返す
3494
+ * @param {number} codepoint - コードポイント
3495
+ * @returns {string}
3496
+ */
3497
+ static toBlockNameFromUnicode(codepoint) {
3498
+ Unicode.init();
3499
+ return toBlockNameFromUnicode(codepoint);
3500
+ }
3501
+
3502
+ /**
3503
+ * コードポイントから制御文字名に変換する
3504
+ * 変換できない場合は null を返す
3505
+ * @param {number} codepoint - コードポイント
3506
+ * @returns {string|null}
3507
+ */
3508
+ static toControlCharcterName(codepoint) {
3509
+ Unicode.init();
3510
+
3511
+ // 異体字セレクタの確認を行い、異体字セレクタ用の制御文字(FVS, VSx)を返す
3512
+ const info_variation_selectors_number = getVariationSelectorsnumberFromCodePoint(codepoint);
3513
+ if (info_variation_selectors_number !== null) {
3514
+ return info_variation_selectors_number;
3515
+ }
3516
+ // タグ文字の確認を行い、タグ文字用の制御文字(TAG_xx)を返す
3517
+ const info_tag_character = getTagCharacterFromCodePoint(codepoint);
3518
+ if (info_tag_character !== null) {
3519
+ return info_tag_character;
3520
+ }
3521
+ // その他の制御文字の確認を行う
3522
+ const name = control_charcter_map[codepoint];
3523
+ return name ? name : null;
3524
+ }
3525
+
3526
+ /**
3527
+ * コードポイントからグラフェム(見た目の1文字)を構成する文字の判定
3528
+ *
3529
+ * ※単独では新しいグラフェムを開始せず、直前のベース文字に結合・修飾される要素
3530
+ *
3531
+ * 含まれるもの:
3532
+ * - 結合文字 (Mn / Mc / Me ※VS除外)
3533
+ * - 異体字セレクタ (VS / IVS / FVS)
3534
+ * - スキントーン修飾子(EMOJI MODIFIER FITZPATRICK)
3535
+ * - タグ文字(TAG CHARACTER)
3536
+ * - ゼロ幅接合子
3537
+ *
3538
+ * 含まれないもの
3539
+ * - 国旗(Regional Indicator)※ペア規則
3540
+ *
3541
+ * @param {number} codepoint - コードポイント
3542
+ * @returns {boolean} 確認結果
3543
+ */
3544
+ static isGraphemeComponentFromCodePoint(codepoint) {
3545
+ // prettier-ignore
3546
+ return (
3547
+ Unicode.isCombiningMarkFromCodePoint(codepoint) // 結合文字
3548
+ || Unicode.isVariationSelectorFromCodePoint(codepoint) // 異体字セレクタ
3549
+ || Unicode.isEmojiModifierFromCodePoint(codepoint) // スキントーン修飾子
3550
+ || Unicode.isTagCharacterFromCodePoint(codepoint) // タグ文字
3551
+ || codepoint === 0x200D // ZWJ (ZERO WIDTH JOINER) ゼロ幅接合子
3552
+ );
3553
+ }
3554
+
3555
+ /**
3556
+ * コードポイントから国旗(Regional Indicator)を構成する文字の判定
3557
+ *
3558
+ * @param {number} codepoint - コードポイント
3559
+ * @returns {boolean} 確認結果
3560
+ */
3561
+ static isRegionalIndicatorFromCodePoint(codepoint) {
3562
+ // prettier-ignore
3563
+ return (0x1F1E6 <= codepoint && codepoint <= 0x1F1FF);
3564
+ }
3565
+
3566
+ /**
3567
+ * 2つのコードポイントが結合する場合の判定処理
3568
+ *
3569
+ * 含まれるもの:
3570
+ * - 国旗(Regional Indicator)
3571
+ *
3572
+ * @param {number|null} codepoint1 - 直前のコードポイント
3573
+ * @param {number|null} codepoint2 - 現在のコードポイント
3574
+ * @returns {boolean} 確認結果
3575
+ */
3576
+ static isRegionalIndicatorContinuation(codepoint1, codepoint2) {
3577
+ if ((codepoint1 == null || codepoint1 === undefined) || codepoint2 == null || codepoint2 === undefined) {
3578
+ return false;
3579
+ }
3580
+ return Unicode.isRegionalIndicatorFromCodePoint(codepoint1)
3581
+ && Unicode.isRegionalIndicatorFromCodePoint(codepoint2);
3582
+ }
3583
+
3584
+ /**
3585
+ * コードポイントから「表示上の横幅が 0 の文字」の文字の判定
3586
+ *
3587
+ * 含まれるもの:
3588
+ * - ゼロ幅スペース, ゼロ幅非接合子, ゼロ幅接合子, 単語結合子
3589
+ * @param {number} codepoint - コードポイント
3590
+ * @returns {boolean} 確認結果
3591
+ */
3592
+ static isZeroWidthCharacterFromCodePoint(codepoint) {
3593
+ // prettier-ignore
3594
+ return (
3595
+ codepoint === 0x200B // ZWSP (ZERO WIDTH SPACE) ゼロ幅スペース
3596
+ || codepoint === 0x200C // ZWNJ (ZERO WIDTH NON-JOINER) ゼロ幅非接合子
3597
+ || codepoint === 0x200D // ZWJ (ZERO WIDTH JOINER) ゼロ幅接合子
3598
+ || codepoint === 0x2060 // WJ (WORD JOINER) 単語結合子
3599
+ );
3600
+ }
3601
+
3602
+ /**
3603
+ * コードポイントから結合文字の判定
3604
+ * @param {number} codepoint - コードポイント
3605
+ * @returns {boolean} 確認結果
3606
+ */
3607
+ static isCombiningMarkFromCodePoint(codepoint) {
3608
+ // 異体字セレクタは除外
3609
+ if (Unicode.isVariationSelectorFromCodePoint(codepoint)) {
3610
+ return false;
3611
+ }
3612
+ try {
3613
+ return new RegExp("\\p{Mark}", "u").test(String.fromCodePoint(codepoint));
3614
+ // eslint-disable-next-line no-unused-vars
3615
+ } catch (e) {
3616
+ // フォールバック処理
3617
+ return (
3618
+ // Combining Diacritical Marks
3619
+ // prettier-ignore
3620
+ (0x0300 <= codepoint && codepoint <= 0x036F)
3621
+ // Combining Diacritical Marks Extended
3622
+ // prettier-ignore
3623
+ || (0x1AB0 <= codepoint && codepoint <= 0x1AFF)
3624
+ // Combining Diacritical Marks Supplement
3625
+ // prettier-ignore
3626
+ || (0x1DC0 <= codepoint && codepoint <= 0x1DFF)
3627
+ // Combining Diacritical Marks for Symbols
3628
+ // prettier-ignore
3629
+ || (0x20D0 <= codepoint && codepoint <= 0x20FF)
3630
+ // 日本語に含まれる2種類の文字
3631
+ // COMBINING VOICED SOUND MARK
3632
+ // COMBINING SEMI-VOICED SOUND MARK
3633
+ // prettier-ignore
3634
+ || (0x3099 <= codepoint && codepoint <= 0x309A)
3635
+ // Combining Half Marks
3636
+ // prettier-ignore
3637
+ || (0xFE20 <= codepoint && codepoint <= 0xFE2F)
3638
+ );
3639
+ }
3640
+ }
3641
+
3642
+ /**
3643
+ * コードポイントから異体字セレクタの判定
3644
+ * @param {number} codepoint - コードポイント
3645
+ * @returns {boolean} 確認結果
3646
+ */
3647
+ static isVariationSelectorFromCodePoint(codepoint) {
3648
+ return (
3649
+ // モンゴル自由字形選択子 U+180B〜U+180D (3個)
3650
+ // prettier-ignore
3651
+ (0x180B <= codepoint && codepoint <= 0x180D)
3652
+ // SVSで利用される異体字セレクタ U+FE00〜U+FE0F (VS1~VS16) (16個)
3653
+ // prettier-ignore
3654
+ || (0xFE00 <= codepoint && codepoint <= 0xFE0F)
3655
+ // IVSで利用される異体字セレクタ U+E0100〜U+E01EF (VS17~VS256) (240個)
3656
+ // prettier-ignore
3657
+ || (0xE0100 <= codepoint && codepoint <= 0xE01EF)
3658
+ );
3659
+ }
3660
+
3661
+ /**
3662
+ * コードポイントからスキントーン修飾子の判定
3663
+ * @param {number} codepoint - コードポイント
3664
+ * @returns {boolean} 確認結果
3665
+ */
3666
+ static isEmojiModifierFromCodePoint(codepoint) {
3667
+ return (
3668
+ // EMOJI MODIFIER FITZPATRICK
3669
+ // prettier-ignore
3670
+ 0x1F3FB <= codepoint && codepoint <= 0x1F3FF
3671
+ );
3672
+ }
3673
+
3674
+ /**
3675
+ * コードポイントからタグ文字の判定
3676
+ * @param {number} codepoint - コードポイント
3677
+ * @returns {boolean} 確認結果
3678
+ */
3679
+ static isTagCharacterFromCodePoint(codepoint) {
3680
+ return (
3681
+ // TAG CHARACTER
3682
+ // prettier-ignore
3683
+ 0xE0000 <= codepoint && codepoint <= 0xE007F
3684
+ );
3685
+ }
3686
+ }
3687
+
3688
+ /**
3689
+ * マップを初期化した否か
3690
+ */
3691
+ Unicode.is_initmap = false;
3692
+
3693
+ /**
3694
+ * The script is part of Mojix for TextInputGuard.
3695
+ *
3696
+ * AUTHOR:
3697
+ * natade-jp (https://github.com/natade-jp)
3698
+ *
3699
+ * LICENSE:
3700
+ * The MIT license https://opensource.org/licenses/MIT
3701
+ */
3702
+
3703
+
3704
+ /**
3705
+ * 面区点情報
3706
+ * @typedef {Object} MenKuTen
3707
+ * @property {string} [text] 面-区-点
3708
+ * @property {number} [men=1] 面
3709
+ * @property {number} ku 区
3710
+ * @property {number} ten 点
3711
+ */
3712
+
3713
+ /**
3714
+ * Shift_JIS を扱うクラス
3715
+ * @ignore
3716
+ */
3717
+ class SJIS {
3718
+ /**
3719
+ * 文字列を Shift_JIS の配列に変換。変換できない文字は "?" に変換される。
3720
+ * @param {string} text - 変換したいテキスト
3721
+ * @param {Record<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
3722
+ * @returns {number[]} Shift_JIS のデータが入った配列
3723
+ * @ignore
3724
+ */
3725
+ static toSJISArray(text, unicode_to_sjis) {
3726
+ const map = unicode_to_sjis;
3727
+ const utf32 = Unicode.toUTF32Array(text);
3728
+ const sjis = [];
3729
+ const ng = "?".charCodeAt(0);
3730
+ for (let i = 0; i < utf32.length; i++) {
3731
+ const map_bin = map[utf32[i]];
3732
+ if (map_bin) {
3733
+ sjis.push(map_bin);
3734
+ } else {
3735
+ sjis.push(ng);
3736
+ }
3737
+ }
3738
+ return sjis;
3739
+ }
3740
+
3741
+ /**
3742
+ * 文字列を Shift_JIS のバイナリ配列に変換。変換できない文字は "?" に変換される。
3743
+ * - 日本語文字は2バイトとして、配列も2つ分、使用します。
3744
+ * @param {string} text - 変換したいテキスト
3745
+ * @param {Record<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
3746
+ * @returns {number[]} Shift_JIS のデータが入ったバイナリ配列
3747
+ * @ignore
3748
+ */
3749
+ static toSJISBinary(text, unicode_to_sjis) {
3750
+ const sjis = SJIS.toSJISArray(text, unicode_to_sjis);
3751
+ const sjisbin = [];
3752
+ for (let i = 0; i < sjis.length; i++) {
3753
+ if (sjis[i] < 0x100) {
3754
+ sjisbin.push(sjis[i]);
3755
+ } else {
3756
+ sjisbin.push(sjis[i] >> 8);
3757
+ sjisbin.push(sjis[i] & 0xFF);
3758
+ }
3759
+ }
3760
+ return sjisbin;
3761
+ }
3762
+
3763
+ /**
3764
+ * SJISの配列から文字列に変換
3765
+ * @param {number[]} sjis - 変換したいテキスト
3766
+ * @param {Record<number, number|number[]>} sjis_to_unicode - Shift_JIS から Unicode への変換マップ
3767
+ * @returns {string} 変換後のテキスト
3768
+ * @ignore
3769
+ */
3770
+ static fromSJISArray(sjis, sjis_to_unicode) {
3771
+ const map = sjis_to_unicode;
3772
+ const utf16 = [];
3773
+ const ng = "?".charCodeAt(0);
3774
+ for (let i = 0; i < sjis.length; i++) {
3775
+ let x = sjis[i];
3776
+ /**
3777
+ * @type {number|number[]}
3778
+ */
3779
+ let y;
3780
+ if (x >= 0x100) {
3781
+ // すでに1つの変数にまとめられている
3782
+ y = map[x];
3783
+ } else {
3784
+ // 2バイト文字かのチェック
3785
+ // prettier-ignore
3786
+ if ((0x81 <= x && x <= 0x9F) || (0xE0 <= x && x <= 0xFC)) {
3787
+ x <<= 8;
3788
+ i++;
3789
+ x |= sjis[i];
3790
+ y = map[x];
3791
+ } else {
3792
+ y = map[x];
3793
+ }
3794
+ }
3795
+ if (y) {
3796
+ // 配列なら配列を結合
3797
+ // ※ Unicodeの結合文字の可能性があるため
3798
+ if (Array.isArray(y)) {
3799
+ for (let j = 0; j < y.length; j++) {
3800
+ utf16.push(y[j]);
3801
+ }
3802
+ } else {
3803
+ // 値しかない場合は値を結合
3804
+ utf16.push(y);
3805
+ }
3806
+ } else {
3807
+ utf16.push(ng);
3808
+ }
3809
+ }
3810
+ return Unicode.fromUTF32Array(utf16);
3811
+ }
3812
+
3813
+ /**
3814
+ * 指定したコードポイントの文字から Shift_JIS 上の符号化数値に変換
3815
+ * @param {number} unicode_codepoint - Unicodeのコードポイント
3816
+ * @param {Record<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
3817
+ * @returns {number} 符号化数値(変換できない場合はnullとなる)
3818
+ * @ignore
3819
+ */
3820
+ static toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis) {
3821
+ if (!unicode_to_sjis[unicode_codepoint]) {
3822
+ return null;
3823
+ }
3824
+ const utf16_text = Unicode.fromUTF32Array([unicode_codepoint]);
3825
+ const sjis_array = SJIS.toSJISArray(utf16_text, unicode_to_sjis);
3826
+ return sjis_array[0];
3827
+ }
3828
+
3829
+ /**
3830
+ * 指定した Shift_JIS のコードから区点番号に変換
3831
+ * @param {number} sjis_code - Shift_JIS のコードポイント
3832
+ * @returns {MenKuTen} 区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
3833
+ */
3834
+ static toKuTenFromSJISCode(sjis_code) {
3835
+ if (!sjis_code) {
3836
+ return null;
3837
+ }
3838
+ const x = sjis_code;
3839
+ if (x < 0x100) {
3840
+ return null;
3841
+ }
3842
+ // アルゴリズムは区点番号表からリバースエンジニアリング
3843
+
3844
+ let s1 = x >> 8;
3845
+ let s2 = x & 0xFF;
3846
+
3847
+ /** @type {number} */
3848
+ let ku;
3849
+
3850
+ // 区の計算方法の切り替え
3851
+ // 63区から、0x9F→0xE0に飛ぶ
3852
+ // prettier-ignore
3853
+ if (s1 < 0xE0) {
3854
+ s1 = s1 - 0x81;
3855
+ } else {
3856
+ s1 = s1 - 0xC1;
3857
+ }
3858
+
3859
+ // 区情報の位置判定
3860
+ // prettier-ignore
3861
+ if (s2 < 0x9F) {
3862
+ ku = s1 * 2 + 1;
3863
+ // 点情報の計算方法の切り替え
3864
+ // 0x7Fが欠番のため「+1」を除去
3865
+ // prettier-ignore
3866
+ if (s2 < 0x80) {
3867
+ // prettier-ignore
3868
+ s2 = s2 - 0x40 + 1;
3869
+ } else {
3870
+ // prettier-ignore
3871
+ s2 = s2 - 0x40;
3872
+ }
3873
+ } else {
3874
+ ku = s1 * 2 + 2;
3875
+ // prettier-ignore
3876
+ s2 = s2 - 0x9F + 1;
3877
+ }
3878
+
3879
+ // 点情報の位置判定
3880
+ const ten = s2;
3881
+
3882
+ return {
3883
+ text: ku + "-" + ten,
3884
+ men: 1,
3885
+ ku: ku,
3886
+ ten: ten
3887
+ };
3888
+ }
3889
+
3890
+ /**
3891
+ * 指定した面区点番号から Shift_JIS の仕様上、正規な物か判定
3892
+ * @param {MenKuTen|string} menkuten - 面区点番号(面が省略された場合は、1とみなす)
3893
+ * @returns {boolean} 正規なデータは true, 不正なデータは false
3894
+ */
3895
+ static isRegularMenKuten(menkuten) {
3896
+ let m, k, t;
3897
+
3898
+ // 引数のテスト
3899
+ if (menkuten instanceof Object) {
3900
+ m = menkuten.men ? menkuten.men : 1;
3901
+ k = menkuten.ku;
3902
+ t = menkuten.ten;
3903
+ } else if (typeof menkuten === "string") {
3904
+ const strmkt = menkuten.split("-");
3905
+ if (strmkt.length === 3) {
3906
+ m = parseInt(strmkt[0], 10);
3907
+ k = parseInt(strmkt[1], 10);
3908
+ t = parseInt(strmkt[2], 10);
3909
+ } else if (strmkt.length === 2) {
3910
+ m = 1;
3911
+ k = parseInt(strmkt[0], 10);
3912
+ t = parseInt(strmkt[1], 10);
3913
+ } else {
3914
+ return false;
3915
+ }
3916
+ } else {
3917
+ return false;
3918
+ }
3919
+
3920
+ /**
3921
+ * @type {Record<number, number>}
3922
+ */
3923
+ const kmap = { 1: 1, 3: 1, 4: 1, 5: 1, 8: 1, 12: 1, 13: 1, 14: 1, 15: 1 };
3924
+ if (m === 1) {
3925
+ // 1面は1-94区まで存在
3926
+ if (!(1 <= k && k <= 94)) {
3927
+ return false;
3928
+ }
3929
+ } else if (m === 2) {
3930
+ // 2面は、1,3,4,5,8,12,13,14,15,78-94区まで存在
3931
+ if (!(kmap[k] || (78 <= k && k <= 94))) {
3932
+ return false;
3933
+ }
3934
+ } else {
3935
+ // 面が不正
3936
+ return false;
3937
+ }
3938
+ // 点は1-94点まで存在
3939
+ if (!(1 <= t && t <= 94)) {
3940
+ return false;
3941
+ }
3942
+ return true;
3943
+ }
3944
+ }
3945
+
3946
+ /**
3947
+ * The script is part of Mojix for TextInputGuard.
3948
+ *
3949
+ * AUTHOR:
3950
+ * natade-jp (https://github.com/natade-jp)
3951
+ *
3952
+ * LICENSE:
3953
+ * The MIT license https://opensource.org/licenses/MIT
3954
+ */
3955
+
3956
+
3957
+ /**
3958
+ * CP932, Windows-31J の変換マップ作成用クラス
3959
+ * @ignore
3960
+ */
3961
+ class CP932MAP {
3962
+ /**
3963
+ * 変換マップを初期化
3964
+ */
3965
+ static init() {
3966
+ if (CP932MAP.is_initmap) {
3967
+ return;
3968
+ }
3969
+ CP932MAP.is_initmap = true;
3970
+
3971
+ /**
3972
+ * @returns {Record<number, number>}
3973
+ */
3974
+ const getCp932ToUnicodeMap = function () {
3975
+ /* eslint-disable max-len */
3976
+ /* eslint-disable object-property-newline */
3977
+ /**
3978
+ * 1バイトの変換マップ
3979
+ *
3980
+ *
3981
+ * 参考:WideCharToMultiByte
3982
+ * メモ:今回は使っていないが、以下の文献も参考になるかもしれません。
3983
+ * ftp://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT
3984
+ * @type {Record<number, number>}
3985
+ */
3986
+ // prettier-ignore
3987
+ const cp932_to_unicode_map = {
3988
+ 0x01: 0x01, 0x02: 0x02, 0x03: 0x03, 0x04: 0x04, 0x05: 0x05, 0x06: 0x06, 0x07: 0x07, 0x08: 0x08,
3989
+ 0x09: 0x09, 0x0A: 0x0A, 0x0B: 0x0B, 0x0C: 0x0C, 0x0D: 0x0D, 0x0E: 0x0E, 0x0F: 0x0F, 0x10: 0x10,
3990
+ 0x11: 0x11, 0x12: 0x12, 0x13: 0x13, 0x14: 0x14, 0x15: 0x15, 0x16: 0x16, 0x17: 0x17, 0x18: 0x18,
3991
+ 0x19: 0x19, 0x1A: 0x1A, 0x1B: 0x1B, 0x1C: 0x1C, 0x1D: 0x1D, 0x1E: 0x1E, 0x1F: 0x1F, 0x20: 0x20,
3992
+ 0x21: 0x21, 0x22: 0x22, 0x23: 0x23, 0x24: 0x24, 0x25: 0x25, 0x26: 0x26, 0x27: 0x27, 0x28: 0x28,
3993
+ 0x29: 0x29, 0x2A: 0x2A, 0x2B: 0x2B, 0x2C: 0x2C, 0x2D: 0x2D, 0x2E: 0x2E, 0x2F: 0x2F, 0x30: 0x30,
3994
+ 0x31: 0x31, 0x32: 0x32, 0x33: 0x33, 0x34: 0x34, 0x35: 0x35, 0x36: 0x36, 0x37: 0x37, 0x38: 0x38,
3995
+ 0x39: 0x39, 0x3A: 0x3A, 0x3B: 0x3B, 0x3C: 0x3C, 0x3D: 0x3D, 0x3E: 0x3E, 0x3F: 0x3F, 0x40: 0x40,
3996
+ 0x41: 0x41, 0x42: 0x42, 0x43: 0x43, 0x44: 0x44, 0x45: 0x45, 0x46: 0x46, 0x47: 0x47, 0x48: 0x48,
3997
+ 0x49: 0x49, 0x4A: 0x4A, 0x4B: 0x4B, 0x4C: 0x4C, 0x4D: 0x4D, 0x4E: 0x4E, 0x4F: 0x4F, 0x50: 0x50,
3998
+ 0x51: 0x51, 0x52: 0x52, 0x53: 0x53, 0x54: 0x54, 0x55: 0x55, 0x56: 0x56, 0x57: 0x57, 0x58: 0x58,
3999
+ 0x59: 0x59, 0x5A: 0x5A, 0x5B: 0x5B, 0x5C: 0x5C, 0x5D: 0x5D, 0x5E: 0x5E, 0x5F: 0x5F, 0x60: 0x60,
4000
+ 0x61: 0x61, 0x62: 0x62, 0x63: 0x63, 0x64: 0x64, 0x65: 0x65, 0x66: 0x66, 0x67: 0x67, 0x68: 0x68,
4001
+ 0x69: 0x69, 0x6A: 0x6A, 0x6B: 0x6B, 0x6C: 0x6C, 0x6D: 0x6D, 0x6E: 0x6E, 0x6F: 0x6F, 0x70: 0x70,
4002
+ 0x71: 0x71, 0x72: 0x72, 0x73: 0x73, 0x74: 0x74, 0x75: 0x75, 0x76: 0x76, 0x77: 0x77, 0x78: 0x78,
4003
+ 0x79: 0x79, 0x7A: 0x7A, 0x7B: 0x7B, 0x7C: 0x7C, 0x7D: 0x7D, 0x7E: 0x7E, 0x7F: 0x7F, 0x80: 0x80,
4004
+ 0xA0: 0xF8F0, 0xA1: 0xFF61, 0xA2: 0xFF62, 0xA3: 0xFF63, 0xA4: 0xFF64, 0xA5: 0xFF65, 0xA6: 0xFF66, 0xA7: 0xFF67,
4005
+ 0xA8: 0xFF68, 0xA9: 0xFF69, 0xAA: 0xFF6A, 0xAB: 0xFF6B, 0xAC: 0xFF6C, 0xAD: 0xFF6D, 0xAE: 0xFF6E, 0xAF: 0xFF6F,
4006
+ 0xB0: 0xFF70, 0xB1: 0xFF71, 0xB2: 0xFF72, 0xB3: 0xFF73, 0xB4: 0xFF74, 0xB5: 0xFF75, 0xB6: 0xFF76, 0xB7: 0xFF77,
4007
+ 0xB8: 0xFF78, 0xB9: 0xFF79, 0xBA: 0xFF7A, 0xBB: 0xFF7B, 0xBC: 0xFF7C, 0xBD: 0xFF7D, 0xBE: 0xFF7E, 0xBF: 0xFF7F,
4008
+ 0xC0: 0xFF80, 0xC1: 0xFF81, 0xC2: 0xFF82, 0xC3: 0xFF83, 0xC4: 0xFF84, 0xC5: 0xFF85, 0xC6: 0xFF86, 0xC7: 0xFF87,
4009
+ 0xC8: 0xFF88, 0xC9: 0xFF89, 0xCA: 0xFF8A, 0xCB: 0xFF8B, 0xCC: 0xFF8C, 0xCD: 0xFF8D, 0xCE: 0xFF8E, 0xCF: 0xFF8F,
4010
+ 0xD0: 0xFF90, 0xD1: 0xFF91, 0xD2: 0xFF92, 0xD3: 0xFF93, 0xD4: 0xFF94, 0xD5: 0xFF95, 0xD6: 0xFF96, 0xD7: 0xFF97,
4011
+ 0xD8: 0xFF98, 0xD9: 0xFF99, 0xDA: 0xFF9A, 0xDB: 0xFF9B, 0xDC: 0xFF9C, 0xDD: 0xFF9D, 0xDE: 0xFF9E, 0xDF: 0xFF9F,
4012
+ 0xFD: 0xF8F1, 0xFE: 0xF8F2, 0xFF: 0xF8F3
4013
+ };
4014
+ /* eslint-enable object-property-newline */
4015
+ /* eslint-enable max-len */
4016
+
4017
+ /**
4018
+ * 2バイト文字(0x8140-0xffff)の変換マップ作成用の文字列
4019
+ * @type {string}
4020
+ */
4021
+ // prettier-ignore
4022
+ const map = [
4023
+ " 、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\~∥|…‥‘’“”()〔〕[]{}〈〉《》「」『』【】+-±×1÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓〓11∈∋⊆⊇⊂⊃∪∩8∧∨¬⇒⇔∀∃11∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬7ʼn♯♭♪†‡¶4◯82",
4024
+ "01234567897ABCDEFGHIJKLMNOPQRSTUVWXYZ7abcdefghijklmnopqrstuvwxyz4ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをん78",
4025
+ "ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミ1ムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ8ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ8αβγδεζηθικλμνξοπρστυφχψω105АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ15абвгдеёжзийклмн1опрстуфхцчшщъыьэюя13─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂641",
4026
+ "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ1㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㎡8㍻1〝〟№㏍℡㊤㊥㊦㊧㊨㈱㈲㈹㍾㍽㍼≒≡∫∮∑√⊥∠∟⊿∵∩∪258",
4027
+ "亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲夷委威尉惟意慰易椅為畏異移維緯胃萎衣謂違遺医井亥域育郁磯一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭67",
4028
+ "院陰隠韻吋右宇烏羽迂雨卯鵜窺丑碓臼渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円1園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応押旺横欧殴王翁襖鴬鴎黄岡沖荻億屋憶臆桶牡乙俺卸恩温穏音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河火珂禍禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改67",
4029
+ "魁晦械海灰界皆絵芥蟹開階貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣隔革学岳楽額顎掛笠樫1橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱粥刈苅瓦乾侃冠寒刊勘勧巻喚堪姦完官寛干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環甘監看竿管簡緩缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄67",
4030
+ "機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救1朽求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極玉桐粁僅勤均巾錦斤欣欽琴禁禽筋緊芹菌衿襟謹近金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈67",
4031
+ "掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨1劇戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲検権牽犬献研硯絹県肩見謙賢軒遣鍵険顕験鹸元原厳幻弦減源玄現絃舷言諺限乎個古呼固姑孤己庫弧戸故枯湖狐糊袴股胡菰虎誇跨鈷雇顧鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚口向67",
4032
+ "后喉坑垢好孔孝宏工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講貢購郊酵鉱砿鋼閤降1項香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込此頃今困坤墾婚恨懇昏昆根梱混痕紺艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷67",
4033
+ "察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止1死氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時次滋治爾璽痔磁示而耳自蒔辞汐鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守手朱殊狩珠種腫趣酒首儒受呪寿授樹綬需囚収周67",
4034
+ "宗就州修愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳1準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償勝匠升召哨商唱嘗奨妾娼宵将小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾67",
4035
+ "拭植殖燭織職色触食蝕辱尻伸信侵唇娠寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨1逗吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾澄摺寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲生盛精聖声製西誠誓請逝醒青静斉税脆隻席惜戚斥昔析石積籍績脊責赤跡蹟碩切拙接摂折設窃節説雪絶舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線67",
4036
+ "繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻1操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎臓蔵贈造促側則即息捉束測足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只67",
4037
+ "叩但達辰奪脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄1逐秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚長頂鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓67",
4038
+ "邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努度土奴怒倒党冬1凍刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到董蕩藤討謄豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝二尼弐迩匂賑肉虹廿日乳入67",
4039
+ "如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅1楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦函箱硲箸肇筈櫨幡肌畑畠八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐比泌疲皮碑秘緋罷肥被誹費避非飛樋簸備尾微枇毘琵眉美67",
4040
+ "鼻柊稗匹疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷1斧普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰文聞丙併兵塀幣平弊柄並蔽閉陛米頁僻壁癖碧別瞥蔑箆偏変片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放方朋67",
4041
+ "法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆1摩磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満漫蔓味未魅巳箕岬密蜜湊蓑稔脈妙粍民眠務夢無牟矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬面麺摸模茂妄孟毛猛盲網耗蒙儲木黙目杢勿餅尤戻籾貰問悶紋門匁也冶夜爺耶野弥矢厄役約薬訳躍靖柳薮鑓愉愈油癒67",
4042
+ "諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲1沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃痢裏裡里離陸律率立葎掠略劉流溜琉留硫粒隆竜龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯67",
4043
+ "蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀湾碗腕44弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲67",
4044
+ "僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭1凰凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨67",
4045
+ "咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸1噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩67",
4046
+ "奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀1它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓屐屏孱屬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏67",
4047
+ "廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠1怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀戈戉戍戌戔戛67",
4048
+ "戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫1捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼67",
4049
+ "曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎1梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣67",
4050
+ "檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯1麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌16451",
4051
+ "漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝1烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱67",
4052
+ "瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿1痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬67",
4053
+ "磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰1窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆67",
4054
+ "紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷1縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋67",
4055
+ "隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤1艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈67",
4056
+ "蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬1蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞67",
4057
+ "襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧1諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊67",
4058
+ "蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜1轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮67",
4059
+ "錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙1閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰67",
4060
+ "顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃1騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈67",
4061
+ "鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯1黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠堯槇遙瑤凜熙667",
4062
+ "纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏1塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱67",
4063
+ "犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙1蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑2ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ¬¦'"323",
4064
+ "167",
4065
+ "167",
4066
+ "167",
4067
+ "167",
4068
+ "167",
4069
+ "167",
4070
+ "167",
4071
+ "167",
4072
+ "167",
4073
+ "167",
4074
+ "ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ¬¦'"㈱№℡∵纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊1兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯67",
4075
+ "涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神1祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙67",
4076
+ "髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑"
4077
+ ].join("");
4078
+
4079
+ /*
4080
+ 上の変換マップ作成用の文字列は数値が入った変換マップのコードから作成している
4081
+ let output = "";
4082
+ let nul_count = 0;
4083
+ for(i = 0x8140; i <= 0xffff; i++) {
4084
+ if(map[i]) {
4085
+ if(nul_count !== 0){
4086
+ output += nul_count;
4087
+ nul_count = 0;
4088
+ }
4089
+ output += MojiJS.fromCodePoint(map[i]);
4090
+ }
4091
+ else {
4092
+ nul_count++;
4093
+ }
4094
+ }
4095
+ */
4096
+
4097
+ /**
4098
+ * UTF16へ変換
4099
+ */
4100
+ const utf16_array = Unicode.toUTF16Array(map);
4101
+
4102
+ // マップ展開
4103
+ let is_num = false;
4104
+ let num_array = [];
4105
+ let key = 0x8140;
4106
+ for (let i = 0; i < utf16_array.length; i++) {
4107
+ const x = utf16_array[i];
4108
+ if (0x30 <= x && x <= 0x39) {
4109
+ if (!is_num) {
4110
+ is_num = true;
4111
+ num_array = [];
4112
+ }
4113
+ num_array.push(x);
4114
+ } else {
4115
+ if (is_num) {
4116
+ key += parseFloat(Unicode.fromUTF16Array(num_array));
4117
+ is_num = false;
4118
+ }
4119
+ cp932_to_unicode_map[key] = x;
4120
+ key++;
4121
+ }
4122
+ }
4123
+
4124
+ return cp932_to_unicode_map;
4125
+ };
4126
+
4127
+ /**
4128
+ * CP932 変換マップ
4129
+ * @type {Record<number, number>}
4130
+ */
4131
+ const cp932_to_unicode_map = getCp932ToUnicodeMap();
4132
+
4133
+ /* eslint-disable max-len */
4134
+ /**
4135
+ * 重複された CP932 のコード
4136
+ * @type {number[]}
4137
+ */
4138
+ // prettier-ignore
4139
+ const duplicate_map_array = [
4140
+ 0x8790, 0x8791, 0x8792, 0x8795, 0x8796, 0x8797, 0x879A, 0x879B, 0x879C, 0xED40, 0xED41, 0xED42, 0xED43, 0xED44, 0xED45, 0xED46,
4141
+ 0xED47, 0xED48, 0xED49, 0xED4A, 0xED4B, 0xED4C, 0xED4D, 0xED4E, 0xED4F, 0xED50, 0xED51, 0xED52, 0xED53, 0xED54, 0xED55, 0xED56,
4142
+ 0xED57, 0xED58, 0xED59, 0xED5A, 0xED5B, 0xED5C, 0xED5D, 0xED5E, 0xED5F, 0xED60, 0xED61, 0xED62, 0xED63, 0xED64, 0xED65, 0xED66,
4143
+ 0xED67, 0xED68, 0xED69, 0xED6A, 0xED6B, 0xED6C, 0xED6D, 0xED6E, 0xED6F, 0xED70, 0xED71, 0xED72, 0xED73, 0xED74, 0xED75, 0xED76,
4144
+ 0xED77, 0xED78, 0xED79, 0xED7A, 0xED7B, 0xED7C, 0xED7D, 0xED7E, 0xED80, 0xED81, 0xED82, 0xED83, 0xED84, 0xED85, 0xED86, 0xED87,
4145
+ 0xED88, 0xED89, 0xED8A, 0xED8B, 0xED8C, 0xED8D, 0xED8E, 0xED8F, 0xED90, 0xED91, 0xED92, 0xED93, 0xED94, 0xED95, 0xED96, 0xED97,
4146
+ 0xED98, 0xED99, 0xED9A, 0xED9B, 0xED9C, 0xED9D, 0xED9E, 0xED9F, 0xEDA0, 0xEDA1, 0xEDA2, 0xEDA3, 0xEDA4, 0xEDA5, 0xEDA6, 0xEDA7,
4147
+ 0xEDA8, 0xEDA9, 0xEDAA, 0xEDAB, 0xEDAC, 0xEDAD, 0xEDAE, 0xEDAF, 0xEDB0, 0xEDB1, 0xEDB2, 0xEDB3, 0xEDB4, 0xEDB5, 0xEDB6, 0xEDB7,
4148
+ 0xEDB8, 0xEDB9, 0xEDBA, 0xEDBB, 0xEDBC, 0xEDBD, 0xEDBE, 0xEDBF, 0xEDC0, 0xEDC1, 0xEDC2, 0xEDC3, 0xEDC4, 0xEDC5, 0xEDC6, 0xEDC7,
4149
+ 0xEDC8, 0xEDC9, 0xEDCA, 0xEDCB, 0xEDCC, 0xEDCD, 0xEDCE, 0xEDCF, 0xEDD0, 0xEDD1, 0xEDD2, 0xEDD3, 0xEDD4, 0xEDD5, 0xEDD6, 0xEDD7,
4150
+ 0xEDD8, 0xEDD9, 0xEDDA, 0xEDDB, 0xEDDC, 0xEDDD, 0xEDDE, 0xEDDF, 0xEDE0, 0xEDE1, 0xEDE2, 0xEDE3, 0xEDE4, 0xEDE5, 0xEDE6, 0xEDE7,
4151
+ 0xEDE8, 0xEDE9, 0xEDEA, 0xEDEB, 0xEDEC, 0xEDED, 0xEDEE, 0xEDEF, 0xEDF0, 0xEDF1, 0xEDF2, 0xEDF3, 0xEDF4, 0xEDF5, 0xEDF6, 0xEDF7,
4152
+ 0xEDF8, 0xEDF9, 0xEDFA, 0xEDFB, 0xEDFC, 0xEE40, 0xEE41, 0xEE42, 0xEE43, 0xEE44, 0xEE45, 0xEE46, 0xEE47, 0xEE48, 0xEE49, 0xEE4A,
4153
+ 0xEE4B, 0xEE4C, 0xEE4D, 0xEE4E, 0xEE4F, 0xEE50, 0xEE51, 0xEE52, 0xEE53, 0xEE54, 0xEE55, 0xEE56, 0xEE57, 0xEE58, 0xEE59, 0xEE5A,
4154
+ 0xEE5B, 0xEE5C, 0xEE5D, 0xEE5E, 0xEE5F, 0xEE60, 0xEE61, 0xEE62, 0xEE63, 0xEE64, 0xEE65, 0xEE66, 0xEE67, 0xEE68, 0xEE69, 0xEE6A,
4155
+ 0xEE6B, 0xEE6C, 0xEE6D, 0xEE6E, 0xEE6F, 0xEE70, 0xEE71, 0xEE72, 0xEE73, 0xEE74, 0xEE75, 0xEE76, 0xEE77, 0xEE78, 0xEE79, 0xEE7A,
4156
+ 0xEE7B, 0xEE7C, 0xEE7D, 0xEE7E, 0xEE80, 0xEE81, 0xEE82, 0xEE83, 0xEE84, 0xEE85, 0xEE86, 0xEE87, 0xEE88, 0xEE89, 0xEE8A, 0xEE8B,
4157
+ 0xEE8C, 0xEE8D, 0xEE8E, 0xEE8F, 0xEE90, 0xEE91, 0xEE92, 0xEE93, 0xEE94, 0xEE95, 0xEE96, 0xEE97, 0xEE98, 0xEE99, 0xEE9A, 0xEE9B,
4158
+ 0xEE9C, 0xEE9D, 0xEE9E, 0xEE9F, 0xEEA0, 0xEEA1, 0xEEA2, 0xEEA3, 0xEEA4, 0xEEA5, 0xEEA6, 0xEEA7, 0xEEA8, 0xEEA9, 0xEEAA, 0xEEAB,
4159
+ 0xEEAC, 0xEEAD, 0xEEAE, 0xEEAF, 0xEEB0, 0xEEB1, 0xEEB2, 0xEEB3, 0xEEB4, 0xEEB5, 0xEEB6, 0xEEB7, 0xEEB8, 0xEEB9, 0xEEBA, 0xEEBB,
4160
+ 0xEEBC, 0xEEBD, 0xEEBE, 0xEEBF, 0xEEC0, 0xEEC1, 0xEEC2, 0xEEC3, 0xEEC4, 0xEEC5, 0xEEC6, 0xEEC7, 0xEEC8, 0xEEC9, 0xEECA, 0xEECB,
4161
+ 0xEECC, 0xEECD, 0xEECE, 0xEECF, 0xEED0, 0xEED1, 0xEED2, 0xEED3, 0xEED4, 0xEED5, 0xEED6, 0xEED7, 0xEED8, 0xEED9, 0xEEDA, 0xEEDB,
4162
+ 0xEEDC, 0xEEDD, 0xEEDE, 0xEEDF, 0xEEE0, 0xEEE1, 0xEEE2, 0xEEE3, 0xEEE4, 0xEEE5, 0xEEE6, 0xEEE7, 0xEEE8, 0xEEE9, 0xEEEA, 0xEEEB,
4163
+ 0xEEEC, 0xEEEF, 0xEEF0, 0xEEF1, 0xEEF2, 0xEEF3, 0xEEF4, 0xEEF5, 0xEEF6, 0xEEF7, 0xEEF8, 0xEEF9, 0xEEFA, 0xEEFB, 0xEEFC, 0xFA4A,
4164
+ 0xFA4B, 0xFA4C, 0xFA4D, 0xFA4E, 0xFA4F, 0xFA50, 0xFA51, 0xFA52, 0xFA53, 0xFA54, 0xFA58, 0xFA59, 0xFA5A, 0xFA5B
4165
+ ];
4166
+ /* eslint-enable max-len */
4167
+
4168
+ /**
4169
+ * @type {Record<number, number>}
4170
+ */
4171
+ const duplicate_map = {};
4172
+
4173
+ /**
4174
+ * @type {Record<number, number>}
4175
+ */
4176
+ const unicode_to_cp932_map = {};
4177
+
4178
+ for (const key in duplicate_map_array) {
4179
+ duplicate_map[duplicate_map_array[key]] = 1;
4180
+ }
4181
+ for (const key in cp932_to_unicode_map) {
4182
+ // 重複登録された文字
4183
+ // IBM拡張文字 と NEC特殊文字 と NEC選定IBM拡張文字 で
4184
+ // マッピング先が一部重複している。
4185
+ // WideCharToMultiByte の仕様に基づき、登録しない。
4186
+ if (duplicate_map[key]) {
4187
+ continue;
4188
+ }
4189
+ const x = cp932_to_unicode_map[key];
4190
+ unicode_to_cp932_map[x] = parseInt(key, 10);
4191
+ }
4192
+
4193
+ // 逆引きの注意点
4194
+
4195
+ // 半角¥マーク問題
4196
+ // 半角¥マークは、Shift_JISの「5c 0xReverse Solidus 逆斜線」にする
4197
+ // Unicode '¥' 0x00a5 Yen Sign 半角円マーク
4198
+ unicode_to_cp932_map[0xA5] = 0x5C;
4199
+
4200
+ // 波線問題
4201
+ // SJIS2004上は 0x8160 と 0x81B0 とで区別されている。
4202
+ // Shift_JISは 0x301c を 0x8160 に統一
4203
+ // Unicode '〜' 0x301c Shift_JIS-2004 0x8160 Wave Dash 波ダッシュ
4204
+ // Unicode '~' 0xff5e Shift_JIS-2004 0x81B0 Fullwidth Tilde 全角チルダ
4205
+ unicode_to_cp932_map[0x301C] = 0x8160;
4206
+
4207
+ // マイナス問題
4208
+ // SJIS2004上は 0x817c と 0x81af とで区別されている。
4209
+ // Shift_JISは、0x2212 を全角負記号 0x817c へ変更
4210
+ // Unicode `−` 0x2212 Shift_JIS-2004 0x817c 負符号/減算記号
4211
+ // Unicode `-` 0xff0d Shift_JIS-2004 0x81af ハイフンマイナス
4212
+ unicode_to_cp932_map[0x2212] = 0x817C;
4213
+
4214
+ CP932MAP.cp932_to_unicode_map = cp932_to_unicode_map;
4215
+ CP932MAP.unicode_to_cp932_map = unicode_to_cp932_map;
4216
+ }
4217
+
4218
+ /**
4219
+ * @returns {Record<number, number>}
4220
+ */
4221
+ static CP932_TO_UNICODE() {
4222
+ CP932MAP.init();
4223
+ return CP932MAP.cp932_to_unicode_map;
4224
+ }
4225
+
4226
+ /**
4227
+ * @returns {Record<number, number>}
4228
+ */
4229
+ static UNICODE_TO_CP932() {
4230
+ CP932MAP.init();
4231
+ return CP932MAP.unicode_to_cp932_map;
4232
+ }
4233
+ }
4234
+
4235
+ /**
4236
+ * 変換マップを初期化したかどうか
4237
+ * @type {boolean}
4238
+ */
4239
+ CP932MAP.is_initmap = false;
4240
+
4241
+ /**
4242
+ * 変換用マップ
4243
+ * @type {Record<number, number>}
4244
+ */
4245
+ CP932MAP.cp932_to_unicode_map = null;
4246
+
4247
+ /**
4248
+ * 変換用マップ
4249
+ * @type {Record<number, number>}
4250
+ */
4251
+ CP932MAP.unicode_to_cp932_map = null;
4252
+
4253
+ /**
4254
+ * CP932, Windows-31J を扱うクラス
4255
+ * @ignore
4256
+ */
4257
+ class CP932 {
4258
+ /**
4259
+ * Unicode のコードから CP932 のコードに変換
4260
+ * @param {number} unicode_codepoint - Unicode のコードポイント
4261
+ * @returns {number} CP932 のコードポイント (存在しない場合は undefined)
4262
+ */
4263
+ static toCP932FromUnicode(unicode_codepoint) {
4264
+ return CP932MAP.UNICODE_TO_CP932()[unicode_codepoint];
4265
+ }
4266
+
4267
+ /**
4268
+ * CP932 のコードから Unicode のコードに変換
4269
+ * @param {number} cp932_codepoint - CP932 のコードポイント
4270
+ * @returns {number} Unicode のコードポイント (存在しない場合は undefined)
4271
+ */
4272
+ static toUnicodeFromCP932(cp932_codepoint) {
4273
+ return CP932MAP.CP932_TO_UNICODE()[cp932_codepoint];
4274
+ }
4275
+
4276
+ /**
4277
+ * 文字列を CP932 の配列に変換。変換できない文字は "?" に変換される。
4278
+ * @param {string} text - 変換したいテキスト
4279
+ * @returns {number[]} CP932 のデータが入った配列
4280
+ */
4281
+ static toCP932Array(text) {
4282
+ return SJIS.toSJISArray(text, CP932MAP.UNICODE_TO_CP932());
4283
+ }
4284
+
4285
+ /**
4286
+ * 文字列を CP932 のバイナリ配列に変換。変換できない文字は "?" に変換される。
4287
+ * - 日本語文字は2バイトとして、配列も2つ分、使用します。
4288
+ * @param {string} text - 変換したいテキスト
4289
+ * @returns {number[]} CP932 のデータが入ったバイナリ配列
4290
+ */
4291
+ static toCP932Binary(text) {
4292
+ return SJIS.toSJISBinary(text, CP932MAP.UNICODE_TO_CP932());
4293
+ }
4294
+
4295
+ /**
4296
+ * CP932 の配列から文字列に変換
4297
+ * @param {number[]} cp932 - 変換したいテキスト
4298
+ * @returns {string} 変換後のテキスト
4299
+ */
4300
+ static fromCP932Array(cp932) {
4301
+ return SJIS.fromSJISArray(cp932, CP932MAP.CP932_TO_UNICODE());
4302
+ }
4303
+
4304
+ /**
4305
+ * 指定した文字から Windows-31J 上の区点番号に変換
4306
+ * - 2文字以上を指定した場合は、1文字目のみを変換する
4307
+ * @param {string} text - 変換したいテキスト
4308
+ * @returns {MenKuTen} 区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
4309
+ */
4310
+ static toKuTen(text) {
4311
+ if (text.length === 0) {
4312
+ return null;
4313
+ }
4314
+ const cp932_code = CP932.toCP932FromUnicode(Unicode.toUTF32Array(text)[0]);
4315
+ return cp932_code ? SJIS.toKuTenFromSJISCode(cp932_code) : null;
4316
+ }
4317
+ }
4318
+
4319
+ /**
4320
+ * The script is part of Mojix for TextInputGuard.
4321
+ *
4322
+ * AUTHOR:
4323
+ * natade-jp (https://github.com/natade-jp)
4324
+ *
4325
+ * LICENSE:
4326
+ * The MIT license https://opensource.org/licenses/MIT
4327
+ */
4328
+
4329
+
4330
+ /**
4331
+ * Encode用のツールクラス
4332
+ * @ignore
4333
+ */
4334
+ class EncodeTools {
4335
+ /**
4336
+ * キャラセット名の正規化
4337
+ * @param {string} charset
4338
+ * @returns {string}
4339
+ */
4340
+ static normalizeCharSetName(charset) {
4341
+ let x1, x2;
4342
+ let is_with_bom = false;
4343
+ // BOM の文字がある場合は BOM 付きとする
4344
+ if (/^bom\s+|\s+bom\s+|\s+bom$/i.test(x1)) {
4345
+ is_with_bom = true;
4346
+ x1 = charset.replace(/^bom\s+|(\s+with)?\s+bom\s+|(\s+with\s*)?\s+bom$/, "");
4347
+ } else {
4348
+ x1 = charset;
4349
+ }
4350
+ if (/^(unicode-1-1-utf-8|UTF[-_]?8)$/i.test(x1)) {
4351
+ x2 = "UTF-8";
4352
+ } else if (/^(csunicode|iso-10646-ucs-2|ucs-2|Unicode|UnicodeFEFF|UTF[-_]?16([-_]?LE)?)$/i.test(x1)) {
4353
+ x2 = "UTF-16LE";
4354
+ } else if (/^(UnicodeFFFE|UTF[-_]?16[-_]?BE)$/i.test(x1)) {
4355
+ x2 = "UTF-16BE";
4356
+ } else if (/^(utf32_littleendian|UTF[-_]?32([-_]?LE)?)$/i.test(x1)) {
4357
+ x2 = "UTF-32LE";
4358
+ } else if (/^(utf32_bigendian|UTF[-_]?32[-_]?BE)$/i.test(x1)) {
4359
+ x2 = "UTF-32BE";
4360
+ } else if (/^(csshiftjis|ms_kanji|(cp|ms)932|shift[-_]?jis|sjis|Windows[-_]?31J|x-sjis)$/i.test(x1)) {
4361
+ x2 = "Shift_JIS";
4362
+ } else {
4363
+ x2 = x1;
4364
+ }
4365
+ if (is_with_bom) {
4366
+ x2 += " with BOM";
4367
+ }
4368
+ return x2;
4369
+ }
4370
+
4371
+ /**
4372
+ * 同一の種別の文字列の重なりをカウントする
4373
+ * @param {number[]} utf32_array
4374
+ * @returns {number}
4375
+ */
4376
+ static countWord(utf32_array) {
4377
+ let count = 0;
4378
+ let type;
4379
+ let old_type = -1;
4380
+ for (let i = 0; i < utf32_array.length; i++) {
4381
+ const ch = utf32_array[i];
4382
+ // a-zA-Z
4383
+ // prettier-ignore
4384
+ if ((0x41 <= ch && ch <= 0x5A) || (0x61 <= ch && ch <= 0x6A)) {
4385
+ type = 1;
4386
+ // prettier-ignore
4387
+ } else if (0x30 <= ch && ch <= 0x39) {
4388
+ // 0-9
4389
+ type = 2;
4390
+ // prettier-ignore
4391
+ } else if (0x3041 <= ch && ch <= 0x3093) {
4392
+ // ぁ-ん
4393
+ type = 3;
4394
+ // prettier-ignore
4395
+ } else if (0x30A1 <= ch && ch <= 0x30F3) {
4396
+ // ァ-ン
4397
+ type = 4;
4398
+ // prettier-ignore
4399
+ } else if ((0xFF21 <= ch && ch <= 0xFF3A) || (0xFF41 <= ch && ch <= 0xFF5A)) {
4400
+ // 全角英字
4401
+ type = 5;
4402
+ } else if (0xFF10 <= ch && ch <= 0xFF19) {
4403
+ // 全角数値
4404
+ type = 6;
4405
+ // prettier-ignore
4406
+ } else if (0xFF61 <= ch && ch < 0xFFA0) {
4407
+ // 半角カタカナ
4408
+ type = 7;
4409
+ // prettier-ignore
4410
+ } else if ((0x3400 <= ch && ch < 0xA000) || (0x20000 <= ch && ch < 0x2FA20)) {
4411
+ // CJK統合漢字拡張A - CJK統合漢字, 追加漢字面
4412
+ type = 8;
4413
+ } else {
4414
+ old_type = -1;
4415
+ continue;
4416
+ }
4417
+ if (type === old_type) {
4418
+ count++;
4419
+ }
4420
+ old_type = type;
4421
+ }
4422
+ return count;
4423
+ }
4424
+ }
4425
+
4426
+ /**
4427
+ * 文字データのバイナリへのエンコード、文字列へのデコードを扱うクラス
4428
+ * @ignore
4429
+ */
4430
+ class Encode {
4431
+ /**
4432
+ * 文字列からバイナリ配列にエンコードする
4433
+ * @param {string} text - 変換したいテキスト
4434
+ * @param {string} charset - キャラセット(UTF-8/16/32,Shift_JIS,Windows-31J,Shift_JIS-2004,EUC-JP,EUC-JP-2004)
4435
+ * @param {boolean} [is_with_bom=true] - BOMをつけるかどうか
4436
+ * @returns {number[]} バイナリ配列(失敗時はnull)
4437
+ */
4438
+ static encode(text, charset, is_with_bom) {
4439
+ const ncharset = charset ? EncodeTools.normalizeCharSetName(charset) : "autodetect";
4440
+ if (/^UTF-(8|16|32)/i.test(ncharset)) {
4441
+ const utf32_array = Unicode.toUTF32Array(text);
4442
+ return Unicode.toUTFBinaryFromCodePoint(utf32_array, ncharset, is_with_bom);
4443
+ } else if (/^Shift_JIS$/i.test(ncharset)) {
4444
+ return CP932.toCP932Binary(text);
4445
+ }
4446
+ return null;
4447
+ }
4448
+
4449
+ /**
4450
+ * バイナリ配列から文字列にデコードする
4451
+ * @param {number[]} binary - 変換したいバイナリ配列
4452
+ * @param {string} [charset="autodetect"] - キャラセット(UTF-8/16/32,Shift_JIS)
4453
+ * @returns {string} 変換した文字列(失敗したらnull)
4454
+ */
4455
+ static decode(binary, charset) {
4456
+ const ncharset = charset ? EncodeTools.normalizeCharSetName(charset) : "autodetect";
4457
+ if (/^UTF-(8|16|32)/i.test(ncharset)) {
4458
+ const ret = Unicode.toCodePointFromUTFBinary(binary, charset);
4459
+ if (ret) {
4460
+ return Unicode.fromUTF32Array(ret);
4461
+ }
4462
+ } else if (/^Shift_JIS$/i.test(ncharset)) {
4463
+ return CP932.fromCP932Array(binary);
4464
+ }
4465
+ return null;
4466
+ }
4467
+ }
4468
+
4469
+ /**
4470
+ * The script is part of Mojix for TextInputGuard.
4471
+ *
4472
+ * AUTHOR:
4473
+ * natade-jp (https://github.com/natade-jp)
4474
+ *
4475
+ * LICENSE:
4476
+ * The MIT license https://opensource.org/licenses/MIT
4477
+ */
4478
+
4479
+
4480
+ /**
4481
+ * 日本語を扱うクラス
4482
+ * @ignore
4483
+ */
4484
+ class Japanese {
4485
+ /**
4486
+ * カタカナをひらがなに変換
4487
+ * @param {string} text - 変換したいテキスト
4488
+ * @returns {string} 変換後のテキスト
4489
+ */
4490
+ static toHiragana(text) {
4491
+ /**
4492
+ * @param {string} ch
4493
+ */
4494
+ const func = function (ch) {
4495
+ // prettier-ignore
4496
+ return String.fromCharCode(ch.charCodeAt(0) - 0x0060);
4497
+ };
4498
+ return text.replace(/[\u30A1-\u30F6]/g, func);
4499
+ }
4500
+
4501
+ /**
4502
+ * ひらがなをカタカナに変換
4503
+ * @param {string} text - 変換したいテキスト
4504
+ * @returns {string} 変換後のテキスト
4505
+ */
4506
+ static toKatakana(text) {
4507
+ /**
4508
+ * @param {string} ch
4509
+ */
4510
+ const func = function (ch) {
4511
+ // prettier-ignore
4512
+ return String.fromCharCode(ch.charCodeAt(0) + 0x0060);
4513
+ };
4514
+ return text.replace(/[\u3041-\u3096]/g, func);
4515
+ }
4516
+
4517
+ /**
4518
+ * スペースを半角に変換
4519
+ * @param {string} text - 変換したいテキスト
4520
+ * @returns {string} 変換後のテキスト
4521
+ */
4522
+ static toHalfWidthSpace(text) {
4523
+ // prettier-ignore
4524
+ return text.replace(/\u3000/g, String.fromCharCode(0x0020));
4525
+ }
4526
+
4527
+ /**
4528
+ * スペースを全角に変換
4529
+ * @param {string} text - 変換したいテキスト
4530
+ * @returns {string} 変換後のテキスト
4531
+ */
4532
+ static toFullWidthSpace(text) {
4533
+ // prettier-ignore
4534
+ return text.replace(/\u0020/g, String.fromCharCode(0x3000));
4535
+ }
4536
+
4537
+ /**
4538
+ * 英数記号を半角に変換
4539
+ * @param {string} text - 変換したいテキスト
4540
+ * @returns {string} 変換後のテキスト
4541
+ */
4542
+ static toHalfWidthAsciiCode(text) {
4543
+ let out = text;
4544
+ out = out.replace(/\u3000/g, "\u0020"); //全角スペース
4545
+ out = out.replace(/[\u2018-\u201B]/g, "\u0027"); //シングルクォーテーション
4546
+ out = out.replace(/[\u201C-\u201F]/g, "\u0022"); //ダブルクォーテーション
4547
+ /**
4548
+ * @param {string} ch
4549
+ */
4550
+ const func = function (ch) {
4551
+ const code = ch.charCodeAt(0);
4552
+ // prettier-ignore
4553
+ return String.fromCharCode(code - 0xFEE0);
4554
+ };
4555
+ return out.replace(/[\uFF01-\uFF5E]/g, func);
4556
+ }
4557
+
4558
+ /**
4559
+ * 英数記号を全角に変換
4560
+ * @param {string} text - 変換したいテキスト
4561
+ * @returns {string} 変換後のテキスト
4562
+ */
4563
+ static toFullWidthAsciiCode(text) {
4564
+ let out = text;
4565
+ out = out.replace(/\u0020/g, "\u3000"); //全角スペース
4566
+ out = out.replace(/\u0022/g, "\u201D"); //ダブルクォーテーション
4567
+ out = out.replace(/\u0027/g, "\u2019"); //アポストロフィー
4568
+ /**
4569
+ * @param {string} ch
4570
+ */
4571
+ const func = function (ch) {
4572
+ const code = ch.charCodeAt(0);
4573
+ // prettier-ignore
4574
+ return String.fromCharCode(code + 0xFEE0);
4575
+ };
4576
+ return out.replace(/[\u0020-\u007E]/g, func);
4577
+ }
4578
+
4579
+ /**
4580
+ * アルファベットを半角に変換
4581
+ * @param {string} text - 変換したいテキスト
4582
+ * @returns {string} 変換後のテキスト
4583
+ */
4584
+ static toHalfWidthAlphabet(text) {
4585
+ /**
4586
+ * @param {string} ch
4587
+ */
4588
+ const func = function (ch) {
4589
+ // prettier-ignore
4590
+ return String.fromCharCode(ch.charCodeAt(0) - 0xFEE0);
4591
+ };
4592
+ return text.replace(/[\uFF21-\uFF3A\uFF41-\uFF5A]/g, func);
4593
+ }
4594
+
4595
+ /**
4596
+ * アルファベットを全角に変換
4597
+ * @param {string} text - 変換したいテキスト
4598
+ * @returns {string} 変換後のテキスト
4599
+ */
4600
+ static toFullWidthAlphabet(text) {
4601
+ /**
4602
+ * @param {string} ch
4603
+ */
4604
+ const func = function (ch) {
4605
+ // prettier-ignore
4606
+ return String.fromCharCode(ch.charCodeAt(0) + 0xFEE0);
4607
+ };
4608
+ return text.replace(/[A-Za-z]/g, func);
4609
+ }
4610
+
4611
+ /**
4612
+ * 数値を半角に変換
4613
+ * @param {string} text - 変換したいテキスト
4614
+ * @returns {string} 変換後のテキスト
4615
+ */
4616
+ static toHalfWidthNumber(text) {
4617
+ /**
4618
+ * @param {string} ch
4619
+ */
4620
+ const func = function (ch) {
4621
+ // prettier-ignore
4622
+ return String.fromCharCode(ch.charCodeAt(0) - 0xFEE0);
4623
+ };
4624
+ return text.replace(/[\uFF10-\uFF19]/g, func);
4625
+ }
4626
+
4627
+ /**
4628
+ * 数値を全角に変換
4629
+ * @param {string} text - 変換したいテキスト
4630
+ * @returns {string} 変換後のテキスト
4631
+ */
4632
+ static toFullWidthNumber(text) {
4633
+ /**
4634
+ * @param {string} ch
4635
+ */
4636
+ const func = function (ch) {
4637
+ // prettier-ignore
4638
+ return String.fromCharCode(ch.charCodeAt(0) + 0xFEE0);
4639
+ };
4640
+ return text.replace(/[0-9]/g, func);
4641
+ }
4642
+
4643
+ /**
4644
+ * カタカナを半角に変換
4645
+ * @param {string} text - 変換したいテキスト
4646
+ * @returns {string} 変換後のテキスト
4647
+ */
4648
+ static toHalfWidthKana(text) {
4649
+ /**
4650
+ * @type {Object<number, string>}
4651
+ */
4652
+ // prettier-ignore
4653
+ const map = {
4654
+ 0x3001: "\uFF64", // 、
4655
+ 0x3002: "\uFF61", // 。 。
4656
+ 0x300C: "\uFF62", // 「 「
4657
+ 0x300D: "\uFF63", // 」 」
4658
+ 0x309B: "\uFF9E", // ゛ ゙
4659
+ 0x309C: "\uFF9F", // ゜ ゚
4660
+ 0x30A1: "\uFF67", // ァ ァ
4661
+ 0x30A2: "\uFF71", // ア ア
4662
+ 0x30A3: "\uFF68", // ィ ィ
4663
+ 0x30A4: "\uFF72", // イ イ
4664
+ 0x30A5: "\uFF69", // ゥ ゥ
4665
+ 0x30A6: "\uFF73", // ウ ウ
4666
+ 0x30A7: "\uFF6A", // ェ ェ
4667
+ 0x30A8: "\uFF74", // エ エ
4668
+ 0x30A9: "\uFF6B", // ォ ォ
4669
+ 0x30AA: "\uFF75", // オ オ
4670
+ 0x30AB: "\uFF76", // カ カ
4671
+ 0x30AC: "\uFF76\uFF9E", // ガ ガ
4672
+ 0x30AD: "\uFF77", // キ キ
4673
+ 0x30AE: "\uFF77\uFF9E", // ギ ギ
4674
+ 0x30AF: "\uFF78", // ク ク
4675
+ 0x30B0: "\uFF78\uFF9E", // グ グ
4676
+ 0x30B1: "\uFF79", // ケ ケ
4677
+ 0x30B2: "\uFF79\uFF9E", // ゲ ゲ
4678
+ 0x30B3: "\uFF7A", // コ コ
4679
+ 0x30B4: "\uFF7A\uFF9E", // ゴ ゴ
4680
+ 0x30B5: "\uFF7B", // サ サ
4681
+ 0x30B6: "\uFF7B\uFF9E", // ザ ザ
4682
+ 0x30B7: "\uFF7C", // シ シ
4683
+ 0x30B8: "\uFF7C\uFF9E", // ジ ジ
4684
+ 0x30B9: "\uFF7D", // ス ス
4685
+ 0x30BA: "\uFF7D\uFF9E", // ズ ズ
4686
+ 0x30BB: "\uFF7E", // セ セ
4687
+ 0x30BC: "\uFF7E\uFF9E", // ゼ ゼ
4688
+ 0x30BD: "\uFF7F", // ソ ソ
4689
+ 0x30BE: "\uFF7F\uFF9E", // ゾ ゾ
4690
+ 0x30BF: "\uFF80", // タ タ
4691
+ 0x30C0: "\uFF80\uFF9E", // ダ ダ
4692
+ 0x30C1: "\uFF81", // チ チ
4693
+ 0x30C2: "\uFF81\uFF9E", // ヂ ヂ
4694
+ 0x30C3: "\uFF6F", // ッ ッ
4695
+ 0x30C4: "\uFF82", // ツ ツ
4696
+ 0x30C5: "\uFF82\uFF9E", // ヅ ヅ
4697
+ 0x30C6: "\uFF83", // テ テ
4698
+ 0x30C7: "\uFF83\uFF9E", // デ デ
4699
+ 0x30C8: "\uFF84", // ト ト
4700
+ 0x30C9: "\uFF84\uFF9E", // ド ド
4701
+ 0x30CA: "\uFF85", // ナ ナ
4702
+ 0x30CB: "\uFF86", // ニ ニ
4703
+ 0x30CC: "\uFF87", // ヌ ヌ
4704
+ 0x30CD: "\uFF88", // ネ ネ
4705
+ 0x30CE: "\uFF89", // ノ ノ
4706
+ 0x30CF: "\uFF8A", // ハ ハ
4707
+ 0x30D0: "\uFF8A\uFF9E", // バ バ
4708
+ 0x30D1: "\uFF8A\uFF9F", // パ パ
4709
+ 0x30D2: "\uFF8B", // ヒ ヒ
4710
+ 0x30D3: "\uFF8B\uFF9E", // ビ ビ
4711
+ 0x30D4: "\uFF8B\uFF9F", // ピ ピ
4712
+ 0x30D5: "\uFF8C", // フ フ
4713
+ 0x30D6: "\uFF8C\uFF9E", // ブ ブ
4714
+ 0x30D7: "\uFF8C\uFF9F", // プ プ
4715
+ 0x30D8: "\uFF8D", // ヘ ヘ
4716
+ 0x30D9: "\uFF8D\uFF9E", // ベ ベ
4717
+ 0x30DA: "\uFF8D\uFF9F", // ペ ペ
4718
+ 0x30DB: "\uFF8E", // ホ ホ
4719
+ 0x30DC: "\uFF8E\uFF9E", // ボ ボ
4720
+ 0x30DD: "\uFF8E\uFF9F", // ポ ポ
4721
+ 0x30DE: "\uFF8F", // マ マ
4722
+ 0x30DF: "\uFF90", // ミ ミ
4723
+ 0x30E0: "\uFF91", // ム ム
4724
+ 0x30E1: "\uFF92", // メ メ
4725
+ 0x30E2: "\uFF93", // モ モ
4726
+ 0x30E3: "\uFF6C", // ャ ャ
4727
+ 0x30E4: "\uFF94", // ヤ ヤ
4728
+ 0x30E5: "\uFF6D", // ュ ュ
4729
+ 0x30E6: "\uFF95", // ユ ユ
4730
+ 0x30E7: "\uFF6E", // ョ ョ
4731
+ 0x30E8: "\uFF96", // ヨ ヨ
4732
+ 0x30E9: "\uFF97", // ラ ラ
4733
+ 0x30EA: "\uFF98", // リ リ
4734
+ 0x30EB: "\uFF99", // ル ル
4735
+ 0x30EC: "\uFF9A", // レ レ
4736
+ 0x30ED: "\uFF9B", // ロ ロ
4737
+ 0x30EE: "\uFF9C", // ヮ ワ
4738
+ 0x30EF: "\uFF9C", // ワ ワ
4739
+ 0x30F0: "\uFF72", // ヰ イ
4740
+ 0x30F1: "\uFF74", // ヱ エ
4741
+ 0x30F2: "\uFF66", // ヲ ヲ
4742
+ 0x30F3: "\uFF9D", // ン ン
4743
+ 0x30F4: "\uFF73\uFF9E", // ヴ ヴ
4744
+ 0x30F5: "\uFF76", // ヵ カ
4745
+ 0x30F6: "\uFF79", // ヶ ケ
4746
+ 0x30F7: "\uFF9C\uFF9E", // ヷ ヷ
4747
+ 0x30F8: "\uFF72\uFF9E", // ヸ イ゙
4748
+ 0x30F9: "\uFF74\uFF9E", // ヹ エ゙
4749
+ 0x30FA: "\uFF66\uFF9E", // ヺ ヺ
4750
+ 0x30FB: "\uFF65", // ・ ・
4751
+ 0x30FC: "\uFF70" // ー ー
4752
+ };
4753
+ /**
4754
+ * @param {string} ch
4755
+ */
4756
+ const func = function (ch) {
4757
+ if (ch.length === 1) {
4758
+ return map[ch.charCodeAt(0)];
4759
+ } else {
4760
+ return map[ch.charCodeAt(0)] + map[ch.charCodeAt(1)];
4761
+ }
4762
+ };
4763
+ return text.replace(/[\u3001\u3002\u300C\u300D\u309B\u309C\u30A1-\u30FC][\u309B\u309C]?/g, func);
4764
+ }
4765
+
4766
+ /**
4767
+ * カタカナを全角に変換
4768
+ * @param {string} text - 変換したいテキスト
4769
+ * @returns {string} 変換後のテキスト
4770
+ */
4771
+ static toFullWidthKana(text) {
4772
+ /**
4773
+ * @type {Record<number, number>}
4774
+ */
4775
+ // prettier-ignore
4776
+ const map = {
4777
+ 0xFF61: 0x3002, // 。 。
4778
+ 0xFF62: 0x300C, // 「 「
4779
+ 0xFF63: 0x300D, // 」 」
4780
+ 0xFF64: 0x3001, // 、
4781
+ 0xFF65: 0x30FB, // ・ ・
4782
+ 0xFF66: 0x30F2, // ヲ ヲ
4783
+ 0xFF67: 0x30A1, // ァ ァ
4784
+ 0xFF68: 0x30A3, // ィ ィ
4785
+ 0xFF69: 0x30A5, // ゥ ゥ
4786
+ 0xFF6A: 0x30A7, // ェ ェ
4787
+ 0xFF6B: 0x30A9, // ォ ォ
4788
+ 0xFF6C: 0x30E3, // ャ ャ
4789
+ 0xFF6D: 0x30E5, // ュ ュ
4790
+ 0xFF6E: 0x30E7, // ョ ョ
4791
+ 0xFF6F: 0x30C3, // ッ ッ
4792
+ 0xFF70: 0x30FC, // ー ー
4793
+ 0xFF71: 0x30A2, // ア ア
4794
+ 0xFF72: 0x30A4, // イ イ
4795
+ 0xFF73: 0x30A6, // ウ ウ
4796
+ 0xFF74: 0x30A8, // エ エ
4797
+ 0xFF75: 0x30AA, // オ オ
4798
+ 0xFF76: 0x30AB, // カ カ
4799
+ 0xFF77: 0x30AD, // キ キ
4800
+ 0xFF78: 0x30AF, // ク ク
4801
+ 0xFF79: 0x30B1, // ケ ケ
4802
+ 0xFF7A: 0x30B3, // コ コ
4803
+ 0xFF7B: 0x30B5, // サ サ
4804
+ 0xFF7C: 0x30B7, // シ シ
4805
+ 0xFF7D: 0x30B9, // ス ス
4806
+ 0xFF7E: 0x30BB, // セ セ
4807
+ 0xFF7F: 0x30BD, // ソ ソ
4808
+ 0xFF80: 0x30BF, // タ タ
4809
+ 0xFF81: 0x30C1, // チ チ
4810
+ 0xFF82: 0x30C4, // ツ ツ
4811
+ 0xFF83: 0x30C6, // テ テ
4812
+ 0xFF84: 0x30C8, // ト ト
4813
+ 0xFF85: 0x30CA, // ナ ナ
4814
+ 0xFF86: 0x30CB, // ニ ニ
4815
+ 0xFF87: 0x30CC, // ヌ ヌ
4816
+ 0xFF88: 0x30CD, // ネ ネ
4817
+ 0xFF89: 0x30CE, // ノ ノ
4818
+ 0xFF8A: 0x30CF, // ハ ハ
4819
+ 0xFF8B: 0x30D2, // ヒ ヒ
4820
+ 0xFF8C: 0x30D5, // フ フ
4821
+ 0xFF8D: 0x30D8, // ヘ ヘ
4822
+ 0xFF8E: 0x30DB, // ホ ホ
4823
+ 0xFF8F: 0x30DE, // マ マ
4824
+ 0xFF90: 0x30DF, // ミ ミ
4825
+ 0xFF91: 0x30E0, // ム ム
4826
+ 0xFF92: 0x30E1, // メ メ
4827
+ 0xFF93: 0x30E2, // モ モ
4828
+ 0xFF94: 0x30E4, // ヤ ヤ
4829
+ 0xFF95: 0x30E6, // ユ ユ
4830
+ 0xFF96: 0x30E8, // ヨ ヨ
4831
+ 0xFF97: 0x30E9, // ラ ラ
4832
+ 0xFF98: 0x30EA, // リ リ
4833
+ 0xFF99: 0x30EB, // ル ル
4834
+ 0xFF9A: 0x30EC, // レ レ
4835
+ 0xFF9B: 0x30ED, // ロ ロ
4836
+ 0xFF9C: 0x30EF, // ワ ワ
4837
+ 0xFF9D: 0x30F3, // ン ン
4838
+ 0xFF9E: 0x309B, // ゛ ゙
4839
+ 0xFF9F: 0x309C // ゜ ゚
4840
+ };
4841
+ /**
4842
+ * @param {string} str
4843
+ */
4844
+ const func = function (str) {
4845
+ if (str.length === 1) {
4846
+ return String.fromCharCode(map[str.charCodeAt(0)]);
4847
+ } else {
4848
+ const next = str.charCodeAt(1);
4849
+ const ch = str.charCodeAt(0);
4850
+ if (next === 0xFF9E) {
4851
+ // Shift-JISにない濁点(ヷ、ヸ、ヹ、ヺ)は意図的に無視
4852
+ if (ch === 0xFF73) {
4853
+ // ヴ
4854
+ return String.fromCharCode(0x3094);
4855
+ } else if ((0xFF76 <= ch && ch <= 0xFF84) || (0xFF8A <= ch && ch <= 0xFF8E)) {
4856
+ // ガ-ド、バ-ボ
4857
+ return String.fromCharCode(map[ch] + 1);
4858
+ }
4859
+ } else if (next === 0xFF9F) {
4860
+ // 半濁点
4861
+ if (0xFF8A <= ch && ch <= 0xFF8E) {
4862
+ // パ-ポ
4863
+ return String.fromCharCode(map[ch] + 2);
4864
+ }
4865
+ }
4866
+ return String.fromCharCode(map[ch]) + String.fromCharCode(map[next]);
4867
+ }
4868
+ };
4869
+ return text.replace(/[\uFF61-\uFF9F][\uFF9E\uFF9F]?/g, func);
4870
+ }
4871
+
4872
+ /**
4873
+ * 半角に変換
4874
+ * @param {string} text - 変換したいテキスト
4875
+ * @returns {string} 変換後のテキスト
4876
+ */
4877
+ static toHalfWidth(text) {
4878
+ return Japanese.toHalfWidthKana(Japanese.toHalfWidthAsciiCode(text));
4879
+ }
4880
+
4881
+ /**
4882
+ * 全角に変換
4883
+ * @param {string} text - 変換したいテキスト
4884
+ * @returns {string} 変換後のテキスト
4885
+ */
4886
+ static toFullWidth(text) {
4887
+ return Japanese.toFullWidthKana(Japanese.toFullWidthAsciiCode(text));
4888
+ }
4889
+
4890
+ /**
4891
+ * 指定したコードポイントの横幅を推定して取得します
4892
+ * - 0幅 ... グラフェムを構成する要素
4893
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
4894
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
4895
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
4896
+ * - 2幅 ... 上記以外
4897
+ * @param {number} cp1 調査するコードポイント
4898
+ * @param {number} [cp2] 調査するコードポイント
4899
+ * @returns {number} 文字の横幅
4900
+ */
4901
+ static getWidthFromCodePoint(cp1, cp2) {
4902
+ if (cp2 !== undefined) {
4903
+ if (Unicode.isRegionalIndicatorContinuation(cp1, cp2)) {
4904
+ return 2;
4905
+ }
4906
+ }
4907
+ if (Unicode.isGraphemeComponentFromCodePoint(cp1) || Unicode.isZeroWidthCharacterFromCodePoint(cp1)) {
4908
+ return 0;
4909
+ // prettier-ignore
4910
+ } else if (cp1 < 0x80 || (0xFF61 <= cp1 && cp1 < 0xFFA0) || Unicode.isRegionalIndicatorFromCodePoint(cp1)) {
4911
+ return 1;
4912
+ } else {
4913
+ return 2;
4914
+ }
4915
+ }
4916
+
4917
+ /**
4918
+ * 指定したテキストの横幅を半角/全角でカウント
4919
+ * - 0幅 ... グラフェムを構成する要素
4920
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
4921
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
4922
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
4923
+ * - 2幅 ... 上記以外
4924
+ * @param {string} text - カウントしたいテキスト
4925
+ * @returns {number} 文字の横幅
4926
+ */
4927
+ static getWidth(text) {
4928
+ const utf32_array = Unicode.toUTF32Array(text);
4929
+ let count = 0;
4930
+ let isZWJ = false;
4931
+ for (let i = 0; i < utf32_array.length; i++) {
4932
+ const cp = utf32_array[i];
4933
+ // 国旗 (Regional Indicator)
4934
+ if (i < utf32_array.length - 1) {
4935
+ const next = utf32_array[i + 1];
4936
+ if (Unicode.isRegionalIndicatorContinuation(cp, next)) {
4937
+ if (!isZWJ) {
4938
+ count += Japanese.getWidthFromCodePoint(cp, next);
4939
+ }
4940
+ i++;
4941
+ isZWJ = false;
4942
+ continue;
4943
+ }
4944
+ }
4945
+ if (!isZWJ) {
4946
+ count += Japanese.getWidthFromCodePoint(cp);
4947
+ }
4948
+ // prettier-ignore
4949
+ isZWJ = cp === 0x200D;
4950
+ }
4951
+ return count;
4952
+ }
4953
+
4954
+ /**
4955
+ * 文字幅を考慮して文字列を文字の配列に変換する
4956
+ * @param {string} text - 変換したいテキスト
4957
+ * @returns {number[][]} UTF32(コードポイント)の配列が入った配列
4958
+ */
4959
+ static toMojiArrayFromString(text) {
4960
+ const utf32_array = Unicode.toUTF32Array(text);
4961
+
4962
+ /** @type {number[][]} */
4963
+ const moji_array = [];
4964
+
4965
+ /** @type {number[]} */
4966
+ let moji = [];
4967
+
4968
+ let isZWJ = false;
4969
+
4970
+ for (let i = 0; i < utf32_array.length; i++) {
4971
+ const cp = utf32_array[i];
4972
+
4973
+ // --- 国旗 (Regional Indicator) は2つで1グラフェム ---
4974
+ if (i < utf32_array.length - 1) {
4975
+ const next = utf32_array[i + 1];
4976
+ if (Unicode.isRegionalIndicatorContinuation(cp, next)) {
4977
+ // 前のグラフェムを確定
4978
+ if (moji.length > 0) {
4979
+ moji_array.push(moji);
4980
+ }
4981
+ // RIペアで新しいグラフェムを作る
4982
+ moji = [cp, next];
4983
+
4984
+ moji_array.push(moji);
4985
+ moji = []; // 次のグラフェムに備える
4986
+
4987
+ i++; // 2つ目のRIを消費
4988
+ isZWJ = false;
4989
+ continue;
4990
+ }
4991
+ }
4992
+
4993
+ // --- 新しいグラフェム開始判定 ---
4994
+ // 「ZWJ直後」または「グラフェム構成要素」は前に結合させる
4995
+ const isComponent = Unicode.isGraphemeComponentFromCodePoint(cp);
4996
+
4997
+ if (!isZWJ && !isComponent) {
4998
+ // ベース文字が来たので、前のグラフェムを確定して新しく開始
4999
+ if (moji.length > 0) {
5000
+ moji_array.push(moji);
5001
+ }
5002
+ moji = [];
5003
+ }
5004
+
5005
+ moji.push(cp);
5006
+
5007
+ // 次ループ用:ZWJ は次の文字とグラフェムを結合するため、新しい境界を作らないフラグを立てる
5008
+ isZWJ = (cp === 0x200D);
5009
+ }
5010
+
5011
+ // 末尾が残っていれば追加
5012
+ if (moji.length > 0) {
5013
+ moji_array.push(moji);
5014
+ }
5015
+
5016
+ return moji_array;
5017
+ }
5018
+
5019
+ /**
5020
+ * 結合した文字を考慮して文字の配列を文字列に変換する
5021
+ * @param {number[][]} mojiarray - UTF32(コードポイント)の配列が入った配列
5022
+ * @returns {string} UTF32(コードポイント)の配列が入った配列
5023
+ */
5024
+ static toStringFromMojiArray(mojiarray) {
5025
+ /**
5026
+ * @type {number[]}
5027
+ */
5028
+ const utf32 = [];
5029
+ for (let i = 0; i < mojiarray.length; i++) {
5030
+ for (let j = 0; j < mojiarray[i].length; j++) {
5031
+ utf32.push(mojiarray[i][j]);
5032
+ }
5033
+ }
5034
+ return Unicode.fromUTF32Array(utf32);
5035
+ }
5036
+
5037
+ /**
5038
+ * 指定したテキストの横幅を半角/全角で換算した場合の切り出し
5039
+ * - 0幅 ... グラフェムを構成する要素
5040
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
5041
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
5042
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
5043
+ * - 2幅 ... 上記以外
5044
+ * @param {string} text - 切り出したいテキスト
5045
+ * @param {number} offset - 切り出し位置
5046
+ * @param {number} size - 切り出す長さ
5047
+ * @returns {string} 切り出したテキスト
5048
+ * @ignore
5049
+ */
5050
+ static cutTextForWidth(text, offset, size) {
5051
+ const moji_array = Japanese.toMojiArrayFromString(text);
5052
+ const SPACE = [0x20]; // ' '
5053
+ /**
5054
+ * @type {number[][]}
5055
+ */
5056
+ const output = [];
5057
+ let is_target = false;
5058
+ let position = 0;
5059
+ let cut_size = size;
5060
+ if (offset < 0) {
5061
+ cut_size += offset;
5062
+ offset = 0;
5063
+ }
5064
+ if (cut_size <= 0) {
5065
+ return "";
5066
+ }
5067
+ for (let i = 0; i < moji_array.length; i++) {
5068
+ // 文字データ
5069
+ const moji = moji_array[i];
5070
+ // 1文字目の横幅を取得
5071
+ const cp = moji[0];
5072
+ // ASCII文字, 半角カタカナ, Regional Indicator(単体)
5073
+ // prettier-ignore
5074
+ const cp_size = cp < 0x80
5075
+ || (0xFF61 <= cp && cp < 0xFFA0)
5076
+ || (moji.length === 1 && Unicode.isRegionalIndicatorFromCodePoint(cp)) ? 1 : 2;
5077
+ if (position >= offset) {
5078
+ is_target = true;
5079
+ if (cut_size >= cp_size) {
5080
+ output.push(moji);
5081
+ } else {
5082
+ output.push(SPACE);
5083
+ }
5084
+ cut_size -= cp_size;
5085
+ if (cut_size <= 0) {
5086
+ break;
5087
+ }
5088
+ }
5089
+ position += cp_size;
5090
+ // 2バイト文字の途中をoffset指定していた場合になる。
5091
+ if (position - 1 >= offset && !is_target) {
5092
+ cut_size--;
5093
+ output.push(SPACE);
5094
+ }
5095
+ }
5096
+ return Japanese.toStringFromMojiArray(output);
5097
+ }
5098
+ }
5099
+
5100
+ /**
5101
+ * The script is part of Mojix for TextInputGuard.
5102
+ *
5103
+ * AUTHOR:
5104
+ * natade-jp (https://github.com/natade-jp)
5105
+ *
5106
+ * LICENSE:
5107
+ * The MIT license https://opensource.org/licenses/MIT
5108
+ */
5109
+
5110
+
5111
+ /**
5112
+ * 文字のエンコード情報
5113
+ * @typedef {Object} MojiEncodeData
5114
+ * @property {MenKuTen} kuten 区点 コード
5115
+ * @property {number} cp932_code CP932(Windows-31J) コード
5116
+ * @property {number[]} utf8_array UTF-8 配列
5117
+ * @property {number[]} utf16_array UTF-16 配列
5118
+ * @property {number[]} utf32_array UTF-32 配列
5119
+ * @property {number[]} cp932_array CP932(Windows-31J) バイト配列
5120
+ * @property {number[]} shift_jis_array Shift_JIS バイト配列
5121
+ * @property {number[]} iso2022jp_array ISO-2022-JP バイト配列
5122
+ */
5123
+
5124
+ /**
5125
+ * 文字の種別情報
5126
+ * @typedef {Object} MojiTypeData
5127
+ * @property {boolean} is_regular_sjis Shift_JIS に登録された文字
5128
+ * @property {boolean} is_gaiji_cp932 Windows-31J(CP932) 外字
5129
+ * @property {boolean} is_IBM_extended_character Windows-31J(CP932) IBM拡張文字
5130
+ * @property {boolean} is_NEC_selection_IBM_extended_character Windows-31J(CP932) NEC選定IBM拡張文字
5131
+ * @property {boolean} is_NEC_special_character Windows-31J(CP932) NEC特殊文字
5132
+ * @property {number} kanji_suijun Shift_JIS-2004 を使用して漢字の水準調査(計算不可の場合 0)
5133
+ * @property {boolean} is_surrogate_pair 要 Unicode サロゲートペア
5134
+ * @property {string|null} control_name 制御文字名(制御文字ではない場合は null)
5135
+ * @property {boolean} is_control_character 制御文字
5136
+ * @property {string} blockname Unicodeブロック名
5137
+ * @property {boolean} is_kanji 漢字
5138
+ * @property {boolean} is_hiragana ひらがな
5139
+ * @property {boolean} is_katakana カタカナ
5140
+ * @property {boolean} is_fullwidth_ascii 全角ASCII
5141
+ * @property {boolean} is_halfwidth_katakana 半角カタカナ
5142
+ * @property {boolean} is_emoji 絵文字(絵文字表示されることが多い Unicode ブロックに属する文字)
5143
+ * @property {boolean} is_emoticons 顔文字(Emoticons ブロックに属する文字)
5144
+ * @property {boolean} is_symbol_base 記号(テキスト記号の定義だがVS16が続くと絵文字に切り替えが発生)
5145
+ * @property {boolean} is_gaiji 外字
5146
+ * @property {boolean} is_grapheme_component グラフェムを構成するための文字
5147
+ * @property {boolean} is_zero_width_character ゼロ幅文字
5148
+ * @property {boolean} is_combining_mark 結合文字
5149
+ * @property {boolean} is_variation_selector 異体字セレクタ
5150
+ * @property {boolean} is_skin_tone_modifier スキントーン修飾子
5151
+ * @property {boolean} is_tag_character タグ文字
5152
+ * @property {boolean} is_regional_indicator 国旗絵文字を構成するための Regional Indicator 文字(2文字で1つの国旗になる)
5153
+ */
5154
+
5155
+ /**
5156
+ * 文字の種別情報
5157
+ * @typedef {Object} MojiData
5158
+ * @property {MojiEncodeData} encode 文字のエンコード情報
5159
+ * @property {MojiTypeData} type 文字の種別情報
5160
+ * @property {string} character 解析した文字
5161
+ * @property {number} codepoint 解析した文字のコードポイント
5162
+ */
5163
+
5164
+ /**
5165
+ * 文字の解析用クラス
5166
+ * @ignore
5167
+ */
5168
+ class MojiAnalyzer {
5169
+ /**
5170
+ * 初期化
5171
+ * @returns {MojiData}
5172
+ * @ignore
5173
+ */
5174
+ static _createMojiData() {
5175
+ /**
5176
+ * @type {MojiEncodeData}
5177
+ */
5178
+ const encode = {
5179
+ kuten: null,
5180
+ cp932_code: 0,
5181
+ utf8_array: [],
5182
+ utf16_array: [],
5183
+ utf32_array: [],
5184
+ cp932_array: [],
5185
+ shift_jis_array: [],
5186
+ iso2022jp_array: []
5187
+ };
5188
+
5189
+ /**
5190
+ * @type {MojiTypeData}
5191
+ */
5192
+ const type = {
5193
+ is_regular_sjis: false,
5194
+ is_gaiji_cp932: false,
5195
+ is_IBM_extended_character: false,
5196
+ is_NEC_selection_IBM_extended_character: false,
5197
+ is_NEC_special_character: false,
5198
+ kanji_suijun: 0,
5199
+ is_surrogate_pair: false,
5200
+ control_name: null,
5201
+ is_control_character: false,
5202
+ blockname: "",
5203
+ is_kanji: false,
5204
+ is_hiragana: false,
5205
+ is_katakana: false,
5206
+ is_fullwidth_ascii: false,
5207
+ is_halfwidth_katakana: false,
5208
+ is_emoji: false,
5209
+ is_emoticons: false,
5210
+ is_symbol_base: false,
5211
+ is_gaiji: false,
5212
+ is_grapheme_component: false,
5213
+ is_zero_width_character: false,
5214
+ is_combining_mark: false,
5215
+ is_variation_selector: false,
5216
+ is_skin_tone_modifier: false,
5217
+ is_tag_character: false,
5218
+ is_regional_indicator: false
5219
+ };
5220
+
5221
+ /**
5222
+ * @type {MojiData}
5223
+ */
5224
+ const data = {
5225
+ encode: encode,
5226
+ type: type,
5227
+ character: null,
5228
+ codepoint: 0
5229
+ };
5230
+
5231
+ return data;
5232
+ }
5233
+
5234
+ /**
5235
+ * 指定した1つのUTF-32 コードポイントに関して、解析を行い情報を返します
5236
+ * @param {number} unicode_codepoint - UTF-32 のコードポイント
5237
+ * @returns {MojiData} 文字の情報がつまったオブジェクト
5238
+ */
5239
+ static getMojiData(unicode_codepoint) {
5240
+ // 基本情報取得
5241
+ const cp932code = CP932.toCP932FromUnicode(unicode_codepoint);
5242
+ const kuten = SJIS.toKuTenFromSJISCode(cp932code);
5243
+ const is_regular_sjis = cp932code < 0x100 || SJIS.isRegularMenKuten(kuten);
5244
+
5245
+ /**
5246
+ * 出力データの箱を用意
5247
+ * @type {MojiData}
5248
+ */
5249
+ const data = MojiAnalyzer._createMojiData();
5250
+ const encode = data.encode;
5251
+ const type = data.type;
5252
+ const character = Unicode.fromCodePoint(unicode_codepoint);
5253
+ data.character = character;
5254
+ data.codepoint = unicode_codepoint;
5255
+
5256
+ // 句点と面区点情報(ない場合はnullになる)
5257
+ encode.kuten = kuten;
5258
+ // コードの代入
5259
+ encode.cp932_code = cp932code ? cp932code : -1;
5260
+
5261
+ // Shift_JIS として許容されるか
5262
+ type.is_regular_sjis = is_regular_sjis;
5263
+
5264
+ // Windows-31J(CP932) に関しての調査
5265
+ // 外字, IBM拡張文字, NEC選定IBM拡張文字, NEC特殊文字
5266
+ // prettier-ignore
5267
+ type.is_gaiji_cp932 = cp932code ? 0xF040 <= cp932code && cp932code <= 0xF9FC : false;
5268
+ // prettier-ignore
5269
+ type.is_IBM_extended_character = cp932code ? 0xFA40 <= cp932code && cp932code <= 0xFC4B : false;
5270
+ // prettier-ignore
5271
+ type.is_NEC_selection_IBM_extended_character = cp932code ? 0xED40 <= cp932code && cp932code <= 0xEEFC : false;
5272
+ // prettier-ignore
5273
+ type.is_NEC_special_character = cp932code ? 0x8740 <= cp932code && cp932code <= 0x879C : false;
5274
+
5275
+ // Unicodeの配列
5276
+ encode.utf8_array = Unicode.toUTF8Array(data.character);
5277
+ encode.utf16_array = Unicode.toUTF16Array(data.character);
5278
+ encode.utf32_array = [unicode_codepoint];
5279
+ type.is_surrogate_pair = encode.utf16_array.length > 1;
5280
+
5281
+ // SJIS系の配列
5282
+ // prettier-ignore
5283
+ encode.cp932_array = cp932code ? (cp932code >= 0x100 ? [cp932code >> 8, cp932code & 0xFF] : [cp932code]) : [];
5284
+
5285
+ // ISO-2022-JP , EUC-JP
5286
+ // prettier-ignore
5287
+ if (cp932code < 0xE0 || is_regular_sjis) {
5288
+ // prettier-ignore
5289
+ if (cp932code < 0x80) {
5290
+ encode.shift_jis_array = [cp932code];
5291
+ encode.iso2022jp_array = [];
5292
+ // prettier-ignore
5293
+ } else if (cp932code < 0xE0) {
5294
+ // 半角カタカナの扱い
5295
+ encode.shift_jis_array = [cp932code];
5296
+ encode.iso2022jp_array = [];
5297
+ } else if (kuten.ku <= 94) {
5298
+ // 区点は94まで利用できる。
5299
+ // つまり、最大でも 94 + 0xA0 = 0xFE となり 0xFF 以上にならない
5300
+ encode.shift_jis_array = [encode.cp932_array[0], encode.cp932_array[1]];
5301
+ encode.iso2022jp_array = [kuten.ku + 0x20, kuten.ten + 0x20];
5302
+ }
5303
+ } else {
5304
+ encode.shift_jis_array = [];
5305
+ encode.iso2022jp_array = [];
5306
+ }
5307
+ // SJISとして正規でなければ強制エンコード失敗
5308
+ if (!is_regular_sjis) {
5309
+ encode.shift_jis_array = [];
5310
+ encode.iso2022jp_array = [];
5311
+ }
5312
+
5313
+ // 制御文字かどうか
5314
+ type.control_name = Unicode.toControlCharcterName(unicode_codepoint);
5315
+ type.is_control_character = type.control_name ? true : false;
5316
+
5317
+ // Unicodeのブロック名
5318
+ type.blockname = Unicode.toBlockNameFromUnicode(unicode_codepoint);
5319
+ // ブロック名から判断
5320
+ type.is_kanji = /Ideographs/.test(type.blockname);
5321
+ type.is_hiragana = /Hiragana/.test(type.blockname);
5322
+ type.is_katakana = /Katakana/.test(type.blockname);
5323
+ type.is_fullwidth_ascii = /[\u3000\uFF01-\uFF5E]/.test(data.character);
5324
+ type.is_halfwidth_katakana = /[\uFF61-\uFF9F]/.test(data.character);
5325
+ // 絵文字
5326
+ type.is_emoji = /Pictographs|Transport and Map Symbols/.test(type.blockname);
5327
+ // 顔文字
5328
+ type.is_emoticons = /Emoticons/.test(type.blockname);
5329
+ // 記号(VS16 が付くと絵文字化)
5330
+ type.is_symbol_base = /Dingbats|Miscellaneous Symbols/.test(type.blockname);
5331
+ // 外字
5332
+ type.is_gaiji = /Private Use Area/.test(type.blockname);
5333
+ // グラフェムを構成するための文字
5334
+ type.is_grapheme_component = Unicode.isGraphemeComponentFromCodePoint(unicode_codepoint);
5335
+ // 横幅が 0 の文字
5336
+ type.is_zero_width_character = Unicode.isZeroWidthCharacterFromCodePoint(unicode_codepoint);
5337
+ // 結合文字
5338
+ type.is_combining_mark = Unicode.isCombiningMarkFromCodePoint(unicode_codepoint);
5339
+ // 異体字セレクタ
5340
+ type.is_variation_selector = Unicode.isVariationSelectorFromCodePoint(unicode_codepoint);
5341
+ // スキントーン修飾子
5342
+ type.is_skin_tone_modifier = Unicode.isEmojiModifierFromCodePoint(unicode_codepoint);
5343
+ // タグ文字
5344
+ type.is_tag_character = Unicode.isTagCharacterFromCodePoint(unicode_codepoint);
5345
+ // 国旗絵文字を構成するためのRI文字
5346
+ type.is_regional_indicator = Unicode.isRegionalIndicatorFromCodePoint(unicode_codepoint);
5347
+
5348
+ return data;
5349
+ }
5350
+ }
5351
+
5352
+ /**
5353
+ * The script is part of Mojix for TextInputGuard.
5354
+ *
5355
+ * AUTHOR:
5356
+ * natade-jp (https://github.com/natade-jp)
5357
+ *
5358
+ * LICENSE:
5359
+ * The MIT license https://opensource.org/licenses/MIT
5360
+ */
5361
+
5362
+
5363
+ /**
5364
+ * 日本語を扱うための様々な機能を提供します
5365
+ */
5366
+ class Mojix {
5367
+ // ---------------------------------
5368
+ // 文字列のエンコードとデコードを扱う関数
5369
+ // ---------------------------------
5370
+
5371
+ /**
5372
+ * 文字列からバイナリ配列にエンコードする
5373
+ * @param {string} text - 変換したいテキスト
5374
+ * @param {string} charset - キャラセット(UTF-8/16/32,Shift_JIS,Windows-31J)
5375
+ * @param {boolean} [is_with_bom=false] - BOMをつけるかどうか
5376
+ * @returns {number[]} バイナリ配列(失敗時はnull)
5377
+ */
5378
+ static encode(text, charset, is_with_bom) {
5379
+ return Encode.encode(text, charset, is_with_bom);
5380
+ }
5381
+
5382
+ /**
5383
+ * バイナリ配列から文字列にデコードする
5384
+ * @param {number[]} binary - 変換したいバイナリ配列
5385
+ * @param {string} [charset="autodetect"] - キャラセット(UTF-8/16/32,Shift_JIS,Windows-31J)
5386
+ * @returns {string} 変換した文字列(失敗したらnull)
5387
+ */
5388
+ static decode(binary, charset) {
5389
+ return Encode.decode(binary, charset);
5390
+ }
5391
+
5392
+ // ---------------------------------
5393
+ // Unicode を扱う関数群
5394
+ // ---------------------------------
5395
+
5396
+ /**
5397
+ * サロゲートペア対応のコードポイント取得
5398
+ * @param {string} text - 対象テキスト
5399
+ * @param {number} [index = 0] - インデックス
5400
+ * @returns {number} コードポイント
5401
+ */
5402
+ static codePointAt(text, index) {
5403
+ return Unicode.codePointAt(text, index);
5404
+ }
5405
+
5406
+ /**
5407
+ * コードポイントの数値データを文字列に変換
5408
+ * @param {...(number|number[])} codepoint - 変換したいコードポイントの数値配列、又は数値を並べた可変引数
5409
+ * @returns {string} 変換後のテキスト
5410
+ */
5411
+ static fromCodePoint(codepoint) {
5412
+ if (Array.isArray(codepoint)) {
5413
+ return Unicode.fromCodePoint(codepoint);
5414
+ } else {
5415
+ const codepoint_array = [];
5416
+ for (let i = 0; i < arguments.length; i++) {
5417
+ codepoint_array[i] = arguments[i];
5418
+ }
5419
+ return Unicode.fromCodePoint(codepoint_array);
5420
+ }
5421
+ }
5422
+
5423
+ /**
5424
+ * コードポイント換算で文字列数をカウント
5425
+ * @param {string} text - 対象テキスト
5426
+ * @param {number} [beginIndex=0] - 最初のインデックス(省略可)
5427
+ * @param {number} [endIndex] - 最後のインデックス(ここは含めない)(省略可)
5428
+ * @returns {number} 文字数
5429
+ */
5430
+ static codePointCount(text, beginIndex, endIndex) {
5431
+ return Unicode.codePointCount(text, beginIndex, endIndex);
5432
+ }
5433
+
5434
+ /**
5435
+ * 文字列をUTF32(コードポイント)の配列に変換
5436
+ * @param {string} text - 変換したいテキスト
5437
+ * @returns {number[]} UTF32(コードポイント)のデータが入った配列
5438
+ */
5439
+ static toUTF32Array(text) {
5440
+ return Unicode.toUTF32Array(text);
5441
+ }
5442
+
5443
+ /**
5444
+ * UTF32の配列から文字列に変換
5445
+ * @param {number[]} utf32 - 変換したいテキスト
5446
+ * @returns {string} 変換後のテキスト
5447
+ */
5448
+ static fromUTF32Array(utf32) {
5449
+ return Unicode.fromUTF32Array(utf32);
5450
+ }
5451
+
5452
+ /**
5453
+ * 文字列をUTF16の配列に変換
5454
+ * @param {string} text - 変換したいテキスト
5455
+ * @returns {number[]} UTF16のデータが入った配列
5456
+ */
5457
+ static toUTF16Array(text) {
5458
+ return Unicode.toUTF16Array(text);
5459
+ }
5460
+
5461
+ /**
5462
+ * UTF16の配列から文字列に変換
5463
+ * @param {number[]} utf16 - 変換したいテキスト
5464
+ * @returns {string} 変換後のテキスト
5465
+ */
5466
+ static fromUTF16Array(utf16) {
5467
+ return Unicode.fromUTF16Array(utf16);
5468
+ }
5469
+
5470
+ /**
5471
+ * 文字列をUTF8の配列に変換
5472
+ * @param {string} text - 変換したいテキスト
5473
+ * @returns {number[]} UTF8のデータが入った配列
5474
+ */
5475
+ static toUTF8Array(text) {
5476
+ return Unicode.toUTF8Array(text);
5477
+ }
5478
+
5479
+ /**
5480
+ * UTF8の配列から文字列に変換
5481
+ * @param {number[]} utf8 - 変換したいテキスト
5482
+ * @returns {string} 変換後のテキスト
5483
+ */
5484
+ static fromUTF8Array(utf8) {
5485
+ return Unicode.fromUTF8Array(utf8);
5486
+ }
5487
+
5488
+ // ---------------------------------
5489
+ // 文字を扱う関数群
5490
+ // ---------------------------------
5491
+
5492
+ /**
5493
+ * 結合した文字を考慮して文字列を文字の配列に変換する
5494
+ * @param {string} text - 変換したいテキスト
5495
+ * @returns {number[][]} UTF32(コードポイント)の配列が入った配列
5496
+ */
5497
+ static toMojiArrayFromString(text) {
5498
+ return Japanese.toMojiArrayFromString(text);
5499
+ }
5500
+
5501
+ /**
5502
+ * 結合した文字を考慮して文字の配列を文字列に変換する
5503
+ * @param {number[][]} mojiarray - UTF32(コードポイント)の配列が入った配列
5504
+ * @returns {string} UTF32(コードポイント)の配列が入った配列
5505
+ */
5506
+ static toStringFromMojiArray(mojiarray) {
5507
+ return Japanese.toStringFromMojiArray(mojiarray);
5508
+ }
5509
+
5510
+ // ---------------------------------
5511
+ // 切り出しを扱う関数群
5512
+ // ---------------------------------
5513
+
5514
+ /**
5515
+ * 指定したテキストを切り出す
5516
+ * - 単位はコードポイントの文字数
5517
+ * - 結合文字, 異体字セレクタ, スキントーン修飾子, タグ文字を考慮しません
5518
+ * @param {string} text - 切り出したいテキスト
5519
+ * @param {number} offset - 切り出し位置
5520
+ * @param {number} size - 切り出す長さ
5521
+ * @returns {string} 切り出したテキスト
5522
+ */
5523
+ static cutTextForCodePoint(text, offset, size) {
5524
+ return Unicode.cutTextForCodePoint(text, offset, size);
5525
+ }
5526
+
5527
+ /**
5528
+ * 指定したテキストの横幅を半角/全角でカウント
5529
+ * - 0幅 ... グラフェムを構成する要素
5530
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
5531
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
5532
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
5533
+ * - 2幅 ... 上記以外
5534
+ * @param {string} text - カウントしたいテキスト
5535
+ * @returns {number} 文字の横幅
5536
+ */
5537
+ static getWidth(text) {
5538
+ return Japanese.getWidth(text);
5539
+ }
5540
+
5541
+ /**
5542
+ * 指定したテキストを切り出す
5543
+ * - 0幅 ... グラフェムを構成する要素
5544
+ * (結合文字, 異体字セレクタ, スキントーン修飾子,
5545
+ * Tag Sequence 構成文字, ZWSP, ZWNJ, ZWJ, WJ)
5546
+ * - 1幅 ... ASCII文字, 半角カタカナ, Regional Indicator(単体)
5547
+ * - 2幅 ... 上記以外
5548
+ * @param {string} text - 切り出したいテキスト
5549
+ * @param {number} offset - 切り出し位置
5550
+ * @param {number} size - 切り出す長さ
5551
+ * @returns {string} 切り出したテキスト
5552
+ */
5553
+ static cutTextForWidth(text, offset, size) {
5554
+ return Japanese.cutTextForWidth(text, offset, size);
5555
+ }
5556
+
5557
+ // ---------------------------------
5558
+ // 面区点コードの変換用
5559
+ // ---------------------------------
5560
+
5561
+ /**
5562
+ * 指定した文字から Windows-31J 上の区点番号に変換
5563
+ * - 2文字以上を指定した場合は、1文字目のみを変換する
5564
+ * @param {string} text - 変換したいテキスト
5565
+ * @returns {MenKuTen} 区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
5566
+ */
5567
+ static toKuTen(text) {
5568
+ return CP932.toKuTen(text);
5569
+ }
5570
+
5571
+ // ---------------------------------
5572
+ // 日本語の変換用の関数群
5573
+ // ---------------------------------
5574
+
5575
+ /**
5576
+ * カタカナをひらがなに変換
5577
+ * @param {string} text - 変換したいテキスト
5578
+ * @returns {string} 変換後のテキスト
5579
+ */
5580
+ static toHiragana(text) {
5581
+ return Japanese.toHiragana(text);
5582
+ }
5583
+
5584
+ /**
5585
+ * ひらがなをカタカナに変換
5586
+ * @param {string} text - 変換したいテキスト
5587
+ * @returns {string} 変換後のテキスト
5588
+ */
5589
+ static toKatakana(text) {
5590
+ return Japanese.toKatakana(text);
5591
+ }
5592
+
5593
+ /**
5594
+ * スペースを半角に変換
5595
+ * @param {string} text - 変換したいテキスト
5596
+ * @returns {string} 変換後のテキスト
5597
+ */
5598
+ static toHalfWidthSpace(text) {
5599
+ return Japanese.toHalfWidthSpace(text);
5600
+ }
5601
+
5602
+ /**
5603
+ * スペースを全角に変換
5604
+ * @param {string} text - 変換したいテキスト
5605
+ * @returns {string} 変換後のテキスト
5606
+ */
5607
+ static toFullWidthSpace(text) {
5608
+ return Japanese.toFullWidthSpace(text);
5609
+ }
5610
+
5611
+ /**
5612
+ * 英数記号を半角に変換
5613
+ * @param {string} text - 変換したいテキスト
5614
+ * @returns {string} 変換後のテキスト
5615
+ */
5616
+ static toHalfWidthAsciiCode(text) {
5617
+ return Japanese.toHalfWidthAsciiCode(text);
5618
+ }
5619
+
5620
+ /**
5621
+ * 英数記号を全角に変換
5622
+ * @param {string} text - 変換したいテキスト
5623
+ * @returns {string} 変換後のテキスト
5624
+ */
5625
+ static toFullWidthAsciiCode(text) {
5626
+ return Japanese.toFullWidthAsciiCode(text);
5627
+ }
5628
+
5629
+ /**
5630
+ * アルファベットを半角に変換
5631
+ * @param {string} text - 変換したいテキスト
5632
+ * @returns {string} 変換後のテキスト
5633
+ */
5634
+ static toHalfWidthAlphabet(text) {
5635
+ return Japanese.toHalfWidthAlphabet(text);
5636
+ }
5637
+
5638
+ /**
5639
+ * アルファベットを全角に変換
5640
+ * @param {string} text - 変換したいテキスト
5641
+ * @returns {string} 変換後のテキスト
5642
+ */
5643
+ static toFullWidthAlphabet(text) {
5644
+ return Japanese.toFullWidthAlphabet(text);
5645
+ }
5646
+
5647
+ /**
5648
+ * 数値を半角に変換
5649
+ * @param {string} text - 変換したいテキスト
5650
+ * @returns {string} 変換後のテキスト
5651
+ */
5652
+ static toHalfWidthNumber(text) {
5653
+ return Japanese.toHalfWidthNumber(text);
5654
+ }
5655
+
5656
+ /**
5657
+ * 数値を全角に変換
5658
+ * @param {string} text - 変換したいテキスト
5659
+ * @returns {string} 変換後のテキスト
5660
+ */
5661
+ static toFullWidthNumber(text) {
5662
+ return Japanese.toFullWidthNumber(text);
5663
+ }
5664
+
5665
+ /**
5666
+ * カタカナを半角に変換
5667
+ * @param {string} text - 変換したいテキスト
5668
+ * @returns {string} 変換後のテキスト
5669
+ */
5670
+ static toHalfWidthKana(text) {
5671
+ return Japanese.toHalfWidthKana(text);
5672
+ }
5673
+
5674
+ /**
5675
+ * カタカナを全角に変換
5676
+ * @param {string} text - 変換したいテキスト
5677
+ * @returns {string} 変換後のテキスト
5678
+ */
5679
+ static toFullWidthKana(text) {
5680
+ return Japanese.toFullWidthKana(text);
5681
+ }
5682
+
5683
+ /**
5684
+ * 半角に変換
5685
+ * @param {string} text - 変換したいテキスト
5686
+ * @returns {string} 変換後のテキスト
5687
+ */
5688
+ static toHalfWidth(text) {
5689
+ return Japanese.toHalfWidth(text);
5690
+ }
5691
+
5692
+ /**
5693
+ * 全角に変換
5694
+ * @param {string} text - 変換したいテキスト
5695
+ * @returns {string} 変換後のテキスト
5696
+ */
5697
+ static toFullWidth(text) {
5698
+ return Japanese.toFullWidth(text);
5699
+ }
5700
+
5701
+ // ---------------------------------
5702
+ // 1つの文字データに対して調査を行う
5703
+ // ---------------------------------
5704
+
5705
+ /**
5706
+ * 指定した1つのUTF-32 コードポイントに関して、解析を行い情報を返します
5707
+ * @param {number} unicode_codepoint - UTF-32 のコードポイント
5708
+ * @returns {MojiData} 文字の情報がつまったオブジェクト
5709
+ */
5710
+ static getMojiData(unicode_codepoint) {
5711
+ return MojiAnalyzer.getMojiData(unicode_codepoint);
5712
+ }
5713
+ }
5714
+
5715
+ /**
5716
+ * The script is part of TextInputGuard.
5717
+ *
5718
+ * AUTHOR:
5719
+ * natade-jp (https://github.com/natade-jp)
5720
+ *
5721
+ * LICENSE:
5722
+ * The MIT license https://opensource.org/licenses/MIT
5723
+ */
5724
+
5725
+
5726
+ /**
5727
+ * kana ルールのオプション
5728
+ * @typedef {Object} KanaRuleOptions
5729
+ * @property {"katakana-full"|"katakana-half"|"hiragana"} [target="katakana-full"] - 統一先
5730
+ * @property {boolean} [nfkc=true] - 事前に Unicode NFKC 正規化を行う(合体文字などを正規化)
5731
+ */
5732
+
5733
+ /**
5734
+ * kana ルールを生成する
5735
+ * @param {KanaRuleOptions} [options]
5736
+ * @returns {Rule}
5737
+ */
5738
+ function kana(options = {}) {
5739
+ /** @type {KanaRuleOptions} */
5740
+ const opt = {
5741
+ target: options.target ?? "katakana-full",
5742
+ nfkc: options.nfkc ?? true
5743
+ };
5744
+
5745
+ return {
5746
+ name: "kana",
5747
+ targets: ["input", "textarea"],
5748
+
5749
+ /**
5750
+ * かな種別の正規化(入力中に都度かける)
5751
+ * - (任意) NFKC 正規化
5752
+ * - Mojix で target へ統一(ここは差し替え)
5753
+ * @param {string} value
5754
+ * @param {GuardContext} ctx
5755
+ * @returns {string}
5756
+ */
5757
+ normalizeChar(value, ctx) {
5758
+ let s = String(value);
5759
+ if (opt.nfkc) {
5760
+ // 古い環境で normalize が無い可能性もあるので安全に
5761
+ try {
5762
+ s = s.normalize("NFKC");
5763
+ } catch {
5764
+ // noop
5765
+ }
5766
+ }
5767
+ s = Mojix.toKatakana(s);
5768
+ if (opt.target === "katakana-full") {
5769
+ s = Mojix.toFullWidthSpace(s);
5770
+ s = Mojix.toFullWidthKana(s);
5771
+ } else if (opt.target === "katakana-half") {
5772
+ s = Mojix.toHalfWidthSpace(s);
5773
+ s = Mojix.toHalfWidthKana(s);
5774
+ } else {
5775
+ s = Mojix.toFullWidthSpace(s);
5776
+ s = Mojix.toFullWidthKana(s);
5777
+ s = Mojix.toHiragana(s);
5778
+ }
5779
+ return s;
5780
+ }
5781
+ };
5782
+ }
5783
+
5784
+ /**
5785
+ * datasetから kana ルールを生成する
5786
+ * - data-tig-rules-kana が無ければ null
5787
+ * - オプションは data-tig-rules-kana-xxx から読む
5788
+ *
5789
+ * 対応する data 属性(dataset 名)
5790
+ * - data-tig-rules-kana -> dataset.tigRulesKana
5791
+ * - data-tig-rules-kana-target -> dataset.tigRulesKanaTarget
5792
+ * - data-tig-rules-kana-nfkc -> dataset.tigRulesKanaNfkc
5793
+ *
5794
+ * @param {DOMStringMap} dataset
5795
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
5796
+ * @returns {Rule|null}
5797
+ */
5798
+ kana.fromDataset = function fromDataset(dataset, _el) {
5799
+ // ON判定
5800
+ if (dataset.tigRulesKana == null) {
5801
+ return null;
5802
+ }
5803
+
5804
+ /** @type {KanaRuleOptions} */
5805
+ const options = {};
5806
+
5807
+ const target = parseDatasetEnum(dataset.tigRulesKanaTarget, [
5808
+ "katakana-full",
5809
+ "katakana-half",
5810
+ "hiragana"
5811
+ ]);
5812
+ if (target != null) {
5813
+ options.target = target;
5814
+ }
5815
+
5816
+ const nfkc = parseDatasetBool(dataset.tigRulesKanaNfkc);
5817
+ if (nfkc != null) {
5818
+ options.nfkc = nfkc;
5819
+ }
5820
+
5821
+ return kana(options);
5822
+ };
5823
+
5824
+ /**
5825
+ * The script is part of TextInputGuard.
5826
+ *
5827
+ * AUTHOR:
5828
+ * natade-jp (https://github.com/natade-jp)
5829
+ *
5830
+ * LICENSE:
5831
+ * The MIT license https://opensource.org/licenses/MIT
5832
+ */
5833
+
5834
+
5835
+ /**
5836
+ * ascii ルールのオプション
5837
+ * @typedef {Object} AsciiRuleOptions
5838
+ * @property {"none"|"upper"|"lower"} [case] - 英字の大文字/小文字統一
5839
+ */
5840
+
5841
+ /**
5842
+ * ascii ルールを生成する
5843
+ * - 全角英数字・記号・全角スペースを半角へ正規化する
5844
+ * - 必要に応じて英字を大文字/小文字へ統一
5845
+ *
5846
+ * @param {AsciiRuleOptions} [options]
5847
+ * @returns {Rule}
5848
+ */
5849
+ function ascii(options = {}) {
5850
+ /** @type {AsciiRuleOptions} */
5851
+ const opt = {
5852
+ case: options.case ?? null
5853
+ };
5854
+
5855
+ return {
5856
+ name: "ascii",
5857
+ targets: ["input", "textarea"],
5858
+
5859
+ normalizeChar(value, ctx) {
5860
+ let s = String(value);
5861
+
5862
+ // まず半角へ正規化
5863
+ s = Mojix.toHalfWidthAsciiCode(s);
5864
+
5865
+ // 英字の大文字/小文字統一
5866
+ if (opt.case === "upper") {
5867
+ s = s.toUpperCase();
5868
+ } else if (opt.case === "lower") {
5869
+ s = s.toLowerCase();
5870
+ }
5871
+
5872
+ return s;
5873
+ }
5874
+ };
5875
+ }
5876
+
5877
+ /**
5878
+ * datasetから ascii ルールを生成する
5879
+ *
5880
+ * 対応する data 属性
5881
+ * - data-tig-rules-ascii
5882
+ * - data-tig-rules-ascii-case ("none" | "upper" | "lower")
5883
+ *
5884
+ * @param {DOMStringMap} dataset
5885
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
5886
+ * @returns {Rule|null}
5887
+ */
5888
+ ascii.fromDataset = function fromDataset(dataset, _el) {
5889
+ if (dataset.tigRulesAscii == null) {
5890
+ return null;
5891
+ }
5892
+
5893
+ const options = {};
5894
+
5895
+ const caseOpt = parseDatasetEnum(dataset.tigRulesAsciiCase, [
5896
+ "none",
5897
+ "upper",
5898
+ "lower"
5899
+ ]);
5900
+ if (caseOpt != null) {
5901
+ options.case = caseOpt;
5902
+ }
5903
+
5904
+ return ascii(options);
5905
+ };
5906
+
5907
+ /* eslint-disable max-len */
5908
+ /**
5909
+ * The script is part of TextInputGuard.
5910
+ *
5911
+ * AUTHOR:
5912
+ * natade-jp (https://github.com/natade-jp)
5913
+ *
5914
+ * LICENSE:
5915
+ * The MIT license https://opensource.org/licenses/MIT
5916
+ */
5917
+
5918
+
5919
+ /**
5920
+ * filter ルールのカテゴリ名
5921
+ *
5922
+ * - "digits" : ASCII 数字 (0-9)
5923
+ * - "alpha-upper" : ASCII 英字大文字 (A-Z)
5924
+ * - "alpha-lower" : ASCII 英字小文字 (a-z)
5925
+ * - "ascii" : ASCII 可視文字 + スペース含む (U+0020–U+007E)
5926
+ * - "hiragana" : ひらがな (U+3040–U+309F)
5927
+ * - "katakana-full" : 全角カタカナ (U+30A0–U+30FF)
5928
+ * - "katakana-half" : 半角カタカナ (U+FF65–U+FF9F)
5929
+ * - "bmp-only" : BMP のみ許可(U+0000–U+FFFF、サロゲートペア、補助平面禁止)
5930
+ * - "sjis-only" : 正規 Shift_JIS(JIS X 0208 + 1バイト領域)のみ許可
5931
+ * - "cp932-only" : Windows-31J (CP932) でエンコード可能な文字のみ許可
5932
+ * - "single-codepoint-only" : 単一コードポイントのみ許可(結合文字や異体字セレクタを含まない)
5933
+ *
5934
+ * @typedef {"digits"|"alpha-upper"|"alpha-lower"|"ascii"|"hiragana"|"katakana-full"|"katakana-half"|"bmp-only"|"sjis-only"|"cp932-only"|"single-codepoint-only"} FilterCategory
5935
+ */
5936
+
5937
+ /**
5938
+ * グラフェム(1グラフェムは、UTF-32の配列)
5939
+ * @typedef {number[]} Grapheme
5940
+ */
5941
+
5942
+ /** @type {readonly FilterCategory[]} */
5943
+ const FILTER_CATEGORIES = [
5944
+ "digits",
5945
+ "alpha-upper",
5946
+ "alpha-lower",
5947
+ "ascii",
5948
+ "hiragana",
5949
+ "katakana-full",
5950
+ "katakana-half",
5951
+ "bmp-only",
5952
+ "sjis-only",
5953
+ "cp932-only",
5954
+ "single-codepoint-only"
5955
+ ];
5956
+
5957
+ /**
5958
+ * filter ルールの動作モード
5959
+ * @typedef {"drop"|"error"} FilterMode
5960
+ */
5961
+
5962
+ /**
5963
+ * filter ルールのオプション
5964
+ * - category は和集合で扱う(複数指定OK)
5965
+ * - allow は追加許可(和集合)
5966
+ * - deny は除外(差集合)
5967
+ *
5968
+ * allowed = (category の和集合 ∪ allow) − deny
5969
+ *
5970
+ * @typedef {Object} FilterRuleOptions
5971
+ * @property {FilterMode} [mode="drop"] - drop: 不要文字を削除 / error: 削除せずエラーを積む
5972
+ * @property {FilterCategory[]} [category] - カテゴリ(配列)
5973
+ * @property {RegExp|string} [allow] - 追加で許可する正規表現(1文字にマッチさせる想定)
5974
+ * @property {string} [allowFlags] - allow が文字列のときの flags("iu" など。g/y は無視)
5975
+ * @property {RegExp|string} [deny] - 除外する正規表現(1文字にマッチさせる想定)
5976
+ * @property {string} [denyFlags] - deny が文字列のときの flags("iu" など。g/y は無視)
5977
+ */
5978
+
5979
+ /**
5980
+ * /g や /y は lastIndex の罠があるので除去して使う
5981
+ * @param {string} flags
5982
+ * @returns {string}
5983
+ */
5984
+ const stripStatefulFlags = function (flags) {
5985
+ return String(flags || "").replace(/[gy]/g, "");
5986
+ };
5987
+
5988
+ /**
5989
+ * 正規表現(RegExp または pattern 文字列)を安全に RegExp 化する
5990
+ * - g/y を外す
5991
+ * - string の場合、flags 未指定なら "u" を付ける
5992
+ *
5993
+ * @param {RegExp|string|undefined} reOrPattern
5994
+ * @param {string|undefined} flags
5995
+ * @returns {RegExp|undefined}
5996
+ */
5997
+ const toSafeRegExp = function (reOrPattern, flags) {
5998
+ if (reOrPattern == null) {
5999
+ return;
6000
+ }
6001
+
6002
+ if (reOrPattern instanceof RegExp) {
6003
+ const safeFlags = stripStatefulFlags(reOrPattern.flags);
6004
+ return new RegExp(reOrPattern.source, safeFlags);
6005
+ }
6006
+
6007
+ const f = stripStatefulFlags(flags ?? "u");
6008
+ return new RegExp(String(reOrPattern), f);
6009
+ };
6010
+
6011
+ /**
6012
+ * カテゴリ判定関数を作る
6013
+ * @param {FilterCategory[]} categories
6014
+ * @returns {(g: Grapheme, s: string) => boolean}
6015
+ */
6016
+ const createCategoryTester = function (categories) {
6017
+ /** @type {Record<FilterCategory, (g: Grapheme, s: string) => boolean>} */
6018
+ const table = {
6019
+ digits: (g, s) => {
6020
+ return g.length === 1 && g[0] >= 0x30 && g[0] <= 0x39; // '0'..'9'
6021
+ },
6022
+ "alpha-upper": (g, s) => {
6023
+ if (g.length !== 1) { return false; }
6024
+ const c = g[0];
6025
+ // 'A'..'Z'
6026
+ return c >= 0x41 && c <= 0x5A;
6027
+ },
6028
+ "alpha-lower": (g, s) => {
6029
+ if (g.length !== 1) { return false; }
6030
+ const c = g[0];
6031
+ // 'a'..'z'
6032
+ return c >= 0x61 && c <= 0x7A;
6033
+ },
6034
+ ascii: (g, s) => {
6035
+ if (g.length !== 1) { return false; }
6036
+ const c = g[0];
6037
+ return c >= 0x20 && c <= 0x7E;
6038
+ },
6039
+ hiragana: (g, s) => {
6040
+ if (g.length !== 1) { return false; }
6041
+ const c = g[0];
6042
+ return c >= 0x3040 && c <= 0x309F;
6043
+ },
6044
+ "katakana-full": (g, s) => {
6045
+ if (g.length !== 1) { return false; }
6046
+ const c = g[0];
6047
+ return c >= 0x30A0 && c <= 0x30FF;
6048
+ },
6049
+ "katakana-half": (g, s) => {
6050
+ if (g.length !== 1) { return false; }
6051
+ const c = g[0];
6052
+ return c >= 0xFF65 && c <= 0xFF9F;
6053
+ },
6054
+ "bmp-only": (g, s) => {
6055
+ // BMPのみ(サロゲートペア禁止)
6056
+ return g.every((cp) => cp <= 0xFFFF);
6057
+ },
6058
+ "sjis-only": (g, s) => {
6059
+ // Shift_JIS でエンコードできる文字かどうか
6060
+ if (g.length !== 1) { return false; }
6061
+ const cp932code = CP932.toCP932FromUnicode(g[0]);
6062
+ if (cp932code === undefined) { return false; }
6063
+ const kuten = SJIS.toKuTenFromSJISCode(cp932code);
6064
+ if (cp932code < 0x100) {
6065
+ return true;
6066
+ }
6067
+ if (!SJIS.isRegularMenKuten(kuten)) { return false; }
6068
+ return kuten.ku <= 94;
6069
+ },
6070
+ "cp932-only": (g, s) => {
6071
+ // Windows-31J (cp932) でエンコードできる文字かどうか
6072
+ if (g.length !== 1) { return false; }
6073
+ return CP932.toCP932FromUnicode(g[0]) !== undefined;
6074
+ },
6075
+ "single-codepoint-only": (g, s) => {
6076
+ // 1グラフェムが単一コードポイントのみで構成されていること
6077
+ return g.length === 1;
6078
+ }
6079
+ };
6080
+
6081
+ // categories は「和集合」なので、該当する tester だけ抜いて使う
6082
+ const list = categories.map((c) => table[c]).filter(Boolean);
6083
+
6084
+ if (list.length === 0) {
6085
+ return function () {
6086
+ return false;
6087
+ };
6088
+ }
6089
+
6090
+ return function (g, s) {
6091
+ for (const test of list) {
6092
+ if (test(g, s)) {
6093
+ return true;
6094
+ }
6095
+ }
6096
+ return false;
6097
+ };
6098
+ };
6099
+
6100
+ /**
6101
+ * 1文字が許可されるか判定する関数を作る
6102
+ * @param {FilterCategory[]} categoryList
6103
+ * @param {(g: Grapheme, s: string) => boolean} categoryTest
6104
+ * @param {RegExp|undefined} allowRe
6105
+ * @param {RegExp|undefined} denyRe
6106
+ * @returns {(g: Grapheme, s: string) => boolean}
6107
+ */
6108
+ const createAllowedTester = function (categoryList, categoryTest, allowRe, denyRe) {
6109
+ const hasCategory = categoryList.length > 0;
6110
+ const hasAllow = allowRe != null;
6111
+ const hasDeny = denyRe != null;
6112
+
6113
+ // deny だけの指定は「deny に当たる文字だけ落とす」ルールとして扱う
6114
+ const denyOnly = !hasCategory && !hasAllow && hasDeny;
6115
+
6116
+ return function (g, s) {
6117
+ if (denyRe && denyRe.test(s)) {
6118
+ return false;
6119
+ }
6120
+
6121
+ if (denyOnly) {
6122
+ return true;
6123
+ }
6124
+
6125
+ if (hasCategory && categoryTest(g, s)) {
6126
+ return true;
6127
+ }
6128
+ if (allowRe && allowRe.test(s)) {
6129
+ return true;
6130
+ }
6131
+
6132
+ return false;
6133
+ };
6134
+ };
6135
+
6136
+ /**
6137
+ * 文字列を走査して、許可文字のみの文字列と、不正文字の集計を返す
6138
+ * @param {string} value
6139
+ * @param {(g: Grapheme, s: string) => boolean} isAllowed
6140
+ * @param {number} [maxInvalidChars=20]
6141
+ * @returns {{ filtered: string, invalidCount: number, invalidChars: string[] }}
6142
+ */
6143
+ const scanByAllowed = function (value, isAllowed, maxInvalidChars = 20) {
6144
+ const v = String(value);
6145
+
6146
+ let filtered = "";
6147
+ let invalidCount = 0;
6148
+
6149
+ /** @type {Set<string>} */
6150
+ const invalidSet = new Set();
6151
+
6152
+ /**
6153
+ * グラフェムの配列
6154
+ * @type {Grapheme[]}
6155
+ */
6156
+ const graphemeArray = Mojix.toMojiArrayFromString(v);
6157
+
6158
+ // JS の文字列イテレータはコードポイント単位で回るので Array.from は不要
6159
+ for (const g of graphemeArray) {
6160
+ const s = Mojix.toStringFromMojiArray([g]);
6161
+ if (isAllowed(g, s)) {
6162
+ filtered += s;
6163
+ } else {
6164
+ invalidCount++;
6165
+ if (invalidSet.size < maxInvalidChars) {
6166
+ invalidSet.add(s);
6167
+ }
6168
+ }
6169
+ }
6170
+
6171
+ return {
6172
+ filtered,
6173
+ invalidCount,
6174
+ invalidChars: Array.from(invalidSet)
6175
+ };
6176
+ };
6177
+
6178
+ /**
6179
+ * filter ルールを生成する
6180
+ * - mode="drop": 不要文字を落とすだけ
6181
+ * - mode="error": 文字は落とさず validate でエラーを積む
6182
+ *
6183
+ * @param {FilterRuleOptions} [options]
6184
+ * @returns {Rule}
6185
+ */
6186
+ function filter(options = {}) {
6187
+ /** @type {FilterRuleOptions} */
6188
+ const opt = {
6189
+ mode: options.mode ?? "drop",
6190
+ category: options.category ?? [],
6191
+ allow: options.allow,
6192
+ allowFlags: options.allowFlags,
6193
+ deny: options.deny,
6194
+ denyFlags: options.denyFlags
6195
+ };
6196
+
6197
+ const categoryList = opt.category;
6198
+ const categoryTest = createCategoryTester(categoryList);
6199
+
6200
+ const allowRe = toSafeRegExp(opt.allow, opt.allowFlags);
6201
+ const denyRe = toSafeRegExp(opt.deny, opt.denyFlags);
6202
+
6203
+ const isAllowed = createAllowedTester(categoryList, categoryTest, allowRe, denyRe);
6204
+
6205
+ const hasAny = categoryList.length > 0 || allowRe != null || denyRe != null;
6206
+
6207
+ return {
6208
+ name: "filter",
6209
+ targets: ["input", "textarea"],
6210
+
6211
+ /**
6212
+ * 許可集合で落とす(drop モードのみ)
6213
+ * @param {string} value
6214
+ * @param {GuardContext} ctx
6215
+ * @returns {string}
6216
+ */
6217
+ normalizeChar(value, ctx) {
6218
+ if (!hasAny) {
6219
+ return value;
6220
+ }
6221
+
6222
+ // error モードは何も落とさない(全て通す)
6223
+ if (opt.mode === "error") {
6224
+ return value;
6225
+ }
6226
+
6227
+ return scanByAllowed(value, isAllowed).filtered;
6228
+ },
6229
+
6230
+ /**
6231
+ * 不正文字が含まれていたらエラーを積む(error モードのみ)
6232
+ * @param {string} value
6233
+ * @param {GuardContext} ctx
6234
+ * @returns {void}
6235
+ */
6236
+ validate(value, ctx) {
6237
+ if (!hasAny) {
6238
+ return;
6239
+ }
6240
+ if (opt.mode !== "error") {
6241
+ return;
6242
+ }
6243
+
6244
+ const v = String(value);
6245
+ if (v === "") {
6246
+ return;
6247
+ }
6248
+
6249
+ const r = scanByAllowed(v, isAllowed);
6250
+ if (r.invalidCount > 0) {
6251
+ ctx.pushError({
6252
+ code: "filter.invalid_char",
6253
+ rule: "filter",
6254
+ phase: "validate",
6255
+ detail: {
6256
+ count: r.invalidCount,
6257
+ chars: r.invalidChars,
6258
+ category: categoryList,
6259
+ hasAllow: allowRe != null,
6260
+ hasDeny: denyRe != null
6261
+ }
6262
+ });
6263
+ }
6264
+ }
6265
+ };
6266
+ }
6267
+
6268
+ /**
6269
+ * datasetから filter ルールを生成する
6270
+ * - data-tig-rules-filter が無ければ null
6271
+ *
6272
+ * 対応する data 属性(dataset 名)
6273
+ * - data-tig-rules-filter -> dataset.tigRulesFilter
6274
+ * - data-tig-rules-filter-mode -> dataset.tigRulesFilterMode ("drop"|"error")
6275
+ * - data-tig-rules-filter-category -> dataset.tigRulesFilterCategory ("a,b,c")
6276
+ * - data-tig-rules-filter-allow -> dataset.tigRulesFilterAllow
6277
+ * - data-tig-rules-filter-allow-flags -> dataset.tigRulesFilterAllowFlags
6278
+ * - data-tig-rules-filter-deny -> dataset.tigRulesFilterDeny
6279
+ * - data-tig-rules-filter-deny-flags -> dataset.tigRulesFilterDenyFlags
6280
+ *
6281
+ * @param {DOMStringMap} dataset
6282
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6283
+ * @returns {Rule|null}
6284
+ */
6285
+ filter.fromDataset = function fromDataset(dataset, _el) {
6286
+ if (dataset.tigRulesFilter == null) {
6287
+ return null;
6288
+ }
6289
+
6290
+ /** @type {FilterRuleOptions} */
6291
+ const options = {};
6292
+
6293
+ const mode = parseDatasetEnum(dataset.tigRulesFilterMode, ["drop", "error"]);
6294
+ if (mode != null) {
6295
+ options.mode = mode;
6296
+ }
6297
+
6298
+ const category = parseDatasetEnumList(dataset.tigRulesFilterCategory, FILTER_CATEGORIES);
6299
+ if (category != null) {
6300
+ options.category = category;
6301
+ }
6302
+
6303
+ if (dataset.tigRulesFilterAllow != null) {
6304
+ const s = String(dataset.tigRulesFilterAllow).trim();
6305
+ if (s !== "") {
6306
+ options.allow = s;
6307
+ }
6308
+ }
6309
+
6310
+ if (dataset.tigRulesFilterAllowFlags != null) {
6311
+ const s = String(dataset.tigRulesFilterAllowFlags).trim();
6312
+ if (s !== "") {
6313
+ options.allowFlags = s;
6314
+ }
6315
+ }
6316
+
6317
+ if (dataset.tigRulesFilterDeny != null) {
6318
+ const s = String(dataset.tigRulesFilterDeny).trim();
6319
+ if (s !== "") {
6320
+ options.deny = s;
6321
+ }
6322
+ }
6323
+
6324
+ if (dataset.tigRulesFilterDenyFlags != null) {
6325
+ const s = String(dataset.tigRulesFilterDenyFlags).trim();
6326
+ if (s !== "") {
6327
+ options.denyFlags = s;
6328
+ }
6329
+ }
6330
+
6331
+ return filter(options);
6332
+ };
6333
+
6334
+ /**
6335
+ * The script is part of TextInputGuard.
6336
+ *
6337
+ * AUTHOR:
6338
+ * natade-jp (https://github.com/natade-jp)
6339
+ *
6340
+ * LICENSE:
6341
+ * The MIT license https://opensource.org/licenses/MIT
6342
+ */
6343
+
6344
+
6345
+ /**
6346
+ * length ルールのオプション
6347
+ * @typedef {Object} LengthRuleOptions
6348
+ * @property {number} [max] - 最大長(グラフェム数)。未指定なら制限なし
6349
+ * @property {"block"|"error"} [overflowInput="block"] - 入力中に最大長を超えたときの挙動
6350
+ * @property {"grapheme"|"utf-16"|"utf-32"} [unit="grapheme"] - 長さの単位
6351
+ *
6352
+ * block : 最大長を超える部分を切る
6353
+ * error : エラーを積むだけ(値は変更しない)
6354
+ */
6355
+
6356
+ /**
6357
+ * グラフェム(1グラフェムは、UTF-32の配列)
6358
+ * @typedef {number[]} Grapheme
6359
+ */
6360
+
6361
+ /**
6362
+ * グラフェム/UTF-16コード単位/UTF-32コード単位の長さを調べる
6363
+ * @param {string} text
6364
+ * @param {"grapheme"|"utf-16"|"utf-32"} unit
6365
+ * @returns {number}
6366
+ */
6367
+ const getTextLengthByUnit = function(text, unit) {
6368
+ if (unit === "grapheme") {
6369
+ return Mojix.toMojiArrayFromString(text).length;
6370
+ } else if (unit === "utf-16") {
6371
+ return Mojix.toUTF16Array(text).length;
6372
+ } else if (unit === "utf-32") {
6373
+ return Mojix.toUTF32Array(text).length;
6374
+ } else {
6375
+ // ここには来ない
6376
+ throw new Error(`Invalid unit: ${unit}`);
6377
+ }
6378
+ };
6379
+
6380
+ /**
6381
+ * グラフェム/UTF-16コード単位/UTF-32コード単位でテキストを切る
6382
+ * @param {string} text
6383
+ * @param {"grapheme"|"utf-16"|"utf-32"} unit
6384
+ * @param {number} max
6385
+ * @returns {string}
6386
+ */
6387
+ const cutTextByUnit = function(text, unit, max) {
6388
+ /**
6389
+ * グラフェムの配列
6390
+ * @type {Grapheme[]}
6391
+ */
6392
+ const graphemeArray = Mojix.toMojiArrayFromString(text);
6393
+
6394
+ /**
6395
+ * 現在の位置
6396
+ */
6397
+ let count = 0;
6398
+
6399
+ /**
6400
+ * グラフェムの配列(出力用)
6401
+ * @type {Grapheme[]}
6402
+ */
6403
+ const outputGraphemeArray = [];
6404
+
6405
+ for (let i = 0; i < graphemeArray.length; i++) {
6406
+ const g = graphemeArray[i];
6407
+
6408
+ // 1グラフェムあたりの長さ
6409
+ let graphemeCount = 0;
6410
+ if (unit === "grapheme") {
6411
+ graphemeCount = 1;
6412
+ } else if (unit === "utf-16") {
6413
+ graphemeCount = 0;
6414
+ for (let i = 0; i < g.length; i++) {
6415
+ graphemeCount += (g[i] > 0xFFFF) ? 2 : 1;
6416
+ }
6417
+ } else if (unit === "utf-32") {
6418
+ graphemeCount = g.length;
6419
+ }
6420
+
6421
+ if (count + graphemeCount > max) {
6422
+ // 空配列を渡すとNUL文字を返すため、空配列のときは空文字を返す
6423
+ if (outputGraphemeArray.length === 0) {
6424
+ return "";
6425
+ }
6426
+ // 超える前の位置で文字列化して返す
6427
+ return Mojix.toStringFromMojiArray(outputGraphemeArray);
6428
+ }
6429
+
6430
+ count += graphemeCount;
6431
+ outputGraphemeArray.push(g);
6432
+ }
6433
+
6434
+ // 全部入るなら元の text を返す
6435
+ return text;
6436
+ };
6437
+
6438
+ /**
6439
+ * 元のテキストと追加のテキストの合計が max を超える場合、追加のテキストを切って合計が max に収まるようにする
6440
+ * @param {string} beforeText 元のテキスト
6441
+ * @param {string} insertedText 追加するテキスト
6442
+ * @param {"grapheme"|"utf-16"|"utf-32"} unit
6443
+ * @param {number} max
6444
+ * @returns {string} 追加するテキストを切ったもの(切る必要がない場合は insertedText をそのまま返す)
6445
+ */
6446
+ const cutLength = function(beforeText, insertedText, unit, max) {
6447
+ const orgLen = getTextLengthByUnit(beforeText, unit);
6448
+
6449
+ // すでに最大長を超えている場合は追加のテキストを全て切る
6450
+ if (orgLen >= max) { return ""; }
6451
+
6452
+ const addLen = getTextLengthByUnit(insertedText, unit);
6453
+ const totalLen = orgLen + addLen;
6454
+
6455
+ if (totalLen <= max) {
6456
+ // 今回の追加で範囲内に収まるなら何もしない
6457
+ return insertedText;
6458
+ }
6459
+
6460
+ // 超える場合は追加のテキストを切る
6461
+ const allowedAddLen = max - orgLen;
6462
+ return cutTextByUnit(insertedText, unit, allowedAddLen);
6463
+ };
6464
+
6465
+ /**
6466
+ * length ルールを生成する
6467
+ * @param {LengthRuleOptions} [options]
6468
+ * @returns {Rule}
6469
+ */
6470
+ function length(options = {}) {
6471
+ /** @type {LengthRuleOptions} */
6472
+ const opt = {
6473
+ max: typeof options.max === "number" ? options.max : undefined,
6474
+ overflowInput: options.overflowInput ?? "block",
6475
+ unit: options.unit ?? "grapheme"
6476
+ };
6477
+
6478
+ return {
6479
+ name: "length",
6480
+ targets: ["input", "textarea"],
6481
+
6482
+ normalizeChar(value, ctx) {
6483
+ // block 以外は何もしない
6484
+ if (opt.overflowInput !== "block") {
6485
+ return value;
6486
+ }
6487
+ // max 未指定なら制限なし
6488
+ if (typeof opt.max !== "number") {
6489
+ return value;
6490
+ }
6491
+
6492
+ const beforeText = ctx.beforeText;
6493
+ const insertedText = ctx.insertedText;
6494
+ if (insertedText === "") {
6495
+ return value;
6496
+ }
6497
+
6498
+ const cutText = cutLength(beforeText, insertedText, opt.unit, opt.max);
6499
+ return cutText;
6500
+ },
6501
+
6502
+ validate(value, ctx) {
6503
+ // error 以外は何もしない
6504
+ if (opt.overflowInput !== "error") {
6505
+ return value;
6506
+ }
6507
+ // max 未指定なら制限なし
6508
+ if (typeof opt.max !== "number") {
6509
+ return;
6510
+ }
6511
+
6512
+ const len = getTextLengthByUnit(value, opt.unit);
6513
+ if (len > opt.max) {
6514
+ ctx.pushError({
6515
+ code: "length.max_overflow",
6516
+ rule: "length",
6517
+ phase: "validate",
6518
+ detail: { max: opt.max, actual: len }
6519
+ });
6520
+ }
6521
+ }
6522
+ };
6523
+ }
6524
+
6525
+ /**
6526
+ * datasetから length ルールを生成する
6527
+ * - data-tig-rules-length が無ければ null
6528
+ * - オプションは data-tig-rules-length-xxx から読む
6529
+ *
6530
+ * 対応する data 属性(dataset 名)
6531
+ * - data-tig-rules-length -> dataset.tigRulesLength
6532
+ * - data-tig-rules-length-max -> dataset.tigRulesLengthMax
6533
+ * - data-tig-rules-length-overflow-input -> dataset.tigRulesLengthOverflowInput
6534
+ * - data-tig-rules-length-unit -> dataset.tigRulesLengthUnit
6535
+ *
6536
+ * @param {DOMStringMap} dataset
6537
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6538
+ * @returns {Rule|null}
6539
+ */
6540
+ length.fromDataset = function fromDataset(dataset, _el) {
6541
+ // ON判定
6542
+ if (dataset.tigRulesLength == null) {
6543
+ return null;
6544
+ }
6545
+
6546
+ /** @type {LengthRuleOptions} */
6547
+ const options = {};
6548
+
6549
+ const max = parseDatasetNumber(dataset.tigRulesLengthMax);
6550
+ if (max != null) {
6551
+ options.max = max;
6552
+ }
6553
+
6554
+ const overflowInput = parseDatasetEnum(
6555
+ dataset.tigRulesLengthOverflowInput,
6556
+ ["block", "error"]
6557
+ );
6558
+ if (overflowInput != null) {
6559
+ options.overflowInput = overflowInput;
6560
+ }
6561
+
6562
+ const unit = parseDatasetEnum(
6563
+ dataset.tigRulesLengthUnit,
6564
+ ["grapheme", "utf-16", "utf-32"]
6565
+ );
6566
+ if (unit != null) {
6567
+ options.unit = unit;
6568
+ }
6569
+
6570
+ return length(options);
6571
+ };
6572
+
6573
+ /**
6574
+ * The script is part of TextInputGuard.
6575
+ *
6576
+ * AUTHOR:
6577
+ * natade-jp (https://github.com/natade-jp)
6578
+ *
6579
+ * LICENSE:
6580
+ * The MIT license https://opensource.org/licenses/MIT
6581
+ */
6582
+
6583
+ /**
6584
+ * prefix ルールのオプション
6585
+ * @typedef {Object} PrefixRuleOptions
6586
+ * @property {string} text - 先頭に付ける文字列
6587
+ * @property {boolean} [showWhenEmpty=false] - 値が空でも表示するか
6588
+ */
6589
+
6590
+ /**
6591
+ * 先頭装飾(prefix)ルール
6592
+ * - 表示用として先頭に文字列を付与する
6593
+ * - 手動入力された同文字列は normalizeStructure で除去する
6594
+ *
6595
+ * @param {PrefixRuleOptions} options
6596
+ * @returns {Rule}
6597
+ */
6598
+ function prefix(options) {
6599
+ /** @type {PrefixRuleOptions} */
6600
+ const opt = {
6601
+ text: options?.text ?? "",
6602
+ showWhenEmpty: options?.showWhenEmpty ?? false
6603
+ };
6604
+
6605
+ return {
6606
+ name: "prefix",
6607
+ targets: ["input"],
6608
+
6609
+ /**
6610
+ * 手動入力された prefix を除去
6611
+ * @param {string} value
6612
+ * @returns {string}
6613
+ */
6614
+ normalizeStructure(value) {
6615
+ if (!opt.text) { return value; }
6616
+
6617
+ let s = String(value);
6618
+
6619
+ while (s.startsWith(opt.text)) {
6620
+ s = s.slice(opt.text.length);
6621
+ }
6622
+
6623
+ return s;
6624
+ },
6625
+
6626
+ /**
6627
+ * 表示用整形
6628
+ * @param {string} value
6629
+ * @returns {string}
6630
+ */
6631
+ format(value) {
6632
+ if (!opt.text) { return value; }
6633
+
6634
+ if (!value) {
6635
+ return opt.showWhenEmpty ? opt.text : value;
6636
+ }
6637
+
6638
+ return opt.text + value;
6639
+ }
6640
+ };
6641
+ }
6642
+
6643
+ /**
6644
+ * datasetから prefix ルールを生成する
6645
+ * - data-tig-rules-prefix が無ければ null
6646
+ *
6647
+ * 対応する data 属性(dataset 名)
6648
+ * - data-tig-rules-prefix -> dataset.tigRulesPrefix
6649
+ * - data-tig-rules-prefix-text -> dataset.tigRulesPrefixText
6650
+ * - data-tig-rules-prefix-show-when-empty -> dataset.tigRulesPrefixShowWhenEmpty
6651
+ *
6652
+ * @param {DOMStringMap} dataset
6653
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6654
+ * @returns {Rule|null}
6655
+ */
6656
+ prefix.fromDataset = function fromDataset(dataset, _el) {
6657
+ if (dataset.tigRulesPrefix == null) {
6658
+ return null;
6659
+ }
6660
+
6661
+ return prefix({
6662
+ text: dataset.tigRulesPrefixText ?? "",
6663
+ showWhenEmpty: dataset.tigRulesPrefixShowWhenEmpty === "true"
6664
+ });
6665
+ };
6666
+
6667
+ /**
6668
+ * The script is part of TextInputGuard.
6669
+ *
6670
+ * AUTHOR:
6671
+ * natade-jp (https://github.com/natade-jp)
6672
+ *
6673
+ * LICENSE:
6674
+ * The MIT license https://opensource.org/licenses/MIT
6675
+ */
6676
+
6677
+ /**
6678
+ * suffix ルールのオプション
6679
+ * @typedef {Object} SuffixRuleOptions
6680
+ * @property {string} text - 末尾に付ける文字列
6681
+ * @property {boolean} [showWhenEmpty=false] - 値が空でも表示するか
6682
+ */
6683
+
6684
+ /**
6685
+ * 末尾装飾(suffix)ルール
6686
+ * - 表示用として末尾に文字列を付与する
6687
+ * - 手動入力された同文字列は normalizeStructure で除去する
6688
+ *
6689
+ * @param {SuffixRuleOptions} options
6690
+ * @returns {Rule}
6691
+ */
6692
+ function suffix(options) {
6693
+ /** @type {SuffixRuleOptions} */
6694
+ const opt = {
6695
+ text: options?.text ?? "",
6696
+ showWhenEmpty: options?.showWhenEmpty ?? false
6697
+ };
6698
+
6699
+ return {
6700
+ name: "suffix",
6701
+ targets: ["input"],
6702
+
6703
+ /**
6704
+ * 手動入力された suffix を除去
6705
+ * @param {string} value
6706
+ * @returns {string}
6707
+ */
6708
+ normalizeStructure(value) {
6709
+ if (!opt.text) { return value; }
6710
+
6711
+ let s = String(value);
6712
+
6713
+ while (s.endsWith(opt.text)) {
6714
+ s = s.slice(0, -opt.text.length);
6715
+ }
6716
+
6717
+ return s;
6718
+ },
6719
+
6720
+ /**
6721
+ * 表示用整形
6722
+ * @param {string} value
6723
+ * @returns {string}
6724
+ */
6725
+ format(value) {
6726
+ if (!opt.text) { return value; }
6727
+
6728
+ if (!value) {
6729
+ return opt.showWhenEmpty ? opt.text : value;
6730
+ }
6731
+
6732
+ return value + opt.text;
6733
+ }
6734
+ };
6735
+ }
6736
+
6737
+ /**
6738
+ * datasetから suffix ルールを生成する
6739
+ * - data-tig-rules-suffix が無ければ null
6740
+ *
6741
+ * 対応する data 属性(dataset 名)
6742
+ * - data-tig-rules-suffix -> dataset.tigRulesSuffix
6743
+ * - data-tig-rules-suffix-text -> dataset.tigRulesSuffixText
6744
+ * - data-tig-rules-suffix-show-when-empty -> dataset.tigRulesSuffixShowWhenEmpty
6745
+ *
6746
+ * @param {DOMStringMap} dataset
6747
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6748
+ * @returns {Rule|null}
6749
+ */
6750
+ suffix.fromDataset = function fromDataset(dataset, _el) {
6751
+ if (dataset.tigRulesSuffix == null) {
6752
+ return null;
6753
+ }
6754
+
6755
+ return suffix({
6756
+ text: dataset.tigRulesSuffixText ?? "",
6757
+ showWhenEmpty: dataset.tigRulesSuffixShowWhenEmpty === "true"
6758
+ });
6759
+ };
6760
+
6761
+ /**
6762
+ * The script is part of TextInputGuard.
6763
+ *
6764
+ * AUTHOR:
6765
+ * natade-jp (https://github.com/natade-jp)
6766
+ *
6767
+ * LICENSE:
6768
+ * The MIT license https://opensource.org/licenses/MIT
6769
+ */
6770
+
6771
+ /**
6772
+ * トリムするルール
6773
+ * @returns {Rule}
6774
+ */
6775
+ function trim() {
6776
+ return {
6777
+ name: "trim",
6778
+ targets: ["input", "textarea"],
6779
+
6780
+ /**
6781
+ * 確定時に整える
6782
+ * @param {string} value
6783
+ * @returns {string}
6784
+ */
6785
+ fix(value) {
6786
+ return value.trim();
6787
+ }
6788
+ };
6789
+ }
6790
+
6791
+ /**
6792
+ * datasetから trim ルールを生成する
6793
+ * - data-tig-rules-trim が無ければ null
6794
+ *
6795
+ * 対応する data 属性(dataset 名)
6796
+ * - data-tig-rules-trim -> dataset.tigRulesTrim
6797
+ *
6798
+ * @param {DOMStringMap} dataset
6799
+ * @param {HTMLInputElement|HTMLTextAreaElement} _el
6800
+ * @returns {Rule|null}
6801
+ */
6802
+ trim.fromDataset = function fromDataset(dataset, _el) {
6803
+ // ON判定:data-tig-rules-trim が無ければ対象外
6804
+ if (dataset.tigRulesTrim == null) {
6805
+ return null;
6806
+ }
6807
+ return trim();
6808
+ };
6809
+
2379
6810
  /**
2380
6811
  * TextInputGuard - Public Entry
2381
6812
  * - ESM/CJS: named exports (attach / autoAttach / rules / numeric / digits / comma / version)
@@ -2393,7 +6824,14 @@ comma.fromDataset = function fromDataset(dataset, _el) {
2393
6824
  const auto = new InputGuardAutoAttach(attach, [
2394
6825
  { name: "numeric", fromDataset: numeric.fromDataset },
2395
6826
  { name: "digits", fromDataset: digits.fromDataset },
2396
- { name: "comma", fromDataset: comma.fromDataset }
6827
+ { name: "comma", fromDataset: comma.fromDataset },
6828
+ { name: "kana", fromDataset: kana.fromDataset },
6829
+ { name: "ascii", fromDataset: ascii.fromDataset },
6830
+ { name: "filter", fromDataset: filter.fromDataset },
6831
+ { name: "length", fromDataset: length.fromDataset },
6832
+ { name: "prefix", fromDataset: prefix.fromDataset },
6833
+ { name: "suffix", fromDataset: suffix.fromDataset },
6834
+ { name: "trim", fromDataset: trim.fromDataset }
2397
6835
  ]);
2398
6836
 
2399
6837
  /**
@@ -2408,15 +6846,22 @@ const autoAttach = (root) => auto.autoAttach(root);
2408
6846
  const rules = {
2409
6847
  numeric,
2410
6848
  digits,
2411
- comma
6849
+ comma,
6850
+ kana,
6851
+ ascii,
6852
+ filter,
6853
+ length,
6854
+ prefix,
6855
+ suffix,
6856
+ trim
2412
6857
  };
2413
6858
 
2414
6859
  /**
2415
6860
  * バージョン(ビルド時に置換したいならここを差し替える)
2416
- * 例: rollup replace で ""0.1.3"" を package.json の version に置換
6861
+ * 例: rollup replace で ""0.1.5"" を package.json の version に置換
2417
6862
  */
2418
6863
  // @ts-ignore
2419
6864
  // eslint-disable-next-line no-undef
2420
- const version = "0.1.3" ;
6865
+ const version = "0.1.5" ;
2421
6866
 
2422
- export { attach, attachAll, autoAttach, comma, digits, numeric, rules, version };
6867
+ export { ascii, attach, attachAll, autoAttach, comma, digits, filter, kana, length, numeric, prefix, rules, suffix, trim, version };