Removing everything.

This commit is contained in:
ShikiSuen 2022-12-17 14:16:28 +08:00
parent 12abcd02a9
commit ce6e8453e7
178 changed files with 0 additions and 333527 deletions

View File

@ -1,55 +0,0 @@
---
name: 功能問題回報
about: 請告訴我們您遇到什麼問題
title: "[功能問題回報]"
labels: ""
assignees: ""
---
在您回報問題之前,建議也可以先參考 Wiki 上的 [常見問題](https://github.com/openvanilla/McBopomofo/wiki/%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C) 與 [使用手冊](https://github.com/openvanilla/McBopomofo/wiki/%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8A)
**摘要**
請簡單說明您遇到了什麼問題。這份表格雖然是中文的(我們相信注音輸入法的用戶應該都能看懂中文),但開發團隊可以使用中英文溝通,您也可以使用英文回報問題。
**快速分類**
請問您遇到的是:
- [ ] 當機或應用程式閃退
- [ ] 輸入法叫不出來,或無法正確輸出中文
- [ ] 無法切換到特定功能
- [ ] 輸入法效能有問題(執行速度太慢/電腦發燙)
- [ ] UI 顯示不正常
- [ ] 其他
**問題發生步驟**
請問您是怎麼遇到這個問題的?像是:
1. 打開 xxx app
2. 開始在 xxx 區域打字
3. 切換到某種設定..
**預期正常狀況**
您覺得這是不正常的狀況,那您覺得正常結果應該是…?
**螢幕截圖或螢幕錄製**
如果您能夠提供像螢幕截圖或螢幕錄製供大家參考,我們可以從畫面中,看出更多只從文字內容無法了解的線索。
**電腦環境**
請問您在怎樣的環境遇到這個問題?
- 小麥注音版本:(請填寫版本)
- 鍵盤樣式:
- [x] 標準
- [ ] 倚天
- [ ] 許氏
- [ ] 倚天 26 鍵
- [ ] 漢語拼音
- [ ] IBM
- macOS 版本:(請填寫版本)
- 在哪個應用程式中打字遇到問題:(應用程式名稱/版本)
- 電腦機種:(筆電或桌機,使用 Intel 或 Apple Silicon CPU
- 其他特殊設備:(請填寫是否有外接螢幕、特製鍵盤如非標準美式鍵盤等)
- 特殊設定:(是否原本正常,改了某個系統設定後就遇到問題)
**其他**
其他你覺得問題發生的疑點,或其他想跟小麥開發者說的話。

View File

@ -1,24 +0,0 @@
---
name: 功能建議
about: 告訴我們您還有什麼沒被滿足的需求
title: "[功能建議]"
labels: ""
assignees: ""
---
**痛點**
請告訴我們為什麼目前小麥注音沒辦法解決您的問題。您可以講一個讓大家能感同身受的小故事,像是
- 我從 xx 年前開始,使用了 xx 輸入法,我相當依賴其中一項功能,但是小麥沒有...
- 一直以來我在輸入 xx 這類內容的時候,都很花時間,我想要有更有效率的辦法…
**功能說明**
請大致說明您想要的功能,最後會看起來會像是怎樣。當然最簡單的說明可能會像是「跟 xxx 輸入法一樣」,但請您諒解,不是什麼人都用過你之前用過的那套輸入法,其他人想了解「跟 xxx 輸入法一樣」,首先還得找到那套輸入法,而且有些年代久遠的輸入法還找不到,這樣雙方的認知不同,會大幅降低溝通效率。
比方說,您想要某種符號表設計,或許可以提供我們一個草圖,或是其他你喜歡的輸入法的符號表的截圖。遇到某些更複雜的情境,您也可以考慮錄製螢幕畫面供大家參考。
**替代方案**
請問,您目前是用什麼方法,可以做到就算沒有這項功能,也可以達最後目的。或著,如果這項功能開發與其他功能衝突,您起碼可以接受什麼方案?
**其他**
其他你想跟小麥注音開發團隊說的話。

View File

@ -1,19 +0,0 @@
---
name: 詞庫問題回報
about: 請告訴我們詞庫的相關問題
title: "[詞庫問題回報]"
labels: ""
assignees: ""
---
**分類**
- [ ] 錯字或錯誤讀音
- [ ] 缺字/詞
- [ ] 其他
**說明**
請說明是哪個字寫錯或是標音錯誤,以及正確的寫法與念法。
**相關資料**
像是字典的連結等。

View File

@ -1,58 +0,0 @@
name: Build
on: [push]
jobs:
build:
name: Build and Test with Xcode 12.4
runs-on: macOS-latest
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
steps:
- uses: actions/checkout@v1
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '12.4'
- name: Build McBopomofoLMLibTest
run: cmake -S . -B build
working-directory: Source/Engine
- name: Run McBopomofoLMLibTest
run: make runTest
working-directory: Source/Engine/build
- name: Build MandarinTest
run: cmake -S . -B build
working-directory: Source/Engine/Mandarin
- name: Run MandarinTest
run: make runTest
working-directory: Source/Engine/Mandarin/build
- name: Build GramambularTest
run: cmake -S . -B build
working-directory: Source/Engine/Gramambular
- name: Run GramambularTest
run: make runTest
working-directory: Source/Engine/Gramambular/build
- name: Test McBopomofo App Bundle
run: xcodebuild -scheme McBopomofo -configuration Debug test
- name: Test CandidateUI
run: swift test
working-directory: Packages/CandidateUI
- name: Test OpenCCBridge
run: swift test
working-directory: Packages/OpenCCBridge
- name: Test VXHanConvert
run: swift test
working-directory: Packages/VXHanConvert
- name: Test NSStringUtils
run: swift test
working-directory: Packages/NSStringUtils
- name: Clean McBopomofo for testing
run: xcodebuild -scheme McBopomofo -configuration Debug clean
- name: Test McBopomofo
run: xcodebuild -scheme McBopomofo -configuration Debug test
- name: Clean McBopomofo
run: xcodebuild -scheme McBopomofo -configuration Release clean
- name: Clean McBopomofoInstaller
run: xcodebuild -scheme McBopomofoInstaller -configuration Release clean
- name: Build McBopomofo
run: xcodebuild -scheme McBopomofo -configuration Release build
- name: Build McBopomofoInstaller
run: xcodebuild -scheme McBopomofoInstaller -configuration Release build

View File

@ -1,58 +0,0 @@
name: Build
on: [push]
jobs:
build:
name: Build and Test with Latest Xcode
runs-on: macOS-latest
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
steps:
- uses: actions/checkout@v1
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Build McBopomofoLMLibTest
run: cmake -S . -B build
working-directory: Source/Engine
- name: Run McBopomofoLMLibTest
run: make runTest
working-directory: Source/Engine/build
- name: Build MandarinTest
run: cmake -S . -B build
working-directory: Source/Engine/Mandarin
- name: Run MandarinTest
run: make runTest
working-directory: Source/Engine/Mandarin/build
- name: Build GramambularTest
run: cmake -S . -B build
working-directory: Source/Engine/Gramambular
- name: Run GramambularTest
run: make runTest
working-directory: Source/Engine/Gramambular/build
- name: Test McBopomofo App Bundle
run: xcodebuild -scheme McBopomofo -configuration Debug test
- name: Test CandidateUI
run: swift test
working-directory: Packages/CandidateUI
- name: Test OpenCCBridge
run: swift test
working-directory: Packages/OpenCCBridge
- name: Test VXHanConvert
run: swift test
working-directory: Packages/VXHanConvert
- name: Test NSStringUtils
run: swift test
working-directory: Packages/NSStringUtils
- name: Clean McBopomofo for testing
run: xcodebuild -scheme McBopomofo -configuration Debug clean
- name: Test McBopomofo
run: xcodebuild -scheme McBopomofo -configuration Debug test
- name: Clean McBopomofo
run: xcodebuild -scheme McBopomofo -configuration Release clean
- name: Clean McBopomofoInstaller
run: xcodebuild -scheme McBopomofoInstaller -configuration Release clean
- name: Build McBopomofo
run: xcodebuild -scheme McBopomofo -configuration Release build
- name: Build McBopomofoInstaller
run: xcodebuild -scheme McBopomofoInstaller -configuration Release build

17
.gitignore vendored
View File

@ -1,17 +0,0 @@
build
.build
*.pbxuser
*.mode1v3
*.tm_build_errors
*.dmg
.DS_Store
project.xcworkspace
xcuserdata
# Credits.rtf
# the executable we used to count the occurance of a string in a file
# that can be built by make -C Source/Data/bin/C_Version
# C_count.occ.exe
.idea
.swiftpm
.vscode
__pycache__

View File

@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
ovadmin@openvanilla.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2011-2022 Mengjuei Hsieh et al.
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6A0D4EA115FC0D2D00ABF4B3"
BuildableName = "McBopomofo.app"
BlueprintName = "McBopomofo"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D485D3B52796A8A000657FF3"
BuildableName = "McBopomofoTests.xctest"
BlueprintName = "McBopomofoTests"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6A0D4EA115FC0D2D00ABF4B3"
BuildableName = "McBopomofo.app"
BlueprintName = "McBopomofo"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6A0D4EA115FC0D2D00ABF4B3"
BuildableName = "McBopomofo.app"
BlueprintName = "McBopomofo"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6ACA41CA15FC1D7500935EF6"
BuildableName = "McBopomofoInstaller.app"
BlueprintName = "McBopomofoInstaller"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6ACA41CA15FC1D7500935EF6"
BuildableName = "McBopomofoInstaller.app"
BlueprintName = "McBopomofoInstaller"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6ACA41CA15FC1D7500935EF6"
BuildableName = "McBopomofoInstaller.app"
BlueprintName = "McBopomofoInstaller"
ReferencedContainer = "container:McBopomofo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

File diff suppressed because it is too large Load Diff

View File

@ -1,309 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import McBopomofo
class KeyHandlerPlainBopomofoTests: XCTestCase {
var savedKeyboardLayout: Int = 0
var handler = KeyHandler()
override func setUpWithError() throws {
LanguageModelManager.loadDataModels()
handler = KeyHandler()
handler.inputMode = .plainBopomofo
savedKeyboardLayout = Preferences.keyboardLayout
// Punctuation-related tests only work when the layout is Standard.
Preferences.keyboardLayout = KeyboardLayout.standard.rawValue
}
override func tearDownWithError() throws {
Preferences.keyboardLayout = savedKeyboardLayout
}
// Regression test for #292.
func testUppercaseLetterWhenEmpty() {
let input = KeyHandlerInput(inputText: "A", keyCode: KeyCode.enter.rawValue, charCode: charCode("A"), flags: [], isVerticalMode: false)
var state: InputState = InputState.Empty()
let result = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertFalse(result)
}
func testPunctuationTable() {
let input = KeyHandlerInput(inputText: "`", keyCode: 0, charCode: charCode("`"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate {
XCTAssertTrue(state.candidates.contains(""))
}
}
func testPunctuationComma() {
let enabled = Preferences.halfWidthPunctuationEnabled
Preferences.halfWidthPunctuationEnabled = false
let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate {
XCTAssertEqual(state.composingBuffer, "")
}
Preferences.halfWidthPunctuationEnabled = enabled
}
func testPunctuationPeriod() {
let enabled = Preferences.halfWidthPunctuationEnabled
Preferences.halfWidthPunctuationEnabled = false
let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate {
XCTAssertEqual(state.composingBuffer, "")
}
Preferences.halfWidthPunctuationEnabled = enabled
}
func testHalfPunctuationPeriod() {
let enabled = Preferences.halfWidthPunctuationEnabled
Preferences.halfWidthPunctuationEnabled = true
let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate {
XCTAssertEqual(state.composingBuffer, ".")
}
Preferences.halfWidthPunctuationEnabled = enabled
}
func testControlPunctuationPeriod() {
let input = KeyHandlerInput(inputText: ".", keyCode: 0, charCode: charCode("."), flags: [.shift, .control], isVerticalMode: false)
var state: InputState = InputState.Empty()
var count = 0
handler.handle(input: input, state: state) { newState in
if count == 0 {
state = newState
}
count += 1
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testEnterWithReading() {
let input = KeyHandlerInput(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
let enter = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 13, flags: [], isVerticalMode: false)
var count = 0
handler.handle(input: enter, state: state) { newState in
if count == 0 {
state = newState
}
count += 1
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testInputNe() {
let input = KeyHandlerInput(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift, isVerticalMode: false)
var state: InputState = InputState.Empty()
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testInputNi() {
var state: InputState = InputState.Empty()
let keys = Array("su").map {
String($0)
}
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "ㄋㄧ")
}
}
func testInputNi3() {
var state: InputState = InputState.Empty()
let keys = Array("su3").map {
String($0)
}
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)")
if let state = state as? InputState.ChoosingCandidate {
XCTAssertTrue(state.candidates.contains(""))
}
}
func testCancelCandidateUsingDelete() {
var state: InputState = InputState.Empty()
let keys = Array("su3").map {
String($0)
}
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)")
}
func testCancelCandidateUsingEsc() {
var state: InputState = InputState.Empty()
let keys = Array("su3").map {
String($0)
}
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)")
}
func testAssociatedPhrases() {
let enabled = Preferences.associatedPhrasesEnabled
Preferences.associatedPhrasesEnabled = true
var state: InputState = InputState.Empty()
let keys = Array("aul ").map {
String($0)
}
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)")
if let state = state as? InputState.AssociatedPhrases {
XCTAssertTrue(state.candidates.contains(""))
}
Preferences.associatedPhrasesEnabled = enabled
}
func testNoAssociatedPhrases() {
let enabled = Preferences.associatedPhrasesEnabled
Preferences.associatedPhrasesEnabled = false
var state: InputState = InputState.Empty()
let keys = Array("aul ").map {
String($0)
}
for key in keys {
let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false)
handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Empty, "\(state)")
Preferences.associatedPhrasesEnabled = enabled
}
}

View File

@ -1,305 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import McBopomofo
class PreferencesTests: XCTestCase {
func reset() {
Preferences.allKeys.forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
}
func makeSnapshot() -> [String: Any] {
var dict = [String: Any]()
Preferences.allKeys.forEach {
dict[$0] = UserDefaults.standard.object(forKey: $0)
}
return dict
}
func restore(from snapshot:[String: Any]) {
Preferences.allKeys.forEach {
UserDefaults.standard.set(snapshot[$0], forKey: $0)
}
}
var snapshot: [String: Any]?
override func setUpWithError() throws {
snapshot = makeSnapshot()
reset()
}
override func tearDownWithError() throws {
if let snapshot = snapshot {
restore(from: snapshot)
}
}
func testKeyboardLayout() {
XCTAssert(Preferences.keyboardLayout == 0)
Preferences.keyboardLayout = 1
XCTAssert(Preferences.keyboardLayout == 1)
}
func testKeyboardLayoutName() {
XCTAssert(Preferences.keyboardLayoutName == "Standard")
Preferences.keyboardLayout = 1
XCTAssert(Preferences.keyboardLayoutName == "ETen")
}
func testBasisKeyboardLayoutPreferenceKey() {
XCTAssert(Preferences.basisKeyboardLayout == "com.apple.keylayout.US")
Preferences.basisKeyboardLayout = "com.apple.keylayout.ABC"
XCTAssert(Preferences.basisKeyboardLayout == "com.apple.keylayout.ABC")
}
func testFunctionKeyboardLayout() {
XCTAssert(Preferences.functionKeyboardLayout == "com.apple.keylayout.US")
Preferences.functionKeyboardLayout = "com.apple.keylayout.ABC"
XCTAssert(Preferences.functionKeyboardLayout == "com.apple.keylayout.ABC")
}
func testFunctionKeyKeyboardLayoutOverrideIncludeShiftKey() {
XCTAssert(Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey == false)
Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey = true
XCTAssert(Preferences.functionKeyKeyboardLayoutOverrideIncludeShiftKey == true)
}
func testCandidateTextSize() {
XCTAssert(Preferences.candidateListTextSize == 16)
Preferences.candidateListTextSize = 18
XCTAssert(Preferences.candidateListTextSize == 18)
Preferences.candidateListTextSize = 11
XCTAssert(Preferences.candidateListTextSize == 12)
Preferences.candidateListTextSize = 197
XCTAssert(Preferences.candidateListTextSize == 196)
Preferences.candidateListTextSize = 12
XCTAssert(Preferences.candidateListTextSize == 12)
Preferences.candidateListTextSize = 196
XCTAssert(Preferences.candidateListTextSize == 196)
Preferences.candidateListTextSize = 13
XCTAssert(Preferences.candidateListTextSize == 13)
Preferences.candidateListTextSize = 195
XCTAssert(Preferences.candidateListTextSize == 195)
}
func testSelectPhraseAfterCursorAsCandidate() {
XCTAssert(Preferences.selectPhraseAfterCursorAsCandidate == false)
Preferences.selectPhraseAfterCursorAsCandidate = true
XCTAssert(Preferences.selectPhraseAfterCursorAsCandidate == true)
}
func testUseHorizontalCandidateList() {
XCTAssert(Preferences.useHorizontalCandidateList == false)
Preferences.useHorizontalCandidateList = true
XCTAssert(Preferences.useHorizontalCandidateList == true)
}
func testComposingBufferSize() {
XCTAssert(Preferences.composingBufferSize == 10)
Preferences.composingBufferSize = 4
XCTAssert(Preferences.composingBufferSize == 4)
Preferences.composingBufferSize = 20
XCTAssert(Preferences.composingBufferSize == 20)
Preferences.composingBufferSize = 3
XCTAssert(Preferences.composingBufferSize == 4)
Preferences.composingBufferSize = 21
XCTAssert(Preferences.composingBufferSize == 20)
Preferences.composingBufferSize = 5
XCTAssert(Preferences.composingBufferSize == 5)
Preferences.composingBufferSize = 19
XCTAssert(Preferences.composingBufferSize == 19)
}
func testChooseCandidateUsingSpace() {
XCTAssert(Preferences.chooseCandidateUsingSpace == true)
Preferences.chooseCandidateUsingSpace = false
XCTAssert(Preferences.chooseCandidateUsingSpace == false)
}
func testChineseConversionEnabled() {
XCTAssert(Preferences.chineseConversionEnabled == false)
Preferences.chineseConversionEnabled = true
XCTAssert(Preferences.chineseConversionEnabled == true)
_ = Preferences.toggleChineseConversionEnabled()
XCTAssert(Preferences.chineseConversionEnabled == false)
}
func testHalfWidthPunctuationEnabled() {
XCTAssert(Preferences.halfWidthPunctuationEnabled == false)
Preferences.halfWidthPunctuationEnabled = true
XCTAssert(Preferences.halfWidthPunctuationEnabled == true)
_ = Preferences.toggleHalfWidthPunctuationEnabled()
XCTAssert(Preferences.halfWidthPunctuationEnabled == false)
}
func testEscToCleanInputBuffer() {
XCTAssert(Preferences.escToCleanInputBuffer == false)
Preferences.escToCleanInputBuffer = true
XCTAssert(Preferences.escToCleanInputBuffer == true)
}
func testCandidateTextFontName() {
XCTAssert(Preferences.candidateTextFontName == nil)
Preferences.candidateTextFontName = "Helvetica"
XCTAssert(Preferences.candidateTextFontName == "Helvetica")
}
func testCandidateKeyLabelFontName() {
XCTAssert(Preferences.candidateKeyLabelFontName == nil)
Preferences.candidateKeyLabelFontName = "Helvetica"
XCTAssert(Preferences.candidateKeyLabelFontName == "Helvetica")
}
func testCandidateKeys() {
XCTAssert(Preferences.candidateKeys == Preferences.defaultCandidateKeys)
Preferences.candidateKeys = "abcd"
XCTAssert(Preferences.candidateKeys == "abcd")
}
func testPhraseReplacementEnabledKey() {
XCTAssert(Preferences.phraseReplacementEnabled == false)
Preferences.phraseReplacementEnabled = true
XCTAssert(Preferences.phraseReplacementEnabled == true)
}
func testChineseConversionEngine() {
XCTAssert(Preferences.chineseConversionEngine == 0)
Preferences.chineseConversionEngine = 1
XCTAssert(Preferences.chineseConversionEngine == 1)
}
func testChineseConversionStyle() {
XCTAssert(Preferences.chineseConversionStyle == 0)
Preferences.chineseConversionStyle = 1
XCTAssert(Preferences.chineseConversionStyle == 1)
}
}
class CandidateKeyValidationTests: XCTestCase {
func testEmpty() {
do {
try Preferences.validate(candidateKeys: "")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.empty) {
} catch {
XCTFail("exception not thrown")
}
}
func testSpaces() {
do {
try Preferences.validate(candidateKeys: " ")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.empty) {
} catch {
XCTFail("exception not thrown")
}
}
func testInvalidKeys() {
do {
try Preferences.validate(candidateKeys: "中文字元")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.invalidCharacters) {
} catch {
XCTFail("exception not thrown")
}
}
func testInvalidLatinLetters() {
do {
try Preferences.validate(candidateKeys: "üåçøöacpo")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.invalidCharacters) {
} catch {
XCTFail("exception not thrown")
}
}
func testSpaceInBetween() {
do {
try Preferences.validate(candidateKeys: "1 2 3 4")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.containSpace) {
} catch {
XCTFail("exception not thrown")
}
}
func testDuplicatedKeys() {
do {
try Preferences.validate(candidateKeys: "aabbccdd")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.duplicatedCharacters) {
} catch {
XCTFail("exception not thrown")
}
}
func testTooShort1() {
do {
try Preferences.validate(candidateKeys: "abc")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.tooShort) {
} catch {
XCTFail("exception not thrown")
}
}
func testTooShort2() {
do {
try Preferences.validate(candidateKeys: "abcd")
} catch {
XCTFail("Should be safe")
}
}
func testTooLong1() {
do {
try Preferences.validate(candidateKeys: "qwertyuiopasdfgh")
XCTFail("exception not thrown")
} catch(Preferences.CandidateKeyError.tooLong) {
} catch {
XCTFail("exception not thrown")
}
}
func testTooLong2() {
do {
try Preferences.validate(candidateKeys: "qwertyuiopasdfg")
}
catch {
XCTFail("Should be safe")
}
}
}

View File

@ -1,41 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import McBopomofo
class VersionUpdateApiTests: XCTestCase {
func testFetchVersionUpdateInfo() {
let exp = self.expectation(description: "wait for 3 seconds")
_ = VersionUpdateApi.check(forced: true) { result in
exp.fulfill()
switch result {
case .success(_):
break
case .failure(let error):
XCTFail(error.localizedDescription)
}
}
self.wait(for: [exp], timeout: 20.0)
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,29 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CandidateUI",
platforms: [.macOS(.v10_10)],
products: [
.library(
name: "CandidateUI",
targets: ["CandidateUI"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "CandidateUI",
dependencies: []),
.testTarget(
name: "CandidateUITests",
dependencies: ["CandidateUI"]),
]
)

View File

@ -1,3 +0,0 @@
# CandidateUI
The candidate window for McBopomofo.

View File

@ -1,169 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
@objc(VTCandidateKeyLabel)
public class CandidateKeyLabel: NSObject {
@objc public private(set) var key: String
@objc public private(set) var displayedText: String
public init(key: String, displayedText: String) {
self.key = key
self.displayedText = displayedText
super.init()
}
}
@objc(VTCandidateControllerDelegate)
public protocol CandidateControllerDelegate: AnyObject {
func candidateCountForController(_ controller: CandidateController) -> UInt
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt)
}
@objc(VTCandidateController)
public class CandidateController: NSWindowController {
@objc public weak var delegate: CandidateControllerDelegate? {
didSet {
reloadData()
}
}
@objc public var selectedCandidateIndex: UInt = UInt.max
@objc public var visible: Bool = false {
didSet {
NSObject.cancelPreviousPerformRequests(withTarget: self)
if visible {
window?.perform(#selector(NSWindow.orderFront(_:)), with: self, afterDelay: 0.0)
} else {
window?.perform(#selector(NSWindow.orderOut(_:)), with: self, afterDelay: 0.0)
}
}
}
@objc public var windowTopLeftPoint: NSPoint {
get {
guard let frameRect = window?.frame else {
return NSPoint.zero
}
return NSPoint(x: frameRect.minX, y: frameRect.maxY)
}
set {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
self.set(windowTopLeftPoint: newValue, bottomOutOfScreenAdjustmentHeight: 0)
}
}
}
@objc public var keyLabels: [CandidateKeyLabel] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
@objc public var keyLabelFont: NSFont = NSFont.systemFont(ofSize: 14)
@objc public var candidateFont: NSFont = NSFont.systemFont(ofSize: 18)
@objc public var tooltip: String = ""
@objc public func reloadData() {
}
@objc public func showNextPage() -> Bool {
false
}
@objc public func showPreviousPage() -> Bool {
false
}
@objc public func highlightNextCandidate() -> Bool {
false
}
@objc public func highlightPreviousCandidate() -> Bool {
false
}
@objc public func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
UInt.max
}
/// Sets the location of the candidate window.
///
/// Please note that the method has side effects that modifies
/// `windowTopLeftPoint` to make the candidate window to stay in at least
/// in a screen.
///
/// - Parameters:
/// - windowTopLeftPoint: The given location.
/// - height: The height that helps the window not to be out of the bottom
/// of a screen.
@objc(setWindowTopLeftPoint:bottomOutOfScreenAdjustmentHeight:)
public func set(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
self.doSet(windowTopLeftPoint: windowTopLeftPoint, bottomOutOfScreenAdjustmentHeight: height)
}
}
func doSet(windowTopLeftPoint: NSPoint, bottomOutOfScreenAdjustmentHeight height: CGFloat) {
var adjustedPoint = windowTopLeftPoint
var adjustedHeight = height
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
for screen in NSScreen.screens {
let frame = screen.visibleFrame
if windowTopLeftPoint.x >= frame.minX &&
windowTopLeftPoint.x <= frame.maxX &&
windowTopLeftPoint.y >= frame.minY &&
windowTopLeftPoint.y <= frame.maxY {
screenFrame = frame
break
}
}
if adjustedHeight > screenFrame.size.height / 2.0 {
adjustedHeight = 0.0
}
let windowSize = window?.frame.size ?? NSSize.zero
// bottom beneath the screen?
if adjustedPoint.y - windowSize.height < screenFrame.minY {
adjustedPoint.y = windowTopLeftPoint.y + adjustedHeight + windowSize.height
}
// top over the screen?
if adjustedPoint.y >= screenFrame.maxY {
adjustedPoint.y = screenFrame.maxY - 1.0
}
// right
if adjustedPoint.x + windowSize.width >= screenFrame.maxX {
adjustedPoint.x = screenFrame.maxX - windowSize.width
}
// left
if adjustedPoint.x < screenFrame.minX {
adjustedPoint.x = screenFrame.minX
}
window?.setFrameTopLeftPoint(adjustedPoint)
}
}

View File

@ -1,435 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
fileprivate class HorizontalCandidateView: NSView {
var highlightedIndex: UInt = 0
var action: Selector?
weak var target: AnyObject?
private var keyLabels: [String] = []
private var displayedCandidates: [String] = []
private var keyLabelHeight: CGFloat = 0
private var candidateTextHeight: CGFloat = 0
private var cellPadding: CGFloat = 0
private var keyLabelAttrDict: [NSAttributedString.Key: AnyObject] = [:]
private var candidateAttrDict: [NSAttributedString.Key: AnyObject] = [:]
private var elementWidths: [CGFloat] = []
private var trackingHighlightedIndex: UInt = UInt.max
private let tooltipPadding: CGFloat = 2.0
private var tooltipSize: NSSize = NSSize.zero
override var toolTip: String? {
didSet {
if let toolTip = toolTip, !toolTip.isEmpty {
let baseSize = NSSize(width: 10240.0, height: 10240.0)
var tooltipRect = (toolTip as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: keyLabelAttrDict)
tooltipRect.size.height += tooltipPadding * 2
tooltipRect.size.width += tooltipPadding * 2
self.tooltipSize = tooltipRect.size
} else {
self.tooltipSize = NSSize.zero
}
}
}
override var isFlipped: Bool {
true
}
var sizeForView: NSSize {
var result = NSSize.zero
if !elementWidths.isEmpty {
result.width = elementWidths.reduce(0, +)
result.width += CGFloat(elementWidths.count)
result.height = keyLabelHeight + candidateTextHeight + 1.0
}
result.height += tooltipSize.height
result.width = max(tooltipSize.width, result.width)
return result
}
func set(keyLabels labels: [String], displayedCandidates candidates: [String]) {
let count = min(labels.count, candidates.count)
keyLabels = Array(labels[0..<count])
displayedCandidates = Array(candidates[0..<count])
var newWidths = [CGFloat]()
let baseSize = NSSize(width: 10240.0, height: 10240.0)
for index in 0..<count {
let labelRect = (keyLabels[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: keyLabelAttrDict)
let candidateRect = (displayedCandidates[index] as NSString).boundingRect(with: baseSize, options: .usesLineFragmentOrigin, attributes: candidateAttrDict)
let cellWidth = max(candidateTextHeight,
max(labelRect.size.width, candidateRect.size.width)) + cellPadding;
newWidths.append(cellWidth)
}
elementWidths = newWidths
}
func set(keyLabelFont labelFont: NSFont, candidateFont: NSFont) {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
keyLabelAttrDict = [.font: labelFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.black]
candidateAttrDict = [.font: candidateFont,
.paragraphStyle: paraStyle,
.foregroundColor: NSColor.textColor]
let labelFontSize = labelFont.pointSize
let candidateFontSize = candidateFont.pointSize
let biggestSize = max(labelFontSize, candidateFontSize)
keyLabelHeight = ceil(labelFontSize * 1.20)
candidateTextHeight = ceil(candidateFontSize * 1.20)
cellPadding = ceil(biggestSize / 2.0)
}
override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.controlBackgroundColor
let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0)
let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0)
let bounds = self.bounds
backgroundColor.setFill()
NSBezierPath.fill(bounds)
if #available(macOS 10.14, *) {
NSColor.separatorColor.setStroke()
} else {
NSColor.darkGray.setStroke()
}
if let toolTip = toolTip {
lightGray.setFill()
NSBezierPath.fill(NSMakeRect(0, 0, bounds.width, tooltipSize.height))
let tooltipFrame = NSRect(x: 0, y: 0, width: tooltipSize.width, height: tooltipSize.height)
(toolTip as NSString).draw(in: tooltipFrame, withAttributes: keyLabelAttrDict)
NSBezierPath.strokeLine(from: NSPoint(x: 0, y: tooltipSize.height - 2), to: NSPoint(x: bounds.width, y: tooltipSize.height - 2))
}
NSBezierPath.strokeLine(from: NSPoint(x: bounds.width, y: 0), to: NSPoint(x: bounds.width, y: bounds.height))
var accuWidth: CGFloat = 0
for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index]
let labelRect = NSRect(x: accuWidth, y: tooltipSize.height, width: currentWidth, height: keyLabelHeight)
let candidateRect = NSRect(x: accuWidth, y: tooltipSize.height + keyLabelHeight + 1.0, width: currentWidth, height: candidateTextHeight)
(index == highlightedIndex ? darkGray : lightGray).setFill()
NSBezierPath.fill(labelRect)
(keyLabels[index] as NSString).draw(in: labelRect, withAttributes: keyLabelAttrDict)
var activeCandidateAttr = candidateAttrDict
if index == highlightedIndex {
NSColor.selectedTextBackgroundColor.setFill()
activeCandidateAttr = candidateAttrDict
activeCandidateAttr[.foregroundColor] = NSColor.selectedTextColor
} else {
backgroundColor.setFill()
}
NSBezierPath.fill(candidateRect)
(displayedCandidates[index] as NSString).draw(in: candidateRect, withAttributes: activeCandidateAttr)
accuWidth += currentWidth + 1.0
}
}
private func findHitIndex(event: NSEvent) -> UInt? {
let location = convert(event.locationInWindow, to: nil)
if !NSPointInRect(location, self.bounds) {
return nil
}
var accuWidth: CGFloat = 0.0
for index in 0..<elementWidths.count {
let currentWidth = elementWidths[index]
if location.x >= accuWidth && location.x <= accuWidth + currentWidth {
return UInt(index)
}
accuWidth += currentWidth + 1.0
}
return nil
}
override func mouseUp(with event: NSEvent) {
trackingHighlightedIndex = highlightedIndex
guard let newIndex = findHitIndex(event: event) else {
return
}
highlightedIndex = newIndex
self.setNeedsDisplay(self.bounds)
}
override func mouseDown(with event: NSEvent) {
guard let newIndex = findHitIndex(event: event) else {
return
}
var triggerAction = false
if newIndex == highlightedIndex {
triggerAction = true
} else {
highlightedIndex = trackingHighlightedIndex
}
trackingHighlightedIndex = 0
self.setNeedsDisplay(self.bounds)
if triggerAction {
if let target = target as? NSObject, let action = action {
target.perform(action, with: self)
}
}
}
}
@objc(VTHorizontalCandidateController)
public class HorizontalCandidateController: CandidateController {
private var candidateView: HorizontalCandidateView
private var prevPageButton: NSButton
private var nextPageButton: NSButton
private var currentPage: UInt = 0
public init() {
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
panel.hasShadow = true
contentRect.origin = NSPoint.zero
candidateView = HorizontalCandidateView(frame: contentRect)
panel.contentView?.addSubview(candidateView)
contentRect.size = NSSize(width: 36.0, height: 20.0)
nextPageButton = NSButton(frame: contentRect)
nextPageButton.setButtonType(.momentaryLight)
nextPageButton.bezelStyle = .smallSquare
nextPageButton.title = "»"
prevPageButton = NSButton(frame: contentRect)
prevPageButton.setButtonType(.momentaryLight)
prevPageButton.bezelStyle = .smallSquare
prevPageButton.title = "«"
panel.contentView?.addSubview(nextPageButton)
panel.contentView?.addSubview(prevPageButton)
super.init(window: panel)
candidateView.target = self
candidateView.action = #selector(candidateViewMouseDidClick(_:))
nextPageButton.target = self
nextPageButton.action = #selector(pageButtonAction(_:))
prevPageButton.target = self
prevPageButton.action = #selector(pageButtonAction(_:))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
candidateView.highlightedIndex = 0
currentPage = 0
layoutCandidateView()
}
public override func showNextPage() -> Bool {
guard delegate != nil else {
return false
}
if currentPage + 1 >= pageCount {
return false
}
currentPage += 1
candidateView.highlightedIndex = 0
layoutCandidateView()
return true
}
public override func showPreviousPage() -> Bool {
guard delegate != nil else {
return false
}
if currentPage == 0 {
return false
}
currentPage -= 1
candidateView.highlightedIndex = 0
layoutCandidateView()
return true
}
public override func highlightNextCandidate() -> Bool {
guard let delegate = delegate else {
return false
}
let currentIndex = selectedCandidateIndex
if currentIndex + 1 >= delegate.candidateCountForController(self) {
return false
}
selectedCandidateIndex = currentIndex + 1
return true
}
public override func highlightPreviousCandidate() -> Bool {
guard delegate != nil else {
return false
}
let currentIndex = selectedCandidateIndex
if currentIndex == 0 {
return false
}
selectedCandidateIndex = currentIndex - 1
return true
}
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
guard let delegate = delegate else {
return UInt.max
}
let result = currentPage * UInt(keyLabels.count) + index
return result < delegate.candidateCountForController(self) ? result : UInt.max
}
public override var selectedCandidateIndex: UInt {
get {
currentPage * UInt(keyLabels.count) + candidateView.highlightedIndex
}
set {
guard let delegate = delegate else {
return
}
let keyLabelCount = UInt(keyLabels.count)
if newValue < delegate.candidateCountForController(self) {
currentPage = newValue / keyLabelCount
candidateView.highlightedIndex = newValue % keyLabelCount
layoutCandidateView()
}
}
}
}
extension HorizontalCandidateController {
private var pageCount: UInt {
guard let delegate = delegate else {
return 0
}
let totalCount = delegate.candidateCountForController(self)
let keyLabelCount = UInt(keyLabels.count)
return totalCount / keyLabelCount + ((totalCount % keyLabelCount) != 0 ? 1 : 0)
}
private func layoutCandidateView() {
guard let delegate = delegate else {
return
}
candidateView.set(keyLabelFont: keyLabelFont, candidateFont: candidateFont)
var candidates = [String]()
let count = delegate.candidateCountForController(self)
let keyLabelCount = UInt(keyLabels.count)
let begin = currentPage * keyLabelCount
for index in begin..<min(begin + keyLabelCount, count) {
let candidate = delegate.candidateController(self, candidateAtIndex: index)
candidates.append(candidate)
}
candidateView.set(keyLabels: keyLabels.map { $0.displayedText}, displayedCandidates: candidates)
candidateView.toolTip = tooltip
var newSize = candidateView.sizeForView
var frameRect = candidateView.frame
frameRect.size = newSize
candidateView.frame = frameRect
if pageCount > 1 {
var buttonRect = nextPageButton.frame
var spacing: CGFloat = 0.0
if newSize.height < 40.0 {
buttonRect.size.height = floor(newSize.height / 2)
} else {
buttonRect.size.height = 20.0
}
if newSize.height >= 60.0 {
spacing = ceil(newSize.height * 0.1)
}
let buttonOriginY = (newSize.height - (buttonRect.size.height * 2.0 + spacing)) / 2.0
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY)
nextPageButton.frame = buttonRect
buttonRect.origin = NSPoint(x: newSize.width + 8.0, y: buttonOriginY + buttonRect.size.height + spacing)
prevPageButton.frame = buttonRect
newSize.width += 52.0
nextPageButton.isHidden = false
prevPageButton.isHidden = false
} else {
nextPageButton.isHidden = true
prevPageButton.isHidden = true
}
frameRect = window?.frame ?? NSRect.zero
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
frameRect.size = newSize
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
self.window?.setFrame(frameRect, display: false)
candidateView.setNeedsDisplay(candidateView.bounds)
}
@objc fileprivate func pageButtonAction(_ sender: Any) {
guard let sender = sender as? NSButton else {
return
}
if sender == nextPageButton {
_ = showNextPage()
} else if sender == prevPageButton {
_ = showPreviousPage()
}
}
@objc fileprivate func candidateViewMouseDidClick(_ sender: Any) {
delegate?.candidateController(self, didSelectCandidateAtIndex: selectedCandidateIndex)
}
}

View File

@ -1,486 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
fileprivate class VerticalKeyLabelStripView: NSView {
var keyLabelFont: NSFont = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
var labelOffsetY: CGFloat = 0
var keyLabels: [String] = []
var highlightedIndex: UInt = UInt.max
override var isFlipped: Bool {
true
}
override func draw(_ dirtyRect: NSRect) {
let bounds = self.bounds
NSColor.white.setFill()
NSBezierPath.fill(bounds)
let count = UInt(keyLabels.count)
if count == 0 {
return
}
let cellHeight: CGFloat = bounds.size.height / CGFloat(count)
let black = NSColor.black
let darkGray = NSColor(deviceWhite: 0.7, alpha: 1.0)
let lightGray = NSColor(deviceWhite: 0.8, alpha: 1.0)
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
let textAttr: [NSAttributedString.Key: AnyObject] = [
.font: keyLabelFont,
.foregroundColor: black,
.paragraphStyle: paraStyle]
for index in 0..<count {
let textRect = NSRect(x: 0.0, y: CGFloat(index) * cellHeight + labelOffsetY, width: bounds.size.width, height: cellHeight - labelOffsetY)
var cellRect = NSRect(x: 0.0, y: CGFloat(index) * cellHeight, width: bounds.size.width, height: cellHeight - 1)
if index + 1 >= count {
cellRect.size.height += 1.0
}
(index == highlightedIndex ? darkGray : lightGray).setFill()
NSBezierPath.fill(cellRect)
let text = keyLabels[Int(index)]
(text as NSString).draw(in: textRect, withAttributes: textAttr)
}
}
}
fileprivate class VerticalCandidateTableView: NSTableView {
override func adjustScroll(_ newVisible: NSRect) -> NSRect {
var scrollRect = newVisible
let rowHeightPlusSpacing = rowHeight + intercellSpacing.height
scrollRect.origin.y = (scrollRect.origin.y / rowHeightPlusSpacing) * rowHeightPlusSpacing
return scrollRect
}
}
private let kCandidateTextPadding: CGFloat = 24.0
private let kCandidateTextLeftMargin: CGFloat = 8.0
private let kCandidateTextPaddingWithMandatedTableViewPadding: CGFloat = 18.0
private let kCandidateTextLeftMarginWithMandatedTableViewPadding: CGFloat = 0.0
private class BackgroundView: NSView {
override func draw(_ dirtyRect: NSRect) {
NSColor.windowBackgroundColor.setFill()
NSBezierPath.fill(self.bounds)
}
}
@objc(VTVerticalCandidateController)
public class VerticalCandidateController: CandidateController {
private var keyLabelStripView: VerticalKeyLabelStripView
private var scrollView: NSScrollView
private var tableView: NSTableView
private var candidateTextParagraphStyle: NSMutableParagraphStyle
private var candidateTextPadding: CGFloat = kCandidateTextPadding
private var candidateTextLeftMargin: CGFloat = kCandidateTextLeftMargin
private var maxCandidateAttrStringWidth: CGFloat = 0
private let tooltipPadding: CGFloat = 2.0
private var tooltipView: NSTextField
public init() {
var contentRect = NSRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
panel.hasShadow = true
panel.contentView = BackgroundView()
tooltipView = NSTextField(frame: NSRect.zero)
tooltipView.isEditable = false
tooltipView.isSelectable = false
tooltipView.isBezeled = false
tooltipView.drawsBackground = true
tooltipView.lineBreakMode = .byTruncatingTail
contentRect.origin = NSPoint.zero
var stripRect = contentRect
stripRect.size.width = 10.0
keyLabelStripView = VerticalKeyLabelStripView(frame: stripRect)
panel.contentView?.addSubview(keyLabelStripView)
var scrollViewRect = contentRect
scrollViewRect.origin.x = stripRect.size.width
scrollViewRect.size.width -= stripRect.size.width
scrollView = NSScrollView(frame: scrollViewRect)
scrollView.verticalScrollElasticity = .none
tableView = NSTableView(frame: contentRect)
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "candidate"))
column.dataCell = NSTextFieldCell()
column.isEditable = false
candidateTextPadding = kCandidateTextPadding
candidateTextLeftMargin = kCandidateTextLeftMargin
tableView.addTableColumn(column)
tableView.intercellSpacing = NSSize(width: 0.0, height: 1.0)
tableView.headerView = nil
tableView.allowsMultipleSelection = false
tableView.allowsEmptySelection = false
if #available(macOS 10.16, *) {
tableView.style = .fullWidth
candidateTextPadding = kCandidateTextPaddingWithMandatedTableViewPadding
candidateTextLeftMargin = kCandidateTextLeftMarginWithMandatedTableViewPadding
}
scrollView.documentView = tableView
panel.contentView?.addSubview(scrollView)
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.firstLineHeadIndent = candidateTextLeftMargin
paraStyle.lineBreakMode = .byClipping
candidateTextParagraphStyle = paraStyle
super.init(window: panel)
tableView.dataSource = self
tableView.delegate = self
tableView.doubleAction = #selector(rowDoubleClicked(_:))
tableView.target = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func reloadData() {
maxCandidateAttrStringWidth = ceil(candidateFont.pointSize * 2.0 + candidateTextPadding)
tableView.reloadData()
layoutCandidateView()
if delegate?.candidateCountForController(self) ?? 0 > 0 {
selectedCandidateIndex = 0
}
}
public override func showNextPage() -> Bool {
scrollPageByOne(true)
}
public override func showPreviousPage() -> Bool {
scrollPageByOne(false)
}
public override func highlightNextCandidate() -> Bool {
moveSelectionByOne(true)
}
public override func highlightPreviousCandidate() -> Bool {
moveSelectionByOne(false)
}
public override func candidateIndexAtKeyLabelIndex(_ index: UInt) -> UInt {
guard let delegate = delegate else {
return UInt.max
}
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
if firstVisibleRow != -1 {
let result = UInt(firstVisibleRow) + index
if result < delegate.candidateCountForController(self) {
return result
}
}
return UInt.max
}
public override var selectedCandidateIndex: UInt {
get {
let selectedRow = tableView.selectedRow
return selectedRow == -1 ? UInt.max : UInt(selectedRow)
}
set {
guard let delegate = delegate else {
return
}
var newIndex = newValue
let selectedRow = tableView.selectedRow
let labelCount = keyLabels.count
let itemCount = delegate.candidateCountForController(self)
if newIndex == UInt.max {
if itemCount == 0 {
tableView.deselectAll(self)
return
}
newIndex = 0
}
var lastVisibleRow = newValue
if selectedRow != -1 && itemCount > 0 && itemCount > labelCount {
if newIndex > selectedRow && (Int(newIndex) - selectedRow) > 1 {
lastVisibleRow = min(newIndex + UInt(labelCount) - 1, itemCount - 1)
}
// no need to handle the backward case: (newIndex < selectedRow && selectedRow - newIndex > 1)
}
if itemCount > labelCount {
tableView.scrollRowToVisible(Int(lastVisibleRow))
}
tableView.selectRowIndexes(IndexSet(integer: Int(newIndex)), byExtendingSelection: false)
}
}
}
extension VerticalCandidateController: NSTableViewDataSource, NSTableViewDelegate {
public func numberOfRows(in tableView: NSTableView) -> Int {
Int(delegate?.candidateCountForController(self) ?? 0)
}
public func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
guard let delegate = delegate else {
return nil
}
var candidate = ""
if row < delegate.candidateCountForController(self) {
candidate = delegate.candidateController(self, candidateAtIndex: UInt(row))
}
let attrString = NSAttributedString(string: candidate, attributes: [
.font: candidateFont,
.paragraphStyle: candidateTextParagraphStyle
])
// we do more work than what this method is expected to; normally not a good practice, but for the amount of data (9 to 10 rows max), we can afford the overhead
// expand the window width if text overflows
let boundingRect = attrString.boundingRect(with: NSSize(width: 10240.0, height: 10240.0), options: .usesLineFragmentOrigin)
let textWidth = boundingRect.size.width + candidateTextPadding
if textWidth > maxCandidateAttrStringWidth {
maxCandidateAttrStringWidth = textWidth
layoutCandidateView()
}
// keep track of the highlighted index in the key label strip
let count = UInt(keyLabels.count)
let selectedRow = tableView.selectedRow
if selectedRow != -1 {
var newHilightIndex = 0
if keyLabelStripView.highlightedIndex != -1 &&
(row >= selectedRow + Int(count) || (selectedRow > count && row <= selectedRow - Int(count))) {
newHilightIndex = -1
} else {
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
newHilightIndex = selectedRow - firstVisibleRow
if newHilightIndex < -1 {
newHilightIndex = -1
}
}
if newHilightIndex != keyLabelStripView.highlightedIndex && newHilightIndex >= 0 {
keyLabelStripView.highlightedIndex = UInt(newHilightIndex)
keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame)
}
}
return attrString
}
public func tableViewSelectionDidChange(_ notification: Notification) {
let selectedRow = tableView.selectedRow
if selectedRow != -1 {
// keep track of the highlighted index in the key label strip
let firstVisibleRow = tableView.row(at: scrollView.documentVisibleRect.origin)
// firstVisibleRow cannot be larger than selectedRow.
if selectedRow >= firstVisibleRow {
keyLabelStripView.highlightedIndex = UInt(selectedRow - firstVisibleRow)
} else {
keyLabelStripView.highlightedIndex = UInt.max
}
keyLabelStripView.setNeedsDisplay(keyLabelStripView.frame)
// fix a subtle OS X "bug" that, since we force the scroller to appear,
// scrolling sometimes shows a temporarily "broken" scroll bar
// (but quickly disappears)
if scrollView.hasVerticalScroller {
scrollView.verticalScroller?.setNeedsDisplay()
}
}
}
@objc func rowDoubleClicked(_ sender: Any) {
let clickedRow = tableView.clickedRow
if clickedRow != -1 {
delegate?.candidateController(self, didSelectCandidateAtIndex: UInt(clickedRow))
}
}
func scrollPageByOne(_ forward: Bool) -> Bool {
guard let delegate = delegate else {
return false
}
let labelCount = UInt(keyLabels.count)
let itemCount = delegate.candidateCountForController(self)
if 0 == itemCount {
return false
}
if itemCount <= labelCount {
return false
}
var newIndex = selectedCandidateIndex
if forward {
if newIndex >= itemCount - 1 {
return false
}
newIndex = min(newIndex + labelCount, itemCount - 1)
} else {
if newIndex == 0 {
return false
}
if newIndex < labelCount {
newIndex = 0
} else {
newIndex -= labelCount
}
}
selectedCandidateIndex = newIndex
return true
}
private func moveSelectionByOne(_ forward: Bool) -> Bool {
guard let delegate = delegate else {
return false
}
let itemCount = delegate.candidateCountForController(self)
if 0 == itemCount {
return false
}
var newIndex = selectedCandidateIndex
if newIndex == UInt.max {
return false
}
if forward {
if newIndex >= itemCount - 1 {
return false
}
newIndex += 1
} else {
if 0 == newIndex {
return false
}
newIndex -= 1
}
selectedCandidateIndex = newIndex
return true
}
private func layoutCandidateView() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) { [self] in
doLayoutCandidateView()
}
}
private func doLayoutCandidateView() {
guard let delegate = delegate else {
return
}
let count = delegate.candidateCountForController(self)
if 0 == count {
return
}
var tooltipHeight: CGFloat = 0
var tooltipWidth: CGFloat = 0
if !tooltip.isEmpty {
tooltipView.stringValue = tooltip
let size = tooltipView.intrinsicContentSize
tooltipWidth = size.width + tooltipPadding * 2
tooltipHeight = size.height + tooltipPadding * 2
self.window?.contentView?.addSubview(tooltipView)
} else {
tooltipView.removeFromSuperview()
}
let candidateFontSize = ceil(candidateFont.pointSize)
let keyLabelFontSize = ceil(keyLabelFont.pointSize)
let fontSize = max(candidateFontSize, keyLabelFontSize)
let controlSize: NSControl.ControlSize = fontSize > 36.0 ? .regular : .small
var keyLabelCount = UInt(keyLabels.count)
var scrollerWidth: CGFloat = 0.0
if count <= keyLabelCount {
keyLabelCount = count
scrollView.hasVerticalScroller = false
} else {
scrollView.hasVerticalScroller = true
let verticalScroller = scrollView.verticalScroller
verticalScroller?.controlSize = controlSize
verticalScroller?.scrollerStyle = .legacy
scrollerWidth = NSScroller.scrollerWidth(for: controlSize, scrollerStyle: .legacy)
}
keyLabelStripView.keyLabelFont = keyLabelFont
let actualKeyLabels = keyLabels[0..<Int(keyLabelCount)].map { $0.displayedText }
keyLabelStripView.keyLabels = actualKeyLabels
keyLabelStripView.labelOffsetY = (keyLabelFontSize >= candidateFontSize) ? 0.0 : floor((candidateFontSize - keyLabelFontSize) / 2.0)
let rowHeight = ceil(fontSize * 1.25)
tableView.rowHeight = rowHeight
var maxKeyLabelWidth = keyLabelFontSize
let textAttr: [NSAttributedString.Key: AnyObject] = [.font: keyLabelFont]
let boundingBox = NSSize(width: 1600.0, height: 1600.0)
for label in actualKeyLabels {
let rect = (label as NSString).boundingRect(with: boundingBox, options: .usesLineFragmentOrigin, attributes: textAttr)
maxKeyLabelWidth = max(rect.size.width, maxKeyLabelWidth)
}
let rowSpacing = tableView.intercellSpacing.height
let stripWidth = ceil(maxKeyLabelWidth * 1.20)
let tableViewStartWidth = ceil(maxCandidateAttrStringWidth + scrollerWidth)
let windowWidth = max(stripWidth + 1.0 + tableViewStartWidth, tooltipWidth)
let windowHeight = CGFloat(keyLabelCount) * (rowHeight + rowSpacing) + tooltipHeight
var frameRect = self.window?.frame ?? NSRect.zero
let topLeftPoint = NSMakePoint(frameRect.origin.x, frameRect.origin.y + frameRect.size.height)
frameRect.size = NSMakeSize(windowWidth, windowHeight)
frameRect.origin = NSMakePoint(topLeftPoint.x, topLeftPoint.y - frameRect.size.height)
keyLabelStripView.frame = NSRect(x: 0.0, y: 0, width: stripWidth, height: windowHeight - tooltipHeight)
scrollView.frame = NSRect(x: stripWidth + 1.0, y: 0, width: (windowWidth - stripWidth - 1), height: windowHeight - tooltipHeight)
tooltipView.frame = NSRect(x: tooltipPadding, y: windowHeight - tooltipHeight + tooltipPadding, width: windowWidth, height: tooltipHeight)
self.window?.setFrame(frameRect, display: false)
}
}

View File

@ -1,156 +0,0 @@
import XCTest
@testable import CandidateUI
class HorizontalCandidateControllerTests: XCTestCase {
class Mock: CandidateControllerDelegate {
let candidates = ["A", "B", "C", "D", "E", "F", "G", "H"]
var selected: String?
func candidateCountForController(_ controller: CandidateController) -> UInt {
UInt(candidates.count)
}
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String {
candidates[Int(index)]
}
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
selected = candidates[Int(index)]
}
}
func testPositioning1() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.delegate = mock
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.reloadData()
controller.visible = true
controller.set(windowTopLeftPoint: NSPoint(x: -100, y: 0), bottomOutOfScreenAdjustmentHeight: 10)
let exp = expectation(description: "wait")
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
XCTAssert(controller.window?.frame.minX ?? -1 >= 0)
}
func testPositioning2() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.delegate = mock
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.reloadData()
controller.visible = true
let screenRect = NSScreen.main?.frame ?? NSRect.zero
controller.set(windowTopLeftPoint: NSPoint(x: screenRect.maxX + 100, y: screenRect.maxY + 100), bottomOutOfScreenAdjustmentHeight: 10)
let exp = expectation(description: "wait")
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
XCTAssert(controller.window?.frame.maxX ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxX)
XCTAssert(controller.window?.frame.maxY ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxY)
}
func testReloadData() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.delegate = mock
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.reloadData()
XCTAssert(controller.selectedCandidateIndex == 0)
}
func testHighlightNextCandidate() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.delegate = mock
var result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 1)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 2)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 3)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 4)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 5)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 6)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 7)
result = controller.highlightNextCandidate()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 7)
}
func testHighlightPreviousCandidate() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.delegate = mock
_ = controller.showNextPage()
XCTAssert(controller.selectedCandidateIndex == 4)
var result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 3)
result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 2)
result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 1)
result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 0)
result = controller.highlightPreviousCandidate()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 0)
}
func testShowNextPage() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
_ = controller.delegate = mock
var result = controller.showNextPage()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 4)
result = controller.showNextPage()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 4)
}
func testShowPreviousPage() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.delegate = mock
_ = controller.showNextPage()
var result = controller.showPreviousPage()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 0)
result = controller.showPreviousPage()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 0)
}
}

View File

@ -1,160 +0,0 @@
import XCTest
@testable import CandidateUI
class VerticalCandidateControllerTests: XCTestCase {
class Mock: CandidateControllerDelegate {
let candidates = ["A", "B", "C", "D", "E", "F", "G", "H"]
var selected: String?
func candidateCountForController(_ controller: CandidateController) -> UInt {
UInt(candidates.count)
}
func candidateController(_ controller: CandidateController, candidateAtIndex index: UInt) -> String {
candidates[Int(index)]
}
func candidateController(_ controller: CandidateController, didSelectCandidateAtIndex index: UInt) {
selected = candidates[Int(index)]
}
}
func testPositioning1() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.delegate = mock
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.reloadData()
controller.visible = true
controller.set(windowTopLeftPoint: NSPoint(x: -100, y: 0), bottomOutOfScreenAdjustmentHeight: 10)
let exp = expectation(description: "wait")
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
XCTAssert(controller.window?.frame.minX ?? -1 >= 0)
}
func testPositioning2() {
let controller = HorizontalCandidateController()
let mock = Mock()
controller.delegate = mock
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.reloadData()
controller.visible = true
let screenRect = NSScreen.main?.frame ?? NSRect.zero
controller.set(windowTopLeftPoint: NSPoint(x: screenRect.maxX + 100, y: screenRect.maxY + 100), bottomOutOfScreenAdjustmentHeight: 10)
let exp = expectation(description: "wait")
_ = XCTWaiter.wait(for: [exp], timeout: 0.2)
XCTAssert(controller.window?.frame.maxX ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxX)
XCTAssert(controller.window?.frame.maxY ?? CGFloat.greatestFiniteMagnitude <= screenRect.maxY)
}
func testReloadData() {
let controller = VerticalCandidateController()
let mock = Mock()
controller.delegate = mock
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.reloadData()
XCTAssert(controller.selectedCandidateIndex == 0)
}
func testHighlightNextCandidate() {
let controller = VerticalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.delegate = mock
controller.reloadData()
var result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 1)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 2)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 3)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 4)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 5)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 6)
result = controller.highlightNextCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 7)
result = controller.highlightNextCandidate()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 7)
}
func testHighlightPreviousCandidate() {
let controller = VerticalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.delegate = mock
_ = controller.showNextPage()
XCTAssert(controller.selectedCandidateIndex == 4)
var result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 3)
result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 2)
result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 1)
result = controller.highlightPreviousCandidate()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 0)
result = controller.highlightPreviousCandidate()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 0)
}
func testShowNextPage() {
let controller = VerticalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
_ = controller.delegate = mock
var result = controller.showNextPage()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 4)
result = controller.showNextPage()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 7)
result = controller.showNextPage()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 7)
}
func testShowPreviousPage() {
let controller = VerticalCandidateController()
let mock = Mock()
controller.keyLabels = ["1", "2", "3", "4"].map {
CandidateKeyLabel(key: $0, displayedText: $0)
}
controller.delegate = mock
_ = controller.showNextPage()
var result = controller.showPreviousPage()
XCTAssert(result == true)
XCTAssert(controller.selectedCandidateIndex == 0)
result = controller.showPreviousPage()
XCTAssert(result == false)
XCTAssert(controller.selectedCandidateIndex == 0)
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,28 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "FSEventStreamHelper",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "FSEventStreamHelper",
targets: ["FSEventStreamHelper"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "FSEventStreamHelper",
dependencies: []),
.testTarget(
name: "FSEventStreamHelperTests",
dependencies: ["FSEventStreamHelper"]),
]
)

View File

@ -1,3 +0,0 @@
# FSEventStreamHelper
A description of this package.

View File

@ -1,96 +0,0 @@
// Copyright (c) 2011 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
public protocol FSEventStreamHelperDelegate: AnyObject {
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event])
}
public class FSEventStreamHelper : NSObject {
public struct Event {
var path: String
var flags: FSEventStreamEventFlags
var id: FSEventStreamEventId
}
public let path: String
public let dispatchQueue: DispatchQueue
public weak var delegate: FSEventStreamHelperDelegate?
@objc public init(path: String, queue: DispatchQueue) {
self.path = path
self.dispatchQueue = queue
}
private var stream: FSEventStreamRef? = nil
public func start() -> Bool {
if stream != nil {
return false
}
var context = FSEventStreamContext()
context.info = Unmanaged.passUnretained(self).toOpaque()
guard let stream = FSEventStreamCreate(nil, {
(stream, clientCallBackInfo, eventCount, eventPaths, eventFlags, eventIds) in
let helper = Unmanaged<FSEventStreamHelper>.fromOpaque(clientCallBackInfo!).takeUnretainedValue()
let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
let pathsPtr = UnsafeBufferPointer(start: pathsBase, count: eventCount)
let flagsPtr = UnsafeBufferPointer(start: eventFlags, count: eventCount)
let eventIDsPtr = UnsafeBufferPointer(start: eventIds, count: eventCount)
let events = (0..<eventCount).map {
FSEventStreamHelper.Event(path: String(cString: pathsPtr[$0]),
flags: flagsPtr[$0],
id: eventIDsPtr[$0] )
}
helper.delegate?.helper(helper, didReceive: events)
},
&context,
[path] as CFArray,
UInt64(kFSEventStreamEventIdSinceNow),
1.0,
FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone)
) else {
return false
}
FSEventStreamSetDispatchQueue(stream, dispatchQueue)
if !FSEventStreamStart(stream) {
FSEventStreamInvalidate(stream)
return false
}
self.stream = stream
return true
}
func stop() {
guard let stream = stream else {
return
}
FSEventStreamStop(stream)
FSEventStreamInvalidate(stream)
self.stream = nil
}
}

View File

@ -1,28 +0,0 @@
// Copyright (c) 2011 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import FSEventStreamHelper
final class FSEventStreamHelperTests: XCTestCase {
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,25 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "InputSourceHelper",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "InputSourceHelper",
targets: ["InputSourceHelper"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "InputSourceHelper",
dependencies: []),
]
)

View File

@ -1,3 +0,0 @@
# InputSourceHelper
A description of this package.

View File

@ -1,131 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
import Carbon
public class InputSourceHelper: NSObject {
@available(*, unavailable)
public override init() {
super.init()
}
public static func allInstalledInputSources() -> [TISInputSource] {
TISCreateInputSourceList(nil, true).takeRetainedValue() as! [TISInputSource]
}
@objc(inputSourceForProperty:stringValue:)
public static func inputSource(for propertyKey: CFString, stringValue: String) -> TISInputSource? {
let stringID = CFStringGetTypeID()
for source in allInstalledInputSources() {
if let propertyPtr = TISGetInputSourceProperty(source, propertyKey) {
let property = Unmanaged<CFTypeRef>.fromOpaque(propertyPtr).takeUnretainedValue()
let typeID = CFGetTypeID(property)
if typeID != stringID {
continue
}
if stringValue == property as? String {
return source
}
}
}
return nil
}
@objc(inputSourceForInputSourceID:)
public static func inputSource(for sourceID: String) -> TISInputSource? {
inputSource(for: kTISPropertyInputSourceID, stringValue: sourceID)
}
@objc(inputSourceEnabled:)
public static func inputSourceEnabled(for source: TISInputSource) -> Bool {
if let valuePts = TISGetInputSourceProperty(source, kTISPropertyInputSourceIsEnabled) {
let value = Unmanaged<CFBoolean>.fromOpaque(valuePts).takeUnretainedValue()
return value == kCFBooleanTrue
}
return false
}
@objc(enableInputSource:)
public static func enable(inputSource: TISInputSource) -> Bool {
let status = TISEnableInputSource(inputSource)
return status == noErr
}
@objc(enableAllInputModesForInputSourceBundleID:)
public static func enableAllInputMode(for inputSourceBundleD: String) -> Bool {
var enabled = false
for source in allInstalledInputSources() {
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
let _ = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
continue
}
let bundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
if String(bundleID) == inputSourceBundleD {
let modeEnabled = self.enable(inputSource: source)
if !modeEnabled {
return false
}
enabled = true
}
}
return enabled
}
@objc(enableInputMode:forInputSourceBundleID:)
public static func enable(inputMode modeID: String, for bundleID: String) -> Bool {
for source in allInstalledInputSources() {
guard let bundleIDPtr = TISGetInputSourceProperty(source, kTISPropertyBundleID),
let modePtr = TISGetInputSourceProperty(source, kTISPropertyInputModeID) else {
continue
}
let inputsSourceBundleID = Unmanaged<CFString>.fromOpaque(bundleIDPtr).takeUnretainedValue()
let inputsSourceModeID = Unmanaged<CFString>.fromOpaque(modePtr).takeUnretainedValue()
if modeID == String(inputsSourceModeID) && bundleID == String(inputsSourceBundleID) {
let enabled = enable(inputSource: source)
print("Attempt to enable input source of mode: \(modeID), bundle ID: \(bundleID), result: \(enabled)")
return enabled
}
}
print("Failed to find any matching input source of mode: \(modeID), bundle ID: \(bundleID)")
return false
}
@objc(disableInputSource:)
public static func disable(inputSource: TISInputSource) -> Bool {
let status = TISDisableInputSource(inputSource)
return status == noErr
}
@objc(registerInputSource:)
public static func registerTnputSource(at url: URL) -> Bool {
let status = TISRegisterInputSource(url as CFURL)
return status == noErr
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,28 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "NSStringUtils",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "NSStringUtils",
targets: ["NSStringUtils"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "NSStringUtils",
dependencies: []),
.testTarget(
name: "NSStringUtilsTests",
dependencies: ["NSStringUtils"]),
]
)

View File

@ -1,3 +0,0 @@
# NSStringUtils
A description of this package.

View File

@ -1,73 +0,0 @@
// Copyright (c) 2011 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
public extension NSString {
/// Converts the index in an NSString to the index in a Swift string.
///
/// An Emoji might be compose by more than one UTF-16 code points, however
/// the length of an NSString is only the sum of the UTF-16 code points. It
/// causes that the NSString and Swift string representation of the same
/// string have different lengths once the string contains such Emoji. The
/// method helps to find the index in a Swift string by passing the index
/// in an NSString.
func characterIndex(from utf16Index:Int) -> (Int, String) {
let string = (self as String)
var length = 0
for (i, character) in string.enumerated() {
length += character.utf16.count
if length > utf16Index {
return (i, string)
}
}
return (string.count, string)
}
@objc func nextUtf16Position(for index: Int) -> Int {
var (fixedIndex, string) = characterIndex(from: index)
if fixedIndex < string.count {
fixedIndex += 1
}
return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count
}
@objc func previousUtf16Position(for index: Int) -> Int {
var (fixedIndex, string) = characterIndex(from: index)
if fixedIndex > 0 {
fixedIndex -= 1
}
return string[..<string.index(string.startIndex, offsetBy: fixedIndex)].utf16.count
}
@objc var count: Int {
(self as String).count
}
@objc func split() -> [NSString] {
Array(self as String).map {
NSString(string: String($0))
}
}
}

View File

@ -1,104 +0,0 @@
// Copyright (c) 2011 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import NSStringUtils
final class NSStringUtilsTests: XCTestCase {
func testNextNormal_0() {
let s = NSString("中文")
XCTAssertEqual(s.nextUtf16Position(for: 0), 1)
}
func testNextNormal_1() {
let s = NSString("中文")
XCTAssertEqual(s.nextUtf16Position(for: 1), 2)
}
func testNextWith🌳_0() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.nextUtf16Position(for: 0), 2)
}
func testNextWith🌳_1() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.nextUtf16Position(for: 1), 2)
}
func testNextWith🌳_2() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.nextUtf16Position(for: 2), 4)
}
func testNextWith🌳_3() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.nextUtf16Position(for: 3), 4)
}
func testNextWith🌳_4() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.nextUtf16Position(for: 4), 4)
}
func testNextWith🌳_5() {
let s = NSString("🌳🌳🌳")
XCTAssertEqual(s.nextUtf16Position(for: 4), 6)
}
func testPrevNormal_0() {
let s = NSString("中文")
XCTAssertEqual(s.previousUtf16Position(for: 1), 0)
}
func testPrevNormal_1() {
let s = NSString("中文")
XCTAssertEqual(s.previousUtf16Position(for: 2), 1)
}
func testPrevWith🌳_0() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.previousUtf16Position(for: 0), 0)
}
func testPrevWith🌳_1() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.previousUtf16Position(for: 1), 0)
}
func testPrevWith🌳_2() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.previousUtf16Position(for: 2), 0)
}
func testPrevWith🌳_3() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.previousUtf16Position(for: 3), 0)
}
func testPrevWith🌳_4() {
let s = NSString("🌳🌳")
XCTAssertEqual(s.previousUtf16Position(for: 4), 2)
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,25 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "NotifierUI",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "NotifierUI",
targets: ["NotifierUI"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "NotifierUI",
dependencies: []),
]
)

View File

@ -1,3 +0,0 @@
# NotifierUI
A description of this package.

View File

@ -1,194 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
private protocol NotifierWindowDelegate: AnyObject {
func windowDidBecomeClicked(_ window: NotifierWindow)
}
private class NotifierWindow: NSWindow {
weak var clickDelegate: NotifierWindowDelegate?
override func mouseDown(with event: NSEvent) {
clickDelegate?.windowDidBecomeClicked(self)
}
}
private let kWindowWidth: CGFloat = 160.0
private let kWindowHeight: CGFloat = 80.0
public class NotifierController: NSWindowController, NotifierWindowDelegate {
private var messageTextField: NSTextField
private var message: String = "" {
didSet {
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle(NSParagraphStyle.default)
paraStyle.alignment = .center
let attr: [NSAttributedString.Key: AnyObject] = [
.foregroundColor: foregroundColor,
.font: NSFont.systemFont(ofSize: NSFont.systemFontSize(for: .regular)),
.paragraphStyle: paraStyle
]
let attrString = NSAttributedString(string: message, attributes: attr)
messageTextField.attributedStringValue = attrString
let width = window?.frame.width ?? kWindowWidth
let rect = attrString.boundingRect(with: NSSize(width: width, height: 1600), options: .usesLineFragmentOrigin)
let height = rect.height
let x = messageTextField.frame.origin.x
let y = ((window?.frame.height ?? kWindowHeight) - height) / 2
let newFrame = NSRect(x: x, y: y, width: width, height: height)
messageTextField.frame = newFrame
}
}
private var shouldStay: Bool = false
private var backgroundColor: NSColor = .black {
didSet {
self.window?.backgroundColor = backgroundColor
self.messageTextField.backgroundColor = backgroundColor
}
}
private var foregroundColor: NSColor = .white {
didSet {
self.messageTextField.textColor = foregroundColor
}
}
private var waitTimer: Timer?
private var fadeTimer: Timer?
private static var instanceCount = 0
private static var lastLocation = NSPoint.zero
@objc public static func notify(message: String, stay: Bool = false) {
let controller = NotifierController()
controller.message = message
controller.shouldStay = stay
controller.show()
}
private static func increaseInstanceCount() {
instanceCount += 1
}
private static func decreaseInstanceCount() {
instanceCount -= 1
if instanceCount < 0 {
instanceCount = 0
}
}
private init() {
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
let contentRect = NSRect(x: 0, y: 0, width: kWindowWidth, height: kWindowHeight)
var windowRect = contentRect
windowRect.origin.x = screenRect.maxX - windowRect.width - 10
windowRect.origin.y = screenRect.maxY - windowRect.height - 10
let styleMask: NSWindow.StyleMask = [.borderless]
let panel = NotifierWindow(contentRect: windowRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel))
panel.hasShadow = true
panel.backgroundColor = backgroundColor
messageTextField = NSTextField()
messageTextField.frame = contentRect
messageTextField.isEditable = false
messageTextField.isSelectable = false
messageTextField.isBezeled = false
messageTextField.textColor = foregroundColor
messageTextField.drawsBackground = true
messageTextField.backgroundColor = backgroundColor
messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))
panel.contentView?.addSubview(messageTextField)
super.init(window: panel)
panel.clickDelegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func show() {
func setStartLocation() {
if NotifierController.instanceCount == 0 {
return
}
let lastLocation = NotifierController.lastLocation
let screenRect = NSScreen.main?.visibleFrame ?? NSRect.zero
var windowRect = self.window?.frame ?? NSRect.zero
windowRect.origin.x = lastLocation.x
windowRect.origin.y = lastLocation.y - 10 - windowRect.height
if windowRect.origin.y < screenRect.minY {
return
}
self.window?.setFrame(windowRect, display: true)
}
func moveIn() {
let afterRect = self.window?.frame ?? NSRect.zero
NotifierController.lastLocation = afterRect.origin
var beforeRect = afterRect
beforeRect.origin.y += 10
window?.setFrame(beforeRect, display: true)
window?.orderFront(self)
window?.setFrame(afterRect, display: true, animate: true)
}
setStartLocation()
moveIn()
NotifierController.increaseInstanceCount()
waitTimer = Timer.scheduledTimer(timeInterval: shouldStay ? 5 : 1, target: self, selector: #selector(fadeOut), userInfo: nil, repeats: false)
}
@objc private func doFadeOut(_ timer: Timer) {
let opacity = self.window?.alphaValue ?? 0
if opacity <= 0 {
self.close()
} else {
self.window?.alphaValue = opacity - 0.2
}
}
@objc private func fadeOut() {
waitTimer?.invalidate()
waitTimer = nil
NotifierController.decreaseInstanceCount()
fadeTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(doFadeOut(_:)), userInfo: nil, repeats: true)
}
public override func close() {
waitTimer?.invalidate()
waitTimer = nil
fadeTimer?.invalidate()
fadeTimer = nil
super.close()
}
fileprivate func windowDidBecomeClicked(_ window: NotifierWindow) {
self.fadeOut()
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,16 +0,0 @@
{
"object": {
"pins": [
{
"package": "SwiftyOpenCC",
"repositoryURL": "https://github.com/ddddxxx/SwiftyOpenCC.git",
"state": {
"branch": null,
"revision": "a802c02cbf1c6fcd529575f11a9876d94fc813f4",
"version": null
}
}
]
},
"version": 1
}

View File

@ -1,32 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "OpenCCBridge",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "OpenCCBridge",
targets: ["OpenCCBridge"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(name: "SwiftyOpenCC", url: "https://github.com/ddddxxx/SwiftyOpenCC.git", from: "2.0.0-beta")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "OpenCCBridge",
dependencies: [
.product(name: "OpenCC", package: "SwiftyOpenCC")
]),
.testTarget(
name: "OpenCCBridgeTests",
dependencies: ["OpenCCBridge"]),
]
)

View File

@ -1,3 +0,0 @@
# OpenCCBridge
A description of this package.

View File

@ -1,47 +0,0 @@
// Copyright (c) 2021 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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
import OpenCC
/// A bridge to let Objctive-C code to access SwiftyOpenCC.
///
/// Since SwiftyOpenCC only provide Swift classes, we create an NSObject subclass
/// in Swift in order to bridge the Swift classes into our Objective-C++ project.
public class OpenCCBridge: NSObject {
private static let shared = OpenCCBridge()
private var converter: ChineseConverter?
private override init() {
try? converter = ChineseConverter(options: .simplify)
super.init()
}
/// Converts to Simplified Chinese.
///
/// - Parameter string: Text in Traditional Chinese.
/// - Returns: Text in Simplified Chinese.
@objc public static func convertToSimplified(_ string: String) -> String? {
shared.converter?.convert(string)
}
}

View File

