| 1 | // Copyright (C) 2016 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "hangul_p.h" | 
| 5 |  | 
| 6 | QT_BEGIN_NAMESPACE | 
| 7 | namespace QtVirtualKeyboard { | 
| 8 |  | 
| 9 | const QList<ushort> Hangul::initials = QList<ushort>() | 
| 10 |     << 0x3131 << 0x3132 << 0x3134 << 0x3137 << 0x3138 << 0x3139 << 0x3141 | 
| 11 |     << 0x3142 << 0x3143 << 0x3145 << 0x3146 << 0x3147 << 0x3148 << 0x3149 | 
| 12 |     << 0x314A << 0x314B << 0x314C << 0x314D << 0x314E; | 
| 13 | const QList<ushort> Hangul::finals = QList<ushort>() | 
| 14 |     << 0x0000 << 0x3131 << 0x3132 << 0x3133 << 0x3134 << 0x3135 << 0x3136 | 
| 15 |     << 0x3137 << 0x3139 << 0x313A << 0x313B << 0x313C << 0x313D << 0x313E | 
| 16 |     << 0x313F << 0x3140 << 0x3141 << 0x3142 << 0x3144 << 0x3145 << 0x3146 | 
| 17 |     << 0x3147 << 0x3148 << 0x314A << 0x314B << 0x314C << 0x314D << 0x314E; | 
| 18 | const QMap<ushort, Hangul::HangulMedialIndex> Hangul::doubleMedialMap = | 
| 19 |     Hangul::initDoubleMedialMap(); | 
| 20 | const QMap<ushort, Hangul::HangulFinalIndex> Hangul::doubleFinalMap = | 
| 21 |     Hangul::initDoubleFinalMap(); | 
| 22 | const int Hangul::SBase = 0xAC00; | 
| 23 | const int Hangul::LBase = 0x1100; | 
| 24 | const int Hangul::VBase = 0x314F; | 
| 25 | const int Hangul::TBase = 0x11A7; | 
| 26 | const int Hangul::LCount = 19; | 
| 27 | const int Hangul::VCount = 21; | 
| 28 | const int Hangul::TCount = 28; | 
| 29 | const int Hangul::NCount = Hangul::VCount * Hangul::TCount; // 588 | 
| 30 | const int Hangul::SCount = Hangul::LCount * Hangul::NCount; // 11172 | 
| 31 |  | 
| 32 | /*! | 
| 33 |     \class QtVirtualKeyboard::Hangul | 
| 34 |     \internal | 
| 35 | */ | 
| 36 |  | 
| 37 | QString Hangul::decompose(const QString &source) | 
| 38 | { | 
| 39 |     QString result; | 
| 40 |     const int len = source.size(); | 
| 41 |     for (int i = 0; i < len; i++) { | 
| 42 |         QChar ch = source.at(i); | 
| 43 |         int SIndex = (int)ch.unicode() - SBase; | 
| 44 |         if (SIndex >= 0 && SIndex < SCount) { | 
| 45 |  | 
| 46 |             // Decompose initial consonant | 
| 47 |             result.append(c: QChar((int)initials[SIndex / NCount])); | 
| 48 |  | 
| 49 |             // Decompose medial vowel and check if it consists of double Jamo | 
| 50 |             int VIndex = (SIndex % NCount) / TCount; | 
| 51 |             ushort key = findDoubleMedial(vowel: (HangulMedialIndex)VIndex); | 
| 52 |             if (key) { | 
| 53 |                 HangulMedialIndex VIndexA, VIndexB; | 
| 54 |                 unpackDoubleMedial(key, a&: VIndexA, b&: VIndexB); | 
| 55 |                 result.append(c: QChar(VBase + (int)VIndexA)); | 
| 56 |                 result.append(c: QChar(VBase + (int)VIndexB)); | 
| 57 |             } else { | 
| 58 |                 result.append(c: QChar(VBase + VIndex)); | 
| 59 |             } | 
| 60 |  | 
| 61 |             // Decompose final consonant and check if it consists of double Jamo | 
| 62 |             int TIndex = SIndex % TCount; | 
| 63 |             if (TIndex != 0) { | 
| 64 |                 key = findDoubleFinal(consonant: (HangulFinalIndex)TIndex); | 
| 65 |                 if (key) { | 
| 66 |                     HangulFinalIndex TIndexA, TIndexB; | 
| 67 |                     unpackDoubleFinal(key, a&: TIndexA, b&: TIndexB); | 
| 68 |                     result.append(c: QChar(finals[(int)TIndexA])); | 
| 69 |                     result.append(c: QChar(finals[(int)TIndexB])); | 
| 70 |                 } else { | 
| 71 |                     result.append(c: QChar(finals[TIndex])); | 
| 72 |                 } | 
| 73 |             } | 
| 74 |         } else { | 
| 75 |             result.append(c: ch); | 
| 76 |         } | 
| 77 |     } | 
| 78 |     return result; | 
| 79 | } | 
| 80 |  | 
| 81 | QString Hangul::compose(const QString &source) | 
| 82 | { | 
| 83 |     const int len = source.size(); | 
| 84 |     if (len == 0) | 
| 85 |         return QString(); | 
| 86 |  | 
| 87 |     // Always add the initial character into buffer. | 
| 88 |     // The last character will serve as the current | 
| 89 |     // Hangul Syllable. | 
| 90 |     QChar last = source.at(i: 0); | 
| 91 |     QString result = QString(last); | 
| 92 |  | 
| 93 |     // Go through the input buffer starting at next character | 
| 94 |     for (int i = 1; i < len; i++) { | 
| 95 |         const QChar ch = source.at(i); | 
| 96 |  | 
| 97 |         // Check to see if the character is Hangul Compatibility Jamo | 
| 98 |         const ushort unicode = ch.unicode(); | 
| 99 |         if (isJamo(unicode)) { | 
| 100 |  | 
| 101 |             // Check to see if the character is syllable | 
| 102 |             const ushort lastUnicode = last.unicode(); | 
| 103 |             int SIndex = (int)lastUnicode - SBase; | 
| 104 |             if (SIndex >= 0 && SIndex < SCount) { | 
| 105 |  | 
| 106 |                 // Check to see if the syllable type is LV or LV+T | 
| 107 |                 int TIndex = SIndex % TCount; | 
| 108 |                 if (TIndex == 0) { | 
| 109 |  | 
| 110 |                     // If the current character is final consonant, then | 
| 111 |                     // make syllable of form LV+T | 
| 112 |                     TIndex = finals.indexOf(t: unicode); | 
| 113 |                     if (TIndex != -1) { | 
| 114 |                         last = QChar((int)lastUnicode + TIndex); | 
| 115 |                         result.replace(i: result.size() - 1, len: 1, after: last); | 
| 116 |                         continue; | 
| 117 |                     } | 
| 118 |  | 
| 119 |                     // Check to see if the current character is vowel | 
| 120 |                     HangulMedialIndex VIndexB = (HangulMedialIndex)((int)unicode - VBase); | 
| 121 |                     if (isMedial(vowel: VIndexB)) { | 
| 122 |  | 
| 123 |                         // Some medial Jamos do not exist in the keyboard layout as is. | 
| 124 |                         // Such Jamos can only be formed by combining the two specific Jamos, | 
| 125 |                         // aka the double Jamos. | 
| 126 |  | 
| 127 |                         HangulMedialIndex VIndexA = (HangulMedialIndex)((SIndex % NCount) / TCount); | 
| 128 |                         if (isMedial(vowel: VIndexA)) { | 
| 129 |  | 
| 130 |                             // Search the double medial map if such a combination exists | 
| 131 |                             ushort key = packDoubleMedial(a: VIndexA, b: VIndexB); | 
| 132 |                             const auto it = doubleMedialMap.constFind(key); | 
| 133 |                             if (it != doubleMedialMap.cend()) { | 
| 134 |  | 
| 135 |                                 // Update syllable by adding the difference between | 
| 136 |                                 // the vowels indices | 
| 137 |                                 HangulMedialIndex VIndexD = it.value(); | 
| 138 |                                 int VDiff = (int)VIndexD - (int)VIndexA; | 
| 139 |                                 last = QChar((int)lastUnicode + VDiff * TCount); | 
| 140 |                                 result.replace(i: result.size() - 1, len: 1, after: last); | 
| 141 |                                 continue; | 
| 142 |                             } | 
| 143 |                         } | 
| 144 |                     } | 
| 145 |  | 
| 146 |                 } else { | 
| 147 |  | 
| 148 |                     // Check to see if current jamo is vowel | 
| 149 |                     int VIndex = (int)unicode - VBase; | 
| 150 |                     if (VIndex >= 0 && VIndex < VCount) { | 
| 151 |  | 
| 152 |                         // Since some initial and final consonants use the same | 
| 153 |                         // Unicode values, we need to check whether the previous final | 
| 154 |                         // Jamo is actually an initial Jamo of the next syllable. | 
| 155 |                         // | 
| 156 |                         // Consider the following scenario: | 
| 157 |                         //      LVT+V == not possible | 
| 158 |                         //      LV, L+V == possible | 
| 159 |                         int LIndex = initials.indexOf(t: finals[TIndex]); | 
| 160 |                         if (LIndex >= 0 && LIndex < LCount) { | 
| 161 |  | 
| 162 |                             // Remove the previous final jamo from the syllable, | 
| 163 |                             // making the current syllable of form LV | 
| 164 |                             last = QChar((int)lastUnicode - TIndex); | 
| 165 |                             result.replace(i: result.size() - 1, len: 1, after: last); | 
| 166 |  | 
| 167 |                             // Make new syllable of form LV | 
| 168 |                             last = QChar(SBase + (LIndex * VCount + VIndex) * TCount); | 
| 169 |                             result.append(c: last); | 
| 170 |                             continue; | 
| 171 |                         } | 
| 172 |  | 
| 173 |                         // Check to see if the current final Jamo is double consonant. | 
| 174 |                         // In this scenario, the double consonant is split into parts | 
| 175 |                         // and the second part is removed from the current syllable. | 
| 176 |                         // Then the second part is joined with the current vowel making | 
| 177 |                         // the new syllable of form LV. | 
| 178 |                         ushort key = findDoubleFinal(consonant: (HangulFinalIndex)TIndex); | 
| 179 |                         if (key) { | 
| 180 |  | 
| 181 |                             // Split the consonant into two jamos and remove the | 
| 182 |                             // second jamo B from the current syllable | 
| 183 |                             HangulFinalIndex TIndexA, TIndexB; | 
| 184 |                             unpackDoubleFinal(key, a&: TIndexA, b&: TIndexB); | 
| 185 |                             last = QChar((int)lastUnicode - TIndex + (int)TIndexA); | 
| 186 |                             result.replace(i: result.size() - 1, len: 1, after: last); | 
| 187 |  | 
| 188 |                             // Add new syllable by combining the initial jamo | 
| 189 |                             // and the current vowel | 
| 190 |                             LIndex = initials.indexOf(t: finals[TIndexB]); | 
| 191 |                             last = QChar(SBase + (LIndex * VCount + VIndex) * TCount); | 
| 192 |                             result.append(c: last); | 
| 193 |                             continue; | 
| 194 |                         } | 
| 195 |                     } | 
| 196 |  | 
| 197 |                     // Check whether the current consonant can connect to current | 
| 198 |                     // consonant forming a double final consonant | 
| 199 |                     HangulFinalIndex TIndexA = (HangulFinalIndex)TIndex; | 
| 200 |                     if (isFinal(consonant: TIndexA)) { | 
| 201 |  | 
| 202 |                         HangulFinalIndex TIndexB = (HangulFinalIndex)finals.indexOf(t: unicode); | 
| 203 |                         if (isFinal(consonant: TIndexB)) { | 
| 204 |  | 
| 205 |                             // Search the double final map if such a combination exists | 
| 206 |                             ushort key = packDoubleFinal(a: TIndexA, b: TIndexB); | 
| 207 |                             const auto it = doubleFinalMap.constFind(key); | 
| 208 |                             if (it != doubleFinalMap.cend()) { | 
| 209 |  | 
| 210 |                                 // Update syllable by adding the difference between | 
| 211 |                                 // the consonant indices | 
| 212 |                                 HangulFinalIndex TIndexD = it.value(); | 
| 213 |                                 int TDiff = (int)TIndexD - (int)TIndexA; | 
| 214 |                                 last = QChar((int)lastUnicode + TDiff); | 
| 215 |                                 result.replace(i: result.size() - 1, len: 1, after: last); | 
| 216 |                                 continue; | 
| 217 |                             } | 
| 218 |                         } | 
| 219 |                     } | 
| 220 |                 } | 
| 221 |  | 
| 222 |             } else { | 
| 223 |  | 
| 224 |                 // The last character is not syllable. | 
| 225 |                 // Check to see if the last character is an initial consonant | 
| 226 |                 int LIndex = initials.indexOf(t: lastUnicode); | 
| 227 |                 if (LIndex != -1) { | 
| 228 |  | 
| 229 |                     // If the current character is medial vowel, | 
| 230 |                     // make syllable of form LV | 
| 231 |                     int VIndex = (int)unicode - VBase; | 
| 232 |                     if (VIndex >= 0 && VIndex < VCount) { | 
| 233 |                         last = QChar(SBase + (LIndex * VCount + VIndex) * TCount); | 
| 234 |                         result.replace(i: result.size() - 1, len: 1, after: last); | 
| 235 |                         continue; | 
| 236 |                     } | 
| 237 |                 } | 
| 238 |  | 
| 239 |             } | 
| 240 |         } | 
| 241 |  | 
| 242 |         // Otherwise, add the character into buffer | 
| 243 |         last = ch; | 
| 244 |         result = result.append(c: ch); | 
| 245 |     } | 
| 246 |     return result; | 
| 247 | } | 
| 248 |  | 
| 249 | bool Hangul::isJamo(const ushort &unicode) | 
| 250 | { | 
| 251 |     return unicode >= 0x3131 && unicode <= 0x3163; | 
| 252 | } | 
| 253 |  | 
| 254 | bool Hangul::isMedial(HangulMedialIndex vowel) | 
| 255 | { | 
| 256 |     return vowel >= HANGUL_MEDIAL_A && vowel <= HANGUL_MEDIAL_I; | 
| 257 | } | 
| 258 |  | 
| 259 | bool Hangul::isFinal(HangulFinalIndex consonant) | 
| 260 | { | 
| 261 |     return consonant >= HANGUL_FINAL_KIYEOK && consonant <= HANGUL_FINAL_HIEUH; | 
| 262 | } | 
| 263 |  | 
| 264 | ushort Hangul::findDoubleMedial(HangulMedialIndex vowel) | 
| 265 | { | 
| 266 |     return doubleMedialMap.key(value: vowel, defaultKey: 0); | 
| 267 | } | 
| 268 |  | 
| 269 | ushort Hangul::findDoubleFinal(HangulFinalIndex consonant) | 
| 270 | { | 
| 271 |     return doubleFinalMap.key(value: consonant, defaultKey: 0); | 
| 272 | } | 
| 273 |  | 
| 274 | // Packs two Hangul Jamo indices into 16-bit integer. | 
| 275 | // The result can be used as a key to the double jamos lookup table. | 
| 276 | // Note: The returned value is not a Unicode character! | 
| 277 | ushort Hangul::packDoubleMedial(HangulMedialIndex a, HangulMedialIndex b) | 
| 278 | { | 
| 279 |     Q_ASSERT(isMedial(a)); | 
| 280 |     Q_ASSERT(isMedial(b)); | 
| 281 |     return (ushort)a | ((ushort)b << 8); | 
| 282 | } | 
| 283 |  | 
| 284 | ushort Hangul::packDoubleFinal(HangulFinalIndex a, HangulFinalIndex b) | 
| 285 | { | 
| 286 |     Q_ASSERT(isFinal(a)); | 
| 287 |     Q_ASSERT(isFinal(b)); | 
| 288 |     return (ushort)a | ((ushort)b << 8); | 
| 289 | } | 
| 290 |  | 
| 291 | void Hangul::unpackDoubleMedial(ushort key, HangulMedialIndex &a, HangulMedialIndex &b) | 
| 292 | { | 
| 293 |     a = (HangulMedialIndex)(key & 0xFF); | 
| 294 |     b = (HangulMedialIndex)(key >> 8); | 
| 295 |     Q_ASSERT(isMedial(a)); | 
| 296 |     Q_ASSERT(isMedial(b)); | 
| 297 | } | 
| 298 |  | 
| 299 | void Hangul::unpackDoubleFinal(ushort key, HangulFinalIndex &a, HangulFinalIndex &b) | 
| 300 | { | 
| 301 |     a = (HangulFinalIndex)(key & 0xFF); | 
| 302 |     b = (HangulFinalIndex)(key >> 8); | 
| 303 |     Q_ASSERT(isFinal(a)); | 
| 304 |     Q_ASSERT(isFinal(b)); | 
| 305 | } | 
| 306 |  | 
| 307 | QMap<ushort, Hangul::HangulMedialIndex> Hangul::initDoubleMedialMap() | 
| 308 | { | 
| 309 |     QMap<ushort, HangulMedialIndex> map; | 
| 310 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_O, b: HANGUL_MEDIAL_A), value: HANGUL_MEDIAL_WA); | 
| 311 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_O, b: HANGUL_MEDIAL_AE), value: HANGUL_MEDIAL_WAE); | 
| 312 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_O, b: HANGUL_MEDIAL_I), value: HANGUL_MEDIAL_OE); | 
| 313 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_U, b: HANGUL_MEDIAL_EO), value: HANGUL_MEDIAL_WEO); | 
| 314 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_U, b: HANGUL_MEDIAL_E), value: HANGUL_MEDIAL_WE); | 
| 315 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_U, b: HANGUL_MEDIAL_I), value: HANGUL_MEDIAL_WI); | 
| 316 |     map.insert(key: packDoubleMedial(a: HANGUL_MEDIAL_EU, b: HANGUL_MEDIAL_I), value: HANGUL_MEDIAL_YI); | 
| 317 |     return map; | 
| 318 | } | 
| 319 |  | 
| 320 | QMap<ushort, Hangul::HangulFinalIndex> Hangul::initDoubleFinalMap() | 
| 321 | { | 
| 322 |     QMap<ushort, HangulFinalIndex> map; | 
| 323 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_KIYEOK, b: HANGUL_FINAL_SIOS), value: HANGUL_FINAL_KIYEOK_SIOS); | 
| 324 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_NIEUN, b: HANGUL_FINAL_CIEUC), value: HANGUL_FINAL_NIEUN_CIEUC); | 
| 325 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_NIEUN, b: HANGUL_FINAL_HIEUH), value: HANGUL_FINAL_NIEUN_HIEUH); | 
| 326 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_KIYEOK), value: HANGUL_FINAL_RIEUL_KIYEOK); | 
| 327 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_MIEUM), value: HANGUL_FINAL_RIEUL_MIEUM); | 
| 328 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_PIEUP), value: HANGUL_FINAL_RIEUL_PIEUP); | 
| 329 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_SIOS), value: HANGUL_FINAL_RIEUL_SIOS); | 
| 330 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_THIEUTH), value: HANGUL_FINAL_RIEUL_THIEUTH); | 
| 331 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_PHIEUPH), value: HANGUL_FINAL_RIEUL_PHIEUPH); | 
| 332 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_RIEUL, b: HANGUL_FINAL_HIEUH), value: HANGUL_FINAL_RIEUL_HIEUH); | 
| 333 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_PIEUP, b: HANGUL_FINAL_SIOS), value: HANGUL_FINAL_PIEUP_SIOS); | 
| 334 |     map.insert(key: packDoubleFinal(a: HANGUL_FINAL_SIOS, b: HANGUL_FINAL_SIOS), value: HANGUL_FINAL_SSANGSIOS); | 
| 335 |     return map; | 
| 336 | } | 
| 337 |  | 
| 338 | } // namespace QtVirtualKeyboard | 
| 339 | QT_END_NAMESPACE | 
| 340 |  |