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
6QT_BEGIN_NAMESPACE
7namespace QtVirtualKeyboard {
8
9const 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;
13const 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;
18const QMap<ushort, Hangul::HangulMedialIndex> Hangul::doubleMedialMap =
19 Hangul::initDoubleMedialMap();
20const QMap<ushort, Hangul::HangulFinalIndex> Hangul::doubleFinalMap =
21 Hangul::initDoubleFinalMap();
22const int Hangul::SBase = 0xAC00;
23const int Hangul::LBase = 0x1100;
24const int Hangul::VBase = 0x314F;
25const int Hangul::TBase = 0x11A7;
26const int Hangul::LCount = 19;
27const int Hangul::VCount = 21;
28const int Hangul::TCount = 28;
29const int Hangul::NCount = Hangul::VCount * Hangul::TCount; // 588
30const int Hangul::SCount = Hangul::LCount * Hangul::NCount; // 11172
31
32/*!
33 \class QtVirtualKeyboard::Hangul
34 \internal
35*/
36
37QString 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
81QString 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
249bool Hangul::isJamo(const ushort &unicode)
250{
251 return unicode >= 0x3131 && unicode <= 0x3163;
252}
253
254bool Hangul::isMedial(HangulMedialIndex vowel)
255{
256 return vowel >= HANGUL_MEDIAL_A && vowel <= HANGUL_MEDIAL_I;
257}
258
259bool Hangul::isFinal(HangulFinalIndex consonant)
260{
261 return consonant >= HANGUL_FINAL_KIYEOK && consonant <= HANGUL_FINAL_HIEUH;
262}
263
264ushort Hangul::findDoubleMedial(HangulMedialIndex vowel)
265{
266 return doubleMedialMap.key(value: vowel, defaultKey: 0);
267}
268
269ushort 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!
277ushort 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
284ushort 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
291void 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
299void 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
307QMap<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
320QMap<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
339QT_END_NAMESPACE
340

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