@ -1,33 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import OpenCCBridge
final class OpenCCBridgeTests: XCTestCase {
func testTC2SC() throws {
let text = "繁體中文轉簡體中文"
let converted = OpenCCBridge.convertToSimplified(text)
XCTAssert(converted == "繁体中文转简体中文")
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,25 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "TooltipUI",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "TooltipUI",
targets: ["TooltipUI"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "TooltipUI",
dependencies: []),
]
)

View File

@ -1,3 +0,0 @@
# TooltipUI
A description of this package.

View File

@ -1,123 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
public class TooltipController: NSWindowController {
private let backgroundColor = NSColor(calibratedHue: 0.16, saturation: 0.22, brightness: 0.97, alpha: 1.0)
private var messageTextField: NSTextField
private var tooltip: String = "" {
didSet {
messageTextField.stringValue = tooltip
adjustSize()
}
}
public init() {
let contentRect = NSRect(x: 128.0, y: 128.0, width: 300.0, height: 20.0)
let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel]
let panel = NSPanel(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false)
panel.level = NSWindow.Level(Int(kCGPopUpMenuWindowLevel) + 1)
panel.hasShadow = true
messageTextField = NSTextField()
messageTextField.isEditable = false
messageTextField.isSelectable = false
messageTextField.isBezeled = false
messageTextField.textColor = .black
messageTextField.drawsBackground = true
messageTextField.backgroundColor = backgroundColor
messageTextField.font = .systemFont(ofSize: NSFont.systemFontSize(for: .small))
panel.contentView?.addSubview(messageTextField)
super.init(window: panel)
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc(showTooltip:atPoint:)
public func show(tooltip: String, at point: NSPoint) {
self.tooltip = tooltip
window?.orderFront(nil)
set(windowLocation: point)
}
@objc
public func hide() {
window?.orderOut(nil)
}
private func set(windowLocation windowTopLeftPoint: NSPoint) {
var adjustedPoint = windowTopLeftPoint
adjustedPoint.y -= 5
var screenFrame = NSScreen.main?.visibleFrame ?? NSRect.zero
for screen in NSScreen.screens {
let frame = screen.visibleFrame
if windowTopLeftPoint.x >= frame.minX &&
windowTopLeftPoint.x <= frame.maxX &&
windowTopLeftPoint.y >= frame.minY &&
windowTopLeftPoint.y <= frame.maxY {
screenFrame = frame
break
}
}
let windowSize = window?.frame.size ?? NSSize.zero
// bottom beneath the screen?
if adjustedPoint.y - windowSize.height < screenFrame.minY {
adjustedPoint.y = screenFrame.minY + windowSize.height
}
// top over the screen?
if adjustedPoint.y >= screenFrame.maxY {
adjustedPoint.y = screenFrame.maxY - 1.0
}
// right
if adjustedPoint.x + windowSize.width >= screenFrame.maxX {
adjustedPoint.x = screenFrame.maxX - windowSize.width
}
// left
if adjustedPoint.x < screenFrame.minX {
adjustedPoint.x = screenFrame.minX
}
window?.setFrameTopLeftPoint(adjustedPoint)
}
private func adjustSize() {
let attrString = messageTextField.attributedStringValue;
var rect = attrString.boundingRect(with: NSSize(width: 1600.0, height: 1600.0), options: .usesLineFragmentOrigin)
rect.size.width += 10
messageTextField.frame = rect
window?.setFrame(rect, display: true)
}
}

View File

@ -1,7 +0,0 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View File

@ -1,28 +0,0 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "VXHanConvert",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "VXHanConvert",
targets: ["VXHanConvert"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "VXHanConvert",
dependencies: []),
.testTarget(
name: "VXHanConvertTests",
dependencies: ["VXHanConvert"]),
]
)

View File

@ -1,3 +0,0 @@
# VXHanConvert
A description of this package.

File diff suppressed because it is too large Load Diff

View File

