src/encode/SJIS.js
/**
* The script is part of MojiJS.
*
* AUTHOR:
* natade (http://twitter.com/natadea)
*
* LICENSE:
* The MIT license https://opensource.org/licenses/MIT
*/
import Unicode from "./Unicode.js";
/**
* 面区点情報
* @typedef {Object} MenKuTen
* @property {string} [text] 面-区-点
* @property {number} [men=1] 面
* @property {number} ku 区
* @property {number} ten 点
*/
/**
* Shift_JIS を扱うクラス
* @ignore
*/
export default class SJIS {
/**
* 文字列を Shift_JIS の配列に変換
* @param {String} text - 変換したいテキスト
* @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
* @returns {Array<number>} Shift_JIS のデータが入った配列
* @ignore
*/
static toSJISArray(text, unicode_to_sjis) {
const map = unicode_to_sjis;
const utf32 = Unicode.toUTF32Array(text);
const sjis = [];
const ng = "?".charCodeAt(0);
for(let i = 0; i < utf32.length; i++) {
const map_bin = map[utf32[i]];
if(map_bin) {
sjis.push(map_bin);
}
else {
sjis.push(ng);
}
}
return sjis;
}
/**
* 文字列を Shift_JIS のバイナリ配列に変換
* - 日本語文字は2バイトとして、配列も2つ分、使用します。
* @param {String} text - 変換したいテキスト
* @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
* @returns {Array<number>} Shift_JIS のデータが入ったバイナリ配列
* @ignore
*/
static toSJISBinary(text, unicode_to_sjis) {
const sjis = SJIS.toSJISArray(text, unicode_to_sjis);
const sjisbin = [];
for(let i = 0; i < sjis.length; i++) {
if(sjis[i] < 0x100) {
sjisbin.push(sjis[i]);
}
else {
sjisbin.push(sjis[i] >> 8);
sjisbin.push(sjis[i] & 0xFF);
}
}
return sjisbin;
}
/**
* SJISの配列から文字列に変換
* @param {Array<number>} sjis - 変換したいテキスト
* @param {Object<number, number|Array<number>>} sjis_to_unicode - Shift_JIS から Unicode への変換マップ
* @returns {String} 変換後のテキスト
* @ignore
*/
static fromSJISArray(sjis, sjis_to_unicode) {
const map = sjis_to_unicode;
const utf16 = [];
const ng = "?".charCodeAt(0);
for(let i = 0; i < sjis.length; i++) {
let x = sjis[i];
/**
* @type {number|Array<number>}
*/
let y = [];
if(x >= 0x100) {
// すでに1つの変数にまとめられている
y = map[x];
}
else {
// 2バイト文字かのチェック
if( ((0x81 <= x) && (x <= 0x9F)) || ((0xE0 <= x) && (x <= 0xFC)) ) {
x <<= 8;
i++;
x |= sjis[i];
y = map[x];
}
else {
y = map[x];
}
}
if(y) {
// 配列なら配列を結合
// ※ Unicodeの結合文字の可能性があるため
if(y instanceof Array) {
for(let j = 0; j < y.length; j++) {
utf16.push(y[j]);
}
}
// 値しかない場合は値を結合
else {
utf16.push(y);
}
}
else {
utf16.push(ng);
}
}
return Unicode.fromUTF32Array(utf16);
}
/**
* 指定したコードポイントの文字から Shift_JIS 上の符号化数値に変換
* @param {Number} unicode_codepoint - Unicodeのコードポイント
* @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
* @returns {Number} 符号化数値(変換できない場合はnullとなる)
* @ignore
*/
static toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis) {
if(!unicode_to_sjis[unicode_codepoint]) {
return null;
}
const utf16_text = Unicode.fromUTF32Array([unicode_codepoint]);
const sjis_array = SJIS.toSJISArray(utf16_text, unicode_to_sjis);
return sjis_array[0];
}
/**
* 指定した Shift_JIS-2004 のコードから面区点番号に変換
* @param {Number} sjis_code - Shift_JIS-2004 のコードポイント
* @returns {MenKuTen} 面区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
*/
static toMenKuTenFromSJIS2004Code(sjis_code) {
if(!sjis_code) {
return null;
}
const x = sjis_code;
if(x < 0x100) {
return null;
}
// アルゴリズムは面区点番号表からリバースエンジニアリング
let s1 = x >> 8;
let s2 = x & 0xFF;
let men = 0;
let ku = 0;
let ten = 0;
// 面情報の位置判定
if(s1 < 0xF0) {
men = 1;
// 区の計算方法の切り替え
// 63区から、0x9F→0xE0に飛ぶ
if(s1 < 0xE0) {
s1 = s1 - 0x81;
}
else {
s1 = s1 - 0xC1;
}
}
else {
// ※2面は第4水準のみ
men = 2;
// 2面1区 ~ 2面8区
if((((s1 === 0xF0) || (s1 === 0xF2)) && (s2 < 0x9F)) || (s1 === 0xF1)) {
s1 = s1 - 0xF0;
}
// 2面12区 ~ 2面15区
else if(((s1 === 0xF4) && (s2 < 0x9F)) || (s1 < 0xF4)) {
s1 = s1 - 0xED;
}
// 2面78区 ~ 2面94区
else {
s1 = s1 - 0xCE;
}
}
// 区情報の位置判定
if(s2 < 0x9f) {
ku = s1 * 2 + 1;
// 点情報の計算方法の切り替え
// 0x7Fが欠番のため「+1」を除去
if(s2 < 0x80) {
s2 = s2 - 0x40 + 1;
}
else {
s2 = s2 - 0x40;
}
}
else {
ku = s1 * 2 + 2;
s2 = s2 - 0x9f + 1;
}
// 点情報の位置判定
ten = s2;
return {
text : "" + men + "-" + ku + "-" + ten,
men : men,
ku : ku,
ten : ten
};
}
/**
* 指定したコードポイントの文字から Shift_JIS-2004 上の面区点番号に変換
* @param {Number} unicode_codepoint - Unicodeのコードポイント
* @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS-2004 への変換マップ
* @returns {MenKuTen} 面区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
* @ignore
*/
static toMenKuTenFromUnicode(unicode_codepoint, unicode_to_sjis) {
if(!unicode_to_sjis[unicode_codepoint]) {
return null;
}
const x = SJIS.toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis);
return SJIS.toMenKuTenFromSJIS2004Code(x);
}
/**
* 指定した面区点番号から Shift_JIS-2004 コードに変換
* @param {MenKuTen|string} menkuten - 面区点番号(面が省略された場合は、1とみなす)
* @returns {Number} Shift_JIS-2004 のコードポイント(存在しない場合はnullを返す)
*/
static toSJIS2004CodeFromMenKuTen(menkuten) {
let m = null, k = null, t = null;
let text = null;
if(menkuten instanceof Object) {
if(menkuten.text && (typeof menkuten.text === "string")) {
text = menkuten.text;
}
else if((menkuten.ku) && (menkuten.ten)) {
m = menkuten.men ? menkuten.men : 1;
k = menkuten.ku;
t = menkuten.ten;
}
}
else if((typeof menkuten === "string")) {
text = menkuten;
}
if(text) {
const strmkt = text.split("-");
if(strmkt.length === 3) {
m = parseInt(strmkt[0], 10);
k = parseInt(strmkt[1], 10);
t = parseInt(strmkt[2], 10);
}
else if(strmkt.length === 2) {
m = 1;
k = parseInt(strmkt[0], 10);
t = parseInt(strmkt[1], 10);
}
}
if(!m || !k || !t) {
throw "IllegalArgumentException";
}
let s1 = -1;
let s2 = -1;
/**
* @type {Object<number, number>}
*/
const kmap = {1:1,3:1,4:1,5:1,8:1,12:1,13:1,14:1,15:1};
// 参考
// 2019/1/1 Shift JIS - Wikipedia
// https://en.wikipedia.org/wiki/Shift_JIS
//
// 区や点の判定部分は、通常94までであるため、正確にはkやtは <=94 とするべき。
// しかし、Shift_JIS範囲外(IBM拡張漢字)でも利用されるため制限を取り払っている。
if(m === 1) {
if((1 <= k) && (k <= 62)) {
s1 = Math.floor((k + 257) / 2);
}
else if(63 <= k) {
s1 = Math.floor((k + 385) / 2);
}
}
else if(m === 2) {
if(kmap[k]) {
s1 = Math.floor((k + 479) / 2) - (Math.floor(k / 8) * 3);
}
else if(78 <= k) {
s1 = Math.floor((k + 411) / 2);
}
}
if((k % 2) === 1) {
if((1 <= t) && (t <= 63)) {
s2 = t + 63;
}
else if(64 <= t) {
s2 = t + 64;
}
}
else {
s2 = t + 158;
}
if((s1 === -1) || (s2 === -1)) {
return null;
}
return (s1 << 8) | s2;
}
/**
* 指定した面区点番号から Unicode コードポイントに変換
* @param {MenKuTen|string} menkuten - 面区点番号
* @param {Object<number, number|Array<number>>} sjis_to_unicode - Shift_JIS-2004 から Unicode への変換マップ
* @returns {Array<number>} UTF-32の配列(存在しない場合はnullを返す)
* @ignore
*/
static toUnicodeCodeFromMenKuTen(menkuten, sjis_to_unicode) {
const sjis_code = SJIS.toSJIS2004CodeFromMenKuTen(menkuten);
if(!sjis_code) {
return null;
}
const unicode = sjis_to_unicode[sjis_code];
if(!unicode) {
return null;
}
if(unicode instanceof Array) {
return unicode;
}
else {
return [unicode];
}
}
/**
* 指定した Shift_JIS のコードから区点番号に変換
* @param {Number} sjis_code - Shift_JIS のコードポイント
* @returns {MenKuTen} 区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
*/
static toKuTenFromSJISCode(sjis_code) {
if(!sjis_code) {
return null;
}
const x = sjis_code;
if(x < 0x100) {
return null;
}
// アルゴリズムは区点番号表からリバースエンジニアリング
let s1 = x >> 8;
let s2 = x & 0xFF;
let ku = 0;
let ten = 0;
// 区の計算方法の切り替え
// 63区から、0x9F→0xE0に飛ぶ
if(s1 < 0xE0) {
s1 = s1 - 0x81;
}
else {
s1 = s1 - 0xC1;
}
// 区情報の位置判定
if(s2 < 0x9f) {
ku = s1 * 2 + 1;
// 点情報の計算方法の切り替え
// 0x7Fが欠番のため「+1」を除去
if(s2 < 0x80) {
s2 = s2 - 0x40 + 1;
}
else {
s2 = s2 - 0x40;
}
}
else {
ku = s1 * 2 + 2;
s2 = s2 - 0x9f + 1;
}
// 点情報の位置判定
ten = s2;
return {
text : ku + "-" + ten,
men : 1,
ku : ku,
ten : ten
};
}
/**
* 指定したコードポイントの文字から Shift_JIS 上の面区点番号に変換
* @param {Number} unicode_codepoint - Unicodeのコードポイント
* @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
* @returns {MenKuTen} 面区点番号(存在しない場合(1バイトのJISコードなど)はnullを返す)
* @ignore
*/
static toKuTenFromUnicode(unicode_codepoint, unicode_to_sjis) {
if(!unicode_to_sjis[unicode_codepoint]) {
return null;
}
const x = SJIS.toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis);
return SJIS.toKuTenFromSJISCode(x);
}
/**
* 指定した面区点番号/区点番号から Shift_JIS コードに変換
* @param {MenKuTen|string} kuten - 面区点番号/区点番号
* @returns {Number} Shift_JIS のコードポイント(存在しない場合はnullを返す)
*/
static toSJISCodeFromKuTen(kuten) {
// 1~94区まで存在しているため面句点変換用で流用可能。
// ただ、CP932の場合、範囲外の区、115区〜119区にIBM拡張文字が存在している。
// 今回、toSJIS2004CodeFromMenKuTenでは区の範囲チェックをしないため問題なし。
return SJIS.toSJIS2004CodeFromMenKuTen(kuten);
}
/**
* 指定した区点番号から Unicode コードポイントに変換
* @param {MenKuTen|string} kuten - 区点番号
* @param {Object<number, number|Array<number>>} sjis_to_unicode - Shift_JIS から Unicode への変換マップ
* @returns {Array<number>} UTF-32の配列(存在しない場合はnullを返す)
* @ignore
*/
static toUnicodeCodeFromKuTen(kuten, sjis_to_unicode) {
const sjis_code = SJIS.toSJISCodeFromKuTen(kuten);
if(!sjis_code) {
return null;
}
const unicode = sjis_to_unicode[sjis_code];
if(!unicode) {
return null;
}
if(unicode instanceof Array) {
return unicode;
}
else {
return [unicode];
}
}
/**
* Shift_JIS のコードポイントからJIS漢字水準(JIS Chinese character standard)に変換
* @param {Number} sjis_code - Shift_JIS-2004 のコードポイント
* @returns {Number} -1...変換不可, 0...水準なし, 1...第1水準, ...
*/
static toJISKanjiSuijunFromSJISCode(sjis_code) {
if(!sjis_code) {
return 0;
}
const menkuten = SJIS.toMenKuTenFromSJIS2004Code(sjis_code);
// アルゴリズムはJIS漢字一覧表からリバースエンジニアリング
if(!menkuten) {
return 0;
}
// 2面は第4水準
if(menkuten.men > 1) {
return 4;
}
// 1面は第1~3水準
if(menkuten.ku < 14) {
// 14区より小さいと非漢字
return 0;
}
if(menkuten.ku < 16) {
// 14区と15区は第3水準
return 3;
}
if(menkuten.ku < 47) {
return 1;
}
// 47区には、第1水準と第3水準が混じる
if(menkuten.ku === 47) {
if(menkuten.ten < 52) {
return 1;
}
else {
return 3;
}
}
if(menkuten.ku < 84) {
return 2;
}
// 84区には、第2水準と第3水準が混じる
if(menkuten.ku === 84) {
if(menkuten.ten < 7) {
return 2;
}
else {
return 3;
}
}
// 残り94区まで第3水準
if(menkuten.ku < 95) {
return 3;
}
return 0;
}
/**
* Unicode のコードポイントからJIS漢字水準(JIS Chinese character standard)に変換
* @param {Number} unicode_codepoint - Unicodeのコードポイント
* @param {Object<number, number>} unicode_to_sjis - Unicode から Shift_JIS への変換マップ
* @returns {Number} -1...変換不可, 0...水準なし, 1...第1水準, ...
* @ignore
*/
static toJISKanjiSuijunFromUnicode(unicode_codepoint, unicode_to_sjis) {
if(!unicode_to_sjis[unicode_codepoint]) {
return -1;
}
const x = SJIS.toSJISCodeFromUnicode(unicode_codepoint, unicode_to_sjis);
return SJIS.toJISKanjiSuijunFromSJISCode(x);
}
/**
* 指定した面区点番号から Shift_JIS の仕様上、正規な物か判定
* @param {MenKuTen|string} menkuten - 面区点番号(面が省略された場合は、1とみなす)
* @returns {Boolean} 正規なデータは true, 不正なデータは false
*/
static isRegularMenKuten(menkuten) {
let m, k, t;
// 引数のテスト
if(menkuten instanceof Object) {
m = menkuten.men ? menkuten.men : 1;
k = menkuten.ku;
t = menkuten.ten;
}
else if(typeof menkuten === "string") {
const strmkt = menkuten.split("-");
if(strmkt.length === 3) {
m = parseInt(strmkt[0], 10);
k = parseInt(strmkt[1], 10);
t = parseInt(strmkt[2], 10);
}
else if(strmkt.length === 2) {
m = 1;
k = parseInt(strmkt[0], 10);
t = parseInt(strmkt[1], 10);
}
else {
return false;
}
}
else {
return false;
}
/**
* @type {Object<number, number>}
*/
const kmap = {1:1,3:1,4:1,5:1,8:1,12:1,13:1,14:1,15:1};
if(m === 1) {
// 1面は1-94区まで存在
if(!((1 <= k) && (k <= 94))) {
return false;
}
}
else if(m === 2) {
// 2面は、1,3,4,5,8,12,13,14,15,78-94区まで存在
if(!((kmap[k]) || ((78 <= k) && (k <= 94)))) {
return false;
}
}
else {
// 面が不正
return false;
}
// 点は1-94点まで存在
if(!((1 <= t) && (t <= 94))) {
return false;
}
return true;
}
}