KeyHandler // Using Tekkon in lieu of OVMandarin.
- Also put the composer instance back to KeyHandler.
This commit is contained in:
parent
d33978550b
commit
9e1b0e0724
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Composer : NSObject
|
||||
+ (BOOL)chkKeyValidity:(UniChar)charCode;
|
||||
+ (BOOL)isBufferEmpty;
|
||||
+ (void)clearBuffer;
|
||||
+ (void)combineReadingKey:(UniChar)charCode;
|
||||
+ (BOOL)hasToneMarker;
|
||||
+ (BOOL)hasToneMarkerOnly;
|
||||
+ (NSString *)getSyllableComposition;
|
||||
+ (void)doBackSpaceToBuffer;
|
||||
+ (NSString *)getComposition;
|
||||
+ (void)ensureParser;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,122 +0,0 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import "Composer.hh"
|
||||
#import "Mandarin.h"
|
||||
#import "vChewing-Swift.h"
|
||||
|
||||
static Mandarin::BopomofoReadingBuffer *PhoneticBuffer;
|
||||
|
||||
@implementation Composer
|
||||
|
||||
+ (BOOL)chkKeyValidity:(UniChar)charCode
|
||||
{
|
||||
return PhoneticBuffer->isValidKey((char)charCode);
|
||||
}
|
||||
|
||||
+ (BOOL)isBufferEmpty
|
||||
{
|
||||
return PhoneticBuffer->isEmpty();
|
||||
}
|
||||
|
||||
+ (void)clearBuffer
|
||||
{
|
||||
PhoneticBuffer->clear();
|
||||
}
|
||||
|
||||
+ (void)combineReadingKey:(UniChar)charCode
|
||||
{
|
||||
PhoneticBuffer->combineKey((char)charCode);
|
||||
}
|
||||
|
||||
+ (BOOL)hasToneMarker
|
||||
{
|
||||
return PhoneticBuffer->hasToneMarker();
|
||||
}
|
||||
|
||||
+ (BOOL)hasToneMarkerOnly
|
||||
{
|
||||
return PhoneticBuffer->hasToneMarkerOnly();
|
||||
}
|
||||
|
||||
+ (NSString *)getSyllableComposition
|
||||
{
|
||||
return [NSString stringWithUTF8String:PhoneticBuffer->syllable().composedString().c_str()];
|
||||
}
|
||||
|
||||
+ (void)doBackSpaceToBuffer
|
||||
{
|
||||
PhoneticBuffer->backspace();
|
||||
}
|
||||
|
||||
+ (NSString *)getComposition
|
||||
{
|
||||
return [NSString stringWithUTF8String:PhoneticBuffer->composedString().c_str()];
|
||||
}
|
||||
|
||||
+ (void)ensureParser
|
||||
{
|
||||
if (PhoneticBuffer)
|
||||
{
|
||||
switch (mgrPrefs.mandarinParser)
|
||||
{
|
||||
case MandarinParserOfStandard:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
||||
break;
|
||||
case MandarinParserOfEten:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETenLayout());
|
||||
break;
|
||||
case MandarinParserOfHsu:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HsuLayout());
|
||||
break;
|
||||
case MandarinParserOfEen26:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::ETen26Layout());
|
||||
break;
|
||||
case MandarinParserOfIBM:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::IBMLayout());
|
||||
break;
|
||||
case MandarinParserOfMiTAC:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::MiTACLayout());
|
||||
break;
|
||||
case MandarinParserOfFakeSeigyou:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::FakeSeigyouLayout());
|
||||
break;
|
||||
case MandarinParserOfHanyuPinyin:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::HanyuPinyinLayout());
|
||||
break;
|
||||
default:
|
||||
PhoneticBuffer->setKeyboardLayout(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
||||
mgrPrefs.mandarinParser = MandarinParserOfStandard;
|
||||
}
|
||||
PhoneticBuffer->clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
PhoneticBuffer = new Mandarin::BopomofoReadingBuffer(Mandarin::BopomofoKeyboardLayout::StandardLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
|
@ -1,587 +0,0 @@
|
|||
// Copyright (c) 2011 and onwards The OpenVanilla Project (MIT License).
|
||||
// All possible vChewing-specific modifications are of:
|
||||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MANDARIN_H_
|
||||
#define MANDARIN_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Mandarin
|
||||
{
|
||||
|
||||
class BopomofoSyllable
|
||||
{
|
||||
public:
|
||||
typedef uint16_t Component;
|
||||
|
||||
explicit BopomofoSyllable(Component syllable = 0) : syllable_(syllable)
|
||||
{
|
||||
}
|
||||
|
||||
BopomofoSyllable(const BopomofoSyllable &) = default;
|
||||
BopomofoSyllable(BopomofoSyllable &&another) = default;
|
||||
BopomofoSyllable &operator=(const BopomofoSyllable &) = default;
|
||||
BopomofoSyllable &operator=(BopomofoSyllable &&) = default;
|
||||
|
||||
// takes the ASCII-form, "v"-tolerant, TW-style Hanyu Pinyin (fong, pong, bong
|
||||
// acceptable)
|
||||
static const BopomofoSyllable FromHanyuPinyin(const std::string &str);
|
||||
|
||||
// TO DO: Support accented vowels
|
||||
const std::string HanyuPinyinString(bool includesTone, bool useVForUUmlaut) const;
|
||||
|
||||
static const BopomofoSyllable FromComposedString(const std::string &str);
|
||||
const std::string composedString() const;
|
||||
|
||||
void clear()
|
||||
{
|
||||
syllable_ = 0;
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return !syllable_;
|
||||
}
|
||||
|
||||
bool hasConsonant() const
|
||||
{
|
||||
return !!(syllable_ & ConsonantMask);
|
||||
}
|
||||
|
||||
bool hasMiddleVowel() const
|
||||
{
|
||||
return !!(syllable_ & MiddleVowelMask);
|
||||
}
|
||||
bool hasVowel() const
|
||||
{
|
||||
return !!(syllable_ & VowelMask);
|
||||
}
|
||||
|
||||
bool hasToneMarker() const
|
||||
{
|
||||
return !!(syllable_ & ToneMarkerMask);
|
||||
}
|
||||
|
||||
Component consonantComponent() const
|
||||
{
|
||||
return syllable_ & ConsonantMask;
|
||||
}
|
||||
|
||||
Component middleVowelComponent() const
|
||||
{
|
||||
return syllable_ & MiddleVowelMask;
|
||||
}
|
||||
|
||||
Component vowelComponent() const
|
||||
{
|
||||
return syllable_ & VowelMask;
|
||||
}
|
||||
|
||||
Component toneMarkerComponent() const
|
||||
{
|
||||
return syllable_ & ToneMarkerMask;
|
||||
}
|
||||
|
||||
bool operator==(const BopomofoSyllable &another) const
|
||||
{
|
||||
return syllable_ == another.syllable_;
|
||||
}
|
||||
|
||||
bool operator!=(const BopomofoSyllable &another) const
|
||||
{
|
||||
return syllable_ != another.syllable_;
|
||||
}
|
||||
|
||||
bool isOverlappingWith(const BopomofoSyllable &another) const
|
||||
{
|
||||
#define IOW_SAND(mask) ((syllable_ & mask) && (another.syllable_ & mask))
|
||||
return IOW_SAND(ConsonantMask) || IOW_SAND(MiddleVowelMask) || IOW_SAND(VowelMask) || IOW_SAND(ToneMarkerMask);
|
||||
#undef IOW_SAND
|
||||
}
|
||||
|
||||
// consonants J, Q, X all require the existence of vowel I or UE
|
||||
bool belongsToJQXClass() const
|
||||
{
|
||||
Component consonant = syllable_ & ConsonantMask;
|
||||
return (consonant == J || consonant == Q || consonant == X);
|
||||
}
|
||||
|
||||
// zi, ci, si, chi, chi, shi, ri
|
||||
bool belongsToZCSRClass() const
|
||||
{
|
||||
Component consonant = syllable_ & ConsonantMask;
|
||||
return (consonant >= ZH && consonant <= S);
|
||||
}
|
||||
|
||||
Component maskType() const
|
||||
{
|
||||
Component mask = 0;
|
||||
mask |= (syllable_ & ConsonantMask) ? ConsonantMask : 0;
|
||||
mask |= (syllable_ & MiddleVowelMask) ? MiddleVowelMask : 0;
|
||||
mask |= (syllable_ & VowelMask) ? VowelMask : 0;
|
||||
mask |= (syllable_ & ToneMarkerMask) ? ToneMarkerMask : 0;
|
||||
return mask;
|
||||
}
|
||||
|
||||
const BopomofoSyllable operator+(const BopomofoSyllable &another) const
|
||||
{
|
||||
Component newSyllable = syllable_;
|
||||
#define OP_SOVER(mask) \
|
||||
if (another.syllable_ & mask) \
|
||||
{ \
|
||||
newSyllable = (newSyllable & ~mask) | (another.syllable_ & mask); \
|
||||
}
|
||||
OP_SOVER(ConsonantMask);
|
||||
OP_SOVER(MiddleVowelMask);
|
||||
OP_SOVER(VowelMask);
|
||||
OP_SOVER(ToneMarkerMask);
|
||||
#undef OP_SOVER
|
||||
return BopomofoSyllable(newSyllable);
|
||||
}
|
||||
|
||||
BopomofoSyllable &operator+=(const BopomofoSyllable &another)
|
||||
{
|
||||
#define OPE_SOVER(mask) \
|
||||
if (another.syllable_ & mask) \
|
||||
{ \
|
||||
syllable_ = (syllable_ & ~mask) | (another.syllable_ & mask); \
|
||||
}
|
||||
OPE_SOVER(ConsonantMask);
|
||||
OPE_SOVER(MiddleVowelMask);
|
||||
OPE_SOVER(VowelMask);
|
||||
OPE_SOVER(ToneMarkerMask);
|
||||
#undef OPE_SOVER
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const BopomofoSyllable &syllable);
|
||||
|
||||
static constexpr Component ConsonantMask = 0x001f, // 0000 0000 0001 1111, 21 consonants
|
||||
MiddleVowelMask = 0x0060, // 0000 0000 0110 0000, 3 middle vowels
|
||||
VowelMask = 0x0780, // 0000 0111 1000 0000, 13 vowels
|
||||
ToneMarkerMask = 0x3800, // 0011 1000 0000 0000, 5 tones (tone1 = 0x00)
|
||||
B = 0x0001, P = 0x0002, M = 0x0003, F = 0x0004, D = 0x0005, T = 0x0006, N = 0x0007, L = 0x0008, G = 0x0009,
|
||||
K = 0x000a, H = 0x000b, J = 0x000c, Q = 0x000d, X = 0x000e, ZH = 0x000f, CH = 0x0010,
|
||||
SH = 0x0011, R = 0x0012, Z = 0x0013, C = 0x0014, S = 0x0015, I = 0x0020, U = 0x0040,
|
||||
UE = 0x0060, // ue = u umlaut (we use the German convention here as an
|
||||
// ersatz to the /ju:/ sound)
|
||||
A = 0x0080, O = 0x0100, ER = 0x0180, E = 0x0200, AI = 0x0280, EI = 0x0300, AO = 0x0380, OU = 0x0400,
|
||||
AN = 0x0480, EN = 0x0500, ANG = 0x0580, ENG = 0x0600, ERR = 0x0680, Tone1 = 0x0000,
|
||||
Tone2 = 0x0800, Tone3 = 0x1000, Tone4 = 0x1800, Tone5 = 0x2000;
|
||||
|
||||
protected:
|
||||
Component syllable_;
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &stream, const BopomofoSyllable &syllable)
|
||||
{
|
||||
stream << syllable.composedString();
|
||||
return stream;
|
||||
}
|
||||
|
||||
typedef BopomofoSyllable BPMF;
|
||||
|
||||
typedef std::map<char, std::vector<BPMF::Component>> BopomofoKeyToComponentMap;
|
||||
typedef std::map<BPMF::Component, char> BopomofoComponentToKeyMap;
|
||||
|
||||
extern "C" class BopomofoKeyboardLayout
|
||||
{
|
||||
public:
|
||||
static const BopomofoKeyboardLayout *StandardLayout();
|
||||
static const BopomofoKeyboardLayout *ETenLayout();
|
||||
static const BopomofoKeyboardLayout *HsuLayout();
|
||||
static const BopomofoKeyboardLayout *ETen26Layout();
|
||||
static const BopomofoKeyboardLayout *IBMLayout();
|
||||
static const BopomofoKeyboardLayout *MiTACLayout();
|
||||
static const BopomofoKeyboardLayout *FakeSeigyouLayout();
|
||||
static const BopomofoKeyboardLayout *HanyuPinyinLayout();
|
||||
|
||||
BopomofoKeyboardLayout(const BopomofoKeyToComponentMap &ktcm, const std::string &name)
|
||||
: m_keyToComponent(ktcm), m_name(name)
|
||||
{
|
||||
for (BopomofoKeyToComponentMap::const_iterator miter = m_keyToComponent.begin();
|
||||
miter != m_keyToComponent.end(); ++miter)
|
||||
for (std::vector<BPMF::Component>::const_iterator viter = (*miter).second.begin();
|
||||
viter != (*miter).second.end(); ++viter)
|
||||
m_componentToKey[*viter] = (*miter).first;
|
||||
}
|
||||
|
||||
const std::string name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
char componentToKey(BPMF::Component component) const
|
||||
{
|
||||
BopomofoComponentToKeyMap::const_iterator iter = m_componentToKey.find(component);
|
||||
return (iter == m_componentToKey.end()) ? 0 : (*iter).second;
|
||||
}
|
||||
|
||||
const std::vector<BPMF::Component> keyToComponents(char key) const
|
||||
{
|
||||
BopomofoKeyToComponentMap::const_iterator iter = m_keyToComponent.find(key);
|
||||
return (iter == m_keyToComponent.end()) ? std::vector<BPMF::Component>() : (*iter).second;
|
||||
}
|
||||
|
||||
const std::string keySequenceFromSyllable(BPMF syllable) const
|
||||
{
|
||||
std::string sequence;
|
||||
|
||||
BPMF::Component c;
|
||||
char k;
|
||||
#define STKS_COMBINE(component) \
|
||||
if ((c = component)) \
|
||||
{ \
|
||||
if ((k = componentToKey(c))) \
|
||||
sequence += std::string(1, k); \
|
||||
}
|
||||
STKS_COMBINE(syllable.consonantComponent());
|
||||
STKS_COMBINE(syllable.middleVowelComponent());
|
||||
STKS_COMBINE(syllable.vowelComponent());
|
||||
STKS_COMBINE(syllable.toneMarkerComponent());
|
||||
#undef STKS_COMBINE
|
||||
return sequence;
|
||||
}
|
||||
|
||||
const BPMF syllableFromKeySequence(const std::string &sequence) const
|
||||
{
|
||||
BPMF syllable;
|
||||
|
||||
for (std::string::const_iterator iter = sequence.begin(); iter != sequence.end(); ++iter)
|
||||
{
|
||||
bool beforeSeqHasIorUE = sequenceContainsIorUE(sequence.begin(), iter);
|
||||
bool aheadSeqHasIorUE = sequenceContainsIorUE(iter + 1, sequence.end());
|
||||
|
||||
std::vector<BPMF::Component> components = keyToComponents(*iter);
|
||||
|
||||
if (!components.size())
|
||||
continue;
|
||||
|
||||
if (components.size() == 1)
|
||||
{
|
||||
syllable += BPMF(components[0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
BPMF head = BPMF(components[0]);
|
||||
BPMF follow = BPMF(components[1]);
|
||||
BPMF ending = components.size() > 2 ? BPMF(components[2]) : follow;
|
||||
|
||||
// apply the I/UE + E rule
|
||||
if (head.vowelComponent() == BPMF::E && follow.vowelComponent() != BPMF::E)
|
||||
{
|
||||
syllable += beforeSeqHasIorUE ? head : follow;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (head.vowelComponent() != BPMF::E && follow.vowelComponent() == BPMF::E)
|
||||
{
|
||||
syllable += beforeSeqHasIorUE ? follow : head;
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply the J/Q/X + I/UE rule, only two components are allowed in the
|
||||
// components vector here
|
||||
if (head.belongsToJQXClass() && !follow.belongsToJQXClass())
|
||||
{
|
||||
if (!syllable.isEmpty())
|
||||
{
|
||||
if (ending != follow)
|
||||
syllable += ending;
|
||||
}
|
||||
else
|
||||
{
|
||||
syllable += aheadSeqHasIorUE ? head : follow;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!head.belongsToJQXClass() && follow.belongsToJQXClass())
|
||||
{
|
||||
if (!syllable.isEmpty())
|
||||
{
|
||||
if (ending != follow)
|
||||
syllable += ending;
|
||||
}
|
||||
else
|
||||
{
|
||||
syllable += aheadSeqHasIorUE ? follow : head;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// the nasty issue of only one char in the buffer
|
||||
if (iter == sequence.begin() && iter + 1 == sequence.end())
|
||||
{
|
||||
if (head.hasVowel() || follow.hasToneMarker() || head.belongsToZCSRClass())
|
||||
{
|
||||
syllable += head;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (follow.hasVowel() || ending.hasToneMarker())
|
||||
{
|
||||
syllable += follow;
|
||||
}
|
||||
else
|
||||
{
|
||||
syllable += ending;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(syllable.maskType() & head.maskType()) && !endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()))
|
||||
{
|
||||
syllable += head;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (endAheadOrAheadHasToneMarkKey(iter + 1, sequence.end()) && head.belongsToZCSRClass() &&
|
||||
syllable.isEmpty())
|
||||
{
|
||||
syllable += head;
|
||||
}
|
||||
else if (syllable.maskType() < follow.maskType())
|
||||
{
|
||||
syllable += follow;
|
||||
}
|
||||
else
|
||||
{
|
||||
syllable += ending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// heuristics for Hsu keyboard layout
|
||||
if (this == HsuLayout())
|
||||
{
|
||||
// fix the left out L to ERR when it has sound, and GI, GUE -> JI, JUE
|
||||
if (syllable.vowelComponent() == BPMF::ENG && !syllable.hasConsonant() && !syllable.hasMiddleVowel())
|
||||
{
|
||||
syllable += BPMF(BPMF::ERR);
|
||||
}
|
||||
else if (syllable.consonantComponent() == BPMF::G &&
|
||||
(syllable.middleVowelComponent() == BPMF::I || syllable.middleVowelComponent() == BPMF::UE))
|
||||
{
|
||||
syllable += BPMF(BPMF::J);
|
||||
}
|
||||
}
|
||||
|
||||
return syllable;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool endAheadOrAheadHasToneMarkKey(std::string::const_iterator ahead, std::string::const_iterator end) const
|
||||
{
|
||||
if (ahead == end)
|
||||
return true;
|
||||
|
||||
char tone1 = componentToKey(BPMF::Tone1);
|
||||
char tone2 = componentToKey(BPMF::Tone2);
|
||||
char tone3 = componentToKey(BPMF::Tone3);
|
||||
char tone4 = componentToKey(BPMF::Tone4);
|
||||
char tone5 = componentToKey(BPMF::Tone5);
|
||||
|
||||
if (tone1)
|
||||
if (*ahead == tone1)
|
||||
return true;
|
||||
|
||||
if (*ahead == tone2 || *ahead == tone3 || *ahead == tone4 || *ahead == tone5)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sequenceContainsIorUE(std::string::const_iterator start, std::string::const_iterator end) const
|
||||
{
|
||||
char iChar = componentToKey(BPMF::I);
|
||||
char ueChar = componentToKey(BPMF::UE);
|
||||
|
||||
for (; start != end; ++start)
|
||||
if (*start == iChar || *start == ueChar)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string m_name;
|
||||
BopomofoKeyToComponentMap m_keyToComponent;
|
||||
BopomofoComponentToKeyMap m_componentToKey;
|
||||
};
|
||||
|
||||
extern "C" class BopomofoReadingBuffer
|
||||
{
|
||||
public:
|
||||
explicit BopomofoReadingBuffer(const BopomofoKeyboardLayout *layout) : layout_(layout), pinyin_mode_(false)
|
||||
{
|
||||
if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout())
|
||||
{
|
||||
pinyin_mode_ = true;
|
||||
pinyin_sequence_ = "";
|
||||
}
|
||||
}
|
||||
|
||||
void setKeyboardLayout(const BopomofoKeyboardLayout *layout)
|
||||
{
|
||||
layout_ = layout;
|
||||
|
||||
if (layout == BopomofoKeyboardLayout::HanyuPinyinLayout())
|
||||
{
|
||||
pinyin_mode_ = true;
|
||||
pinyin_sequence_ = "";
|
||||
}
|
||||
}
|
||||
|
||||
bool isValidKey(char k) const
|
||||
{
|
||||
if (!pinyin_mode_)
|
||||
{
|
||||
return layout_ ? (layout_->keyToComponents(k)).size() > 0 : false;
|
||||
}
|
||||
|
||||
char lk = tolower(k);
|
||||
if (lk >= 'a' && lk <= 'z')
|
||||
{
|
||||
// if a tone marker is already in place
|
||||
if (pinyin_sequence_.length())
|
||||
{
|
||||
char lastc = pinyin_sequence_[pinyin_sequence_.length() - 1];
|
||||
if (lastc >= '2' && lastc <= '5')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pinyin_sequence_.length() && (lk >= '2' && lk <= '5'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool combineKey(char k)
|
||||
{
|
||||
if (!isValidKey(k))
|
||||
return false;
|
||||
|
||||
if (pinyin_mode_)
|
||||
{
|
||||
pinyin_sequence_ += std::string(1, tolower(k));
|
||||
syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string sequence = layout_->keySequenceFromSyllable(syllable_) + std::string(1, k);
|
||||
syllable_ = layout_->syllableFromKeySequence(sequence);
|
||||
return true;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
pinyin_sequence_.clear();
|
||||
syllable_.clear();
|
||||
}
|
||||
|
||||
void backspace()
|
||||
{
|
||||
if (!layout_)
|
||||
return;
|
||||
|
||||
if (pinyin_mode_)
|
||||
{
|
||||
if (pinyin_sequence_.length())
|
||||
{
|
||||
pinyin_sequence_ = pinyin_sequence_.substr(0, pinyin_sequence_.length() - 1);
|
||||
}
|
||||
|
||||
syllable_ = BPMF::FromHanyuPinyin(pinyin_sequence_);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string sequence = layout_->keySequenceFromSyllable(syllable_);
|
||||
if (sequence.length())
|
||||
{
|
||||
sequence = sequence.substr(0, sequence.length() - 1);
|
||||
syllable_ = layout_->syllableFromKeySequence(sequence);
|
||||
}
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return syllable_.isEmpty();
|
||||
}
|
||||
|
||||
const std::string composedString() const
|
||||
{
|
||||
if (pinyin_mode_)
|
||||
{
|
||||
return pinyin_sequence_;
|
||||
}
|
||||
|
||||
return syllable_.composedString();
|
||||
}
|
||||
|
||||
const BPMF syllable() const
|
||||
{
|
||||
return syllable_;
|
||||
}
|
||||
|
||||
const std::string standardLayoutQueryString() const
|
||||
{
|
||||
return BopomofoKeyboardLayout::StandardLayout()->keySequenceFromSyllable(syllable_);
|
||||
}
|
||||
|
||||
bool hasToneMarker() const
|
||||
{
|
||||
return syllable_.hasToneMarker();
|
||||
}
|
||||
|
||||
bool hasToneMarkerOnly() const
|
||||
{
|
||||
return syllable_.hasToneMarker() &&
|
||||
!(syllable_.hasConsonant() || syllable_.hasMiddleVowel() || syllable_.hasVowel());
|
||||
}
|
||||
|
||||
protected:
|
||||
const BopomofoKeyboardLayout *layout_;
|
||||
BPMF syllable_;
|
||||
|
||||
bool pinyin_mode_;
|
||||
std::string pinyin_sequence_;
|
||||
};
|
||||
} // namespace Mandarin
|
||||
|
||||
#endif // MANDARIN_H_
|
|
@ -31,4 +31,3 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
@import Foundation;
|
||||
|
||||
#import "CTools.h"
|
||||
#import "Composer.hh"
|
||||
|
|
|
@ -48,6 +48,7 @@ protocol KeyHandlerDelegate: NSObjectProtocol {
|
|||
|
||||
class KeyHandler: NSObject {
|
||||
let kEpsilon: Double = 0.000001
|
||||
var _composer: Tekkon.Composer = .init()
|
||||
var _inputMode: String = ""
|
||||
var _languageModel: vChewing.LMInstantiator = .init()
|
||||
var _userOverrideModel: vChewing.LMUserOverride = .init()
|
||||
|
@ -73,12 +74,12 @@ class KeyHandler: NSObject {
|
|||
override init() {
|
||||
_builder = Megrez.BlockReadingBuilder(lm: _languageModel)
|
||||
super.init()
|
||||
Composer.ensureParser()
|
||||
ensureParser()
|
||||
setInputMode(ctlInputMethod.currentInputMode)
|
||||
}
|
||||
|
||||
func clear() {
|
||||
Composer.clearBuffer()
|
||||
_composer.clear()
|
||||
_builder.clear()
|
||||
_walkedNodes.removeAll()
|
||||
}
|
||||
|
@ -104,8 +105,8 @@ class KeyHandler: NSObject {
|
|||
// Create new grid builder.
|
||||
createNewBuilder()
|
||||
|
||||
if !Composer.isBufferEmpty() {
|
||||
Composer.clearBuffer()
|
||||
if !_composer.isEmpty {
|
||||
_composer.clear()
|
||||
}
|
||||
}
|
||||
// 直接寫到衛星模組內,省得類型轉換
|
||||
|
@ -258,7 +259,7 @@ class KeyHandler: NSObject {
|
|||
return highestScore + epsilon
|
||||
}
|
||||
|
||||
// MARK: - Extracted methods and functions.
|
||||
// MARK: - Extracted methods and functions (Megrez).
|
||||
|
||||
func isBuilderEmpty() -> Bool { _builder.grid().width() == 0 }
|
||||
|
||||
|
@ -321,4 +322,30 @@ class KeyHandler: NSObject {
|
|||
func getKeyLengthAtIndexZero() -> Int {
|
||||
_walkedNodes[0].node?.currentKeyValue().value.count ?? 0
|
||||
}
|
||||
|
||||
// MARK: - Extracted methods and functions (Tekkon).
|
||||
|
||||
func ensureParser() {
|
||||
switch mgrPrefs.mandarinParser {
|
||||
case MandarinParser.ofStandard.rawValue:
|
||||
_composer.ensureParser(arrange: .ofDachen)
|
||||
case MandarinParser.ofEten.rawValue:
|
||||
_composer.ensureParser(arrange: .ofEten)
|
||||
case MandarinParser.ofHsu.rawValue:
|
||||
_composer.ensureParser(arrange: .ofHsu)
|
||||
case MandarinParser.ofEten26.rawValue:
|
||||
_composer.ensureParser(arrange: .ofEten26)
|
||||
case MandarinParser.ofIBM.rawValue:
|
||||
_composer.ensureParser(arrange: .ofIBM)
|
||||
case MandarinParser.ofMiTAC.rawValue:
|
||||
_composer.ensureParser(arrange: .ofMiTAC)
|
||||
case MandarinParser.ofFakeSeigyou.rawValue:
|
||||
_composer.ensureParser(arrange: .ofFakeSeigyou)
|
||||
default:
|
||||
_composer.ensureParser(arrange: .ofDachen)
|
||||
mgrPrefs.mandarinParser = MandarinParser.ofStandard.rawValue
|
||||
}
|
||||
_composer.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -331,7 +331,7 @@ extension KeyHandler {
|
|||
let punctuation: String = arrPunctuations.joined(separator: "")
|
||||
|
||||
var shouldAutoSelectCandidate: Bool =
|
||||
Composer.chkKeyValidity(charCode) || ifLangModelHasUnigrams(forKey: customPunctuation)
|
||||
_composer.inputValidityCheck(key: charCode) || ifLangModelHasUnigrams(forKey: customPunctuation)
|
||||
|| ifLangModelHasUnigrams(forKey: punctuation)
|
||||
|
||||
if !shouldAutoSelectCandidate, input.isUpperCaseASCIILetterKey {
|
||||
|
|
|
@ -147,34 +147,34 @@ extension KeyHandler {
|
|||
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold
|
||||
|
||||
// See if Phonetic reading is valid.
|
||||
if !skipPhoneticHandling && Composer.chkKeyValidity(charCode) {
|
||||
Composer.combineReadingKey(charCode)
|
||||
if !skipPhoneticHandling && _composer.inputValidityCheck(key: charCode) {
|
||||
_composer.receiveKey(fromCharCode: charCode)
|
||||
keyConsumedByReading = true
|
||||
|
||||
// If we have a tone marker, we have to insert the reading to the
|
||||
// builder in other words, if we don't have a tone marker, we just
|
||||
// update the composing buffer.
|
||||
let composeReading = Composer.hasToneMarker()
|
||||
let composeReading = _composer.hasToneMarker()
|
||||
if !composeReading {
|
||||
stateCallback(buildInputtingState())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var composeReading = Composer.hasToneMarker() || Composer.hasToneMarkerOnly()
|
||||
var composeReading = _composer.hasToneMarker() // 這裡不需要做排他性判斷。
|
||||
|
||||
// See if we have composition if Enter/Space is hit and buffer is not empty.
|
||||
// We use "|=" conditioning so that the tone marker key is also taken into account.
|
||||
// However, Swift does not support "|=".
|
||||
composeReading = composeReading || (!Composer.isBufferEmpty() && (input.isSpace || input.isEnter))
|
||||
composeReading = composeReading || (!_composer.isEmpty && (input.isSpace || input.isEnter))
|
||||
if composeReading {
|
||||
let reading = Composer.getSyllableComposition()
|
||||
let reading = _composer.getRealComposition()
|
||||
|
||||
// See whether we have a unigram for this...
|
||||
if !ifLangModelHasUnigrams(forKey: reading) {
|
||||
IME.prtDebugIntel("B49C0979:語彙庫內無「\(reading)」的匹配記錄。")
|
||||
errorCallback()
|
||||
Composer.clearBuffer()
|
||||
_composer.clear()
|
||||
stateCallback((getBuilderLength() == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState())
|
||||
return true
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ extension KeyHandler {
|
|||
dealWithOverrideModelSuggestions()
|
||||
|
||||
// ... then update the text.
|
||||
Composer.clearBuffer()
|
||||
_composer.clear()
|
||||
|
||||
let inputting = buildInputtingState()
|
||||
inputting.poppedText = poppedText
|
||||
|
@ -239,7 +239,7 @@ extension KeyHandler {
|
|||
// MARK: Calling candidate window using Space or Down or PageUp / PageDn.
|
||||
|
||||
if let currentState = state as? InputState.NotEmpty {
|
||||
if Composer.isBufferEmpty(),
|
||||
if _composer.isEmpty,
|
||||
input.isExtraChooseCandidateKey || input.isExtraChooseCandidateKeyReverse || input.isSpace
|
||||
|| input.isPageDown || input.isPageUp || input.isTab
|
||||
|| (input.useVerticalMode && (input.isVerticalModeOnlyChooseCandidateKey))
|
||||
|
@ -354,7 +354,7 @@ extension KeyHandler {
|
|||
if input.isSymbolMenuPhysicalKey && !input.isShiftHold {
|
||||
if !input.isOptionHold {
|
||||
if ifLangModelHasUnigrams(forKey: "_punctuation_list") {
|
||||
if Composer.isBufferEmpty() {
|
||||
if _composer.isEmpty {
|
||||
insertReadingToBuilderAtCursor(reading: "_punctuation_list")
|
||||
let poppedText: String! = popOverflowComposingTextAndWalk()
|
||||
let inputting = buildInputtingState()
|
||||
|
@ -443,7 +443,7 @@ extension KeyHandler {
|
|||
// "thinking" that the key is not actually consumed.
|
||||
// 砍掉這一段會導致「F1-F12 按鍵干擾組字區」的問題。
|
||||
// 暫時只能先恢復這段,且補上偵錯彙報機制,方便今後排查故障。
|
||||
if (state is InputState.NotEmpty) || !Composer.isBufferEmpty() {
|
||||
if (state is InputState.NotEmpty) || !_composer.isEmpty {
|
||||
IME.prtDebugIntel(
|
||||
"Blocked data: charCode: \(charCode), keyCode: \(input.keyCode)")
|
||||
IME.prtDebugIntel("A9BFF20E")
|
||||
|
|
|
@ -87,7 +87,7 @@ extension KeyHandler {
|
|||
// The reading text is what the user is typing.
|
||||
|
||||
let head = String((composingBuffer as NSString).substring(to: composedStringCursorIndex))
|
||||
let reading = Composer.getComposition()
|
||||
let reading = _composer.getDisplayedComposition()
|
||||
let tail = String((composingBuffer as NSString).substring(from: composedStringCursorIndex))
|
||||
let composedText = head + reading + tail
|
||||
let cursorIndex = composedStringCursorIndex + reading.count
|
||||
|
@ -213,14 +213,14 @@ extension KeyHandler {
|
|||
return false
|
||||
}
|
||||
|
||||
if Composer.isBufferEmpty() {
|
||||
if _composer.isEmpty {
|
||||
insertReadingToBuilderAtCursor(reading: customPunctuation)
|
||||
let poppedText = popOverflowComposingTextAndWalk()
|
||||
let inputting = buildInputtingState()
|
||||
inputting.poppedText = poppedText
|
||||
stateCallback(inputting)
|
||||
|
||||
if mgrPrefs.useSCPCTypingMode, Composer.isBufferEmpty() {
|
||||
if mgrPrefs.useSCPCTypingMode, _composer.isEmpty {
|
||||
let candidateState = buildCandidate(
|
||||
state: inputting,
|
||||
useVerticalMode: useVerticalMode
|
||||
|
@ -335,9 +335,9 @@ extension KeyHandler {
|
|||
return false
|
||||
}
|
||||
|
||||
if Composer.hasToneMarkerOnly() {
|
||||
Composer.clearBuffer()
|
||||
} else if Composer.isBufferEmpty() {
|
||||
if _composer.hasToneMarker(withNothingElse: true) {
|
||||
_composer.clear()
|
||||
} else if _composer.isEmpty {
|
||||
if getBuilderCursorIndex() >= 0 {
|
||||
deleteBuilderReadingInFrontOfCursor()
|
||||
walk()
|
||||
|
@ -348,10 +348,10 @@ extension KeyHandler {
|
|||
return true
|
||||
}
|
||||
} else {
|
||||
Composer.doBackSpaceToBuffer()
|
||||
_composer.doBackSpace()
|
||||
}
|
||||
|
||||
if Composer.isBufferEmpty(), getBuilderLength() == 0 {
|
||||
if _composer.isEmpty, getBuilderLength() == 0 {
|
||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
} else {
|
||||
stateCallback(buildInputtingState())
|
||||
|
@ -370,7 +370,7 @@ extension KeyHandler {
|
|||
return false
|
||||
}
|
||||
|
||||
if Composer.isBufferEmpty() {
|
||||
if _composer.isEmpty {
|
||||
if getBuilderCursorIndex() != getBuilderLength() {
|
||||
deleteBuilderReadingAfterCursor()
|
||||
walk()
|
||||
|
@ -405,7 +405,7 @@ extension KeyHandler {
|
|||
if !(state is InputState.Inputting) {
|
||||
return false
|
||||
}
|
||||
if !Composer.isBufferEmpty() {
|
||||
if !_composer.isEmpty {
|
||||
IME.prtDebugIntel("9B6F908D")
|
||||
errorCallback()
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ extension KeyHandler {
|
|||
return false
|
||||
}
|
||||
|
||||
if !Composer.isBufferEmpty() {
|
||||
if !_composer.isEmpty {
|
||||
IME.prtDebugIntel("ABC44080")
|
||||
errorCallback()
|
||||
stateCallback(state)
|
||||
|
@ -454,7 +454,7 @@ extension KeyHandler {
|
|||
return false
|
||||
}
|
||||
|
||||
if !Composer.isBufferEmpty() {
|
||||
if !_composer.isEmpty {
|
||||
IME.prtDebugIntel("9B69908D")
|
||||
errorCallback()
|
||||
stateCallback(state)
|
||||
|
@ -493,8 +493,8 @@ extension KeyHandler {
|
|||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
} else {
|
||||
// If reading is not empty, we cancel the reading.
|
||||
if !Composer.isBufferEmpty() {
|
||||
Composer.clearBuffer()
|
||||
if !_composer.isEmpty {
|
||||
_composer.clear()
|
||||
if getBuilderLength() == 0 {
|
||||
stateCallback(InputState.EmptyIgnoringPreviousState())
|
||||
} else {
|
||||
|
@ -515,7 +515,7 @@ extension KeyHandler {
|
|||
) -> Bool {
|
||||
if !(state is InputState.Inputting) { return false }
|
||||
|
||||
if !Composer.isBufferEmpty() {
|
||||
if !_composer.isEmpty {
|
||||
IME.prtDebugIntel("B3BA5257")
|
||||
errorCallback()
|
||||
stateCallback(state)
|
||||
|
@ -566,7 +566,7 @@ extension KeyHandler {
|
|||
) -> Bool {
|
||||
if !(state is InputState.Inputting) { return false }
|
||||
|
||||
if !Composer.isBufferEmpty() {
|
||||
if !_composer.isEmpty {
|
||||
IME.prtDebugIntel("6ED95318")
|
||||
errorCallback()
|
||||
stateCallback(state)
|
||||
|
|
|
@ -0,0 +1,546 @@
|
|||
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
|
||||
/*
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
1. The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
2. No trademark license is granted to use the trade names, trademarks, service
|
||||
marks, or product names of Contributor, except as required to fulfill notice
|
||||
requirements above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The namespace for this package.
|
||||
public struct Tekkon {
|
||||
// MARK: - Static Constants and Basic Enums
|
||||
|
||||
public enum PhoneType: Int {
|
||||
case null = 0 // 假
|
||||
case consonant = 1 // 聲
|
||||
case semivowel = 2 // 韻
|
||||
case vowel = 3 // 介
|
||||
case intonation = 4 // 調
|
||||
}
|
||||
|
||||
public enum MandarinParser: Int {
|
||||
case ofDachen = 0
|
||||
case ofEten = 1
|
||||
case ofHsu = 2
|
||||
case ofEten26 = 3
|
||||
case ofIBM = 4
|
||||
case ofMiTAC = 5
|
||||
case ofFakeSeigyou = 6
|
||||
case ofSeigyou = 7
|
||||
case ofHanyuPinyin = 10
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .ofDachen:
|
||||
return "Dachen"
|
||||
case .ofEten:
|
||||
return "ETen"
|
||||
case .ofHsu:
|
||||
return "Hsu"
|
||||
case .ofEten26:
|
||||
return "ETen26"
|
||||
case .ofIBM:
|
||||
return "IBM"
|
||||
case .ofMiTAC:
|
||||
return "MiTAC"
|
||||
case .ofFakeSeigyou:
|
||||
return "FakeSeigyou"
|
||||
case .ofSeigyou:
|
||||
return "Seigyou"
|
||||
case .ofHanyuPinyin:
|
||||
return "HanyuPinyin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static let allowedConsonants = [
|
||||
"ㄅ", "ㄆ", "ㄇ", "ㄈ", "ㄉ", "ㄊ", "ㄋ", "ㄌ",
|
||||
"ㄍ", "ㄎ", "ㄏ", "ㄐ", "ㄑ", "ㄒ",
|
||||
"ㄓ", "ㄔ", "ㄕ", "ㄖ", "ㄗ", "ㄘ", "ㄙ",
|
||||
]
|
||||
|
||||
public static let allowedsemivowels = ["ㄧ", "ㄨ", "ㄩ"]
|
||||
|
||||
public static let allowedVowels = [
|
||||
"ㄚ", "ㄛ", "ㄜ", "ㄝ", "ㄞ", "ㄟ",
|
||||
"ㄠ", "ㄡ", "ㄢ", "ㄣ", "ㄤ", "ㄥ", "ㄦ",
|
||||
]
|
||||
|
||||
public static let allowedIntonations = [" ", "ˊ", "ˇ", "ˋ", "˙"]
|
||||
|
||||
public static var allowedPhonabets: [String] {
|
||||
allowedConsonants + allowedsemivowels + allowedVowels + allowedIntonations
|
||||
}
|
||||
|
||||
// MARK: - Phonabet Structure
|
||||
|
||||
@frozen public struct Phonabet: Equatable, Hashable, ExpressibleByStringLiteral {
|
||||
public var type: PhoneType = .null
|
||||
public var value: String = ""
|
||||
public var isEmpty: Bool {
|
||||
value.isEmpty
|
||||
}
|
||||
|
||||
public init(_ input: String = "") {
|
||||
if !input.isEmpty {
|
||||
if allowedPhonabets.contains(String(input.reversed()[0])) {
|
||||
value = String(input.reversed()[0])
|
||||
if Tekkon.allowedConsonants.contains(value) { type = .consonant }
|
||||
if Tekkon.allowedsemivowels.contains(value) { type = .semivowel }
|
||||
if Tekkon.allowedVowels.contains(value) { type = .vowel }
|
||||
if Tekkon.allowedIntonations.contains(value) { type = .intonation }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func clear() {
|
||||
value = ""
|
||||
}
|
||||
|
||||
// MARK: - Misc Definitions
|
||||
|
||||
public static func == (lhs: Phonabet, rhs: Phonabet) -> Bool {
|
||||
lhs.value == rhs.value
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(value)
|
||||
hasher.combine(type)
|
||||
}
|
||||
|
||||
public init(stringLiteral value: String) {
|
||||
self.init(value)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral value: String) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: String) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Syllable Composer
|
||||
|
||||
@frozen public struct Composer: Equatable, Hashable, ExpressibleByStringLiteral {
|
||||
public var consonant: Phonabet = ""
|
||||
public var semivowel: Phonabet = ""
|
||||
public var vowel: Phonabet = ""
|
||||
public var intonation: Phonabet = ""
|
||||
public var parser: MandarinParser = .ofDachen
|
||||
public var value: String {
|
||||
consonant.value + semivowel.value + vowel.value + intonation.value.replacingOccurrences(of: " ", with: "")
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
intonation.isEmpty && vowel.isEmpty && semivowel.isEmpty && consonant.isEmpty
|
||||
}
|
||||
|
||||
public init(_ input: String = "", arrange parser: MandarinParser = .ofDachen) {
|
||||
receiveKey(fromString: input)
|
||||
ensureParser(arrange: parser)
|
||||
}
|
||||
|
||||
public mutating func clear() {
|
||||
consonant.clear()
|
||||
semivowel.clear()
|
||||
vowel.clear()
|
||||
intonation.clear()
|
||||
}
|
||||
|
||||
// MARK: - Public Functions
|
||||
|
||||
/// 用於檢測「某個輸入字符訊號的合規性」的函數。
|
||||
/// Phonabet 是一個特殊的 String 類 Struct,
|
||||
/// 只會接受正確的注音符號資料、且根據其類型自動回報其類型。
|
||||
/// 類型只有「聲、韻、介、調」這四類。
|
||||
public func inputValidityCheck(key inputKey: UniChar = 0) -> Bool {
|
||||
if let scalar = UnicodeScalar(inputKey) {
|
||||
let input = String(scalar)
|
||||
switch parser {
|
||||
case .ofDachen:
|
||||
return Tekkon.mapQwertyDachen[input] != nil
|
||||
case .ofEten:
|
||||
return Tekkon.mapQwertyEtenTraditional[input] != nil
|
||||
case .ofHsu:
|
||||
return Tekkon.mapHsuStaticKeys[input] != nil
|
||||
case .ofEten26:
|
||||
return Tekkon.mapEten26StaticKeys[input] != nil
|
||||
case .ofIBM:
|
||||
return Tekkon.mapQwertyIBM[input] != nil
|
||||
case .ofMiTAC:
|
||||
return Tekkon.mapQwertyMiTAC[input] != nil
|
||||
case .ofSeigyou:
|
||||
return Tekkon.mapSeigyou[input] != nil
|
||||
case .ofFakeSeigyou:
|
||||
return Tekkon.mapFakeSeigyou[input] != nil
|
||||
case .ofHanyuPinyin:
|
||||
return Tekkon.mapArayuruPinyin.contains(input)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public mutating func receiveKey(fromString input: String = "") {
|
||||
let translatedInput = translate(key: String(input))
|
||||
let thePhone: Phonabet = .init(translatedInput)
|
||||
switch thePhone.type {
|
||||
case .consonant: consonant = thePhone
|
||||
case .semivowel: semivowel = thePhone
|
||||
case .vowel: vowel = thePhone
|
||||
case .intonation: intonation = thePhone
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func receiveKey(fromCharCode inputCharCode: UniChar = 0) {
|
||||
if let scalar = UnicodeScalar(inputCharCode) {
|
||||
let translatedInput = translate(key: String(scalar))
|
||||
let thePhone: Phonabet = .init(translatedInput)
|
||||
switch thePhone.type {
|
||||
case .consonant: consonant = thePhone
|
||||
case .semivowel: semivowel = thePhone
|
||||
case .vowel: vowel = thePhone
|
||||
case .intonation: intonation = thePhone
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 本來這個函數是不需要的,但將來在做漢語拼音功能時、這裡得回傳別的東西。
|
||||
/// 也就是說,這個函數就是用來決定輸入法組字區內顯示的注音/拼音內容。
|
||||
public func getDisplayedComposition() -> String {
|
||||
value
|
||||
}
|
||||
|
||||
/// 這是專門用來「生成用以進行詞庫檢索的 Key」的函數。
|
||||
public func getRealComposition() -> String {
|
||||
value
|
||||
}
|
||||
|
||||
/// 專門用來響應使用者摁下 BackSpace 按鍵時的行為。
|
||||
public mutating func doBackSpace() {
|
||||
if !intonation.isEmpty {
|
||||
intonation.clear()
|
||||
} else if !vowel.isEmpty {
|
||||
vowel.clear()
|
||||
} else if !semivowel.isEmpty {
|
||||
semivowel.clear()
|
||||
} else if !consonant.isEmpty {
|
||||
consonant.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/// 用來檢測是否有調號
|
||||
public func hasToneMarker(withNothingElse: Bool = false) -> Bool {
|
||||
if !withNothingElse {
|
||||
return !intonation.isEmpty
|
||||
}
|
||||
return !intonation.isEmpty && vowel.isEmpty && semivowel.isEmpty && consonant.isEmpty
|
||||
}
|
||||
|
||||
/// 當接收按鍵輸入時的處理。本來 Phonabet 就會自動使輸入的無效數值被忽略掉。
|
||||
/// 然後再根據 Phonabet 初期化後自身的 type 屬性來決定「聲、韻、介、調」到底哪個該更新。
|
||||
public mutating func receiveCharCode(_ inputKey: UniChar = 0) {
|
||||
// TODO: 在這裡補上鍵盤轉換工序
|
||||
if let scalar = UnicodeScalar(inputKey) {
|
||||
let input = String(scalar)
|
||||
receiveKey(fromString: input)
|
||||
}
|
||||
}
|
||||
|
||||
// 設定該 Composer 處於何種鍵盤排列分析模式
|
||||
public mutating func ensureParser(arrange: MandarinParser = .ofDachen) {
|
||||
parser = arrange
|
||||
}
|
||||
|
||||
// MARK: - Parser Processings
|
||||
|
||||
mutating func translate(key: String = "") -> String {
|
||||
switch parser {
|
||||
case .ofDachen:
|
||||
return Tekkon.mapQwertyDachen[key] ?? ""
|
||||
case .ofEten:
|
||||
return Tekkon.mapQwertyEtenTraditional[key] ?? ""
|
||||
case .ofHsu:
|
||||
return handleHsu(key: key)
|
||||
case .ofEten26:
|
||||
return handleEten26(key: key)
|
||||
case .ofIBM:
|
||||
return Tekkon.mapQwertyIBM[key] ?? ""
|
||||
case .ofMiTAC:
|
||||
return Tekkon.mapQwertyMiTAC[key] ?? ""
|
||||
case .ofSeigyou:
|
||||
return Tekkon.mapSeigyou[key] ?? ""
|
||||
case .ofFakeSeigyou:
|
||||
return Tekkon.mapFakeSeigyou[key] ?? ""
|
||||
case .ofHanyuPinyin: break // TODO: 待辦
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// 倚天忘形注音排列比較麻煩,需要單獨處理。
|
||||
mutating func handleEten26(key: String = "") -> String {
|
||||
var strReturn = ""
|
||||
strReturn = Tekkon.mapEten26StaticKeys[key] ?? ""
|
||||
let incomingPhonabet = Phonabet(strReturn)
|
||||
|
||||
switch key {
|
||||
case "d": if consonant.isEmpty { consonant = "ㄉ" } else { intonation = "˙" }
|
||||
case "f": if consonant.isEmpty { consonant = "ㄈ" } else { intonation = "ˊ" }
|
||||
case "h": if consonant.isEmpty { consonant = "ㄏ" } else { vowel = "ㄦ" }
|
||||
case "j": if consonant.isEmpty { consonant = "ㄖ" } else { intonation = "ˇ" }
|
||||
case "k": if consonant.isEmpty { consonant = "ㄎ" } else { intonation = "ˋ" }
|
||||
case "l": if consonant.isEmpty { consonant = "ㄌ" } else { vowel = "ㄥ" }
|
||||
case "m": if consonant.isEmpty { consonant = "ㄇ" } else { vowel = "ㄢ" }
|
||||
case "n": if consonant.isEmpty { consonant = "ㄋ" } else { vowel = "ㄣ" }
|
||||
case "p": if consonant.isEmpty { consonant = "ㄆ" } else { vowel = "ㄡ" }
|
||||
case "q": if consonant.isEmpty { consonant = "ㄗ" } else { vowel = "ㄟ" }
|
||||
case "t": if consonant.isEmpty { consonant = "ㄊ" } else { vowel = "ㄤ" }
|
||||
case "w": if consonant.isEmpty { consonant = "ㄘ" } else { vowel = "ㄝ" }
|
||||
default: break
|
||||
}
|
||||
|
||||
// 處理「一個按鍵對應兩個聲母」的情形。
|
||||
if !consonant.isEmpty, incomingPhonabet.type == .semivowel {
|
||||
switch consonant {
|
||||
case "ㄍ":
|
||||
switch incomingPhonabet {
|
||||
case "ㄧ": consonant = "ㄑ" // ㄑㄧ
|
||||
case "ㄨ": consonant = "ㄍ" // ㄍㄨ
|
||||
case "ㄩ": consonant = "ㄑ" // ㄑㄩ
|
||||
default: break
|
||||
}
|
||||
case "ㄓ":
|
||||
switch incomingPhonabet {
|
||||
case "ㄧ": consonant = "ㄐ" // ㄐㄧ
|
||||
case "ㄨ": consonant = "ㄓ" // ㄓㄨ
|
||||
case "ㄩ": consonant = "ㄐ" // ㄐㄩ
|
||||
default: break
|
||||
}
|
||||
case "ㄕ":
|
||||
switch incomingPhonabet {
|
||||
case "ㄧ": consonant = "ㄒ" // ㄒㄧ
|
||||
case "ㄨ": consonant = "ㄕ" // ㄕㄨ
|
||||
case "ㄩ": consonant = "ㄒ" // ㄒㄩ
|
||||
default: break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
return strReturn
|
||||
}
|
||||
|
||||
/// 許氏鍵盤與倚天忘形一樣同樣也比較麻煩,需要單獨處理。
|
||||
mutating func handleHsu(key: String = "") -> String {
|
||||
var strReturn = ""
|
||||
strReturn = Tekkon.mapHsuStaticKeys[key] ?? ""
|
||||
let incomingPhonabet = Phonabet(strReturn)
|
||||
|
||||
switch key {
|
||||
case "e": if semivowel.isEmpty { semivowel = "ㄧ" } else { vowel = "ㄝ" }
|
||||
case "a": if consonant.isEmpty { consonant = "ㄘ" } else { vowel = "ㄟ" }
|
||||
case "g": if consonant.isEmpty { consonant = "ㄍ" } else { vowel = "ㄜ" }
|
||||
case "h": if consonant.isEmpty { consonant = "ㄏ" } else { vowel = "ㄛ" }
|
||||
case "k": if consonant.isEmpty { consonant = "ㄎ" } else { vowel = "ㄤ" }
|
||||
case "m": if consonant.isEmpty { consonant = "ㄇ" } else { vowel = "ㄢ" }
|
||||
case "n": if consonant.isEmpty { consonant = "ㄋ" } else { vowel = "ㄣ" }
|
||||
case "s": if consonant.isEmpty { consonant = "ㄙ" } else { intonation = "˙" }
|
||||
case "d": if consonant.isEmpty { consonant = "ㄉ" } else { intonation = "ˊ" }
|
||||
case "f": if consonant.isEmpty { consonant = "ㄈ" } else { intonation = "ˇ" }
|
||||
case "l": if value.isEmpty { vowel = "ㄦ" } else if consonant.isEmpty { consonant = "ㄌ" } else { vowel = "ㄥ" }
|
||||
case "j": if !consonant.isEmpty { intonation = "ˋ" }
|
||||
default: break
|
||||
}
|
||||
|
||||
// 處理「一個按鍵對應兩個聲母」的情形。
|
||||
if !consonant.isEmpty, incomingPhonabet.type == .semivowel {
|
||||
switch consonant {
|
||||
case "ㄓ":
|
||||
if intonation.isEmpty {
|
||||
switch incomingPhonabet {
|
||||
case "ㄧ": consonant = "ㄐ" // ㄐㄧ
|
||||
case "ㄨ": consonant = "ㄓ" // ㄓㄨ
|
||||
case "ㄩ": consonant = "ㄐ" // ㄐㄩ
|
||||
default: break
|
||||
}
|
||||
}
|
||||
case "ㄕ":
|
||||
switch incomingPhonabet {
|
||||
case "ㄧ": consonant = "ㄒ" // ㄒㄧ
|
||||
case "ㄨ": consonant = "ㄕ" // ㄕㄨ
|
||||
case "ㄩ": consonant = "ㄒ" // ㄒㄩ
|
||||
default: break
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
return strReturn
|
||||
}
|
||||
|
||||
// MARK: - Misc Definitions
|
||||
|
||||
public static func == (lhs: Composer, rhs: Composer) -> Bool {
|
||||
lhs.value == rhs.value
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(consonant)
|
||||
hasher.combine(semivowel)
|
||||
hasher.combine(vowel)
|
||||
hasher.combine(intonation)
|
||||
}
|
||||
|
||||
public init(stringLiteral value: String) {
|
||||
self.init(value)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral value: String) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: String) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Phonabets (Enum)
|
||||
|
||||
enum Phonabets: Phonabet {
|
||||
case ofBO = "ㄅ"
|
||||
case ofPO = "ㄆ"
|
||||
case ofMO = "ㄇ"
|
||||
case ofFO = "ㄈ"
|
||||
case ofDE = "ㄉ"
|
||||
case ofTE = "ㄊ"
|
||||
case ofNE = "ㄋ"
|
||||
case ofLE = "ㄌ"
|
||||
case ofGE = "ㄍ"
|
||||
case ofKE = "ㄎ"
|
||||
case ofHE = "ㄏ"
|
||||
case ofJI = "ㄐ"
|
||||
case ofQI = "ㄑ"
|
||||
case ofXI = "ㄒ"
|
||||
case ofZH = "ㄓ"
|
||||
case ofCH = "ㄔ"
|
||||
case ofSH = "ㄕ"
|
||||
case ofRI = "ㄖ"
|
||||
case ofZI = "ㄗ"
|
||||
case ofCI = "ㄘ"
|
||||
case ofSI = "ㄙ"
|
||||
case ofYI = "ㄧ"
|
||||
case ofWU = "ㄨ"
|
||||
case ofYU = "ㄩ"
|
||||
case ofAA = "ㄚ"
|
||||
case ofOO = "ㄛ"
|
||||
case ofEE = "ㄜ"
|
||||
case ofEA = "ㄝ"
|
||||
case ofAI = "ㄞ"
|
||||
case ofEI = "ㄟ"
|
||||
case ofAO = "ㄠ"
|
||||
case ofOU = "ㄡ"
|
||||
case ofAN = "ㄢ"
|
||||
case ofEN = "ㄣ"
|
||||
case ofAG = "ㄤ"
|
||||
case ofOG = "ㄥ"
|
||||
case ofT1 = " "
|
||||
case ofT2 = "ˊ"
|
||||
case ofT3 = "ˇ"
|
||||
case ofT4 = "ˋ"
|
||||
case ofT5 = "˙"
|
||||
}
|
||||
|
||||
// MARK: - Maps for Keyboard-to-Phonabet parsers
|
||||
|
||||
// 任何形式的拼音排列都會用到的陣列,用 Strings 反而省事一些。
|
||||
static let mapArayuruPinyin: String = "abcdefghijklmnopqrstuvwxyz12345 "
|
||||
|
||||
/// 標準大千排列專用處理陣列。
|
||||
/// 威注音輸入法 macOS 版使用了 Ukelele 佈局來完成對諸如倚天傳統等其它注音鍵盤排列的支援。
|
||||
/// 如果要將鐵恨模組拿給別的平台的輸入法使用的話,恐怕需要針對這些注音鍵盤排列各自新增專用陣列才可以。
|
||||
static let mapQwertyDachen: [String: String] = [
|
||||
"0": "ㄢ", "1": "ㄅ", "2": "ㄉ", "3": "ˇ", "4": "ˋ", "5": "ㄓ", "6": "ˊ", "7": "˙", "8": "ㄚ", "9": "ㄞ", "-": "ㄦ",
|
||||
",": "ㄝ", ".": "ㄡ", "/": "ㄥ", ";": "ㄤ", "a": "ㄇ", "b": "ㄖ", "c": "ㄏ", "d": "ㄎ", "e": "ㄍ", "f": "ㄑ", "g": "ㄕ",
|
||||
"h": "ㄘ", "i": "ㄛ", "j": "ㄨ", "k": "ㄜ", "l": "ㄠ", "m": "ㄩ", "n": "ㄙ", "o": "ㄟ", "p": "ㄣ", "q": "ㄆ", "r": "ㄐ",
|
||||
"s": "ㄋ", "t": "ㄔ", "u": "ㄧ", "v": "ㄒ", "w": "ㄊ", "x": "ㄌ", "y": "ㄗ", "z": "ㄈ", " ": " ",
|
||||
]
|
||||
|
||||
/// 許氏排列專用處理陣列,但未包含全部的映射內容。
|
||||
/// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。
|
||||
/// 這裡提前對複音按鍵做處理,然後再用程式判斷介母類型、據此判斷是否需要做複音切換。
|
||||
static let mapHsuStaticKeys: [String: String] = [
|
||||
"a": "ㄘ", "b": "ㄅ", "c": "ㄕ", "d": "ㄉ", "e": "ㄧ", "f": "ㄈ", "g": "ㄍ", "h": "ㄏ", "i": "ㄞ", "j": "ㄓ", "k": "ㄎ",
|
||||
"l": "ㄌ", "m": "ㄇ", "n": "ㄋ", "o": "ㄡ", "p": "ㄆ", "r": "ㄖ", "s": "ㄙ", "t": "ㄊ", "u": "ㄩ", "v": "ㄑ", "w": "ㄠ",
|
||||
"x": "ㄨ", "y": "ㄚ", "z": "ㄗ", " ": " ",
|
||||
]
|
||||
|
||||
/// 倚天忘形排列預處理專用陣列,但未包含全部的映射內容。
|
||||
/// 在這裡將二十六個字母寫全,也只是為了方便做 validity check。
|
||||
/// 這裡提前對ㄓ/ㄍ/ㄕ做處理,然後再用程式判斷介母類型、據此判斷是否需要換成ㄒ/ㄑ/ㄐ。
|
||||
static let mapEten26StaticKeys: [String: String] = [
|
||||
"a": "ㄚ", "b": "ㄅ", "c": "ㄕ", "d": "ㄉ", "e": "ㄧ", "f": "ㄈ", "g": "ㄓ", "h": "ㄏ", "i": "ㄞ", "j": "ㄖ", "k": "ㄎ",
|
||||
"l": "ㄌ", "m": "ㄇ", "n": "ㄋ", "o": "ㄛ", "p": "ㄆ", "q": "ㄗ", "r": "ㄜ", "s": "ㄙ", "t": "ㄊ", "u": "ㄩ", "v": "ㄍ",
|
||||
"w": "ㄘ", "x": "ㄨ", "y": "ㄔ", "z": "ㄠ", " ": " ",
|
||||
]
|
||||
|
||||
/// 倚天傳統排列專用處理陣列。
|
||||
static let mapQwertyEtenTraditional: [String: String] = [
|
||||
"'": "ㄘ", ",": "ㄓ", "-": "ㄥ", ".": "ㄔ", "/": "ㄕ", "0": "ㄤ", "1": "˙", "2": "ˊ", "3": "ˇ", "4": "ˋ", "7": "ㄑ",
|
||||
"8": "ㄢ", "9": "ㄣ", ";": "ㄗ", "=": "ㄦ", "a": "ㄚ", "b": "ㄅ", "c": "ㄒ", "d": "ㄉ", "e": "ㄧ", "f": "ㄈ", "g": "ㄐ",
|
||||
"h": "ㄏ", "i": "ㄞ", "j": "ㄖ", "k": "ㄎ", "l": "ㄌ", "m": "ㄇ", "n": "ㄋ", "o": "ㄛ", "p": "ㄆ", "q": "ㄟ", "r": "ㄜ",
|
||||
"s": "ㄙ", "t": "ㄊ", "u": "ㄩ", "v": "ㄍ", "w": "ㄝ", "x": "ㄨ", "y": "ㄡ", "z": "ㄠ", " ": " ",
|
||||
]
|
||||
|
||||
/// IBM排列專用處理陣列。
|
||||
static let mapQwertyIBM: [String: String] = [
|
||||
",": "ˇ", "-": "ㄏ", ".": "ˋ", "/": "˙", "0": "ㄎ", "1": "ㄅ", "2": "ㄆ", "3": "ㄇ", "4": "ㄈ", "5": "ㄉ", "6": "ㄊ",
|
||||
"7": "ㄋ", "8": "ㄌ", "9": "ㄍ", ";": "ㄠ", "a": "ㄧ", "b": "ㄥ", "c": "ㄣ", "d": "ㄩ", "e": "ㄒ", "f": "ㄚ", "g": "ㄛ",
|
||||
"h": "ㄜ", "i": "ㄗ", "j": "ㄝ", "k": "ㄞ", "l": "ㄟ", "m": "ˊ", "n": "ㄦ", "o": "ㄘ", "p": "ㄙ", "q": "ㄐ", "r": "ㄓ",
|
||||
"s": "ㄨ", "t": "ㄔ", "u": "ㄖ", "v": "ㄤ", "w": "ㄑ", "x": "ㄢ", "y": "ㄕ", "z": "ㄡ", " ": " ",
|
||||
]
|
||||
|
||||
/// 精業排列專用處理陣列。
|
||||
static let mapSeigyou: [String: String] = [
|
||||
"a": "ˇ", "b": "ㄒ", "c": "ㄌ", "d": "ㄋ", "e": "ㄊ", "f": "ㄎ", "g": "ㄑ", "h": "ㄕ", "i": "ㄛ", "j": "ㄘ", "k": "ㄜ",
|
||||
"l": "ㄠ", "m": "ㄙ", "n": "ㄖ", "o": "ㄟ", "p": "ㄣ", "q": "ˊ", "r": "ㄍ", "s": "ㄇ", "t": "ㄐ", "u": "ㄗ", "v": "ㄏ",
|
||||
"w": "ㄆ", "x": "ㄈ", "y": "ㄔ", "z": "ˋ", "1": "˙", "2": "ㄅ", "3": "ㄉ", "6": "ㄓ", "8": "ㄚ", "9": "ㄞ", "0": "ㄢ",
|
||||
"-": "ㄧ", ";": "ㄤ", ",": "ㄝ", ".": "ㄡ", "/": "ㄥ", "'": "ㄩ", "[": "ㄨ", "=": "ㄦ", " ": " ",
|
||||
]
|
||||
|
||||
/// 偽精業排列專用處理陣列。
|
||||
static let mapFakeSeigyou: [String: String] = [
|
||||
"a": "ˇ", "b": "ㄒ", "c": "ㄌ", "d": "ㄋ", "e": "ㄊ", "f": "ㄎ", "g": "ㄑ", "h": "ㄕ", "i": "ㄛ", "j": "ㄘ", "k": "ㄜ",
|
||||
"l": "ㄠ", "m": "ㄙ", "n": "ㄖ", "o": "ㄟ", "p": "ㄣ", "q": "ˊ", "r": "ㄍ", "s": "ㄇ", "t": "ㄐ", "u": "ㄗ", "v": "ㄏ",
|
||||
"w": "ㄆ", "x": "ㄈ", "y": "ㄔ", "z": "ˋ", "1": "˙", "2": "ㄅ", "3": "ㄉ", "6": "ㄓ", "8": "ㄚ", "9": "ㄞ", "0": "ㄢ",
|
||||
"4": "ㄧ", ";": "ㄤ", ",": "ㄝ", ".": "ㄡ", "/": "ㄥ", "7": "ㄩ", "5": "ㄨ", "-": "ㄦ", " ": " ",
|
||||
]
|
||||
|
||||
/// 神通排列專用處理陣列。
|
||||
static let mapQwertyMiTAC: [String: String] = [
|
||||
",": "ㄓ", "-": "ㄦ", ".": "ㄔ", "/": "ㄕ", "0": "ㄥ", "1": "˙", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "ㄞ", "6": "ㄠ",
|
||||
"7": "ㄢ", "8": "ㄣ", "9": "ㄤ", ";": "ㄝ", "a": "ㄚ", "b": "ㄅ", "c": "ㄘ", "d": "ㄉ", "e": "ㄜ", "f": "ㄈ", "g": "ㄍ",
|
||||
"h": "ㄏ", "i": "ㄟ", "j": "ㄐ", "k": "ㄎ", "l": "ㄌ", "m": "ㄇ", "n": "ㄋ", "o": "ㄛ", "p": "ㄆ", "q": "ㄑ", "r": "ㄖ",
|
||||
"s": "ㄙ", "t": "ㄊ", "u": "ㄡ", "v": "ㄩ", "w": "ㄨ", "x": "ㄒ", "y": "ㄧ", "z": "ㄗ", " ": " ",
|
||||
]
|
||||
}
|
|
@ -93,7 +93,8 @@ class ctlInputMethod: IMKInputController {
|
|||
currentClient = client
|
||||
|
||||
keyHandler.clear()
|
||||
Composer.ensureParser()
|
||||
keyHandler.ensureParser()
|
||||
|
||||
if let bundleCheckID = (client as? IMKTextInput)?.bundleIdentifier() {
|
||||
if bundleCheckID != Bundle.main.bundleIdentifier {
|
||||
// Override the keyboard layout to the basic one.
|
||||
|
|
|
@ -168,7 +168,7 @@ struct ComposingBufferSize {
|
|||
case ofStandard = 0
|
||||
case ofEten = 1
|
||||
case ofHsu = 2
|
||||
case ofEen26 = 3
|
||||
case ofEten26 = 3
|
||||
case ofIBM = 4
|
||||
case ofMiTAC = 5
|
||||
case ofFakeSeigyou = 6
|
||||
|
@ -182,7 +182,7 @@ struct ComposingBufferSize {
|
|||
return "ETen"
|
||||
case .ofHsu:
|
||||
return "Hsu"
|
||||
case .ofEen26:
|
||||
case .ofEten26:
|
||||
return "ETen26"
|
||||
case .ofIBM:
|
||||
return "IBM"
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
5B38F5A2281E2E49007D5F5D /* 0_Megrez.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1615FC0EB100ABF4B3 /* 0_Megrez.swift */; };
|
||||
5B38F5A3281E2E49007D5F5D /* 3_Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */; };
|
||||
5B38F5A4281E2E49007D5F5D /* 5_LanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1915FC0EB100ABF4B3 /* 5_LanguageModel.swift */; };
|
||||
5B407153281F94E6009C24CB /* Composer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B407152281F94E6009C24CB /* Composer.mm */; };
|
||||
5B40730C281672610023DFFF /* lmAssociates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B407309281672610023DFFF /* lmAssociates.swift */; };
|
||||
5B40730D281672610023DFFF /* lmReplacements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B40730A281672610023DFFF /* lmReplacements.swift */; };
|
||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E535127EF261400C6AA1E /* IME.swift */; };
|
||||
|
@ -70,6 +69,7 @@
|
|||
5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3C27FEF3C8002DE248 /* Section.swift */; };
|
||||
5BA9FD4A27FEF3C9002DE248 /* PreferencesTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */; };
|
||||
5BA9FD8B28006B41002DE248 /* VDKComboBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */; };
|
||||
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAA8FBD282CAF380066C406 /* SyllableComposer.swift */; };
|
||||
5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */; };
|
||||
5BAEFAD028012565001F42C9 /* mgrLangModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAEFACF28012565001F42C9 /* mgrLangModel.swift */; };
|
||||
5BB802DA27FABA8300CF1C19 /* ctlInputMethod_Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */; };
|
||||
|
@ -98,7 +98,6 @@
|
|||
5BE78BD927B3775B005EA1BE /* ctlAboutWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE78BD827B37750005EA1BE /* ctlAboutWindow.swift */; };
|
||||
5BE78BDD27B3776D005EA1BE /* frmAboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BE78BDA27B37764005EA1BE /* frmAboutWindow.xib */; };
|
||||
5BF8423127BAA942008E7E4C /* vChewingKanjiConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF8423027BAA942008E7E4C /* vChewingKanjiConverter.swift */; };
|
||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */; };
|
||||
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A187E2816004C5900466B2E /* MainMenu.xib */; };
|
||||
6A225A1F23679F2600F685C6 /* NotarizedArchives in Resources */ = {isa = PBXBuildFile; fileRef = 6A225A1E23679F2600F685C6 /* NotarizedArchives */; };
|
||||
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
|
||||
|
@ -199,8 +198,6 @@
|
|||
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
|
||||
5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = "<group>"; };
|
||||
5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5B407151281F94E6009C24CB /* Composer.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Composer.hh; sourceTree = "<group>"; };
|
||||
5B407152281F94E6009C24CB /* Composer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Composer.mm; sourceTree = "<group>"; };
|
||||
5B407309281672610023DFFF /* lmAssociates.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = lmAssociates.swift; sourceTree = "<group>"; usesTabs = 0; };
|
||||
5B40730A281672610023DFFF /* lmReplacements.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = lmReplacements.swift; sourceTree = "<group>"; usesTabs = 0; };
|
||||
5B5E535127EF261400C6AA1E /* IME.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = IME.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
|
@ -246,6 +243,7 @@
|
|||
5BA9FD3C27FEF3C8002DE248 /* Section.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = Section.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BA9FD3D27FEF3C8002DE248 /* PreferencesTabViewController.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = PreferencesTabViewController.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BA9FD8A28006B41002DE248 /* VDKComboBox.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = VDKComboBox.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BAA8FBD282CAF380066C406 /* SyllableComposer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyllableComposer.swift; sourceTree = "<group>"; };
|
||||
5BAEFACF28012565001F42C9 /* mgrLangModel.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = mgrLangModel.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BB802D927FABA8300CF1C19 /* ctlInputMethod_Menu.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = ctlInputMethod_Menu.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
5BBBB75D27AED54C0023B93A /* Beep.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Beep.m4a; sourceTree = "<group>"; };
|
||||
|
@ -303,8 +301,6 @@
|
|||
6A0D4F1C15FC0EB100ABF4B3 /* 3_Span.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 3_Span.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 6_Unigram.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A0D4F1E15FC0EB100ABF4B3 /* 1_Walker.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = 1_Walker.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
|
||||
6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = Mandarin.cpp; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
|
||||
6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Mandarin.h; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
|
||||
6A15B32421A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
6A15B32521A51F2300B92CD3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
6A225A1E23679F2600F685C6 /* NotarizedArchives */ = {isa = PBXFileReference; lastKnownFileType = folder; path = NotarizedArchives; sourceTree = "<group>"; };
|
||||
|
@ -399,24 +395,12 @@
|
|||
children = (
|
||||
5B949BD72816DC4400D87B5D /* LineReader */,
|
||||
5B707CE627D9F43E0099EF99 /* OpenCCBridge */,
|
||||
5B62A30227AE733500A19448 /* OVMandarin */,
|
||||
5BA9FCEA27FED652002DE248 /* SindreSorhus */,
|
||||
5BA9FD8C28006BA7002DE248 /* VDKComboBox */,
|
||||
);
|
||||
path = 3rdParty;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5B62A30227AE733500A19448 /* OVMandarin */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6A0D4F2015FC0EB100ABF4B3 /* Mandarin.cpp */,
|
||||
6A0D4F2115FC0EB100ABF4B3 /* Mandarin.h */,
|
||||
5B407151281F94E6009C24CB /* Composer.hh */,
|
||||
5B407152281F94E6009C24CB /* Composer.mm */,
|
||||
);
|
||||
path = OVMandarin;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5B62A31E27AE74E400A19448 /* SFX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -439,6 +423,7 @@
|
|||
5B61B0C9280BEFD4002E3CFA /* KeyHandler_Misc.swift */,
|
||||
5B3133BE280B229700A4A505 /* KeyHandler_States.swift */,
|
||||
5B62A33727AE79CD00A19448 /* NSStringUtils.swift */,
|
||||
5BAA8FBD282CAF380066C406 /* SyllableComposer.swift */,
|
||||
5BF8423027BAA942008E7E4C /* vChewingKanjiConverter.swift */,
|
||||
);
|
||||
path = ControllerModules;
|
||||
|
@ -1103,7 +1088,6 @@
|
|||
5B62A33827AE79CD00A19448 /* NSStringUtils.swift in Sources */,
|
||||
5BA9FD0F27FEDB6B002DE248 /* suiPrefPaneGeneral.swift in Sources */,
|
||||
5BA9FD4927FEF3C9002DE248 /* Section.swift in Sources */,
|
||||
5B407153281F94E6009C24CB /* Composer.mm in Sources */,
|
||||
5BA9FD3E27FEF3C8002DE248 /* Utilities.swift in Sources */,
|
||||
5BA9FD1127FEDB6B002DE248 /* ctlPrefUI.swift in Sources */,
|
||||
5B38F59C281E2E49007D5F5D /* 2_Grid.swift in Sources */,
|
||||
|
@ -1112,12 +1096,12 @@
|
|||
5B62A33227AE792F00A19448 /* InputSourceHelper.swift in Sources */,
|
||||
5B5E535227EF261400C6AA1E /* IME.swift in Sources */,
|
||||
5B62A34927AE7CD900A19448 /* TooltipController.swift in Sources */,
|
||||
6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */,
|
||||
5B61B0CA280BEFD4002E3CFA /* KeyHandler_Misc.swift in Sources */,
|
||||
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */,
|
||||
5B38F5A0281E2E49007D5F5D /* 1_Walker.swift in Sources */,
|
||||
5B62A34827AE7CD900A19448 /* ctlCandidateVertical.swift in Sources */,
|
||||
5BA9FD4027FEF3C8002DE248 /* Localization.swift in Sources */,
|
||||
5BAA8FBE282CAF380066C406 /* SyllableComposer.swift in Sources */,
|
||||
5BA9FD1327FEDB6B002DE248 /* suiPrefPaneDictionary.swift in Sources */,
|
||||
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */,
|
||||
5BA9FD4727FEF3C9002DE248 /* PreferencesStyleController.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue