mirror of
https://github.com/mii443/AzooKeyKanaKanjiConverter.git
synced 2025-08-22 15:05:26 +00:00
Merge branch 'develop' into feature/improve_convert_graph_impls
This commit is contained in:
2
.github/workflows/swift.yml
vendored
2
.github/workflows/swift.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- uses: swift-actions/setup-swift@v1
|
||||
- uses: swift-actions/setup-swift@v2
|
||||
with:
|
||||
swift-version: 5.9
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -2,4 +2,7 @@
|
||||
|
||||
本ディレクトリはAzooKeyKanaKanjiConverterModuleに関するドキュメントをまとめています。
|
||||
|
||||
azooKey本体については[azooKey/docs](https://github.com/ensan-hcl/azooKey/tree/develop/docs/overview.md)をご覧ください。
|
||||
azooKey本体については[azooKey/docs](https://github.com/ensan-hcl/azooKey/tree/develop/docs/overview.md)をご覧ください。
|
||||
|
||||
開発ガイドは[development_guide.md](./development_guide.md)をご覧ください。
|
||||
|
||||
|
25
Docs/cli.md
Normal file
25
Docs/cli.md
Normal file
@ -0,0 +1,25 @@
|
||||
# anco (azooKey Cli)
|
||||
|
||||
`anco`コマンドにより、AzooKeyKanaKanjiConverterをCliで利用することができます。`anco`はデバッグ用ツールの位置付けです。
|
||||
|
||||
`anco`を利用するには、最初にinstallが必要です。
|
||||
|
||||
```
|
||||
sh install_cli.sh
|
||||
```
|
||||
|
||||
例えば以下のように利用できます。
|
||||
|
||||
```
|
||||
your@pc Desktop % anco にほんごにゅうりょく --disable_prediction -n 10
|
||||
日本語入力
|
||||
にほんご入力
|
||||
2本ご入力
|
||||
2本後入力
|
||||
2本語入力
|
||||
日本語
|
||||
2本
|
||||
日本
|
||||
にほんご
|
||||
2本後
|
||||
```
|
38
Docs/converter_api.md
Normal file
38
Docs/converter_api.md
Normal file
@ -0,0 +1,38 @@
|
||||
# KanaKanjiConverter API
|
||||
|
||||
KanaKanjiConverterのインスタンスに対して利用できるいくつかのAPIを示します。
|
||||
|
||||
## `setKeyboardLanguage`
|
||||
|
||||
これから入力しようとしている言語を設定します。このAPIを呼ぶのは必須ではありません。
|
||||
|
||||
英語入力の場合、この関数を入力開始前に呼ぶことで事前に必要なデータをロードすることができるため、ユーザ体験が向上する可能性があります。
|
||||
|
||||
## `sendToDicdataStore`
|
||||
|
||||
辞書データに関する情報を追加します。
|
||||
|
||||
### `importDynamicUserDict`
|
||||
|
||||
動的ユーザ辞書を登録します。`DicdataElement`構造体の配列を直接渡します。
|
||||
|
||||
```Swift
|
||||
converter.sendToDicdataStore(.importDynamicUserDict([
|
||||
DicdataElement(word: "anco", ruby: "アンコ", cid: 1288, mid: 501, value: -5),
|
||||
]))
|
||||
```
|
||||
|
||||
`ruby`には読みを指定します。カタカナで指定してください。 `cid`はIPADIC品詞ID、`mid`は「501」としてください。`value`は`-5`から`-10`程度の範囲で設定してください。小さい値ほど変換されにくくなります。
|
||||
|
||||
### `forgetMemory`
|
||||
|
||||
特定の`Candidate`を渡すと、その`Candidate`に含まれている学習データを全てリセットします。
|
||||
|
||||
## `setCompletedData`
|
||||
|
||||
prefixとして確定された候補を与えてください。
|
||||
|
||||
## `updateLearningData`
|
||||
|
||||
確定された候補を与えると、学習を更新します。
|
||||
|
23
Docs/development_guide.md
Normal file
23
Docs/development_guide.md
Normal file
@ -0,0 +1,23 @@
|
||||
# 開発ガイド
|
||||
|
||||
開発にはSwift 5.9以上が必要です。
|
||||
|
||||
開発にはレポジトリをクローンしてください。サブモジュールを含むため、`--recursive`オプションが必要です。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ensan-hcl/AzooKeyKanaKanjiConverter --recursive
|
||||
```
|
||||
|
||||
## Cliツール
|
||||
|
||||
デバッグ用のCliツールとして`anco`コマンドがあります。`install_cli.sh`を実行してインストールしてください。場合によっては、`sudo`が必要です。
|
||||
|
||||
```bash
|
||||
sh install_cli.sh
|
||||
```
|
||||
|
||||
詳しくは[cli.md](./cli.md)をお読みください。
|
||||
|
||||
## コントリビュート
|
||||
|
||||
コントリビュートは歓迎です!
|
@ -1,8 +1,10 @@
|
||||
# Failures
|
||||
|
||||
azooKeyの開発上、明確に失敗だったと考えている実装や仕様をまとめます。これらは将来的に修正できるかもしれないし、できないかもしれないです。
|
||||
AzooKeyKanaKanjiConverterの開発上、明確に失敗だったと考えている実装や仕様をまとめます。これらは将来的に修正できるかもしれないし、できないかもしれないです。
|
||||
|
||||
このドキュメントの目的はazooKeyの判断ミスを明確にして、今後azooKeyのフォークを作成する方や、新たな日本語入力ソフトウェアを作ろうとする方に向けて知見を残しておくことです。
|
||||
このドキュメントの目的はAzooKeyKanaKanjiConverterの判断ミスを明確にして、今後AzooKeyKanaKanjiConverterのフォークを作成する方や、新たな日本語入力ソフトウェアを作ろうとする方に向けて知見を残しておくことです。
|
||||
|
||||
AzooKeyKanaKanjiConverter本体については[azooKeyをベースとするプロダクトの開発開始時に注意すべき点](https://github.com/ensan-hcl/azooKey/tree/develop/docs/advice_for_azooKey_based_development.md)もご覧ください。
|
||||
|
||||
## コストの設計
|
||||
|
||||
|
@ -31,13 +31,14 @@ let package = Package(
|
||||
.library(
|
||||
name: "KanaKanjiConverterModule",
|
||||
targets: ["KanaKanjiConverterModule"]
|
||||
)
|
||||
),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"),
|
||||
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0")
|
||||
.package(url: "https://github.com/apple/swift-collections", from: "1.0.0"),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "1.0.0")),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
@ -72,6 +73,13 @@ let package = Package(
|
||||
],
|
||||
swiftSettings: swiftSettings
|
||||
),
|
||||
.executableTarget(
|
||||
name: "CliTool",
|
||||
dependencies: [
|
||||
"KanaKanjiConverterModuleWithDefaultDictionary",
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "SwiftUtilsTests",
|
||||
dependencies: ["SwiftUtils"],
|
||||
@ -88,9 +96,10 @@ let package = Package(
|
||||
),
|
||||
.testTarget(
|
||||
name: "KanaKanjiConverterModuleWithDefaultDictionaryTests",
|
||||
dependencies: ["KanaKanjiConverterModuleWithDefaultDictionary",
|
||||
.product(name: "Collections", package: "swift-collections")
|
||||
],
|
||||
dependencies: [
|
||||
"KanaKanjiConverterModuleWithDefaultDictionary",
|
||||
.product(name: "Collections", package: "swift-collections")
|
||||
],
|
||||
swiftSettings: swiftSettings
|
||||
)
|
||||
]
|
||||
|
@ -3,7 +3,9 @@
|
||||
AzooKeyKanaKanjiConverterは[azooKey](https://github.com/ensan-hcl/azooKey)のために開発したかな漢字変換エンジンです。数行のコードでかな漢字変換をiOS / macOS / visionOSのアプリケーションに組み込むことができます。
|
||||
|
||||
## 動作環境
|
||||
iOS 14以降, macOS 11以降, visionOS 1以降, Ubuntu 22.04以降で動作を確認しています
|
||||
iOS 14以降, macOS 11以降, visionOS 1以降, Ubuntu 22.04以降で動作を確認しています。
|
||||
|
||||
AzooKeyKanaKanjiConverterの開発については[開発ガイド](Docs/development_guide.md)をご覧ください。
|
||||
|
||||
## KanaKanjiConverterModule
|
||||
かな漢字変換を受け持つモジュールです。
|
||||
|
13
Sources/CliTool/Anco.swift
Normal file
13
Sources/CliTool/Anco.swift
Normal file
@ -0,0 +1,13 @@
|
||||
import KanaKanjiConverterModuleWithDefaultDictionary
|
||||
import ArgumentParser
|
||||
|
||||
@main
|
||||
public struct Anco: ParsableCommand {
|
||||
public static var configuration = CommandConfiguration(
|
||||
abstract: "Anco is A(zooKey) Kana-Ka(n)ji (co)nverter",
|
||||
subcommands: [Subcommands.Run.self],
|
||||
defaultSubcommand: Subcommands.Run.self
|
||||
)
|
||||
|
||||
public init() {}
|
||||
}
|
2
Sources/CliTool/Subcommands/Commands.swift
Normal file
2
Sources/CliTool/Subcommands/Commands.swift
Normal file
@ -0,0 +1,2 @@
|
||||
/// namespace for subcommands
|
||||
enum Subcommands {}
|
50
Sources/CliTool/Subcommands/RunCommand.swift
Normal file
50
Sources/CliTool/Subcommands/RunCommand.swift
Normal file
@ -0,0 +1,50 @@
|
||||
import KanaKanjiConverterModuleWithDefaultDictionary
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
extension Subcommands {
|
||||
struct Run: ParsableCommand {
|
||||
@Argument(help: "ひらがなで表記された入力")
|
||||
var input: String = ""
|
||||
|
||||
@Option(name: [.customLong("config_n_best")], help: "The parameter n (n best parameter) for internal viterbi search.")
|
||||
var configNBest: Int = 10
|
||||
@Option(name: [.customShort("n"), .customLong("top_n")], help: "Display top n candidates.")
|
||||
var displayTopN: Int = 1
|
||||
|
||||
@Flag(name: [.customLong("disable_prediction")], help: "Disable producing prediction candidates.")
|
||||
var disablePrediction = false
|
||||
|
||||
static var configuration = CommandConfiguration(commandName: "run", abstract: "Show help for this utility.")
|
||||
|
||||
@MainActor mutating func run() {
|
||||
let converter = KanaKanjiConverter()
|
||||
var composingText = ComposingText()
|
||||
composingText.insertAtCursorPosition(input, inputStyle: .direct)
|
||||
let result = converter.requestCandidates(composingText, options: requestOptions())
|
||||
for candidate in result.mainResults.prefix(self.displayTopN) {
|
||||
print(candidate.text)
|
||||
}
|
||||
}
|
||||
|
||||
func requestOptions() -> ConvertRequestOptions {
|
||||
.withDefaultDictionary(
|
||||
N_best: configNBest,
|
||||
requireJapanesePrediction: !disablePrediction,
|
||||
requireEnglishPrediction: false,
|
||||
keyboardLanguage: .ja_JP,
|
||||
typographyLetterCandidate: false,
|
||||
unicodeCandidate: true,
|
||||
englishCandidateInRoman2KanaInput: true,
|
||||
fullWidthRomanCandidate: false,
|
||||
halfWidthKanaCandidate: false,
|
||||
learningType: .nothing,
|
||||
maxMemoryCount: 0,
|
||||
shouldResetMemory: false,
|
||||
memoryDirectoryURL: URL(fileURLWithPath: ""),
|
||||
sharedContainerURL: URL(fileURLWithPath: ""),
|
||||
metadata: .init(appVersionString: "anco")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ extension KanaKanjiConverter {
|
||||
}
|
||||
var string = string[...]
|
||||
// ネンをdropする
|
||||
guard "ネン" == string.suffix(2) else {
|
||||
guard string.hasSuffix("ネン") else {
|
||||
return nil
|
||||
}
|
||||
string = string.dropLast(2)
|
||||
|
@ -30,7 +30,7 @@ public final class DicdataStore {
|
||||
private var charsID: [Character: UInt8] = [:]
|
||||
private var learningManager = LearningManager()
|
||||
|
||||
private var osUserDict: [DicdataElement] = []
|
||||
private var dynamicUserDict: [DicdataElement] = []
|
||||
|
||||
/// 辞書のエントリの最大長さ
|
||||
/// - TODO: make this value as an option
|
||||
@ -71,6 +71,10 @@ public final class DicdataStore {
|
||||
}
|
||||
|
||||
public enum Notification {
|
||||
/// use `importDynamicUserDict` for data that cannot be obtained statically.
|
||||
/// - warning: Too many dynamic user dictionary will damage conversion performance, as dynamic user dictionary uses inefficent algorithms for looking up. If your entries can be listed up statically, then use normal user dictionaries.
|
||||
case importDynamicUserDict([DicdataElement])
|
||||
@available(*, deprecated, renamed: "importDynamicUserDict", message: "it will be removed in AzooKeyKanaKanjiConverter v1.0")
|
||||
case importOSUserDict([DicdataElement])
|
||||
case setRequestOptions(ConvertRequestOptions)
|
||||
case forgetMemory(Candidate)
|
||||
@ -81,8 +85,8 @@ public final class DicdataStore {
|
||||
switch data {
|
||||
case .closeKeyboard:
|
||||
self.closeKeyboard()
|
||||
case let .importOSUserDict(osUserDict):
|
||||
self.osUserDict = osUserDict
|
||||
case .importOSUserDict(let dicdata), .importDynamicUserDict(let dicdata):
|
||||
self.dynamicUserDict = dicdata
|
||||
case let .forgetMemory(candidate):
|
||||
self.learningManager.forgetMemory(data: candidate.data)
|
||||
// loudsの処理があるので、リセットを実施する
|
||||
@ -420,57 +424,29 @@ public final class DicdataStore {
|
||||
if count == .zero {
|
||||
return []
|
||||
}
|
||||
// 1文字に対する予測変換は検索が難しいので、特別に用意した辞書を用いて実施する
|
||||
if count == 1 {
|
||||
do {
|
||||
let csvString = try String(contentsOf: requestOptions.dictionaryResourceURL.appendingPathComponent("p/p_\(key).csv", isDirectory: false), encoding: String.Encoding.utf8)
|
||||
let csvLines = csvString.split(separator: "\n")
|
||||
let csvData = csvLines.map {$0.split(separator: ",", omittingEmptySubsequences: false)}
|
||||
let dicdata: [DicdataElement] = csvData
|
||||
.map {self.parseLoudstxt2FormattedEntry(from: $0)}
|
||||
.filter { Self.predictionUsable[$0.rcid] }
|
||||
return dicdata
|
||||
} catch {
|
||||
debug("ファイルが存在しません: \(error)")
|
||||
return []
|
||||
}
|
||||
} else if count == 2 {
|
||||
var result: [DicdataElement] = []
|
||||
let first = String(key.first!)
|
||||
let charIDs = key.map(self.character2charId)
|
||||
// 最大700件に絞ることによって低速化を回避する。
|
||||
let prefixIndices = self.prefixMatchLOUDS(identifier: first, charIDs: charIDs, depth: 5).prefix(700)
|
||||
result.append(
|
||||
contentsOf: self.getDicdataFromLoudstxt3(identifier: first, indices: Set(prefixIndices))
|
||||
.filter { Self.predictionUsable[$0.rcid] }
|
||||
)
|
||||
let userDictIndices = self.prefixMatchLOUDS(identifier: "user", charIDs: charIDs, depth: 5).prefix(700)
|
||||
result.append(contentsOf: self.getDicdataFromLoudstxt3(identifier: "user", indices: Set(userDictIndices)))
|
||||
if learningManager.enabled {
|
||||
let memoryDictIndices = self.prefixMatchLOUDS(identifier: "memory", charIDs: charIDs, depth: 5).prefix(700)
|
||||
result.append(contentsOf: self.getDicdataFromLoudstxt3(identifier: "memory", indices: Set(memoryDictIndices)))
|
||||
result.append(contentsOf: self.learningManager.temporaryPrefixMatch(charIDs: charIDs))
|
||||
}
|
||||
return result
|
||||
// 最大700件に絞ることによって低速化を回避する。
|
||||
var result: [DicdataElement] = []
|
||||
let first = String(key.first!)
|
||||
let charIDs = key.map(self.character2charId)
|
||||
// 1, 2文字に対する予測変換は候補数が大きいので、depth(〜文字数)を制限する
|
||||
let depth = if count == 1 || count == 2 {
|
||||
5
|
||||
} else {
|
||||
var result: [DicdataElement] = []
|
||||
let first = String(key.first!)
|
||||
let charIDs = key.map(self.character2charId)
|
||||
// 最大700件に絞ることによって低速化を回避する。
|
||||
let prefixIndices = self.prefixMatchLOUDS(identifier: first, charIDs: charIDs).prefix(700)
|
||||
result.append(
|
||||
contentsOf: self.getDicdataFromLoudstxt3(identifier: first, indices: Set(prefixIndices))
|
||||
.filter { Self.predictionUsable[$0.rcid] }
|
||||
)
|
||||
let userDictIndices = self.prefixMatchLOUDS(identifier: "user", charIDs: charIDs).prefix(700)
|
||||
result.append(contentsOf: self.getDicdataFromLoudstxt3(identifier: "user", indices: Set(userDictIndices)))
|
||||
if learningManager.enabled {
|
||||
let memoryDictIndices = self.prefixMatchLOUDS(identifier: "memory", charIDs: charIDs).prefix(700)
|
||||
result.append(contentsOf: self.getDicdataFromLoudstxt3(identifier: "memory", indices: Set(memoryDictIndices)))
|
||||
result.append(contentsOf: self.learningManager.temporaryPrefixMatch(charIDs: charIDs))
|
||||
}
|
||||
return result
|
||||
Int.max
|
||||
}
|
||||
let prefixIndices = self.prefixMatchLOUDS(identifier: first, charIDs: charIDs, depth: depth).prefix(700)
|
||||
result.append(
|
||||
contentsOf: self.getDicdataFromLoudstxt3(identifier: first, indices: Set(consume prefixIndices))
|
||||
.filter { Self.predictionUsable[$0.rcid] }
|
||||
)
|
||||
let userDictIndices = self.prefixMatchLOUDS(identifier: "user", charIDs: charIDs, depth: depth).prefix(700)
|
||||
result.append(contentsOf: self.getDicdataFromLoudstxt3(identifier: "user", indices: Set(consume userDictIndices)))
|
||||
if learningManager.enabled {
|
||||
let memoryDictIndices = self.prefixMatchLOUDS(identifier: "memory", charIDs: charIDs).prefix(700)
|
||||
result.append(contentsOf: self.getDicdataFromLoudstxt3(identifier: "memory", indices: Set(consume memoryDictIndices)))
|
||||
result.append(contentsOf: self.learningManager.temporaryPrefixMatch(charIDs: charIDs))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func parseLoudstxt2FormattedEntry(from dataString: [some StringProtocol]) -> DicdataElement {
|
||||
@ -634,12 +610,12 @@ public final class DicdataStore {
|
||||
|
||||
/// OSのユーザ辞書からrubyに等しい語を返す。
|
||||
func getMatchOSUserDict(_ ruby: some StringProtocol) -> [DicdataElement] {
|
||||
self.osUserDict.filter {$0.ruby == ruby}
|
||||
self.dynamicUserDict.filter {$0.ruby == ruby}
|
||||
}
|
||||
|
||||
/// OSのユーザ辞書からrubyに先頭一致する語を返す。
|
||||
func getPrefixMatchOSUserDict(_ ruby: some StringProtocol) -> [DicdataElement] {
|
||||
self.osUserDict.filter {$0.ruby.hasPrefix(ruby)}
|
||||
self.dynamicUserDict.filter {$0.ruby.hasPrefix(ruby)}
|
||||
}
|
||||
|
||||
// 学習を反映する
|
||||
|
@ -54,7 +54,7 @@ struct LOUDS: Sendable {
|
||||
}
|
||||
return flatChar2nodeIndices
|
||||
}
|
||||
self.flatChar2nodeIndicesIndex = consume flatChar2nodeIndicesIndex
|
||||
self.flatChar2nodeIndicesIndex = flatChar2nodeIndicesIndex
|
||||
|
||||
var rankLarge: [UInt32] = .init(repeating: 0, count: bytes.count + 1)
|
||||
rankLarge.withUnsafeMutableBufferPointer { buffer in
|
||||
@ -62,7 +62,7 @@ struct LOUDS: Sendable {
|
||||
buffer[i + 1] = buffer[i] &+ UInt32(Self.unit &- byte.nonzeroBitCount)
|
||||
}
|
||||
}
|
||||
self.rankLarge = consume rankLarge
|
||||
self.rankLarge = rankLarge
|
||||
}
|
||||
|
||||
/// parentNodeIndex個の0を探索し、その次から1個増えるまでのIndexを返す。
|
||||
|
@ -53,7 +53,7 @@ public struct TextReplacer: Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "init(emojiDataProvider:)", message: "init() is depreacted and will be removed in v1.0. Use init(emojiDataProvider:) instead")
|
||||
@available(*, deprecated, renamed: "init(emojiDataProvider:)", message: "it be removed in AzooKeyKanaKanjiConverter v1.0")
|
||||
public init() {
|
||||
self.init {
|
||||
if #available(iOS 16.4, *) {
|
||||
|
@ -55,7 +55,7 @@ extension InputGraphProtocol {
|
||||
mutating func insert(_ node: Node, connection: InputGraphStructure.Connection = .none) -> Int {
|
||||
var nodes = self.nodes
|
||||
let index = self.structure.insert(node, nodes: &nodes, displayedTextRange: node.displayedTextRange, inputElementsRange: node.inputElementsRange, connection: connection)
|
||||
self.nodes = consume nodes
|
||||
self.nodes = nodes
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
2
install_cli.sh
Normal file
2
install_cli.sh
Normal file
@ -0,0 +1,2 @@
|
||||
swift build -c release
|
||||
cp -f .build/release/CliTool /usr/local/bin/anco
|
Reference in New Issue
Block a user