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