Repo // Massive refactor & renovation.

- This commit is a massive refactor update towards the current vChewing codebase, modernizing the entire project structure.
- Put things into packages to boost compile speed.
- Also: IMKCandidates // macOS 10.13 High Sierra compatibility.
- ctlIME // Remove duplicated setValue().
This commit is contained in:
ShikiSuen 2022-09-16 17:33:33 +08:00
parent 498ddcc153
commit 9d077a9d49
180 changed files with 67026 additions and 4116 deletions

View File

@ -19,7 +19,7 @@ $ Contributors and volunteers of the upstream repo, having no responsibility in
- Voltaire candidate window MK2 (massively modified as MK3 in vChewing by Shiki Suen).
- Notifier window.
- App-style installer (only preserved for developer purposes).
- mgrPrefs (userdefaults manager).
- PrefMgr (userdefaults manager).
- Mengjuei Hsieh:
- McBopomofo for macOS 1.x main developer and architect.
- The original C++ version of the User Override Module.

View File

@ -9,6 +9,7 @@
// requirements defined in MIT License.
import Cocoa
import IMKUtils
import InputMethodKit
private let kTargetBin = "vChewing"

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "FolderMonitor",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "FolderMonitor",
targets: ["FolderMonitor"]
)
],
dependencies: [],
targets: [
.target(
name: "FolderMonitor",
dependencies: []
)
]
)

View File

@ -0,0 +1,8 @@
# FolderMonitor
```
// (c) 2018 Daniel Galasko
// Ref: https://medium.com/over-engineering/monitoring-a-folder-for-changes-in-ios-dc3f8614f902
```
用以監視使用者辭典資料夾內容變化的模組。監視到變化之後,威注音輸入法會自動整理格式、且自動重新載入之。

View File

@ -3,7 +3,7 @@
import Foundation
class FolderMonitor {
public class FolderMonitor {
// MARK: Properties
/// A file descriptor for the monitored directory.
@ -13,20 +13,20 @@ class FolderMonitor {
/// A dispatch source to monitor a file descriptor created from the directory.
private var folderMonitorSource: DispatchSourceFileSystemObject?
/// URL for the directory being monitored.
let url: URL
public let url: URL
var folderDidChange: (() -> Void)?
public var folderDidChange: (() -> Void)?
// MARK: Initializers
init(url: URL) {
public init(url: URL) {
self.url = url
}
// MARK: Monitoring
/// Listen for changes to the directory (if we are not already).
func startMonitoring() {
public func startMonitoring() {
guard folderMonitorSource == nil, monitoredFolderFileDescriptor == -1 else {
return
}
@ -52,7 +52,7 @@ class FolderMonitor {
}
/// Stop listening for changes to the directory, if the source has been created.
func stopMonitoring() {
public func stopMonitoring() {
folderMonitorSource?.cancel()
}
}

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "NSAttributedTextView",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "NSAttributedTextView",
targets: ["NSAttributedTextView"]
)
],
dependencies: [],
targets: [
.target(
name: "NSAttributedTextView",
dependencies: []
)
]
)

View File

@ -0,0 +1,10 @@
# NSAttributedTextView
可以顯示縱排文字的一種 NSView。
作者是 Fuziki並未指定任何授權但有允許說可以任意使用。
原始版本參見https://qiita.com/fuziki/items/b31055a69330a3ce55a5
威注音輸入法倉庫內提供的版本為經過威注音專案修改過的版本,可用於 macOS 應用程式研發。

View File

@ -98,7 +98,7 @@ public class NSAttributedTextView: NSView {
]
public var text: String? { didSet { ctFrame = nil } }
private var ctFrame: CTFrame?
private(set) var currentRect: NSRect?
public private(set) var currentRect: NSRect?
@discardableResult public func shrinkFrame() -> NSRect {
let attrString: NSAttributedString = {

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "BookmarkManager",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "BookmarkManager",
targets: ["BookmarkManager"]
)
],
dependencies: [],
targets: [
.target(
name: "BookmarkManager",
dependencies: []
)
]
)

View File

@ -0,0 +1,8 @@
# BookmarkManager
```
// Ref: https://stackoverflow.com/a/61695824
// License: https://creativecommons.org/licenses/by-sa/4.0/
```
該模組為沙箱機制所必需。不然的話,每次重新啟動之後,輸入法都會喪失對前一次設定過的使用者辭典的沙箱存取許可的記錄,然後使用者就得再次手動設定辭典目錄。也有直接實現該功能的方法,但是比較麻煩,所以就用了這個現成的方案。

View File

@ -1,18 +1,16 @@
//
// Ref: https://stackoverflow.com/a/61695824
// License: https://creativecommons.org/licenses/by-sa/4.0/
//
import Cocoa
class BookmarkManager {
static let shared = BookmarkManager()
public class BookmarkManager {
public static let shared = BookmarkManager()
// Save bookmark for URL. Use this inside the NSOpenPanel `begin` closure
func saveBookmark(for url: URL) {
public func saveBookmark(for url: URL) {
guard let bookmarkDic = getBookmarkData(url: url),
let bookmarkURL = getBookmarkURL()
else {
IME.prtDebugIntel("Error getting data or bookmarkURL")
NSLog("Error getting data or bookmarkURL")
return
}
@ -20,15 +18,15 @@ class BookmarkManager {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarkDic, requiringSecureCoding: false)
try data.write(to: bookmarkURL)
IME.prtDebugIntel("Did save data to url")
NSLog("Did save data to url")
} catch {
IME.prtDebugIntel("Couldn't save bookmarks")
NSLog("Couldn't save bookmarks")
}
}
}
// Load bookmarks when your app launch for example
func loadBookmarks() {
public func loadBookmarks() {
guard let url = getBookmarkURL() else {
return
}
@ -42,7 +40,7 @@ class BookmarkManager {
}
}
} catch {
IME.prtDebugIntel("Couldn't load bookmarks")
NSLog("Couldn't load bookmarks")
}
}
}
@ -51,23 +49,23 @@ class BookmarkManager {
let restoredUrl: URL?
var isStale = false
IME.prtDebugIntel("Restoring \(key)")
NSLog("Restoring \(key)")
do {
restoredUrl = try URL(
resolvingBookmarkData: value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil,
bookmarkDataIsStale: &isStale
)
} catch {
IME.prtDebugIntel("Error restoring bookmarks")
NSLog("Error restoring bookmarks")
restoredUrl = nil
}
if let url = restoredUrl {
if isStale {
IME.prtDebugIntel("URL is stale")
NSLog("URL is stale")
} else {
if !url.startAccessingSecurityScopedResource() {
IME.prtDebugIntel("Couldn't access: \(url.path)")
NSLog("Couldn't access: \(url.path)")
}
}
}

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "ShiftKeyUpChecker",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "ShiftKeyUpChecker",
targets: ["ShiftKeyUpChecker"]
)
],
dependencies: [],
targets: [
.target(
name: "ShiftKeyUpChecker",
dependencies: []
)
]
)

View File

@ -0,0 +1,13 @@
# ShiftKeyUpChecker
```
// (c) 2022 and onwards Qwertyyb (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
```
自[業火五筆輸入法](https://github.com/qwertyyb/Fire)承襲的模組用來判定「Shift 鍵是否有被單獨摁過」。
該方法並非 Apple 在官方技術文件當中講明的方法,而是業火五筆輸入法的作者首創。
方法的原理就是連續分析前後兩個 NSEvent。由於只需要接收藉由 IMK 傳入的 NSEvent 而不需要監聽系統全局鍵盤事件,所以沒有資安疑慮。

View File

@ -2,28 +2,36 @@
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
import Carbon
import Cocoa
extension Date {
static func - (lhs: Date, rhs: Date) -> TimeInterval {
fileprivate static func - (lhs: Date, rhs: Date) -> TimeInterval {
lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
}
}
class ShiftKeyUpChecker {
init() {}
private static var checkModifier: NSEvent.ModifierFlags { NSEvent.ModifierFlags.shift }
private static var checkKeyCode: [UInt16] {
mgrPrefs.togglingAlphanumericalModeWithLShift
? [KeyCode.kShift.rawValue, KeyCode.kRightShift.rawValue]
: [KeyCode.kRightShift.rawValue]
public struct ShiftKeyUpChecker {
public init(useLShift: Bool) {
alsoToggleWithLShift = useLShift
}
private static let delayInterval = 0.3
public var alsoToggleWithLShift = false
public var lShiftKeyCode: UInt16 = 56
public var rShiftKeyCode: UInt16 = 60
private static var lastTime: Date = .init()
private var checkModifier: NSEvent.ModifierFlags { NSEvent.ModifierFlags.shift }
private var checkKeyCode: [UInt16] {
alsoToggleWithLShift
? [lShiftKeyCode, rShiftKeyCode]
: [rShiftKeyCode]
}
private static func checkModifierKeyUp(event: NSEvent) -> Bool {
private let delayInterval = 0.3
private var lastTime: Date = .init()
private mutating func checkModifierKeyUp(event: NSEvent) -> Bool {
if event.type == .flagsChanged,
event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .init(rawValue: 0),
Date() - lastTime <= delayInterval
@ -35,14 +43,14 @@ class ShiftKeyUpChecker {
return false
}
private static func checkModifierKeyDown(event: NSEvent) -> Bool {
private mutating func checkModifierKeyDown(event: NSEvent) -> Bool {
let isLeftShift = event.modifierFlags.rawValue & UInt(NX_DEVICELSHIFTKEYMASK) != 0
let isRightShift = event.modifierFlags.rawValue & UInt(NX_DEVICERSHIFTKEYMASK) != 0
print("isLeftShift: \(isLeftShift), isRightShift: \(isRightShift)")
let isKeyDown =
event.type == .flagsChanged
&& checkModifier.contains(event.modifierFlags.intersection(.deviceIndependentFlagsMask))
&& ShiftKeyUpChecker.checkKeyCode.contains(event.keyCode)
&& checkKeyCode.contains(event.keyCode)
if isKeyDown {
// modifier keydown event
lastTime = Date()
@ -53,7 +61,7 @@ class ShiftKeyUpChecker {
}
// To confirm that the shift key is "pressed-and-released".
public static func check(_ event: NSEvent) -> Bool {
ShiftKeyUpChecker.checkModifierKeyUp(event: event) || ShiftKeyUpChecker.checkModifierKeyDown(event: event)
public mutating func check(_ event: NSEvent) -> Bool {
checkModifierKeyUp(event: event) || checkModifierKeyDown(event: event)
}
}

9
Packages/RMJay_LineReader/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "LineReader",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "LineReader",
targets: ["LineReader"]
)
],
dependencies: [],
targets: [
.target(
name: "LineReader",
dependencies: []
)
]
)

View File

@ -0,0 +1,5 @@
# LineReader
```
// (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
```
快速讀行掃描器。僅用於威注音的格式整理模組內。

View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
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.

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Preferences",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "Preferences",
targets: [
"Preferences"
]
)
],
targets: [
.target(
name: "Preferences"
)
]
)

View File

@ -0,0 +1,9 @@
# Sindresorhus Preferences
```
// (c) 2018 and onwards Sindre Sorhus (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
```
See its original repository for details: https://github.com/sindresorhus/Preferences/

View File

@ -104,7 +104,7 @@ struct Localization {
*/
static subscript(identifier: Identifier) -> String {
// Force-unwrapped since all of the involved code is under our control.
let localizedDict = Localization.localizedStrings[identifier]!
let localizedDict = Self.localizedStrings[identifier]!
let defaultLocalizedString = localizedDict["en"]!
// Iterate through all user-preferred languages until we find one that has a valid language code.

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,26 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "CocoaExtension",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "CocoaExtension",
targets: ["CocoaExtension"]
)
],
dependencies: [
.package(path: "../vChewing_IMKUtils")
],
targets: [
.target(
name: "CocoaExtension",
dependencies: [
.product(name: "IMKUtils", package: "vChewing_IMKUtils")
]
)
]
)

View File

@ -0,0 +1,13 @@
# CocoaExtension
威注音輸入法針對 Cocoa 的一些功能擴充,使程式維護體驗更佳。
```
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
```

View File

@ -0,0 +1,96 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Cocoa
// MARK: NSRect Extension
extension NSRect {
public static var seniorTheBeast: NSRect {
NSRect(x: 0.0, y: 0.0, width: 0.114, height: 0.514)
}
}
// MARK: - Shell Extension
extension NSApplication {
public static func shell(_ command: String) throws -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
if #available(macOS 10.13, *) {
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
} else {
task.launchPath = "/bin/zsh"
}
task.standardInput = nil
if #available(macOS 10.13, *) {
try task.run()
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
}
extension NSApplication {
// MARK: - System Dark Mode Status Detector.
public static var isDarkMode: Bool {
if #unavailable(macOS 10.14) { return false }
if #available(macOS 10.15, *) {
let appearanceDescription = NSApplication.shared.effectiveAppearance.debugDescription
.lowercased()
return appearanceDescription.contains("dark")
} else if let appleInterfaceStyle = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") {
return appleInterfaceStyle.lowercased().contains("dark")
}
return false
}
// MARK: - Tell whether this IME is running with Root privileges.
public static var isSudoMode: Bool {
NSUserName() == "root"
}
}
// MARK: - Real Home Dir for Sandboxed Apps
extension FileManager {
public static let realHomeDir = URL(
fileURLWithFileSystemRepresentation: getpwuid(getuid()).pointee.pw_dir, isDirectory: true, relativeTo: nil
)
}
// MARK: - Trash a file if it exists.
extension FileManager {
@discardableResult public static func trashTargetIfExists(_ path: String) -> Bool {
do {
if FileManager.default.fileExists(atPath: path) {
//
try FileManager.default.trashItem(
at: URL(fileURLWithPath: path), resultingItemURL: nil
)
} else {
NSLog("Item doesn't exist: \(path)")
}
} catch let error as NSError {
NSLog("Failed from removing this object: \(path) || Error: \(error)")
return false
}
return true
}
}

View File