@ -1,651 +0,0 @@
/* VXHCTC2SCTable.c: Traditional Chinese -> Simplified Chinese conv. table
* (VX is abbr. for Vanilla OS X, a legacy from OpenVanilla 0.6.x)
*
* Copyright (c) 2004-2007 The OpenVanilla Project (http://openvanilla.org)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of OpenVanilla nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The data is derived from Perl's Encode::HanConvert (0.31),
* Copyright (c) 2002-2004 by Autrijus Tang
*
*/
unsigned short vxTC2SCTable[3059 * 2]={
0x2215,0xff0f, 0x2223,0xff5c, 0x2225,0x2016, 0x2266,0x2264, 0x2267,0x2265,
0x2550,0x3013, 0x255e,0x251d, 0x2561,0x2525, 0x256a,0x253f, 0x2574,0xff3f,
0x2594,0xffe3, 0x2f02,0x4e36, 0x2f03,0x4e3f, 0x2f07,0x4ea0, 0x2f0c,0x5182,
0x2f0d,0x5196, 0x2f0e,0x51ab, 0x2f13,0x52f9, 0x2f19,0x5369, 0x2f1b,0x53b6,
0x2f22,0x5902, 0x2f27,0x5b80, 0x2f2e,0x5ddb, 0x2f33,0x5e7a, 0x2f34,0x5e7f,
0x2f35,0x5ef4, 0x2f39,0x5f50, 0x2f3a,0x5f61, 0x2f41,0x6534, 0x2f46,0x65e0,
0x2f67,0x7592, 0x2faa,0x96b6, 0x301e,0xff02, 0x3029,0x6587, 0x3038,0x5341,
0x3039,0x5344, 0x303a,0x5345, 0x4e1f,0x4e22, 0x4e26,0x5e76, 0x4e82,0x4e71,
0x4e99,0x4e98, 0x4e9e,0x4e9a, 0x4eb9,0x5a13, 0x4f00,0x5fea, 0x4f15,0x592b,
0x4f1d,0x4f20, 0x4f47,0x4f2b, 0x4f48,0x5e03, 0x4f54,0x5360, 0x4f6a,0x5f8a,
0x4f75,0x5e76, 0x4f86,0x6765, 0x4f96,0x4ed1, 0x4fb6,0x4fa3, 0x4fb7,0x5c40,
0x4fc1,0x4fe3, 0x4fc2,0x7cfb, 0x4fdb,0x4fef, 0x4fe0,0x4fa0, 0x4ff6,0x501c,
0x5000,0x4f25, 0x5006,0x4fe9, 0x5009,0x4ed3, 0x500b,0x4e2a, 0x5011,0x4eec,
0x5016,0x5e78, 0x5023,0x4eff, 0x502b,0x4f26, 0x5049,0x4f1f, 0x5061,0x903c,
0x5062,0x8e3d, 0x506a,0x903c, 0x5074,0x4fa7, 0x5075,0x4fa6, 0x507d,0x4f2a,
0x5091,0x6770, 0x5092,0x5faf, 0x5096,0x4f27, 0x5098,0x4f1e, 0x5099,0x5907,
0x509a,0x6548, 0x50a2,0x5bb6, 0x50ad,0x4f63, 0x50af,0x506c, 0x50b3,0x4f20,
0x50b4,0x4f1b, 0x50b5,0x503a, 0x50b7,0x4f24, 0x50be,0x503e, 0x50c2,0x507b,
0x50c5,0x4ec5, 0x50c9,0x4f65, 0x50ca,0x4ed9, 0x50d1,0x4fa8, 0x50d5,0x4ec6,
0x50e3,0x50ed, 0x50e5,0x4fa5, 0x50e8,0x507e, 0x50f1,0x96c7, 0x50f9,0x4ef7,
0x5100,0x4eea, 0x5102,0x4fac, 0x5104,0x4ebf, 0x5105,0x5f53, 0x5108,0x4fa9,
0x5109,0x4fed, 0x510c,0x4fa5, 0x5110,0x50a7, 0x5114,0x4fe6, 0x5115,0x4faa,
0x5118,0x5c3d, 0x511f,0x507f, 0x512a,0x4f18, 0x5132,0x50a8, 0x5133,0x998b,
0x5137,0x4fea, 0x5138,0x7f57, 0x5139,0x6512, 0x513a,0x50a9, 0x513b,0x50a5,
0x513c,0x4fe8, 0x5147,0x51f6, 0x514c,0x5151, 0x5152,0x513f, 0x5157,0x5156,
0x5167,0x5185, 0x5169,0x4e24, 0x5188,0x7f51, 0x518a,0x518c, 0x5191,0x80c4,
0x51aa,0x5e42, 0x51c8,0x51c0, 0x51cd,0x51bb, 0x51dc,0x51db, 0x51f1,0x51ef,
0x5225,0x522b, 0x522a,0x5220, 0x5231,0x521b, 0x5244,0x522d, 0x5247,0x5219,
0x5249,0x9509, 0x524b,0x514b, 0x524e,0x5239, 0x525b,0x521a, 0x525d,0x5265,
0x526e,0x5250, 0x5274,0x5240, 0x5275,0x521b, 0x5277,0x94f2, 0x5283,0x5212,
0x5284,0x672d, 0x5287,0x5267, 0x5289,0x5218, 0x528a,0x523d, 0x528b,0x527f,
0x528c,0x523f, 0x528d,0x5251, 0x5291,0x5242, 0x52c1,0x52b2, 0x52d5,0x52a8,
0x52d7,0x52d6, 0x52d9,0x52a1, 0x52db,0x52cb, 0x52dd,0x80dc, 0x52de,0x52b3,
0x52e2,0x52bf, 0x52e3,0x7ee9, 0x52e6,0x527f, 0x52f1,0x52a2, 0x52f3,0x52cb,
0x52f5,0x52b1, 0x52f8,0x529d, 0x52fb,0x5300, 0x5309,0x7830, 0x530a,0x63ac,
0x531f,0x7095, 0x532d,0x5326, 0x532f,0x6c47, 0x5331,0x532e, 0x5340,0x533a,
0x5354,0x534f, 0x5379,0x6064, 0x537b,0x5374, 0x538e,0x7825, 0x5399,0x538d,
0x53a7,0x5386, 0x53ad,0x538c, 0x53b2,0x5389, 0x53b4,0x53a3, 0x53c3,0x53c2,
0x53e1,0x777f, 0x53e2,0x4e1b, 0x540b,0x5bf8, 0x542a,0x8bb9, 0x5433,0x5434,
0x5436,0x5450, 0x5442,0x5415, 0x544e,0x5c3a, 0x5467,0x8bcb, 0x54b7,0x5555,
0x54bc,0x5459, 0x54e1,0x5458, 0x5504,0x5457, 0x5505,0x542b, 0x551a,0x5423,
0x5538,0x5ff5, 0x554f,0x95ee, 0x555e,0x54d1, 0x555f,0x542f, 0x5562,0x5521,
0x5563,0x8854, 0x557f,0x5556, 0x559a,0x5524, 0x55a5,0x54a4, 0x55aa,0x4e27,
0x55ab,0x5403, 0x55ac,0x4e54, 0x55ae,0x5355, 0x55b2,0x54df, 0x55c6,0x545b,
0x55c7,0x556c, 0x55ce,0x5417, 0x55da,0x545c, 0x55e9,0x5522, 0x55f6,0x54d4,
0x5606,0x53f9, 0x560d,0x55bd, 0x5614,0x5455, 0x5616,0x5567, 0x5617,0x5c1d,
0x561c,0x551b, 0x5629,0x54d7, 0x562e,0x5520, 0x562f,0x5578, 0x5630,0x53fd,
0x5635,0x54d3, 0x5638,0x5452, 0x5641,0x6076, 0x5649,0x5556, 0x5653,0x5618,
0x5660,0x54d2, 0x5665,0x54dd, 0x5666,0x54d5, 0x566f,0x55f3, 0x5672,0x54d9,
0x5674,0x55b7, 0x5678,0x5428, 0x5679,0x5f53, 0x5680,0x549b, 0x5687,0x5413,
0x568c,0x54dc, 0x5690,0x5c1d, 0x5695,0x565c, 0x5699,0x556e, 0x56a5,0x54bd,
0x56a6,0x5456, 0x56a8,0x5499, 0x56aa,0x5556, 0x56ac,0x9891, 0x56ae,0x5411,
0x56b3,0x55be, 0x56b4,0x4e25, 0x56b6,0x5624, 0x56be,0x6b22, 0x56c0,0x556d,
0x56c1,0x55eb, 0x56c2,0x56a3, 0x56c5,0x5181, 0x56c8,0x5453, 0x56c9,0x7f57,
0x56cc,0x82cf, 0x56d1,0x5631, 0x56d3,0x556e, 0x56ea,0x56f1, 0x5707,0x56f5,
0x570b,0x56fd, 0x570c,0x5459, 0x570d,0x56f4, 0x5712,0x56ed, 0x5713,0x5706,
0x5716,0x56fe, 0x5718,0x56e2, 0x571e,0x683e, 0x5775,0x4e18, 0x5794,0x5819,
0x57b5,0x57ef, 0x57e1,0x57ad, 0x57f7,0x6267, 0x5801,0x575d, 0x5805,0x575a,
0x580a,0x57a9, 0x581d,0x57da, 0x582f,0x5c27, 0x5831,0x62a5, 0x5834,0x573a,
0x583d,0x5188, 0x583f,0x78b1, 0x584a,0x5757, 0x584b,0x8314, 0x584f,0x57b2,
0x5852,0x57d8, 0x5857,0x6d82, 0x5859,0x786e, 0x585a,0x51a2, 0x5862,0x575e,
0x5864,0x57d9, 0x586d,0x761f, 0x5874,0x580b, 0x5875,0x5c18, 0x5879,0x5811,
0x588a,0x57ab, 0x5891,0x5892, 0x589c,0x5760, 0x58ae,0x5815, 0x58b3,0x575f,
0x58bb,0x5899, 0x58be,0x57a6, 0x58c7,0x575b, 0x58ce,0x57d9, 0x58d3,0x538b,
0x58d8,0x5792, 0x58d9,0x5739, 0x58da,0x5786, 0x58de,0x574f, 0x58df,0x5784,
0x58e2,0x575c, 0x58e9,0x575d, 0x58ef,0x58ee, 0x58fa,0x58f6, 0x58fd,0x5bff,
0x5920,0x591f, 0x5922,0x68a6, 0x593e,0x5939, 0x5950,0x5942, 0x5967,0x5965,
0x5969,0x5941, 0x596a,0x593a, 0x596e,0x594b, 0x597c,0x59f9, 0x599d,0x5986,
0x59b3,0x4f60, 0x59cd,0x59d7, 0x59e6,0x5978, 0x59ea,0x4f84, 0x59ee,0x5ae6,
0x59f7,0x4f91, 0x5a1b,0x5a31, 0x5a37,0x5077, 0x5a41,0x5a04, 0x5a53,0x6deb,
0x5a5f,0x5a40, 0x5a66,0x5987, 0x5a6d,0x5a05, 0x5aa7,0x5a32, 0x5aaf,0x59ab,
0x5abc,0x5aaa, 0x5abd,0x5988, 0x5abf,0x6127, 0x5acb,0x8885, 0x5ad7,0x59aa,
0x5af5,0x59a9, 0x5afa,0x5a34, 0x5afb,0x5a34, 0x5b08,0x5a06, 0x5b0b,0x5a75,
0x5b0c,0x5a07, 0x5b19,0x5af1, 0x5b1d,0x8885, 0x5b21,0x5ad2, 0x5b24,0x5b37,
0x5b2a,0x5ad4, 0x5b2d,0x5976, 0x5b30,0x5a74, 0x5b38,0x5a76, 0x5b3d,0x61d2,
0x5b43,0x5a18, 0x5b4c,0x5a08, 0x5b6b,0x5b59, 0x5b77,0x6635, 0x5b78,0x5b66,
0x5b7f,0x5b6a, 0x5bae,0x5bab, 0x5bca,0x5bdd, 0x5bd1,0x5b9e, 0x5bd6,0x6d78,
0x5bd8,0x7f6e, 0x5be2,0x5bdd, 0x5be6,0x5b9e, 0x5be7,0x5b81, 0x5be9,0x5ba1,
0x5beb,0x5199, 0x5bec,0x5bbd, 0x5bf1,0x5453, 0x5bf5,0x5ba0, 0x5bf6,0x5b9d,
0x5c07,0x5c06, 0x5c08,0x4e13, 0x5c0b,0x5bfb, 0x5c0d,0x5bf9, 0x5c0e,0x5bfc,
0x5c12,0x5c14, 0x5c1f,0x9c9c, 0x5c37,0x5c34, 0x5c46,0x5c4a, 0x5c4d,0x5c38,
0x5c5c,0x5c49, 0x5c62,0x5c61, 0x5c64,0x5c42, 0x5c68,0x5c66, 0x5c6c,0x5c5e,
0x5c8a,0x5c9c, 0x5ca1,0x5188, 0x5ca5,0x5761, 0x5cf4,0x5c98, 0x5cf6,0x5c9b,
0x5cfd,0x5ce1, 0x5d0d,0x5d03, 0x5d11,0x6606, 0x5d17,0x5c97, 0x5d19,0x4ed1,
0x5d1a,0x5ce5, 0x5d20,0x5cbd, 0x5d22,0x5ce5, 0x5d30,0x5cb7, 0x5d33,0x5d5b,
0x5d50,0x5c9a, 0x5d51,0x7800, 0x5d55,0x9685, 0x5d68,0x575e, 0x5d7e,0x53c2,
0x5d81,0x5d5d, 0x5d84,0x5d2d, 0x5d87,0x5c96, 0x5d94,0x5d5a, 0x5d97,0x5d02,
0x5da0,0x5ce4, 0x5da7,0x5cc4, 0x5da8,0x5c99, 0x5dae,0x9669, 0x5db4,0x5c99,
0x5db8,0x5d58, 0x5dba,0x5cad, 0x5dbc,0x5c7f, 0x5dbd,0x5cb3, 0x5dcb,0x5cbf,
0x5dd2,0x5ce6, 0x5dd4,0x5dc5, 0x5dd6,0x5ca9, 0x5df0,0x5def, 0x5df9,0x537a,
0x5e25,0x5e05, 0x5e2b,0x5e08, 0x5e33,0x5e10, 0x5e36,0x5e26, 0x5e40,0x5e27,
0x5e43,0x5e0f, 0x5e4e,0x5e42, 0x5e57,0x5e3c, 0x5e58,0x5e3b, 0x5e59,0x5e55,
0x5e5f,0x5e1c, 0x5e63,0x5e01, 0x5e6b,0x5e2e, 0x5e6c,0x5e31, 0x5e79,0x5e72,
0x5e7e,0x51e0, 0x5eab,0x5e93, 0x5ec1,0x5395, 0x5ec2,0x53a2, 0x5ec4,0x53a9,
0x5ec8,0x53a6, 0x5ed5,0x836b, 0x5eda,0x53a8, 0x5edd,0x53ae, 0x5edf,0x5e99,
0x5ee0,0x5382, 0x5ee1,0x5e91, 0x5ee2,0x5e9f, 0x5ee3,0x5e7f, 0x5ee9,0x5eea,
0x5eec,0x5e90, 0x5ef3,0x5385, 0x5f12,0x5f11, 0x5f14,0x540a, 0x5f33,0x5f2a,
0x5f35,0x5f20, 0x5f37,0x5f3a, 0x5f46,0x522b, 0x5f48,0x5f39, 0x5f4a,0x5f3a,
0x5f4c,0x5f25, 0x5f4e,0x5f2f, 0x5f54,0x5f55, 0x5f59,0x6c47, 0x5f5e,0x5f5d,
0x5f65,0x5f66, 0x5f6b,0x96d5, 0x5f7e,0x4f36, 0x5f7f,0x4f5b, 0x5f8c,0x540e,
0x5f91,0x5f84, 0x5f9e,0x4ece, 0x5fa0,0x5f95, 0x5fa9,0x590d, 0x5fac,0x65c1,
0x5fb9,0x5f7b, 0x5fe3,0x6025, 0x600c,0x6000, 0x6033,0x604d, 0x6046,0x6052,
0x6049,0x65e8, 0x6065,0x803b, 0x6085,0x60a6, 0x6090,0x54f2, 0x60b5,0x6005,
0x60b6,0x95f7, 0x60bd,0x51c4, 0x60bf,0x60ca, 0x60c7,0x6566, 0x60c8,0x60dd,
0x60e1,0x6076, 0x60e4,0x5a6a, 0x60f1,0x607c, 0x60f2,0x607d, 0x60f7,0x8822,
0x60fb,0x607b, 0x60fc,0x51ff, 0x6105,0x5ff1, 0x611b,0x7231, 0x611c,0x60ec,
0x6128,0x60ab, 0x612c,0x8bc9, 0x6134,0x6006, 0x6137,0x607a, 0x613e,0x5ffe,
0x6144,0x6817, 0x6147,0x6bb7, 0x614b,0x6001, 0x614d,0x6120, 0x6158,0x60e8,
0x615a,0x60ed, 0x615f,0x6078, 0x6163,0x60ef, 0x616a,0x6004, 0x616b,0x6002,
0x616e,0x8651, 0x6173,0x60ad, 0x6174,0x6151, 0x6176,0x5e86, 0x6179,0x7857,
0x617c,0x621a, 0x617e,0x6b32, 0x6182,0x5fe7, 0x6183,0x6a3d, 0x618a,0x60eb,
0x6190,0x601c, 0x6191,0x51ed, 0x6192,0x6126, 0x6193,0x60e0, 0x619a,0x60ee,
0x61a4,0x6124, 0x61ab,0x60af, 0x61ad,0x6a3d, 0x61ae,0x6003, 0x61b2,0x5baa,
0x61b6,0x5fc6, 0x61c3,0x52e4, 0x61c7,0x6073, 0x61c9,0x5e94, 0x61cc,0x603f,
0x61cd,0x61d4, 0x61de,0x8499, 0x61df,0x603c, 0x61e3,0x61d1, 0x61e8,0x6079,
0x61e9,0x75d2, 0x61f2,0x60e9, 0x61f6,0x61d2, 0x61f7,0x6000, 0x61f8,0x60ac,
0x61fa,0x5fcf, 0x61fc,0x60e7, 0x61fd,0x6b22, 0x61fe,0x6151, 0x6200,0x604b,
0x6207,0x6206, 0x6209,0x94ba, 0x6214,0x620b, 0x6227,0x6217, 0x6229,0x622c,
0x6230,0x6218, 0x6232,0x620f, 0x6236,0x6237, 0x625e,0x634d, 0x6260,0x53c9,
0x6261,0x62d6, 0x6283,0x62da, 0x62ad,0x8200, 0x62b4,0x62fd, 0x62cb,0x629b,
0x62d1,0x94b3, 0x632c,0x62d4, 0x633c,0x632a, 0x633e,0x631f, 0x6344,0x6551,
0x6368,0x820d, 0x636b,0x626a, 0x6372,0x5377, 0x6383,0x626b, 0x6384,0x62a1,
0x6399,0x6323, 0x639b,0x6302, 0x639c,0x62bb, 0x639e,0x78b0, 0x63a1,0x91c7,
0x63a4,0x632a, 0x63af,0x636e, 0x63c0,0x62e3, 0x63da,0x626c, 0x63db,0x6362,
0x63eb,0x63ea, 0x63ee,0x6325, 0x63f0,0x8f70, 0x63f9,0x80cc, 0x6406,0x6784,
0x640a,0x638f, 0x640d,0x635f, 0x6412,0x699c, 0x6416,0x6447, 0x6417,0x6363,
0x6424,0x627c, 0x6425,0x6376, 0x6427,0x6247, 0x6428,0x62d3, 0x642b,0x642c,
0x6436,0x62a2, 0x6439,0x627c, 0x643e,0x69a8, 0x6440,0x6342, 0x6443,0x625b,
0x6451,0x63b4, 0x645c,0x63bc, 0x645f,0x6402, 0x646f,0x631a, 0x6473,0x62a0,
0x6476,0x629f, 0x647b,0x63ba, 0x6488,0x635e, 0x6490,0x6491, 0x6493,0x6320,
0x6498,0x642d, 0x649a,0x637b, 0x649f,0x6322, 0x64a2,0x63b8, 0x64a3,0x63b8,
0x64a5,0x62e8, 0x64a6,0x626f, 0x64ab,0x629a, 0x64b2,0x6251, 0x64b3,0x63ff,
0x64bb,0x631e, 0x64be,0x631d, 0x64bf,0x6361, 0x64c1,0x62e5, 0x64c4,0x63b3,
0x64c7,0x62e9, 0x64ca,0x51fb, 0x64cb,0x6321, 0x64cf,0x64ce, 0x64d4,0x62c5,
0x64da,0x636e, 0x64e0,0x6324, 0x64e9,0x6363, 0x64ec,0x62df, 0x64ef,0x6448,
0x64f0,0x62e7, 0x64f1,0x6401, 0x64f2,0x63b7, 0x64f4,0x6269, 0x64f7,0x64b7,
0x64fa,0x6446, 0x64fb,0x64de, 0x64fc,0x64b8, 0x64fe,0x6270, 0x64ff,0x63b7,
0x6504,0x6445, 0x6506,0x64b5, 0x650f,0x62e2, 0x6513,0x6434, 0x6514,0x62e6,
0x6516,0x6484, 0x6517,0x6343, 0x6519,0x6400, 0x651b,0x64ba, 0x651c,0x643a,
0x651d,0x6444, 0x6522,0x6512, 0x6523,0x631b, 0x6524,0x644a, 0x6529,0x6321,
0x652a,0x6405, 0x652c,0x63fd, 0x6537,0x8003, 0x6557,0x8d25, 0x6558,0x53d9,
0x6575,0x654c, 0x6576,0x9635, 0x6578,0x6570, 0x657a,0x9a71, 0x6582,0x655b,
0x6583,0x6bd9, 0x6592,0x6591, 0x6595,0x6593, 0x65ac,0x65a9, 0x65b2,0x65ab,
0x65b7,0x65ad, 0x65bc,0x4e8e, 0x65c2,0x65d7, 0x65e1,0x65e0, 0x6607,0x5347,
0x662b,0x7166, 0x663a,0x70b3, 0x6642,0x65f6, 0x6649,0x664b, 0x665d,0x663c,
0x6665,0x7696, 0x6688,0x6655, 0x6689,0x6656, 0x6698,0x65f8, 0x66a2,0x7545,
0x66ab,0x6682, 0x66b1,0x6635, 0x66c4,0x6654, 0x66c6,0x5386, 0x66c7,0x6619,
0x66c9,0x6653, 0x66cf,0x5411, 0x66d6,0x66a7, 0x66e0,0x65f7, 0x66ec,0x6652,
0x66f8,0x4e66, 0x6703,0x4f1a, 0x6722,0x671b, 0x6727,0x80e7, 0x672e,0x672f,
0x6747,0x572c, 0x6771,0x4e1c, 0x6774,0x9528, 0x6792,0x4e2b, 0x679f,0x6a80,
0x67b4,0x62d0, 0x67df,0x6960, 0x67f5,0x6805, 0x6814,0x5951, 0x687f,0x6746,
0x6887,0x6994, 0x6894,0x6800, 0x689d,0x6761, 0x689f,0x67ad, 0x68b1,0x6346,
0x68c4,0x5f03, 0x68d6,0x67a8, 0x68d7,0x67a3, 0x68df,0x680b, 0x68e7,0x6808,
0x68f2,0x6816, 0x690f,0x6860, 0x6917,0x7887, 0x6937,0x7f04, 0x694a,0x6768,
0x6953,0x67ab, 0x6965,0x6966, 0x6968,0x6862, 0x696d,0x4e1a, 0x6975,0x6781,
0x69a6,0x5e72, 0x69aa,0x6769, 0x69ae,0x8363, 0x69bf,0x6864, 0x69c3,0x76d8,
0x69cb,0x6784, 0x69cd,0x67aa, 0x69d3,0x6760, 0x69e7,0x6920, 0x69e8,0x6901,
0x69f3,0x6868, 0x69fc,0x89c4, 0x69fe,0x5881, 0x6a01,0x6869, 0x6a02,0x4e50,
0x6a05,0x679e, 0x6a11,0x6881, 0x6a13,0x697c, 0x6a19,0x6807, 0x6a1e,0x67a2,
0x6a23,0x6837, 0x6a38,0x6734, 0x6a39,0x6811, 0x6a3a,0x6866, 0x6a46,0x65e0,
0x6a48,0x6861, 0x6a4b,0x6865, 0x6a55,0x6491, 0x6a5f,0x673a, 0x6a62,0x692d,
0x6a64,0x854a, 0x6a6b,0x6a2a, 0x6a7e,0x78b0, 0x6a81,0x6aa9, 0x6a89,0x67fd,
0x6a94,0x6863, 0x6a9c,0x6867, 0x6aa2,0x68c0, 0x6aa3,0x6a2f, 0x6aa5,0x8223,
0x6aae,0x68bc, 0x6aaf,0x53f0, 0x6ab3,0x69df, 0x6ab8,0x67e0, 0x6abb,0x69db,
0x6ac2,0x68f9, 0x6ac3,0x67dc, 0x6ad3,0x6a79, 0x6ada,0x6988, 0x6adb,0x6809,
0x6add,0x691f, 0x6ade,0x6a7c, 0x6adf,0x680e, 0x6ae5,0x6a71, 0x6ae7,0x69e0,
0x6ae8,0x680c, 0x6aea,0x67a5, 0x6aeb,0x6a65, 0x6aec,0x6987, 0x6af0,0x69d0,
0x6af3,0x680a, 0x6af8,0x6989, 0x6afa,0x68c2, 0x6afb,0x6a31, 0x6b04,0x680f,
0x6b0a,0x6743, 0x6b0b,0x68f9, 0x6b0f,0x6924, 0x6b11,0x6512, 0x6b12,0x683e,
0x6b16,0x6984, 0x6b1e,0x68c2, 0x6b2c,0x54b3, 0x6b3d,0x94a6, 0x6b4e,0x53f9,
0x6b50,0x6b27, 0x6b5b,0x655b, 0x6b5f,0x6b24, 0x6b61,0x6b22, 0x6b72,0x5c81,
0x6b77,0x5386, 0x6b78,0x5f52, 0x6b7e,0x6b81, 0x6b7f,0x6b81, 0x6b80,0x592d,
0x6b98,0x6b8b, 0x6b9e,0x6b92, 0x6ba4,0x6b87, 0x6bab,0x6b9a, 0x6bad,0x50f5,
0x6bae,0x6b93, 0x6baf,0x6ba1, 0x6bb2,0x6b7c, 0x6bba,0x6740, 0x6bbc,0x58f3,
0x6bbd,0x6dc6, 0x6bc0,0x6bc1, 0x6bc6,0x6bb4, 0x6bc9,0x533b, 0x6bd8,0x6bd7,
0x6bde,0x7eb0, 0x6be0,0x8888, 0x6be7,0x7ed2, 0x6bec,0x7403, 0x6bff,0x6bf5,
0x6c08,0x6be1, 0x6c0c,0x6c07, 0x6c23,0x6c14, 0x6c2b,0x6c22, 0x6c2c,0x6c29,
0x6c33,0x6c32, 0x6c3e,0x6cdb, 0x6c4e,0x6cdb, 0x6c59,0x6c61, 0x6c7a,0x51b3,
0x6c7b,0x6d52, 0x6c8d,0x51b1, 0x6c92,0x6ca1, 0x6c96,0x51b2, 0x6cb4,0x6eaf,
0x6cc1,0x51b5, 0x6d29,0x6cc4, 0x6d36,0x6c79, 0x6d6c,0x91cc, 0x6d79,0x6d43,
0x6d87,0x6cfe, 0x6db7,0x6d2b, 0x6dbc,0x51c9, 0x6dbd,0x6df9, 0x6dcf,0x6491,
0x6dd2,0x51c4, 0x6dda,0x6cea, 0x6de5,0x6e0c, 0x6de8,0x51c0, 0x6dea,0x6ca6,
0x6df5,0x6e0a, 0x6df6,0x6d9e, 0x6dfa,0x6d45, 0x6e19,0x6da3, 0x6e1b,0x51cf,
0x6e26,0x6da1, 0x6e2c,0x6d4b, 0x6e2e,0x83cf, 0x6e3e,0x6d51, 0x6e4a,0x51d1,
0x6e5e,0x6d48, 0x6e63,0x6cef, 0x6e67,0x6d8c, 0x6e6f,0x6c64, 0x6e88,0x6ca9,
0x6e96,0x51c6, 0x6e9d,0x6c9f, 0x6eab,0x6e29, 0x6eb0,0x7691, 0x6eb9,0x6eaf,
0x6ebc,0x6e7f, 0x6ec4,0x6ca7, 0x6ec5,0x706d, 0x6ecc,0x6da4, 0x6ece,0x8365,
0x6eec,0x6caa, 0x6eef,0x6ede, 0x6ef2,0x6e17, 0x6ef7,0x5364, 0x6ef8,0x6d52,
0x6efe,0x6eda, 0x6eff,0x6ee1, 0x6f01,0x6e14, 0x6f1a,0x6ca4, 0x6f22,0x6c49,
0x6f23,0x6d9f, 0x6f27,0x5e72, 0x6f2c,0x6e0d, 0x6f32,0x6da8, 0x6f35,0x6e86,
0x6f38,0x6e10, 0x6f3f,0x6d46, 0x6f41,0x988d, 0x6f51,0x6cfc, 0x6f54,0x6d01,
0x6f5a,0x6f47, 0x6f5b,0x6f5c, 0x6f5f,0x8204, 0x6f64,0x6da6, 0x6f6c,0x6ee9,
0x6f6f,0x6d54, 0x6f70,0x6e83, 0x6f77,0x6ed7, 0x6f7f,0x6da0, 0x6f80,0x6da9,
0x6f82,0x6f84, 0x6f86,0x6d47, 0x6f87,0x6d9d, 0x6f94,0x6d69, 0x6f97,0x6da7,
0x6fa0,0x6e11, 0x6fa3,0x6d63, 0x6fa4,0x6cfd, 0x6fa6,0x6eea, 0x6fa9,0x6cf6,
0x6fab,0x85d5, 0x6fae,0x6d4d, 0x6fb1,0x6dc0, 0x6fc1,0x6d4a, 0x6fc3,0x6d53,
0x6fc7,0x6da9, 0x6fca,0x8c41, 0x6fd4,0x5f25, 0x6fd5,0x6e7f, 0x6fd8,0x6cde,
0x6fdb,0x8499, 0x6fdf,0x6d4e, 0x6fe4,0x6d9b, 0x6feb,0x6ee5, 0x6fec,0x6d5a,
0x6ff0,0x6f4d, 0x6ff1,0x6ee8, 0x6ffa,0x6e85, 0x6ffc,0x6cfa, 0x6ffe,0x6ee4,
0x7005,0x6ee2, 0x7006,0x6e0e, 0x7009,0x6cfb, 0x700b,0x6e16, 0x700f,0x6d4f,
0x7015,0x6fd2, 0x7018,0x6cf8, 0x701d,0x6ca5, 0x701f,0x6f47, 0x7020,0x6f46,
0x7026,0x6f74, 0x7027,0x6cf7, 0x7028,0x6fd1, 0x7030,0x5f25, 0x7032,0x6f4b,
0x703e,0x6f9c, 0x7043,0x6ca3, 0x7044,0x6ee0, 0x7051,0x6d12, 0x7055,0x6f13,
0x7058,0x6ee9, 0x705d,0x704f, 0x7060,0x6f24, 0x7063,0x6e7e, 0x7064,0x6ee6,
0x7068,0x8d63, 0x7069,0x6edf, 0x707d,0x707e, 0x70a4,0x7167, 0x70ba,0x4e3a,
0x70cf,0x4e4c, 0x70f4,0x70c3, 0x7121,0x65e0, 0x7123,0x7092, 0x7146,0x867e,
0x7147,0x8f89, 0x7149,0x70bc, 0x7152,0x709c, 0x7156,0x6696, 0x7159,0x70df,
0x7162,0x8315, 0x7165,0x7115, 0x7169,0x70e6, 0x716c,0x7080, 0x7192,0x8367,
0x7197,0x709d, 0x71b1,0x70ed, 0x71be,0x70bd, 0x71bf,0x714c, 0x71c1,0x70e8,
0x71c4,0x7130, 0x71c8,0x706f, 0x71c9,0x7096, 0x71d0,0x78f7, 0x71d2,0x70e7,
0x71d9,0x70eb, 0x71dc,0x7116, 0x71df,0x8425, 0x71e6,0x707f, 0x71ec,0x6bc1,
0x71ed,0x70db, 0x71f4,0x70e9, 0x71fb,0x718f, 0x71fc,0x70ec, 0x71fe,0x7118,
0x71ff,0x8000, 0x720d,0x70c1, 0x7210,0x7089, 0x7213,0x7130, 0x721b,0x70c2,
0x721f,0x6b22, 0x722d,0x4e89, 0x723a,0x7237, 0x723e,0x5c14, 0x7246,0x5899,
0x724b,0x7b3a, 0x7253,0x699c, 0x7258,0x724d, 0x7260,0x5b83, 0x7274,0x62b5,
0x727d,0x7275, 0x7296,0x8366, 0x729b,0x7266, 0x72a2,0x728a, 0x72a5,0x71ac,
0x72a7,0x727a, 0x72c0,0x72b6, 0x72cc,0x7329, 0x72f9,0x72ed, 0x72fd,0x72c8,
0x730b,0x98d9, 0x7319,0x72f0, 0x7336,0x72b9, 0x733b,0x72f2, 0x7343,0x5446,
0x7344,0x72f1, 0x7345,0x72ee, 0x7349,0x699b, 0x734e,0x5956, 0x7358,0x6bd9,
0x7367,0x72f7, 0x7368,0x72ec, 0x736a,0x72ef, 0x736b,0x7303, 0x7370,0x72de,
0x7372,0x83b7, 0x7375,0x730e, 0x7377,0x72b7, 0x7378,0x517d, 0x737a,0x736d,
0x737b,0x732e, 0x737c,0x7315, 0x7380,0x7321, 0x7381,0x7303, 0x7383,0x883c,
0x7385,0x5999, 0x7386,0x5179, 0x7394,0x948f, 0x73a1,0x740a, 0x73a8,0x73cf,
0x73ea,0x572d, 0x73ee,0x4f69, 0x73fd,0x7487, 0x73fe,0x73b0, 0x740d,0x7483,
0x7416,0x76cf, 0x7431,0x96d5, 0x743a,0x73d0, 0x743f,0x73f2, 0x744b,0x73ae,
0x7463,0x7410, 0x7464,0x7476, 0x7469,0x83b9, 0x746a,0x739b, 0x746f,0x7405,
0x7485,0x7410, 0x7486,0x7403, 0x7489,0x740f, 0x749a,0x743c, 0x74a3,0x7391,
0x74a6,0x7477, 0x74b0,0x73af, 0x74bd,0x73ba, 0x74bf,0x7487, 0x74ca,0x743c,
0x74cf,0x73d1, 0x74d4,0x748e, 0x74d5,0x5f25, 0x74da,0x74d2, 0x750c,0x74ef,
0x7515,0x74ee, 0x7516,0x7f42, 0x7522,0x4ea7, 0x7526,0x82cf, 0x753f,0x6c13,
0x755d,0x4ea9, 0x7562,0x6bd5, 0x756b,0x753b, 0x756c,0x7572, 0x7570,0x5f02,
0x7576,0x5f53, 0x757d,0x7583, 0x7587,0x7574, 0x758a,0x53e0, 0x7598,0x809b,
0x75bf,0x75f1, 0x75ce,0x7b54, 0x75cf,0x75d2, 0x75d1,0x606b, 0x75d9,0x75c9,
0x75e0,0x9178, 0x75f2,0x9ebb, 0x75f3,0x9ebb, 0x75fa,0x75f9, 0x75fe,0x75b4,
0x7609,0x6108, 0x760b,0x75af, 0x760d,0x75a1, 0x7613,0x75ea, 0x7616,0x5591,
0x761e,0x7617, 0x7621,0x75ae, 0x7627,0x759f, 0x7628,0x766b, 0x763a,0x7618,
0x7642,0x7597, 0x7646,0x75e8, 0x7647,0x75eb, 0x7648,0x5e9f, 0x7649,0x7605,
0x7652,0x6108, 0x7658,0x75a0, 0x765f,0x762a, 0x7661,0x75f4, 0x7662,0x75d2,
0x7664,0x7596, 0x7665,0x75c7, 0x7669,0x765e, 0x766c,0x7663, 0x766d,0x763f,
0x766e,0x763e, 0x7670,0x75c8, 0x7671,0x762b, 0x7672,0x766b, 0x767c,0x53d1,
0x7681,0x7682, 0x768f,0x86d4, 0x769a,0x7691, 0x769b,0x777d, 0x769c,0x98a2,
0x76b0,0x75b1, 0x76b8,0x76b2, 0x76ba,0x76b1, 0x76bb,0x9f44, 0x76c3,0x676f,
0x76dc,0x76d7, 0x76de,0x76cf, 0x76e1,0x5c3d, 0x76e3,0x76d1, 0x76e4,0x76d8,
0x76e7,0x5362, 0x76ea,0x8361, 0x7725,0x7726, 0x773b,0x4ec5, 0x773e,0x4f17,
0x7744,0x77a7, 0x774f,0x56f0, 0x775c,0x7741, 0x775e,0x7750, 0x7760,0x7737,
0x776a,0x777e, 0x7787,0x772f, 0x779e,0x7792, 0x77ad,0x4e86, 0x77bc,0x7751,
0x77c7,0x8499, 0x77c9,0x9891, 0x77d3,0x80e7, 0x77d9,0x77b0, 0x77da,0x77a9,
0x77ef,0x77eb, 0x7832,0x70ae, 0x7843,0x6731, 0x7864,0x7856, 0x7868,0x7817,
0x786f,0x781a, 0x78a2,0x7823, 0x78a9,0x7855, 0x78aa,0x7827, 0x78ad,0x7800,
0x78ba,0x786e, 0x78bb,0x786e, 0x78bc,0x7801, 0x78da,0x7816, 0x78df,0x788c,
0x78e3,0x789c, 0x78e5,0x78ca, 0x78e7,0x789b, 0x78ef,0x77f6, 0x78fd,0x7857,
0x790c,0x78ca, 0x790e,0x7840, 0x7914,0x9739, 0x7919,0x788d, 0x7921,0x7934,
0x7926,0x77ff, 0x7927,0x78ca, 0x792a,0x783a, 0x792b,0x783e, 0x792c,0x77fe,
0x7931,0x783b, 0x7945,0x8884, 0x7950,0x4f51, 0x7955,0x79d8, 0x7961,0x5939,
0x797f,0x7984, 0x798d,0x7978, 0x798e,0x796f, 0x79a6,0x5fa1, 0x79aa,0x7985,
0x79ae,0x793c, 0x79b0,0x7962, 0x79b1,0x7977, 0x79bf,0x79c3, 0x79c8,0x7c7c,
0x79d6,0x53ea, 0x79fa,0x79bb, 0x7a05,0x7a0e, 0x7a08,0x79c6, 0x7a1c,0x68f1,
0x7a1f,0x7980, 0x7a2e,0x79cd, 0x7a31,0x79f0, 0x7a40,0x8c37, 0x7a4c,0x7a23,
0x7a4d,0x79ef, 0x7a4e,0x9896, 0x7a60,0x79fe, 0x7a61,0x7a51, 0x7a62,0x79fd,
0x7a68,0x9893, 0x7a69,0x7a33, 0x7a6b,0x83b7, 0x7a6d,0x7a06, 0x7a75,0x6316,
0x7a9e,0x51fc, 0x7aa9,0x7a9d, 0x7aaa,0x6d3c, 0x7aae,0x7a77, 0x7aaf,0x7a91,
0x7ab4,0x586b, 0x7ab6,0x7aad, 0x7aba,0x7aa5, 0x7ac4,0x7a9c, 0x7ac5,0x7a8d,
0x7ac7,0x7aa6, 0x7aca,0x7a83, 0x7af6,0x7ade, 0x7b46,0x7b14, 0x7b4d,0x7b0b,
0x7b66,0x7ba1, 0x7b67,0x7b15, 0x7b69,0x7b52, 0x7b6d,0x7b97, 0x7b74,0x5395,
0x7b87,0x4e2a, 0x7b8b,0x7b3a, 0x7b8e,0x7bea, 0x7b8f,0x7b5d, 0x7ba0,0x68f0,
0x7bc0,0x8282, 0x7bc4,0x8303, 0x7bc9,0x7b51, 0x7bcb,0x7ba7, 0x7bdb,0x7bac,
0x7be0,0x7b71, 0x7be4,0x7b03, 0x7be9,0x7b5b, 0x7bf3,0x7b5a, 0x7c00,0x7ba6,
0x7c0d,0x7bd3, 0x7c11,0x84d1, 0x7c1e,0x7baa, 0x7c21,0x7b80, 0x7c23,0x7bd1,
0x7c2b,0x7bab, 0x7c30,0x7b85, 0x7c37,0x6a90, 0x7c3d,0x7b7e, 0x7c3e,0x5e18,
0x7c43,0x7bee, 0x7c49,0x7b1e, 0x7c4c,0x7b79, 0x7c50,0x85e4, 0x7c5c,0x7ba8,
0x7c5f,0x7c41, 0x7c60,0x7b3c, 0x7c64,0x7b7e, 0x7c65,0x9fa0, 0x7c69,0x7b3e,
0x7c6a,0x7c16, 0x7c6c,0x7bf1, 0x7c6e,0x7ba9, 0x7c72,0x5401, 0x7c79,0x5986,
0x7ca7,0x5986, 0x7cb5,0x7ca4, 0x7cba,0x7a17, 0x7cdd,0x7cc1, 0x7cde,0x7caa,
0x7ce2,0x6a21, 0x7ce7,0x7cae, 0x7cf0,0x56e2, 0x7cf2,0x7c9d, 0x7cf4,0x7c74,
0x7cf6,0x7c9c, 0x7cfe,0x7ea0, 0x7d00,0x7eaa, 0x7d02,0x7ea3, 0x7d04,0x7ea6,
0x7d05,0x7ea2, 0x7d06,0x7ea1, 0x7d07,0x7ea5, 0x7d08,0x7ea8, 0x7d09,0x7eab,
0x7d0b,0x7eb9, 0x7d0d,0x7eb3, 0x7d10,0x7ebd, 0x7d13,0x7ebe, 0x7d14,0x7eaf,
0x7d15,0x7eb0, 0x7d17,0x7eb1, 0x7d19,0x7eb8, 0x7d1a,0x7ea7, 0x7d1b,0x7eb7,
0x7d1c,0x7ead, 0x7d21,0x7eba, 0x7d29,0x7ef8, 0x7d2e,0x624e, 0x7d30,0x7ec6,
0x7d31,0x7ec2, 0x7d32,0x7ec1, 0x7d33,0x7ec5, 0x7d39,0x7ecd, 0x7d3a,0x7ec0,
0x7d3c,0x7ecb, 0x7d3f,0x7ed0, 0x7d40,0x7ecc, 0x7d42,0x7ec8, 0x7d43,0x5f26,
0x7d44,0x7ec4, 0x7d46,0x7eca, 0x7d4e,0x7ed7, 0x7d4f,0x7ec1, 0x7d50,0x7ed3,
0x7d55,0x7edd, 0x7d56,0x7ea9, 0x7d5b,0x7ee6, 0x7d5c,0x6d01, 0x7d5e,0x7ede,
0x7d61,0x7edc, 0x7d62,0x7eda, 0x7d66,0x7ed9, 0x7d68,0x7ed2, 0x7d70,0x7ed6,
0x7d71,0x7edf, 0x7d72,0x4e1d, 0x7d73,0x7edb, 0x7d79,0x7ee2, 0x7d81,0x7ed1,
0x7d83,0x7ee1, 0x7d86,0x7ee0, 0x7d88,0x7ee8, 0x7d8d,0x7ecb, 0x7d8f,0x7ee5,
0x7d91,0x56f0, 0x7d93,0x7ecf, 0x7d9c,0x7efc, 0x7d9e,0x7f0d, 0x7da0,0x7eff,
0x7da2,0x7ef8, 0x7da3,0x7efb, 0x7dac,0x7ef6, 0x7dad,0x7ef4, 0x7db0,0x7efe,
0x7db1,0x7eb2, 0x7db2,0x7f51, 0x7db4,0x7f00, 0x7db5,0x5f69, 0x7db8,0x7eb6,
0x7db9,0x7efa, 0x7dba,0x7eee, 0x7dbb,0x7efd, 0x7dbd,0x7ef0, 0x7dbe,0x7eeb,
0x7dbf,0x7ef5, 0x7dc4,0x7ef2, 0x7dc7,0x7f01, 0x7dca,0x7d27, 0x7dcb,0x7eef,
0x7dd2,0x7eea, 0x7dd7,0x7f03, 0x7dd8,0x7f04, 0x7dd9,0x7f02, 0x7dda,0x7ebf,
0x7ddd,0x7f09, 0x7dde,0x7f0e, 0x7de0,0x7f14, 0x7de1,0x7f17, 0x7de3,0x7f18,
0x7de6,0x7f0c, 0x7de8,0x7f16, 0x7de9,0x7f13, 0x7dec,0x7f05, 0x7def,0x7eac,
0x7df1,0x7f11, 0x7df2,0x7f08, 0x7df4,0x7ec3, 0x7df6,0x7f0f, 0x7df9,0x7f07,
0x7dfb,0x81f4, 0x7e08,0x8426, 0x7e09,0x7f19, 0x7e0a,0x7f22, 0x7e0b,0x7f12,
0x7e10,0x7ec9, 0x7e11,0x7f23, 0x7e1a,0x7ee6, 0x7e1b,0x7f1a, 0x7e1d,0x7f1c,
0x7e1e,0x7f1f, 0x7e1f,0x7f1b, 0x7e23,0x53bf, 0x7e2b,0x7f1d, 0x7e2d,0x7f21,
0x7e2e,0x7f29, 0x7e2f,0x6f14, 0x7e31,0x7eb5, 0x7e32,0x7f27, 0x7e34,0x7ea4,
0x7e35,0x7f26, 0x7e36,0x7d77, 0x7e37,0x7f15, 0x7e39,0x7f25, 0x7e3d,0x603b,
0x7e3e,0x7ee9, 0x7e43,0x7ef7, 0x7e45,0x7f2b, 0x7e46,0x7f2a, 0x7e48,0x8941,
0x7e52,0x7f2f, 0x7e54,0x7ec7, 0x7e55,0x7f2e, 0x7e56,0x4f1e, 0x7e59,0x7ffb,
0x7e5a,0x7f2d, 0x7e5e,0x7ed5, 0x7e61,0x7ee3, 0x7e62,0x7f0b, 0x7e69,0x7ef3,
0x7e6a,0x7ed8, 0x7e6b,0x7cfb, 0x7e6d,0x8327, 0x7e6f,0x7f33, 0x7e70,0x7f32,
0x7e73,0x7f34, 0x7e79,0x7ece, 0x7e7c,0x7ee7, 0x7e7d,0x7f24, 0x7e7e,0x7f31,
0x7e88,0x7f2c, 0x7e8a,0x7ea9, 0x7e8c,0x7eed, 0x7e8d,0x7d2f, 0x7e8f,0x7f20,
0x7e93,0x7f28, 0x7e94,0x624d, 0x7e96,0x7ea4, 0x7e98,0x7f35, 0x7e9c,0x7f06,
0x7f3d,0x94b5, 0x7f3e,0x74f6, 0x7f48,0x575b, 0x7f4b,0x74ee, 0x7f4c,0x7f42,
0x7f4f,0x5786, 0x7f70,0x7f5a, 0x7f75,0x9a82, 0x7f77,0x7f62, 0x7f7c,0x6bd5,
0x7f83,0x5e42, 0x7f85,0x7f57, 0x7f86,0x7f74, 0x7f88,0x7f81, 0x7f8b,0x8288,
0x7f91,0x8bf1, 0x7f95,0x6302, 0x7fa2,0x7ed2, 0x7fa5,0x7f9f, 0x7fa8,0x7fa1,
0x7fa9,0x4e49, 0x7fb6,0x81bb, 0x7fd2,0x4e60, 0x7fe8,0x7fc5, 0x7feb,0x73a9,
0x7ff9,0x7fd8, 0x8011,0x4e13, 0x8021,0x9504, 0x802c,0x8027, 0x8056,0x5723,
0x805d,0x9998, 0x805e,0x95fb, 0x806f,0x8054, 0x8070,0x806a, 0x8072,0x58f0,
0x8073,0x8038, 0x8075,0x8069, 0x8076,0x8042, 0x8077,0x804c, 0x8079,0x804d,
0x807d,0x542c, 0x807e,0x804b, 0x8085,0x8083, 0x808a,0x81c6, 0x8090,0x80f3,
0x80ca,0x6710, 0x80d1,0x80a2, 0x8105,0x80c1, 0x8108,0x8109, 0x8115,0x8118,
0x811b,0x80eb, 0x8123,0x5507, 0x8125,0x8118, 0x8129,0x4fee, 0x812b,0x8131,
0x8139,0x80c0, 0x813a,0x8106, 0x814e,0x80be, 0x8161,0x8136, 0x8166,0x8111,
0x816b,0x80bf, 0x8173,0x811a, 0x8178,0x80a0, 0x8183,0x817d, 0x819a,0x80a4,
0x81a0,0x80f6, 0x81a9,0x817b, 0x81ac,0x8106, 0x81bd,0x80c6, 0x81be,0x810d,
0x81bf,0x8113, 0x81c9,0x8138, 0x81cd,0x8110, 0x81cf,0x8191, 0x81d5,0x8198,
0x81d8,0x814a, 0x81d9,0x80ed, 0x81da,0x80ea, 0x81dd,0x88f8, 0x81df,0x810f,
0x81e0,0x8114, 0x81e2,0x81dc, 0x81e5,0x5367, 0x81e8,0x4e34, 0x81fa,0x53f0,
0x8207,0x4e0e, 0x8208,0x5174, 0x8209,0x4e3e, 0x820a,0x65e7, 0x8216,0x94fa,
0x8259,0x8231, 0x8264,0x8223, 0x8266,0x8230, 0x826b,0x823b, 0x8271,0x8270,
0x8277,0x8273, 0x8278,0x8349, 0x8290,0x82c4, 0x8294,0x5349, 0x82bb,0x520d,
0x82c3,0x8307, 0x82e7,0x82ce, 0x8316,0x835e, 0x8332,0x5179, 0x8337,0x8585,
0x834a,0x8346, 0x834c,0x62e3, 0x8373,0x8c46, 0x838a,0x5e84, 0x8396,0x830e,
0x8399,0x8347, 0x83a2,0x835a, 0x83a4,0x8c46, 0x83a7,0x82cb, 0x83c9,0x7eff,
0x83d1,0x707e, 0x83e2,0x62b1, 0x83ef,0x534e, 0x8407,0x82cc, 0x840a,0x83b1,
0x842c,0x4e07, 0x8435,0x83b4, 0x8437,0x5ced, 0x8449,0x53f6, 0x8452,0x836d,
0x8460,0x53c2, 0x8466,0x82c7, 0x846e,0x846d, 0x846f,0x836f, 0x8477,0x8364,
0x847e,0x852b, 0x8494,0x83b3, 0x849e,0x8385, 0x84a8,0x831c, 0x84a9,0x83f9,
0x84bc,0x82cd, 0x84c0,0x836a, 0x84c6,0x5e2d, 0x84cb,0x76d6, 0x84e8,0x839c,
0x84ee,0x83b2, 0x84ef,0x82c1, 0x84f1,0x8d2b, 0x84f4,0x83bc, 0x84fb,0x827a,
0x84fd,0x835c, 0x8506,0x83f1, 0x8514,0x535c, 0x8515,0x8482, 0x851e,0x848c,
0x8523,0x848b, 0x8525,0x8471, 0x8526,0x8311, 0x852d,0x836b, 0x8541,0x8368,
0x8546,0x8487, 0x854e,0x835e, 0x8553,0x82b8, 0x8555,0x83b8, 0x8558,0x835b,
0x8562,0x8489, 0x8569,0x8361, 0x856a,0x829c, 0x856d,0x8427, 0x8577,0x84e3,
0x8580,0x8574, 0x8588,0x835f, 0x8589,0x79fd, 0x858a,0x84df, 0x858c,0x8297,
0x8591,0x59dc, 0x8594,0x8537, 0x8599,0x5243, 0x859f,0x83b6, 0x85a6,0x8350,
0x85a9,0x8428, 0x85b6,0x57cb, 0x85ba,0x8360, 0x85c2,0x4e1b, 0x85c7,0x84e3,
0x85cd,0x84dd, 0x85ce,0x8369, 0x85dd,0x827a, 0x85e5,0x836f, 0x85ea,0x85ae,
0x85f6,0x82c8, 0x85f9,0x853c, 0x85fa,0x853a, 0x8604,0x8572, 0x8606,0x82a6,
0x8607,0x82cf, 0x8609,0x7816, 0x860a,0x8574, 0x860b,0x82f9, 0x8617,0x8616,
0x861a,0x85d3, 0x861c,0x83ca, 0x861e,0x8539, 0x8620,0x8537, 0x8622,0x830f,
0x862a,0x863c, 0x862d,0x5170, 0x863a,0x84e0, 0x863f,0x841d, 0x8653,0x8327,
0x8655,0x5904, 0x8656,0x5b93, 0x865b,0x865a, 0x865c,0x864f, 0x865f,0x53f7,
0x8667,0x4e8f, 0x866f,0x866c, 0x8685,0x8695, 0x8696,0x8788, 0x869a,0x86a7,
0x86a1,0x869d, 0x86a5,0x86d4, 0x86bf,0x547c, 0x86e3,0x9c92, 0x86fa,0x86f1,
0x86fb,0x8715, 0x8706,0x86ac, 0x870b,0x8793, 0x8711,0x86cb, 0x8728,0x8776,
0x873a,0x9713, 0x8755,0x8680, 0x875e,0x7338, 0x875f,0x732c, 0x8761,0x8815,
0x8766,0x867e, 0x8768,0x8671, 0x876f,0x733f, 0x8773,0x73b3, 0x8778,0x8717,
0x8784,0x86f3, 0x8798,0x8681, 0x879e,0x8682, 0x87a2,0x8424, 0x87bb,0x877c,
0x87be,0x8693, 0x87c4,0x86f0, 0x87c8,0x8748, 0x87e3,0x866e, 0x87ec,0x8749,
0x87ef,0x86f2, 0x87f2,0x866b, 0x87f6,0x86cf, 0x87f7,0x87b3, 0x87fa,0x87ee,
0x87fb,0x8681, 0x87ff,0x881b, 0x8805,0x8747, 0x8806,0x867f, 0x880d,0x874e,
0x8810,0x86f4, 0x8811,0x877e, 0x8814,0x869d, 0x881f,0x8721, 0x8823,0x86ce,
0x8831,0x86ca, 0x8836,0x8695, 0x883b,0x86ee, 0x884a,0x8511, 0x8853,0x672f,
0x8855,0x540c, 0x8856,0x5f04, 0x885b,0x536b, 0x885d,0x51b2, 0x8879,0x53ea,
0x8889,0x70ab, 0x8893,0x888d, 0x8898,0x7617, 0x889e,0x886e, 0x889f,0x7c97,
0x88ba,0x5939, 0x88ca,0x8885, 0x88cc,0x5939, 0x88cf,0x91cc, 0x88dc,0x8865,
0x88dd,0x88c5, 0x88e1,0x91cc, 0x88fd,0x5236, 0x8907,0x590d, 0x890e,0x8896,
0x892d,0x8885, 0x8932,0x88e4, 0x8933,0x88e2, 0x8935,0x7f21, 0x8938,0x891b,
0x893b,0x4eb5, 0x8946,0x5e5e, 0x8949,0x88e5, 0x8956,0x8884, 0x895d,0x88e3,
0x8960,0x88c6, 0x8962,0x8892, 0x8964,0x8934, 0x896a,0x889c, 0x896d,0x7f2c,
0x896f,0x886c, 0x8972,0x88ad, 0x8988,0x6838, 0x898b,0x89c1, 0x898f,0x89c4,
0x8993,0x89c5, 0x8995,0x2171, 0x8996,0x89c6, 0x8998,0x89c7, 0x899c,0x773a,
0x89a1,0x89cb, 0x89a6,0x89ce, 0x89aa,0x4eb2, 0x89ac,0x89ca, 0x89af,0x89cf,
0x89b2,0x89d0, 0x89b7,0x89d1, 0x89ba,0x89c9, 0x89bd,0x89c8, 0x89bf,0x89cc,
0x89c0,0x89c2, 0x89d4,0x7b4b, 0x89dd,0x62b5, 0x89f4,0x89de, 0x89f6,0x89ef,
0x89f8,0x89e6, 0x8a02,0x8ba2, 0x8a03,0x8ba3, 0x8a08,0x8ba1, 0x8a0a,0x8baf,
0x8a0c,0x8ba7, 0x8a0e,0x8ba8, 0x8a0f,0x5401, 0x8a10,0x8ba6, 0x8a13,0x8bad,
0x8a15,0x8baa, 0x8a16,0x8bab, 0x8a17,0x6258, 0x8a18,0x8bb0, 0x8a1b,0x8bb9,
0x8a1d,0x8bb6, 0x8a1f,0x8bbc, 0x8a22,0x6b23, 0x8a23,0x8bc0, 0x8a25,0x8bb7,
0x8a2a,0x8bbf, 0x8a2d,0x8bbe, 0x8a31,0x8bb8, 0x8a34,0x8bc9, 0x8a36,0x8bc3,
0x8a3a,0x8bca, 0x8a3b,0x6ce8, 0x8a3c,0x8bc1, 0x8a3f,0x8a3e, 0x8a41,0x8bc2,
0x8a46,0x8bcb, 0x8a4e,0x8bb5, 0x8a50,0x8bc8, 0x8a52,0x8bd2, 0x8a54,0x8bcf,
0x8a55,0x8bc4, 0x8a56,0x8bd0, 0x8a58,0x8bce, 0x8a5b,0x8bc5, 0x8a5e,0x8bcd,
0x8a60,0x548f, 0x8a61,0x8be9, 0x8a62,0x8be2, 0x8a63,0x8be3, 0x8a66,0x8bd5,
0x8a69,0x8bd7, 0x8a6b,0x8be7, 0x8a6c,0x8bdf, 0x8a6d,0x8be1, 0x8a6e,0x8be0,
0x8a70,0x8bd8, 0x8a71,0x8bdd, 0x8a72,0x8be5, 0x8a73,0x8be6, 0x8a75,0x8bdc,
0x8a7c,0x8bd9, 0x8a7f,0x8bd6, 0x8a84,0x8bd4, 0x8a85,0x8bdb, 0x8a86,0x8bd3,
0x8a87,0x5938, 0x8a8c,0x5fd7, 0x8a8d,0x8ba4, 0x8a91,0x8bf3, 0x8a92,0x8bf6,
0x8a95,0x8bde, 0x8a96,0x6096, 0x8a98,0x8bf1, 0x8a9a,0x8bee, 0x8a9e,0x8bed,
0x8aa0,0x8bda, 0x8aa1,0x8beb, 0x8aa3,0x8bec, 0x8aa4,0x8bef, 0x8aa5,0x8bf0,
0x8aa6,0x8bf5, 0x8aa8,0x8bf2, 0x8aaa,0x8bf4, 0x8ab0,0x8c01, 0x8ab2,0x8bfe,
0x8ab6,0x8c07, 0x8ab9,0x8bfd, 0x8abc,0x8c0a, 0x8abf,0x8c03, 0x8ac2,0x8c04,
0x8ac4,0x8c06, 0x8ac7,0x8c08, 0x8ac9,0x8bff, 0x8acb,0x8bf7, 0x8acd,0x8be4,
0x8acf,0x8bf9, 0x8ad1,0x8bfc, 0x8ad2,0x8c05, 0x8ad6,0x8bba, 0x8ad7,0x8c02,
0x8adb,0x8c00, 0x8adc,0x8c0d, 0x8ade,0x8c1d, 0x8ae0,0x55a7, 0x8ae2,0x8be8,
0x8ae4,0x8c14, 0x8ae6,0x8c1b, 0x8ae7,0x8c10, 0x8aeb,0x8c0f, 0x8aed,0x8c15,
0x8aee,0x8c18, 0x8af1,0x8bb3, 0x8af3,0x8c19, 0x8af6,0x8c0c, 0x8af7,0x8bbd,
0x8af8,0x8bf8, 0x8afa,0x8c1a, 0x8afc,0x8c16, 0x8afe,0x8bfa, 0x8b00,0x8c0b,
0x8b01,0x8c12, 0x8b02,0x8c13, 0x8b04,0x8a8a, 0x8b05,0x8bcc, 0x8b0a,0x8c0e,
0x8b0e,0x8c1c, 0x8b10,0x8c27, 0x8b14,0x8c11, 0x8b16,0x8c21, 0x8b17,0x8c24,
0x8b19,0x8c26, 0x8b1a,0x8c25, 0x8b1b,0x8bb2, 0x8b1d,0x8c22, 0x8b20,0x8c23,
0x8b28,0x8c1f, 0x8b2b,0x8c2a, 0x8b2c,0x8c2c, 0x8b2e,0x5567, 0x8b2f,0x8bc5,
0x8b33,0x8bb4, 0x8b39,0x8c28, 0x8b3c,0x547c, 0x8b3e,0x8c29, 0x8b41,0x54d7,
0x8b46,0x563b, 0x8b48,0x619d, 0x8b49,0x8bc1, 0x8b4b,0x8c30, 0x8b4e,0x8c32,
0x8b4f,0x8ba5, 0x8b54,0x64b0, 0x8b56,0x8c2e, 0x8b58,0x8bc6, 0x8b59,0x8c2f,
0x8b5a,0x8c2d, 0x8b5c,0x8c31, 0x8b5f,0x566a, 0x8b63,0x9a8c, 0x8b6b,0x8c35,
0x8b6d,0x6bc1, 0x8b6f,0x8bd1, 0x8b70,0x8bae, 0x8b74,0x8c34, 0x8b77,0x62a4,
0x8b7d,0x8a89, 0x8b7e,0x8c2b, 0x8b80,0x8bfb, 0x8b85,0x5ba1, 0x8b8a,0x53d8,
0x8b8c,0x5bb4, 0x8b8e,0x96e0, 0x8b92,0x8c17, 0x8b93,0x8ba9, 0x8b95,0x8c30,
0x8b96,0x8c36, 0x8b99,0x6b22, 0x8b9a,0x8d5e, 0x8b9c,0x8c20, 0x8b9e,0x8c33,
0x8c3f,0x6eaa, 0x8c48,0x5c82, 0x8c4e,0x7ad6, 0x8c50,0x4e30, 0x8c54,0x8273,
0x8c6c,0x732a, 0x8c8d,0x72f8, 0x8c93,0x732b, 0x8c9d,0x8d1d, 0x8c9e,0x8d1e,
0x8ca0,0x8d1f, 0x8ca1,0x8d22, 0x8ca2,0x8d21, 0x8ca4,0x72b4, 0x8ca7,0x8d2b,
0x8ca8,0x8d27, 0x8ca9,0x8d29, 0x8caa,0x8d2a, 0x8cab,0x8d2f, 0x8cac,0x8d23,
0x8caf,0x8d2e, 0x8cb0,0x8d33, 0x8cb2,0x8d40, 0x8cb3,0x8d30, 0x8cb4,0x8d35,
0x8cb6,0x8d2c, 0x8cb7,0x4e70, 0x8cb8,0x8d37, 0x8cba,0x8d36, 0x8cbb,0x8d39,
0x8cbc,0x8d34, 0x8cbd,0x8d3b, 0x8cbf,0x8d38, 0x8cc0,0x8d3a, 0x8cc1,0x8d32,
0x8cc2,0x8d42, 0x8cc3,0x8d41, 0x8cc4,0x8d3f, 0x8cc5,0x8d45, 0x8cc7,0x8d44,
0x8cc8,0x8d3e, 0x8cca,0x8d3c, 0x8cd1,0x8d48, 0x8cd2,0x8d4a, 0x8cd3,0x5bbe,
0x8cd5,0x8d47, 0x8cda,0x8d49, 0x8cdc,0x8d50, 0x8cdd,0x741b, 0x8cde,0x8d4f,
0x8ce0,0x8d54, 0x8ce1,0x8d53, 0x8ce2,0x8d24, 0x8ce3,0x5356, 0x8ce4,0x8d31,
0x8ce6,0x8d4b, 0x8ce7,0x8d55, 0x8cea,0x8d28, 0x8cec,0x8d26, 0x8ced,0x8d4c,
0x8cf4,0x8d56, 0x8cf8,0x5269, 0x8cfa,0x8d5a, 0x8cfb,0x8d59, 0x8cfc,0x8d2d,
0x8cfd,0x8d5b, 0x8cfe,0x8d5c, 0x8d04,0x8d3d, 0x8d05,0x8d58, 0x8d08,0x8d60,
0x8d0a,0x8d5e, 0x8d0d,0x8d61, 0x8d0f,0x8d62, 0x8d10,0x8d46, 0x8d13,0x8d43,
0x8d16,0x8d4e, 0x8d17,0x8d5d, 0x8d1b,0x8d63, 0x8d78,0x8d76, 0x8d95,0x8d76,
0x8d99,0x8d75, 0x8da8,0x8d8b, 0x8dae,0x8e81, 0x8db2,0x8db1, 0x8de1,0x8ff9,
0x8df0,0x80fc, 0x8df4,0x8e29, 0x8dfc,0x5c40, 0x8e10,0x8df5, 0x8e21,0x8737,
0x8e30,0x903e, 0x8e34,0x8e0a, 0x8e4c,0x8dc4, 0x8e53,0x905b, 0x8e55,0x8df8,
0x8e5a,0x8d9f, 0x8e5d,0x5c63, 0x8e5e,0x8e2f, 0x8e5f,0x8ff9, 0x8e60,0x8dd6,
0x8e62,0x8e2f, 0x8e63,0x8e52, 0x8e64,0x8e2a, 0x8e67,0x7cdf, 0x8e78,0x8e8f,
0x8e7a,0x8df7, 0x8e82,0x8e0f, 0x8e89,0x8db8, 0x8e8a,0x8e0c, 0x8e8b,0x8dfb,
0x8e8d,0x8dc3, 0x8e91,0x8e2f, 0x8e92,0x8dde, 0x8e93,0x8e2c, 0x8e95,0x8e70,
0x8e9a,0x8df9, 0x8ea1,0x8e51, 0x8ea5,0x8e7f, 0x8ea6,0x8e9c, 0x8eaa,0x8e8f,
0x8ec0,0x8eaf, 0x8eca,0x8f66, 0x8ecb,0x8f67, 0x8ecc,0x8f68, 0x8ecd,0x519b,
0x8ed2,0x8f69, 0x8ed4,0x8f6b, 0x8edb,0x8f6d, 0x8edf,0x8f6f, 0x8eeb,0x8f78,
0x8ef6,0x8f6d, 0x8ef8,0x8f74, 0x8ef9,0x8f75, 0x8efa,0x8f7a, 0x8efb,0x8f72,
0x8efc,0x8f76, 0x8efe,0x8f7c, 0x8f03,0x8f83, 0x8f05,0x8f82, 0x8f07,0x8f81,
0x8f09,0x8f7d, 0x8f0a,0x8f7e, 0x8f12,0x8f84, 0x8f13,0x633d, 0x8f14,0x8f85,
0x8f15,0x8f7b, 0x8f1b,0x8f86, 0x8f1c,0x8f8e, 0x8f1d,0x8f89, 0x8f1e,0x8f8b,
0x8f1f,0x8f8d, 0x8f20,0x9505, 0x8f25,0x8f8a, 0x8f26,0x8f87, 0x8f29,0x8f88,
0x8f2a,0x8f6e, 0x8f2f,0x8f91, 0x8f33,0x8f8f, 0x8f37,0x8f70, 0x8f38,0x8f93,
0x8f3b,0x8f90, 0x8f3e,0x8f97, 0x8f3f,0x8206, 0x8f42,0x6bc2, 0x8f44,0x8f96,
0x8f45,0x8f95, 0x8f46,0x8f98, 0x8f49,0x8f6c, 0x8f4d,0x8f99, 0x8f4e,0x8f7f,
0x8f54,0x8f9a, 0x8f5d,0x8206, 0x8f5e,0x69db, 0x8f5f,0x8f70, 0x8f61,0x8f94,
0x8f62,0x8f79, 0x8f64,0x8f73, 0x8fa6,0x529e, 0x8fad,0x8f9e, 0x8fae,0x8fab,
0x8faf,0x8fa9, 0x8fb2,0x519c, 0x8fc6,0x8fe4, 0x8ff4,0x56de, 0x8ffa,0x4e43,
0x8fff,0x9002, 0x9015,0x8ff3, 0x9019,0x8fd9, 0x9023,0x8fde, 0x9031,0x5468,
0x9032,0x8fdb, 0x9049,0x4fa6, 0x904a,0x6e38, 0x904b,0x8fd0, 0x904e,0x8fc7,
0x9054,0x8fbe, 0x9055,0x8fdd, 0x9059,0x9065, 0x905c,0x900a, 0x905d,0x6c93,
0x905e,0x9012, 0x9060,0x8fdc, 0x9069,0x9002, 0x906f,0x9041, 0x9070,0x9012,
0x9072,0x8fdf, 0x9076,0x7ed5, 0x9077,0x8fc1, 0x9078,0x9009, 0x907a,0x9057,
0x907c,0x8fbd, 0x9081,0x8fc8, 0x9084,0x8fd8, 0x9087,0x8fe9, 0x908a,0x8fb9,
0x908f,0x903b, 0x9090,0x9026, 0x90c3,0x5408, 0x90df,0x90cf, 0x90f5,0x90ae,
0x9101,0x90b6, 0x9106,0x90d3, 0x9109,0x4e61, 0x9112,0x90b9, 0x9114,0x90ac,
0x9116,0x90e7, 0x9118,0x5889, 0x9126,0x8bb8, 0x9127,0x9093, 0x912d,0x90d1,
0x9130,0x90bb, 0x9132,0x90f8, 0x9134,0x90ba, 0x9136,0x90d0, 0x913a,0x909d,
0x9148,0x90e6, 0x9183,0x814c, 0x9186,0x76cf, 0x919c,0x4e11, 0x919e,0x915d,
0x91ab,0x533b, 0x91ac,0x9171, 0x91b1,0x7c95, 0x91c0,0x917f, 0x91c1,0x8845,
0x91c3,0x917e, 0x91c5,0x917d, 0x91c6,0x91c7, 0x91cb,0x91ca, 0x91d0,0x5398,
0x91d3,0x9486, 0x91d4,0x9487, 0x91d5,0x948c, 0x91d7,0x948a, 0x91d8,0x9489,
0x91d9,0x948b, 0x91dd,0x9488, 0x91e3,0x9493, 0x91e4,0x9490, 0x91e6,0x6263,
0x91e7,0x948f, 0x91e9,0x9492, 0x91f4,0x710a, 0x91f5,0x9497, 0x91f7,0x948d,
0x91f9,0x9495, 0x9200,0x94af, 0x9201,0x94ab, 0x9204,0x94ad, 0x9205,0x94a5,
0x9206,0x94c5, 0x9209,0x94a0, 0x920d,0x949d, 0x9210,0x94a4, 0x9211,0x94a3,
0x9214,0x949e, 0x9215,0x94ae, 0x921e,0x94a7, 0x9223,0x9499, 0x9225,0x94ac,
0x9226,0x949b, 0x9227,0x94aa, 0x922e,0x94cc, 0x9230,0x94c8, 0x9233,0x94b6,
0x9234,0x94c3, 0x9237,0x94b4, 0x9238,0x94b9, 0x9239,0x94cd, 0x923a,0x94b0,
0x923d,0x94b8, 0x923e,0x94c0, 0x923f,0x94bf, 0x9240,0x94be, 0x9245,0x949c,
0x9246,0x94bb, 0x9248,0x94ca, 0x9249,0x94c9, 0x924b,0x5228, 0x924d,0x94cb,
0x924f,0x9504, 0x9251,0x94c2, 0x9257,0x94b3, 0x925a,0x94c6, 0x925b,0x94c5,
0x925e,0x94ba, 0x9264,0x94a9, 0x9266,0x94b2, 0x926c,0x94bc, 0x926d,0x94bd,
0x9272,0x950e, 0x9278,0x94f0, 0x927a,0x94d2, 0x927b,0x94ec, 0x927f,0x94ea,
0x9280,0x94f6, 0x9283,0x94f3, 0x9285,0x94dc, 0x9291,0x94e3, 0x9293,0x94e8,
0x9295,0x94c1, 0x9296,0x94e2, 0x9298,0x94ed, 0x929a,0x94eb, 0x929c,0x8854,
0x92a0,0x94d1, 0x92a3,0x94f7, 0x92a5,0x94f1, 0x92a6,0x94df, 0x92a8,0x94f5,
0x92a9,0x94e5, 0x92aa,0x94d5, 0x92ab,0x94ef, 0x92ac,0x94d0, 0x92b2,0x710a,
0x92b3,0x9510, 0x92b5,0x94ff, 0x92b7,0x9500, 0x92b9,0x9508, 0x92bb,0x9511,
0x92bc,0x9509, 0x92c1,0x94dd, 0x92c2,0x9545, 0x92c3,0x9512, 0x92c5,0x950c,
0x92c7,0x94a1, 0x92cc,0x94e4, 0x92cd,0x9b3b, 0x92cf,0x94d7, 0x92d2,0x950b,
0x92dd,0x950a, 0x92df,0x9513, 0x92e4,0x9504, 0x92e6,0x9514, 0x92e8,0x9507,
0x92ea,0x94fa, 0x92ee,0x94d6, 0x92ef,0x9506, 0x92f0,0x9502, 0x92f1,0x94fd,
0x92f8,0x952f, 0x92fc,0x94a2, 0x9301,0x951e, 0x9304,0x5f55, 0x9306,0x9516,
0x9308,0x9529, 0x9310,0x9525, 0x9312,0x9515, 0x9315,0x951f, 0x9318,0x9524,
0x9319,0x9531, 0x931a,0x94ee, 0x931b,0x951b, 0x931e,0x9566, 0x931f,0x952c,
0x9320,0x952d, 0x9321,0x951c, 0x9322,0x94b1, 0x9326,0x9526, 0x9328,0x951a,
0x932b,0x9521, 0x932e,0x9522, 0x932f,0x9519, 0x9333,0x9530, 0x9336,0x8868,
0x9338,0x94fc, 0x9346,0x9494, 0x9347,0x9534, 0x934b,0x9505, 0x934d,0x9540,
0x9354,0x9537, 0x9358,0x94e1, 0x935a,0x9496, 0x935b,0x953b, 0x9364,0x9538,
0x9365,0x9532, 0x936c,0x9539, 0x9370,0x953e, 0x9375,0x952e, 0x9376,0x9536,
0x937a,0x9517, 0x937c,0x9488, 0x937e,0x953a, 0x9382,0x9541, 0x938a,0x9551,
0x938c,0x9570, 0x9392,0x8028, 0x9394,0x7194, 0x9396,0x9501, 0x9397,0x67aa,
0x9398,0x9549, 0x939a,0x9524, 0x939d,0x951d, 0x93a2,0x94a8, 0x93a6,0x954f,
0x93a7,0x94e0, 0x93a9,0x94e9, 0x93aa,0x953c, 0x93ac,0x9550, 0x93ae,0x9547,
0x93b0,0x9552, 0x93b3,0x954d, 0x93b5,0x9553, 0x93c3,0x955e, 0x93c7,0x955f,
0x93c8,0x94fe, 0x93cc,0x9546, 0x93cd,0x9559, 0x93d1,0x955d, 0x93d7,0x94ff,
0x93d8,0x9535, 0x93dc,0x9557, 0x93dd,0x9558, 0x93de,0x955b, 0x93df,0x94f2,
0x93e1,0x955c, 0x93e2,0x9556, 0x93e4,0x9542, 0x93e8,0x933e, 0x93ec,0x7f45,
0x93f5,0x94e7, 0x93f7,0x9564, 0x93f9,0x956a, 0x93fd,0x9508, 0x9400,0x67dc,
0x9403,0x94d9, 0x940b,0x94f4, 0x9410,0x9563, 0x9411,0x9532, 0x9412,0x94f9,
0x9413,0x9566, 0x9414,0x9561, 0x9418,0x949f, 0x9419,0x956b, 0x9420,0x9568,
0x9428,0x9544, 0x942b,0x954c, 0x942e,0x9570, 0x9431,0x5251, 0x9432,0x956f,
0x9433,0x956d, 0x9435,0x94c1, 0x9436,0x949a, 0x9438,0x94ce, 0x943a,0x94db,
0x943f,0x9571, 0x9440,0x953f, 0x9444,0x94f8, 0x944a,0x956c, 0x944c,0x9554,
0x9451,0x9274, 0x9452,0x9274, 0x9460,0x94c4, 0x9463,0x9573, 0x9464,0x5228,
0x946a,0x7089, 0x946d,0x9567, 0x9470,0x94a5, 0x9472,0x9576, 0x9473,0x952e,
0x9475,0x7f50, 0x9477,0x954a, 0x947c,0x9523, 0x947d,0x94bb, 0x947e,0x92ae,
0x947f,0x51ff, 0x9481,0x9562, 0x9482,0x954b, 0x9577,0x957f, 0x9580,0x95e8,
0x9582,0x95e9, 0x9583,0x95ea, 0x9586,0x95eb, 0x9589,0x95ed, 0x958b,0x5f00,
0x958c,0x95f6, 0x958e,0x95f3, 0x958f,0x95f0, 0x9591,0x95f2, 0x9592,0x95f2,
0x9593,0x95f4, 0x9594,0x95f5, 0x9598,0x95f8, 0x95a1,0x9602, 0x95a3,0x9601,
0x95a4,0x9601, 0x95a5,0x9600, 0x95a8,0x95fa, 0x95a9,0x95fd, 0x95ab,0x9603,
0x95ac,0x9606, 0x95ad,0x95fe, 0x95b1,0x9605, 0x95b6,0x960a, 0x95b9,0x9609,
0x95bb,0x960e, 0x95bc,0x960f, 0x95bd,0x960d, 0x95be,0x9608, 0x95bf,0x960c,
0x95c0,0x54c4, 0x95c3,0x9612, 0x95c6,0x677f, 0x95c7,0x6697, 0x95c8,0x95f1,
0x95ca,0x9614, 0x95cb,0x9615, 0x95cc,0x9611, 0x95d0,0x9617, 0x95d4,0x9616,
0x95d5,0x9619, 0x95d6,0x95ef, 0x95dc,0x5173, 0x95de,0x961a, 0x95e1,0x9610,
0x95e2,0x8f9f, 0x95e5,0x95fc, 0x9623,0x5c79, 0x9624,0x9640, 0x9628,0x5384,
0x962c,0x5751, 0x962f,0x5740, 0x9658,0x9649, 0x965d,0x9655, 0x965e,0x5347,
0x9663,0x9635, 0x9670,0x9634, 0x9673,0x9648, 0x9678,0x9646, 0x967d,0x9633,
0x9684,0x5824, 0x9689,0x9667, 0x968a,0x961f, 0x968e,0x9636, 0x9695,0x9668,
0x969b,0x9645, 0x96a4,0x9893, 0x96a8,0x968f, 0x96aa,0x9669, 0x96ae,0x8dfb,
0x96b1,0x9690, 0x96b4,0x9647, 0x96b8,0x96b6, 0x96bb,0x53ea, 0x96cb,0x96bd,
0x96d6,0x867d, 0x96d9,0x53cc, 0x96db,0x96cf, 0x96dc,0x6742, 0x96de,0x9e21,
0x96e2,0x79bb, 0x96e3,0x96be, 0x96f0,0x6c1b, 0x96f2,0x4e91, 0x96fb,0x7535,
0x9711,0x6cbe, 0x9712,0x9634, 0x9724,0x6e9c, 0x9727,0x96fe, 0x973d,0x9701,
0x9742,0x96f3, 0x9743,0x970d, 0x9744,0x972d, 0x9748,0x7075, 0x975a,0x9753,
0x975c,0x9759, 0x9766,0x8146, 0x9768,0x9765, 0x977d,0x7eca, 0x9780,0x9f17,
0x9781,0x97b4, 0x978f,0x5de9, 0x979c,0x7ef1, 0x97a1,0x9f19, 0x97a6,0x79cb,
0x97c1,0x7f30, 0x97c3,0x9791, 0x97c6,0x5343, 0x97c9,0x97af, 0x97cb,0x97e6,
0x97cc,0x97e7, 0x97d3,0x97e9, 0x97d9,0x97ea, 0x97dc,0x97ec, 0x97dd,0x97b2,
0x97de,0x97eb, 0x97fb,0x97f5, 0x97ff,0x54cd, 0x9801,0x9875, 0x9802,0x9876,
0x9803,0x9877, 0x9805,0x9879, 0x9806,0x987a, 0x9807,0x9878, 0x9808,0x987b,
0x980a,0x987c, 0x980c,0x9882, 0x980e,0x9880, 0x980f,0x9883, 0x9810,0x9884,
0x9811,0x987d, 0x9812,0x9881, 0x9813,0x987f, 0x9816,0x6cee, 0x9817,0x9887,
0x9818,0x9886, 0x981c,0x988c, 0x9821,0x9889, 0x9824,0x9890, 0x9826,0x988f,
0x982b,0x4fef, 0x982d,0x5934, 0x9830,0x988a, 0x9837,0x9894, 0x9838,0x9888,
0x9839,0x9893, 0x983b,0x9891, 0x9846,0x9897, 0x984c,0x9898, 0x984d,0x989d,
0x984e,0x989a, 0x984f,0x989c, 0x9850,0x8be8, 0x9853,0x989b, 0x9858,0x613f,
0x9859,0x98a1, 0x985b,0x98a0, 0x985e,0x7c7b, 0x9862,0x989f, 0x9865,0x98a2,
0x9867,0x987e, 0x986b,0x98a4, 0x986f,0x663e, 0x9870,0x98a6, 0x9871,0x9885,
0x9873,0x989e, 0x9874,0x98a7, 0x98a8,0x98ce, 0x98ae,0x98d1, 0x98af,0x98d2,
0x98b1,0x53f0, 0x98b3,0x522e, 0x98b6,0x98d3, 0x98ba,0x626c, 0x98bc,0x98d5,
0x98bf,0x5e06, 0x98c4,0x98d8, 0x98c6,0x98d9, 0x98db,0x98de, 0x98e2,0x9965,
0x98e9,0x9968, 0x98ea,0x996a, 0x98eb,0x996b, 0x98ed,0x996c, 0x98ef,0x996d,
0x98f2,0x996e, 0x98f4,0x9974, 0x98fc,0x9972, 0x98fd,0x9971, 0x98fe,0x9970,
0x9903,0x997a, 0x9905,0x997c, 0x9908,0x7ccd, 0x9909,0x9977, 0x990a,0x517b,
0x990c,0x9975, 0x9911,0x997d, 0x9912,0x9981, 0x9913,0x997f, 0x9918,0x9980,
0x991a,0x80b4, 0x991b,0x9984, 0x991e,0x996f, 0x9921,0x9985, 0x9927,0x5582,
0x9928,0x9986, 0x992c,0x7cca, 0x9931,0x7cc7, 0x9933,0x9967, 0x9935,0x5582,
0x993c,0x9969, 0x993d,0x9988, 0x993e,0x998f, 0x993f,0x998a, 0x9943,0x998d,
0x9945,0x9992, 0x9947,0x996b, 0x9948,0x9990, 0x9949,0x9991, 0x994b,0x9988,
0x994c,0x9994, 0x9951,0x9965, 0x9952,0x9976, 0x9957,0x98e8, 0x995c,0x990d,
0x995e,0x998b, 0x995f,0x9977, 0x99ac,0x9a6c, 0x99ad,0x9a6d, 0x99ae,0x51af,
0x99b1,0x9a6e, 0x99b3,0x9a70, 0x99b4,0x9a6f, 0x99bd,0x7d77, 0x99c1,0x9a73,
0x99d0,0x9a7b, 0x99d1,0x9a7d, 0x99d2,0x9a79, 0x99d4,0x9a75, 0x99d5,0x9a7e,
0x99d8,0x9a80, 0x99d9,0x9a78, 0x99db,0x9a76, 0x99dd,0x9a7c, 0x99df,0x9a77,
0x99e2,0x9a88, 0x99ed,0x9a87, 0x99ee,0x9a73, 0x99f1,0x9a86, 0x99ff,0x9a8f,
0x9a01,0x9a8b, 0x9a03,0x5446, 0x9a05,0x9a93, 0x9a0d,0x9a92, 0x9a0e,0x9a91,
0x9a0f,0x9a90, 0x9a16,0x9a9b, 0x9a19,0x9a97, 0x9a23,0x9b03, 0x9a2b,0x9a9e,
0x9a2d,0x9a98, 0x9a2e,0x9a9d, 0x9a30,0x817e, 0x9a32,0x8349, 0x9a36,0x9a7a,
0x9a37,0x9a9a, 0x9a38,0x9a9f, 0x9a3e,0x9aa1, 0x9a40,0x84e6, 0x9a41,0x9a9c,
0x9a42,0x9a96, 0x9a43,0x9aa0, 0x9a44,0x9aa2, 0x9a45,0x9a71, 0x9a4a,0x9a85,
0x9a4d,0x9a81, 0x9a4f,0x9aa3, 0x9a55,0x9a84, 0x9a57,0x9a8c, 0x9a5a,0x60ca,
0x9a5b,0x9a7f, 0x9a5f,0x9aa4, 0x9a62,0x9a74, 0x9a64,0x9aa7, 0x9a65,0x9aa5,
0x9a66,0x9aa6, 0x9a6a,0x9a8a, 0x9a6b,0x9a89, 0x9aaf,0x80ae, 0x9abb,0x80ef,
0x9abe,0x9ca0, 0x9abf,0x80fc, 0x9ac6,0x818a, 0x9acf,0x9ac5, 0x9ad2,0x810f,
0x9ad4,0x4f53, 0x9ad5,0x9acc, 0x9ad6,0x9acb, 0x9ae3,0x4eff, 0x9aee,0x53d1,
0x9af1,0x2171, 0x9af3,0x9ae6, 0x9b06,0x677e, 0x9b0d,0x80e1, 0x9b12,0x7f1c,
0x9b1a,0x987b, 0x9b22,0x9b13, 0x9b25,0x6597, 0x9b27,0x95f9, 0x9b28,0x54c4,
0x9b29,0x960b, 0x9b2e,0x9604, 0x9b31,0x90c1, 0x9b4a,0x872e, 0x9b4e,0x9b49,
0x9b58,0x9b47, 0x9b5a,0x9c7c, 0x9b6f,0x9c81, 0x9b74,0x9c82, 0x9b77,0x9c7f,
0x9b7c,0x9c88, 0x9b86,0x9c9a, 0x9b90,0x9c90, 0x9b91,0x9c8d, 0x9b92,0x9c8b,
0x9b9a,0x9c92, 0x9b9e,0x9c95, 0x9baa,0x9c94, 0x9bab,0x9c9b, 0x9bad,0x9c91,
0x9bae,0x9c9c, 0x9bc0,0x9ca7, 0x9bc1,0x9ca0, 0x9bc7,0x9ca9, 0x9bc9,0x9ca4,
0x9bca,0x9ca8, 0x9bd4,0x9cbb, 0x9bd6,0x9cad, 0x9bd7,0x9c9e, 0x9bdb,0x9cb7,
0x9be1,0x9cb1, 0x9be2,0x9cb5, 0x9be4,0x9cb2, 0x9be7,0x9cb3, 0x9be8,0x9cb8,
0x9bea,0x9cae, 0x9beb,0x9cb0, 0x9bf0,0x9cb6, 0x9bfd,0x9cab, 0x9c08,0x9cbd,
0x9c09,0x9cc7, 0x9c0d,0x9cc5, 0x9c12,0x9cc6, 0x9c13,0x9cc3, 0x9c23,0x9ca5,
0x9c25,0x9ccf, 0x9c28,0x9cce, 0x9c29,0x9cd0, 0x9c2d,0x9ccd, 0x9c31,0x9ca2,
0x9c32,0x9ccc, 0x9c33,0x9cd3, 0x9c37,0x9ca6, 0x9c39,0x9ca3, 0x9c3b,0x9cd7,
0x9c3c,0x9cdb, 0x9c3e,0x9cd4, 0x9c48,0x9cd5, 0x9c49,0x9cd6, 0x9c4d,0x9c85,
0x9c52,0x9cdf, 0x9c54,0x9cdd, 0x9c56,0x9cdc, 0x9c57,0x9cde, 0x9c58,0x9c9f,
0x9c5f,0x9c8e, 0x9c60,0x810d, 0x9c63,0x9cdd, 0x9c67,0x9ce2, 0x9c6d,0x9c9a,
0x9c77,0x9cc4, 0x9c78,0x9c88, 0x9c7a,0x9ca1, 0x9ce5,0x9e1f, 0x9ce7,0x51eb,
0x9ce9,0x9e20, 0x9cf3,0x51e4, 0x9cf4,0x9e23, 0x9cf5,0x9e28, 0x9cf6,0x9e22,
0x9d03,0x89d6, 0x9d06,0x9e29, 0x9d07,0x9e28, 0x9d08,0x96c1, 0x9d09,0x9e26,
0x9d12,0x4ee4, 0x9d15,0x9e35, 0x9d1b,0x9e33, 0x9d1d,0x9e32, 0x9d1f,0x9e31,
0x9d23,0x9e2a, 0x9d26,0x9e2f, 0x9d28,0x9e2d, 0x9d2f,0x9e38, 0x9d30,0x9e39,
0x9d3b,0x9e3f, 0x9d3f,0x9e3d, 0x9d42,0x9e3a, 0x9d43,0x9e3c, 0x9d51,0x9e43,
0x9d52,0x9e46, 0x9d53,0x9e41, 0x9d5c,0x9e48, 0x9d5d,0x9e45, 0x9d60,0x9e44,
0x9d61,0x9e49, 0x9d6a,0x9e4c, 0x9d6c,0x9e4f, 0x9d6f,0x9e4e, 0x9d70,0x96d5,
0x9d72,0x9e4a, 0x9d87,0x9e2b, 0x9d89,0x9e51, 0x9d98,0x9e55, 0x9d9a,0x9e57,
0x9da9,0x9e5c, 0x9daf,0x83ba, 0x9db4,0x9e64, 0x9db8,0x5f31, 0x9dbb,0x9e58,
0x9dbc,0x9e63, 0x9dbf,0x9e5a, 0x9dc2,0x9e5e, 0x9dd3,0x9e67, 0x9dd7,0x9e25,
0x9dd9,0x9e37, 0x9dda,0x9e68, 0x9de5,0x9e36, 0x9de6,0x9e6a, 0x9def,0x9e69,
0x9df0,0x71d5, 0x9df2,0x9e6b, 0x9df3,0x9e47, 0x9df8,0x9e6c, 0x9df9,0x9e70,
0x9dfa,0x9e6d, 0x9e15,0x9e2c, 0x9e1a,0x9e66, 0x9e1b,0x9e73, 0x9e1d,0x9e42,
0x9e1e,0x9e3e, 0x9e75,0x5364, 0x9e79,0x54b8, 0x9e7a,0x9e7e, 0x9e7c,0x7877,
0x9e7d,0x76d0, 0x9e83,0x6d0b, 0x9e91,0x8c8c, 0x9e97,0x4e3d, 0x9ea4,0x7c97,
0x9ea5,0x9ea6, 0x9ea9,0x9eb8, 0x9ead,0x5305, 0x9eb5,0x9762, 0x9ebc,0x4e48,
0x9ec3,0x9ec4, 0x9ecc,0x9ec9, 0x9ede,0x70b9, 0x9ee8,0x515a, 0x9ef0,0x7f1c,
0x9ef2,0x9eea, 0x9ef4,0x9709, 0x9ef7,0x9ee9, 0x9efd,0x9efe, 0x9eff,0x9f0b,
0x9f07,0x9ccc, 0x9f09,0x9f0d, 0x9f1e,0x51ac, 0x9f34,0x9f39, 0x9f4a,0x9f50,
0x9f4b,0x658b, 0x9f4e,0x8d4d, 0x9f4f,0x9f51, 0x9f52,0x9f7f, 0x9f54,0x9f80,
0x9f59,0x9f85, 0x9f5c,0x9f87, 0x9f5f,0x9f83, 0x9f60,0x9f86, 0x9f61,0x9f84,
0x9f63,0x51fa, 0x9f66,0x9f88, 0x9f67,0x556e, 0x9f6a,0x9f8a, 0x9f6c,0x9f89,
0x9f70,0x548b, 0x9f72,0x9f8b, 0x9f76,0x816d, 0x9f77,0x9f8c, 0x9f8d,0x9f99,
0x9f90,0x5e9e, 0x9f94,0x9f9a, 0x9f95,0x9f9b, 0x9f9c,0x9f9f, 0x9fa2,0x548c,
0x9fa4,0x8c10, 0xfa0c,0x5140, 0xfe30,0xff1a, 0xfe31,0xff5c, 0xfe35,0xff08,
0xfe3f,0x2227, 0xfe40,0x2228, 0xfe50,0xff0c, 0xfe51,0x3001, 0xfe52,0xff0e,
0xfe54,0xff1b, 0xfe55,0xff1a, 0xfe56,0xff1f, 0xfe57,0xff01, 0xfe59,0xff08,
0xfe5a,0xff09, 0xfe5b,0xff5b, 0xfe5c,0xff5d, 0xfe5d,0x3014, 0xfe5e,0x3015,
0xfe5f,0xff03, 0xfe60,0xff06, 0xfe61,0xff0a, 0xfe68,0xff3c, 0xff02,0x201d,
0xff07,0x2019, 0xffe2,0x300c, 0xffe4,0x2506
};

View File

@ -1,94 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 "VXHanConvert.h"
const size_t vxSC2TCTableSize = 8189;
extern unsigned short vxSC2TCTable[];
const size_t vxTC2SCTableSize = 3059;
extern unsigned short vxTC2SCTable[];
struct VXHCData {
unsigned short key, value;
};
int VXHCCompare(const void *a, const void *b)
{
unsigned short x = ((const struct VXHCData *)a)->key, y = ((const struct VXHCData *)b)->key;
if (x == y)
return 0;
if (x < y)
return -1;
return 1;
}
unsigned short VXHCFind(unsigned key, unsigned short *table, size_t size)
{
struct VXHCData k;
k.key = key;
struct VXHCData *d = (struct VXHCData *)bsearch(&k, table, size, sizeof(struct VXHCData), VXHCCompare);
if (!d)
return 0;
return d->value;
}
unsigned short VXUCS2TradToSimpChinese(unsigned short c)
{
return VXHCFind(c, vxTC2SCTable, vxTC2SCTableSize);
}
unsigned short VXUCS2SimpToTradChinese(unsigned short c)
{
return VXHCFind(c, vxSC2TCTable, vxSC2TCTableSize);
}
@implementation VXHanConvert
+ (NSString *)convertToSimplifiedFrom:(NSString *)string NS_SWIFT_NAME(convertToSimplified(from:))
{
NSData *utf16Data = [string dataUsingEncoding:NSUTF16StringEncoding];
unsigned short *bytes = (unsigned short *)utf16Data.bytes;
for (NSInteger i = 0; i < utf16Data.length; i++) {
unsigned short c = bytes[i];
unsigned short value = VXUCS2TradToSimpChinese(c);
bytes[i] = value ? value : c;
}
return [[NSString alloc] initWithData:utf16Data encoding:NSUTF16StringEncoding];
}
+ (NSString *)convertToTraditionalFrom:(NSString *)string NS_SWIFT_NAME(convertToTraditional(from:))
{
NSData *utf16Data = [string dataUsingEncoding:NSUTF16StringEncoding];
unsigned short *bytes = (unsigned short *)utf16Data.bytes;
for (NSInteger i = 0; i < utf16Data.length; i++) {
unsigned short c = bytes[i];
unsigned short value = VXUCS2SimpToTradChinese(c);
bytes[i] = value ? value : c;
}
return [[NSString alloc] initWithData:utf16Data encoding:NSUTF16StringEncoding];
}
@end

View File

@ -1,41 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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
/// The legacy chinese conversion library from OpenVanilla.
@interface VXHanConvert : NSObject
/// Converts to Simplified Chinese from Traditional Chinese.
/// @param string The traditional Chinese text.
+ (NSString *)convertToSimplifiedFrom:(NSString *)string NS_SWIFT_NAME(convertToSimplified(from:));
/// Convert to Traditional Chinese from Simplified CHinese.
/// @param string The Simplified Chinese text.
+ (NSString *)convertToTraditionalFrom:(NSString *)string NS_SWIFT_NAME(convertToTraditional(from:));
@end
NS_ASSUME_NONNULL_END

View File

@ -1,39 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 XCTest
@testable import VXHanConvert
final class VXHanConvertTests: XCTestCase {
func testSC2TC() {
let text = "简体中文转繁体中文"
let converted = VXHanConvert.convertToTraditional(from: text)
XCTAssert(converted == "簡體中文轉繁體中文")
}
func testTC2SC() {
let text = "繁體中文轉簡體中文"
let converted = VXHanConvert.convertToSimplified(from: text)
XCTAssert(converted == "繁体中文转简体中文")
}
}

View File

@ -1,31 +0,0 @@
# OpenVanilla McBopomofo 小麥注音輸入法
## 系統需求
小麥注音輸入法可以在 macOS 10.10 以上版本運作。如果您要自行編譯小麥注音輸入法,或參與開發,您需要:
- macOS 10.15.4 Catalina 以上版本
- Xcode 12.4 以上版本
- Python 3.8 (可使用 macOS 內附的,或是使用 homebrew 等方式安裝)
由於小麥注音輸入法開始使用 SPMSwift Package Manager管理相依套件因此必須使用 Xcode 12 開發。
## 開發流程
用 Xcode 開啟 `McBopomofo.xcodeproj`,選 "McBopomofo Installer" targetbuild 完之後直接執行該安裝程式,就可以安裝小麥注音。
第一次安裝完,日後程式碼或詞庫有任何修改,只要重複上述流程,再次安裝小麥注音即可。
要注意的是 macOS 可能會限制同一次 login session 能 kill 同一個輸入法 process 的次數(安裝程式透過 kill input method process 來讓新版的輸入法生效)。如果安裝若干次後,發現程式修改的結果並沒有出現,或甚至輸入法已無法再選用,只要登出目前帳號再重新登入即可。
## 社群公約
歡迎小麥注音用戶回報問題與指教,也歡迎大家參與小麥注音開發。
首先,請參考我們在「[常見問題](https://github.com/openvanilla/McBopomofo/wiki/常見問題)」中所提「[我可以怎麼參與小麥注音?](https://github.com/openvanilla/McBopomofo/wiki/常見問題#我可以怎麼參與小麥注音)」一節的說明。
我們採用了 GitHub 的[通用社群公約](https://github.com/openvanilla/McBopomofo/blob/master/CODE_OF_CONDUCT.md)。公約的中文版請參考[這裡的翻譯](https://www.contributor-covenant.org/zh-tw/version/1/4/code-of-conduct/)。
## 軟體授權
本專案採用 MIT License 釋出,使用者可自由使用、散播本軟體,惟散播時必須完整保留版權聲明及軟體授權([詳全文](https://github.com/openvanilla/McBopomofo/blob/master/LICENSE.txt))。

View File

@ -1,258 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Cocoa
import InputMethodKit
import FSEventStreamHelper
private let kCheckUpdateAutomatically = "CheckUpdateAutomatically"
private let kNextUpdateCheckDateKey = "NextUpdateCheckDate"
private let kUpdateInfoEndpointKey = "UpdateInfoEndpoint"
private let kUpdateInfoSiteKey = "UpdateInfoSite"
private let kNextCheckInterval: TimeInterval = 86400.0
private let kTimeoutInterval: TimeInterval = 60.0
struct VersionUpdateReport {
var siteUrl: URL?
var currentShortVersion: String = ""
var currentVersion: String = ""
var remoteShortVersion: String = ""
var remoteVersion: String = ""
var versionDescription: String = ""
}
enum VersionUpdateApiResult {
case shouldUpdate(report: VersionUpdateReport)
case noNeedToUpdate
case ignored
}
enum VersionUpdateApiError: Error, LocalizedError {
case connectionError(message: String)
var errorDescription: String? {
switch self {
case .connectionError(let message):
return String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message)
}
}
}
struct VersionUpdateApi {
static func check(forced: Bool, callback: @escaping (Result<VersionUpdateApiResult, Error>) -> ()) -> URLSessionTask? {
guard let infoDict = Bundle.main.infoDictionary,
let updateInfoURLString = infoDict[kUpdateInfoEndpointKey] as? String,
let updateInfoURL = URL(string: updateInfoURLString) else {
return nil
}
let request = URLRequest(url: updateInfoURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: kTimeoutInterval)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
DispatchQueue.main.async {
forced ?
callback(.failure(VersionUpdateApiError.connectionError(message: error.localizedDescription))) :
callback(.success(.ignored))
}
return
}
do {
guard let plist = try PropertyListSerialization.propertyList(from: data ?? Data(), options: [], format: nil) as? [AnyHashable: Any],
let remoteVersion = plist[kCFBundleVersionKey] as? String,
let infoDict = Bundle.main.infoDictionary
else {
DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
}
return
}
// TODO: Validate info (e.g. bundle identifier)
// TODO: Use HTML to display change log, need a new key like UpdateInfoChangeLogURL for this
let currentVersion = infoDict[kCFBundleVersionKey as String] as? String ?? ""
let result = currentVersion.compare(remoteVersion, options: .numeric, range: nil, locale: nil)
if result != .orderedAscending {
DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
}
return
}
guard let siteInfoURLString = plist[kUpdateInfoSiteKey] as? String,
let siteInfoURL = URL(string: siteInfoURLString)
else {
DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
}
return
}
var report = VersionUpdateReport(siteUrl: siteInfoURL)
var versionDescription = ""
let versionDescriptions = plist["Description"] as? [AnyHashable: Any]
if let versionDescriptions = versionDescriptions {
var locale = "en"
let supportedLocales = ["en", "zh-Hant", "zh-Hans"]
let preferredTags = Bundle.preferredLocalizations(from: supportedLocales)
if let first = preferredTags.first {
locale = first
}
versionDescription = versionDescriptions[locale] as? String ?? versionDescriptions["en"] as? String ?? ""
if !versionDescription.isEmpty {
versionDescription = "\n\n" + versionDescription
}
}
report.currentShortVersion = infoDict["CFBundleShortVersionString"] as? String ?? ""
report.currentVersion = currentVersion
report.remoteShortVersion = plist["CFBundleShortVersionString"] as? String ?? ""
report.remoteVersion = remoteVersion
report.versionDescription = versionDescription
DispatchQueue.main.async {
callback(.success(.shouldUpdate(report: report)))
}
} catch {
DispatchQueue.main.async {
forced ? callback(.success(.noNeedToUpdate)) : callback(.success(.ignored))
}
}
}
task.resume()
return task
}
}
@objc(AppDelegate)
class AppDelegate: NSObject, NSApplicationDelegate, NonModalAlertWindowControllerDelegate {
@IBOutlet weak var window: NSWindow?
private var preferencesWindowController: PreferencesWindowController?
private var checkTask: URLSessionTask?
private var updateNextStepURL: URL?
private var fsStreamHelper = FSEventStreamHelper(path: LanguageModelManager.dataFolderPath, queue: DispatchQueue(label: "User Phrases"))
func applicationDidFinishLaunching(_ notification: Notification) {
LanguageModelManager.setupDataModelValueConverter()
LanguageModelManager.loadUserPhrases()
LanguageModelManager.loadUserPhraseReplacement()
fsStreamHelper.delegate = self
_ = fsStreamHelper.start()
if UserDefaults.standard.object(forKey: kCheckUpdateAutomatically) == nil {
UserDefaults.standard.set(true, forKey: kCheckUpdateAutomatically)
UserDefaults.standard.synchronize()
}
checkForUpdate()
}
@objc func showPreferences() {
if preferencesWindowController == nil {
preferencesWindowController = PreferencesWindowController(windowNibName: "preferences")
}
preferencesWindowController?.window?.center()
preferencesWindowController?.window?.orderFront(self)
}
@objc(checkForUpdate)
func checkForUpdate() {
checkForUpdate(forced: false)
}
@objc(checkForUpdateForced:)
func checkForUpdate(forced: Bool) {
if checkTask != nil {
// busy
return
}
// time for update?
if !forced {
if UserDefaults.standard.bool(forKey: kCheckUpdateAutomatically) == false {
return
}
let now = Date()
let date = UserDefaults.standard.object(forKey: kNextUpdateCheckDateKey) as? Date ?? now
if now.compare(date) == .orderedAscending {
return
}
}
let nextUpdateDate = Date(timeInterval: kNextCheckInterval, since: Date())
UserDefaults.standard.set(nextUpdateDate, forKey: kNextUpdateCheckDateKey)
checkTask = VersionUpdateApi.check(forced: forced) { result in
defer {
self.checkTask = nil
}
switch result {
case .success(let apiResult):
switch apiResult {
case .shouldUpdate(let report):
self.updateNextStepURL = report.siteUrl
let content = String(format: NSLocalizedString("You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@", comment: ""),
report.currentShortVersion,
report.currentVersion,
report.remoteShortVersion,
report.remoteVersion,
report.versionDescription)
NonModalAlertWindowController.shared.show(title: NSLocalizedString("New Version Available", comment: ""), content: content, confirmButtonTitle: NSLocalizedString("Visit Website", comment: ""), cancelButtonTitle: NSLocalizedString("Not Now", comment: ""), cancelAsDefault: false, delegate: self)
case .noNeedToUpdate, .ignored:
break
}
case .failure(let error):
switch error {
case VersionUpdateApiError.connectionError(let message):
let title = NSLocalizedString("Update Check Failed", comment: "")
let content = String(format: NSLocalizedString("There may be no internet connection or the server failed to respond.\n\nError message: %@", comment: ""), message)
let buttonTitle = NSLocalizedString("Dismiss", comment: "")
NonModalAlertWindowController.shared.show(title: title, content: content, confirmButtonTitle: buttonTitle, cancelButtonTitle: nil, cancelAsDefault: false, delegate: nil)
default:
break
}
}
}
}
func nonModalAlertWindowControllerDidConfirm(_ controller: NonModalAlertWindowController) {
if let updateNextStepURL = updateNextStepURL {
NSWorkspace.shared.open(updateNextStepURL)
}
updateNextStepURL = nil
}
func nonModalAlertWindowControllerDidCancel(_ controller: NonModalAlertWindowController) {
updateNextStepURL = nil
}
}
extension AppDelegate: FSEventStreamHelperDelegate {
func helper(_ helper: FSEventStreamHelper, didReceive events: [FSEventStreamHelper.Event]) {
DispatchQueue.main.async {
LanguageModelManager.loadUserPhrases()
LanguageModelManager.loadUserPhraseReplacement()
}
}
}

View File

@ -1,16 +0,0 @@
{\rtf1\ansi\ansicpg1252\cocoartf2580
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset136 system;}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
\margl1440\margr1440\vieww18280\viewh14400\viewkind0
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc\partightenfactor0
{\field{\*\fldinst{HYPERLINK "https://mcbopomofo.openvanilla.org/"}}{\fldrslt
\f0\fs24 \cf0 Website}}
\f0\fs24 \
{\field{\*\fldinst{HYPERLINK "https://github.com/openvanilla/McBopomofo/wiki/%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8A"}}{\fldrslt User Manual}}\
{\field{\*\fldinst{HYPERLINK "https://github.com/openvanilla/McBopomofo/wiki/%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C"}}{\fldrslt FAQ}}\
{\field{\*\fldinst{HYPERLINK "https://github.com/openvanilla/McBopomofo"}}{\fldrslt Source Code}}\
{\field{\*\fldinst{HYPERLINK "https://github.com/openvanilla/McBopomofo/issues"}}{\fldrslt Report Bugs}}\
{\field{\*\fldinst{HYPERLINK "https://github.com/openvanilla/McBopomofo/discussions"}}{\fldrslt Discussions}}\
{\field{\*\fldinst{HYPERLINK "https://twitter.com/McBopomofo"}}{\fldrslt Twitter @McBopomofo}}\
}

