Home Reference Source

src/mojijs/Japanese.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";

/**
 * 日本語の変換を扱うクラス
 */
export default class Japanese {

	/**
	 * カタカナをひらがなに変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHiragana(text) {
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			return(String.fromCharCode(ch.charCodeAt(0) - 0x0060));
		};
		return (text.replace(/[\u30A1-\u30F6]/g, func));
	}

	/**
	 * ひらがなをカタカナに変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toKatakana(text) {
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			return(String.fromCharCode(ch.charCodeAt(0) + 0x0060));
		};
		return (text.replace(/[\u3041-\u3096]/g, func));
	}
	
	/**
	 * スペースを半角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHalfWidthSpace(text) {
		return (text.replace(/\u3000/g, String.fromCharCode(0x0020)));
	}
	
	/**
	 * スペースを全角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toFullWidthSpace(text) {
		return (text.replace(/\u0020/g, String.fromCharCode(0x3000)));
	}
	
	/**
	 * 英数記号を半角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHalfWidthAsciiCode(text) {
		let out = text;
		out = out.replace(/\u3000/g, "\u0020");				//全角スペース
		out = out.replace(/[\u2018-\u201B]/g, "\u0027");	//シングルクォーテーション
		out = out.replace(/[\u201C-\u201F]/g, "\u0022");	//ダブルクォーテーション
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			const code = ch.charCodeAt(0);
			return (String.fromCharCode(code - 0xFEE0));
		};
		return (out.replace(/[\uFF01-\uFF5E]/g, func));
	}
	
	/**
	 * 英数記号を全角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toFullWidthAsciiCode(text) {
		let out = text;
		out = out.replace(/\u0020/g, "\u3000");	//全角スペース
		out = out.replace(/\u0022/g, "\u201D");	//ダブルクォーテーション
		out = out.replace(/\u0027/g, "\u2019");	//アポストロフィー
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			const code = ch.charCodeAt(0);
			return (String.fromCharCode(code + 0xFEE0));
		};
		return (out.replace(/[\u0020-\u007E]/g, func));
	}
	
	/**
	 * アルファベットを半角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHalfWidthAlphabet(text) {
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			return (String.fromCharCode(ch.charCodeAt(0) - 0xFEE0));
		};
		return (text.replace(/[\uFF21-\uFF3A\uFF41-\uFF5A]/g, func));
	}
	
	/**
	 * アルファベットを全角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toFullWidthAlphabet(text) {
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			return (String.fromCharCode(ch.charCodeAt(0) + 0xFEE0));
		};
		return (text.replace(/[A-Za-z]/g, func));
	}
	
	/**
	 * 数値を半角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHalfWidthNumber(text) {
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			return(String.fromCharCode(ch.charCodeAt(0) - 0xFEE0));
		};
		return (text.replace(/[\uFF10-\uFF19]/g, func));
	}
	
	/**
	 * 数値を全角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toFullWidthNumber(text) {
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			return(String.fromCharCode(ch.charCodeAt(0) + 0xFEE0));
		};
		return (text.replace(/[0-9]/g, func));
	}
	
	/**
	 * カタカナを半角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHalfWidthKana(text) {
		/**
		 * @type {Object<number, string>}
		 * @private
		 */
		const map = {
			0x3001	:	"\uFF64"	,	//	、
			0x3002	:	"\uFF61"	,	//	。	。
			0x300C	:	"\uFF62"	,	//	「	「
			0x300D	:	"\uFF63"	,	//	」	」
			0x309B	:	"\uFF9E"	,	//	゛	゙
			0x309C	:	"\uFF9F"	,	//	゜	゚
			0x30A1	:	"\uFF67"	,	//	ァ	ァ
			0x30A2	:	"\uFF71"	,	//	ア	ア
			0x30A3	:	"\uFF68"	,	//	ィ	ィ
			0x30A4	:	"\uFF72"	,	//	イ	イ
			0x30A5	:	"\uFF69"	,	//	ゥ	ゥ
			0x30A6	:	"\uFF73"	,	//	ウ	ウ
			0x30A7	:	"\uFF6A"	,	//	ェ	ェ
			0x30A8	:	"\uFF74"	,	//	エ	エ
			0x30A9	:	"\uFF6B"	,	//	ォ	ォ
			0x30AA	:	"\uFF75"	,	//	オ	オ
			0x30AB	:	"\uFF76"	,	//	カ	カ
			0x30AC	:	"\uFF76\uFF9E"	,	//	ガ	ガ
			0x30AD	:	"\uFF77"	,	//	キ	キ
			0x30AE	:	"\uFF77\uFF9E"	,	//	ギ	ギ
			0x30AF	:	"\uFF78"	,	//	ク	ク
			0x30B0	:	"\uFF78\uFF9E"	,	//	グ	グ
			0x30B1	:	"\uFF79"	,	//	ケ	ケ
			0x30B2	:	"\uFF79\uFF9E"	,	//	ゲ	ゲ
			0x30B3	:	"\uFF7A"	,	//	コ	コ
			0x30B4	:	"\uFF7A\uFF9E"	,	//	ゴ	ゴ
			0x30B5	:	"\uFF7B"	,	//	サ	サ
			0x30B6	:	"\uFF7B\uFF9E"	,	//	ザ	ザ
			0x30B7	:	"\uFF7C"	,	//	シ	シ
			0x30B8	:	"\uFF7C\uFF9E"	,	//	ジ	ジ
			0x30B9	:	"\uFF7D"	,	//	ス	ス
			0x30BA	:	"\uFF7D\uFF9E"	,	//	ズ	ズ
			0x30BB	:	"\uFF7E"	,	//	セ	セ
			0x30BC	:	"\uFF7E\uFF9E"	,	//	ゼ	ゼ
			0x30BD	:	"\uFF7F"	,	//	ソ	ソ
			0x30BE	:	"\uFF7F\uFF9E"	,	//	ゾ	ゾ
			0x30BF	:	"\uFF80"	,	//	タ	タ
			0x30C0	:	"\uFF80\uFF9E"	,	//	ダ	ダ
			0x30C1	:	"\uFF81"	,	//	チ	チ
			0x30C2	:	"\uFF81\uFF9E"	,	//	ヂ	ヂ
			0x30C3	:	"\uFF6F"	,	//	ッ	ッ
			0x30C4	:	"\uFF82"	,	//	ツ	ツ
			0x30C5	:	"\uFF82\uFF9E"	,	//	ヅ	ヅ
			0x30C6	:	"\uFF83"	,	//	テ	テ
			0x30C7	:	"\uFF83\uFF9E"	,	//	デ	デ
			0x30C8	:	"\uFF84"	,	//	ト	ト
			0x30C9	:	"\uFF84\uFF9E"	,	//	ド	ド
			0x30CA	:	"\uFF85"	,	//	ナ	ナ
			0x30CB	:	"\uFF86"	,	//	ニ	ニ
			0x30CC	:	"\uFF87"	,	//	ヌ	ヌ
			0x30CD	:	"\uFF88"	,	//	ネ	ネ
			0x30CE	:	"\uFF89"	,	//	ノ	ノ
			0x30CF	:	"\uFF8A"	,	//	ハ	ハ
			0x30D0	:	"\uFF8A\uFF9E"	,	//	バ	バ
			0x30D1	:	"\uFF8A\uFF9F"	,	//	パ	パ
			0x30D2	:	"\uFF8B"	,	//	ヒ	ヒ
			0x30D3	:	"\uFF8B\uFF9E"	,	//	ビ	ビ
			0x30D4	:	"\uFF8B\uFF9F"	,	//	ピ	ピ
			0x30D5	:	"\uFF8C"	,	//	フ	フ
			0x30D6	:	"\uFF8C\uFF9E"	,	//	ブ	ブ
			0x30D7	:	"\uFF8C\uFF9F"	,	//	プ	プ
			0x30D8	:	"\uFF8D"	,	//	ヘ	ヘ
			0x30D9	:	"\uFF8D\uFF9E"	,	//	ベ	ベ
			0x30DA	:	"\uFF8D\uFF9F"	,	//	ペ	ペ
			0x30DB	:	"\uFF8E"	,	//	ホ	ホ
			0x30DC	:	"\uFF8E\uFF9E"	,	//	ボ	ボ
			0x30DD	:	"\uFF8E\uFF9F"	,	//	ポ	ポ
			0x30DE	:	"\uFF8F"	,	//	マ	マ
			0x30DF	:	"\uFF90"	,	//	ミ	ミ
			0x30E0	:	"\uFF91"	,	//	ム	ム
			0x30E1	:	"\uFF92"	,	//	メ	メ
			0x30E2	:	"\uFF93"	,	//	モ	モ
			0x30E3	:	"\uFF6C"	,	//	ャ	ャ
			0x30E4	:	"\uFF94"	,	//	ヤ	ヤ
			0x30E5	:	"\uFF6D"	,	//	ュ	ュ
			0x30E6	:	"\uFF95"	,	//	ユ	ユ
			0x30E7	:	"\uFF6E"	,	//	ョ	ョ
			0x30E8	:	"\uFF96"	,	//	ヨ	ヨ
			0x30E9	:	"\uFF97"	,	//	ラ	ラ
			0x30EA	:	"\uFF98"	,	//	リ	リ
			0x30EB	:	"\uFF99"	,	//	ル	ル
			0x30EC	:	"\uFF9A"	,	//	レ	レ
			0x30ED	:	"\uFF9B"	,	//	ロ	ロ
			0x30EE	:	"\uFF9C"	,	//	ヮ	ワ
			0x30EF	:	"\uFF9C"	,	//	ワ	ワ
			0x30F0	:	"\uFF72"	,	//	ヰ	イ
			0x30F1	:	"\uFF74"	,	//	ヱ	エ
			0x30F2	:	"\uFF66"	,	//	ヲ	ヲ
			0x30F3	:	"\uFF9D"	,	//	ン	ン
			0x30F4	:	"\uFF73\uFF9E"	,	//	ヴ	ヴ
			0x30F5	:	"\uFF76"	,	//	ヵ	カ
			0x30F6	:	"\uFF79"	,	//	ヶ	ケ
			0x30F7	:	"\uFF9C\uFF9E"	,	//	ヷ	ヷ
			0x30F8	:	"\uFF72\uFF9E"	,	//	ヸ	イ゙
			0x30F9	:	"\uFF74\uFF9E"	,	//	ヹ	エ゙
			0x30FA	:	"\uFF66\uFF9E"	,	//	ヺ	ヺ
			0x30FB	:	"\uFF65"	,	//	・	・
			0x30FC	:	"\uFF70"		//	ー	ー
		};
		/**
		 * @param {string} ch 
		 * @private
		 */
		const func = function(ch) {
			if(ch.length === 1) {
				return(map[ch.charCodeAt(0)]);
			}
			else {
				return(map[ch.charCodeAt(0)] + map[ch.charCodeAt(1)]);
			}
		};
		return (text.replace(/[\u3001\u3002\u300C\u300D\u309B\u309C\u30A1-\u30FC][\u309B\u309C]?/g, func));
	}

	/**
	 * カタカナを全角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toFullWidthKana(text) {
		/**
		 * @type {Object<number, number>}
		 * @private
		 */
		const map = {
			0xFF61	:	0x3002	,	//	。	。
			0xFF62	:	0x300C	,	//	「	「
			0xFF63	:	0x300D	,	//	」	」
			0xFF64	:	0x3001	,	//	、
			0xFF65	:	0x30FB	,	//	・	・
			0xFF66	:	0x30F2	,	//	ヲ	ヲ
			0xFF67	:	0x30A1	,	//	ァ	ァ
			0xFF68	:	0x30A3	,	//	ィ	ィ
			0xFF69	:	0x30A5	,	//	ゥ	ゥ
			0xFF6A	:	0x30A7	,	//	ェ	ェ
			0xFF6B	:	0x30A9	,	//	ォ	ォ
			0xFF6C	:	0x30E3	,	//	ャ	ャ
			0xFF6D	:	0x30E5	,	//	ュ	ュ
			0xFF6E	:	0x30E7	,	//	ョ	ョ
			0xFF6F	:	0x30C3	,	//	ッ	ッ
			0xFF70	:	0x30FC	,	//	ー	ー
			0xFF71	:	0x30A2	,	//	ア	ア
			0xFF72	:	0x30A4	,	//	イ	イ
			0xFF73	:	0x30A6	,	//	ウ	ウ
			0xFF74	:	0x30A8	,	//	エ	エ
			0xFF75	:	0x30AA	,	//	オ	オ
			0xFF76	:	0x30AB	,	//	カ	カ
			0xFF77	:	0x30AD	,	//	キ	キ
			0xFF78	:	0x30AF	,	//	ク	ク
			0xFF79	:	0x30B1	,	//	ケ	ケ
			0xFF7A	:	0x30B3	,	//	コ	コ
			0xFF7B	:	0x30B5	,	//	サ	サ
			0xFF7C	:	0x30B7	,	//	シ	シ
			0xFF7D	:	0x30B9	,	//	ス	ス
			0xFF7E	:	0x30BB	,	//	セ	セ
			0xFF7F	:	0x30BD	,	//	ソ	ソ
			0xFF80	:	0x30BF	,	//	タ	タ
			0xFF81	:	0x30C1	,	//	チ	チ
			0xFF82	:	0x30C4	,	//	ツ	ツ
			0xFF83	:	0x30C6	,	//	テ	テ
			0xFF84	:	0x30C8	,	//	ト	ト
			0xFF85	:	0x30CA	,	//	ナ	ナ
			0xFF86	:	0x30CB	,	//	ニ	ニ
			0xFF87	:	0x30CC	,	//	ヌ	ヌ
			0xFF88	:	0x30CD	,	//	ネ	ネ
			0xFF89	:	0x30CE	,	//	ノ	ノ
			0xFF8A	:	0x30CF	,	//	ハ	ハ
			0xFF8B	:	0x30D2	,	//	ヒ	ヒ
			0xFF8C	:	0x30D5	,	//	フ	フ
			0xFF8D	:	0x30D8	,	//	ヘ	ヘ
			0xFF8E	:	0x30DB	,	//	ホ	ホ
			0xFF8F	:	0x30DE	,	//	マ	マ
			0xFF90	:	0x30DF	,	//	ミ	ミ
			0xFF91	:	0x30E0	,	//	ム	ム
			0xFF92	:	0x30E1	,	//	メ	メ
			0xFF93	:	0x30E2	,	//	モ	モ
			0xFF94	:	0x30E4	,	//	ヤ	ヤ
			0xFF95	:	0x30E6	,	//	ユ	ユ
			0xFF96	:	0x30E8	,	//	ヨ	ヨ
			0xFF97	:	0x30E9	,	//	ラ	ラ
			0xFF98	:	0x30EA	,	//	リ	リ
			0xFF99	:	0x30EB	,	//	ル	ル
			0xFF9A	:	0x30EC	,	//	レ	レ
			0xFF9B	:	0x30ED	,	//	ロ	ロ
			0xFF9C	:	0x30EF	,	//	ワ	ワ
			0xFF9D	:	0x30F3	,	//	ン	ン
			0xFF9E	:	0x309B	,	//	゛	゙
			0xFF9F	:	0x309C		//	゜	゚
		};
		/**
		 * @param {string} str 
		 * @private
		 */
		const func = function(str) {
			if(str.length === 1) {
				return (String.fromCharCode(map[str.charCodeAt(0)]));
			}
			else {
				const next = str.charCodeAt(1);
				const ch   = str.charCodeAt(0);
				if(next === 0xFF9E) {
					// Shift-JISにない濁点(ヷ、ヸ、ヹ、ヺ)は意図的に無視
					// ヴ
					if (ch === 0xFF73) {
						return (String.fromCharCode(0x3094));
					}
					// ガ-ド、バ-ボ
					else if(
						((0xFF76 <= ch) && (ch <= 0xFF84)) ||
						((0xFF8A <= ch) && (ch <= 0xFF8E))	) {
						return (String.fromCharCode(map[ch] + 1));
					}
				}
				// 半濁点
				else if(next === 0xFF9F) {
					// パ-ポ
					if((0xFF8A <= ch) && (ch <= 0xFF8E)) {
						return (String.fromCharCode(map[ch] + 2));
					}
				}
				return (String.fromCharCode(map[ch]) + String.fromCharCode(map[next]));
			}
		};
		return (text.replace(/[\uFF61-\uFF9F][\uFF9E\uFF9F]?/g, func));
	}
	
	/**
	 * 半角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toHalfWidth(text) {
		return Japanese.toHalfWidthKana(Japanese.toHalfWidthAsciiCode(text));
	}
	
	/**
	 * 全角に変換
	 * @param {String} text - 変換したいテキスト
	 * @returns {String} 変換後のテキスト
	 */
	static toFullWidth(text) {
		return Japanese.toFullWidthKana(Japanese.toFullWidthAsciiCode(text));
	}

	/**
	 * 指定したテキストの横幅を半角/全角でカウント
	 * - 半角を1、全角を2としてカウント
	 * - 半角は、ASCII文字、半角カタカナ。全角はそれ以外とします。
	 * @param {String} text - カウントしたいテキスト
	 * @returns {Number} 文字の横幅
	 */
	static getWidth(text) {
		const utf32_array = Unicode.toUTF32Array(text);
		let count = 0;
		for(let i = 0; i < utf32_array.length; i++) {
			const ch = utf32_array[i];
			if((ch < 0x80) || ((0xFF61 <= ch) && (ch < 0xFFA0))) {
				count++;
			}
			else {
				count += 2;
			}
		}
		return count;
	}

	/**
	 * 指定したテキストの横幅を半角/全角で換算した場合の切り出し
	 * - 半角を1、全角を2としてカウント
	 * - 半角は、ASCII文字、半角カタカナ。全角はそれ以外とします。
	 * @param {String} text - 切り出したいテキスト
	 * @param {Number} offset - 切り出し位置
	 * @param {Number} size - 切り出す長さ
	 * @returns {String} 切り出したテキスト
	 */
	static cutTextForWidth(text, offset, size) {
		const utf32_array = Unicode.toUTF32Array(text);
		const SPACE = 0x20 ; // ' '
		const output = [];
		let is_target = false;
		let position = 0;
		let cut_size = size;
		let ioffset = offset;
		if(ioffset < 0) {
			cut_size += ioffset;
			ioffset = 0;
		}
		if(cut_size <= 0) {
			return "";
		}
		for(let i = 0; i < utf32_array.length; i++) {
			const ch = utf32_array[i];
			const ch_size = ((ch < 0x80) || ((0xFF61 <= ch) && (ch < 0xFFA0))) ? 1 : 2;
			if(position >= ioffset) {
				is_target = true;
				if(cut_size >= ch_size) {
					output.push(ch);
				}
				else {
					output.push(SPACE);
				}
				cut_size -= ch_size;
				if(cut_size <= 0) {
					break;
				}
			}
			position += ch_size;
			// 2バイト文字の途中をoffset指定していた場合になる。
			if(((position - 1) >= ioffset) && !is_target) {
				cut_size--;
				output.push(SPACE);
			}
		}
		return Unicode.fromUTF32Array(output);
	}

}