Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "hangul.h"
++
++ QT_BEGIN_NAMESPACE
++ namespace QtVirtualKeyboard {
++
++ 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
++
++ /*!
++ \class QtVirtualKeyboard::Hangul
++ \internal
++ */
++
++ QString Hangul::decompose(const QString &source)
++ {
++ QString result;
++ const int len = source.size();
++ 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.size();
++ 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.size() - 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);
++ const auto it = doubleMedialMap.constFind(key);
++ if (it != doubleMedialMap.cend()) {
++
++ // Update syllable by adding the difference between
++ // the vowels indices
++ HangulMedialIndex VIndexD = it.value();
++ int VDiff = (int)VIndexD - (int)VIndexA;
++ last = QChar((int)lastUnicode + VDiff * TCount);
++ result.replace(result.size() - 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.size() - 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.size() - 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);
++ const auto it = doubleFinalMap.constFind(key);
++ if (it != doubleFinalMap.cend()) {
++
++ // Update syllable by adding the difference between
++ // the consonant indices
++ HangulFinalIndex TIndexD = it.value();
++ int TDiff = (int)TIndexD - (int)TIndexA;
++ last = QChar((int)lastUnicode + TDiff);
++ result.replace(result.size() - 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.size() - 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)
++ {
++ return doubleMedialMap.key(vowel, 0);
++ }
++
++ ushort Hangul::findDoubleFinal(HangulFinalIndex consonant)
++ {
++ return doubleFinalMap.key(consonant, 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;
++ }
++
++ } // namespace QtVirtualKeyboard
++ QT_END_NAMESPACE