View File

@ -1,5 +0,0 @@
CFBundleName = "McBopomofo";
CFBundleDisplayName = "McBopomofo";
NSHumanReadableCopyright = "Copyright © 2011-2022 Mengjuei Hsieh et al.\nAll Rights Reserved.";
"org.openvanilla.inputmethod.McBopomofo.Bopomofo" = "Bopomofo";
"org.openvanilla.inputmethod.McBopomofo.PlainBopomofo" = "Plain Bopomofo";

View File

@ -1,94 +0,0 @@
/* No comment provided by engineer. */
"About McBopomofo…" = "About McBopomofo…";
/* No comment provided by engineer. */
"McBopomofo Preferences" = "McBopomofo Preferences";
/* No comment provided by engineer. */
"Check for Updates…" = "Check for Updates…";
/* No comment provided by engineer. */
"Update Check Failed" = "Update Check Failed";
/* No comment provided by engineer. */
"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "There may be no internet connection or the server failed to respond.\n\nError message: %@";
/* No comment provided by engineer. */
"OK" = "OK";
/* No comment provided by engineer. */
"Dismiss" = "Dismiss";
/* No comment provided by engineer. */
"New Version Available" = "New Version Available";
/* No comment provided by engineer. */
"Not Now" = "Not Now";
/* No comment provided by engineer. */
"Visit Website" = "Visit Website";
/* No comment provided by engineer. */
"You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@" = "You're currently using McBopomofo %@ (%@), a new version %@ (%@) is now available. Do you want to visit McBopomofo's website to download the version?%@";
"Chinese Conversion" = "Convert to Simplified Chinese";
"User Phrases" = "User Phrases";
"Edit User Phrases" = "Edit User Phrases";
"Reload User Phrases" = "Reload User Phrases";
"Unable to create the user phrase file." = "Unable to create the user phrase file.";
"Please check the permission of the path at \"%@\"." = "Please check the permission of the path at \"%@\".";
"Edit Excluded Phrases" = "Edit Excluded Phrases";
"Use Half-Width Punctuations" = "Use Half-Width Punctuations";
"Marking \"%@\": add a custom phrase by selecting two or more characters." = "Marking \"%@\": add a custom phrase by selecting two or more characters.";
"Marking \"%@\". Press Enter to add it as a new phrase." = "Marking \"%@\". Press Enter to add it as a new phrase.";
"The phrase being marked \"%@\" is longer than the allowed %d characters." = "The phrase being marked \"%@\" is longer than the allowed %d characters.";
"Chinese conversion on" = "Chinese conversion on";
"Chinese conversion off" = "Chinese conversion off";
"Edit Phrase Replacement Table" = "Edit Phrase Replacement Table";
"Use Phrase Replacement" = "Use Phrase Replacement";
"Candidates keys cannot be empty." = "Candidates keys cannot be empty.";
"Candidate keys can only contain Latin characters and numbers." = "Candidate keys can only contain Latin characters and numbers.";
"Candidate keys cannot contain space." = "Candidate keys cannot contain space.";
"There should not be duplicated keys." = "There should not be duplicated keys.";
"Candidate keys cannot be shorter than 4 characters." = "Candidate keys cannot be shorter than 4 characters.";
"Candidate keys cannot be longer than 15 characters." = "Candidate keys cannot be longer than 15 characters.";
"Phrase replacement mode is on. Not recommended to add user phrases." = "Phrase replacement mode is on. Not recommended to add user phrases.";
"Model-based Chinese conversion is on. Not recommended to add user phrases." = "Model-based Chinese conversion is on. Not recommended to add user phrases.";
"Half-Width Punctuation On" = "Half-Width Punctuation On";
"Half-Width Punctuation Off" = "Half-Width Punctuation Off";
"Associated Phrases" = "Associated Phrases";
"Certain Unicode symbols or characters not supported as user phrases." = "Certain Unicode symbols or characters not supported as user phrases.";
"Cursor is before \"%@\"." = "Cursor is before \"%@\".";
"Cursor is after \"%@\"." = "Cursor is after \"%@\".";
"Cursor is between \"%@\" and \"%@\"." = "Cursor is between \"%@\" and \"%@\".";
"The phrase being marked \"%@\" already exists." = "The phrase being marked \"%@\" already exists.";

View File

@ -1,296 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="494" id="495"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<menu title="AMainMenu" systemMenu="main" id="29">
<items>
<menuItem title="McBopomofo" id="56">
<menu key="submenu" title="McBopomofo" systemMenu="apple" id="57">
<items>
<menuItem title="About McBopomofo" id="58">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="236">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Services" id="131">
<menu key="submenu" title="Services" systemMenu="services" id="130"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="144">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Hide McBopomofo" keyEquivalent="h" id="134">
<connections>
<action selector="performClose:" target="-1" id="536"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="145">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="368"/>
</connections>
</menuItem>
<menuItem title="Show All" id="150">
<connections>
<action selector="unhideAllApplications:" target="-1" id="370"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="File" id="83">
<menu key="submenu" title="File" id="81">
<items>
<menuItem title="Close" keyEquivalent="w" id="73">
<connections>
<action selector="performClose:" target="-1" id="535"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="217">
<menu key="submenu" title="Edit" id="205">
<items>
<menuItem title="Undo" keyEquivalent="z" id="207">
<connections>
<action selector="undo:" target="-1" id="223"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="215">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="redo:" target="-1" id="231"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="206">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Cut" keyEquivalent="x" id="199">
<connections>
<action selector="cut:" target="-1" id="228"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="197">
<connections>
<action selector="copy:" target="-1" id="224"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="203">
<connections>
<action selector="paste:" target="-1" id="226"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="485">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="486"/>
</connections>
</menuItem>
<menuItem title="Delete" id="202">
<connections>
<action selector="delete:" target="-1" id="235"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="198">
<connections>
<action selector="selectAll:" target="-1" id="232"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="214">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Find" id="218">
<menu key="submenu" title="Find" id="220">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="209">
<connections>
<action selector="performFindPanelAction:" target="-1" id="241"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="208">
<connections>
<action selector="performFindPanelAction:" target="-1" id="487"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="213">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="488"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="221">
<connections>
<action selector="performFindPanelAction:" target="-1" id="489"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="210">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="245"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="216">
<menu key="submenu" title="Spelling and Grammar" id="200">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="204">
<connections>
<action selector="showGuessPanel:" target="-1" id="230"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="201">
<connections>
<action selector="checkSpelling:" target="-1" id="225"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="453"/>
<menuItem title="Check Spelling While Typing" id="219">
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="222"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="346">
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="347"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="454">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="456"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="348">
<menu key="submenu" title="Substitutions" id="349">
<items>
<menuItem title="Show Substitutions" id="457">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="458"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="459"/>
<menuItem title="Smart Copy/Paste" tag="1" keyEquivalent="f" id="350">
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="355"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" tag="2" keyEquivalent="g" id="351">
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="356"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="460">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="461"/>
</connections>
</menuItem>
<menuItem title="Smart Links" tag="3" keyEquivalent="G" id="354">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="357"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="462">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="463"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="450">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="451">
<items>
<menuItem title="Make Upper Case" id="452">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="464"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="465">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="468"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="466">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="467"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="211">
<menu key="submenu" title="Speech" id="212">
<items>
<menuItem title="Start Speaking" id="196">
<connections>
<action selector="startSpeaking:" target="-1" id="233"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="195">
<connections>
<action selector="stopSpeaking:" target="-1" id="227"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="19">
<menu key="submenu" title="Window" systemMenu="window" id="24">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="23">
<connections>
<action selector="performMiniaturize:" target="-1" id="37"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="239">
<connections>
<action selector="performZoom:" target="-1" id="240"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="92">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
<menuItem title="Bring All to Front" id="5">
<connections>
<action selector="arrangeInFront:" target="-1" id="39"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="154"/>
</menu>
<customObject id="494" customClass="AppDelegate"/>
<customObject id="420" customClass="NSFontManager"/>
</objects>
</document>

View File

@ -1,341 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="PreferencesWindowController">
<connections>
<outlet property="basisKeyboardLayoutButton" destination="124" id="1zl-3C-sPD"/>
<outlet property="fontSizePopUpButton" destination="90" id="108"/>
<outlet property="selectionKeyComboBox" destination="uHU-aL-du7" id="bcd-bW-W5Q"/>
<outlet property="window" destination="1" id="03n-F5-D0u"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Bopomofo Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" animationBehavior="default" id="1" userLabel="Window - Preferences">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="401" y="295" width="475" height="502"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<view key="contentView" id="2">
<rect key="frame" x="0.0" y="0.0" width="475" height="502"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="11">
<rect key="frame" x="38" y="464" width="183" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bopomofo Keyboard Layout:" id="12">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="124">
<rect key="frame" x="225" y="427" width="156" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="127">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="OtherViews" id="128"/>
</popUpButtonCell>
<connections>
<action selector="updateBasisKeyboardLayoutAction:" target="-2" id="136"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="125">
<rect key="frame" x="16" y="433" width="205" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Alphanumeric Keyboard Layout:" id="126">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="109">
<rect key="frame" x="226" y="373" width="217" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Space key chooses candidate" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="110">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.ChooseCandidateUsingSpaceKey" id="112"/>
</connections>
</button>
<comboBox verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uHU-aL-du7">
<rect key="frame" x="228" y="400" width="209" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="jQC-12-UuK">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<objectValues>
<string>Item 1</string>
<string>Item 2</string>
<string>Item 3</string>
</objectValues>
</comboBoxCell>
<connections>
<action selector="changeSelectionKeyAction:" target="-2" id="REj-7y-bbQ"/>
</connections>
</comboBox>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ETa-09-qWI">
<rect key="frame" x="38" y="406" width="183" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Selection Keys:" id="FnD-oH-El5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3">
<rect key="frame" x="225" y="458" width="132" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Standard" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="6" id="4">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="OtherViews" id="5">
<items>
<menuItem title="Standard" state="on" id="6"/>
<menuItem title="ETen" tag="1" id="7"/>
<menuItem title="Hsu" tag="2" id="8"/>
<menuItem title="ETen26" tag="3" id="9"/>
<menuItem title="IBM" tag="5" id="137"/>
<menuItem title="Hanyu Pinyin" tag="4" id="10"/>
</items>
</menu>
<connections>
<binding destination="32" name="selectedTag" keyPath="values.KeyboardLayout" id="103"/>
</connections>
</popUpButtonCell>
</popUpButton>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bE0-Lq-Pj7">
<rect key="frame" x="226" y="351" width="229" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="ESC key clears entire input buffer" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="f2j-xD-4xK">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.EscToCleanInputBuffer" id="tD5-7J-qTF"/>
</connections>
</button>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
<rect key="frame" x="63" y="316" width="155" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Show Candidate Phrase:" id="14">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23">
<rect key="frame" x="80" y="233" width="138" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Candidate List Style:" id="24">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="28">
<rect key="frame" x="84" y="186" width="134" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Candidate Text Size:" id="29">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MBE-hh-AxG">
<rect key="frame" x="48" y="41" width="173" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Chinese Conversion Engine:" id="hwF-gg-TYW">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<matrix verticalHuggingPriority="750" fixedFrame="YES" tag="1" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="15">
<rect key="frame" x="227" y="295" width="213" height="38"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="206" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
<buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" inset="2" id="18">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<cells>
<column>
<buttonCell type="radio" title="Before the cursor (like Hanin)" imagePosition="left" alignment="left" state="on" inset="2" id="16">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<buttonCell type="radio" title="After the cursor (like MS IME)" imagePosition="left" alignment="left" tag="1" inset="2" id="17">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</column>
</cells>
<connections>
<binding destination="32" name="selectedTag" keyPath="values.SelectPhraseAfterCursorAsCandidate" id="104"/>
</connections>
</matrix>
<matrix verticalHuggingPriority="750" fixedFrame="YES" tag="1" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rAU-Xz-M2R">
<rect key="frame" x="227" y="20" width="213" height="38"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="206" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
<buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" inset="2" id="ipi-25-nsn">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<cells>
<column>
<buttonCell type="radio" title="OpenCC" imagePosition="left" alignment="left" state="on" inset="2" id="gw4-Y9-fH4">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<buttonCell type="radio" title="Legacy VXHanConvert" imagePosition="left" alignment="left" tag="1" inset="2" id="Wfr-Lt-eVE">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</column>
</cells>
<connections>
<binding destination="32" name="selectedTag" keyPath="values.ChineseConversionEngine" id="qf2-qK-OJ0"/>
</connections>
</matrix>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2Lk-JF-I6D">
<rect key="frame" x="45" y="87" width="173" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Chinese Conversion Style:" id="DMf-hh-Tut">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<matrix verticalHuggingPriority="750" fixedFrame="YES" tag="1" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="11Y-7Q-irj">
<rect key="frame" x="228" y="66" width="213" height="38"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="206" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
<buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" inset="2" id="H7Y-M5-po3">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<cells>
<column>
<buttonCell type="radio" title="Convert ouput" imagePosition="left" alignment="left" state="on" inset="2" id="GhA-hR-a8V">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<buttonCell type="radio" title="Convert models" imagePosition="left" alignment="left" tag="1" inset="2" id="k2S-cq-v0o">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</column>
</cells>
<connections>
<binding destination="32" name="selectedTag" keyPath="values.ChineseConversionStyle" id="dTK-DC-XfO"/>
</connections>
</matrix>
<matrix verticalHuggingPriority="750" fixedFrame="YES" allowsEmptySelection="NO" translatesAutoresizingMaskIntoConstraints="NO" id="19">
<rect key="frame" x="227" y="212" width="207" height="38"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<size key="cellSize" width="88" height="18"/>
<size key="intercellSpacing" width="4" height="2"/>
<buttonCell key="prototype" type="radio" title="Radio" imagePosition="left" alignment="left" inset="2" id="20">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<cells>
<column>
<buttonCell type="radio" title="Vertical" imagePosition="left" alignment="left" state="on" inset="2" id="22">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<buttonCell type="radio" title="Horizontal" imagePosition="left" alignment="left" tag="1" inset="2" id="21">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</column>
</cells>
<connections>
<binding destination="32" name="selectedTag" keyPath="values.UseHorizontalCandidateList" id="105"/>
</connections>
</matrix>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="90">
<rect key="frame" x="224" y="178" width="86" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="18" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="18" imageScaling="proportionallyDown" inset="2" selectedItem="96" id="91">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" title="OtherViews" id="92">
<items>
<menuItem title="12" tag="12" id="93"/>
<menuItem title="14" tag="14" id="94"/>
<menuItem title="16" tag="16" id="95"/>
<menuItem title="18" state="on" tag="18" id="96"/>
<menuItem title="24" tag="24" id="98"/>
<menuItem title="32" tag="32" id="99"/>
<menuItem title="64" tag="64" id="100"/>
<menuItem title="96" tag="96" id="101"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<binding destination="32" name="selectedTag" keyPath="values.CandidateListTextSize" id="107"/>
</connections>
</popUpButton>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Fc2-qh-r1H">
<rect key="frame" x="226" y="131" width="218" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Check for updates automatically" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="Z9t-P0-BLF">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="32" name="value" keyPath="values.CheckUpdateAutomatically" id="6WP-5h-sHG"/>
</connections>
</button>
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="BtG-38-RSK">
<rect key="frame" x="12" y="117" width="443" height="5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</box>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BZV-O5-4za">
<rect key="frame" x="226" y="270" width="192" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Move cursor after selection" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="FT6-wb-sx1">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="32" name="enabled" keyPath="values.SelectPhraseAfterCursorAsCandidate" id="GZB-gK-2JZ"/>
<binding destination="32" name="value" keyPath="values.MoveCursorAfterSelectingCandidate" id="o5h-nT-3PL"/>
</connections>
</button>
</subviews>
</view>
<point key="canvasLocation" x="148.5" y="265"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="32"/>
<button verticalHuggingPriority="750" id="hKA-Ld-tSe">
<rect key="frame" x="0.0" y="0.0" width="231" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="選字之後自動移動游標位置" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="FG1-Le-plw">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" size="13" name="Times-Roman"/>
</buttonCell>
<point key="canvasLocation" x="151" y="-92"/>
</button>
</objects>
</document>

View File

@ -1,11 +0,0 @@
# Custom Phrases or Characters.
#
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動加詞 for usage.
#
# Add your phrases and their respective Bopomofo reading below. Use hyphen ("-")
# to connect the Bopomofo syllables.
#
# 小麥注音 ㄒㄧㄠˇ-ㄇㄞˋ-ㄓㄨˋ-ㄧㄣ
#
# Any line that starts with "#" is treated as comment.

View File

@ -1,12 +0,0 @@
# Custom Exculded Characters or Symbols (for Plain Bopomofo).
#
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞 for usage.
#
# For example, the two lines below will remove the punctuations 〈 and 《 whenever
# you type the character <:
#
# 〈 _punctuation_Standard_<
# 《 _punctuation_Standard_<
#
# Any line that starts with "#" is treated as comment.

View File

@ -1,13 +0,0 @@
# Custom Exculded Phrases or Characters.
#
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動刪詞 for usage.
#
# For example, the line below will prevent the phrase "家祠" from showing up anywhere
# whenever you type "ㄐㄧㄚ ㄘˊ":
#
# 家祠 ㄐㄧㄚ-ㄘˊ
#
# Note that you need to use a hyphen ("-") between Bopomofo syllables.
#
# Any line that starts with "#" is treated as comment.

View File

@ -1,12 +0,0 @@
# Custom Replacements File.
#
# See https://github.com/openvanilla/McBopomofo/wiki/使用手冊#手動換詞 for usage.
#
# This is an advanced feature. For example, if you add this line:
#
# 這個 呢個
#
# All instances of 這個 will be replaced with 呢個.
#
# Any line that starts with "#" is treated as comment.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
cmake-build-debug

View File

@ -1,127 +0,0 @@
//
// AssociatedPhrases.cpp
//
// Copyright (c) 2017 The McBopomofo Project.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
//
#include "AssociatedPhrases.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fstream>
#include <unistd.h>
#include "KeyValueBlobReader.h"
namespace McBopomofo {
AssociatedPhrases::AssociatedPhrases()
: fd(-1)
, data(0)
, length(0)
{
}
AssociatedPhrases::~AssociatedPhrases()
{
if (data) {
close();
}
}
const bool AssociatedPhrases::isLoaded()
{
if (data) {
return true;
}
return false;
}
bool AssociatedPhrases::open(const char *path)
{
if (data) {
return false;
}
fd = ::open(path, O_RDONLY);
if (fd == -1) {
printf("open:: file not exist");
return false;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
printf("open:: cannot open file");
return false;
}
length = (size_t)sb.st_size;
data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
if (!data) {
::close(fd);
return false;
}
KeyValueBlobReader reader(static_cast<char*>(data), length);
KeyValueBlobReader::KeyValue keyValue;
KeyValueBlobReader::State state;
while ((state = reader.Next(&keyValue)) == KeyValueBlobReader::State::HAS_PAIR) {
keyRowMap[keyValue.key].emplace_back(keyValue.key, keyValue.value);
}
return true;
}
void AssociatedPhrases::close()
{
if (data) {
munmap(data, length);
::close(fd);
data = 0;
}
keyRowMap.clear();
}
const std::vector<std::string> AssociatedPhrases::valuesForKey(const std::string& key)
{
std::vector<std::string> v;
auto iter = keyRowMap.find(key);
if (iter != keyRowMap.end()) {
const std::vector<Row>& rows = iter->second;
for (const auto& row : rows) {
std::string_view value = row.value;
v.push_back({value.data(), value.size()});
}
}
return v;
}
const bool AssociatedPhrases::hasValuesForKey(const std::string& key)
{
return keyRowMap.find(key) != keyRowMap.end();
}
}; // namespace McBopomofo

View File

@ -1,66 +0,0 @@
//
// AssociatedPhrases.h
//
// Copyright (c) 2017 The McBopomofo Project.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 ASSOCIATEDPHRASES_H
#define ASSOCIATEDPHRASES_H
#include <string>
#include <map>
#include <iostream>
#include <vector>
namespace McBopomofo {
class AssociatedPhrases
{
public:
AssociatedPhrases();
~AssociatedPhrases();
const bool isLoaded();
bool open(const char *path);
void close();
const std::vector<std::string> valuesForKey(const std::string& key);
const bool hasValuesForKey(const std::string& key);
protected:
struct Row {
Row(std::string_view& k, std::string_view& v) : key(k), value(v) {}
std::string_view key;
std::string_view value;
};
std::map<std::string_view, std::vector<Row>> keyRowMap;
int fd;
void *data;
size_t length;
};
}
#endif /* AssociatedPhrases_hpp */

View File

@ -1,54 +0,0 @@
cmake_minimum_required(VERSION 3.17)
project(McBopomofoLMLib)
set(CMAKE_CXX_STANDARD 17)
include_directories("Gramambular")
add_library(McBopomofoLMLib
KeyValueBlobReader.cpp
KeyValueBlobReader.h
ParselessPhraseDB.cpp
ParselessPhraseDB.h
ParselessLM.cpp
ParselessLM.h
PhraseReplacementMap.h
PhraseReplacementMap.cpp
UserPhrasesLM.h
UserPhrasesLM.cpp)
# Let CMake fetch Google Test for us.
# https://github.com/google/googletest/tree/main/googletest#incorporating-into-an-existing-cmake-project
include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
# Test target declarations.
add_executable(McBopomofoLMLibTest
KeyValueBlobReaderTest.cpp
ParselessLMTest.cpp
ParselessPhraseDBTest.cpp
PhraseReplacementMapTest.cpp
UserPhrasesLMTest.cpp)
target_link_libraries(McBopomofoLMLibTest gtest_main McBopomofoLMLib)
include(GoogleTest)
gtest_discover_tests(McBopomofoLMLibTest)
add_custom_target(
runTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/McBopomofoLMLibTest
)
add_dependencies(runTest McBopomofoLMLibTest)
# Benchmark target; to run, manually uncomment the lines below.
#
# find_package(benchmark)
# add_executable(ParselessLMBenchmark
# ParselessLMBenchmark.cpp)
# target_link_libraries(ParselessLMBenchmark McBopomofoLMLib benchmark::benchmark)

View File

