From dfc3710108fe2617a98fca0f57f5a2f6bb7d1830 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:05:23 -0400 Subject: Add normalization of combining dakuten and handakuten to ja preprocessors (#1136) * Add normalization of combining dakuten and handakuten to ja preprocessors * Fix typo * Remove redundant variable assignment * Fix first character processed incorrectly when it is a character that gets combined * Add test for combining dakuten and handakuten --- ext/js/language/ja/japanese-text-preprocessors.js | 9 ++++ ext/js/language/ja/japanese.js | 59 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) (limited to 'ext/js/language/ja') diff --git a/ext/js/language/ja/japanese-text-preprocessors.js b/ext/js/language/ja/japanese-text-preprocessors.js index 2d0d23b3..cdd8ce9a 100644 --- a/ext/js/language/ja/japanese-text-preprocessors.js +++ b/ext/js/language/ja/japanese-text-preprocessors.js @@ -24,6 +24,7 @@ import { convertHalfWidthKanaToFullWidth, convertHiraganaToKatakana as convertHiraganaToKatakanaFunction, convertKatakanaToHiragana as convertKatakanaToHiraganaFunction, + normalizeCombiningCharacters as normalizeCombiningCharactersFunction, } from './japanese.js'; /** @type {import('language').TextProcessor} */ @@ -90,3 +91,11 @@ export const collapseEmphaticSequences = { return str; }, }; + +/** @type {import('language').TextProcessor} */ +export const normalizeCombiningCharacters = { + name: 'Normalize combining characters', + description: 'ド → ド (U+30C8 U+3099 → U+30C9)', + options: basicTextProcessorOptions, + process: (str, setting) => (setting ? normalizeCombiningCharactersFunction(str) : str), +}; diff --git a/ext/js/language/ja/japanese.js b/ext/js/language/ja/japanese.js index 3a009ebb..231d8f62 100644 --- a/ext/js/language/ja/japanese.js +++ b/ext/js/language/ja/japanese.js @@ -560,6 +560,65 @@ export function getKanaDiacriticInfo(character) { return typeof info !== 'undefined' ? {character: info.character, type: info.type} : null; } +/** + * @param {number} codePoint + * @returns {boolean} + */ +function dakutenAllowed(codePoint) { + // To reduce processing time some characters which shouldn't have dakuten but are highly unlikely to have a combining character attached are included + // かがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとはばぱひびぴふぶぷへべぺほ + // カガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトハバパヒビピフブプヘベペホ + return ((codePoint >= 0x304B && codePoint <= 0x3068) || + (codePoint >= 0x306F && codePoint <= 0x307B) || + (codePoint >= 0x30AB && codePoint <= 0x30C8) || + (codePoint >= 0x30CF && codePoint <= 0x30DB)); +} + +/** + * @param {number} codePoint + * @returns {boolean} + */ +function handakutenAllowed(codePoint) { + // To reduce processing time some characters which shouldn't have handakuten but are highly unlikely to have a combining character attached are included + // はばぱひびぴふぶぷへべぺほ + // ハバパヒビピフブプヘベペホ + return ((codePoint >= 0x306F && codePoint <= 0x307B) || + (codePoint >= 0x30CF && codePoint <= 0x30DB)); +} + +/** + * @param {string} text + * @returns {string} + */ +export function normalizeCombiningCharacters(text) { + let result = ''; + let i = text.length - 1; + // Ignoring the first character is intentional, it cannot combine with anything + while (i > 0) { + if (text[i] === '\u3099') { + const dakutenCombinee = text[i - 1].codePointAt(0); + if (dakutenCombinee && dakutenAllowed(dakutenCombinee)) { + result = String.fromCodePoint(dakutenCombinee + 1) + result; + i -= 2; + continue; + } + } else if (text[i] === '\u309A') { + const handakutenCombinee = text[i - 1].codePointAt(0); + if (handakutenCombinee && handakutenAllowed(handakutenCombinee)) { + result = String.fromCodePoint(handakutenCombinee + 2) + result; + i -= 2; + continue; + } + } + result = text[i] + result; + i--; + } + // i === -1 when first two characters are combined + if (i === 0) { + result = text[0] + result; + } + return result; +} // Furigana distribution -- cgit v1.2.3