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
32QT_BEGIN_NAMESPACE
33namespace QtVirtualKeyboard {
34
35const 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;
39const 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;
44const QMap<ushort, Hangul::HangulMedialIndex> Hangul::doubleMedialMap =
45 Hangul::initDoubleMedialMap();
46const QMap<ushort, Hangul::HangulFinalIndex> Hangul::doubleFinalMap =
47 Hangul::initDoubleFinalMap();
48const int Hangul::SBase = 0xAC00;
49const int Hangul::LBase = 0x1100;
50const int Hangul::VBase = 0x314F;
51const int Hangul::TBase = 0x11A7;
52const int Hangul::LCount = 19;
53const int Hangul::VCount = 21;
54const int Hangul::TCount = 28;
55const int Hangul::NCount = Hangul::VCount * Hangul::TCount; // 588
56const int Hangul::SCount = Hangul::LCount * Hangul::NCount; // 11172
57
58/*!
59 \class QtVirtualKeyboard::Hangul
60 \internal
61*/
62
63QString 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
107QString 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
275bool Hangul::isJamo(const ushort &unicode)
276{
277 return unicode >= 0x3131 && unicode <= 0x3163;
278}
279
280bool Hangul::isMedial(HangulMedialIndex vowel)
281{
282 return vowel >= HANGUL_MEDIAL_A && vowel <= HANGUL_MEDIAL_I;
283}
284
285bool Hangul::isFinal(HangulFinalIndex consonant)
286{
287 return consonant >= HANGUL_FINAL_KIYEOK && consonant <= HANGUL_FINAL_HIEUH;
288}
289
290ushort Hangul::findDoubleMedial(HangulMedialIndex vowel)
291{
292 return doubleMedialMap.key(avalue: vowel, defaultKey: 0);
293}
294
295ushort 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!
303ushort 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
310ushort 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
317void 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
325void 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
333QMap<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
346QMap<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
365QT_END_NAMESPACE
366

source code of qtvirtualkeyboard/src/plugins/hangul/hangul.cpp