@ -1,101 +0,0 @@
//
// Bigram.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 BIGRAM_H_
#define BIGRAM_H_
#include <vector>
#include "KeyValuePair.h"
namespace Formosa {
namespace Gramambular {
class Bigram {
public:
Bigram();
KeyValuePair preceedingKeyValue;
KeyValuePair keyValue;
double score;
bool operator==(const Bigram& another) const;
bool operator<(const Bigram& another) const;
};
inline std::ostream& operator<<(std::ostream& stream, const Bigram& gram) {
std::streamsize p = stream.precision();
stream.precision(6);
stream << "(" << gram.keyValue << "|" << gram.preceedingKeyValue << ","
<< gram.score << ")";
stream.precision(p);
return stream;
}
inline std::ostream& operator<<(std::ostream& stream,
const std::vector<Bigram>& grams) {
stream << "[" << grams.size() << "]=>{";
size_t index = 0;
for (std::vector<Bigram>::const_iterator gi = grams.begin();
gi != grams.end(); ++gi, ++index) {
stream << index << "=>";
stream << *gi;
if (gi + 1 != grams.end()) {
stream << ",";
}
}
stream << "}";
return stream;
}
inline Bigram::Bigram() : score(0.0) {}
inline bool Bigram::operator==(const Bigram& another) const {
return preceedingKeyValue == another.preceedingKeyValue &&
keyValue == another.keyValue && score == another.score;
}
inline bool Bigram::operator<(const Bigram& another) const {
if (preceedingKeyValue < another.preceedingKeyValue) {
return true;
} else if (preceedingKeyValue == another.preceedingKeyValue) {
if (keyValue < another.keyValue) {
return true;
} else if (keyValue == another.keyValue) {
return score < another.score;
}
return false;
}
return false;
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,214 +0,0 @@
//
// BlockReadingBuilder.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 BLOCKREADINGBUILDER_H_
#define BLOCKREADINGBUILDER_H_
#include <string>
#include <vector>
#include "Grid.h"
#include "LanguageModel.h"
namespace Formosa {
namespace Gramambular {
class BlockReadingBuilder {
public:
explicit BlockReadingBuilder(LanguageModel* lm);
void clear();
size_t length() const;
size_t cursorIndex() const;
void setCursorIndex(size_t newIndex);
void insertReadingAtCursor(const std::string& reading);
bool deleteReadingBeforeCursor(); // backspace
bool deleteReadingAfterCursor(); // delete
bool removeHeadReadings(size_t count);
void setJoinSeparator(const std::string& separator);
const std::string joinSeparator() const;
std::vector<std::string> readings() const;
Grid& grid();
protected:
void build();
static const std::string Join(std::vector<std::string>::const_iterator begin,
std::vector<std::string>::const_iterator end,
const std::string& separator);
// 最多使用六個字組成一個詞
static const size_t MaximumBuildSpanLength = 6;
size_t m_cursorIndex;
std::vector<std::string> m_readings;
Grid m_grid;
LanguageModel* m_LM;
std::string m_joinSeparator;
};
inline BlockReadingBuilder::BlockReadingBuilder(LanguageModel* lm)
: m_LM(lm), m_cursorIndex(0) {}
inline void BlockReadingBuilder::clear() {
m_cursorIndex = 0;
m_readings.clear();
m_grid.clear();
}
inline size_t BlockReadingBuilder::length() const { return m_readings.size(); }
inline size_t BlockReadingBuilder::cursorIndex() const { return m_cursorIndex; }
inline void BlockReadingBuilder::setCursorIndex(size_t newIndex) {
m_cursorIndex = newIndex > m_readings.size() ? m_readings.size() : newIndex;
}
inline void BlockReadingBuilder::insertReadingAtCursor(
const std::string& reading) {
m_readings.insert(m_readings.begin() + m_cursorIndex, reading);
m_grid.expandGridByOneAtLocation(m_cursorIndex);
build();
m_cursorIndex++;
}
inline std::vector<std::string> BlockReadingBuilder::readings() const {
return m_readings;
}
inline bool BlockReadingBuilder::deleteReadingBeforeCursor() {
if (!m_cursorIndex) {
return false;
}
m_readings.erase(m_readings.begin() + m_cursorIndex - 1,
m_readings.begin() + m_cursorIndex);
m_cursorIndex--;
m_grid.shrinkGridByOneAtLocation(m_cursorIndex);
build();
return true;
}
inline bool BlockReadingBuilder::deleteReadingAfterCursor() {
if (m_cursorIndex == m_readings.size()) {
return false;
}
m_readings.erase(m_readings.begin() + m_cursorIndex,
m_readings.begin() + m_cursorIndex + 1);
m_grid.shrinkGridByOneAtLocation(m_cursorIndex);
build();
return true;
}
inline bool BlockReadingBuilder::removeHeadReadings(size_t count) {
if (count > length()) {
return false;
}
for (size_t i = 0; i < count; i++) {
if (m_cursorIndex) {
m_cursorIndex--;
}
m_readings.erase(m_readings.begin(), m_readings.begin() + 1);
m_grid.shrinkGridByOneAtLocation(0);
build();
}
return true;
}
inline void BlockReadingBuilder::setJoinSeparator(
const std::string& separator) {
m_joinSeparator = separator;
}
inline const std::string BlockReadingBuilder::joinSeparator() const {
return m_joinSeparator;
}
inline Grid& BlockReadingBuilder::grid() { return m_grid; }
inline void BlockReadingBuilder::build() {
if (!m_LM) {
return;
}
size_t begin = 0;
size_t end = m_cursorIndex + MaximumBuildSpanLength;
if (m_cursorIndex < MaximumBuildSpanLength) {
begin = 0;
} else {
begin = m_cursorIndex - MaximumBuildSpanLength;
}
if (end > m_readings.size()) {
end = m_readings.size();
}
for (size_t p = begin; p < end; p++) {
for (size_t q = 1; q <= MaximumBuildSpanLength && p + q <= end; q++) {
std::string combinedReading = Join(
m_readings.begin() + p, m_readings.begin() + p + q, m_joinSeparator);
if (!m_grid.hasNodeAtLocationSpanningLengthMatchingKey(p, q,
combinedReading)) {
std::vector<Unigram> unigrams = m_LM->unigramsForKey(combinedReading);
if (unigrams.size() > 0) {
Node n(combinedReading, unigrams, std::vector<Bigram>());
m_grid.insertNode(n, p, q);
}
}
}
}
}
inline const std::string BlockReadingBuilder::Join(
std::vector<std::string>::const_iterator begin,
std::vector<std::string>::const_iterator end,
const std::string& separator) {
std::string result;
for (std::vector<std::string>::const_iterator iter = begin; iter != end;) {
result += *iter;
++iter;
if (iter != end) {
result += separator;
}
}
return result;
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,31 +0,0 @@
cmake_minimum_required(VERSION 3.17)
project(Gramambular)
set(CMAKE_CXX_STANDARD 17)
add_library(GramambularLib Bigram.h BlockReadingBuilder.h Gramambular.h Grid.h Grid.cpp KeyValuePair.h LanguageModel.h Node.h NodeAnchor.h Span.h Unigram.h Walker.h)
# Let CMake fetch Google Test for us.
# https://github.com/google/googletest/tree/main/googletest#incorporating-into-an-existing-cmake-project
include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
# Test target declarations.
add_executable(GramambularTest GramambularTest.cpp)
target_link_libraries(GramambularTest gtest_main GramambularLib)
include(GoogleTest)
gtest_discover_tests(GramambularTest)
add_custom_target(
runTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/GramambularTest
)
add_dependencies(runTest GramambularTest)

View File

@ -1,42 +0,0 @@
//
// Gramambular.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 GRAMAMBULAR_H_
#define GRAMAMBULAR_H_
#include "Bigram.h"
#include "BlockReadingBuilder.h"
#include "Grid.h"
#include "KeyValuePair.h"
#include "LanguageModel.h"
#include "Node.h"
#include "NodeAnchor.h"
#include "Span.h"
#include "Unigram.h"
#include "Walker.h"
#endif

View File

@ -1,247 +0,0 @@
// Copyright (c) 2022 and onwards Lukhnos Liu
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
#include "Gramambular.h"
#include "gtest/gtest.h"
namespace Formosa {
namespace Gramambular {
const char* SampleData = R"(
#
# The sample is from libtabe (http://sourceforge.net/projects/libtabe/)
# last updated in 2002. The project was originally initiated by
# Pai-Hsiang Hsiao in 1999.
#
# Libtabe is a frequency table of Taiwanese Mandarin words. The database
# itself is, according to the tar file, released under the BSD License.
#
-9.495858
-9.006414
-99.000000
-8.091803
-99.000000
-13.513987
-12.259095
-7.171551
-10.574273
-11.504072
-10.450457
-7.171052
-99.000000
-11.928720
-13.624335
-12.390804
˙ -3.516024
ˊ -3.516024
ˋ -3.516024
-5.809297
˙ -7.427179
-8.381971
-8.501463
ˋ -99.000000
-8.034095
-8.858181
ˋ -7.608341
ˋ -99.000000
-7.290109
ˋ -10.939895
-99.000000
ˋ -99.000000
ˋ -99.000000
-99.000000
ˋ -9.715317
ˋ -7.926683
ˋ -8.373022
-9.877580
-10.711079
-7.877973
-7.822167
-99.000000
-99.000000
-99.000000
-9.685671
ˋ -10.425662
-99.000000
-99.000000
ˋ -8.888722
ˋ -10.204425
-11.378321
-99.000000
ˋ -99.000000
ˋ -8.450826
-11.074890
-99.000000
ˋ -12.045357
-99.000000
ˋ -99.000000
ˋ -9.517568
ˋ -12.021587
-99.000000
-12.784206
ˊ -6.086515
ˇ -9.164384
ˇ -8.690941
ˇ -10.127828
ˊ -11.336864
ˊ -11.285740
ˇ -12.492933
-6.299461
ˋ -6.736613
ˋ -13.336653
ˇ -10.344678
ˊ -11.668947
ˊ -11.373044
ˋ -9.842421
)";
class SimpleLM : public LanguageModel {
public:
SimpleLM(const char* input, bool swapKeyValue = false) {
std::stringstream sstream(input);
while (sstream.good()) {
std::string line;
getline(sstream, line);
if (!line.size() || (line.size() && line[0] == '#')) {
continue;
}
std::stringstream linestream(line);
std::string col0;
std::string col1;
std::string col2;
linestream >> col0;
linestream >> col1;
linestream >> col2;
Unigram u;
if (swapKeyValue) {
u.keyValue.key = col1;
u.keyValue.value = col0;
} else {
u.keyValue.key = col0;
u.keyValue.value = col1;
}
u.score = atof(col2.c_str());
m_db[u.keyValue.key].push_back(u);
}
}
const std::vector<Bigram> bigramsForKeys(const std::string& preceedingKey,
const std::string& key) override {
return std::vector<Bigram>();
}
const std::vector<Unigram> unigramsForKey(const std::string& key) override {
std::map<std::string, std::vector<Unigram> >::const_iterator f =
m_db.find(key);
return f == m_db.end() ? std::vector<Unigram>() : (*f).second;
}
bool hasUnigramsForKey(const std::string& key) override {
std::map<std::string, std::vector<Unigram> >::const_iterator f =
m_db.find(key);
return f != m_db.end();
}
protected:
std::map<std::string, std::vector<Unigram> > m_db;
};
TEST(GramambularTest, InputTest) {
SimpleLM lm(SampleData);
BlockReadingBuilder builder(&lm);
builder.insertReadingAtCursor("ㄍㄠ");
builder.insertReadingAtCursor("ㄐㄧˋ");
builder.setCursorIndex(1);
builder.insertReadingAtCursor("ㄎㄜ");
builder.setCursorIndex(0);
builder.deleteReadingAfterCursor();
builder.insertReadingAtCursor("ㄍㄠ");
builder.setCursorIndex(builder.length());
builder.insertReadingAtCursor("ㄍㄨㄥ");
builder.insertReadingAtCursor("");
builder.insertReadingAtCursor("ㄉㄜ˙");
builder.insertReadingAtCursor("ㄋㄧㄢˊ");
builder.insertReadingAtCursor("ㄓㄨㄥ");
builder.insertReadingAtCursor("ㄐㄧㄤˇ");
builder.insertReadingAtCursor("ㄐㄧㄣ");
Walker walker(&builder.grid());
std::vector<NodeAnchor> walked =
walker.reverseWalk(builder.grid().width(), 0.0);
reverse(walked.begin(), walked.end());
std::vector<std::string> composed;
for (std::vector<NodeAnchor>::iterator wi = walked.begin();
wi != walked.end(); ++wi) {
composed.push_back((*wi).node->currentKeyValue().value);
}
ASSERT_EQ(composed,
(std::vector<std::string>{"高科技", "公司", "", "年中", "獎金"}));
}
TEST(GramambularTest, WordSegmentationTest) {
SimpleLM lm2(SampleData, true);
BlockReadingBuilder builder2(&lm2);
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
builder2.insertReadingAtCursor("");
Walker walker2(&builder2.grid());
std::vector<NodeAnchor> walked =
walker2.reverseWalk(builder2.grid().width(), 0.0);
reverse(walked.begin(), walked.end());
std::vector<std::string> segmented;
for (std::vector<NodeAnchor>::iterator wi = walked.begin();
wi != walked.end(); ++wi) {
segmented.push_back((*wi).node->currentKeyValue().key);
}
ASSERT_EQ(segmented,
(std::vector<std::string>{"高科技", "公司", "", "年終", "獎金"}));
}
} // namespace Gramambular
} // namespace Formosa

View File

@ -1,74 +0,0 @@
// Copyright (c) 2007 and onwards Lukhnos Liu
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include "Grid.h"
#include <iostream>
#include <string>
namespace Formosa {
namespace Gramambular {
std::string Grid::dumpDOT() {
std::stringstream sst;
sst << "digraph {" << std::endl;
sst << "graph [ rankdir=LR ];" << std::endl;
sst << "BOS;" << std::endl;
for (size_t p = 0; p < m_spans.size(); p++) {
Span& span = m_spans[p];
for (size_t ni = 0; ni <= span.maximumLength(); ni++) {
Node* np = span.nodeOfLength(ni);
if (np) {
if (!p) {
sst << "BOS -> " << np->currentKeyValue().value << ";" << std::endl;
}
sst << np->currentKeyValue().value << ";" << std::endl;
if (p + ni < m_spans.size()) {
Span& dstSpan = m_spans[p + ni];
for (size_t q = 0; q <= dstSpan.maximumLength(); q++) {
Node* dn = dstSpan.nodeOfLength(q);
if (dn) {
sst << np->currentKeyValue().value << " -> "
<< dn->currentKeyValue().value << ";" << std::endl;
}
}
}
if (p + ni == m_spans.size()) {
sst << np->currentKeyValue().value << " -> "
<< "EOS;" << std::endl;
}
}
}
}
sst << "EOS;" << std::endl;
sst << "}";
return sst.str();
}
} // namespace Gramambular
} // namespace Formosa

View File

@ -1,231 +0,0 @@
//
// Grid.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 GRID_H_
#define GRID_H_
#include <map>
#include <string>
#include <vector>
#include "NodeAnchor.h"
#include "Span.h"
namespace Formosa {
namespace Gramambular {
class Grid {
public:
void clear();
void insertNode(const Node& node, size_t location, size_t spanningLength);
bool hasNodeAtLocationSpanningLengthMatchingKey(size_t location,
size_t spanningLength,
const std::string& key);
void expandGridByOneAtLocation(size_t location);
void shrinkGridByOneAtLocation(size_t location);
size_t width() const;
std::vector<NodeAnchor> nodesEndingAt(size_t location);
std::vector<NodeAnchor> nodesCrossingOrEndingAt(size_t location);
// "Freeze" the node with the unigram that represents the selected candidate
// value. After this, the node that contains the unigram will always be
// evaluated to that unigram, while all other overlapping nodes will be reset
// to their initial state (that is, if any of those nodes were "frozen" or
// fixed, they will be unfrozen.)
NodeAnchor fixNodeSelectedCandidate(size_t location,
const std::string& value);
// Similar to fixNodeSelectedCandidate, but instead of "freezing" the node,
// only boost the unigram that represents the value with an overriding score.
// This has the same side effect as fixNodeSelectedCandidate, which is that
// all other overlapping nodes will be reset to their initial state.
void overrideNodeScoreForSelectedCandidate(size_t location,
const std::string& value,
float overridingScore);
std::string dumpDOT();
protected:
std::vector<Span> m_spans;
};
inline void Grid::clear() { m_spans.clear(); }
inline void Grid::insertNode(const Node& node, size_t location,
size_t spanningLength) {
if (location >= m_spans.size()) {
size_t diff = location - m_spans.size() + 1;
for (size_t i = 0; i < diff; i++) {
m_spans.push_back(Span());
}
}
m_spans[location].insertNodeOfLength(node, spanningLength);
}
inline bool Grid::hasNodeAtLocationSpanningLengthMatchingKey(
size_t location, size_t spanningLength, const std::string& key) {
if (location > m_spans.size()) {
return false;
}
const Node* n = m_spans[location].nodeOfLength(spanningLength);
if (!n) {
return false;
}
return key == n->key();
}
inline void Grid::expandGridByOneAtLocation(size_t location) {
if (!location || location == m_spans.size()) {
m_spans.insert(m_spans.begin() + location, Span());
} else {
m_spans.insert(m_spans.begin() + location, Span());
for (size_t i = 0; i < location; i++) {
// zaps overlapping spans
m_spans[i].removeNodeOfLengthGreaterThan(location - i);
}
}
}
inline void Grid::shrinkGridByOneAtLocation(size_t location) {
if (location >= m_spans.size()) {
return;
}
m_spans.erase(m_spans.begin() + location);
for (size_t i = 0; i < location; i++) {
// zaps overlapping spans
m_spans[i].removeNodeOfLengthGreaterThan(location - i);
}
}
inline size_t Grid::width() const { return m_spans.size(); }
inline std::vector<NodeAnchor> Grid::nodesEndingAt(size_t location) {
std::vector<NodeAnchor> result;
if (m_spans.size() && location <= m_spans.size()) {
for (size_t i = 0; i < location; i++) {
Span& span = m_spans[i];
if (i + span.maximumLength() >= location) {
Node* np = span.nodeOfLength(location - i);
if (np) {
NodeAnchor na;
na.node = np;
na.location = i;
na.spanningLength = location - i;
result.push_back(na);
}
}
}
}
return result;
}
inline std::vector<NodeAnchor> Grid::nodesCrossingOrEndingAt(size_t location) {
std::vector<NodeAnchor> result;
if (m_spans.size() && location <= m_spans.size()) {
for (size_t i = 0; i < location; i++) {
Span& span = m_spans[i];
if (i + span.maximumLength() >= location) {
for (size_t j = 1, m = span.maximumLength(); j <= m; j++) {
if (i + j < location) {
continue;
}
Node* np = span.nodeOfLength(j);
if (np) {
NodeAnchor na;
na.node = np;
na.location = i;
na.spanningLength = location - i;
result.push_back(na);
}
}
}
}
}
return result;
}
// For nodes found at the location, fix their currently-selected candidate using
// the supplied string value.
inline NodeAnchor Grid::fixNodeSelectedCandidate(size_t location,
const std::string& value) {
std::vector<NodeAnchor> nodes = nodesCrossingOrEndingAt(location);
NodeAnchor node;
for (auto nodeAnchor : nodes) {
auto candidates = nodeAnchor.node->candidates();
// Reset the candidate-fixed state of every node at the location.
const_cast<Node*>(nodeAnchor.node)->resetCandidate();
for (size_t i = 0, c = candidates.size(); i < c; ++i) {
if (candidates[i].value == value) {
const_cast<Node*>(nodeAnchor.node)->selectCandidateAtIndex(i);
node = nodeAnchor;
break;
}
}
}
return node;
}
inline void Grid::overrideNodeScoreForSelectedCandidate(
size_t location, const std::string& value, float overridingScore) {
std::vector<NodeAnchor> nodes = nodesCrossingOrEndingAt(location);
for (auto nodeAnchor : nodes) {
auto candidates = nodeAnchor.node->candidates();
// Reset the candidate-fixed state of every node at the location.
const_cast<Node*>(nodeAnchor.node)->resetCandidate();
for (size_t i = 0, c = candidates.size(); i < c; ++i) {
if (candidates[i].value == value) {
const_cast<Node*>(nodeAnchor.node)
->selectFloatingCandidateAtIndex(i, overridingScore);
break;
}
}
}
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,67 +0,0 @@
//
// KeyValuePair.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 KEYVALUEPAIR_H_
#define KEYVALUEPAIR_H_
#include <ostream>
#include <string>
namespace Formosa {
namespace Gramambular {
class KeyValuePair {
public:
std::string key;
std::string value;
bool operator==(const KeyValuePair& another) const;
bool operator<(const KeyValuePair& another) const;
};
inline std::ostream& operator<<(std::ostream& stream,
const KeyValuePair& pair) {
stream << "(" << pair.key << "," << pair.value << ")";
return stream;
}
inline bool KeyValuePair::operator==(const KeyValuePair& another) const {
return key == another.key && value == another.value;
}
inline bool KeyValuePair::operator<(const KeyValuePair& another) const {
if (key < another.key) {
return true;
} else if (key == another.key) {
return value < another.value;
}
return false;
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,52 +0,0 @@
//
// LanguageModel.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 LANGUAGEMODEL_H_
#define LANGUAGEMODEL_H_
#include <string>
#include <vector>
#include "Bigram.h"
#include "Unigram.h"
namespace Formosa {
namespace Gramambular {
class LanguageModel {
public:
virtual ~LanguageModel() {}
virtual const std::vector<Bigram> bigramsForKeys(
const std::string& preceedingKey, const std::string& key) = 0;
virtual const std::vector<Unigram> unigramsForKey(const std::string& key) = 0;
virtual bool hasUnigramsForKey(const std::string& key) = 0;
};
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,221 +0,0 @@
//
// Node.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 NODE_H_
#define NODE_H_
#include <limits>
#include <map>
#include <string>
#include <vector>
#include "LanguageModel.h"
namespace Formosa {
namespace Gramambular {
class Node {
public:
Node();
Node(const std::string& key, const std::vector<Unigram>& unigrams,
const std::vector<Bigram>& bigrams);
void primeNodeWithPreceedingKeyValues(
const std::vector<KeyValuePair>& keyValues);
bool isCandidateFixed() const;
const std::vector<KeyValuePair>& candidates() const;
void selectCandidateAtIndex(size_t index = 0, bool fix = true);
void resetCandidate();
void selectFloatingCandidateAtIndex(size_t index, double score);
const std::string& key() const;
double score() const;
double scoreForCandidate(const std::string& candidate) const;
const KeyValuePair currentKeyValue() const;
double highestUnigramScore() const;
protected:
const LanguageModel* m_LM;
std::string m_key;
double m_score;
std::vector<Unigram> m_unigrams;
std::vector<KeyValuePair> m_candidates;
std::map<std::string, size_t> m_valueUnigramIndexMap;
std::map<KeyValuePair, std::vector<Bigram> > m_preceedingGramBigramMap;
bool m_candidateFixed;
size_t m_selectedUnigramIndex;
friend std::ostream& operator<<(std::ostream& stream, const Node& node);
};
inline std::ostream& operator<<(std::ostream& stream, const Node& node) {
stream << "(node,key:" << node.m_key
<< ",fixed:" << (node.m_candidateFixed ? "true" : "false")
<< ",selected:" << node.m_selectedUnigramIndex << ","
<< node.m_unigrams << ")";
return stream;
}
inline Node::Node()
: m_candidateFixed(false), m_selectedUnigramIndex(0), m_score(0.0) {}
inline Node::Node(const std::string& key, const std::vector<Unigram>& unigrams,
const std::vector<Bigram>& bigrams)
: m_key(key),
m_unigrams(unigrams),
m_candidateFixed(false),
m_selectedUnigramIndex(0),
m_score(0.0) {
stable_sort(m_unigrams.begin(), m_unigrams.end(), Unigram::ScoreCompare);
if (m_unigrams.size()) {
m_score = m_unigrams[0].score;
}
size_t i = 0;
for (std::vector<Unigram>::const_iterator ui = m_unigrams.begin();
ui != m_unigrams.end(); ++ui) {
m_valueUnigramIndexMap[(*ui).keyValue.value] = i;
i++;
m_candidates.push_back((*ui).keyValue);
}
for (std::vector<Bigram>::const_iterator bi = bigrams.begin();
bi != bigrams.end(); ++bi) {
m_preceedingGramBigramMap[(*bi).preceedingKeyValue].push_back(*bi);
}
}
inline void Node::primeNodeWithPreceedingKeyValues(
const std::vector<KeyValuePair>& keyValues) {
size_t newIndex = m_selectedUnigramIndex;
double max = m_score;
if (!isCandidateFixed()) {
for (std::vector<KeyValuePair>::const_iterator kvi = keyValues.begin();
kvi != keyValues.end(); ++kvi) {
std::map<KeyValuePair, std::vector<Bigram> >::const_iterator f =
m_preceedingGramBigramMap.find(*kvi);
if (f != m_preceedingGramBigramMap.end()) {
const std::vector<Bigram>& bigrams = (*f).second;
for (std::vector<Bigram>::const_iterator bi = bigrams.begin();
bi != bigrams.end(); ++bi) {
const Bigram& bigram = *bi;
if (bigram.score > max) {
std::map<std::string, size_t>::const_iterator uf =
m_valueUnigramIndexMap.find((*bi).keyValue.value);
if (uf != m_valueUnigramIndexMap.end()) {
newIndex = (*uf).second;
max = bigram.score;
}
}
}
}
}
}
if (m_score != max) {
m_score = max;
}
if (newIndex != m_selectedUnigramIndex) {
m_selectedUnigramIndex = newIndex;
}
}
inline bool Node::isCandidateFixed() const { return m_candidateFixed; }
inline const std::vector<KeyValuePair>& Node::candidates() const {
return m_candidates;
}
inline void Node::selectCandidateAtIndex(size_t index, bool fix) {
if (index >= m_unigrams.size()) {
m_selectedUnigramIndex = 0;
} else {
m_selectedUnigramIndex = index;
}
m_candidateFixed = fix;
m_score = 99;
}
inline void Node::resetCandidate() {
m_selectedUnigramIndex = 0;
m_candidateFixed = 0;
if (m_unigrams.size()) {
m_score = m_unigrams[0].score;
}
}
inline void Node::selectFloatingCandidateAtIndex(size_t index, double score) {
if (index >= m_unigrams.size()) {
m_selectedUnigramIndex = 0;
} else {
m_selectedUnigramIndex = index;
}
m_candidateFixed = false;
m_score = score;
}
inline const std::string& Node::key() const { return m_key; }
inline double Node::score() const { return m_score; }
inline double Node::scoreForCandidate(const std::string& candidate) const {
for (auto unigram : m_unigrams) {
if (unigram.keyValue.value == candidate) {
return unigram.score;
}
}
return 0.0;
}
inline double Node::highestUnigramScore() const {
if (m_unigrams.empty()) {
return 0.0;
}
return m_unigrams[0].score;
}
inline const KeyValuePair Node::currentKeyValue() const {
if (m_selectedUnigramIndex >= m_unigrams.size()) {
return KeyValuePair();
} else {
return m_candidates[m_selectedUnigramIndex];
}
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,72 +0,0 @@
//
// NodeAnchor.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 NODEANCHOR_H_
#define NODEANCHOR_H_
#include <vector>
#include "Node.h"
namespace Formosa {
namespace Gramambular {
struct NodeAnchor {
const Node* node = nullptr;
size_t location = 0;
size_t spanningLength = 0;
double accumulatedScore = 0.0;
};
inline std::ostream& operator<<(std::ostream& stream,
const NodeAnchor& anchor) {
stream << "{@(" << anchor.location << "," << anchor.spanningLength << "),";
if (anchor.node) {
stream << *(anchor.node);
} else {
stream << "null";
}
stream << "}";
return stream;
}
inline std::ostream& operator<<(std::ostream& stream,
const std::vector<NodeAnchor>& anchor) {
for (std::vector<NodeAnchor>::const_iterator i = anchor.begin();
i != anchor.end(); ++i) {
stream << *i;
if (i + 1 != anchor.end()) {
stream << "<-";
}
}
return stream;
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,101 +0,0 @@
//
// Span.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 SPAN_H_
#define SPAN_H_
#include <map>
#include <set>
#include <sstream>
#include "Node.h"
namespace Formosa {
namespace Gramambular {
class Span {
public:
void clear();
void insertNodeOfLength(const Node& node, size_t length);
void removeNodeOfLengthGreaterThan(size_t length);
Node* nodeOfLength(size_t length);
size_t maximumLength() const;
protected:
std::map<size_t, Node> m_lengthNodeMap;
size_t m_maximumLength = 0;
};
inline void Span::clear() {
m_lengthNodeMap.clear();
m_maximumLength = 0;
}
inline void Span::insertNodeOfLength(const Node& node, size_t length) {
m_lengthNodeMap[length] = node;
if (length > m_maximumLength) {
m_maximumLength = length;
}
}
inline void Span::removeNodeOfLengthGreaterThan(size_t length) {
if (length > m_maximumLength) {
return;
}
size_t max = 0;
std::set<size_t> removeSet;
for (std::map<size_t, Node>::iterator i = m_lengthNodeMap.begin(),
e = m_lengthNodeMap.end();
i != e; ++i) {
if ((*i).first > length) {
removeSet.insert((*i).first);
} else {
if ((*i).first > max) {
max = (*i).first;
}
}
}
for (std::set<size_t>::iterator i = removeSet.begin(), e = removeSet.end();
i != e; ++i) {
m_lengthNodeMap.erase(*i);
}
m_maximumLength = max;
}
inline Node* Span::nodeOfLength(size_t length) {
std::map<size_t, Node>::iterator f = m_lengthNodeMap.find(length);
return f == m_lengthNodeMap.end() ? 0 : &(*f).second;
}
inline size_t Span::maximumLength() const { return m_maximumLength; }
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,99 +0,0 @@
//
// Unigram.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 UNIGRAM_H_
#define UNIGRAM_H_
#include <vector>
#include "KeyValuePair.h"
namespace Formosa {
namespace Gramambular {
class Unigram {
public:
Unigram();
KeyValuePair keyValue;
double score;
bool operator==(const Unigram& another) const;
bool operator<(const Unigram& another) const;
static bool ScoreCompare(const Unigram& a, const Unigram& b);
};
inline std::ostream& operator<<(std::ostream& stream, const Unigram& gram) {
std::streamsize p = stream.precision();
stream.precision(6);
stream << "(" << gram.keyValue << "," << gram.score << ")";
stream.precision(p);
return stream;
}
inline std::ostream& operator<<(std::ostream& stream,
const std::vector<Unigram>& grams) {
stream << "[" << grams.size() << "]=>{";
size_t index = 0;
for (std::vector<Unigram>::const_iterator gi = grams.begin();
gi != grams.end(); ++gi, ++index) {
stream << index << "=>";
stream << *gi;
if (gi + 1 != grams.end()) {
stream << ",";
}
}
stream << "}";
return stream;
}
inline Unigram::Unigram() : score(0.0) {}
inline bool Unigram::operator==(const Unigram& another) const {
return keyValue == another.keyValue && score == another.score;
}
inline bool Unigram::operator<(const Unigram& another) const {
if (keyValue < another.keyValue) {
return true;
} else if (keyValue == another.keyValue) {
return score < another.score;
}
return false;
}
inline bool Unigram::ScoreCompare(const Unigram& a, const Unigram& b) {
return a.score > b.score;
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,93 +0,0 @@
//
// Walker.h
//
// Copyright (c) 2007-2010 Lukhnos D. Liu (http://lukhnos.org)
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 WALKER_H_
#define WALKER_H_
#include <algorithm>
#include <vector>
#include "Grid.h"
namespace Formosa {
namespace Gramambular {
class Walker {
public:
explicit Walker(Grid* inGrid);
const std::vector<NodeAnchor> reverseWalk(size_t location,
double accumulatedScore = 0.0);
protected:
Grid* m_grid;
};
inline Walker::Walker(Grid* inGrid) : m_grid(inGrid) {}
inline const std::vector<NodeAnchor> Walker::reverseWalk(
size_t location, double accumulatedScore) {
if (!location || location > m_grid->width()) {
return std::vector<NodeAnchor>();
}
std::vector<std::vector<NodeAnchor> > paths;
std::vector<NodeAnchor> nodes = m_grid->nodesEndingAt(location);
for (std::vector<NodeAnchor>::iterator ni = nodes.begin(); ni != nodes.end();
++ni) {
if (!(*ni).node) {
continue;
}
(*ni).accumulatedScore = accumulatedScore + (*ni).node->score();
std::vector<NodeAnchor> path =
reverseWalk(location - (*ni).spanningLength, (*ni).accumulatedScore);
path.insert(path.begin(), *ni);
paths.push_back(path);
}
if (!paths.size()) {
return std::vector<NodeAnchor>();
}
std::vector<NodeAnchor>* result = &*(paths.begin());
for (std::vector<std::vector<NodeAnchor> >::iterator pi = paths.begin();
pi != paths.end(); ++pi) {
if ((*pi).back().accumulatedScore > result->back().accumulatedScore) {
result = &*pi;
}
}
return *result;
}
} // namespace Gramambular
} // namespace Formosa
#endif

View File

@ -1,142 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include "KeyValueBlobReader.h"
namespace McBopomofo {
KeyValueBlobReader::State KeyValueBlobReader::Next(KeyValue* out)
{
static auto new_line = [](char c) { return c == '\n' || c == '\r'; };
static auto blank = [](char c) { return c == ' ' || c == '\t'; };
static auto blank_or_newline
= [](char c) { return blank(c) || new_line(c); };
static auto content_char = [](char c) { return !blank(c) && !new_line(c); };
if (state_ == State::ERROR) {
return state_;
}
const char* key_begin = nullptr;
size_t key_length = 0;
const char* value_begin = nullptr;
size_t value_length = 0;
while (true) {
state_ = SkipUntilNot(blank_or_newline);
if (state_ != State::CAN_CONTINUE) {
return state_;
}
// Check if it's a comment line; if so, read until end of line.
if (*current_ != '#') {
break;
}
state_ = SkipUntil(new_line);
if (state_ != State::CAN_CONTINUE) {
return state_;
}
}
// No need to check whether* current_ is a content_char, since content_char
// is defined as not blank and not new_line.
key_begin = current_;
state_ = SkipUntilNot(content_char);
if (state_ != State::CAN_CONTINUE) {
goto error;
}
key_length = current_ - key_begin;
// There should be at least one blank character after the key string.
if (!blank(*current_)) {
goto error;
}
state_ = SkipUntilNot(blank);
if (state_ != State::CAN_CONTINUE) {
goto error;
}
if (!content_char(*current_)) {
goto error;
}
value_begin = current_;
// value must only contain content characters, blanks not are allowed.
// also, there's no need to check the state after this, since we will always
// emit the value. This also avoids the situation where trailing spaces in a
// line would become part of the value.
SkipUntilNot(content_char);
value_length = current_ - value_begin;
// Unconditionally skip until the end of the line. This prevents the case
// like "foo bar baz\n" where baz should not be treated as the Next key.
SkipUntil(new_line);
if (out != nullptr) {
*out = KeyValue { std::string_view { key_begin, key_length },
std::string_view { value_begin, value_length } };
}
state_ = State::HAS_PAIR;
return state_;
error:
state_ = State::ERROR;
return state_;
}
KeyValueBlobReader::State KeyValueBlobReader::SkipUntilNot(
const std::function<bool(char)>& f)
{
while (current_ != end_ && *current_) {
if (!f(*current_)) {
return State::CAN_CONTINUE;
}
++current_;
}
return State::END;
}
KeyValueBlobReader::State KeyValueBlobReader::SkipUntil(
const std::function<bool(char)>& f)
{
while (current_ != end_ && *current_) {
if (f(*current_)) {
return State::CAN_CONTINUE;
}
++current_;
}
return State::END;
}
std::ostream& operator<<(
std::ostream& os, const KeyValueBlobReader::KeyValue& kv)
{
os << "(key: " << kv.key << ", value: " << kv.value << ")";
return os;
}
} // namespace McBopomofo

View File

@ -1,106 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 SOURCE_ENGINE_KEYVALUEBLOBREADER_H_
#define SOURCE_ENGINE_KEYVALUEBLOBREADER_H_
#include <cstddef>
#include <functional>
#include <iostream>
#include <string_view>
// A reader for text-based, blank-separated key-value pairs in a binary blob.
//
// This reader is suitable for reading language model files that entirely
// consist of key-value pairs. Leading or trailing spaces are ignored.
// Lines that start with "#" are treated as comments. Values cannot contain
// spaces. Any space after the value string is parsed is ignored. This implies
// that after a blank, anything that comes after the value can be used as
// comment. Both ' ' and '\t' are treated as blank characters, and the parser
// is agnostic to how lines are ended, and so LF, CR LF, and CR are all valid
// line endings.
//
// std::string_view is used to allow returning results efficiently. As a result,
// the blob is a const char* and will never be mutated. This implies, for
// example, read-only mmap can be used to parse large files.
namespace McBopomofo {
class KeyValueBlobReader {
public:
enum class State : int {
// There are no more key-value pairs in this blob.
END = 0,
// The reader has produced a new key-value pair.
HAS_PAIR = 1,
// An error is encountered and the parsing stopped.
ERROR = -1,
// Internal-only state: the parser can continue parsing.
CAN_CONTINUE = 2
};
struct KeyValue {
constexpr KeyValue()
: key("")
, value("")
{
}
constexpr KeyValue(std::string_view k, std::string_view v)
: key(k)
, value(v)
{
}
bool operator==(const KeyValue& another) const
{
return key == another.key && value == another.value;
}
std::string_view key;
std::string_view value;
};
KeyValueBlobReader(const char* blob, size_t size)
: current_(blob)
, end_(blob + size)
{
}
// Parse the next key-value pair and return the state of the reader. If
// `out` is passed, out will be set to the produced key-value pair if there
// is one.
State Next(KeyValue* out = nullptr);
private:
State SkipUntil(const std::function<bool(char)>& f);
State SkipUntilNot(const std::function<bool(char)>& f);
const char* current_;
const char* end_;
State state_ = State::CAN_CONTINUE;
};
std::ostream& operator<<(std::ostream&, const KeyValueBlobReader::KeyValue&);
} // namespace McBopomofo
#endif // SOURCE_ENGINE_KEYVALUEBLOBREADER_H_

View File

@ -1,255 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include <string>
#include "KeyValueBlobReader.h"
#include "gtest/gtest.h"
namespace McBopomofo {
using State = KeyValueBlobReader::State;
using KeyValue = KeyValueBlobReader::KeyValue;
TEST(KeyValueBlobReaderTest, EmptyBlob)
{
std::string empty;
KeyValueBlobReader reader(empty.c_str(), empty.length());
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, EmptyBlobIdempotency)
{
char empty[0];
KeyValueBlobReader reader(empty, 0);
EXPECT_EQ(reader.Next(), State::END);
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, BlankBlob)
{
std::string blank = " ";
KeyValueBlobReader reader(blank.c_str(), blank.length());
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, KeyWithoutValueIsInvalid)
{
std::string empty = "hello";
KeyValueBlobReader reader(empty.c_str(), empty.length());
EXPECT_EQ(reader.Next(), State::ERROR);
}
TEST(KeyValueBlobReaderTest, ErrorStateMakesNoMoreProgress)
{
std::string empty = "hello";
KeyValueBlobReader reader(empty.c_str(), empty.length());
EXPECT_EQ(reader.Next(), State::ERROR);
EXPECT_EQ(reader.Next(), State::ERROR);
}
TEST(KeyValueBlobReaderTest, KeyValueSeparatedByNullCharIsInvalid)
{
char bad[] = { 'h', 0, 'w' };
KeyValueBlobReader reader(bad, sizeof(bad));
EXPECT_EQ(reader.Next(), State::ERROR);
}
TEST(KeyValueBlobReaderTest, SingleKeyValuePair)
{
std::string empty = "hello world\n";
KeyValueBlobReader reader(empty.c_str(), empty.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, SingleKeyValuePairFromButterWithoutNullEnding)
{
char small[] = { 'p', ' ', 'q' };
KeyValueBlobReader reader(small, sizeof(small));
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "p", "q" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, NullCharInTheMiddleTerminatesParsing)
{
char small[] = { 'p', ' ', 'q', ' ', 0, '\n', 'r', ' ', 's' };
KeyValueBlobReader reader(small, sizeof(small));
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "p", "q" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, SingleKeyValuePairWithoutLFAtEnd)
{
std::string simple = "hello world";
KeyValueBlobReader reader(simple.c_str(), simple.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, EncodingAgnostic1)
{
std::string simple = u8"smile ☺️";
KeyValueBlobReader reader(simple.c_str(), simple.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "smile", u8"☺️" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, EncodingAgnostic2)
{
std::string simple = "Nobel-Laureate "
"\xe9\x81\x94\xe8\xb3\xb4\xe5\x96\x87\xe5\x98\x9b";
KeyValueBlobReader reader(simple.c_str(), simple.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue,
(KeyValue { "Nobel-Laureate",
"\xe9\x81\x94\xe8\xb3\xb4\xe5\x96\x87\xe5\x98\x9b" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, ValueDoesNotIncludeSpace)
{
std::string simple = "hello world and all\nanother value";
KeyValueBlobReader reader(simple.c_str(), simple.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "another", "value" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, TrailingSpaceInValueIsIgnored)
{
std::string simple
= "\thello world \n\n foo bar \t\t\t \n\n\n";
KeyValueBlobReader reader(simple.c_str(), simple.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "foo", "bar" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, WindowsCRLFSupported)
{
std::string simple = "lorem ipsum\r\nhello world";
KeyValueBlobReader reader(simple.c_str(), simple.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "lorem", "ipsum" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, MultipleKeyValuePair)
{
std::string multi = "\n \nhello world\n foo \t bar ";
KeyValueBlobReader reader(multi.c_str(), multi.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "foo", "bar" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, ReadUntilNullChar)
{
char buf[] = { 'p', '\t', 'q', '\n', 0, 'r', ' ', 's' };
KeyValueBlobReader reader(buf, sizeof(buf));
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "p", "q" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, MultipleKeyValuePairWithComments)
{
std::string text = R"(
# comment1
# comment2
# comment3
hello World
caffè latte
# another comment
foo bar
# comment4
# comment5
)";
KeyValueBlobReader reader(text.c_str(), text.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "World" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "caffè", "latte" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "foo", "bar" }));
EXPECT_EQ(reader.Next(), State::END);
}
TEST(KeyValueBlobReaderTest, ValueCommentSupported)
{
std::string text = R"(
# empty
hello world#peace
hello world#peace #peace
hello world#peace // peace
caffè latte # café au lait
foo bar
)";
KeyValueBlobReader reader(text.c_str(), text.length());
KeyValue keyValue;
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world#peace" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world#peace" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "hello", "world#peace" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "caffè", "latte" }));
EXPECT_EQ(reader.Next(&keyValue), State::HAS_PAIR);
EXPECT_EQ(keyValue, (KeyValue { "foo", "bar" }));
EXPECT_EQ(reader.Next(), State::END);
}
} // namespace McBopomofo

View File

@ -1,31 +0,0 @@
cmake_minimum_required(VERSION 3.17)
project(Mandarin)
set(CMAKE_CXX_STANDARD 17)
add_library(MandarinLib Mandarin.h Mandarin.cpp)
# Let CMake fetch Google Test for us.
# https://github.com/google/googletest/tree/main/googletest#incorporating-into-an-existing-cmake-project
include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
# Test target declarations.
add_executable(MandarinTest MandarinTest.cpp)
target_link_libraries(MandarinTest gtest_main MandarinLib)
include(GoogleTest)
gtest_discover_tests(MandarinTest)
add_custom_target(
runTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/MandarinTest
)
add_dependencies(runTest MandarinTest)

View File

@ -1,932 +0,0 @@
// Copyright (c) 2006 and onwards Lukhnos Liu
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include "Mandarin.h"
#include <algorithm>
#include <cctype>
namespace Formosa {
namespace Mandarin {
class PinyinParseHelper {
public:
static const bool ConsumePrefix(std::string& target,
const std::string& prefix) {
if (target.length() < prefix.length()) {
return false;
}
if (target.substr(0, prefix.length()) == prefix) {
target =
target.substr(prefix.length(), target.length() - prefix.length());
return true;
}
return false;
}
};
class BopomofoCharacterMap {
public:
static const BopomofoCharacterMap& SharedInstance();
std::map<BPMF::Component, std::string> componentToCharacter;
std::map<std::string, BPMF::Component> characterToComponent;
protected:
BopomofoCharacterMap();
};
const BPMF BPMF::FromHanyuPinyin(const std::string& str) {
if (!str.length()) {
return BPMF();
}
std::string pinyin = str;
transform(pinyin.begin(), pinyin.end(), pinyin.begin(), ::tolower);
BPMF::Component firstComponent = 0;
BPMF::Component secondComponent = 0;
BPMF::Component thirdComponent = 0;
BPMF::Component toneComponent = 0;
// lookup consonants and consume them
bool independentConsonant = false;
// the y exceptions fist
if (0) {
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yuan")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::AN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ying")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yung")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yong")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yue")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::E;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yun")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "you")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "yu")) {
secondComponent = BPMF::UE;
}
// try the first character
char c = pinyin.length() ? pinyin[0] : 0;
switch (c) {
case 'b':
firstComponent = BPMF::B;
pinyin = pinyin.substr(1);
break;
case 'p':
firstComponent = BPMF::P;
pinyin = pinyin.substr(1);
break;
case 'm':
firstComponent = BPMF::M;
pinyin = pinyin.substr(1);
break;
case 'f':
firstComponent = BPMF::F;
pinyin = pinyin.substr(1);
break;
case 'd':
firstComponent = BPMF::D;
pinyin = pinyin.substr(1);
break;
case 't':
firstComponent = BPMF::T;
pinyin = pinyin.substr(1);
break;
case 'n':
firstComponent = BPMF::N;
pinyin = pinyin.substr(1);
break;
case 'l':
firstComponent = BPMF::L;
pinyin = pinyin.substr(1);
break;
case 'g':
firstComponent = BPMF::G;
pinyin = pinyin.substr(1);
break;
case 'k':
firstComponent = BPMF::K;
pinyin = pinyin.substr(1);
break;
case 'h':
firstComponent = BPMF::H;
pinyin = pinyin.substr(1);
break;
case 'j':
firstComponent = BPMF::J;
pinyin = pinyin.substr(1);
break;
case 'q':
firstComponent = BPMF::Q;
pinyin = pinyin.substr(1);
break;
case 'x':
firstComponent = BPMF::X;
pinyin = pinyin.substr(1);
break;
// special hanlding for w and y
case 'w':
secondComponent = BPMF::U;
pinyin = pinyin.substr(1);
break;
case 'y':
if (!secondComponent && !thirdComponent) {
secondComponent = BPMF::I;
}
pinyin = pinyin.substr(1);
break;
}
// then we try ZH, CH, SH, R, Z, C, S (in that order)
if (0) {
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "zh")) {
firstComponent = BPMF::ZH;
independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ch")) {
firstComponent = BPMF::CH;
independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "sh")) {
firstComponent = BPMF::SH;
independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "r")) {
firstComponent = BPMF::R;
independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "z")) {
firstComponent = BPMF::Z;
independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "c")) {
firstComponent = BPMF::C;
independentConsonant = true;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "s")) {
firstComponent = BPMF::S;
independentConsonant = true;
}
// consume exceptions first: (ien, in), (iou, iu), (uen, un), (veng, iong),
// (ven, vn), (uei, ui), ung but longer sequence takes precedence
if (0) {
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "veng")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "iong")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ing")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ien")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "iou")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "uen")) {
secondComponent = BPMF::U;
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ven")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "uei")) {
secondComponent = BPMF::U;
thirdComponent = BPMF::EI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ung")) {
// f exception
if (firstComponent == BPMF::F) {
thirdComponent = BPMF::ENG;
} else {
secondComponent = BPMF::U;
thirdComponent = BPMF::ENG;
}
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ong")) {
// f exception
if (firstComponent == BPMF::F) {
thirdComponent = BPMF::ENG;
} else {
secondComponent = BPMF::U;
thirdComponent = BPMF::ENG;
}
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "un")) {
if (firstComponent == BPMF::J || firstComponent == BPMF::Q ||
firstComponent == BPMF::X) {
secondComponent = BPMF::UE;
} else {
secondComponent = BPMF::U;
}
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "iu")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "in")) {
secondComponent = BPMF::I;
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "vn")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ui")) {
secondComponent = BPMF::U;
thirdComponent = BPMF::EI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ue")) {
secondComponent = BPMF::UE;
thirdComponent = BPMF::E;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, u8"ü")) {
secondComponent = BPMF::UE;
}
// then consume the middle component...
if (0) {
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "i")) {
secondComponent = independentConsonant ? 0 : BPMF::I;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "u")) {
if (firstComponent == BPMF::J || firstComponent == BPMF::Q ||
firstComponent == BPMF::X) {
secondComponent = BPMF::UE;
} else {
secondComponent = BPMF::U;
}
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "v")) {
secondComponent = BPMF::UE;
}
// the vowels, longer sequence takes precedence
if (0) {
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ang")) {
thirdComponent = BPMF::ANG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "eng")) {
thirdComponent = BPMF::ENG;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "err")) {
thirdComponent = BPMF::ERR;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ai")) {
thirdComponent = BPMF::AI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ei")) {
thirdComponent = BPMF::EI;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ao")) {
thirdComponent = BPMF::AO;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "ou")) {
thirdComponent = BPMF::OU;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "an")) {
thirdComponent = BPMF::AN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "en")) {
thirdComponent = BPMF::EN;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "er")) {
thirdComponent = BPMF::ERR;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "a")) {
thirdComponent = BPMF::A;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "o")) {
thirdComponent = BPMF::O;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "e")) {
if (secondComponent) {
thirdComponent = BPMF::E;
} else {
thirdComponent = BPMF::ER;
}
}
// at last!
if (0) {
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "1")) {
toneComponent = BPMF::Tone1;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "2")) {
toneComponent = BPMF::Tone2;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "3")) {
toneComponent = BPMF::Tone3;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "4")) {
toneComponent = BPMF::Tone4;
} else if (PinyinParseHelper::ConsumePrefix(pinyin, "5")) {
toneComponent = BPMF::Tone5;
}
return BPMF(firstComponent | secondComponent | thirdComponent |
toneComponent);
}
const std::string BPMF::HanyuPinyinString(bool includesTone,
bool useVForUUmlaut) const {
std::string consonant, middle, vowel, tone;
Component cc = consonantComponent(), mvc = middleVowelComponent(),
vc = vowelComponent();
bool hasNoMVCOrVC = !(mvc || vc);
switch (cc) {
case B:
consonant = "b";
break;
case P:
consonant = "p";
break;
case M:
consonant = "m";
break;
case F:
consonant = "f";
break;
case D:
consonant = "d";
break;
case T:
consonant = "t";
break;
case N:
consonant = "n";
break;
case L:
consonant = "l";
break;
case G:
consonant = "g";
break;
case K:
consonant = "k";
break;
case H:
consonant = "h";
break;
case J:
consonant = "j";
if (hasNoMVCOrVC) middle = "i";
break;
case Q:
consonant = "q";
if (hasNoMVCOrVC) middle = "i";
break;
case X:
consonant = "x";
if (hasNoMVCOrVC) middle = "i";
break;
case ZH:
consonant = "zh";
if (hasNoMVCOrVC) middle = "i";
break;
case CH:
consonant = "ch";
if (hasNoMVCOrVC) middle = "i";
break;
case SH:
consonant = "sh";
if (hasNoMVCOrVC) middle = "i";
break;
case R:
consonant = "r";
if (hasNoMVCOrVC) middle = "i";
break;
case Z:
consonant = "z";
if (hasNoMVCOrVC) middle = "i";
break;
case C:
consonant = "c";
if (hasNoMVCOrVC) middle = "i";
break;
case S:
consonant = "s";
if (hasNoMVCOrVC) middle = "i";
break;
}
switch (mvc) {
case I:
if (!cc) {
consonant = "y";
}
middle = (!vc || cc) ? "i" : "";
break;
case U:
if (!cc) {
consonant = "w";
}
middle = (!vc || cc) ? "u" : "";
break;
case UE:
if (!cc) {
consonant = "y";
}
if ((cc == N || cc == L) && vc != E) {
middle = useVForUUmlaut ? "v" : "ü";
} else {
middle = "u";
}
break;
}
switch (vc) {
case A:
vowel = "a";
break;
case O:
vowel = "o";
break;
case ER:
vowel = "e";
break;
case E:
vowel = "e";
break;
case AI:
vowel = "ai";
break;
case EI:
vowel = "ei";
break;
case AO:
vowel = "ao";
break;
case OU:
vowel = "ou";
break;
case AN:
vowel = "an";
break;
case EN:
vowel = "en";
break;
case ANG:
vowel = "ang";
break;
case ENG:
vowel = "eng";
break;
case ERR:
vowel = "er";
break;
}
// combination rules
// ueng -> ong, but note "weng"
if ((mvc == U || mvc == UE) && vc == ENG) {
middle = "";
vowel = (cc == J || cc == Q || cc == X)
? "iong"
: ((!cc && mvc == U) ? "eng" : "ong");
}
// ien, uen, üen -> in, un, ün ; but note "wen", "yin" and "yun"
if (mvc && vc == EN) {
if (cc) {
vowel = "n";
} else {
if (mvc == UE) {
vowel = "n"; // yun
} else if (mvc == U) {
vowel = "en"; // wen
} else {
vowel = "in"; // yin
}
}
}
// iou -> iu
if (cc && mvc == I && vc == OU) {
middle = "";
vowel = "iu";
}
// ieng -> ing
if (mvc == I && vc == ENG) {
middle = "";
vowel = "ing";
}
// uei -> ui
if (cc && mvc == U && vc == EI) {
middle = "";
vowel = "ui";
}
if (includesTone) {
switch (toneMarkerComponent()) {
case Tone2:
tone = "2";
break;
case Tone3:
tone = "3";
break;
case Tone4:
tone = "4";
break;
case Tone5:
tone = "5";
break;
}
}
return consonant + middle + vowel + tone;
}
const BPMF BPMF::FromComposedString(const std::string& str) {
BPMF syllable;
auto iter = str.begin();
while (iter != str.end()) {
// This is a naive implementation and we bail early at anything we don't
// recognize. A sound implementation would require to either use a trie for
// the Bopomofo character map or to split the input by codepoints. This
// suffices for now.
// Illegal.
if (!(*iter & 0x80)) {
break;
}
size_t utf8_length = -1;
// These are the code points for the tone markers.
if ((*iter & (0x80 | 0x40)) && !(*iter & 0x20)) {
utf8_length = 2;
} else if ((*iter & (0x80 | 0x40 | 0x20)) && !(*iter & 0x10)) {
utf8_length = 3;
} else {
// Illegal.
break;
}
if (iter + (utf8_length - 1) == str.end()) {
break;
}
std::string component = std::string(iter, iter + utf8_length);
const std::map<std::string, BPMF::Component>& charToComp =
BopomofoCharacterMap::SharedInstance().characterToComponent;
std::map<std::string, BPMF::Component>::const_iterator result =
charToComp.find(component);
if (result == charToComp.end()) {
break;
} else {
syllable += BPMF((*result).second);
}
iter += utf8_length;
}
return syllable;
}
const std::string BPMF::composedString() const {
std::string result;
#define APPEND(c) \
if (syllable_ & c) \
result += \
(*BopomofoCharacterMap::SharedInstance().componentToCharacter.find( \
syllable_ & c)) \
.second
APPEND(ConsonantMask);
APPEND(MiddleVowelMask);
APPEND(VowelMask);
APPEND(ToneMarkerMask);
#undef APPEND
return result;
}
const BopomofoCharacterMap& BopomofoCharacterMap::SharedInstance() {
static BopomofoCharacterMap* map = new BopomofoCharacterMap();
return *map;
}
BopomofoCharacterMap::BopomofoCharacterMap() {
characterToComponent[u8""] = BPMF::B;
characterToComponent[u8""] = BPMF::P;
characterToComponent[u8""] = BPMF::M;
characterToComponent[u8""] = BPMF::F;
characterToComponent[u8""] = BPMF::D;
characterToComponent[u8""] = BPMF::T;
characterToComponent[u8""] = BPMF::N;
characterToComponent[u8""] = BPMF::L;
characterToComponent[u8""] = BPMF::K;
characterToComponent[u8""] = BPMF::G;
characterToComponent[u8""] = BPMF::H;
characterToComponent[u8""] = BPMF::J;
characterToComponent[u8""] = BPMF::Q;
characterToComponent[u8""] = BPMF::X;
characterToComponent[u8""] = BPMF::ZH;
characterToComponent[u8""] = BPMF::CH;
characterToComponent[u8""] = BPMF::SH;
characterToComponent[u8""] = BPMF::R;
characterToComponent[u8""] = BPMF::Z;
characterToComponent[u8""] = BPMF::C;
characterToComponent[u8""] = BPMF::S;
characterToComponent[u8""] = BPMF::I;
characterToComponent[u8""] = BPMF::U;
characterToComponent[u8""] = BPMF::UE;
characterToComponent[u8""] = BPMF::A;
characterToComponent[u8""] = BPMF::O;
characterToComponent[u8""] = BPMF::ER;
characterToComponent[u8""] = BPMF::E;
characterToComponent[u8""] = BPMF::AI;
characterToComponent[u8""] = BPMF::EI;
characterToComponent[u8""] = BPMF::AO;
characterToComponent[u8""] = BPMF::OU;
characterToComponent[u8""] = BPMF::AN;
characterToComponent[u8""] = BPMF::EN;
characterToComponent[u8""] = BPMF::ANG;
characterToComponent[u8""] = BPMF::ENG;
characterToComponent[u8""] = BPMF::ERR;
characterToComponent[u8"ˊ"] = BPMF::Tone2;
characterToComponent[u8"ˇ"] = BPMF::Tone3;
characterToComponent[u8"ˋ"] = BPMF::Tone4;
characterToComponent[u8"˙"] = BPMF::Tone5;
for (std::map<std::string, BPMF::Component>::iterator iter =
characterToComponent.begin();
iter != characterToComponent.end(); ++iter)
componentToCharacter[(*iter).second] = (*iter).first;
}
#define ASSIGNKEY1(m, vec, k, val) \
m[k] = (vec.clear(), vec.push_back((BPMF::Component)val), vec)
#define ASSIGNKEY2(m, vec, k, val1, val2) \
m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \
vec.push_back((BPMF::Component)val2), vec)
#define ASSIGNKEY3(m, vec, k, val1, val2, val3) \
m[k] = (vec.clear(), vec.push_back((BPMF::Component)val1), \
vec.push_back((BPMF::Component)val2), \
vec.push_back((BPMF::Component)val3), vec)
static BopomofoKeyboardLayout* CreateStandardLayout() {
std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm;
ASSIGNKEY1(ktcm, vec, '1', BPMF::B);
ASSIGNKEY1(ktcm, vec, 'q', BPMF::P);
ASSIGNKEY1(ktcm, vec, 'a', BPMF::M);
ASSIGNKEY1(ktcm, vec, 'z', BPMF::F);
ASSIGNKEY1(ktcm, vec, '2', BPMF::D);
ASSIGNKEY1(ktcm, vec, 'w', BPMF::T);
ASSIGNKEY1(ktcm, vec, 's', BPMF::N);
ASSIGNKEY1(ktcm, vec, 'x', BPMF::L);
ASSIGNKEY1(ktcm, vec, 'e', BPMF::G);
ASSIGNKEY1(ktcm, vec, 'd', BPMF::K);
ASSIGNKEY1(ktcm, vec, 'c', BPMF::H);
ASSIGNKEY1(ktcm, vec, 'r', BPMF::J);
ASSIGNKEY1(ktcm, vec, 'f', BPMF::Q);
ASSIGNKEY1(ktcm, vec, 'v', BPMF::X);
ASSIGNKEY1(ktcm, vec, '5', BPMF::ZH);
ASSIGNKEY1(ktcm, vec, 't', BPMF::CH);
ASSIGNKEY1(ktcm, vec, 'g', BPMF::SH);
ASSIGNKEY1(ktcm, vec, 'b', BPMF::R);
ASSIGNKEY1(ktcm, vec, 'y', BPMF::Z);
ASSIGNKEY1(ktcm, vec, 'h', BPMF::C);
ASSIGNKEY1(ktcm, vec, 'n', BPMF::S);
ASSIGNKEY1(ktcm, vec, 'u', BPMF::I);
ASSIGNKEY1(ktcm, vec, 'j', BPMF::U);
ASSIGNKEY1(ktcm, vec, 'm', BPMF::UE);
ASSIGNKEY1(ktcm, vec, '8', BPMF::A);
ASSIGNKEY1(ktcm, vec, 'i', BPMF::O);
ASSIGNKEY1(ktcm, vec, 'k', BPMF::ER);
ASSIGNKEY1(ktcm, vec, ',', BPMF::E);
ASSIGNKEY1(ktcm, vec, '9', BPMF::AI);
ASSIGNKEY1(ktcm, vec, 'o', BPMF::EI);
ASSIGNKEY1(ktcm, vec, 'l', BPMF::AO);
ASSIGNKEY1(ktcm, vec, '.', BPMF::OU);
ASSIGNKEY1(ktcm, vec, '0', BPMF::AN);
ASSIGNKEY1(ktcm, vec, 'p', BPMF::EN);
ASSIGNKEY1(ktcm, vec, ';', BPMF::ANG);
ASSIGNKEY1(ktcm, vec, '/', BPMF::ENG);
ASSIGNKEY1(ktcm, vec, '-', BPMF::ERR);
ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3);
ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4);
ASSIGNKEY1(ktcm, vec, '6', BPMF::Tone2);
ASSIGNKEY1(ktcm, vec, '7', BPMF::Tone5);
return new BopomofoKeyboardLayout(ktcm, "Standard");
}
static BopomofoKeyboardLayout* CreateIBMLayout() {
std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm;
ASSIGNKEY1(ktcm, vec, '1', BPMF::B);
ASSIGNKEY1(ktcm, vec, '2', BPMF::P);
ASSIGNKEY1(ktcm, vec, '3', BPMF::M);
ASSIGNKEY1(ktcm, vec, '4', BPMF::F);
ASSIGNKEY1(ktcm, vec, '5', BPMF::D);
ASSIGNKEY1(ktcm, vec, '6', BPMF::T);
ASSIGNKEY1(ktcm, vec, '7', BPMF::N);
ASSIGNKEY1(ktcm, vec, '8', BPMF::L);
ASSIGNKEY1(ktcm, vec, '9', BPMF::G);
ASSIGNKEY1(ktcm, vec, '0', BPMF::K);
ASSIGNKEY1(ktcm, vec, '-', BPMF::H);
ASSIGNKEY1(ktcm, vec, 'q', BPMF::J);
ASSIGNKEY1(ktcm, vec, 'w', BPMF::Q);
ASSIGNKEY1(ktcm, vec, 'e', BPMF::X);
ASSIGNKEY1(ktcm, vec, 'r', BPMF::ZH);
ASSIGNKEY1(ktcm, vec, 't', BPMF::CH);
ASSIGNKEY1(ktcm, vec, 'y', BPMF::SH);
ASSIGNKEY1(ktcm, vec, 'u', BPMF::R);
ASSIGNKEY1(ktcm, vec, 'i', BPMF::Z);
ASSIGNKEY1(ktcm, vec, 'o', BPMF::C);
ASSIGNKEY1(ktcm, vec, 'p', BPMF::S);
ASSIGNKEY1(ktcm, vec, 'a', BPMF::I);
ASSIGNKEY1(ktcm, vec, 's', BPMF::U);
ASSIGNKEY1(ktcm, vec, 'd', BPMF::UE);
ASSIGNKEY1(ktcm, vec, 'f', BPMF::A);
ASSIGNKEY1(ktcm, vec, 'g', BPMF::O);
ASSIGNKEY1(ktcm, vec, 'h', BPMF::ER);
ASSIGNKEY1(ktcm, vec, 'j', BPMF::E);
ASSIGNKEY1(ktcm, vec, 'k', BPMF::AI);
ASSIGNKEY1(ktcm, vec, 'l', BPMF::EI);
ASSIGNKEY1(ktcm, vec, ';', BPMF::AO);
ASSIGNKEY1(ktcm, vec, 'z', BPMF::OU);
ASSIGNKEY1(ktcm, vec, 'x', BPMF::AN);
ASSIGNKEY1(ktcm, vec, 'c', BPMF::EN);
ASSIGNKEY1(ktcm, vec, 'v', BPMF::ANG);
ASSIGNKEY1(ktcm, vec, 'b', BPMF::ENG);
ASSIGNKEY1(ktcm, vec, 'n', BPMF::ERR);
ASSIGNKEY1(ktcm, vec, 'm', BPMF::Tone2);
ASSIGNKEY1(ktcm, vec, ',', BPMF::Tone3);
ASSIGNKEY1(ktcm, vec, '.', BPMF::Tone4);
ASSIGNKEY1(ktcm, vec, '/', BPMF::Tone5);
return new BopomofoKeyboardLayout(ktcm, "IBM");
}
static BopomofoKeyboardLayout* CreateETenLayout() {
std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm;
ASSIGNKEY1(ktcm, vec, 'b', BPMF::B);
ASSIGNKEY1(ktcm, vec, 'p', BPMF::P);
ASSIGNKEY1(ktcm, vec, 'm', BPMF::M);
ASSIGNKEY1(ktcm, vec, 'f', BPMF::F);
ASSIGNKEY1(ktcm, vec, 'd', BPMF::D);
ASSIGNKEY1(ktcm, vec, 't', BPMF::T);
ASSIGNKEY1(ktcm, vec, 'n', BPMF::N);
ASSIGNKEY1(ktcm, vec, 'l', BPMF::L);
ASSIGNKEY1(ktcm, vec, 'v', BPMF::G);
ASSIGNKEY1(ktcm, vec, 'k', BPMF::K);
ASSIGNKEY1(ktcm, vec, 'h', BPMF::H);
ASSIGNKEY1(ktcm, vec, 'g', BPMF::J);
ASSIGNKEY1(ktcm, vec, '7', BPMF::Q);
ASSIGNKEY1(ktcm, vec, 'c', BPMF::X);
ASSIGNKEY1(ktcm, vec, ',', BPMF::ZH);
ASSIGNKEY1(ktcm, vec, '.', BPMF::CH);
ASSIGNKEY1(ktcm, vec, '/', BPMF::SH);
ASSIGNKEY1(ktcm, vec, 'j', BPMF::R);
ASSIGNKEY1(ktcm, vec, ';', BPMF::Z);
ASSIGNKEY1(ktcm, vec, '\'', BPMF::C);
ASSIGNKEY1(ktcm, vec, 's', BPMF::S);
ASSIGNKEY1(ktcm, vec, 'e', BPMF::I);
ASSIGNKEY1(ktcm, vec, 'x', BPMF::U);
ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE);
ASSIGNKEY1(ktcm, vec, 'a', BPMF::A);
ASSIGNKEY1(ktcm, vec, 'o', BPMF::O);
ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER);
ASSIGNKEY1(ktcm, vec, 'w', BPMF::E);
ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI);
ASSIGNKEY1(ktcm, vec, 'q', BPMF::EI);
ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO);
ASSIGNKEY1(ktcm, vec, 'y', BPMF::OU);
ASSIGNKEY1(ktcm, vec, '8', BPMF::AN);
ASSIGNKEY1(ktcm, vec, '9', BPMF::EN);
ASSIGNKEY1(ktcm, vec, '0', BPMF::ANG);
ASSIGNKEY1(ktcm, vec, '-', BPMF::ENG);
ASSIGNKEY1(ktcm, vec, '=', BPMF::ERR);
ASSIGNKEY1(ktcm, vec, '2', BPMF::Tone2);
ASSIGNKEY1(ktcm, vec, '3', BPMF::Tone3);
ASSIGNKEY1(ktcm, vec, '4', BPMF::Tone4);
ASSIGNKEY1(ktcm, vec, '1', BPMF::Tone5);
return new BopomofoKeyboardLayout(ktcm, "ETen");
}
static BopomofoKeyboardLayout* CreateHsuLayout() {
std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm;
ASSIGNKEY1(ktcm, vec, 'b', BPMF::B);
ASSIGNKEY1(ktcm, vec, 'p', BPMF::P);
ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN);
ASSIGNKEY2(ktcm, vec, 'f', BPMF::F, BPMF::Tone3);
ASSIGNKEY2(ktcm, vec, 'd', BPMF::D, BPMF::Tone2);
ASSIGNKEY1(ktcm, vec, 't', BPMF::T);
ASSIGNKEY2(ktcm, vec, 'n', BPMF::N, BPMF::EN);
ASSIGNKEY3(ktcm, vec, 'l', BPMF::L, BPMF::ENG, BPMF::ERR);
ASSIGNKEY2(ktcm, vec, 'g', BPMF::G, BPMF::ER);
ASSIGNKEY2(ktcm, vec, 'k', BPMF::K, BPMF::ANG);
ASSIGNKEY2(ktcm, vec, 'h', BPMF::H, BPMF::O);
ASSIGNKEY3(ktcm, vec, 'j', BPMF::J, BPMF::ZH, BPMF::Tone4);
ASSIGNKEY2(ktcm, vec, 'v', BPMF::Q, BPMF::CH);
ASSIGNKEY2(ktcm, vec, 'c', BPMF::X, BPMF::SH);
ASSIGNKEY1(ktcm, vec, 'r', BPMF::R);
ASSIGNKEY1(ktcm, vec, 'z', BPMF::Z);
ASSIGNKEY2(ktcm, vec, 'a', BPMF::C, BPMF::EI);
ASSIGNKEY2(ktcm, vec, 's', BPMF::S, BPMF::Tone5);
ASSIGNKEY2(ktcm, vec, 'e', BPMF::I, BPMF::E);
ASSIGNKEY1(ktcm, vec, 'x', BPMF::U);
ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE);
ASSIGNKEY1(ktcm, vec, 'y', BPMF::A);
ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI);
ASSIGNKEY1(ktcm, vec, 'w', BPMF::AO);
ASSIGNKEY1(ktcm, vec, 'o', BPMF::OU);
return new BopomofoKeyboardLayout(ktcm, "Hsu");
}
static BopomofoKeyboardLayout* CreateETen26Layout() {
std::vector<BPMF::Component> vec;
BopomofoKeyToComponentMap ktcm;
ASSIGNKEY1(ktcm, vec, 'b', BPMF::B);
ASSIGNKEY2(ktcm, vec, 'p', BPMF::P, BPMF::OU);
ASSIGNKEY2(ktcm, vec, 'm', BPMF::M, BPMF::AN);
ASSIGNKEY2(ktcm, vec, 'f', BPMF::F, BPMF::Tone2);
ASSIGNKEY2(ktcm, vec, 'd', BPMF::D, BPMF::Tone5);
ASSIGNKEY2(ktcm, vec, 't', BPMF::T, BPMF::ANG);
ASSIGNKEY2(ktcm, vec, 'n', BPMF::N, BPMF::EN);
ASSIGNKEY2(ktcm, vec, 'l', BPMF::L, BPMF::ENG);
ASSIGNKEY2(ktcm, vec, 'v', BPMF::G, BPMF::Q);
ASSIGNKEY2(ktcm, vec, 'k', BPMF::K, BPMF::Tone4);
ASSIGNKEY2(ktcm, vec, 'h', BPMF::H, BPMF::ERR);
ASSIGNKEY2(ktcm, vec, 'g', BPMF::ZH, BPMF::J);
ASSIGNKEY2(ktcm, vec, 'c', BPMF::SH, BPMF::X);
ASSIGNKEY1(ktcm, vec, 'y', BPMF::CH);
ASSIGNKEY2(ktcm, vec, 'j', BPMF::R, BPMF::Tone3);
ASSIGNKEY2(ktcm, vec, 'q', BPMF::Z, BPMF::EI);
ASSIGNKEY2(ktcm, vec, 'w', BPMF::C, BPMF::E);
ASSIGNKEY1(ktcm, vec, 's', BPMF::S);
ASSIGNKEY1(ktcm, vec, 'e', BPMF::I);
ASSIGNKEY1(ktcm, vec, 'x', BPMF::U);
ASSIGNKEY1(ktcm, vec, 'u', BPMF::UE);
ASSIGNKEY1(ktcm, vec, 'a', BPMF::A);
ASSIGNKEY1(ktcm, vec, 'o', BPMF::O);
ASSIGNKEY1(ktcm, vec, 'r', BPMF::ER);
ASSIGNKEY1(ktcm, vec, 'i', BPMF::AI);
ASSIGNKEY1(ktcm, vec, 'z', BPMF::AO);
return new BopomofoKeyboardLayout(ktcm, "ETen26");
}
static BopomofoKeyboardLayout* CreateHanyuPinyinLayout() {
BopomofoKeyToComponentMap ktcm;
return new BopomofoKeyboardLayout(ktcm, "HanyuPinyin");
}
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::StandardLayout() {
static BopomofoKeyboardLayout* layout = CreateStandardLayout();
return layout;
}
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETenLayout() {
static BopomofoKeyboardLayout* layout = CreateETenLayout();
return layout;
}
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HsuLayout() {
static BopomofoKeyboardLayout* layout = CreateHsuLayout();
return layout;
}
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::ETen26Layout() {
static BopomofoKeyboardLayout* layout = CreateETen26Layout();
return layout;
}
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::IBMLayout() {
static BopomofoKeyboardLayout* layout = CreateIBMLayout();
return layout;
}
const BopomofoKeyboardLayout* BopomofoKeyboardLayout::HanyuPinyinLayout() {
static BopomofoKeyboardLayout* layout = CreateHanyuPinyinLayout();
return layout;
}
} // namespace Mandarin
} // namespace Formosa

