diff options
Diffstat (limited to 'src/virtualkeyboard/hangul.cpp')
-rw-r--r-- | src/virtualkeyboard/hangul.cpp | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/src/virtualkeyboard/hangul.cpp b/src/virtualkeyboard/hangul.cpp new file mode 100644 index 00000000..d5b820e8 --- /dev/null +++ b/src/virtualkeyboard/hangul.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://www.qt.io +** +** This file is part of the Qt Virtual Keyboard add-on for Qt Enterprise. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://www.qt.io +** +****************************************************************************/ + +#include "hangul.h" + +const QList<ushort> Hangul::initials = QList<ushort>() + << 0x3131 << 0x3132 << 0x3134 << 0x3137 << 0x3138 << 0x3139 << 0x3141 + << 0x3142 << 0x3143 << 0x3145 << 0x3146 << 0x3147 << 0x3148 << 0x3149 + << 0x314A << 0x314B << 0x314C << 0x314D << 0x314E; +const QList<ushort> Hangul::finals = QList<ushort>() + << 0x0000 << 0x3131 << 0x3132 << 0x3133 << 0x3134 << 0x3135 << 0x3136 + << 0x3137 << 0x3139 << 0x313A << 0x313B << 0x313C << 0x313D << 0x313E + << 0x313F << 0x3140 << 0x3141 << 0x3142 << 0x3144 << 0x3145 << 0x3146 + << 0x3147 << 0x3148 << 0x314A << 0x314B << 0x314C << 0x314D << 0x314E; +const QMap<ushort, Hangul::HangulMedialIndex> Hangul::doubleMedialMap = + Hangul::initDoubleMedialMap(); +const QMap<ushort, Hangul::HangulFinalIndex> Hangul::doubleFinalMap = + Hangul::initDoubleFinalMap(); +const int Hangul::SBase = 0xAC00; +const int Hangul::LBase = 0x1100; +const int Hangul::VBase = 0x314F; +const int Hangul::TBase = 0x11A7; +const int Hangul::LCount = 19; +const int Hangul::VCount = 21; +const int Hangul::TCount = 28; +const int Hangul::NCount = Hangul::VCount * Hangul::TCount; // 588 +const int Hangul::SCount = Hangul::LCount * Hangul::NCount; // 11172 + +QString Hangul::decompose(const QString &source) +{ + QString result; + const int len = source.length(); + for (int i = 0; i < len; i++) { + QChar ch = source.at(i); + int SIndex = (int)ch.unicode() - SBase; + if (SIndex >= 0 && SIndex < SCount) { + + // Decompose initial consonant + result.append(QChar((int)initials[SIndex / NCount])); + + // Decompose medial vowel and check if it consists of double Jamo + int VIndex = (SIndex % NCount) / TCount; + ushort key = findDoubleMedial((HangulMedialIndex)VIndex); + if (key) { + HangulMedialIndex VIndexA, VIndexB; + unpackDoubleMedial(key, VIndexA, VIndexB); + result.append(QChar(VBase + (int)VIndexA)); + result.append(QChar(VBase + (int)VIndexB)); + } else { + result.append(QChar(VBase + VIndex)); + } + + // Decompose final consonant and check if it consists of double Jamo + int TIndex = SIndex % TCount; + if (TIndex != 0) { + key = findDoubleFinal((HangulFinalIndex)TIndex); + if (key) { + HangulFinalIndex TIndexA, TIndexB; + unpackDoubleFinal(key, TIndexA, TIndexB); + result.append(QChar(finals[(int)TIndexA])); + result.append(QChar(finals[(int)TIndexB])); + } else { + result.append(QChar(finals[TIndex])); + } + } + } else { + result.append(ch); + } + } + return result; +} + +QString Hangul::compose(const QString &source) +{ + const int len = source.length(); + if (len == 0) + return QString(); + + // Always add the initial character into buffer. + // The last character will serve as the current + // Hangul Syllable. + QChar last = source.at(0); + QString result = QString(last); + + // Go through the input buffer starting at next character + for (int i = 1; i < len; i++) { + const QChar ch = source.at(i); + + // Check to see if the character is Hangul Compatibility Jamo + const ushort unicode = ch.unicode(); + if (isJamo(unicode)) { + + // Check to see if the character is syllable + const ushort lastUnicode = last.unicode(); + int SIndex = (int)lastUnicode - SBase; + if (SIndex >= 0 && SIndex < SCount) { + + // Check to see if the syllable type is LV or LV+T + int TIndex = SIndex % TCount; + if (TIndex == 0) { + + // If the current character is final consonant, then + // make syllable of form LV+T + TIndex = finals.indexOf(unicode); + if (TIndex != -1) { + last = QChar((int)lastUnicode + TIndex); + result.replace(result.length() - 1, 1, last); + continue; + } + + // Check to see if the current character is vowel + HangulMedialIndex VIndexB = (HangulMedialIndex)((int)unicode - VBase); + if (isMedial(VIndexB)) { + + // Some medial Jamos do not exist in the keyboard layout as is. + // Such Jamos can only be formed by combining the two specific Jamos, + // aka the double Jamos. + + HangulMedialIndex VIndexA = (HangulMedialIndex)((SIndex % NCount) / TCount); + if (isMedial(VIndexA)) { + + // Search the double medial map if such a combination exists + ushort key = packDoubleMedial(VIndexA, VIndexB); + if (doubleMedialMap.contains(key)) { + + // Update syllable by adding the difference between + // the vowels indices + HangulMedialIndex VIndexD = doubleMedialMap[key]; + int VDiff = (int)VIndexD - (int)VIndexA; + last = QChar((int)lastUnicode + VDiff * TCount); + result.replace(result.length() - 1, 1, last); + continue; + } + } + } + + } else { + + // Check to see if current jamo is vowel + int VIndex = (int)unicode - VBase; + if (VIndex >= 0 && VIndex < VCount) { + + // Since some initial and final consonants use the same + // Unicode values, we need to check whether the previous final + // Jamo is actually an initial Jamo of the next syllable. + // + // Consider the following scenario: + // LVT+V == not possible + // LV, L+V == possible + int LIndex = initials.indexOf(finals[TIndex]); + if (LIndex >= 0 && LIndex < LCount) { + + // Remove the previous final jamo from the syllable, + // making the current syllable of form LV + last = QChar((int)lastUnicode - TIndex); + result.replace(result.length() - 1, 1, last); + + // Make new syllable of form LV + last = QChar(SBase + (LIndex * VCount + VIndex) * TCount); + result.append(last); + continue; + } + + // Check to see if the current final Jamo is double consonant. + // In this scenario, the double consonant is split into parts + // and the second part is removed from the current syllable. + // Then the second part is joined with the current vowel making + // the new syllable of form LV. + ushort key = findDoubleFinal((HangulFinalIndex)TIndex); + if (key) { + + // Split the consonant into two jamos and remove the + // second jamo B from the current syllable + HangulFinalIndex TIndexA, TIndexB; + unpackDoubleFinal(key, TIndexA, TIndexB); + last = QChar((int)lastUnicode - TIndex + (int)TIndexA); + result.replace(result.length() - 1, 1, last); + + // Add new syllable by combining the initial jamo + // and the current vowel + LIndex = initials.indexOf(finals[TIndexB]); + last = QChar(SBase + (LIndex * VCount + VIndex) * TCount); + result.append(last); + continue; + } + } + + // Check whether the current consonant can connect to current + // consonant forming a double final consonant + HangulFinalIndex TIndexA = (HangulFinalIndex)TIndex; + if (isFinal(TIndexA)) { + + HangulFinalIndex TIndexB = (HangulFinalIndex)finals.indexOf(unicode); + if (isFinal(TIndexB)) { + + // Search the double final map if such a combination exists + ushort key = packDoubleFinal(TIndexA, TIndexB); + if (doubleFinalMap.contains(key)) { + + // Update syllable by adding the difference between + // the consonant indices + HangulFinalIndex TIndexD = doubleFinalMap[key]; + int TDiff = (int)TIndexD - (int)TIndexA; + last = QChar((int)lastUnicode + TDiff); + result.replace(result.length() - 1, 1, last); + continue; + } + } + } + } + + } else { + + // The last character is not syllable. + // Check to see if the last character is an initial consonant + int LIndex = initials.indexOf(lastUnicode); + if (LIndex != -1) { + + // If the current character is medial vowel, + // make syllable of form LV + int VIndex = (int)unicode - VBase; + if (VIndex >= 0 && VIndex < VCount) { + last = QChar(SBase + (LIndex * VCount + VIndex) * TCount); + result.replace(result.length() - 1, 1, last); + continue; + } + } + + } + } + + // Otherwise, add the character into buffer + last = ch; + result = result.append(ch); + } + return result; +} + +bool Hangul::isJamo(const ushort &unicode) +{ + return unicode >= 0x3131 && unicode <= 0x3163; +} + +bool Hangul::isMedial(HangulMedialIndex vowel) +{ + return vowel >= HANGUL_MEDIAL_A && vowel <= HANGUL_MEDIAL_I; +} + +bool Hangul::isFinal(HangulFinalIndex consonant) +{ + return consonant >= HANGUL_FINAL_KIYEOK && consonant <= HANGUL_FINAL_HIEUH; +} + +ushort Hangul::findDoubleMedial(HangulMedialIndex vowel) +{ + for (QMap<ushort, HangulMedialIndex>::ConstIterator i = doubleMedialMap.constBegin(); + i != doubleMedialMap.constEnd(); i++) { + if (i.value() == vowel) + return i.key(); + } + return 0; +} + +ushort Hangul::findDoubleFinal(HangulFinalIndex consonant) +{ + for (QMap<ushort, HangulFinalIndex>::ConstIterator i = doubleFinalMap.constBegin(); + i != doubleFinalMap.constEnd(); i++) { + if (i.value() == consonant) + return i.key(); + } + return 0; +} + +// Packs two Hangul Jamo indices into 16-bit integer. +// The result can be used as a key to the double jamos lookup table. +// Note: The returned value is not a Unicode character! +ushort Hangul::packDoubleMedial(HangulMedialIndex a, HangulMedialIndex b) +{ + Q_ASSERT(isMedial(a)); + Q_ASSERT(isMedial(b)); + return (ushort)a | ((ushort)b << 8); +} + +ushort Hangul::packDoubleFinal(HangulFinalIndex a, HangulFinalIndex b) +{ + Q_ASSERT(isFinal(a)); + Q_ASSERT(isFinal(b)); + return (ushort)a | ((ushort)b << 8); +} + +void Hangul::unpackDoubleMedial(ushort key, HangulMedialIndex &a, HangulMedialIndex &b) +{ + a = (HangulMedialIndex)(key & 0xFF); + b = (HangulMedialIndex)(key >> 8); + Q_ASSERT(isMedial(a)); + Q_ASSERT(isMedial(b)); +} + +void Hangul::unpackDoubleFinal(ushort key, HangulFinalIndex &a, HangulFinalIndex &b) +{ + a = (HangulFinalIndex)(key & 0xFF); + b = (HangulFinalIndex)(key >> 8); + Q_ASSERT(isFinal(a)); + Q_ASSERT(isFinal(b)); +} + +QMap<ushort, Hangul::HangulMedialIndex> Hangul::initDoubleMedialMap() +{ + QMap<ushort, HangulMedialIndex> map; + map.insert(packDoubleMedial(HANGUL_MEDIAL_O, HANGUL_MEDIAL_A), HANGUL_MEDIAL_WA); + map.insert(packDoubleMedial(HANGUL_MEDIAL_O, HANGUL_MEDIAL_AE), HANGUL_MEDIAL_WAE); + map.insert(packDoubleMedial(HANGUL_MEDIAL_O, HANGUL_MEDIAL_I), HANGUL_MEDIAL_OE); + map.insert(packDoubleMedial(HANGUL_MEDIAL_U, HANGUL_MEDIAL_EO), HANGUL_MEDIAL_WEO); + map.insert(packDoubleMedial(HANGUL_MEDIAL_U, HANGUL_MEDIAL_E), HANGUL_MEDIAL_WE); + map.insert(packDoubleMedial(HANGUL_MEDIAL_U, HANGUL_MEDIAL_I), HANGUL_MEDIAL_WI); + map.insert(packDoubleMedial(HANGUL_MEDIAL_EU, HANGUL_MEDIAL_I), HANGUL_MEDIAL_YI); + return map; +} + +QMap<ushort, Hangul::HangulFinalIndex> Hangul::initDoubleFinalMap() +{ + QMap<ushort, HangulFinalIndex> map; + map.insert(packDoubleFinal(HANGUL_FINAL_KIYEOK, HANGUL_FINAL_SIOS), HANGUL_FINAL_KIYEOK_SIOS); + map.insert(packDoubleFinal(HANGUL_FINAL_NIEUN, HANGUL_FINAL_CIEUC), HANGUL_FINAL_NIEUN_CIEUC); + map.insert(packDoubleFinal(HANGUL_FINAL_NIEUN, HANGUL_FINAL_HIEUH), HANGUL_FINAL_NIEUN_HIEUH); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_KIYEOK), HANGUL_FINAL_RIEUL_KIYEOK); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_MIEUM), HANGUL_FINAL_RIEUL_MIEUM); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_PIEUP), HANGUL_FINAL_RIEUL_PIEUP); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_SIOS), HANGUL_FINAL_RIEUL_SIOS); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_THIEUTH), HANGUL_FINAL_RIEUL_THIEUTH); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_PHIEUPH), HANGUL_FINAL_RIEUL_PHIEUPH); + map.insert(packDoubleFinal(HANGUL_FINAL_RIEUL, HANGUL_FINAL_HIEUH), HANGUL_FINAL_RIEUL_HIEUH); + map.insert(packDoubleFinal(HANGUL_FINAL_PIEUP, HANGUL_FINAL_SIOS), HANGUL_FINAL_PIEUP_SIOS); + map.insert(packDoubleFinal(HANGUL_FINAL_SIOS, HANGUL_FINAL_SIOS), HANGUL_FINAL_SSANGSIOS); + return map; +} |