@ -7,6 +7,7 @@
// requirements defined in MIT License.
import Cocoa
import IMKUtils
// MARK: - NSEvent Extension - Reconstructors
@ -47,8 +48,8 @@ extension NSEvent {
guard isEmacsKey else { return self }
let newKeyCode: UInt16 = {
switch isVerticalContext {
case false: return IME.vChewingEmacsKey.charKeyMapHorizontal[charCode] ?? 0
case true: return IME.vChewingEmacsKey.charKeyMapVertical[charCode] ?? 0
case false: return EmacsKey.charKeyMapHorizontal[charCode] ?? 0
case true: return EmacsKey.charKeyMapVertical[charCode] ?? 0
}
}()
guard newKeyCode != 0 else { return self }
@ -75,13 +76,12 @@ extension NSEvent {
// MARK: - NSEvent Extension - InputSignalProtocol
extension NSEvent: InputSignalProtocol {
public var isASCIIModeInput: Bool { ctlInputMethod.isASCIIModeSituation }
public var isTypingVertical: Bool { ctlInputMethod.isVerticalTypingSituation }
public var text: String { AppleKeyboardConverter.cnvStringApple2ABC(characters ?? "") }
extension NSEvent {
public var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
public var text: String { convertedFromPhonabets() }
public var inputTextIgnoringModifiers: String? {
guard let charIgnoringModifiers = charactersIgnoringModifiers else { return nil }
return AppleKeyboardConverter.cnvStringApple2ABC(charIgnoringModifiers)
guard charactersIgnoringModifiers != nil else { return nil }
return convertedFromPhonabets(ignoringModifiers: true)
}
public var charCode: UInt16 {
@ -121,11 +121,6 @@ extension NSEvent: InputSignalProtocol {
return code.rawValue != KeyCode.kNone.rawValue
}
public var isCandidateKey: Bool {
mgrPrefs.candidateKeys.contains(text)
|| mgrPrefs.candidateKeys.contains(inputTextIgnoringModifiers ?? "114514")
}
/// flags KeyCode
public var isNumericPadKey: Bool { arrNumpadKeyCodes.contains(keyCode) }
public var isMainAreaNumKey: Bool { arrMainAreaNumKey.contains(keyCode) }
@ -191,55 +186,6 @@ extension NSEvent: InputSignalProtocol {
}
}
// MARK: - InputSignalProtocol
public protocol InputSignalProtocol {
var isASCIIModeInput: Bool { get }
var isTypingVertical: Bool { get }
var text: String { get }
var inputTextIgnoringModifiers: String? { get }
var charCode: UInt16 { get }
var keyCode: UInt16 { get }
var isFlagChanged: Bool { get }
var mainAreaNumKeyChar: String? { get }
var isASCII: Bool { get }
var isInvalid: Bool { get }
var isKeyCodeBlacklisted: Bool { get }
var isReservedKey: Bool { get }
var isCandidateKey: Bool { get }
var isNumericPadKey: Bool { get }
var isMainAreaNumKey: Bool { get }
var isShiftHold: Bool { get }
var isCommandHold: Bool { get }
var isControlHold: Bool { get }
var isControlHotKey: Bool { get }
var isOptionHold: Bool { get }
var isOptionHotKey: Bool { get }
var isCapsLockOn: Bool { get }
var isFunctionKeyHold: Bool { get }
var isNonLaptopFunctionKey: Bool { get }
var isEnter: Bool { get }
var isTab: Bool { get }
var isUp: Bool { get }
var isDown: Bool { get }
var isLeft: Bool { get }
var isRight: Bool { get }
var isPageUp: Bool { get }
var isPageDown: Bool { get }
var isSpace: Bool { get }
var isBackSpace: Bool { get }
var isEsc: Bool { get }
var isHome: Bool { get }
var isEnd: Bool { get }
var isDelete: Bool { get }
var isCursorBackward: Bool { get }
var isCursorForward: Bool { get }
var isCursorClockRight: Bool { get }
var isCursorClockLeft: Bool { get }
var isUpperCaseASCIILetterKey: Bool { get }
var isSymbolMenuPhysicalKey: Bool { get }
}
// MARK: - Enums of Constants
// Use KeyCodes as much as possible since its recognition won't be affected by macOS Base Keyboard Layouts.
@ -350,3 +296,10 @@ enum CharCode: UInt16 {
// KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ...
// ... but only focuses on which physical key is pressed.
}
// MARK: - Emacs CharCode-KeyCode translation tables.
public enum EmacsKey {
static let charKeyMapHorizontal: [UInt16: UInt16] = [6: 124, 2: 123, 1: 115, 5: 119, 4: 117, 22: 121]
static let charKeyMapVertical: [UInt16: UInt16] = [6: 125, 2: 126, 1: 115, 5: 119, 4: 117, 22: 121]
}

View File

@ -7,32 +7,24 @@
// requirements defined in MIT License.
import AVFoundation
import Foundation
import AppKit
public enum clsSFX {
static func beep() {
let filePath = Bundle.main.path(forResource: mgrPrefs.shouldNotFartInLieuOfBeep ? "Beep" : "Fart", ofType: "m4a")!
extension NSSound {
public static func buzz(fart: Bool = false) {
let filePath = Bundle.main.path(forResource: fart ? "Fart" : "Beep", ofType: "m4a")!
let fileURL = URL(fileURLWithPath: filePath)
var soundID: SystemSoundID = 0
AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID)
AudioServicesPlaySystemSound(soundID)
}
static func fart() {
let filePath = Bundle.main.path(forResource: "Fart", ofType: "m4a")!
let fileURL = URL(fileURLWithPath: filePath)
var soundID: SystemSoundID = 0
AudioServicesCreateSystemSoundID(fileURL as CFURL, &soundID)
AudioServicesPlaySystemSound(soundID)
}
static func beep(count: Int = 1) {
public static func buzz(fart _: Bool = false, count: Int) {
if count <= 1 {
clsSFX.beep()
NSSound.buzz()
return
}
for _ in 0...count {
clsSFX.beep()
NSSound.buzz()
usleep(500_000)
}
}

View File

@ -0,0 +1,20 @@
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,26 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Hotenka",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "Hotenka",
targets: ["Hotenka"]
)
],
dependencies: [],
targets: [
.target(
name: "Hotenka",
dependencies: []
),
.testTarget(
name: "HotenkaTests",
dependencies: ["Hotenka"]
),
]
)

View File

@ -0,0 +1,18 @@
# Hotenka Engine 步天歌引擎
- Gitee: [Swift](https://gitee.com/vChewing/Hotenka)
- GitHub: [Swift](https://github.com/vChewing/Hotenka)
步天歌引擎是一套簡繁轉換模組,將 Nick Chen 的 ObjC 模組「[NCChineseConverter](https://github.com/nickcheng/NCChineseConverter)」用 Swift 重寫而得。簡繁轉換資料改用 OpenCC 的轉換資料Apache License 2.0)且有做了一些修改。
Hotenka Engine is a module made for converting between Simplified Chinese and Traditional Chinese. This module is using the translation data from OpenCC (Apache License 2.0).
## 使用說明
詳見 HotenkaTest.swift。要編譯 plist 詞庫的話,跑一遍單元測試即可自動生成 plist 詞庫檔案。
## 著作權 (Credits)
- Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
- Swift programmer: Shiki Suen
- Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License).

View File

@ -1,11 +1,27 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
/*
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import Foundation
@ -25,7 +41,7 @@ public enum DictType {
}
public class HotenkaChineseConverter {
private(set) var dict: [String: [String: String]]
public private(set) var dict: [String: [String: String]]
private var dictFiles: [String: [String]]
public init(plistDir: String) {
@ -107,7 +123,7 @@ public class HotenkaChineseConverter {
// MARK: - Public Methods
func convert(_ target: String, to dictType: DictType) -> String {
public func convert(_ target: String, to dictType: DictType) -> String {
var dictTypeKey: String
switch dictType {

View File

@ -0,0 +1,64 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License).
/*
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import Foundation
import XCTest
@testable import Hotenka
private let packageRootPath = URL(fileURLWithPath: #file).pathComponents.prefix(while: { $0 != "Tests" }).joined(
separator: "/"
).dropFirst()
private let testDataPath: String = packageRootPath + "/Tests/TestDictData/"
final class HotenkaTests: XCTestCase {
func testGeneratingPlist() throws {
NSLog("// Start loading from: \(packageRootPath)")
let testInstance: HotenkaChineseConverter = .init(dictDir: testDataPath)
NSLog("// Loading complete. Generating plist dict file.")
do {
try PropertyListSerialization.data(fromPropertyList: testInstance.dict, format: .binary, options: 0).write(
to: URL(fileURLWithPath: testDataPath + "convdict.plist"))
} catch {
NSLog("// Error on writing strings to file: \(error)")
}
}
func testSampleWithPlist() throws {
NSLog("// Start loading plist from: \(packageRootPath)")
let testInstance2: HotenkaChineseConverter = .init(plistDir: testDataPath + "convdict.plist")
NSLog("// Successfully loading plist dictionary.")
let oriString = "为中华崛起而读书"
let result1 = testInstance2.convert(oriString, to: .zhHantTW)
let result2 = testInstance2.convert(result1, to: .zhHantKX)
let result3 = testInstance2.convert(result2, to: .zhHansJP)
NSLog("// Results: \(result1) \(result2) \(result3)")
XCTAssertEqual(result1, "為中華崛起而讀書")
XCTAssertEqual(result2, "爲中華崛起而讀書")
XCTAssertEqual(result3, "為中華崛起而読書")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,549 @@
一攫千金 一獲千金
叮嚀 丁寧
鄭重 丁重
三叉路 三差路
輿論 世論
啞鈴 亜鈴
交叉 交差
饗宴 供宴
駿馬 俊馬
堡壘 保塁
箇条書 個条書
扁平 偏平
碇泊 停泊
優駿 優俊
尖兵 先兵
尖鋭 先鋭
共軛 共役
饒舌 冗舌
兇器 凶器
鑿岩 削岩
庖丁 包丁
繃帯 包帯
區劃 区画
儼然 厳然
友誼 友宜
叛乱 反乱
蒐集 収集
抒情 叙情
擡頭 台頭
合辦 合弁
嬉遊曲 喜遊曲
歎願 嘆願
廻転 回転
回游 回遊
捧持 奉持
萎縮 委縮
輾轉 展転
稀少 希少
眩惑 幻惑
廣汎 広範
曠野 広野
廢墟 廃虚
建蔽率 建坪率
辨當 弁当
瓣膜 弁膜
辯護 弁護
辮髮 弁髪
絃歌 弦歌
恩誼 恩義
意嚮 意向
慰藉料 慰謝料
臆断 憶断
臆病 憶病
戰歿 戦没
煽情 扇情
手帖 手帳
伎倆 技量
抜萃 抜粋
披瀝 披歴
牴触 抵触
抽籤 抽選
勾引 拘引
醵出 拠出
醵金 拠金
掘鑿 掘削
扣除 控除
掩護 援護
抛棄 放棄
撒水 散水
敬虔 敬謙
敷衍 敷延
断乎 断固
簇生 族生
陞敘 昇叙
煖房 暖房
暗誦 暗唱
闇夜 暗夜
曝露 暴露
涸渇 枯渇
恰好 格好
恰幅 格幅
毀損 棄損
摸索 模索
橋頭堡 橋頭保
欠缺 欠缺
屍體 死体
臀部 殿部
拇指 母指
気魄 気迫
訣別 決別
決潰 決壊
沈澱 沈殿
油槽船 油送船
波瀾 波乱
註釋 注釈
洗滌 洗浄 洗浄
活潑 活発
滲透 浸透
浸蝕 浸食
銷卻 消却
渾然 混然
彎曲 湾曲
熔接 溶接
漁撈 漁労
飄然 漂然
激昂 激高
火焰 火炎
焦躁 焦燥
斑点 班点
溜飲 留飲
掠奪 略奪
疏通 疎通
醱酵 発酵
白堊 白亜
相剋 相克
智慧 知恵
破毀 破棄
確乎 確固
禁錮 禁固
符牒 符丁
扮装 粉装
紫斑 紫班
終熄 終息
綜合 総合
編輯 編集
義捐 義援
耕耘機 耕運機
肝腎 肝心
肩胛骨 肩甲骨
悖德 背徳
脈搏 脈拍
膨脹 膨張
芳醇 芳純
叡智 英知
蒸溜 蒸留
燻蒸 薫蒸
燻製 薫製
衣裳 衣装
衰退 衰頽 衰退
悠然 裕然
輔佐 補佐
訓誡 訓戒
試煉 試練
詭辯 詭弁
媾和 講和
象嵌 象眼
貫禄 貫録
買辦 買弁
讚辭 賛辞
蹈襲 踏襲
車輛 車両
顛倒 転倒
輪廓 輪郭
褪色 退色
杜絶 途絶
連繫 連係
聯合 連合
銓衡 選考
醋酸 酢酸
野鄙 野卑
礦石 鉱石
間歇 間欠
函數 関数
防禦 防御
嶮岨 険阻
牆壁 障壁
障礙 障害
湮滅 隠滅
聚落 集落
雇傭 雇用
諷喩 風諭
蜚語 飛語
香奠 香典
骨骼 骨格
亢進 高進
鳥瞰 鳥観
兩 両
輛 両
辨 弁
辯 弁
瓣 弁
辦 弁
禦 御
缺 欠
絲 糸
藝 芸
濱 浜
乘 乗
亂 乱
亙 亘
亞 亜
佛 仏
來 来
假 仮
傳 伝
僞 偽
價 価
儉 倹
兒 児
內 内
剎 刹
剩 剰
劍 剣
剱 剣
劎 剣
劒 剣
劔 剣
劑 剤
勞 労
勳 勲
勵 励
勸 勧
勻 匀
區 区
卷 巻
卻 却
參 参
吳 呉
咒 呪
啞 唖
單 単
噓 嘘
嚙 噛
嚴 厳
囑 嘱
圈 圏
國 国
圍 囲
圓 円
圖 図
團 団
增 増
墮 堕
壓 圧
壘 塁
壞 壊
壤 壌
壯 壮
壹 壱
壽 寿
奧 奥
奬 奨
妝 粧
孃 嬢
學 学
寢 寝
實 実
寫 写
寬 寛
寶 宝
將 将
專 専
對 対
屆 届
屬 属
峯 峰
峽 峡
嶽 岳
巖 巌
巢 巣
帶 帯
廁 厠
廢 廃
廣 広
廳 庁
彈 弾
彌 弥
彎 弯
彥 彦
徑 径
從 従
徵 徴
德 徳
恆 恒
悅 悦
惠 恵
惡 悪
惱 悩
慘 惨
應 応
懷 懐
戀 恋
戰 戦
戲 戯
戶 戸
戾 戻
拂 払
拔 抜
拜 拝
挾 挟
插 挿
揭 掲
搔 掻
搖 揺
搜 捜
摑 掴
擇 択
擊 撃
擔 担
據 拠
擴 拡
攝 摂
攪 撹
收 収
效 効
敕 勅
敘 叙
數 数
斷 断
晉 晋
晚 晩
晝 昼
暨 曁
曆 暦
曉 暁
曾 曽
會 会
枡 桝
查 査
條 条
棧 桟
棱 稜
榆 楡
榮 栄
樂 楽
樓 楼
樞 枢
樣 様
橫 横
檢 検
櫻 桜
權 権
歐 欧
歡 歓
步 歩
歲 歳
歷 歴
歸 帰
殘 残
殼 殻
毆 殴
每 毎
氣 気
污 汚
沒 没
涉 渉
淚 涙
淨 浄
淺 浅
渴 渇
溌 潑
溪 渓
溫 温
溼 湿
滯 滞
滿 満
潛 潜
澀 渋
澤 沢
濟 済
濤 涛
濾 沪
瀧 滝
瀨 瀬
灣 湾
焰 焔
燈 灯
燒 焼
營 営
爐 炉
爭 争
爲 為
牀 床
犧 犠
狀 状
狹 狭
獨 独
獵 猟
獸 獣
獻 献
產 産
畫 画
當 当
疊 畳
疎 疏
痹 痺
瘦 痩
癡 痴
發 発
皋 皐
盜 盗
盡 尽
碎 砕
祕 秘
祿 禄
禪 禅
禮 礼
禱 祷
稅 税
稱 称
稻 稲
穎 頴
穗 穂
穩 穏
穰 穣
竃 竈
竊 窃
粹 粋
糉 粽
絕 絶
經 経
綠 緑
緖 緒
緣 縁
縣 県
縱 縦
總 総
繋 繫
繡 繍
繩 縄
繪 絵
繼 継
續 続
纔 才
纖 繊
罐 缶
羣 群
聯 連
聰 聡
聲 声
聽 聴
肅 粛
脣 唇
脫 脱
腦 脳
腳 脚
膽 胆
臟 臓
臺 台
與 与
舉 挙
舊 旧
舍 舎
荔 茘
莊 荘
莖 茎
菸 煙
萊 莱
萬 万
蔣 蒋
蔥 葱
薰 薫
藏 蔵
藥 薬
蘆 芦
處 処
虛 虚
號 号
螢 蛍
蟲 虫
蠟 蝋
蠶 蚕
蠻 蛮
裝 装
覺 覚
覽 覧
觀 観
觸 触
說 説
謠 謡
證 証
譯 訳
譽 誉
讀 読
變 変
讓 譲
豐 豊
豫 予
貓 猫
貳 弐
賣 売
賴 頼
贊 賛
贗 贋
踐 践
輕 軽
輛 輌
轉 転
辭 辞
遞 逓
遥 遙
遲 遅
邊 辺
鄉 郷
酢 醋
醉 酔
醗 醱
醫 医
醬 醤
釀 醸
釋 釈
鋪 舗
錄 録
錢 銭
鍊 錬
鐵 鉄
鑄 鋳
鑛 鉱
閱 閲
關 関
陷 陥
隨 随
險 険
隱 隠
雙 双
雜 雑
雞 鶏
霸 覇
靈 霊
靜 静
顏 顔
顯 顕
餘 余
騷 騒
驅 駆
驗 験
驛 駅
髓 髄
體 体
髮 髪
鬥 闘
鱉 鼈
鷗 鴎
鹼 鹸
鹽 塩
麥 麦
麪 麺
麴 麹
黃 黄
黑 黒
默 黙
點 点
黨 党
齊 斉
齋 斎
齒 歯
齡 齢
龍 竜
龜 亀

View File

@ -0,0 +1,107 @@
一口吃個 一口喫個
一口吃成 一口喫成
一家三口 一家三口
一家五口 一家五口
一家六口 一家六口
一家四口 一家四口
凶事 凶事
凶信 凶信
凶兆 凶兆
凶吉 凶吉
凶地 凶地
凶多吉少 凶多吉少
凶宅 凶宅
凶年 凶年
凶德 凶德
凶怪 凶怪
凶日 凶日
凶服 凶服
凶歲 凶歲
凶死 凶死
凶氣 凶氣
凶煞 凶煞
凶燄 凶燄
凶神 凶神
凶禮 凶禮
凶耗 凶耗
凶肆 凶肆
凶荒 凶荒
凶訊 凶訊
凶豎 凶豎
凶身 凶身
凶逆 凶逆
凶門 凶門
口吃 口吃
吃口 喫口 吃口
吃口令 吃口令
吃口飯 喫口飯
吃吃 喫喫 吃吃
吃子 喫子 吃子
合著 合著
吉凶 吉凶
名著 名著
四凶 四凶
大凶 大凶
巨著 巨著
張口 張口
昭著 昭著
歲凶 歲凶
胃口 胃口
著作 著作
著名 著名
著式 著式
著志 著志
著於 著於
著書 著書
著白 著白
著稱 著稱
著者 著者
著述 著述
著錄 著錄
蹇吃 蹇吃
逢凶 逢凶
避凶 避凶
鄧艾吃 鄧艾吃
鉅著 鉅著
開口 開口
閔凶 閔凶
顯著 顯著
偽 僞
啟 啓
吃 喫
嫻 嫺
媯 嬀
峰 峯
么 幺
抬 擡
稜 棱
簷 檐
汙 污
洩 泄
溈 潙
潀 潨
為 爲
床 牀
痺 痹
痴 癡
皂 皁
著 着
睪 睾
秘 祕
灶 竈
粽 糉
韁 繮
才 纔
群 羣
唇 脣
參 蔘
蒍 蔿
眾 衆
裡 裏
核 覈
踴 踊
缽 鉢
針 鍼
鯰 鮎
麵 麪
顎 齶

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
.PHONY: format
format:
swiftformat ./ --swiftversion 5.5
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel

9
Packages/vChewing_IMKUtils/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "IMKUtils",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "IMKUtils",
targets: ["IMKUtils"]
)
],
dependencies: [],
targets: [
.target(
name: "IMKUtils",
dependencies: []
)
]
)

View File

@ -0,0 +1,9 @@
# IMKUtils
該模組套裝使得輸入法的登記啟用過程與鍵盤佈局管理過程更加簡便。分兩部分組成:
- IMKHelper: (c) 2021 and onwards The vChewing Project (MIT-NTL License).
- TISInputSourceExtension:
- (c) 2021 and onwards The vChewing Project (MIT-NTL License).
- (c) 2018 and onwards Mizuno Hiroki (a.k.a. "Mzp") (MIT License).
- 詳情請參見 TISInputSourceExtension.swift 內部的各種 pragma mark 標記。

View File

@ -6,16 +6,110 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
enum AppleKeyboardConverter {
static var isDynamicBasicKeyboardLayoutEnabled: Bool {
IMKHelper.arrDynamicBasicKeyLayouts.contains(mgrPrefs.basicKeyboardLayout)
import Foundation
import InputMethodKit
// MARK: - IMKHelper by The vChewing Project (MIT License).
public enum IMKHelper {
///
/// SwiftUI 便使
public static let arrWhitelistedKeyLayoutsASCII: [String] = [
"com.apple.keylayout.ABC",
"com.apple.keylayout.ABC-AZERTY",
"com.apple.keylayout.ABC-QWERTZ",
"com.apple.keylayout.British",
"com.apple.keylayout.Colemak",
"com.apple.keylayout.Dvorak",
"com.apple.keylayout.Dvorak-Left",
"com.apple.keylayout.DVORAK-QWERTYCMD",
"com.apple.keylayout.Dvorak-Right",
]
public static let arrDynamicBasicKeyLayouts: [String] = [
"com.apple.keylayout.ZhuyinBopomofo",
"com.apple.keylayout.ZhuyinEten",
"org.atelierInmu.vChewing.keyLayouts.vchewingdachen",
"org.atelierInmu.vChewing.keyLayouts.vchewingmitac",
"org.atelierInmu.vChewing.keyLayouts.vchewingibm",
"org.atelierInmu.vChewing.keyLayouts.vchewingseigyou",
"org.atelierInmu.vChewing.keyLayouts.vchewingeten",
"org.unknown.keylayout.vChewingDachen",
"org.unknown.keylayout.vChewingFakeSeigyou",
"org.unknown.keylayout.vChewingETen",
"org.unknown.keylayout.vChewingIBM",
"org.unknown.keylayout.vChewingMiTAC",
]
public static var currentBasicKeyboardLayout: String {
UserDefaults.standard.string(forKey: "BasicKeyboardLayout") ?? ""
}
static func cnvStringApple2ABC(_ strProcessed: String) -> String {
var strProcessed = strProcessed
if isDynamicBasicKeyboardLayoutEnabled {
public static var isDynamicBasicKeyboardLayoutEnabled: Bool {
Self.arrDynamicBasicKeyLayouts.contains(currentBasicKeyboardLayout) || !currentBasicKeyboardLayout.isEmpty
}
public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
}
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
//
var containerA: [TISInputSource?] = []
var containerB: [TISInputSource?] = []
var containerC: [TISInputSource?] = []
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
Self.arrWhitelistedKeyLayoutsASCII.forEach {
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
containerC.append(neta)
}
}
Self.arrDynamicBasicKeyLayouts.forEach {
if let neta = rawDictionary[$0] {
if neta.identifier.contains("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
}
}
// nil
if !containerA.isEmpty { containerA.append(nil) }
if !containerB.isEmpty { containerB.append(nil) }
return containerA + containerB + containerC
}
public struct CarbonKeyboardLayout {
var strName: String = ""
var strValue: String = ""
}
}
// MARK: -
extension IMKHelper {
@discardableResult public static func registerInputMethod() -> Int32 {
TISInputSource.registerInputMethod() ? 0 : -1
}
}
// MARK: - Apple Keyboard Converter
extension NSEvent {
public func convertedFromPhonabets(ignoringModifiers: Bool = false) -> String {
if type == .flagsChanged { return "" }
var strProcessed = charactersIgnoringModifiers ?? characters ?? ""
if !ignoringModifiers {
strProcessed = characters ?? ""
}
if IMKHelper.isDynamicBasicKeyboardLayoutEnabled {
// Apple
switch mgrPrefs.basicKeyboardLayout {
switch IMKHelper.currentBasicKeyboardLayout {
case "com.apple.keylayout.ZhuyinBopomofo":
if strProcessed.count == 1, Character(strProcessed).isLowercase, Character(strProcessed).isASCII {
strProcessed = strProcessed.uppercased()
@ -125,7 +219,7 @@ enum AppleKeyboardConverter {
// Alt
if strProcessed == "" { strProcessed = "-" }
// Apple
if mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
if IMKHelper.currentBasicKeyboardLayout == "com.apple.keylayout.ZhuyinEten" {
switch strProcessed {
case "_": strProcessed = "_"
case "": strProcessed = ":"
@ -135,6 +229,10 @@ enum AppleKeyboardConverter {
default: break
}
}
// macOS bug
if "".contains(strProcessed), keyCode == 50 {
strProcessed = "`"
}
}
return strProcessed
}

View File

@ -9,87 +9,6 @@
import Foundation
import InputMethodKit
// MARK: - IMKHelper by The vChewing Project (MIT License).
enum IMKHelper {
///
/// SwiftUI 便使
static let arrWhitelistedKeyLayoutsASCII: [String] = [
"com.apple.keylayout.ABC",
"com.apple.keylayout.ABC-AZERTY",
"com.apple.keylayout.ABC-QWERTZ",
"com.apple.keylayout.British",
"com.apple.keylayout.Colemak",
"com.apple.keylayout.Dvorak",
"com.apple.keylayout.Dvorak-Left",
"com.apple.keylayout.DVORAK-QWERTYCMD",
"com.apple.keylayout.Dvorak-Right",
]
static let arrDynamicBasicKeyLayouts: [String] = [
"com.apple.keylayout.ZhuyinBopomofo",
"com.apple.keylayout.ZhuyinEten",
"org.atelierInmu.vChewing.keyLayouts.vchewingdachen",
"org.atelierInmu.vChewing.keyLayouts.vchewingmitac",
"org.atelierInmu.vChewing.keyLayouts.vchewingibm",
"org.atelierInmu.vChewing.keyLayouts.vchewingseigyou",
"org.atelierInmu.vChewing.keyLayouts.vchewingeten",
"org.unknown.keylayout.vChewingDachen",
"org.unknown.keylayout.vChewingFakeSeigyou",
"org.unknown.keylayout.vChewingETen",
"org.unknown.keylayout.vChewingIBM",
"org.unknown.keylayout.vChewingMiTAC",
]
static var allowedAlphanumericalTISInputSources: [TISInputSource] {
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
}
static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
//
var containerA: [TISInputSource?] = []
var containerB: [TISInputSource?] = []
var containerC: [TISInputSource?] = []
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
IMKHelper.arrWhitelistedKeyLayoutsASCII.forEach {
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
containerC.append(neta)
}
}
IMKHelper.arrDynamicBasicKeyLayouts.forEach {
if let neta = rawDictionary[$0] {
if neta.identifier.contains("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
}
}
// nil
if !containerA.isEmpty { containerA.append(nil) }
if !containerB.isEmpty { containerB.append(nil) }
return containerA + containerB + containerC
}
struct CarbonKeyboardLayout {
var strName: String = ""
var strValue: String = ""
}
}
// MARK: -
extension IMKHelper {
@discardableResult static func registerInputMethod() -> Int32 {
TISInputSource.registerInputMethod() ? 0 : -1
}
}
// MARK: - TISInputSource Extension by The vChewing Project (MIT License).
extension TISInputSource {
@ -162,7 +81,7 @@ extension TISInputSource {
}
public static func generate(from identifier: String) -> TISInputSource? {
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier] ?? nil
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
}
public var inputModeID: String {

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,32 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "LangModelAssembly",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "LangModelAssembly",
targets: ["LangModelAssembly"]
)
],
dependencies: [
.package(path: "../RMJay_LineReader"),
.package(path: "../vChewing_Megrez"),
.package(path: "../vChewing_PinyinPhonaConverter"),
.package(path: "../vChewing_Shared"),
],
targets: [
.target(
name: "LangModelAssembly",
dependencies: [
.product(name: "LineReader", package: "RMJay_LineReader"),
.product(name: "Megrez", package: "vChewing_Megrez"),
.product(name: "Shared", package: "vChewing_Shared"),
.product(name: "PinyinPhonaConverter", package: "vChewing_PinyinPhonaConverter"),
]
)
]
)

View File

@ -0,0 +1,13 @@
# LangModelAssembly
威注音輸入法的語言模組總成套裝。
```
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
```

View File

@ -1,5 +1,3 @@
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
@ -10,53 +8,46 @@
import Foundation
public class SymbolNode {
var title: String
var children: [SymbolNode]?
var previous: SymbolNode?
public class CandidateNode {
public var name: String
public var members: [CandidateNode]
public var previous: CandidateNode?
init(_ title: String, _ children: [SymbolNode]? = nil, previous: SymbolNode? = nil) {
self.title = title
self.children = children
self.children?.forEach {
$0.previous = self
}
public init(name: String, members: [CandidateNode] = [], previous: CandidateNode? = nil) {
self.name = name
self.members = members
members.forEach { $0.previous = self }
self.previous = previous
}
init(_ title: String, symbols: String) {
self.title = title
children = Array(symbols).map { SymbolNode(String($0), nil) }
children?.forEach {
$0.previous = self
}
public init(name: String, symbols: String) {
self.name = name
members = Array(symbols).map { CandidateNode(name: String($0), symbols: []) }
members.forEach { $0.previous = self }
}
init(_ title: String, symbols: [String]) {
self.title = title
children = symbols.map { SymbolNode($0, nil) }
children?.forEach {
$0.previous = self
}
public init(name: String, symbols: [String]) {
self.name = name
members = symbols.map { CandidateNode(name: $0, symbols: []) }
members.forEach { $0.previous = self }
}
static func parseUserSymbolNodeData() {
let url = mgrLangModel.userSymbolNodeDataURL()
public static func load(url: URL) {
//
var arrLines = [String.SubSequence]()
var fieldSlice = [Substring.SubSequence]()
var arrChildren = [SymbolNode]()
var arrMembers = [CandidateNode]()
do {
arrLines = try String(contentsOfFile: url.path, encoding: .utf8).split(separator: "\n")
for strLine in arrLines.lazy.filter({ !$0.isEmpty }) {
fieldSlice = strLine.split(separator: "=")
switch fieldSlice.count {
case 1: arrChildren.append(.init(String(fieldSlice[0])))
case 2: arrChildren.append(.init(String(fieldSlice[0]), symbols: .init(fieldSlice[1])))
case 1: arrMembers.append(.init(name: String(fieldSlice[0])))
case 2: arrMembers.append(.init(name: String(fieldSlice[0]), symbols: .init(fieldSlice[1])))
default: break
}
}
root = arrChildren.isEmpty ? defaultSymbolRoot : .init("/", arrChildren)
root = arrMembers.isEmpty ? defaultSymbolRoot : .init(name: "/", members: arrMembers)
} catch {
root = defaultSymbolRoot
}
@ -89,81 +80,81 @@ public class SymbolNode {
static let catThai = NSLocalizedString("catThai", comment: "")
static let catYi = NSLocalizedString("catYi", comment: "")
private(set) static var root: SymbolNode = .init("/")
public private(set) static var root: CandidateNode = .init(name: "/")
private static let defaultSymbolRoot: SymbolNode = .init(
"/",
[
SymbolNode(" "),
SymbolNode(""),
SymbolNode(catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%﹪@&#*・…—〜/\_―‖﹫﹟﹠﹡"),
SymbolNode(
catHoriBrackets,
private static let defaultSymbolRoot: CandidateNode = .init(
name: "/",
members: [
CandidateNode(name: " "),
CandidateNode(name: ""),
CandidateNode(name: catCommonSymbols, symbols: ",、。.?!;:‧‥﹐﹒˙·‘’“”〝〞‵′〃~$%﹪@&#*・…—〜/\_―‖﹫﹟﹠﹡"),
CandidateNode(
name: catHoriBrackets,
symbols:
"()[]{}〈〉《》「」『』【】〔〕〖〗〘〙〚〛︗︘︷︸︹︺︻︼︽︾︿﹀﹁﹂﹃﹄﹇﹈︵︶[]{}⁅⁆⎡⎢⎣⎤⎥⎦⎧⎨⎩⎪⎫⎬⎭⎰⎱「」❬❭❰❱❲❳❴❵⟦⟧⟨⟩⟪⟫⟬⟭⦃⦄⦇⦈⦉⦊⦋⦌⦍⦎⦏⦐⦑⦒⦓⦔⦕⦖⦗⦘⧼⧽⸂⸃⸄⸅⸉⸊⸌⸍⸜⸝⸢⸣⸤⸥⸦⸧⎴⎵⎶⏞⏟⏠⏡﹙﹚﹛﹜﹝﹞﹤﹥‘’“”〝〞‵′″'"
),
SymbolNode(catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
SymbolNode(
catAlphabets,
CandidateNode(name: catVertBrackets, symbols: "︵︶﹁﹂︹︺︷︸︿﹀﹃﹄︽︾︻︼"),
CandidateNode(
name: catAlphabets,
symbols:
"αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩабвгдежзийклмнопрстуфхцчшщъыьэюяёАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯЁàáèéêìíòóùúüāēěīńňōūǎǐǒǔǖǘǚǜɑɡ¨·"
),
SymbolNode(
catSpecialNumbers, symbols: "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ〇〡〢〣〤〥〦〧〨〩"
CandidateNode(
name: catSpecialNumbers, symbols: "ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ〇〡〢〣〤〥〦〧〨〩"
),
SymbolNode(
catMathSymbols,
CandidateNode(
name: catMathSymbols,
symbols:
"﹢﹤﹥+-<=>✕%+<=>¡¢«°±µ»¼½¾¿×÷ˇθπ‰₠₡₢₣₤₥₦₧₨₩₪₫€℀℁℃℅℆℉⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞⅟∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡∢∣∤∥∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙⊚⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱"
),
SymbolNode(catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷¤¢¥£$﹩₩"),
SymbolNode(
catSpecialSymbols,
CandidateNode(name: catCurrencyUnits, symbols: "$€¥¢£₽₨₩฿₺₮₱₭₴₦৲৳૱௹﷼₹₲₪₡₫៛₵₢₸₤₳₥₠₣₰₧₯₶₷¤¢¥£$﹩₩"),
CandidateNode(
name: catSpecialSymbols,
symbols:
"↩⌘⎋⏏⇥⇤⇪⇧⌤⌥⎇␣⌃⌄⌅⌆⌦⌫⌧⇱↖↸⇲↘⇞⇟↑↓←→⇡⇣⇠⇢⚙⇭⌽⌀⌁⌂⌐⌑⌒⌓⌔⌕⌖⌗⌙⌨⎄⎅⎆⎈⎉⎊⎌⌚⌛⎗⎘⎙⎚⎀⎁⎂⎃⌇⌈⌉⌊⌋⌌⌍⌏⌠⎮⌡⌢⌣⌜⌝⌞⌟⁒〈〉⌬⌭⌮⌯⌰⌱⌲⌳↗↙↺⇩⇦⇨⇄⇆⇅⇵↻◎○●⊕⊙※△▲☆★◇◆□■▽▼№℡§〒♀♂↯¶©®™🜀🜁🜂🜃🜄🜅🜆🜇🜈🜉🜊🜋🜌🜍🜎🜏🜐🜑🜒🜓🜔🜕🜖🜗🜘🜙🜚🜛🜜🜝🜞🜟🜠🜡🜢🜣🜤🜥🜦🜧🜨🜩🜪🜫🜬🜭🜮🜯🜰🜱🜲🜳🜴🜵🜶🜷🜸🜹🜺🜻🜼🜽🜾🜿🝀🝁🝂🝃🝄🝅🝆🝇🝈🝉🝊🝋🝌🝍🝎🝏🝐🝑🝒🝓🝔🝕🝖🝗🝘🝙🝚🝛🝜🝝🝞🝟🝠🝡🝢🝣🝤🝥🝦🝧🝨🝩🝪🝫🝬🝭🝮🝯🝰🝱🝲🝳↚↛↜↝↞↟↠↡↢↣↤↥↦↧↨↪↫↬↭↮"
),
SymbolNode(
catUnicodeSymbols,
CandidateNode(
name: catUnicodeSymbols,
symbols:
"※∮∴∵∽☀☁☂☃☺☻♠♣♥♦♨♩♪♫♬♭♯■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◆◇◈◉◊○◌◍◎●◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◻◼◽◾◿☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♡♢♤♧♮♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚝⚞⚟⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮⚯⚰⚱⚲⚳⚴⚵⚶⚷⚸⚹⚺⚻⚼⚽⚾⚿⛀⛁⛂⛃⛄⛅⛆⛇⛈⛉⛊⛋⛌⛍⛎⛏⛐⛑⛒⛓⛔⛕⛖⛗⛘⛙⛚⛛⛜⛝⛞⛟⛠⛡⛢⛣⛤⛥⛦⛧⛨⛩⛪⛫⛬⛭⛮⛯⛰⛱⛲⛳⛴⛵⛶⛷⛸⛹⛺⛻⛼⛽⛾⛿✁✂✃✄✅✆✇✈✉✊✋✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✨✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❌❍❎❏❐❑❒❓❔❕❖❗❘❙❚❛❜❝❞❟❠❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵➔➕➖➗➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➰➱➲➳➴➵➶➷➸➹➺➻➼➽➾➿⬀⬁⬂⬃⬄⬅⬆⬇⬈⬉⬊⬋⬌⬍⬎⬏⬐⬑⬒⬓⬔⬕⬖⬗⬘⬙⬚⬛⬜⬝⬞⬟⬠⬡⬢⬣⬤⬥⬦⬧⬨⬩⬪⬫⬬⬭⬮⬯⬰⬱⬲⬳⬴⬵⬶⬷⬸⬹⬺⬻⬼⬽⬾⬿⭀⭁⭂⭃⭄⭅⭆⭇⭈⭉⭊⭋⭌⭐⭑⭒⭓⭔⭕⭖⭗⭘⭙₮〠〶ↀↁↂ₭〇〄㉿〆₯ℂ℄"
),
SymbolNode(
catMusicSymbols,
CandidateNode(
name: catMusicSymbols,
symbols:
"𝄀𝄁𝄂𝄃𝄄𝄅𝄆𝄇𝄈𝄉𝄊𝄋𝄌𝄍𝄎𝄏𝄐𝄑𝄒𝄓𝄔𝄕𝄖𝄗𝄘𝄙𝄚𝄛𝄜𝄝𝄞𝄟𝄠𝄡𝄢𝄣𝄤𝄥𝄦𝄩𝄪𝄫𝄬𝄭𝄮𝄯𝄰𝄱𝄲𝄳𝄴𝄵𝄶𝄷𝄸𝄹𝄺𝄻𝄼𝄽𝄾𝄿𝅀𝅁𝅂𝅃𝅄𝅅𝅆𝅇𝅈𝅉𝅊𝅋𝅌𝅍𝅎𝅏𝅐𝅑𝅒𝅓𝅔𝅕𝅖𝅗𝅗𝅥𝅘𝅘𝅥𝅘𝅥𝅮𝅘𝅥𝅯𝅘𝅥𝅰𝅘𝅥𝅱𝅘𝅧𝅨𝅩𝅥𝅲𝅥𝅦𝅙𝅚𝅛𝅜𝅝𝅪𝅫𝅬𝅮𝅯𝅰𝅱𝅲𝅭𝅳𝅴𝅵𝅶𝅷𝅸𝅹𝅺𝅻𝅼𝅽𝅾𝅿𝆀𝆁𝆂𝆃𝆄𝆊𝆋𝆅𝆆𝆇𝆈𝆉𝆌𝆍𝆎𝆏𝆐𝆑𝆒𝆓𝆔𝆕𝆖𝆗𝆘𝆙𝆚𝆛𝆜𝆝𝆞𝆟𝆠𝆡𝆢𝆣𝆤𝆥𝆦𝆧𝆨𝆩𝆪𝆫𝆬𝆭𝆮𝆯𝆰𝆱𝆲𝆳𝆴𝆵𝆶𝆷𝆸𝆹𝆹𝅥𝆹𝅥𝅮𝆹𝅥𝅯𝆺𝆺𝅥𝆺𝅥𝅮𝆺𝅥𝅯𝇁𝇂𝇃𝇄𝇅𝇆𝇇𝇈𝇉𝇊𝇋𝇌𝇍𝇎𝇏𝇐𝇑𝇒𝇓𝇔𝇕𝇖𝇗𝇘𝇙𝇚𝇛𝇜𝇝𝇞𝇟𝇠𝇡𝇢𝇣𝇤𝇥𝇦𝇧𝇨"
),
SymbolNode(catCircledKanjis, symbols: "㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊚㊛㊜㊝㊞㊟㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉🈚︎🈯︎"),
SymbolNode(
catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾"
CandidateNode(name: catCircledKanjis, symbols: "㊊㊋㊌㊍㊎㊏㊐㊑㊒㊓㊔㊕㊖㊗︎㊘㊙︎㊚㊛㊜㊝㊞㊟㊠㊡㊢㊣㊤㊥㊦㊧㊨㊩㊪㊫㊬㊭㊮㊯㊰㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉🈚︎🈯︎"),
CandidateNode(
name: catCircledKataKana, symbols: "㋐㋑㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜㋝㋞㋟㋠㋡㋢㋣㋤㋥㋦㋧㋨㋩㋪㋫㋬㋭㋮㋯㋰㋱㋲㋳㋴㋵㋶㋷㋸㋹㋺㋻㋼㋽㋾"
),
SymbolNode(catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
SymbolNode(catSingleTableLines, symbols: "─│┌┐└┕┘├┤┬┴┼═╞╡╪╭╮╯╰▕"),
SymbolNode(catDoubleTableLines, symbols: "═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬"),
SymbolNode(catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
SymbolNode(catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\ˍ━┃▕〓╳∏ˆ⌒┄┅┆┇┈┉┊┋"),
SymbolNode(
catKana,
CandidateNode(name: catBracketKanjis, symbols: "㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀㉁㉂㉃"),
CandidateNode(name: catSingleTableLines, symbols: "─│┌┐└┕┘├┤┬┴┼═╞╡╪╭╮╯╰▕"),
CandidateNode(name: catDoubleTableLines, symbols: "═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬"),
CandidateNode(name: catFillingBlocks, symbols: "_ˍ▁▂▃▄▅▆▇█▏▎▍▌▋▊▉◢◣◥◤"),
CandidateNode(name: catLineSegments, symbols: "﹣﹦≡|∣∥–︱—︳╴¯ ̄﹉﹊﹍﹎﹋﹌﹏︴∕﹨╱╲/\ˍ━┃▕〓╳∏ˆ⌒┄┅┆┇┈┉┊┋"),
CandidateNode(
name: catKana,
symbols:
"ぁあぃいぅうゔぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばひびふぶへべほぼまみむめもゃやゅゆょよらら゚りり゚るる゚れれ゚ろろ゚ゎわわ゙ゐゐ゙ゑゑ゙をを゙んゕゖゝゞゟ〻゠ァアィイゥウヴェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨララ゚リリ゚ルル゚レレ゚ロロ゚ヮワヷヰヸヱヹヲヺンヵヶ・ーヽヾヿ々ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン ゙ ゚〲〱〳〴〵"
),
SymbolNode(
catCombinations,
CandidateNode(
name: catCombinations,
symbols:
"㍘㍙㍚㍛㍜㍝㍞㍟㍠㍡㍢㍣㍤㍥㍦㍧㍨㍩㍪㍫㍬㍭㍮㍯㍰㏠㏡㏢㏣㏤㏥㏦㏧㏨㏩㏪㏫㏬㏭㏮㏯㏰㏱㏲㏳㏴㏵㏶㏷㏸㏹㏺㏻㏼㏽㏾㍱㍲㍳㍴㍵㍶㍷㍸㍹㍺㎀㎁㎂㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏋㏌㏍㏎㏏㏐㏑㏒㏓㏔㏕㏖㏗㏘㏙㏚㏛㏜㏝㏞㏟㏿㋿㍼㍽㍾㍻㍿㌀㌁㌂㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌㌍㌎㌏㌐㌑㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜㌝㌞㌟㌠㌡㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿㍀㍁㍂㍃㍄㍅㍆㍇㍈㍉㍊㍋㍌㍍㍎㍏㍐㍑㍒㍓㍔㍕㍖㍗"
),
SymbolNode(
catPhonabets, symbols: "ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩㄪㄫㄬㄭㄮㄯㆵㆠㆡㆢㆣㆤㆥㆦㆧㆨㆩㆪㆫㆬㆭㆮㆯㆰㆱㆲㆳㆴㆶㆷㆸㆹㆺㆻㆼㆽㆾㆿ˙ˊˇˋ˪˫"
CandidateNode(
name: catPhonabets, symbols: "ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩㄪㄫㄬㄭㄮㄯㆵㆠㆡㆢㆣㆤㆥㆦㆧㆨㆩㆪㆫㆬㆭㆮㆯㆰㆱㆲㆳㆴㆶㆷㆸㆹㆺㆻㆼㆽㆾㆿ˙ˊˇˋ˪˫"
),
SymbolNode(
catCircledASCII,
CandidateNode(
name: catCircledASCII,
symbols:
"①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓂ︎ⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿︎🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉"
),
SymbolNode(
catBracketedASCII, symbols: "⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵🄐🄑🄒🄓🄔🄕🄖🄗🄘🄙🄚🄛🄜🄝🄞🄟🄠🄡🄢🄣🄤🄥🄦🄧🄨🄩"
CandidateNode(
name: catBracketedASCII, symbols: "⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵🄐🄑🄒🄓🄔🄕🄖🄗🄘🄙🄚🄛🄜🄝🄞🄟🄠🄡🄢🄣🄤🄥🄦🄧🄨🄩"
),
SymbolNode(
catThai,
CandidateNode(
name: catThai,
symbols: [
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
@ -171,8 +162,8 @@ public class SymbolNode {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
]
),
SymbolNode(
catYi,
CandidateNode(
name: catYi,
symbols:
"ꀀꀁꀂꀃꀄꀅꀆꀇꀈꀉꀊꀋꀌꀍꀎꀏꀐꀑꀒꀓꀔꀕꀖꀗꀘꀙꀚꀛꀜꀝꀞꀟꀠꀡꀢꀣꀤꀥꀦꀧꀨꀩꀪꀫꀬꀭꀮꀯꀰꀱꀲꀳꀴꀵꀶꀷꀸꀹꀺꀻꀼꀽꀾꀿꁀꁁꁂꁃꁄꁅꁆꁇꁈꁉꁊꁋꁌꁍꁎꁏꁐꁑꁒꁓꁔꁕꁖꁗꁘꁙꁚꁛꁜꁝꁞꁟꁠꁡꁢꁣꁤꁥꁦꁧꁨꁩꁪꁫꁬꁭꁮꁯꁰꁱꁲꁳꁴꁵꁶꁷꁸꁹꁺꁻꁼꁽꁾꁿꂀꂁꂂꂃꂄꂅꂆꂇꂈꂉꂊꂋꂌꂍꂎꂏꂐꂑꂒꂓꂔꂕꂖꂗꂘꂙꂚꂛꂜꂝꂞꂟꂠꂡꂢꂣꂤꂥꂦꂧꂨꂩꂪꂫꂬꂭꂮꂯꂰꂱꂲꂳꂴꂵꂶꂷꂸꂹꂺꂻꂼꂽꂾꂿꃀꃁꃂꃃꃄꃅꃆꃇꃈꃉꃊꃋꃌꃍꃎꃏꃐꃑꃒꃓꃔꃕꃖꃗꃘꃙꃚꃛꃜꃝꃞꃟꃠꃡꃢꃣꃤꃥꃦꃧꃨꃩꃪꃫꃬꃭꃮꃯꃰꃱꃲꃳꃴꃵꃶꃷꃸꃹꃺꃻꃼꃽꃾꃿꄀꄁꄂꄃꄄꄅꄆꄇꄈꄉꄊꄋꄌꄍꄎꄏꄐꄑꄒꄓꄔꄕꄖꄗꄘꄙꄚꄛꄜꄝꄞꄟꄠꄡꄢꄣꄤꄥꄦꄧꄨꄩꄪꄫꄬꄭꄮꄯꄰꄱꄲꄳꄴꄵꄶꄷꄸꄹꄺꄻꄼꄽꄾꄿꅀꅁꅂꅃꅄꅅꅆꅇꅈꅉꅊꅋꅌꅍꅎꅏꅐꅑꅒꅓꅔꅕꅖꅗꅘꅙꅚꅛꅜꅝꅞꅟꅠꅡꅢꅣꅤꅥꅦꅧꅨꅩꅪꅫꅬꅭꅮꅯꅰꅱꅲꅳꅴꅵꅶꅷꅸꅹꅺꅻꅼꅽꅾꅿꆀꆁꆂꆃꆄꆅꆆꆇꆈꆉꆊꆋꆌꆍꆎꆏꆐꆑꆒꆓꆔꆕꆖꆗꆘꆙꆚꆛꆜꆝꆞꆟꆠꆡꆢꆣꆤꆥꆦꆧꆨꆩꆪꆫꆬꆭꆮꆯꆰꆱꆲꆳꆴꆵꆶꆷꆸꆹꆺꆻꆼꆽꆾꆿꇀꇁꇂꇃꇄꇅꇆꇇꇈꇉꇊꇋꇌꇍꇎꇏꇐꇑꇒꇓꇔꇕꇖꇗꇘꇙꇚꇛꇜꇝꇞꇟꇠꇡꇢꇣꇤꇥꇦꇧꇨꇩꇪꇫꇬꇭꇮꇯꇰꇱꇲꇳꇴꇵꇶꇷꇸꇹꇺꇻꇼꇽꇾꇿꈀꈁꈂꈃꈄꈅꈆꈇꈈꈉꈊꈋꈌꈍꈎꈏꈐꈑꈒꈓꈔꈕꈖꈗꈘꈙꈚꈛꈜꈝꈞꈟꈠꈡꈢꈣꈤꈥꈦꈧꈨꈩꈪꈫꈬꈭꈮꈯꈰꈱꈲꈳꈴꈵꈶꈷꈸꈹꈺꈻꈼꈽꈾꈿꉀꉁꉂꉃꉄꉅꉆꉇꉈꉉꉊꉋꉌꉍꉎꉏꉐꉑꉒꉓꉔꉕꉖꉗꉘꉙꉚꉛꉜꉝꉞꉟꉠꉡꉢꉣꉤꉥꉦꉧꉨꉩꉪꉫꉬꉭꉮꉯꉰꉱꉲꉳꉴꉵꉶꉷꉸꉹꉺꉻꉼꉽꉾꉿꊀꊁꊂꊃꊄꊅꊆꊇꊈꊉꊊꊋꊌꊍꊎꊏꊐꊑꊒꊓꊔꊕꊖꊗꊘꊙꊚꊛꊜꊝꊞꊟꊠꊡꊢꊣꊤꊥꊦꊧꊨꊩꊪꊫꊬꊭꊮꊯꊰꊱꊲꊳꊴꊵꊶꊷꊸꊹꊺꊻꊼꊽꊾꊿꋀꋁꋂꋃꋄꋅꋆꋇꋈꋉꋊꋋꋌꋍꋎꋏꋐꋑꋒꋓꋔꋕꋖꋗꋘꋙꋚꋛꋜꋝꋞꋟꋠꋡꋢꋣꋤꋥꋦꋧꋨꋩꋪꋫꋬꋭꋮꋯꋰꋱꋲꋳꋴꋵꋶꋷꋸꋹꋺꋻꋼꋽꋾꋿꌀꌁꌂꌃꌄꌅꌆꌇꌈꌉꌊꌋꌌꌍꌎꌏꌐꌑꌒꌓꌔꌕꌖꌗꌘꌙꌚꌛꌜꌝꌞꌟꌠꌡꌢꌣꌤꌥꌦꌧꌨꌩꌪꌫꌬꌭꌮꌯꌰꌱꌲꌳꌴꌵꌶꌷꌸꌹꌺꌻꌼꌽꌾꌿꍀꍁꍂꍃꍄꍅꍆꍇꍈꍉꍊꍋꍌꍍꍎꍏꍐꍑꍒꍓꍔꍕꍖꍗꍘꍙꍚꍛꍜꍝꍞꍟꍠꍡꍢꍣꍤꍥꍦꍧꍨꍩꍪꍫꍬꍭꍮꍯꍰꍱꍲꍳꍴꍵꍶꍷꍸꍹꍺꍻꍼꍽꍾꍿꎀꎁꎂꎃꎄꎅꎆꎇꎈꎉꎊꎋꎌꎍꎎꎏꎐꎑꎒꎓꎔꎕꎖꎗꎘꎙꎚꎛꎜꎝꎞꎟꎠꎡꎢꎣꎤꎥꎦꎧꎨꎩꎪꎫꎬꎭꎮꎯꎰꎱꎲꎳꎴꎵꎶꎷꎸꎹꎺꎻꎼꎽꎾꎿꏀꏁꏂꏃꏄꏅꏆꏇꏈꏉꏊꏋꏌꏍꏎꏏꏐꏑꏒꏓꏔꏕꏖꏗꏘꏙꏚꏛꏜꏝꏞꏟꏠꏡꏢꏣꏤꏥꏦꏧꏨꏩꏪꏫꏬꏭꏮꏯꏰꏱꏲꏳꏴꏵꏶꏷꏸꏹꏺꏻꏼꏽꏾꏿꐀꐁꐂꐃꐄꐅꐆꐇꐈꐉꐊꐋꐌꐍꐎꐏꐐꐑꐒꐓꐔꐕꐖꐗꐘꐙꐚꐛꐜꐝꐞꐟꐠꐡꐢꐣꐤꐥꐦꐧꐨꐩꐪꐫꐬꐭꐮꐯꐰꐱꐲꐳꐴꐵꐶꐷꐸꐹꐺꐻꐼꐽꐾꐿꑀꑁꑂꑃꑄꑅꑆꑇꑈꑉꑊꑋꑌꑍꑎꑏꑐꑑꑒꑓꑔꑕꑖꑗꑘꑙꑚꑛꑜꑝꑞꑟꑠꑡꑢꑣꑤꑥꑦꑧꑨꑩꑪꑫꑬꑭꑮꑯꑰꑱꑲꑳꑴꑵꑶꑷꑸꑹꑺꑻꑼꑽꑾꑿꒀꒁꒂꒃꒄꒅꒆꒇꒈꒉꒊꒋꒌ꒐꒑꒒꒓꒔꒕꒖꒗꒘꒙꒚꒛꒜꒝꒞꒟꒠꒡꒢꒣꒤꒥꒦꒧꒨꒩꒪꒫꒬꒭꒮꒯꒰꒱꒲꒳꒴꒵꒶꒷꒸꒹꒺꒻꒼꒽꒾꒿꓀꓁꓂꓃꓄꓅꓆"
),

View File

@ -7,8 +7,10 @@
// requirements defined in MIT License.
import Foundation
import LineReader
import Shared
extension vChewing {
extension vChewingLM {
public enum LMConsolidator {
public static let kPragmaHeader = "# 𝙵𝙾𝚁𝙼𝙰𝚃 𝚘𝚛𝚐.𝚊𝚝𝚎𝚕𝚒𝚎𝚛𝙸𝚗𝚖𝚞.𝚟𝚌𝚑𝚎𝚠𝚒𝚗𝚐.𝚞𝚜𝚎𝚛𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝙼𝚘𝚍𝚎𝚕𝙳𝚊𝚝𝚊.𝚏𝚘𝚛𝚖𝚊𝚝𝚝𝚎𝚍"
@ -22,19 +24,19 @@ extension vChewing {
let lineReader = try LineReader(file: fileHandle)
for strLine in lineReader { // i=0
if strLine != kPragmaHeader {
IME.prtDebugIntel("Header Mismatch, Starting In-Place Consolidation.")
vCLog("Header Mismatch, Starting In-Place Consolidation.")
return false
} else {
IME.prtDebugIntel("Header Verification Succeeded: \(strLine).")
vCLog("Header Verification Succeeded: \(strLine).")
return true
}
}
} catch {
IME.prtDebugIntel("Header Verification Failed: File Access Error.")
vCLog("Header Verification Failed: File Access Error.")
return false
}
}
IME.prtDebugIntel("Header Verification Failed: File Missing.")
vCLog("Header Verification Failed: File Missing.")
return false
}
@ -50,7 +52,7 @@ extension vChewing {
/// Swift LMConsolidator EOF
/// consolidate()
if !strIncoming.hasSuffix("\n") {
IME.prtDebugIntel("EOF Fix Necessity Confirmed, Start Fixing.")
vCLog("EOF Fix Necessity Confirmed, Start Fixing.")
if let writeFile = FileHandle(forUpdatingAtPath: path),
let endl = "\n".data(using: .utf8)
{
@ -62,14 +64,14 @@ extension vChewing {
}
}
} catch {
IME.prtDebugIntel("EOF Fix Failed w/ File: \(path)")
IME.prtDebugIntel("EOF Fix Failed w/ Error: \(error).")
vCLog("EOF Fix Failed w/ File: \(path)")
vCLog("EOF Fix Failed w/ Error: \(error).")
return false
}
IME.prtDebugIntel("EOF Successfully Ensured (with possible autofixes performed).")
vCLog("EOF Successfully Ensured (with possible autofixes performed).")
return true
}
IME.prtDebugIntel("EOF Fix Failed: File Missing at \(path).")
vCLog("EOF Fix Failed: File Missing at \(path).")
return false
}
@ -129,14 +131,14 @@ extension vChewing {
try strProcessed.write(to: urlPath, atomically: false, encoding: .utf8)
} catch {
IME.prtDebugIntel("Consolidation Failed w/ File: \(path)")
IME.prtDebugIntel("Consolidation Failed w/ Error: \(error).")
vCLog("Consolidation Failed w/ File: \(path)")
vCLog("Consolidation Failed w/ Error: \(error).")
return false
}
IME.prtDebugIntel("Either Consolidation Successful Or No-Need-To-Consolidate.")
vCLog("Either Consolidation Successful Or No-Need-To-Consolidate.")
return true
}
IME.prtDebugIntel("Consolidation Failed: File Missing at \(path).")
vCLog("Consolidation Failed: File Missing at \(path).")
return false
}
}

View File

@ -1,6 +1,4 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// Refactored from the ObjCpp-version of this class by:
// (c) 2011 and onwards The OpenVanilla Project (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
@ -9,8 +7,10 @@
// requirements defined in MIT License.
import Foundation
import Megrez
import Shared
extension vChewing {
extension vChewingLM {
/// LMInstantiatorLMI
/// LangModelProtocol 使
///
@ -33,6 +33,14 @@ extension vChewing {
public var isPhraseReplacementEnabled = false
public var isCNSEnabled = false
public var isSymbolEnabled = false
public var isSCPCEnabled = false
public var isCHS = false
public var deltaOfCalendarYears: Int = -2000
// package
public init(isCHS: Bool = false) {
self.isCHS = isCHS
}
///
/// ----------------------
@ -56,10 +64,10 @@ extension vChewing {
//
// 100MB
static var lmCNS = vChewing.LMCoreNS(
static var lmCNS = vChewingLM.LMCoreNS(
reverse: true, consolidate: false, defaultScore: -11.0, forceDefaultScore: false
)
static var lmSymbols = vChewing.LMCoreNS(
static var lmSymbols = vChewingLM.LMCoreNS(
reverse: true, consolidate: false, defaultScore: -13.0, forceDefaultScore: false
)
@ -74,7 +82,7 @@ extension vChewing {
var lmUserSymbols = LMCoreEX(
reverse: true, consolidate: true, defaultScore: -12.0, forceDefaultScore: true
)
var lmReplacements = LMReplacments()
var lmReplacements = LMReplacements()
var lmAssociates = LMAssociates()
var lmPlainBopomofo = LMPlainBopomofo()
@ -84,19 +92,19 @@ extension vChewing {
public func loadLanguageModel(path: String) {
if FileManager.default.isReadableFile(atPath: path) {
lmCore.open(path)
IME.prtDebugIntel("lmCore: \(lmCore.count) entries of data loaded from: \(path)")
vCLog("lmCore: \(lmCore.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmCore: File access failure: \(path)")
vCLog("lmCore: File access failure: \(path)")
}
}
public var isCNSDataLoaded: Bool { vChewing.LMInstantiator.lmCNS.isLoaded() }
public var isCNSDataLoaded: Bool { vChewingLM.LMInstantiator.lmCNS.isLoaded() }
public func loadCNSData(path: String) {
if FileManager.default.isReadableFile(atPath: path) {
vChewing.LMInstantiator.lmCNS.open(path)
IME.prtDebugIntel("lmCNS: \(vChewing.LMInstantiator.lmCNS.count) entries of data loaded from: \(path)")
vChewingLM.LMInstantiator.lmCNS.open(path)
vCLog("lmCNS: \(vChewingLM.LMInstantiator.lmCNS.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmCNS: File access failure: \(path)")
vCLog("lmCNS: File access failure: \(path)")
}
}
@ -104,19 +112,20 @@ extension vChewing {
public func loadMiscData(path: String) {
if FileManager.default.isReadableFile(atPath: path) {
lmMisc.open(path)
IME.prtDebugIntel("lmMisc: \(lmMisc.count) entries of data loaded from: \(path)")
vCLog("lmMisc: \(lmMisc.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmMisc: File access failure: \(path)")
vCLog("lmMisc: File access failure: \(path)")
}
}
public var isSymbolDataLoaded: Bool { vChewing.LMInstantiator.lmSymbols.isLoaded() }
public var isSymbolDataLoaded: Bool { vChewingLM.LMInstantiator.lmSymbols.isLoaded() }
public func loadSymbolData(path: String) {
if FileManager.default.isReadableFile(atPath: path) {
vChewing.LMInstantiator.lmSymbols.open(path)
IME.prtDebugIntel("lmSymbol: \(vChewing.LMInstantiator.lmSymbols.count) entries of data loaded from: \(path)")
vChewingLM.LMInstantiator.lmSymbols.open(path)
vCLog(
"lmSymbol: \(vChewingLM.LMInstantiator.lmSymbols.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmSymbols: File access failure: \(path)")
vCLog("lmSymbols: File access failure: \(path)")
}
}
@ -124,16 +133,16 @@ extension vChewing {
if FileManager.default.isReadableFile(atPath: path) {
lmUserPhrases.close()
lmUserPhrases.open(path)
IME.prtDebugIntel("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)")
vCLog("lmUserPhrases: \(lmUserPhrases.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmUserPhrases: File access failure: \(path)")
vCLog("lmUserPhrases: File access failure: \(path)")
}
if FileManager.default.isReadableFile(atPath: filterPath) {
lmFiltered.close()
lmFiltered.open(filterPath)
IME.prtDebugIntel("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
vCLog("lmFiltered: \(lmFiltered.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmFiltered: File access failure: \(path)")
vCLog("lmFiltered: File access failure: \(path)")
}
}
@ -141,9 +150,9 @@ extension vChewing {
if FileManager.default.isReadableFile(atPath: path) {
lmUserSymbols.close()
lmUserSymbols.open(path)
IME.prtDebugIntel("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)")
vCLog("lmUserSymbol: \(lmUserSymbols.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmUserSymbol: File access failure: \(path)")
vCLog("lmUserSymbol: File access failure: \(path)")
}
}
@ -151,9 +160,9 @@ extension vChewing {
if FileManager.default.isReadableFile(atPath: path) {
lmAssociates.close()
lmAssociates.open(path)
IME.prtDebugIntel("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)")
vCLog("lmAssociates: \(lmAssociates.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmAssociates: File access failure: \(path)")
vCLog("lmAssociates: File access failure: \(path)")
}
}
@ -161,9 +170,9 @@ extension vChewing {
if FileManager.default.isReadableFile(atPath: path) {
lmReplacements.close()
lmReplacements.open(path)
IME.prtDebugIntel("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)")
vCLog("lmReplacements: \(lmReplacements.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmReplacements: File access failure: \(path)")
vCLog("lmReplacements: File access failure: \(path)")
}
}
@ -171,16 +180,32 @@ extension vChewing {
if FileManager.default.isReadableFile(atPath: path) {
lmPlainBopomofo.close()
lmPlainBopomofo.open(path)
IME.prtDebugIntel("lmPlainBopomofo: \(lmPlainBopomofo.count) entries of data loaded from: \(path)")
vCLog("lmPlainBopomofo: \(lmPlainBopomofo.count) entries of data loaded from: \(path)")
} else {
IME.prtDebugIntel("lmPlainBopomofo: File access failure: \(path)")
vCLog("lmPlainBopomofo: File access failure: \(path)")
}
}
// MARK: -
///
// public func bigramsFor(preceedingKey: String, key: String) -> [Megrez.Bigram] { }
public func hasAssociatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> Bool {
lmAssociates.hasValuesFor(pair: pair)
}
public func associatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] {
lmAssociates.valuesFor(pair: pair)
}
///
/// - Parameter key:
/// - Returns:
public func hasUnigramsFor(key: String) -> Bool {
if key == " " { return true }
if !lmFiltered.hasUnigramsFor(key: key) {
return lmUserPhrases.hasUnigramsFor(key: key) || lmCore.hasUnigramsFor(key: key)
}
return !unigramsFor(key: key).isEmpty
}
/// LMI
/// - Parameter key:
@ -193,7 +218,7 @@ extension vChewing {
var rawAllUnigrams: [Megrez.Unigram] = []
// 使
if mgrPrefs.useSCPCTypingMode {
if isSCPCEnabled {
rawAllUnigrams += lmPlainBopomofo.valuesFor(key: key).map { Megrez.Unigram(value: $0, score: 0) }
}
@ -207,77 +232,38 @@ extension vChewing {
rawAllUnigrams += lmCore.unigramsFor(key: key)
if isCNSEnabled {
rawAllUnigrams += vChewing.LMInstantiator.lmCNS.unigramsFor(key: key)
rawAllUnigrams += vChewingLM.LMInstantiator.lmCNS.unigramsFor(key: key)
}
if isSymbolEnabled {
rawAllUnigrams += lmUserSymbols.unigramsFor(key: key)
rawAllUnigrams += vChewing.LMInstantiator.lmSymbols.unigramsFor(key: key)
rawAllUnigrams += vChewingLM.LMInstantiator.lmSymbols.unigramsFor(key: key)
}
//
rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: key))
// Swift 使 NSOrderedSet
var filteredPairs: Set<String> = []
var filteredList: Set<String> = []
// KeyValuePair
for unigram in lmFiltered.unigramsFor(key: key) {
filteredPairs.insert(unigram.value)
filteredList.insert(unigram.value)
}
return filterAndTransform(
unigrams: rawAllUnigrams,
filter: filteredPairs
)
}
///
/// - Parameter key:
/// - Returns:
public func hasUnigramsFor(key: String) -> Bool {
if key == " " { return true }
if !lmFiltered.hasUnigramsFor(key: key) {
return lmUserPhrases.hasUnigramsFor(key: key) || lmCore.hasUnigramsFor(key: key)
}
return !unigramsFor(key: key).isEmpty
}
public func associatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] {
lmAssociates.valuesFor(pair: pair)
}
public func hasAssociatedPhrasesFor(pair: Megrez.Compositor.KeyValuePaired) -> Bool {
lmAssociates.hasValuesFor(pair: pair)
}
// MARK: -
///
/// - Parameters:
/// - unigrams:
/// - filteredPairs:
/// - Returns:
func filterAndTransform(
unigrams: [Megrez.Unigram],
filter filteredPairs: Set<String>
) -> [Megrez.Unigram] {
var results: [Megrez.Unigram] = []
var insertedPairs: Set<String> = []
for unigram in unigrams {
var theValue: String = unigram.value
if filteredPairs.contains(theValue) { continue }
if isPhraseReplacementEnabled {
let replacement = lmReplacements.valuesFor(key: theValue)
if !replacement.isEmpty { theValue = replacement }
//
if isPhraseReplacementEnabled {
for i in 0..<rawAllUnigrams.count {
let newValue = lmReplacements.valuesFor(key: rawAllUnigrams[i].value)
guard !newValue.isEmpty else { continue }
rawAllUnigrams[i].value = newValue
}
if insertedPairs.contains(theValue) { continue }
results.append(Megrez.Unigram(value: theValue, score: unigram.score))
insertedPairs.insert(theValue)
}
return results
//
//
rawAllUnigrams.consolidate(filter: filteredList)
return rawAllUnigrams
}
}
}

View File

@ -7,10 +7,11 @@
// requirements defined in MIT License.
import Foundation
import Megrez
// MARK: - 便
extension vChewing.LMInstantiator {
extension vChewingLM.LMInstantiator {
func queryDateTimeUnigrams(with key: String = "") -> [Megrez.Unigram] {
if !["ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ", "ㄕˊ-ㄐㄧㄢ", "ㄒㄧㄥ-ㄑㄧ", "ㄒㄧㄥ-ㄑㄧˊ"].contains(key) { return .init() }
var results = [Megrez.Unigram]()
@ -18,7 +19,7 @@ extension vChewing.LMInstantiator {
let currentDate = Date()
var delta = DateComponents()
let thisYear = Calendar.current.dateComponents([.year], from: currentDate).year ?? 2018
delta.year = max(min(mgrPrefs.deltaOfCalendarYears, 0), thisYear * -1)
delta.year = max(min(deltaOfCalendarYears, 0), thisYear * -1)
let currentDateShortened = Calendar.current.date(byAdding: delta, to: currentDate)
switch key {
case "ㄖˋ-ㄑㄧ", "ㄖˋ-ㄑㄧˊ":
@ -28,7 +29,7 @@ extension vChewing.LMInstantiator {
formatterDate2.dateFormat = "yyyy年MM月dd日"
let date1 = formatterDate1.string(from: currentDate)
let date2 = formatterDate2.string(from: currentDate)
var date3 = ChineseConverter.convertArabicNumeralsToChinese(target: date2)
var date3 = date2.convertArabicNumeralsToChinese
date3 = date3.replacingOccurrences(of: "年〇", with: "")
date3 = date3.replacingOccurrences(of: "月〇", with: "")
results.append(.init(value: date1, score: -94))
@ -39,7 +40,7 @@ extension vChewing.LMInstantiator {
dateAlt1.regReplace(pattern: #"^0+"#)
var dateAlt2: String = formatterDate2.string(from: currentDateShortened)
dateAlt2.regReplace(pattern: #"^0+"#)
var dateAlt3 = ChineseConverter.convertArabicNumeralsToChinese(target: dateAlt2)
var dateAlt3 = dateAlt2.convertArabicNumeralsToChinese
dateAlt3 = dateAlt3.replacingOccurrences(of: "年〇", with: "")
dateAlt3 = dateAlt3.replacingOccurrences(of: "月〇", with: "")
results.append(.init(value: dateAlt1, score: -97))
@ -51,8 +52,8 @@ extension vChewing.LMInstantiator {
let formatterTime2 = DateFormatter()
let formatterTime3 = DateFormatter()
formatterTime1.dateFormat = "HH:mm"
formatterTime2.dateFormat = IME.currentInputMode == .imeModeCHS ? "HH点mm分" : "HH點mm分"
formatterTime3.dateFormat = IME.currentInputMode == .imeModeCHS ? "HH时mm分" : "HH時mm分"
formatterTime2.dateFormat = isCHS ? "HH点mm分" : "HH點mm分"
formatterTime3.dateFormat = isCHS ? "HH时mm分" : "HH時mm分"
let time1 = formatterTime1.string(from: currentDate)
let time2 = formatterTime2.string(from: currentDate)
let time3 = formatterTime3.string(from: currentDate)
@ -92,3 +93,22 @@ extension String {
} catch { return }
}
}
// MARK: - Date Time Language Conversion Extension
private let tableMappingArabicNumeralsToChinese: [String: String] = [
"0": "", "1": "", "2": "", "3": "", "4": "", "5": "", "6": "", "7": "", "8": "", "9": "",
]
extension String {
///
/// - Parameter target:
fileprivate var convertArabicNumeralsToChinese: String {
var target = self
for key in tableMappingArabicNumeralsToChinese.keys {
guard let result = tableMappingArabicNumeralsToChinese[key] else { continue }
target = target.replacingOccurrences(of: key, with: result)
}
return target
}
}

View File

@ -7,9 +7,11 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
import PinyinPhonaConverter
import Shared
extension vChewing {
extension vChewingLM {
@frozen public struct LMAssociates {
var rangeMap: [String: [(Range<String.Index>, Int)]] = [:]
var strData: String = ""
@ -32,7 +34,9 @@ extension vChewing {
}
let arrTarget = target.dropLast().dropFirst().split(separator: ",")
guard arrTarget.count == 2 else { return target }
return "(\(Tekkon.cnvHanyuPinyinToPhona(target: String(arrTarget[0]).lowercased())),\(arrTarget[1]))"
var arrTarget0 = String(arrTarget[0]).lowercased()
arrTarget0.converToPhonabets()
return "(\(arrTarget0),\(arrTarget[1]))"
}
@discardableResult public mutating func open(_ path: String) -> Bool {
@ -59,8 +63,8 @@ extension vChewing {
}
}
} catch {
IME.prtDebugIntel("\(error)")
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -73,11 +77,6 @@ extension vChewing {
}
}
public func dump() {
// We remove this function in order to reduce out maintenance workload.
// This function will be implemented only if further hard-necessity comes.
}
public func valuesFor(pair: Megrez.Compositor.KeyValuePaired) -> [String] {
var pairs: [String] = []
if let arrRangeRecords: [(Range<String.Index>, Int)] = rangeMap[pair.toNGramKey] {

View File

@ -7,9 +7,11 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Megrez
import PinyinPhonaConverter
import Shared
extension vChewing {
extension vChewingLM {
/// LMCore LMCoreEX range
/// range strData
/// C++ ParselessLM Swift
@ -20,12 +22,12 @@ extension vChewing {
///
var strData: String = ""
///
var shouldReverse: Bool = false
var allowConsolidation: Bool = false
var shouldReverse = false
var allowConsolidation = false
///
var defaultScore: Double = 0
///
var shouldForceDefaultScore: Bool = false
var shouldForceDefaultScore = false
///
public var count: Int {
@ -75,15 +77,16 @@ extension vChewing {
let neta = strData[$0].split(separator: " ")
if neta.count >= 2, String(neta[0]).first != "#" {
if !neta[0].isEmpty, !neta[1].isEmpty {
let theKey = shouldReverse ? String(neta[1]) : String(neta[0])
var theKey = shouldReverse ? String(neta[1]) : String(neta[0])
let theValue = $0
rangeMap[Tekkon.cnvHanyuPinyinToPhona(target: theKey.lowercased()), default: []].append(theValue)
theKey.converToPhonabets()
rangeMap[theKey, default: []].append(theValue)
}
}
}
} catch {
IME.prtDebugIntel("\(error)")
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -112,7 +115,7 @@ extension vChewing {
strDump += addline
}
}
IME.prtDebugIntel(strDump)
vCLog(strDump)
}
/// strData

View File

@ -7,8 +7,10 @@
// requirements defined in MIT License.
import Foundation
import Megrez
import Shared
extension vChewing {
extension vChewingLM {
/// LMCore LMCoreNS plist
/// mac
/// 使 plist
@ -18,13 +20,13 @@ extension vChewing {
/// LMCoreNS
var strData: String = ""
///
var shouldReverse: Bool = false
var shouldReverse = false
/// 使
var allowConsolidation: Bool = false
var allowConsolidation = false
///
var defaultScore: Double = 0
///
var shouldForceDefaultScore: Bool = false
var shouldForceDefaultScore = false
///
public var count: Int {
@ -70,7 +72,7 @@ extension vChewing {
try PropertyListSerialization.propertyList(from: rawData, format: nil) as? [String: [Data]] ?? .init()
rangeMap = rawPlist
} catch {
IME.prtDebugIntel("↑ Exception happened when reading plist file at: \(path).")
vCLog("↑ Exception happened when reading plist file at: \(path).")
return false
}
@ -105,7 +107,7 @@ extension vChewing {
strDump += "\(cnvPhonabetToASCII(theKey)) \(theValue) \(theScore)\n"
}
}
IME.prtDebugIntel(strDump)
vCLog(strDump)
}
/// UTF8

View File

@ -8,8 +8,9 @@
// requirements defined in MIT License.
import Foundation
import Shared
extension vChewing {
extension vChewingLM {
@frozen public struct LMPlainBopomofo {
var rangeMap: [String: String] = [:]
@ -36,8 +37,8 @@ extension vChewing {
try PropertyListSerialization.propertyList(from: rawData, format: nil) as? [String: String] ?? .init()
rangeMap = rawPlist
} catch {
IME.prtDebugIntel("\(error)")
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -50,15 +51,10 @@ extension vChewing {
}
}
public func dump() {
// We remove this function in order to reduce out maintenance workload.
// This function will be implemented only if further hard-necessity comes.
}
public func valuesFor(key: String) -> [String] {
var pairs: [String] = []
if let arrRangeRecords: String = rangeMap[key] {
pairs.append(contentsOf: arrRangeRecords.charComponents)
pairs.append(contentsOf: arrRangeRecords.map { String($0) })
}
var set = Set<String>()
return pairs.filter { set.insert($0).inserted }

View File

@ -7,10 +7,10 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
import Shared
extension vChewing {
@frozen public struct LMReplacments {
extension vChewingLM {
@frozen public struct LMReplacements {
var rangeMap: [String: Range<String.Index>] = [:]
var strData: String = ""
@ -48,8 +48,8 @@ extension vChewing {
}
}
} catch {
IME.prtDebugIntel("\(error)")
IME.prtDebugIntel("↑ Exception happened when reading data at: \(path).")
vCLog("\(error)")
vCLog("↑ Exception happened when reading data at: \(path).")
return false
}
@ -67,7 +67,7 @@ extension vChewing {
for entry in rangeMap {
strDump += strData[entry.value] + "\n"
}
IME.prtDebugIntel(strDump)
vCLog(strDump)
}
public func valuesFor(key: String) -> String {

View File

@ -1,5 +1,5 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// Refactored from the Cpp version of this class by Mengjuei Hsieh (MIT License).
// Refactored from the Cpp version of this class by Lukhnos Liu (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
@ -8,8 +8,10 @@
// requirements defined in MIT License.
import Foundation
import Megrez
import Shared
extension vChewing {
extension vChewingLM {
public class LMUserOverride {
// MARK: - Main
@ -18,12 +20,14 @@ extension vChewing {
var mutLRUList: [KeyObservationPair] = []
var mutLRUMap: [String: KeyObservationPair] = [:]
let kDecayThreshold: Double = 1.0 / 1_048_576.0 //
var fileSaveLocationURL: URL
public static let kObservedOverrideHalfLife: Double = 3600.0 * 6 // 6
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife) {
public init(capacity: Int = 500, decayConstant: Double = LMUserOverride.kObservedOverrideHalfLife, dataURL: URL) {
mutCapacity = max(capacity, 1) // Ensures that this integer value is always > 0.
mutDecayExponent = log(0.5) / decayConstant
fileSaveLocationURL = dataURL
}
public func performObservation(
@ -49,7 +53,7 @@ extension vChewing {
let breakingUp = currentNode.spanLength == 1 && prevNode.spanLength > 1
let targetNodeIndex = breakingUp ? currentNodeIndex : prevNodeIndex
let key: String = vChewing.LMUserOverride.formObservationKey(
let key: String = vChewingLM.LMUserOverride.formObservationKey(
walkedNodes: walkedAfter, headIndex: targetNodeIndex
)
guard !key.isEmpty else { return }
@ -64,7 +68,7 @@ extension vChewing {
) -> Suggestion {
var headIndex = 0
guard let nodeIter = currentWalk.findNode(at: cursor, target: &headIndex) else { return .init() }
let key = vChewing.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
let key = vChewingLM.LMUserOverride.formObservationKey(walkedNodes: currentWalk, headIndex: headIndex)
return getSuggestion(key: key, timestamp: timestamp, headReading: nodeIter.key)
}
}
@ -72,7 +76,7 @@ extension vChewing {
// MARK: - Private Structures
extension vChewing.LMUserOverride {
extension vChewingLM.LMUserOverride {
enum OverrideUnit: CodingKey { case count, timestamp }
enum ObservationUnit: CodingKey { case count, overrides }
enum KeyObservationPairUnit: CodingKey { case key, observation }
@ -149,7 +153,7 @@ extension vChewing.LMUserOverride {
// MARK: - Hash and Dehash the entire UOM data
extension vChewing.LMUserOverride {
extension vChewingLM.LMUserOverride {
/// LRU
public func bleachUnigrams(saveCallback: @escaping () -> Void) {
for key in mutLRUMap.keys {
@ -174,18 +178,19 @@ extension vChewing.LMUserOverride {
let nullData = "{}"
try nullData.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
IME.prtDebugIntel("UOM Error: Unable to clear data. Details: \(error)")
vCLog("UOM Error: Unable to clear data. Details: \(error)")
return
}
}
public func saveData(toURL fileURL: URL) {
public func saveData(toURL fileURL: URL? = nil) {
let encoder = JSONEncoder()
do {
guard let jsonData = try? encoder.encode(mutLRUMap) else { return }
let fileURL: URL = fileURL ?? fileSaveLocationURL
try jsonData.write(to: fileURL, options: .atomic)
} catch {
IME.prtDebugIntel("UOM Error: Unable to save data, abort saving. Details: \(error)")
vCLog("UOM Error: Unable to save data, abort saving. Details: \(error)")
return
}
}
@ -196,27 +201,27 @@ extension vChewing.LMUserOverride {
let data = try Data(contentsOf: fileURL, options: .mappedIfSafe)
if ["", "{}"].contains(String(data: data, encoding: .utf8)) { return }
guard let jsonResult = try? decoder.decode([String: KeyObservationPair].self, from: data) else {
IME.prtDebugIntel("UOM Error: Read file content type invalid, abort loading.")
vCLog("UOM Error: Read file content type invalid, abort loading.")
return
}
mutLRUMap = jsonResult
resetMRUList()
} catch {
IME.prtDebugIntel("UOM Error: Unable to read file or parse the data, abort loading. Details: \(error)")
vCLog("UOM Error: Unable to read file or parse the data, abort loading. Details: \(error)")
return
}
}
public struct Suggestion {
var candidates = [(String, Megrez.Unigram)]()
var forceHighScoreOverride = false
var isEmpty: Bool { candidates.isEmpty }
public var candidates = [(String, Megrez.Unigram)]()
public var forceHighScoreOverride = false
public var isEmpty: Bool { candidates.isEmpty }
}
}
// MARK: - Private Methods
extension vChewing.LMUserOverride {
extension vChewingLM.LMUserOverride {
private func doObservation(
key: String, candidate: String, timestamp: Double, forceHighScoreOverride: Bool,
saveCallback: @escaping () -> Void
@ -235,7 +240,7 @@ extension vChewing.LMUserOverride {
mutLRUMap.removeValue(forKey: mutLRUList[mutLRUList.endIndex].key)
mutLRUList.removeLast()
}
IME.prtDebugIntel("UOM: Observation finished with new observation: \(key)")
vCLog("UOM: Observation finished with new observation: \(key)")
saveCallback()
return
}
@ -246,7 +251,7 @@ extension vChewing.LMUserOverride {
)
mutLRUList.insert(theNeta, at: 0)
mutLRUMap[key] = theNeta
IME.prtDebugIntel("UOM: Observation finished with existing observation: \(key)")
vCLog("UOM: Observation finished with existing observation: \(key)")
saveCallback()
}
}

View File

@ -0,0 +1,11 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
public enum vChewingLM {}

View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2022 and onwards Lukhnos Liu for upstream contents.
Copyright (c) 2022 and onwards The vChewing Project for Megrez-specific changes.
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.

View File

@ -0,0 +1,26 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Megrez",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "Megrez",
targets: ["Megrez"]
)
],
dependencies: [],
targets: [
.target(
name: "Megrez",
dependencies: []
),
.testTarget(
name: "MegrezTests",
dependencies: ["Megrez"]
),
]
)

View File

@ -0,0 +1,123 @@
# Megrez Engine 天權星引擎
- Gitee: [Swift](https://gitee.com/vChewing/Megrez) | [C#](https://gitee.com/vChewing/MegrezNT)
- GitHub: [Swift](https://github.com/vChewing/Megrez) | [C#](https://github.com/vChewing/MegrezNT)
> 該引擎已經實裝於基於純 Swift 語言完成的 **威注音輸入法** 內,歡迎好奇者嘗試:[GitHub](https://github.com/vChewing/vChewing-macOS ) | [Gitee](https://gitee.com/vchewing/vChewing-macOS ) 。
天權星引擎是用來處理輸入法語彙庫的一個模組。該倉庫乃威注音專案的弒神行動Operation Longinus的一部分。
Megrez Engine is a module made for processing lingual data of an input method. This repository is part of Operation Longinus of The vChewing Project.
## 使用說明
### §1. 初期化
在你的 ctlInputMethod (InputMethodController) 或者 KeyHandler 內初期化一份 Megrez.Compositor 組字器副本(這裡將該副本命名為「`compositor`」)。由於 Megrez.Compositor 的型別是 Struct 型別(為了讓 Compositor 可以 deep copy所以其副本可以用 var 來宣告。
以 KeyHandler 為例:
```swift
class KeyHandler {
// 先設定好變數
var compositor: Megrez.Compositor = .init()
...
}
```
以 ctlInputMethod 為例:
```swift
@objc(ctlInputMethod) // 根據 info.plist 內的情況來確定型別的命名
class ctlInputMethod: IMKInputController {
// 先設定好變數
var compositor: Megrez.Compositor = .init()
...
}
```
由於 Swift 會在某個大副本KeyHandler 或者 ctlInputMethod 副本)被銷毀的時候自動銷毀其中的全部副本,所以 Megrez.Compositor 的副本初期化沒必要寫在 init() 當中。但你很可能會想在 init() 時指定 Tekkon.Composer 所對接的語言模組型別、以及其可以允許的最大詞長。
這裡就需要在 init() 時使用參數:
```swift
/// 組字器。
/// - Parameters:
/// - lm: 語言模型。可以是任何基於 Megrez.LangModel 的衍生型別。
/// - length: 指定該組字器內可以允許的最大詞長,預設為 10 字。
/// - separator: 多字讀音鍵當中用以分割漢字讀音的記號,預設為空。
var compositor: Megrez.Compositor = .init(lm: lmTest, length: 13, separator: "-")
```
### §2. 使用範例
請結合 MegrezTests.swift 檔案來學習。這裡只是給個概述。
#### // 1. 準備用作語言模型的專用型別
首先Megrez 內建的 LangModel 型別是遠遠不夠用的,只能說是個類似於 protocol 一樣的存在。你需要自己單獨寫一個新的衍生型別:
```swift
class ExampleLM: Megrez.LangModel {
...
override func unigramsFor(key: String) -> [Megrez.Unigram] {
...
}
...
}
```
這個型別需要下述兩個函式能夠針對給定的鍵回饋對應的資料值、或其存無狀態:
- unigramsFor(key: String) -> [Megrez.Unigram]
- hasUnigramsFor(key: String) -> Bool
MegrezTests.swift 檔案內的 SimpleLM 可以作為範例。
至於該檔案內的 FiniteStateMachine 則是一個「一次性讀取檔案/大字串且分析資料」的範例。
如果需要更實戰的範例的話,可以洽威注音專案的倉庫內的 LMInstantiator.swift 及其關聯檔案。
#### // 2. 怎樣與 compositor 互動:
這裡只講幾個常用函式:
- 游標位置 `compositor.cursorIndex` 是可以賦值與取值的動態變數,且會在賦值內容為超出位置範圍的數值時自動修正。初期值為 0。
- `compositor.insertKey("gao1")` 可以在當前的游標位置插入讀音「gao1」。
- `compositor.dropKey(direction: .front)` 的作用是:朝著往文字輸入方向、砍掉一個與游標相鄰的讀音。反之,`dropKey(direction: .rear)` 則朝著與文字輸入方向相反的方向、砍掉一個與游標相鄰的讀音。
- 在威注音的術語體系當中「文字輸入方向」為向前Front、與此相反的方向為向後Rear
- `compositor.overrideCandidate(.init(key: "讀音", value: "候選字"), at: 游標位置, overrideType: 覆寫模式)` 用來根據輸入法選中的候選字詞、據此更新當前游標位置選中的候選字詞節點當中的候選字詞。
輸入完內容之後,可以聲明一個用來接收結果的變數:
```swift
/// 對已給定的軌格按照給定的位置與條件進行正向爬軌。
var walked = compositor.walk()
```
MegrezTests.swift 是輸入了很多內容之後再 walk 的。實際上一款輸入法會在你每次插入讀音或刪除讀音的時候都重新 walk。那些處於候選字詞鎖定狀態的節點不會再受到之後的 walk 的行為的影響,但除此之外的節點會因為每次 walk 而可能各自的候選字詞會出現自動變化。如果給了 nodesLimit 一個非零的數值的話,則 walk 的範圍外的節點不會受到影響。
walk 之後的取值的方法及利用方法可以有很多種。這裡有其中的一個:
```swift
var composed: [String] = walked.map(\.value)
print(composed)
```
類似於:
```swift
for phrase in walked {
composed.append(phrase.value)
}
print(composed)
```
上述 print 結果就是 compositor 目前的組句,是這種陣列格式(以吳宗憲的詩句為例):
```swift
["八月", "中秋", "山林", "涼", "風吹", "大地", "草枝", "擺"]
```
自己看 MegrezTests.swift 慢慢研究吧。
## 著作權 (Credits)
- Swiftified and further development by (c) 2022 and onwards The vChewing Project (MIT License).
- Swift programmer: Shiki Suen
- Was initially rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).

View File

@ -44,13 +44,13 @@ extension Megrez {
public var isEmpty: Bool { spans.isEmpty && keys.isEmpty }
///
private(set) var keys = [String]()
public private(set) var keys = [String]()
///
private(set) var spans = [Span]()
public private(set) var spans = [Span]()
/// 使 LangModelRanked
private(set) var langModel: LangModelRanked
public var langModel: LangModelRanked
/// 0
private(set) var cursorRegionMap: [Int: Int] = .init()
public private(set) var cursorRegionMap: [Int: Int] = .init()
///
/// - Parameter langModel:

View File

@ -7,7 +7,7 @@ extension Megrez.Compositor {
///
public class Span {
private var nodes: [Node?] = []
private(set) var maxLength = 0
public private(set) var maxLength = 0
private var maxSpanLength: Int { Megrez.Compositor.maxSpanLength }
public init() {
clear()
@ -57,7 +57,7 @@ extension Megrez.Compositor {
public func nodeOf(length: Int) -> Node? {
guard (1...maxSpanLength).contains(length) else { return nil }
return nodes[length - 1] ?? nil
return nodes[length - 1]
}
}

View File

@ -34,11 +34,11 @@ extension Megrez.Compositor {
/// c
public var overridingScore: Double = 114_514
private(set) var key: String
private(set) var keyArray: [String]
private(set) var spanLength: Int
private(set) var unigrams: [Megrez.Unigram]
private(set) var currentUnigramIndex: Int = 0 {
public private(set) var key: String
public private(set) var keyArray: [String]
public private(set) var spanLength: Int
public private(set) var unigrams: [Megrez.Unigram]
public private(set) var currentUnigramIndex: Int = 0 {
didSet { currentUnigramIndex = min(max(0, currentUnigramIndex), unigrams.count - 1) }
}
@ -53,7 +53,7 @@ extension Megrez.Compositor {
hasher.combine(overrideType)
}
private(set) var overrideType: Node.OverrideType
public private(set) var overrideType: Node.OverrideType
public static func == (lhs: Node, rhs: Node) -> Bool {
lhs.key == rhs.key && lhs.spanLength == rhs.spanLength

View File

@ -38,3 +38,20 @@ extension Megrez {
}
}
}
// MARK: - Array Extensions.
extension Array where Element == Megrez.Unigram {
///
///
public mutating func consolidate(filter theFilter: Set<String> = .init()) {
var inserted: [String: Double] = [:]
var insertedArray: [Megrez.Unigram] = []
for neta in filter({ !theFilter.contains($0.value) }) {
if let existed = inserted[neta.value], existed >= neta.score { continue }
inserted[neta.value] = neta.score
insertedArray.append(neta)
}
self = insertedArray
}
}

View File

@ -0,0 +1,171 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
import Megrez
// MARK: -
class SimpleLM: LangModelProtocol {
var mutDatabase: [String: [Megrez.Unigram]] = [:]
init(input: String, swapKeyValue: Bool = false) {
let sstream = input.components(separatedBy: "\n")
for line in sstream {
if line.isEmpty || line.hasPrefix("#") {
continue
}
let linestream = line.split(separator: " ")
let col0 = String(linestream[0])
let col1 = String(linestream[1])
let col2 = Double(linestream[2]) ?? 0.0
var u = Megrez.Unigram(value: swapKeyValue ? col0 : col1, score: 0)
u.score = col2
mutDatabase[swapKeyValue ? col1 : col0, default: []].append(u)
}
}
func unigramsFor(key: String) -> [Megrez.Unigram] {
if let f = mutDatabase[key] {
return f
} else {
return [Megrez.Unigram]().sorted { $0.score > $1.score }
}
}
func hasUnigramsFor(key: String) -> Bool {
mutDatabase.keys.contains(key)
}
}
class MockLM: LangModelProtocol {
func unigramsFor(key: String) -> [Megrez.Unigram] {
[Megrez.Unigram(value: key, score: -1)]
}
func hasUnigramsFor(key: String) -> Bool {
!key.isEmpty
}
}
// MARK: -
public let strStressData = #"""
yi1 -2.08170692
yi1-yi1 -4.38468400
"""#
public let strEmojiSampleData = #"""
gao1 -2.9396
re4 -3.6024
gao1re4 -6.526
huo3 -3.6966
huo3 🔥 -8
yan4 -5.4466
huo3yan4 -5.6231
huo3yan4 🔥 -8
wei2 -3.9832
xian3 -3.7810
wei2xian3 -4.2623
mi4feng1 -3.6231
mi4 -4.6231
feng1 -4.6231
feng1 🐝 -11
mi4feng1 🐝 -11
"""#
public let strSampleData = #"""
#
# libTaBE (http://sourceforge.net/projects/libtabe/)
# (2002 ). 1999 Pai-Hsiang Hsiao BSD
#
ni3 -6.000000 // Non-LibTaBE
zhe4 -6.000000 // Non-LibTaBE
yang4 -6.000000 // Non-LibTaBE
si1 -9.495858
si1 -9.006414
si1 -99.000000
si1 -8.091803
si1 -99.000000
si1 -13.513987
si1 -12.259095
gao1 -7.171551
ke1 -10.574273
ke1 -11.504072
ke1 -10.450457
ke1 -7.171052
ke1 -99.000000
gao1 -11.928720
gao1 -13.624335
gao1 -12.390804
de5 -3.516024
di2 -3.516024
di4 -3.516024
zhong1 -5.809297
de5 -7.427179
gong1 -8.381971
gong1 -8.501463
ji4 -99.000000
jin1 -8.034095
gong1 -8.858181
ji4 -7.608341
ji4 -99.000000
jin1 -7.290109
ji4 -10.939895
zhong1 -99.000000
ji4 -99.000000
ji4 -99.000000
jin1 -99.000000
ji4 -9.715317
ji4 -7.926683
ji4 -8.373022
zhong1 -9.877580
jin1 -10.711079
gong1 -7.877973
gong1 -7.822167
gong1 -99.000000
gong1 -99.000000
gong1 -99.000000
zhong1 -9.685671
ji4 -10.425662
gong1 -99.000000
gong1 -99.000000
ji4 -8.888722
ji4 -10.204425
jin1 -11.378321
zhong1 -99.000000
ji4 -99.000000
ji4 -8.450826
jin1 -11.074890
gong1 -99.000000
ji4 -12.045357
zhong1 -99.000000
ji4 -99.000000
ji4 -9.517568
ji4 -12.021587
jin1 -99.000000
jin1 -12.784206
nian2 -6.086515
jiang3 -9.164384
jiang3 -8.690941
jiang3 -10.127828
nian2 -11.336864
nian2 -11.285740
jiang3 -12.492933
gong1si1 -6.299461
ke1ji4 -6.736613
ji4gong1 -13.336653
jiang3jin1 -10.344678
nian2zhong1 -11.668947
nian2zhong1 -11.373044
gao1ke1ji4 -9.842421
zhe4yang4 -6.000000 // Non-LibTaBE
ni3zhe4 -9.000000 // Non-LibTaBE
jiao4 -3.676169
jiao4 -3.24869962
jiao4yu4 -3.32220565
yu4 -3.30192952
"""#

View File

@ -0,0 +1,521 @@
// Swiftified by (c) 2022 and onwards The vChewing Project (MIT License).
// Rebranded from (c) Lukhnos Liu's C++ library "Gramambular 2" (MIT License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
import Cocoa
import XCTest
@testable import Megrez
final class MegrezTests: XCTestCase {
func testSpan() throws {
let langModel = SimpleLM(input: strSampleData)
let span = Megrez.Compositor.Span()
let n1 = Megrez.Compositor.Node(keyArray: ["gao1"], spanLength: 1, unigrams: langModel.unigramsFor(key: "gao1"))
let n3 = Megrez.Compositor.Node(
keyArray: ["gao1ke1ji4"], spanLength: 3, unigrams: langModel.unigramsFor(key: "gao1ke1ji4")
)
XCTAssertEqual(span.maxLength, 0)
span.append(node: n1)
XCTAssertEqual(span.maxLength, 1)
span.append(node: n3)
XCTAssertEqual(span.maxLength, 3)
XCTAssertEqual(span.nodeOf(length: 1), n1)
XCTAssertEqual(span.nodeOf(length: 2), nil)
XCTAssertEqual(span.nodeOf(length: 3), n3)
XCTAssertEqual(span.nodeOf(length: Megrez.Compositor.maxSpanLength), nil)
span.clear()
XCTAssertEqual(span.maxLength, 0)
XCTAssertEqual(span.nodeOf(length: 1), nil)
XCTAssertEqual(span.nodeOf(length: 2), nil)
XCTAssertEqual(span.nodeOf(length: 3), nil)
XCTAssertEqual(span.nodeOf(length: Megrez.Compositor.maxSpanLength), nil)
span.append(node: n1)
span.append(node: n3)
span.dropNodesOfOrBeyond(length: 2)
XCTAssertEqual(span.maxLength, 1)
XCTAssertEqual(span.nodeOf(length: 1), n1)
XCTAssertEqual(span.nodeOf(length: 2), nil)
XCTAssertEqual(span.nodeOf(length: 3), nil)
span.dropNodesOfOrBeyond(length: 1)
XCTAssertEqual(span.maxLength, 0)
XCTAssertEqual(span.nodeOf(length: 1), nil)
let n114514 = Megrez.Compositor.Node(spanLength: 114_514)
XCTAssertFalse(span.append(node: n114514))
XCTAssertNil(span.nodeOf(length: 0))
XCTAssertNil(span.nodeOf(length: Megrez.Compositor.maxSpanLength + 1))
}
func testRankedLangModel() throws {
class TestLM: LangModelProtocol {
func hasUnigramsFor(key: String) -> Bool { key == "foo" }
func unigramsFor(key: String) -> [Megrez.Unigram] {
key == "foo"
? [.init(value: "middle", score: -5), .init(value: "highest", score: -2), .init(value: "lowest", score: -10)]
: .init()
}
}
let lmRanked = Megrez.Compositor.LangModelRanked(withLM: TestLM())
XCTAssertTrue(lmRanked.hasUnigramsFor(key: "foo"))
XCTAssertFalse(lmRanked.hasUnigramsFor(key: "bar"))
XCTAssertTrue(lmRanked.unigramsFor(key: "bar").isEmpty)
let unigrams = lmRanked.unigramsFor(key: "foo")
XCTAssertEqual(unigrams.count, 3)
XCTAssertEqual(unigrams[0].value, "highest")
XCTAssertEqual(unigrams[0].score, -2)
XCTAssertEqual(unigrams[1].value, "middle")
XCTAssertEqual(unigrams[1].score, -5)
XCTAssertEqual(unigrams[2].value, "lowest")
XCTAssertEqual(unigrams[2].score, -10)
}
func testCompositor_BasicTests() throws {
var compositor = Megrez.Compositor(with: MockLM())
XCTAssertEqual(compositor.separator, Megrez.Compositor.kDefaultSeparator)
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.length, 0)
compositor.insertKey("a")
XCTAssertEqual(compositor.cursor, 1)
XCTAssertEqual(compositor.length, 1)
XCTAssertEqual(compositor.spans.count, 1)
XCTAssertEqual(compositor.spans[0].maxLength, 1)
guard let zeroNode = compositor.spans[0].nodeOf(length: 1) else {
print("fuckme")
return
}
XCTAssertEqual(zeroNode.key, "a")
compositor.dropKey(direction: .rear)
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.spans.count, 0)
}
func testCompositor_InvalidOperations() throws {
class TestLM: LangModelProtocol {
func hasUnigramsFor(key: String) -> Bool { key == "foo" }
func unigramsFor(key: String) -> [Megrez.Unigram] {
key == "foo" ? [.init(value: "foo", score: -1)] : .init()
}
}
var compositor = Megrez.Compositor(with: TestLM())
compositor.separator = ";"
XCTAssertFalse(compositor.insertKey("bar"))
XCTAssertFalse(compositor.insertKey(""))
XCTAssertFalse(compositor.insertKey(""))
XCTAssertFalse(compositor.dropKey(direction: .rear))
XCTAssertFalse(compositor.dropKey(direction: .front))
XCTAssertTrue(compositor.insertKey("foo"))
XCTAssertTrue(compositor.dropKey(direction: .rear))
XCTAssertEqual(compositor.length, 0)
XCTAssertTrue(compositor.insertKey("foo"))
compositor.cursor = 0
XCTAssertTrue(compositor.dropKey(direction: .front))
XCTAssertEqual(compositor.length, 0)
}
func testCompositor_DeleteToTheFrontOfCursor() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.insertKey("a")
compositor.cursor = 0
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.length, 1)
XCTAssertEqual(compositor.spans.count, 1)
XCTAssertFalse(compositor.dropKey(direction: .rear))
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.length, 1)
XCTAssertTrue(compositor.dropKey(direction: .front))
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.length, 0)
XCTAssertEqual(compositor.spans.count, 0)
}
func testCompositor_MultipleSpans() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ";"
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
XCTAssertEqual(compositor.cursor, 3)
XCTAssertEqual(compositor.length, 3)
XCTAssertEqual(compositor.spans.count, 3)
XCTAssertEqual(compositor.spans[0].maxLength, 3)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;b;c")
XCTAssertEqual(compositor.spans[1].maxLength, 2)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "b;c")
XCTAssertEqual(compositor.spans[2].maxLength, 1)
XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "c")
}
func testCompositor_SpanDeletionFromFront() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ";"
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
XCTAssertFalse(compositor.dropKey(direction: .front))
XCTAssertTrue(compositor.dropKey(direction: .rear))
XCTAssertEqual(compositor.cursor, 2)
XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;b")
XCTAssertEqual(compositor.spans[1].maxLength, 1)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "b")
}
func testCompositor_SpanDeletionFromMiddle() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ";"
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
compositor.cursor = 2
XCTAssertTrue(compositor.dropKey(direction: .rear))
XCTAssertEqual(compositor.cursor, 1)
XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c")
XCTAssertEqual(compositor.spans[1].maxLength, 1)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c")
compositor.clear()
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
compositor.cursor = 1
XCTAssertTrue(compositor.dropKey(direction: .front))
XCTAssertEqual(compositor.cursor, 1)
XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;c")
XCTAssertEqual(compositor.spans[1].maxLength, 1)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c")
}
func testCompositor_SpanDeletionFromRear() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ";"
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
compositor.cursor = 0
XCTAssertFalse(compositor.dropKey(direction: .rear))
XCTAssertTrue(compositor.dropKey(direction: .front))
XCTAssertEqual(compositor.cursor, 0)
XCTAssertEqual(compositor.length, 2)
XCTAssertEqual(compositor.spans.count, 2)
XCTAssertEqual(compositor.spans[0].maxLength, 2)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "b")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "b;c")
XCTAssertEqual(compositor.spans[1].maxLength, 1)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "c")
}
func testCompositor_SpanInsertion() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ";"
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
compositor.cursor = 1
compositor.insertKey("X")
XCTAssertEqual(compositor.cursor, 2)
XCTAssertEqual(compositor.length, 4)
XCTAssertEqual(compositor.spans.count, 4)
XCTAssertEqual(compositor.spans[0].maxLength, 4)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 1)?.key, "a")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 2)?.key, "a;X")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 3)?.key, "a;X;b")
XCTAssertEqual(compositor.spans[0].nodeOf(length: 4)?.key, "a;X;b;c")
XCTAssertEqual(compositor.spans[1].maxLength, 3)
XCTAssertEqual(compositor.spans[1].nodeOf(length: 1)?.key, "X")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 2)?.key, "X;b")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 3)?.key, "X;b;c")
XCTAssertEqual(compositor.spans[2].maxLength, 2)
XCTAssertEqual(compositor.spans[2].nodeOf(length: 1)?.key, "b")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 2)?.key, "b;c")
XCTAssertEqual(compositor.spans[3].maxLength, 1)
XCTAssertEqual(compositor.spans[3].nodeOf(length: 1)?.key, "c")
}
func testCompositor_LongGridDeletion() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ""
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
compositor.insertKey("d")
compositor.insertKey("e")
compositor.insertKey("f")
compositor.insertKey("g")
compositor.insertKey("h")
compositor.insertKey("i")
compositor.insertKey("j")
compositor.insertKey("k")
compositor.insertKey("l")
compositor.insertKey("m")
compositor.insertKey("n")
compositor.cursor = 7
XCTAssertTrue(compositor.dropKey(direction: .rear))
XCTAssertEqual(compositor.cursor, 6)
XCTAssertEqual(compositor.length, 13)
XCTAssertEqual(compositor.spans.count, 13)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefh")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 5)?.key, "bcdef")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefhi")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 5)?.key, "cdefh")
XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defhij")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efhijk")
XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fhijkl")
XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "hijklm")
XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "ijklmn")
XCTAssertEqual(compositor.spans[8].nodeOf(length: 5)?.key, "jklmn")
}
func testCompositor_LongGridInsertion() throws {
var compositor = Megrez.Compositor(with: MockLM())
compositor.separator = ""
compositor.insertKey("a")
compositor.insertKey("b")
compositor.insertKey("c")
compositor.insertKey("d")
compositor.insertKey("e")
compositor.insertKey("f")
compositor.insertKey("g")
compositor.insertKey("h")
compositor.insertKey("i")
compositor.insertKey("j")
compositor.insertKey("k")
compositor.insertKey("l")
compositor.insertKey("m")
compositor.insertKey("n")
compositor.cursor = 7
compositor.insertKey("X")
XCTAssertEqual(compositor.cursor, 8)
XCTAssertEqual(compositor.length, 15)
XCTAssertEqual(compositor.spans.count, 15)
XCTAssertEqual(compositor.spans[0].nodeOf(length: 6)?.key, "abcdef")
XCTAssertEqual(compositor.spans[1].nodeOf(length: 6)?.key, "bcdefg")
XCTAssertEqual(compositor.spans[2].nodeOf(length: 6)?.key, "cdefgX")
XCTAssertEqual(compositor.spans[3].nodeOf(length: 6)?.key, "defgXh")
XCTAssertEqual(compositor.spans[3].nodeOf(length: 5)?.key, "defgX")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 6)?.key, "efgXhi")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 5)?.key, "efgXh")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 4)?.key, "efgX")
XCTAssertEqual(compositor.spans[4].nodeOf(length: 3)?.key, "efg")
XCTAssertEqual(compositor.spans[5].nodeOf(length: 6)?.key, "fgXhij")
XCTAssertEqual(compositor.spans[6].nodeOf(length: 6)?.key, "gXhijk")
XCTAssertEqual(compositor.spans[7].nodeOf(length: 6)?.key, "Xhijkl")
XCTAssertEqual(compositor.spans[8].nodeOf(length: 6)?.key, "hijklm")
}
func testCompositor_StressBench() throws {
NSLog("// Stress test preparation begins.")
var compositor = Megrez.Compositor(with: SimpleLM(input: strStressData))
for _ in 0..<1919 {
compositor.insertKey("yi")
}
NSLog("// Stress test started.")
let startTime = CFAbsoluteTimeGetCurrent()
compositor.walk()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
NSLog("// Stress test elapsed: \(timeElapsed)s.")
}
func testCompositor_WordSegmentation() throws {
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData, swapKeyValue: true))
compositor.separator = ""
for i in "高科技公司的年終獎金" {
compositor.insertKey(String(i))
}
let result = compositor.walk().0
XCTAssertEqual(result.keys, ["高科技", "公司", "", "年終", "獎金"])
}
func testCompositor_InputTestAndCursorJump() throws {
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData))
compositor.separator = ""
compositor.insertKey("gao1")
compositor.walk()
compositor.insertKey("ji4")
compositor.walk()
compositor.cursor = 1
compositor.insertKey("ke1")
compositor.walk()
compositor.cursor = 0
compositor.dropKey(direction: .front)
compositor.walk()
compositor.insertKey("gao1")
compositor.walk()
compositor.cursor = compositor.length
compositor.insertKey("gong1")
compositor.walk()
compositor.insertKey("si1")
compositor.walk()
compositor.insertKey("de5")
compositor.walk()
compositor.insertKey("nian2")
compositor.walk()
compositor.insertKey("zhong1")
compositor.walk()
compositor.insertKey("jiang3")
compositor.walk()
compositor.insertKey("jin1")
var result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技", "公司", "", "年中", "獎金"])
XCTAssertEqual(compositor.length, 10)
compositor.cursor = 7
let candidates = compositor.fetchCandidates(at: compositor.cursor).map(\.value)
XCTAssertTrue(candidates.contains("年中"))
XCTAssertTrue(candidates.contains("年終"))
XCTAssertTrue(candidates.contains(""))
XCTAssertTrue(candidates.contains(""))
XCTAssertTrue(compositor.overrideCandidateLiteral("年終", at: 7))
result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技", "公司", "", "年終", "獎金"])
let candidatesBeginAt = compositor.fetchCandidates(at: 3, filter: .beginAt).map(\.value)
let candidatesEndAt = compositor.fetchCandidates(at: 3, filter: .endAt).map(\.value)
XCTAssertFalse(candidatesBeginAt.contains("濟公"))
XCTAssertFalse(candidatesEndAt.contains("公司"))
// Test cursor jump.
compositor.cursor = 8
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
XCTAssertEqual(compositor.cursor, 6)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
XCTAssertEqual(compositor.cursor, 5)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
XCTAssertEqual(compositor.cursor, 3)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .rear))
XCTAssertEqual(compositor.cursor, 0)
XCTAssertFalse(compositor.jumpCursorBySpan(to: .rear))
XCTAssertEqual(compositor.cursor, 0)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
XCTAssertEqual(compositor.cursor, 3)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
XCTAssertEqual(compositor.cursor, 5)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
XCTAssertEqual(compositor.cursor, 6)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
XCTAssertEqual(compositor.cursor, 8)
XCTAssertTrue(compositor.jumpCursorBySpan(to: .front))
XCTAssertEqual(compositor.cursor, 10)
XCTAssertFalse(compositor.jumpCursorBySpan(to: .front))
XCTAssertEqual(compositor.cursor, 10)
// Test dumpDOT.
let expectedDumpDOT =
"digraph {\ngraph [ rankdir=LR ];\nBOS;\nBOS -> 高;\n高;\n高 -> 科;\n高 -> 科技;\nBOS -> 高科技;\n高科技;\n高科技 -> 工;\n高科技 -> 公司;\n科;\n科 -> 際;\n科 -> 濟公;\n科技;\n科技 -> 工;\n科技 -> 公司;\n際;\n際 -> 工;\n際 -> 公司;\n濟公;\n濟公 -> 斯;\n工;\n工 -> 斯;\n公司;\n公司 -> 的;\n斯;\n斯 -> 的;\n的;\n的 -> 年;\n的 -> 年終;\n年;\n年 -> 中;\n年終;\n年終 -> 獎;\n年終 -> 獎金;\n中;\n中 -> 獎;\n中 -> 獎金;\n獎;\n獎 -> 金;\n獎金;\n獎金 -> EOS;\n金;\n金 -> EOS;\nEOS;\n}\n"
XCTAssertEqual(compositor.dumpDOT, expectedDumpDOT)
}
func testCompositor_InputTest2() throws {
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData))
compositor.separator = ""
compositor.insertKey("gao1")
compositor.insertKey("ke1")
compositor.insertKey("ji4")
var result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技"])
compositor.insertKey("gong1")
compositor.insertKey("si1")
result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技", "公司"])
}
func testCompositor_OverrideOverlappingNodes() throws {
var compositor = Megrez.Compositor(with: SimpleLM(input: strSampleData))
compositor.separator = ""
compositor.insertKey("gao1")
compositor.insertKey("ke1")
compositor.insertKey("ji4")
var result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技"])
compositor.cursor = 0
XCTAssertTrue(compositor.overrideCandidateLiteral("", at: compositor.cursor))
result = compositor.walk().0
XCTAssertEqual(result.values, ["", "科技"])
XCTAssertTrue(compositor.overrideCandidateLiteral("高科技", at: 1))
result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技"])
XCTAssertTrue(compositor.overrideCandidateLiteral("", at: 0))
result = compositor.walk().0
XCTAssertEqual(result.values, ["", "科技"])
XCTAssertTrue(compositor.overrideCandidateLiteral("", at: 1))
result = compositor.walk().0
XCTAssertEqual(result.values, ["", "", ""])
XCTAssertTrue(compositor.overrideCandidateLiteral("", at: 2))
result = compositor.walk().0
XCTAssertEqual(result.values, ["", "", ""])
XCTAssertTrue(compositor.overrideCandidateLiteral("高科技", at: 3))
result = compositor.walk().0
XCTAssertEqual(result.values, ["高科技"])
}
func testCompositor_OverrideReset() throws {
var compositor = Megrez.Compositor(
with: SimpleLM(input: strSampleData + "zhong1jiang3 終講 -11.0\n" + "jiang3jin1 槳襟 -11.0\n"))
compositor.separator = ""
compositor.insertKey("nian2")
compositor.insertKey("zhong1")
compositor.insertKey("jiang3")
compositor.insertKey("jin1")
var result = compositor.walk().0
XCTAssertEqual(result.values, ["年中", "獎金"])
XCTAssertTrue(compositor.overrideCandidateLiteral("終講", at: 1))
result = compositor.walk().0
XCTAssertEqual(result.values, ["", "終講", ""])
XCTAssertTrue(compositor.overrideCandidateLiteral("槳襟", at: 2))
result = compositor.walk().0
XCTAssertEqual(result.values, ["年中", "槳襟"])
XCTAssertTrue(compositor.overrideCandidateLiteral("年終", at: 0))
result = compositor.walk().0
XCTAssertEqual(result.values, ["年終", "槳襟"])
}
func testCompositor_CandidateDisambiguation() throws {
var compositor = Megrez.Compositor(with: SimpleLM(input: strEmojiSampleData))
compositor.separator = ""
compositor.insertKey("gao1")
compositor.insertKey("re4")
compositor.insertKey("huo3")
compositor.insertKey("yan4")
compositor.insertKey("wei2")
compositor.insertKey("xian3")
var result = compositor.walk().0
XCTAssertEqual(result.values, ["高熱", "火焰", "危險"])
let location = 2
XCTAssertTrue(compositor.overrideCandidate(.init(key: "huo3", value: "🔥"), at: location))
result = compositor.walk().0
XCTAssertEqual(result.values, ["高熱", "🔥", "", "危險"])
XCTAssertTrue(compositor.overrideCandidate(.init(key: "huo3yan4", value: "🔥"), at: location))
result = compositor.walk().0
XCTAssertEqual(result.values, ["高熱", "🔥", "危險"])
}
}

View File

@ -0,0 +1,6 @@
.PHONY: format
format:
swiftformat ./ --swiftversion 5.5
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format format --in-place --configuration ./.clang-format-swift.json --parallel
@git ls-files --exclude-standard | grep -E '\.swift$$' | xargs swift-format lint --configuration ./.clang-format-swift.json --parallel

View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,22 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "PinyinPhonaConverter",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "PinyinPhonaConverter",
targets: ["PinyinPhonaConverter"]
)
],
dependencies: [],
targets: [
.target(
name: "PinyinPhonaConverter",
dependencies: []
)
]
)

View File

@ -0,0 +1,13 @@
# PinyinPhonaConverter
拼音轉注音模組。因為太耽誤編譯時間,所以單獨拿出來寫了一個給 LangModelAssembly 專用的版本。
```
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
```

View File

@ -0,0 +1,92 @@
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Cocoa
extension String {
public mutating func converToPhonabets(newToneOne: String = "") {
if isEmpty || contains("_") || !isNotPureAlphanumerical { return }
for key in arrHanyuPinyinToPhonabets {
self = replacingOccurrences(of: key.0, with: key.1)
}
self = replacingOccurrences(of: " ", with: newToneOne)
}
}
///
extension String {
fileprivate var isNotPureAlphanumerical: Bool {
let regex = ".*[^A-Za-z0-9].*"
let testString = NSPredicate(format: "SELF MATCHES %@", regex)
return testString.evaluate(with: self)
}
}
private let arrHanyuPinyinToPhonabets: [(String, String)] = [
("chuang", "ㄔㄨㄤ"), ("shuang", "ㄕㄨㄤ"), ("zhuang", "ㄓㄨㄤ"), ("chang", "ㄔㄤ"), ("cheng", "ㄔㄥ"), ("chong", "ㄔㄨㄥ"),
("chuai", "ㄔㄨㄞ"), ("chuan", "ㄔㄨㄢ"), ("guang", "ㄍㄨㄤ"), ("huang", "ㄏㄨㄤ"), ("jiang", "ㄐㄧㄤ"), ("jiong", "ㄐㄩㄥ"),
("kiang", "ㄎㄧㄤ"), ("kuang", "ㄎㄨㄤ"), ("biang", "ㄅㄧㄤ"), ("duang", "ㄉㄨㄤ"), ("liang", "ㄌㄧㄤ"), ("niang", "ㄋㄧㄤ"),
("qiang", "ㄑㄧㄤ"), ("qiong", "ㄑㄩㄥ"), ("shang", "ㄕㄤ"), ("sheng", "ㄕㄥ"), ("shuai", "ㄕㄨㄞ"), ("shuan", "ㄕㄨㄢ"),
("xiang", "ㄒㄧㄤ"), ("xiong", "ㄒㄩㄥ"), ("zhang", "ㄓㄤ"), ("zheng", "ㄓㄥ"), ("zhong", "ㄓㄨㄥ"), ("zhuai", "ㄓㄨㄞ"),
("zhuan", "ㄓㄨㄢ"), ("bang", "ㄅㄤ"), ("beng", "ㄅㄥ"), ("bian", "ㄅㄧㄢ"), ("biao", "ㄅㄧㄠ"), ("bing", "ㄅㄧㄥ"), ("cang", "ㄘㄤ"),
("ceng", "ㄘㄥ"), ("chai", "ㄔㄞ"), ("chan", "ㄔㄢ"), ("chao", "ㄔㄠ"), ("chen", "ㄔㄣ"), ("chou", "ㄔㄡ"), ("chua", "ㄔㄨㄚ"),
("chui", "ㄔㄨㄟ"), ("chun", "ㄔㄨㄣ"), ("chuo", "ㄔㄨㄛ"), ("cong", "ㄘㄨㄥ"), ("cuan", "ㄘㄨㄢ"), ("dang", "ㄉㄤ"), ("deng", "ㄉㄥ"),
("dian", "ㄉㄧㄢ"), ("diao", "ㄉㄧㄠ"), ("ding", "ㄉㄧㄥ"), ("dong", "ㄉㄨㄥ"), ("duan", "ㄉㄨㄢ"), ("fang", "ㄈㄤ"), ("feng", "ㄈㄥ"),
("fiao", "ㄈㄧㄠ"), ("fong", "ㄈㄨㄥ"), ("gang", "ㄍㄤ"), ("geng", "ㄍㄥ"), ("giao", "ㄍㄧㄠ"), ("gong", "ㄍㄨㄥ"), ("guai", "ㄍㄨㄞ"),
("guan", "ㄍㄨㄢ"), ("hang", "ㄏㄤ"), ("heng", "ㄏㄥ"), ("hong", "ㄏㄨㄥ"), ("huai", "ㄏㄨㄞ"), ("huan", "ㄏㄨㄢ"), ("jian", "ㄐㄧㄢ"),
("jiao", "ㄐㄧㄠ"), ("jing", "ㄐㄧㄥ"), ("juan", "ㄐㄩㄢ"), ("kang", "ㄎㄤ"), ("keng", "ㄎㄥ"), ("kong", "ㄎㄨㄥ"), ("kuai", "ㄎㄨㄞ"),
("kuan", "ㄎㄨㄢ"), ("lang", "ㄌㄤ"), ("leng", "ㄌㄥ"), ("lian", "ㄌㄧㄢ"), ("liao", "ㄌㄧㄠ"), ("ling", "ㄌㄧㄥ"), ("long", "ㄌㄨㄥ"),
("luan", "ㄌㄨㄢ"), ("lvan", "ㄌㄩㄢ"), ("mang", "ㄇㄤ"), ("meng", "ㄇㄥ"), ("mian", "ㄇㄧㄢ"), ("miao", "ㄇㄧㄠ"), ("ming", "ㄇㄧㄥ"),
("nang", "ㄋㄤ"), ("neng", "ㄋㄥ"), ("nian", "ㄋㄧㄢ"), ("niao", "ㄋㄧㄠ"), ("ning", "ㄋㄧㄥ"), ("nong", "ㄋㄨㄥ"), ("nuan", "ㄋㄨㄢ"),
("pang", "ㄆㄤ"), ("peng", "ㄆㄥ"), ("pian", "ㄆㄧㄢ"), ("piao", "ㄆㄧㄠ"), ("ping", "ㄆㄧㄥ"), ("qian", "ㄑㄧㄢ"), ("qiao", "ㄑㄧㄠ"),
("qing", "ㄑㄧㄥ"), ("quan", "ㄑㄩㄢ"), ("rang", "ㄖㄤ"), ("reng", "ㄖㄥ"), ("rong", "ㄖㄨㄥ"), ("ruan", "ㄖㄨㄢ"), ("sang", "ㄙㄤ"),
("seng", "ㄙㄥ"), ("shai", "ㄕㄞ"), ("shan", "ㄕㄢ"), ("shao", "ㄕㄠ"), ("shei", "ㄕㄟ"), ("shen", "ㄕㄣ"), ("shou", "ㄕㄡ"),
("shua", "ㄕㄨㄚ"), ("shui", "ㄕㄨㄟ"), ("shun", "ㄕㄨㄣ"), ("shuo", "ㄕㄨㄛ"), ("song", "ㄙㄨㄥ"), ("suan", "ㄙㄨㄢ"), ("tang", "ㄊㄤ"),
("teng", "ㄊㄥ"), ("tian", "ㄊㄧㄢ"), ("tiao", "ㄊㄧㄠ"), ("ting", "ㄊㄧㄥ"), ("tong", "ㄊㄨㄥ"), ("tuan", "ㄊㄨㄢ"), ("wang", "ㄨㄤ"),
("weng", "ㄨㄥ"), ("xian", "ㄒㄧㄢ"), ("xiao", "ㄒㄧㄠ"), ("xing", "ㄒㄧㄥ"), ("xuan", "ㄒㄩㄢ"), ("yang", "ㄧㄤ"), ("ying", "ㄧㄥ"),
("yong", "ㄩㄥ"), ("yuan", "ㄩㄢ"), ("zang", "ㄗㄤ"), ("zeng", "ㄗㄥ"), ("zhai", "ㄓㄞ"), ("zhan", "ㄓㄢ"), ("zhao", "ㄓㄠ"),
("zhei", "ㄓㄟ"), ("zhen", "ㄓㄣ"), ("zhou", "ㄓㄡ"), ("zhua", "ㄓㄨㄚ"), ("zhui", "ㄓㄨㄟ"), ("zhun", "ㄓㄨㄣ"), ("zhuo", "ㄓㄨㄛ"),
("zong", "ㄗㄨㄥ"), ("zuan", "ㄗㄨㄢ"), ("jun", "ㄐㄩㄣ"), ("ang", ""), ("bai", "ㄅㄞ"), ("ban", "ㄅㄢ"), ("bao", "ㄅㄠ"),
("bei", "ㄅㄟ"), ("ben", "ㄅㄣ"), ("bie", "ㄅㄧㄝ"), ("bin", "ㄅㄧㄣ"), ("cai", "ㄘㄞ"), ("can", "ㄘㄢ"), ("cao", "ㄘㄠ"),
("cei", "ㄘㄟ"), ("cen", "ㄘㄣ"), ("cha", "ㄔㄚ"), ("che", "ㄔㄜ"), ("chi", ""), ("chu", "ㄔㄨ"), ("cou", "ㄘㄡ"),
("cui", "ㄘㄨㄟ"), ("cun", "ㄘㄨㄣ"), ("cuo", "ㄘㄨㄛ"), ("dai", "ㄉㄞ"), ("dan", "ㄉㄢ"), ("dao", "ㄉㄠ"), ("dei", "ㄉㄟ"),
("den", "ㄉㄣ"), ("dia", "ㄉㄧㄚ"), ("die", "ㄉㄧㄝ"), ("diu", "ㄉㄧㄡ"), ("dou", "ㄉㄡ"), ("dui", "ㄉㄨㄟ"), ("dun", "ㄉㄨㄣ"),
("duo", "ㄉㄨㄛ"), ("eng", ""), ("fan", "ㄈㄢ"), ("fei", "ㄈㄟ"), ("fen", "ㄈㄣ"), ("fou", "ㄈㄡ"), ("gai", "ㄍㄞ"),
("gan", "ㄍㄢ"), ("gao", "ㄍㄠ"), ("gei", "ㄍㄟ"), ("gin", "ㄍㄧㄣ"), ("gen", "ㄍㄣ"), ("gou", "ㄍㄡ"), ("gua", "ㄍㄨㄚ"),
("gue", "ㄍㄨㄜ"), ("gui", "ㄍㄨㄟ"), ("gun", "ㄍㄨㄣ"), ("guo", "ㄍㄨㄛ"), ("hai", "ㄏㄞ"), ("han", "ㄏㄢ"), ("hao", "ㄏㄠ"),
("hei", "ㄏㄟ"), ("hen", "ㄏㄣ"), ("hou", "ㄏㄡ"), ("hua", "ㄏㄨㄚ"), ("hui", "ㄏㄨㄟ"), ("hun", "ㄏㄨㄣ"), ("huo", "ㄏㄨㄛ"),
("jia", "ㄐㄧㄚ"), ("jie", "ㄐㄧㄝ"), ("jin", "ㄐㄧㄣ"), ("jiu", "ㄐㄧㄡ"), ("jue", "ㄐㄩㄝ"), ("kai", "ㄎㄞ"), ("kan", "ㄎㄢ"),
("kao", "ㄎㄠ"), ("ken", "ㄎㄣ"), ("kiu", "ㄎㄧㄡ"), ("kou", "ㄎㄡ"), ("kua", "ㄎㄨㄚ"), ("kui", "ㄎㄨㄟ"), ("kun", "ㄎㄨㄣ"),
("kuo", "ㄎㄨㄛ"), ("lai", "ㄌㄞ"), ("lan", "ㄌㄢ"), ("lao", "ㄌㄠ"), ("lei", "ㄌㄟ"), ("lia", "ㄌㄧㄚ"), ("lie", "ㄌㄧㄝ"),
("lin", "ㄌㄧㄣ"), ("liu", "ㄌㄧㄡ"), ("lou", "ㄌㄡ"), ("lun", "ㄌㄨㄣ"), ("luo", "ㄌㄨㄛ"), ("lve", "ㄌㄩㄝ"), ("mai", "ㄇㄞ"),
("man", "ㄇㄢ"), ("mao", "ㄇㄠ"), ("mei", "ㄇㄟ"), ("men", "ㄇㄣ"), ("mie", "ㄇㄧㄝ"), ("min", "ㄇㄧㄣ"), ("miu", "ㄇㄧㄡ"),
("mou", "ㄇㄡ"), ("nai", "ㄋㄞ"), ("nan", "ㄋㄢ"), ("nao", "ㄋㄠ"), ("nei", "ㄋㄟ"), ("nen", "ㄋㄣ"), ("nie", "ㄋㄧㄝ"),
("nin", "ㄋㄧㄣ"), ("niu", "ㄋㄧㄡ"), ("nou", "ㄋㄡ"), ("nui", "ㄋㄨㄟ"), ("nun", "ㄋㄨㄣ"), ("nuo", "ㄋㄨㄛ"), ("nve", "ㄋㄩㄝ"),
("pai", "ㄆㄞ"), ("pan", "ㄆㄢ"), ("pao", "ㄆㄠ"), ("pei", "ㄆㄟ"), ("pen", "ㄆㄣ"), ("pia", "ㄆㄧㄚ"), ("pie", "ㄆㄧㄝ"),
("pin", "ㄆㄧㄣ"), ("pou", "ㄆㄡ"), ("qia", "ㄑㄧㄚ"), ("qie", "ㄑㄧㄝ"), ("qin", "ㄑㄧㄣ"), ("qiu", "ㄑㄧㄡ"), ("que", "ㄑㄩㄝ"),
("qun", "ㄑㄩㄣ"), ("ran", "ㄖㄢ"), ("rao", "ㄖㄠ"), ("ren", "ㄖㄣ"), ("rou", "ㄖㄡ"), ("rui", "ㄖㄨㄟ"), ("run", "ㄖㄨㄣ"),
("ruo", "ㄖㄨㄛ"), ("sai", "ㄙㄞ"), ("san", "ㄙㄢ"), ("sao", "ㄙㄠ"), ("sei", "ㄙㄟ"), ("sen", "ㄙㄣ"), ("sha", "ㄕㄚ"),
("she", "ㄕㄜ"), ("shi", ""), ("shu", "ㄕㄨ"), ("sou", "ㄙㄡ"), ("sui", "ㄙㄨㄟ"), ("sun", "ㄙㄨㄣ"), ("suo", "ㄙㄨㄛ"),
("tai", "ㄊㄞ"), ("tan", "ㄊㄢ"), ("tao", "ㄊㄠ"), ("tie", "ㄊㄧㄝ"), ("tou", "ㄊㄡ"), ("tui", "ㄊㄨㄟ"), ("tun", "ㄊㄨㄣ"),
("tuo", "ㄊㄨㄛ"), ("wai", "ㄨㄞ"), ("wan", "ㄨㄢ"), ("wei", "ㄨㄟ"), ("wen", "ㄨㄣ"), ("xia", "ㄒㄧㄚ"), ("xie", "ㄒㄧㄝ"),
("xin", "ㄒㄧㄣ"), ("xiu", "ㄒㄧㄡ"), ("xue", "ㄒㄩㄝ"), ("xun", "ㄒㄩㄣ"), ("yai", "ㄧㄞ"), ("yan", "ㄧㄢ"), ("yao", "ㄧㄠ"),
("yin", "ㄧㄣ"), ("you", "ㄧㄡ"), ("yue", "ㄩㄝ"), ("yun", "ㄩㄣ"), ("zai", "ㄗㄞ"), ("zan", "ㄗㄢ"), ("zao", "ㄗㄠ"),
("zei", "ㄗㄟ"), ("zen", "ㄗㄣ"), ("zha", "ㄓㄚ"), ("zhe", "ㄓㄜ"), ("zhi", ""), ("zhu", "ㄓㄨ"), ("zou", "ㄗㄡ"),
("zui", "ㄗㄨㄟ"), ("zun", "ㄗㄨㄣ"), ("zuo", "ㄗㄨㄛ"), ("ai", ""), ("an", ""), ("ao", ""), ("ba", "ㄅㄚ"), ("bi", "ㄅㄧ"),
("bo", "ㄅㄛ"), ("bu", "ㄅㄨ"), ("ca", "ㄘㄚ"), ("ce", "ㄘㄜ"), ("ci", ""), ("cu", "ㄘㄨ"), ("da", "ㄉㄚ"), ("de", "ㄉㄜ"),
("di", "ㄉㄧ"), ("du", "ㄉㄨ"), ("eh", ""), ("ei", ""), ("en", ""), ("er", ""), ("fa", "ㄈㄚ"), ("fo", "ㄈㄛ"),
("fu", "ㄈㄨ"), ("ga", "ㄍㄚ"), ("ge", "ㄍㄜ"), ("gi", "ㄍㄧ"), ("gu", "ㄍㄨ"), ("ha", "ㄏㄚ"), ("he", "ㄏㄜ"), ("hu", "ㄏㄨ"),
("ji", "ㄐㄧ"), ("ju", "ㄐㄩ"), ("ka", "ㄎㄚ"), ("ke", "ㄎㄜ"), ("ku", "ㄎㄨ"), ("la", "ㄌㄚ"), ("le", "ㄌㄜ"), ("li", "ㄌㄧ"),
("lo", "ㄌㄛ"), ("lu", "ㄌㄨ"), ("lv", "ㄌㄩ"), ("ma", "ㄇㄚ"), ("me", "ㄇㄜ"), ("mi", "ㄇㄧ"), ("mo", "ㄇㄛ"), ("mu", "ㄇㄨ"),
("na", "ㄋㄚ"), ("ne", "ㄋㄜ"), ("ni", "ㄋㄧ"), ("nu", "ㄋㄨ"), ("nv", "ㄋㄩ"), ("ou", ""), ("pa", "ㄆㄚ"), ("pi", "ㄆㄧ"),
("po", "ㄆㄛ"), ("pu", "ㄆㄨ"), ("qi", "ㄑㄧ"), ("qu", "ㄑㄩ"), ("re", "ㄖㄜ"), ("ri", ""), ("ru", "ㄖㄨ"), ("sa", "ㄙㄚ"),
("se", "ㄙㄜ"), ("si", ""), ("su", "ㄙㄨ"), ("ta", "ㄊㄚ"), ("te", "ㄊㄜ"), ("ti", "ㄊㄧ"), ("tu", "ㄊㄨ"), ("wa", "ㄨㄚ"),
("wo", "ㄨㄛ"), ("wu", ""), ("xi", "ㄒㄧ"), ("xu", "ㄒㄩ"), ("ya", "ㄧㄚ"), ("ye", "ㄧㄝ"), ("yi", ""), ("yo", "ㄧㄛ"),
("yu", ""), ("za", "ㄗㄚ"), ("ze", "ㄗㄜ"), ("zi", ""), ("zu", "ㄗㄨ"), ("a", ""), ("e", ""), ("o", ""), ("q", ""),
("1", " "), ("2", "ˊ"), ("3", "ˇ"), ("4", "ˋ"), ("5", "˙"),
]

9
Packages/vChewing_Shared/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,26 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Shared",
platforms: [
.macOS(.v10_11)
],
products: [
.library(
name: "Shared",
targets: ["Shared"]
)
],
dependencies: [
.package(path: "../vChewing_CocoaExtension")
],
targets: [
.target(
name: "Shared",
dependencies: [
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension")
]
)
]
)

View File

@ -0,0 +1,13 @@
# Protocols
威注音輸入法用到的各種協定與共用靜態內容、以及少許全局共用工具函式。
```
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
```

View File

@ -0,0 +1,59 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Cocoa
import CocoaExtension
extension NSEvent: InputSignalProtocol {}
// MARK: - InputSignalProtocol
public protocol InputSignalProtocol {
var isTypingVertical: Bool { get }
var text: String { get }
var inputTextIgnoringModifiers: String? { get }
var charCode: UInt16 { get }
var keyCode: UInt16 { get }
var isFlagChanged: Bool { get }
var mainAreaNumKeyChar: String? { get }
var isASCII: Bool { get }
var isInvalid: Bool { get }
var isKeyCodeBlacklisted: Bool { get }
var isReservedKey: Bool { get }
var isNumericPadKey: Bool { get }
var isMainAreaNumKey: Bool { get }
var isShiftHold: Bool { get }
var isCommandHold: Bool { get }
var isControlHold: Bool { get }
var isControlHotKey: Bool { get }
var isOptionHold: Bool { get }
var isOptionHotKey: Bool { get }
var isCapsLockOn: Bool { get }
var isFunctionKeyHold: Bool { get }
var isNonLaptopFunctionKey: Bool { get }
var isEnter: Bool { get }
var isTab: Bool { get }
var isUp: Bool { get }
var isDown: Bool { get }
var isLeft: Bool { get }
var isRight: Bool { get }
var isPageUp: Bool { get }
var isPageDown: Bool { get }
var isSpace: Bool { get }
var isBackSpace: Bool { get }
var isEsc: Bool { get }
var isHome: Bool { get }
var isEnd: Bool { get }
var isDelete: Bool { get }
var isCursorBackward: Bool { get }
var isCursorForward: Bool { get }
var isCursorClockRight: Bool { get }
var isCursorClockLeft: Bool { get }
var isUpperCaseASCIILetterKey: Bool { get }
var isSymbolMenuPhysicalKey: Bool { get }
}

View File

@ -0,0 +1,74 @@
// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Foundation
public protocol PrefMgrProtocol {
var isDebugModeEnabled: Bool { get set }
var failureFlagForUOMObservation: Bool { get set }
var deltaOfCalendarYears: Int { get set }
var mostRecentInputMode: String { get set }
var checkUpdateAutomatically: Bool { get set }
var userDataFolderSpecified: String { get set }
var appleLanguages: [String] { get set }
var keyboardParser: Int { get set }
var basicKeyboardLayout: String { get set }
var alphanumericalKeyboardLayout: String { get set }
var showPageButtonsInCandidateWindow: Bool { get set }
var candidateListTextSize: Double { get set }
var shouldAutoReloadUserDataFiles: Bool { get set }
var useRearCursorMode: Bool { get set }
var moveCursorAfterSelectingCandidate: Bool { get set }
var useHorizontalCandidateList: Bool { get set }
var chooseCandidateUsingSpace: Bool { get set }
var allowBoostingSingleKanjiAsUserPhrase: Bool { get set }
var fetchSuggestionsFromUserOverrideModel: Bool { get set }
var useFixecCandidateOrderOnSelection: Bool { get set }
var autoCorrectReadingCombination: Bool { get set }
var alsoConfirmAssociatedCandidatesByEnter: Bool { get set }
var keepReadingUponCompositionError: Bool { get set }
var upperCaseLetterKeyBehavior: Int { get set }
var togglingAlphanumericalModeWithLShift: Bool { get set }
var disableShiftTogglingAlphanumericalMode: Bool { get set }
var consolidateContextOnCandidateSelection: Bool { get set }
var hardenVerticalPunctuations: Bool { get set }
var trimUnfinishedReadingsOnCommit: Bool { get set }
var alwaysShowTooltipTextsHorizontally: Bool { get set }
var clientsIMKTextInputIncapable: [String] { get set }
var useIMKCandidateWindow: Bool { get set }
var handleDefaultCandidateFontsByLangIdentifier: Bool { get set }
var shiftKeyAccommodationBehavior: Int { get set }
var maxCandidateLength: Int { get set }
var shouldNotFartInLieuOfBeep: Bool { get set }
var showHanyuPinyinInCompositionBuffer: Bool { get set }
var inlineDumpPinyinInLieuOfZhuyin: Bool { get set }
var cns11643Enabled: Bool { get set }
var symbolInputEnabled: Bool { get set }
var chineseConversionEnabled: Bool { get set }
var shiftJISShinjitaiOutputEnabled: Bool { get set }
var currencyNumeralsEnabled: Bool { get set }
var halfWidthPunctuationEnabled: Bool { get set }
var escToCleanInputBuffer: Bool { get set }
var specifyIntonationKeyBehavior: Int { get set }
var specifyShiftBackSpaceKeyBehavior: Int { get set }
var specifyShiftTabKeyBehavior: Bool { get set }
var specifyShiftSpaceKeyBehavior: Bool { get set }
var candidateTextFontName: String { get set }
var candidateKeyLabelFontName: String { get set }
var candidateKeys: String { get set }
var useSCPCTypingMode: Bool { get set }
var phraseReplacementEnabled: Bool { get set }
var associatedPhrasesEnabled: Bool { get set }
var usingHotKeySCPC: Bool { get set }
var usingHotKeyAssociates: Bool { get set }
var usingHotKeyCNS: Bool { get set }
var usingHotKeyKangXi: Bool { get set }
var usingHotKeyJIS: Bool { get set }
var usingHotKeyHalfWidthASCII: Bool { get set }
var usingHotKeyCurrencyNumerals: Bool { get set }
}

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