View File

@ -1,479 +0,0 @@
// Copyright (c) 2006 and onwards Lukhnos Liu
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 Formosa {
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;
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* 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;
};
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(); }
protected:
const BopomofoKeyboardLayout* layout_;
BPMF syllable_;
bool pinyin_mode_;
std::string pinyin_sequence_;
};
} // namespace Mandarin
} // namespace Formosa
#endif // MANDARIN_H_

View File

@ -1,120 +0,0 @@
// Copyright (c) 2022 and onwards Lukhnos Liu
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include "Mandarin.h"
#include "gtest/gtest.h"
namespace Formosa {
namespace Mandarin {
static std::string RoundTrip(const std::string& composedString) {
return BopomofoSyllable::FromComposedString(composedString).composedString();
}
TEST(MandarinTest, FromComposedString) {
ASSERT_EQ(RoundTrip(""), "");
ASSERT_EQ(RoundTrip("ㄅㄧ"), "ㄅㄧ");
ASSERT_EQ(RoundTrip("ㄅㄧˇ"), "ㄅㄧˇ");
ASSERT_EQ(RoundTrip("ㄅㄧˇㄆ"), "ㄆㄧˇ");
ASSERT_EQ(RoundTrip("ㄅㄧˇㄆ"), "ㄆㄧˇ");
ASSERT_EQ(RoundTrip("e"), "");
ASSERT_EQ(RoundTrip("é"), "");
ASSERT_EQ(RoundTrip("ㄅéㄆ"), "");
ASSERT_EQ(RoundTrip("ㄅeㄆ"), "");
}
TEST(MandarinTest, SimpleCompositions) {
BopomofoSyllable syllable;
syllable += BopomofoSyllable(BopomofoSyllable::X);
syllable += BopomofoSyllable(BopomofoSyllable::I);
ASSERT_EQ(syllable.composedString(), "ㄒㄧ");
syllable.clear();
syllable += BopomofoSyllable(BopomofoSyllable::Z);
syllable += BopomofoSyllable(BopomofoSyllable::ANG);
syllable += BopomofoSyllable(BopomofoSyllable::Tone4);
ASSERT_EQ(syllable.composedString(), "ㄗㄤˋ");
}
TEST(MandarinTest, StandardLayout) {
BopomofoReadingBuffer buf(BopomofoKeyboardLayout::StandardLayout());
buf.combineKey('w');
buf.combineKey('9');
buf.combineKey('6');
ASSERT_EQ(buf.composedString(), "ㄊㄞˊ");
}
TEST(MandarinTest, StandardLayoutCombination) {
BopomofoReadingBuffer buf(BopomofoKeyboardLayout::StandardLayout());
buf.combineKey('w');
buf.combineKey('9');
buf.combineKey('6');
ASSERT_EQ(buf.composedString(), "ㄊㄞˊ");
buf.backspace();
ASSERT_EQ(buf.composedString(), "ㄊㄞ");
buf.combineKey('y');
ASSERT_EQ(buf.composedString(), "ㄗㄞ");
buf.combineKey('4');
ASSERT_EQ(buf.composedString(), "ㄗㄞˋ");
buf.combineKey('3');
ASSERT_EQ(buf.composedString(), "ㄗㄞˇ");
}
TEST(MandarinTest, ETenLayout) {
BopomofoReadingBuffer buf(BopomofoKeyboardLayout::ETenLayout());
buf.combineKey('x');
buf.combineKey('8');
ASSERT_EQ(buf.composedString(), "ㄨㄢ");
}
TEST(MandarinTest, ETen26Layout) {
BopomofoReadingBuffer buf(BopomofoKeyboardLayout::ETen26Layout());
buf.combineKey('q');
buf.combineKey('m'); // AN in the vowel state
buf.combineKey('k'); // Tone 4 in the tone state
ASSERT_EQ(buf.composedString(), "ㄗㄢˋ");
}
TEST(MandarinTest, HsuLayout) {
BopomofoReadingBuffer buf(BopomofoKeyboardLayout::HsuLayout());
buf.combineKey('f');
buf.combineKey('a'); // EI when in the vowel state
buf.combineKey('f'); // Tone 3 when in the tone state
ASSERT_EQ(buf.composedString(), "ㄈㄟˇ");
}
TEST(MandarinTest, IBMLayout) {
BopomofoReadingBuffer buf(BopomofoKeyboardLayout::IBMLayout());
buf.combineKey('9');
buf.combineKey('s');
buf.combineKey('g');
buf.combineKey('m');
ASSERT_EQ(buf.composedString(), "ㄍㄨㄛˊ");
}
} // namespace Mandarin
} // namespace Formosa

View File

@ -1,217 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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.
#include "McBopomofoLM.h"
#include <algorithm>
#include <iterator>
namespace McBopomofo {
McBopomofoLM::McBopomofoLM()
{
}
McBopomofoLM::~McBopomofoLM()
{
m_languageModel.close();
m_userPhrases.close();
m_excludedPhrases.close();
m_phraseReplacement.close();
m_associatedPhrases.close();
}
void McBopomofoLM::loadLanguageModel(const char* languageModelDataPath)
{
if (languageModelDataPath) {
m_languageModel.close();
m_languageModel.open(languageModelDataPath);
}
}
bool McBopomofoLM::isDataModelLoaded()
{
return m_languageModel.isLoaded();
}
void McBopomofoLM::loadAssociatedPhrases(const char* associatedPhrasesPath)
{
if (associatedPhrasesPath) {
m_associatedPhrases.close();
m_associatedPhrases.open(associatedPhrasesPath);
}
}
bool McBopomofoLM::isAssociatedPhrasesLoaded()
{
return m_associatedPhrases.isLoaded();
}
void McBopomofoLM::loadUserPhrases(const char* userPhrasesDataPath,
const char* excludedPhrasesDataPath)
{
if (userPhrasesDataPath) {
m_userPhrases.close();
m_userPhrases.open(userPhrasesDataPath);
}
if (excludedPhrasesDataPath) {
m_excludedPhrases.close();
m_excludedPhrases.open(excludedPhrasesDataPath);
}
}
void McBopomofoLM::loadPhraseReplacementMap(const char* phraseReplacementPath)
{
if (phraseReplacementPath) {
m_phraseReplacement.close();
m_phraseReplacement.open(phraseReplacementPath);
}
}
const std::vector<Formosa::Gramambular::Bigram> McBopomofoLM::bigramsForKeys(const std::string& preceedingKey, const std::string& key)
{
return std::vector<Formosa::Gramambular::Bigram>();
}
const std::vector<Formosa::Gramambular::Unigram> McBopomofoLM::unigramsForKey(const std::string& key)
{
if (key == " ") {
std::vector<Formosa::Gramambular::Unigram> spaceUnigrams;
Formosa::Gramambular::Unigram g;
g.keyValue.key = " ";
g.keyValue.value = " ";
g.score = 0;
spaceUnigrams.push_back(g);
return spaceUnigrams;
}
std::vector<Formosa::Gramambular::Unigram> allUnigrams;
std::vector<Formosa::Gramambular::Unigram> userUnigrams;
std::unordered_set<std::string> excludedValues;
std::unordered_set<std::string> insertedValues;
if (m_excludedPhrases.hasUnigramsForKey(key)) {
std::vector<Formosa::Gramambular::Unigram> excludedUnigrams = m_excludedPhrases.unigramsForKey(key);
transform(excludedUnigrams.begin(), excludedUnigrams.end(),
inserter(excludedValues, excludedValues.end()),
[](const Formosa::Gramambular::Unigram& u) { return u.keyValue.value; });
}
if (m_userPhrases.hasUnigramsForKey(key)) {
std::vector<Formosa::Gramambular::Unigram> rawUserUnigrams = m_userPhrases.unigramsForKey(key);
userUnigrams = filterAndTransformUnigrams(rawUserUnigrams, excludedValues, insertedValues);
}
if (m_languageModel.hasUnigramsForKey(key)) {
std::vector<Formosa::Gramambular::Unigram> rawGlobalUnigrams = m_languageModel.unigramsForKey(key);
allUnigrams = filterAndTransformUnigrams(rawGlobalUnigrams, excludedValues, insertedValues);
}
allUnigrams.insert(allUnigrams.begin(), userUnigrams.begin(), userUnigrams.end());
return allUnigrams;
}
bool McBopomofoLM::hasUnigramsForKey(const std::string& key)
{
if (key == " ") {
return true;
}
if (!m_excludedPhrases.hasUnigramsForKey(key)) {
return m_userPhrases.hasUnigramsForKey(key) || m_languageModel.hasUnigramsForKey(key);
}
return unigramsForKey(key).size() > 0;
}
void McBopomofoLM::setPhraseReplacementEnabled(bool enabled)
{
m_phraseReplacementEnabled = enabled;
}
bool McBopomofoLM::phraseReplacementEnabled()
{
return m_phraseReplacementEnabled;
}
void McBopomofoLM::setExternalConverterEnabled(bool enabled)
{
m_externalConverterEnabled = enabled;
}
bool McBopomofoLM::externalConverterEnabled()
{
return m_externalConverterEnabled;
}
void McBopomofoLM::setExternalConverter(std::function<std::string(std::string)> externalConverter)
{
m_externalConverter = externalConverter;
}
const std::vector<Formosa::Gramambular::Unigram> McBopomofoLM::filterAndTransformUnigrams(const std::vector<Formosa::Gramambular::Unigram> unigrams, const std::unordered_set<std::string>& excludedValues, std::unordered_set<std::string>& insertedValues)
{
std::vector<Formosa::Gramambular::Unigram> results;
for (auto&& unigram : unigrams) {
// excludedValues filters out the unigrams with the original value.
// insertedValues filters out the ones with the converted value
std::string originalValue = unigram.keyValue.value;
if (excludedValues.find(originalValue) != excludedValues.end()) {
continue;
}
std::string value = originalValue;
if (m_phraseReplacementEnabled) {
std::string replacement = m_phraseReplacement.valueForKey(value);
if (replacement != "") {
value = replacement;
}
}
if (m_externalConverterEnabled && m_externalConverter) {
std::string replacement = m_externalConverter(value);
value = replacement;
}
if (insertedValues.find(value) == insertedValues.end()) {
Formosa::Gramambular::Unigram g;
g.keyValue.value = value;
g.keyValue.key = unigram.keyValue.key;
g.score = unigram.score;
results.push_back(g);
insertedValues.insert(value);
}
}
return results;
}
const std::vector<std::string> McBopomofoLM::associatedPhrasesForKey(const std::string& key)
{
return m_associatedPhrases.valuesForKey(key);
}
bool McBopomofoLM::hasAssociatedPhrasesForKey(const std::string& key)
{
return m_associatedPhrases.hasValuesForKey(key);
}
} // namespace McBopomofo

View File

@ -1,131 +0,0 @@
// Copyright (c) 2022 and onwards The McBopomofo Authors.
//
// 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:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// 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 MCBOPOMOFOLM_H
#define MCBOPOMOFOLM_H
#include "AssociatedPhrases.h"
#include "ParselessLM.h"
#include "PhraseReplacementMap.h"
#include "UserPhrasesLM.h"
#include <stdio.h>
#include <unordered_set>
namespace McBopomofo {
/// McBopomofoLM is a facade for managing a set of models including
/// the input method language model, user phrases and excluded phrases.
///
/// It is the primary model class that the input controller and grammar builder
/// of McBopomofo talks to. When the grammar builder starts to build a sentence
/// from a series of BPMF readings, it passes the readings to the model to see
/// if there are valid unigrams, and use returned unigrams to produce the final
/// results.
///
/// McBopomofoLM combine and transform the unigrams from the primary language
/// model and user phrases. The process is
///
/// 1) Get the original unigrams.
/// 2) Drop the unigrams whose value is contained in the exclusion map.
/// 3) Replace the values of the unigrams using the phrase replacement map.
/// 4) Replace the values of the unigrams using an external converter lambda.
/// 5) Drop the duplicated phrases.
///
/// The controller can ask the model to load the primary input method language
/// model while launching and to load the user phrases anytime if the custom
/// files are modified. It does not keep the reference of the data pathes but
/// you have to pass the paths when you ask it to do loading.
class McBopomofoLM : public Formosa::Gramambular::LanguageModel {
public:
McBopomofoLM();
~McBopomofoLM();
/// Asks to load the primary language model at the given path.
/// @param languageModelPath The path of the language model.
void loadLanguageModel(const char* languageModelPath);
/// If the data model is already loaded.
bool isDataModelLoaded();
/// Asks to load the associated phrases at the given path.
/// @param associatedPhrasesPath The path of the associated phrases.
void loadAssociatedPhrases(const char* associatedPhrasesPath);
/// If the associated phrases already loaded.
bool isAssociatedPhrasesLoaded();
/// Asks to load the user phrases and excluded phrases at the given path.
/// @param userPhrasesPath The path of user phrases.
/// @param excludedPhrasesPath The path of excluded phrases.
void loadUserPhrases(const char* userPhrasesPath, const char* excludedPhrasesPath);
/// Asks to load th phrase replacement table at the given path.
/// @param phraseReplacementPath The path of the phrase replacement table.
void loadPhraseReplacementMap(const char* phraseReplacementPath);
/// Not implemented since we do not have data to provide bigram function.
const std::vector<Formosa::Gramambular::Bigram> bigramsForKeys(const std::string& preceedingKey, const std::string& key);
/// Returns a list of available unigram for the given key.
/// @param key A string represents the BPMF reading or a symbol key. For
/// example, it you pass "ㄇㄚ", it returns "嗎", "媽", and so on.
const std::vector<Formosa::Gramambular::Unigram> unigramsForKey(const std::string& key);
/// If the model has unigrams for the given key.
/// @param key The key.
bool hasUnigramsForKey(const std::string& key);
/// Enables or disables phrase replacement.
void setPhraseReplacementEnabled(bool enabled);
/// If phrase replacement is enabled or not.
bool phraseReplacementEnabled();
/// Enables or disables the external converter.
void setExternalConverterEnabled(bool enabled);
/// If the external converted is enabled or not.
bool externalConverterEnabled();
/// Sets a lambda to let the values of unigrams could be converted by it.
void setExternalConverter(std::function<std::string(std::string)> externalConverter);
const std::vector<std::string> associatedPhrasesForKey(const std::string& key);
bool hasAssociatedPhrasesForKey(const std::string& key);
protected:
/// Filters and converts the input unigrams and return a new list of unigrams.
///
/// @param unigrams The unigrams to be processed.
/// @param excludedValues The values to excluded unigrams.
/// @param insertedValues The values for unigrams already in the results.
/// It helps to prevent duplicated unigrams. Please note that the method
/// has a side effect that it inserts values to `insertedValues`.
const std::vector<Formosa::Gramambular::Unigram> filterAndTransformUnigrams(const std::vector<Formosa::Gramambular::Unigram> unigrams,
const std::unordered_set<std::string>& excludedValues,
std::unordered_set<std::string>& insertedValues);
ParselessLM m_languageModel;
UserPhrasesLM m_userPhrases;
UserPhrasesLM m_excludedPhrases;
PhraseReplacementMap m_phraseReplacement;
AssociatedPhrases m_associatedPhrases;
bool m_phraseReplacementEnabled;
bool m_externalConverterEnabled;
std::function<std::string(std::string)> m_externalConverter;
};
};
#endif

Some files were not shown because too many files have changed in this diff Show More