blob: f29fb3a337d49c7051d6e7c5080fa54204e8b0ad [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "hangul_p.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.length();
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.length();
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.length() - 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.length() - 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.length() - 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.length() - 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.length() - 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.length() - 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