Home Reference Source

src/encode/SJIS.js

  1. /**
  2. * The script is part of MojiJS.
  3. *
  4. * AUTHOR:
  5. * natade (http://twitter.com/natadea)
  6. *
  7. * LICENSE:
  8. * The MIT license https://opensource.org/licenses/MIT
  9. */
  10.  
  11. import Unicode from "./Unicode.js";
  12.  
  13. /**
  14. * 面区点情報
  15. * @typedef {Object} MenKuTen
  16. * @property {string} [text] 面-区-点
  17. * @property {number} [men=1] 面
  18. * @property {number} ku 区
  19. * @property {number} ten 点
  20. */
  21.  
  22. /**
  23. * Shift_JIS を扱うクラス
  24. * @ignore
  25. */
  26. export default class SJIS {
  27.  
  28. /**
  29. * 文字列を Shift_JIS の配列に変換
  30. * @param {String} text - 変換したいテキスト
  31. * @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
  32. * @returns {Array<number>} Shift_JIS のデータが入った配列
  33. * @ignore
  34. */
  35. static toSJISArray(text, unicode_to_sjis) {
  36. const map = unicode_to_sjis;
  37. const utf32 = Unicode.toUTF32Array(text);
  38. const sjis = [];
  39. const ng = "?".charCodeAt(0);
  40. for(let i = 0; i < utf32.length; i++) {
  41. const map_bin = map[utf32[i]];
  42. if(map_bin) {
  43. sjis.push(map_bin);
  44. }
  45. else {
  46. sjis.push(ng);
  47. }
  48. }
  49. return sjis;
  50. }
  51.  
  52. /**
  53. * 文字列を Shift_JIS のバイナリ配列に変換
  54. * - 日本語文字は2バイトとして、配列も2つ分、使用します。
  55. * @param {String} text - 変換したいテキスト
  56. * @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
  57. * @returns {Array<number>} Shift_JIS のデータが入ったバイナリ配列
  58. * @ignore
  59. */
  60. static toSJISBinary(text, unicode_to_sjis) {
  61. const sjis = SJIS.toSJISArray(text, unicode_to_sjis);
  62. const sjisbin = [];
  63. for(let i = 0; i < sjis.length; i++) {
  64. if(sjis[i] < 0x100) {
  65. sjisbin.push(sjis[i]);
  66. }
  67. else {
  68. sjisbin.push(sjis[i] >> 8);
  69. sjisbin.push(sjis[i] & 0xFF);
  70. }
  71. }
  72. return sjisbin;
  73. }
  74.  
  75. /**
  76. * SJISの配列から文字列に変換
  77. * @param {Array<number>} sjis - 変換したいテキスト
  78. * @param {Object<number, number|Array<number>>} sjis_to_unicode - Shift_JIS から Unicode への変換マップ
  79. * @returns {String} 変換後のテキスト
  80. * @ignore
  81. */
  82. static fromSJISArray(sjis, sjis_to_unicode) {
  83. const map = sjis_to_unicode;
  84. const utf16 = [];
  85. const ng = "?".charCodeAt(0);
  86. for(let i = 0; i < sjis.length; i++) {
  87. let x = sjis[i];
  88. /**
  89. * @type {number|Array<number>}
  90. */
  91. let y = [];
  92. if(x >= 0x100) {
  93. // すでに1つの変数にまとめられている
  94. y = map[x];
  95. }
  96. else {
  97. // 2バイト文字かのチェック
  98. if( ((0x81 <= x) && (x <= 0x9F)) || ((0xE0 <= x) && (x <= 0xFC)) ) {
  99. x <<= 8;
  100. i++;
  101. x |= sjis[i];
  102. y = map[x];
  103. }
  104. else {
  105. y = map[x];
  106. }
  107. }
  108. if(y) {
  109. // 配列なら配列を結合
  110. // ※ Unicodeの結合文字の可能性があるため
  111. if(y instanceof Array) {
  112. for(let j = 0; j < y.length; j++) {
  113. utf16.push(y[j]);
  114. }
  115. }
  116. // 値しかない場合は値を結合
  117. else {
  118. utf16.push(y);
  119. }
  120. }
  121. else {
  122. utf16.push(ng);
  123. }
  124. }
  125. return Unicode.fromUTF32Array(utf16);
  126. }
  127.  
  128. /**
  129. * 指定したコードポイントの文字から Shift_JIS 上の符号化数値に変換
  130. * @param {Number} unicode_codepoint - Unicodeのコードポイント
  131. * @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
  132. * @returns {Number} 符号化数値(変換できない場合はnullとなる)
  133. * @ignore
  134. */
  135. static toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis) {
  136. if(!unicode_to_sjis[unicode_codepoint]) {
  137. return null;
  138. }
  139. const utf16_text = Unicode.fromUTF32Array([unicode_codepoint]);
  140. const sjis_array = SJIS.toSJISArray(utf16_text, unicode_to_sjis);
  141. return sjis_array[0];
  142. }
  143.  
  144. /**
  145. * 指定した Shift_JIS-2004 のコードから面区点番号に変換
  146. * @param {Number} sjis_code - Shift_JIS-2004 のコードポイント
  147. * @returns {MenKuTen} 面区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
  148. */
  149. static toMenKuTenFromSJIS2004Code(sjis_code) {
  150. if(!sjis_code) {
  151. return null;
  152. }
  153. const x = sjis_code;
  154. if(x < 0x100) {
  155. return null;
  156. }
  157. // アルゴリズムは面区点番号表からリバースエンジニアリング
  158.  
  159. let s1 = x >> 8;
  160. let s2 = x & 0xFF;
  161. let men = 0;
  162. let ku = 0;
  163. let ten = 0;
  164.  
  165. // 面情報の位置判定
  166. if(s1 < 0xF0) {
  167. men = 1;
  168. // 区の計算方法の切り替え
  169. // 63区から、0x9F→0xE0に飛ぶ
  170. if(s1 < 0xE0) {
  171. s1 = s1 - 0x81;
  172. }
  173. else {
  174. s1 = s1 - 0xC1;
  175. }
  176. }
  177. else {
  178. // ※2面は第4水準のみ
  179. men = 2;
  180. // 2面1区 ~ 2面8区
  181. if((((s1 === 0xF0) || (s1 === 0xF2)) && (s2 < 0x9F)) || (s1 === 0xF1)) {
  182. s1 = s1 - 0xF0;
  183. }
  184. // 2面12区 ~ 2面15区
  185. else if(((s1 === 0xF4) && (s2 < 0x9F)) || (s1 < 0xF4)) {
  186. s1 = s1 - 0xED;
  187. }
  188. // 2面78区 ~ 2面94区
  189. else {
  190. s1 = s1 - 0xCE;
  191. }
  192. }
  193.  
  194. // 区情報の位置判定
  195. if(s2 < 0x9f) {
  196. ku = s1 * 2 + 1;
  197. // 点情報の計算方法の切り替え
  198. // 0x7Fが欠番のため「+1」を除去
  199. if(s2 < 0x80) {
  200. s2 = s2 - 0x40 + 1;
  201. }
  202. else {
  203. s2 = s2 - 0x40;
  204. }
  205. }
  206. else {
  207. ku = s1 * 2 + 2;
  208. s2 = s2 - 0x9f + 1;
  209. }
  210.  
  211. // 点情報の位置判定
  212. ten = s2;
  213.  
  214. return {
  215. text : "" + men + "-" + ku + "-" + ten,
  216. men : men,
  217. ku : ku,
  218. ten : ten
  219. };
  220. }
  221.  
  222. /**
  223. * 指定したコードポイントの文字から Shift_JIS-2004 上の面区点番号に変換
  224. * @param {Number} unicode_codepoint - Unicodeのコードポイント
  225. * @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS-2004 への変換マップ
  226. * @returns {MenKuTen} 面区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
  227. * @ignore
  228. */
  229. static toMenKuTenFromUnicode(unicode_codepoint, unicode_to_sjis) {
  230. if(!unicode_to_sjis[unicode_codepoint]) {
  231. return null;
  232. }
  233. const x = SJIS.toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis);
  234. return SJIS.toMenKuTenFromSJIS2004Code(x);
  235. }
  236. /**
  237. * 指定した面区点番号から Shift_JIS-2004 コードに変換
  238. * @param {MenKuTen|string} menkuten - 面区点番号(面が省略された場合は、1とみなす)
  239. * @returns {Number} Shift_JIS-2004 のコードポイント(存在しない場合はnullを返す)
  240. */
  241. static toSJIS2004CodeFromMenKuTen(menkuten) {
  242. let m = null, k = null, t = null;
  243. let text = null;
  244. if(menkuten instanceof Object) {
  245. if(menkuten.text && (typeof menkuten.text === "string")) {
  246. text = menkuten.text;
  247. }
  248. else if((menkuten.ku) && (menkuten.ten)) {
  249. m = menkuten.men ? menkuten.men : 1;
  250. k = menkuten.ku;
  251. t = menkuten.ten;
  252. }
  253. }
  254. else if((typeof menkuten === "string")) {
  255. text = menkuten;
  256. }
  257. if(text) {
  258. const strmkt = text.split("-");
  259. if(strmkt.length === 3) {
  260. m = parseInt(strmkt[0], 10);
  261. k = parseInt(strmkt[1], 10);
  262. t = parseInt(strmkt[2], 10);
  263. }
  264. else if(strmkt.length === 2) {
  265. m = 1;
  266. k = parseInt(strmkt[0], 10);
  267. t = parseInt(strmkt[1], 10);
  268. }
  269. }
  270. if(!m || !k || !t) {
  271. throw "IllegalArgumentException";
  272. }
  273.  
  274. let s1 = -1;
  275. let s2 = -1;
  276.  
  277. /**
  278. * @type {Object<number, number>}
  279. */
  280. const kmap = {1:1,3:1,4:1,5:1,8:1,12:1,13:1,14:1,15:1};
  281.  
  282. // 参考
  283. // 2019/1/1 Shift JIS - Wikipedia
  284. // https://en.wikipedia.org/wiki/Shift_JIS
  285. //
  286. // 区や点の判定部分は、通常94までであるため、正確にはkやtは <=94 とするべき。
  287. // しかし、Shift_JIS範囲外(IBM拡張漢字)でも利用されるため制限を取り払っている。
  288.  
  289. if(m === 1) {
  290. if((1 <= k) && (k <= 62)) {
  291. s1 = Math.floor((k + 257) / 2);
  292. }
  293. else if(63 <= k) {
  294. s1 = Math.floor((k + 385) / 2);
  295. }
  296. }
  297. else if(m === 2) {
  298. if(kmap[k]) {
  299. s1 = Math.floor((k + 479) / 2) - (Math.floor(k / 8) * 3);
  300. }
  301. else if(78 <= k) {
  302. s1 = Math.floor((k + 411) / 2);
  303. }
  304. }
  305.  
  306. if((k % 2) === 1) {
  307. if((1 <= t) && (t <= 63)) {
  308. s2 = t + 63;
  309. }
  310. else if(64 <= t) {
  311. s2 = t + 64;
  312. }
  313. }
  314. else {
  315. s2 = t + 158;
  316. }
  317.  
  318. if((s1 === -1) || (s2 === -1)) {
  319. return null;
  320. }
  321. return (s1 << 8) | s2;
  322. }
  323. /**
  324. * 指定した面区点番号から Unicode コードポイントに変換
  325. * @param {MenKuTen|string} menkuten - 面区点番号
  326. * @param {Object<number, number|Array<number>>} sjis_to_unicode - Shift_JIS-2004 から Unicode への変換マップ
  327. * @returns {Array<number>} UTF-32の配列(存在しない場合はnullを返す)
  328. * @ignore
  329. */
  330. static toUnicodeCodeFromMenKuTen(menkuten, sjis_to_unicode) {
  331. const sjis_code = SJIS.toSJIS2004CodeFromMenKuTen(menkuten);
  332. if(!sjis_code) {
  333. return null;
  334. }
  335. const unicode = sjis_to_unicode[sjis_code];
  336. if(!unicode) {
  337. return null;
  338. }
  339. if(unicode instanceof Array) {
  340. return unicode;
  341. }
  342. else {
  343. return [unicode];
  344. }
  345. }
  346.  
  347. /**
  348. * 指定した Shift_JIS のコードから区点番号に変換
  349. * @param {Number} sjis_code - Shift_JIS のコードポイント
  350. * @returns {MenKuTen} 区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
  351. */
  352. static toKuTenFromSJISCode(sjis_code) {
  353. if(!sjis_code) {
  354. return null;
  355. }
  356. const x = sjis_code;
  357. if(x < 0x100) {
  358. return null;
  359. }
  360. // アルゴリズムは区点番号表からリバースエンジニアリング
  361.  
  362. let s1 = x >> 8;
  363. let s2 = x & 0xFF;
  364. let ku = 0;
  365. let ten = 0;
  366.  
  367. // 区の計算方法の切り替え
  368. // 63区から、0x9F→0xE0に飛ぶ
  369. if(s1 < 0xE0) {
  370. s1 = s1 - 0x81;
  371. }
  372. else {
  373. s1 = s1 - 0xC1;
  374. }
  375.  
  376. // 区情報の位置判定
  377. if(s2 < 0x9f) {
  378. ku = s1 * 2 + 1;
  379. // 点情報の計算方法の切り替え
  380. // 0x7Fが欠番のため「+1」を除去
  381. if(s2 < 0x80) {
  382. s2 = s2 - 0x40 + 1;
  383. }
  384. else {
  385. s2 = s2 - 0x40;
  386. }
  387. }
  388. else {
  389. ku = s1 * 2 + 2;
  390. s2 = s2 - 0x9f + 1;
  391. }
  392.  
  393. // 点情報の位置判定
  394. ten = s2;
  395.  
  396. return {
  397. text : ku + "-" + ten,
  398. men : 1,
  399. ku : ku,
  400. ten : ten
  401. };
  402. }
  403. /**
  404. * 指定したコードポイントの文字から Shift_JIS 上の面区点番号に変換
  405. * @param {Number} unicode_codepoint - Unicodeのコードポイント
  406. * @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
  407. * @returns {MenKuTen} 面区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
  408. * @ignore
  409. */
  410. static toKuTenFromUnicode(unicode_codepoint, unicode_to_sjis) {
  411. if(!unicode_to_sjis[unicode_codepoint]) {
  412. return null;
  413. }
  414. const x = SJIS.toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis);
  415. return SJIS.toKuTenFromSJISCode(x);
  416. }
  417.  
  418. /**
  419. * 指定した面区点番号/区点番号から Shift_JIS コードに変換
  420. * @param {MenKuTen|string} kuten - 面区点番号/区点番号
  421. * @returns {Number} Shift_JIS のコードポイント(存在しない場合はnullを返す)
  422. */
  423. static toSJISCodeFromKuTen(kuten) {
  424. // 1~94区まで存在しているため面句点変換用で流用可能。
  425. // ただ、CP932の場合、範囲外の区、115区〜119区にIBM拡張文字が存在している。
  426. // 今回、toSJIS2004CodeFromMenKuTenでは区の範囲チェックをしないため問題なし。
  427. return SJIS.toSJIS2004CodeFromMenKuTen(kuten);
  428. }
  429. /**
  430. * 指定した区点番号から Unicode コードポイントに変換
  431. * @param {MenKuTen|string} kuten - 区点番号
  432. * @param {Object<number, number|Array<number>>} sjis_to_unicode - Shift_JIS から Unicode への変換マップ
  433. * @returns {Array<number>} UTF-32の配列(存在しない場合はnullを返す)
  434. * @ignore
  435. */
  436. static toUnicodeCodeFromKuTen(kuten, sjis_to_unicode) {
  437. const sjis_code = SJIS.toSJISCodeFromKuTen(kuten);
  438. if(!sjis_code) {
  439. return null;
  440. }
  441. const unicode = sjis_to_unicode[sjis_code];
  442. if(!unicode) {
  443. return null;
  444. }
  445. if(unicode instanceof Array) {
  446. return unicode;
  447. }
  448. else {
  449. return [unicode];
  450. }
  451. }
  452.  
  453. /**
  454. * Shift_JIS のコードポイントからJIS漢字水準(JIS Chinese character standard)に変換
  455. * @param {Number} sjis_code - Shift_JIS-2004 のコードポイント
  456. * @returns {Number} -1...変換不可, 0...水準なし, 1...第1水準, ...
  457. */
  458. static toJISKanjiSuijunFromSJISCode(sjis_code) {
  459. if(!sjis_code) {
  460. return 0;
  461. }
  462. const menkuten = SJIS.toMenKuTenFromSJIS2004Code(sjis_code);
  463. // アルゴリズムはJIS漢字一覧表からリバースエンジニアリング
  464. if(!menkuten) {
  465. return 0;
  466. }
  467. // 2面は第4水準
  468. if(menkuten.men > 1) {
  469. return 4;
  470. }
  471. // 1面は第1~3水準
  472. if(menkuten.ku < 14) {
  473. // 14区より小さいと非漢字
  474. return 0;
  475. }
  476. if(menkuten.ku < 16) {
  477. // 14区と15区は第3水準
  478. return 3;
  479. }
  480. if(menkuten.ku < 47) {
  481. return 1;
  482. }
  483. // 47区には、第1水準と第3水準が混じる
  484. if(menkuten.ku === 47) {
  485. if(menkuten.ten < 52) {
  486. return 1;
  487. }
  488. else {
  489. return 3;
  490. }
  491. }
  492. if(menkuten.ku < 84) {
  493. return 2;
  494. }
  495. // 84区には、第2水準と第3水準が混じる
  496. if(menkuten.ku === 84) {
  497. if(menkuten.ten < 7) {
  498. return 2;
  499. }
  500. else {
  501. return 3;
  502. }
  503. }
  504. // 残り94区まで第3水準
  505. if(menkuten.ku < 95) {
  506. return 3;
  507. }
  508. return 0;
  509. }
  510.  
  511. /**
  512. * Unicode のコードポイントからJIS漢字水準(JIS Chinese character standard)に変換
  513. * @param {Number} unicode_codepoint - Unicodeのコードポイント
  514. * @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
  515. * @returns {Number} -1...変換不可, 0...水準なし, 1...第1水準, ...
  516. * @ignore
  517. */
  518. static toJISKanjiSuijunFromUnicode(unicode_codepoint, unicode_to_sjis) {
  519. if(!unicode_to_sjis[unicode_codepoint]) {
  520. return -1;
  521. }
  522. const x = SJIS.toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis);
  523. return SJIS.toJISKanjiSuijunFromSJISCode(x);
  524. }
  525.  
  526. /**
  527. * 指定した面区点番号から Shift_JIS の仕様上、正規な物か判定
  528. * @param {MenKuTen|string} menkuten - 面区点番号(面が省略された場合は、1とみなす)
  529. * @returns {Boolean} 正規なデータは true, 不正なデータは false
  530. */
  531. static isRegularMenKuten(menkuten) {
  532. let m, k, t;
  533.  
  534. // 引数のテスト
  535. if(menkuten instanceof Object) {
  536. m = menkuten.men ? menkuten.men : 1;
  537. k = menkuten.ku;
  538. t = menkuten.ten;
  539. }
  540. else if(typeof menkuten === "string") {
  541. const strmkt = menkuten.split("-");
  542. if(strmkt.length === 3) {
  543. m = parseInt(strmkt[0], 10);
  544. k = parseInt(strmkt[1], 10);
  545. t = parseInt(strmkt[2], 10);
  546. }
  547. else if(strmkt.length === 2) {
  548. m = 1;
  549. k = parseInt(strmkt[0], 10);
  550. t = parseInt(strmkt[1], 10);
  551. }
  552. else {
  553. return false;
  554. }
  555. }
  556. else {
  557. return false;
  558. }
  559.  
  560. /**
  561. * @type {Object<number, number>}
  562. */
  563. const kmap = {1:1,3:1,4:1,5:1,8:1,12:1,13:1,14:1,15:1};
  564. if(m === 1) {
  565. // 1面は1-94区まで存在
  566. if(!((1 <= k) && (k <= 94))) {
  567. return false;
  568. }
  569. }
  570. else if(m === 2) {
  571. // 2面は、1,3,4,5,8,12,13,14,15,78-94区まで存在
  572. if(!((kmap[k]) || ((78 <= k) && (k <= 94)))) {
  573. return false;
  574. }
  575. }
  576. else {
  577. // 面が不正
  578. return false;
  579. }
  580. // 点は1-94点まで存在
  581. if(!((1 <= t) && (t <= 94))) {
  582. return false;
  583. }
  584. return true;
  585. }
  586.  
  587. }