Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update romaji buffer when necessary. #65

Merged
merged 1 commit into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 9 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ Regarding pinyin input support, we only support: Hanyu Pinyin, Secondary Pinyin,

### §1. 初期化

在你的 IMKInputController (InputMethodController) 或者 KeyHandler 內初期化一份 Tekkon.Composer 注拼槽副本(這裡將該副本命名為「`_composer`」)。由於 Tekkon.Composer 的型別是 Struct 型別,所以其副本必須為變數(var),否則無法自我 mutate。
在你的 IMKInputController (InputMethodController) 或者 InputHandler 內初期化一份 Tekkon.Composer 注拼槽副本(這裡將該副本命名為「`_composer`」)。由於 Tekkon.Composer 的型別是 Struct 型別,所以其副本必須為變數(var),否則無法自我 mutate。

KeyHandler 為例:
InputHandler 為例:
```swift
class KeyHandler: NSObject {
class InputHandler: NSObject {
// 先設定好變數
var _composer: Tekkon.Composer = .init()
...
Expand All @@ -45,7 +45,7 @@ class IMKMyInputController: IMKInputController {
```


由於 Swift 會在某個大副本(KeyHandler 或者 IMKInputController 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Tekkon.Composer 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所使用的注音排列(是大千?還是倚天傳統?還是神通?等)。
由於 Swift 會在某個大副本(InputHandler 或者 IMKInputController 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Tekkon.Composer 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所使用的注音排列(是大千?還是倚天傳統?還是神通?等)。

這裡就需要在 _composer 這個副本所在的型別當中額外寫一個過程函式。

Expand Down Expand Up @@ -159,7 +159,7 @@ final class TekkonTests: XCTestCase {

#### // 2. 訊號處理

無論是 KeyHandler 還是 IMKInputController 都得要處理被傳入的 NSEvent 當中的 charCode 訊號。
無論是 InputHandler 還是 IMKInputController 都得要處理被傳入的 NSEvent 當中的 charCode 訊號。

比如 IMKInputController 內:
```swift
Expand All @@ -168,115 +168,23 @@ func handleInputText(_ inputText: String?, key keyCode: Int, modifiers flags: In
}
```

或者 KeyHandler 內:
或者 InputHandler 內:
```swift
extension KeyHandler {
func handle(
input: InputHandler,
state: InputState,
stateCallback: @escaping (InputState) -> Void,
errorCallback: @escaping (String) -> Void
) -> Bool {
extension InputHandler {
func handle(input: InputSignalProtocol) -> Bool {
let charCode: UniChar = input.charCode
...
}
```

但對注拼槽的處理都是一樣的。

這裡分享一下小麥注音輸入法與威注音輸入法在 KeyHandler 內對 _composer 的用法。
有關於威注音輸入法在 InputHandler 內對 _composer 的用法,請洽其倉庫內的 InputHandler_HandleComposition.swift 檔案

如果收到的按鍵訊號是 BackSpace 的話,可以用 _composer.doBackSpace() 來移除注拼槽內最前方的元素。

鐵恨引擎的注拼槽 Composer 型別內的函式都有對應的詳細註解說明。這裡不再贅述。



> (這裡的範例取自威注音,只用作演示用途。威注音實際的 codebase 可能會有出入。請留意這一段內的漢語註解。)
>
> (小麥注音 2.2 沒在用鐵恨引擎,而是在用 OVMandarin 引擎與 Objective-Cpp,所以語法會有一些出入。)
>
> (不是所有輸入法都有狀態管理引擎,請根據各自專案的實際情況來結合理解這段程式碼。)

```swift
// MARK: Handle BPMF Keys.

var keyConsumedByReading = false
let skipPhoneticHandling = input.isReservedKey || input.isControlHold || input.isOptionHold

// See if Phonetic reading is valid.
// 這裡 inputValidityCheck() 是讓 _composer 檢查 charCode 這個 UniChar 是否是合法的注音輸入。
// 如果是的話,就將這次傳入的這個按鍵訊號塞入 _composer 內且標記為「keyConsumedByReading」。
// 函式 _composer.receiveKey() 可以既接收 String 又接收 UniChar。
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
// compositor in other words, if we don't have a tone marker, we just
// update the composing buffer.
// 沒有調號的話,只需要 updateClientComposingBuffer() 且終止處理(return true)即可。
// 有調號的話,則不需要這樣處理,轉而繼續在此之後的處理。
let composeReading = _composer.hasToneMarker()
if !composeReading {
stateCallback(buildInputtingState())
return true
}
}

// 這裡不需要做排他性判斷。
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 "|=".
// 如果當前的按鍵是 Enter 或 Space 的話,這時就可以取出 _composer 內的注音來做檢查了。
// 來看看詞庫內到底有沒有對應的讀音索引。這裡用了類似「|=」的判斷處理方式。
composeReading = composeReading || (!_composer.isEmpty && (input.isSpace || input.isEnter))
if composeReading { // 符合按鍵組合條件
if input.isSpace && !_composer.hasToneMarker() {
_composer.receiveKey(fromString: " ") // 補上空格,否則倚天忘形與許氏排列某些音無法響應不了陰平聲調。
// 小麥注音因為使用 OVMandarin 而不是鐵恨引擎,所以不需要這樣補。但鐵恨引擎對所有聲調一視同仁。
}
let reading = _composer.getComposition() // 拿取用來進行索引檢索用的注音
// 如果輸入法的辭典索引是漢語拼音的話,要注意上一行拿到的內容得是漢語拼音。

// See whether we have a unigram for this...
// 向語言模型詢問是否有對應的記錄
if !ifLangModelHasUnigrams(forKey: reading) { // 如果沒有的話
vCLog("B49C0979:語彙庫內無「\(reading)」的匹配記錄。")
errorCallback("114514") // 向狀態管理引擎回呼一個錯誤狀態
_composer.clear() // 清空注拼槽的內容
// 根據「天權星引擎 (威注音) 或 Gramambular (小麥) 的組字器是否為空」來判定回呼哪一種狀態
stateCallback(
(getCompositorLength() == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState())
return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了
}

// ... and insert it into the grid...
// 將該讀音插入至天權星(或 Gramambular)組字器內的軌格當中
insertReadingToCompositorAtCursor(reading: reading)

// ... then walk the grid...
// 讓組字器反爬軌格
let poppedText = popOverflowComposingTextAndWalk()

// ... get and tweak override model suggestion if possible...
// 看看半衰記憶模組是否會對目前的狀態給出自動選字建議
dealWithOverrideModelSuggestions()

// ... then update the text.
// 之後就是更新組字區了。先清空注拼槽的內容。
_composer.clear()
// 再以回呼組字狀態的方式來執行updateClientComposingBuffer()
let inputting = buildInputtingState()
inputting.poppedText = poppedText
stateCallback(inputting)

return true // 向 IMK 報告說這個按鍵訊號已經被輸入法攔截處理了
}
```

## 著作權 (Credits)

- (c) 2022 and onwards The vChewing Project (MIT-NTL License).
Expand Down
10 changes: 8 additions & 2 deletions Sources/Tekkon/Tekkon_SyllableComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension Tekkon {
/// 聲調。
public var intonation: Phonabet = ""

/// 為拉丁字母專用的組音區
/// 拼音組音區
public var romajiBuffer: String = ""

/// 注音排列種類。預設情況下是大千排列(Windows / macOS 預設注音排列)。
Expand Down Expand Up @@ -158,6 +158,11 @@ extension Tekkon {
return false
}

/// 按需更新拼音組音區的內容顯示。
mutating func updateRomajiBuffer() {
romajiBuffer = Tekkon.cnvPhonaToHanyuPinyin(target: consonant.value + semivowel.value + vowel.value)
}

/// 接受傳入的按鍵訊號時的處理,處理對象為 String。
/// 另有同名函式可處理 UniChar 訊號。
///
Expand Down Expand Up @@ -232,6 +237,7 @@ extension Tekkon {
case .intonation: intonation = thePhone
default: break
}
updateRomajiBuffer()
}

/// 處理一連串的按鍵輸入。
Expand Down Expand Up @@ -317,7 +323,7 @@ extension Tekkon {
/// 用來檢測是否有調號的函式,預設情況下不判定聲調以外的內容的存無。
/// - Parameters:
/// - withNothingElse: 追加判定「槽內是否僅有調號」。
public func hasToneMarker(withNothingElse: Bool = false) -> Bool {
public func hasIntonation(withNothingElse: Bool = false) -> Bool {
if !withNothingElse {
return !intonation.isEmpty
}
Expand Down
48 changes: 24 additions & 24 deletions Tests/TekkonTests/BasicTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ final class TekkonTestsBasic: XCTestCase {
composer.receiveKey(fromString: "l") // ㄠ

// Testing missing tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(!toneMarkerIndicator)

composer.receiveKey(fromString: "3") // 上聲
Expand All @@ -82,17 +82,17 @@ final class TekkonTestsBasic: XCTestCase {
XCTAssertEqual(composer.getComposition(isTextBookStyle: true), "˙ㄉㄧㄠ")

// Testing having tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(toneMarkerIndicator)

// Testing having not-only tone markers
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(!toneMarkerIndicator)

// Testing having only tone markers
composer.clear()
composer.receiveKey(fromString: "3") // 上聲
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(toneMarkerIndicator)

// Testing auto phonabet combination fixing process.
Expand Down Expand Up @@ -189,7 +189,7 @@ final class TekkonTestsPinyin: XCTestCase {
composer.receiveKey(fromString: "o")

// Testing missing tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(!toneMarkerIndicator)

composer.receiveKey(fromString: "3") // 上聲
Expand All @@ -210,17 +210,17 @@ final class TekkonTestsPinyin: XCTestCase {
XCTAssertEqual(composer.getComposition(isTextBookStyle: true), "˙ㄉㄧㄠ")

// Testing having tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(toneMarkerIndicator)

// Testing having not-only tone markers
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(!toneMarkerIndicator)

// Testing having only tone markers
composer.clear()
composer.receiveKey(fromString: "3") // 上聲
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(toneMarkerIndicator)
}

Expand All @@ -237,7 +237,7 @@ final class TekkonTestsPinyin: XCTestCase {
composer.receiveKey(fromString: "g")

// Testing missing tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(!toneMarkerIndicator)

composer.receiveKey(fromString: "2") // 陽平
Expand All @@ -258,17 +258,17 @@ final class TekkonTestsPinyin: XCTestCase {
XCTAssertEqual(composer.getComposition(isTextBookStyle: true), "˙ㄑㄩㄥ")

// Testing having tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(toneMarkerIndicator)

// Testing having not-only tone markers
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(!toneMarkerIndicator)

// Testing having only tone markers
composer.clear()
composer.receiveKey(fromString: "3") // 上聲
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(toneMarkerIndicator)
}

Expand All @@ -285,7 +285,7 @@ final class TekkonTestsPinyin: XCTestCase {
composer.receiveKey(fromString: "g")

// Testing missing tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(!toneMarkerIndicator)

composer.receiveKey(fromString: "2") // 陽平
Expand All @@ -306,17 +306,17 @@ final class TekkonTestsPinyin: XCTestCase {
XCTAssertEqual(composer.getComposition(isTextBookStyle: true), "˙ㄑㄩㄥ")

// Testing having tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(toneMarkerIndicator)

// Testing having not-only tone markers
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(!toneMarkerIndicator)

// Testing having only tone markers
composer.clear()
composer.receiveKey(fromString: "3") // 上聲
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(toneMarkerIndicator)
}

Expand All @@ -333,7 +333,7 @@ final class TekkonTestsPinyin: XCTestCase {
composer.receiveKey(fromString: "g")

// Testing missing tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(!toneMarkerIndicator)

composer.receiveKey(fromString: "2") // 陽平
Expand All @@ -354,17 +354,17 @@ final class TekkonTestsPinyin: XCTestCase {
XCTAssertEqual(composer.getComposition(isTextBookStyle: true), "˙ㄑㄩㄥ")

// Testing having tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(toneMarkerIndicator)

// Testing having not-only tone markers
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(!toneMarkerIndicator)

// Testing having only tone markers
composer.clear()
composer.receiveKey(fromString: "3") // 上聲
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(toneMarkerIndicator)
}

Expand All @@ -380,7 +380,7 @@ final class TekkonTestsPinyin: XCTestCase {
composer.receiveKey(fromString: "g")

// Testing missing tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(!toneMarkerIndicator)

composer.receiveKey(fromString: "2") // 陽平
Expand All @@ -401,17 +401,17 @@ final class TekkonTestsPinyin: XCTestCase {
XCTAssertEqual(composer.getComposition(isTextBookStyle: true), "˙ㄑㄩㄥ")

// Testing having tone markers
toneMarkerIndicator = composer.hasToneMarker()
toneMarkerIndicator = composer.hasIntonation()
XCTAssert(toneMarkerIndicator)

// Testing having not-only tone markers
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(!toneMarkerIndicator)

// Testing having only tone markers
composer.clear()
composer.receiveKey(fromString: "3") // 上聲
toneMarkerIndicator = composer.hasToneMarker(withNothingElse: true)
toneMarkerIndicator = composer.hasIntonation(withNothingElse: true)
XCTAssert(toneMarkerIndicator)
}
}