diff --git a/Cargo.lock b/Cargo.lock index 207ecc2..6e2a013 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,12 @@ version = 4 [[package]] name = "azookey-binding" version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" diff --git a/Cargo.toml b/Cargo.toml index 35430ea..7e0b25e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,6 @@ name = "azookey-binding" version = "0.1.0" edition = "2024" +[dependencies] +libc = "0.2.172" + diff --git a/azookey-swift/Package.swift b/azookey-swift/Package.swift index 4d61a87..d43584e 100644 --- a/azookey-swift/Package.swift +++ b/azookey-swift/Package.swift @@ -9,7 +9,7 @@ let package = Package( // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "azookey-swift", - type: .static, + type: .dynamic, targets: ["azookey-swift"]), .library( name: "ffi", @@ -17,7 +17,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/azookey/AzooKeyKanaKanjiConverter", branch: "66a341b7e656c2fff02c1399882e88ee067b3d31") + .package(url: "https://github.com/azookey/AzooKeyKanaKanjiConverter", branch: "66a341b7e656c2fff02c1399882e88ee067b3d31", traits: ["Zenzai"]) ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -26,9 +26,10 @@ let package = Package( .target( name: "azookey-swift", dependencies: [ - .product(name: "KanaKanjiConverterModule", package: "azookeykanakanjiconverter"), + .product(name: "KanaKanjiConverterModuleWithDefaultDictionary", package: "AzooKeyKanaKanjiConverter"), "ffi" - ] + ], + swiftSettings: [.interoperabilityMode(.Cxx)], ), .testTarget( name: "azookey-swiftTests", diff --git a/azookey-swift/Sources/azookey-swift/azookey_swift.swift b/azookey-swift/Sources/azookey-swift/azookey_swift.swift index d23ae17..5f23891 100644 --- a/azookey-swift/Sources/azookey-swift/azookey_swift.swift +++ b/azookey-swift/Sources/azookey-swift/azookey_swift.swift @@ -1,7 +1,124 @@ +// returns first candidate + import Foundation + +import KanaKanjiConverterModuleWithDefaultDictionary + import ffi -@_silgen_name("HelloSwift") -@MainActor public func hello_swift() { - print("hello_swift") +public class ComposingTextWrapper { + var value: ComposingText + + init(value: ComposingText) { + self.value = value + } +} +@_silgen_name("CreateKanaKanjiConverter") +@MainActor public func create_kana_kanji_converter() -> UnsafeMutablePointer { + let converter = KanaKanjiConverter() + return Unmanaged.passRetained(converter).toOpaque().assumingMemoryBound( + to: KanaKanjiConverter.self) +} +@_silgen_name("DestroyKanaKanjiConverter") +@MainActor public func destroy_kana_kanji_converter( + _ converter: UnsafeMutablePointer +) { + Unmanaged.fromOpaque(converter).release() +} +@_silgen_name("CreateComposingText") +@MainActor public func create_composing_text() -> UnsafeMutablePointer { + let c = ComposingTextWrapper(value: ComposingText()) + return Unmanaged.passRetained(c).toOpaque().assumingMemoryBound(to: ComposingTextWrapper.self) +} +@_silgen_name("DestroyComposingText") +@MainActor public func destroy_composing_text( + _ composingText: UnsafeMutablePointer +) { + Unmanaged.fromOpaque(composingText).release() +} +@_silgen_name("KanaKanjiConverter_RequestCandidates") +@MainActor public func kana_kanji_converter_request_candidates( + _ converter: UnsafeMutablePointer, + _ composingText: UnsafeMutablePointer, + _ lengthPtr: UnsafeMutablePointer, + _ context: UnsafePointer +) -> UnsafeMutablePointer> { + let c = Unmanaged.fromOpaque(converter).takeUnretainedValue() + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + + let options = ConvertRequestOptions.withDefaultDictionary( + requireJapanesePrediction: true, + requireEnglishPrediction: false, + keyboardLanguage: .ja_JP, + learningType: .nothing, + memoryDirectoryURL: URL(filePath: "./"), + sharedContainerURL: URL(filePath: "./"), + zenzaiMode: .on( + weight: URL(filePath: "./ggml-model-Q5_K_M.gguf"), inferenceLimit: 1, + requestRichCandidates: true, personalizationMode: nil, + versionDependentMode: .v3(.init(profile: "", leftSideContext: String(cString: context))) + ), + preloadDictionary: true, + metadata: .init(versionString: "rs-azookey-binding") + ) + + let candidates: ConversionResult = c.requestCandidates(ct.value, options: options) + + var result: [FFICandidate] = [] + + for i in 0..>.allocate( + capacity: result.count) + for i in 0...allocate(capacity: 1) + pointer[i].pointee = result[i] + } + lengthPtr.pointee = result.count + return pointer +} +@_silgen_name("KanaKanjiConverter_StopComposition") +@MainActor public func kana_kanji_converter_stop_composition( + _ converter: UnsafeMutablePointer, + _ composingText: UnsafeMutablePointer +) { + let c = Unmanaged.fromOpaque(converter).takeUnretainedValue() + c.stopComposition() +} +@_silgen_name("ComposingText_InsertAtCursorPosition") +@MainActor public func composing_text_insert_at_cursor_position( + _ composingText: UnsafeMutablePointer, + _ text: UnsafePointer, +) { + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + let str = String(cString: text) + ct.value.insertAtCursorPosition(str, inputStyle: .roman2kana) +} +@_silgen_name("ComposingText_DeleteForwardFromCursorPosition") +@MainActor public func composing_text_delete_forward_from_cursor_position( + _ composingText: UnsafeMutablePointer, + _ count: Int32 +) { + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + ct.value.deleteForwardFromCursorPosition(count: Int(count)) +} +@_silgen_name("ComposingText_DeleteBackwardFromCursorPosition") +@MainActor public func composing_text_delete_backward_from_cursor_position( + _ composingText: UnsafeMutablePointer, + _ count: Int32 +) { + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + ct.value.deleteBackwardFromCursorPosition(count: Int(count)) } diff --git a/azookey-swift/Sources/ffi/include/ffi.h b/azookey-swift/Sources/ffi/include/ffi.h index cb1c76e..b636a59 100644 --- a/azookey-swift/Sources/ffi/include/ffi.h +++ b/azookey-swift/Sources/ffi/include/ffi.h @@ -5,3 +5,7 @@ #endif +struct FFICandidate { + char *text; + int correspondingCount; +}; \ No newline at end of file diff --git a/azookey-swift/llama.lib b/azookey-swift/llama.lib new file mode 100644 index 0000000..5e6a60a Binary files /dev/null and b/azookey-swift/llama.lib differ diff --git a/build.rs b/build.rs index a0ce791..24d0804 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,7 @@ +use std::env; + fn main() { - let project_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let project_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); // Build the Swift library let swift_build_command = format!( @@ -18,16 +20,21 @@ fn main() { ); } - // rename the output file to azookey-swift.lib from libazookey-swift.a - let output_path = format!( - "{}/azookey-swift/.build/release/libazookey-swift.a", + // move azookey-swift.dll to the target directory + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let profile = env::var("PROFILE").unwrap(); + let target_dir = format!("{}/target/{}", crate_dir, profile); + let swift_lib_path = format!( + "{}/azookey-swift/.build/release/azookey-swift.dll", project_dir ); - let new_output_path = format!( - "{}/azookey-swift/.build/release/azookey-swift.lib", - project_dir - ); - std::fs::rename(&output_path, &new_output_path).expect("Failed to rename file"); + let target_lib_path = format!("{}/azookey-swift.dll", target_dir); + std::fs::copy(&swift_lib_path, &target_lib_path).unwrap_or_else(|_| { + panic!( + "Failed to copy azookey-swift.dll to target directory: {}", + target_lib_path + ) + }); } else { // non-windows let output = std::process::Command::new("sh") @@ -50,7 +57,7 @@ fn main() { ); println!( "cargo:rustc-link-search={}", - std::env::var("SWIFT_LIB_DIR").unwrap() + env::var("SWIFT_LIB_DIR").unwrap() ); - println!("cargo:rustc-link-lib=static=azookey-swift"); + println!("cargo:rustc-link-lib=azookey-swift"); } diff --git a/ggml-base.dll b/ggml-base.dll new file mode 100644 index 0000000..4ef4026 Binary files /dev/null and b/ggml-base.dll differ diff --git a/ggml-cpu.dll b/ggml-cpu.dll new file mode 100644 index 0000000..c451913 Binary files /dev/null and b/ggml-cpu.dll differ diff --git a/ggml-model-Q5_K_M.gguf b/ggml-model-Q5_K_M.gguf new file mode 100644 index 0000000..6fc8fc1 Binary files /dev/null and b/ggml-model-Q5_K_M.gguf differ diff --git a/ggml-vulkan.dll b/ggml-vulkan.dll new file mode 100644 index 0000000..5a57ea6 Binary files /dev/null and b/ggml-vulkan.dll differ diff --git a/ggml.dll b/ggml.dll new file mode 100644 index 0000000..c6b64a0 Binary files /dev/null and b/ggml.dll differ diff --git a/llama.dll b/llama.dll new file mode 100644 index 0000000..03b657f Binary files /dev/null and b/llama.dll differ diff --git a/src/lib.rs b/src/lib.rs index 2448040..9e46343 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,257 @@ -unsafe extern "C" { - pub fn HelloSwift(); +use std::os::raw::c_void; + +use libc::c_int; + +/* +@_silgen_name("CreateKanaKanjiConverter") +@MainActor public func create_kana_kanji_converter() -> UnsafeMutablePointer { + let converter = KanaKanjiConverter() + return Unmanaged.passRetained(converter).toOpaque().assumingMemoryBound( + to: KanaKanjiConverter.self) +} +@_silgen_name("DestroyKanaKanjiConverter") +@MainActor public func destroy_kana_kanji_converter( + _ converter: UnsafeMutablePointer +) { + Unmanaged.fromOpaque(converter).release() +} +@_silgen_name("CreateComposingText") +@MainActor public func create_composing_text() -> UnsafeMutablePointer { + let c = ComposingTextWrapper(value: ComposingText()) + return Unmanaged.passRetained(c).toOpaque().assumingMemoryBound(to: ComposingTextWrapper.self) +} +@_silgen_name("DestroyComposingText") +@MainActor public func destroy_composing_text( + _ composingText: UnsafeMutablePointer +) { + Unmanaged.fromOpaque(composingText).release() +} +@_silgen_name("KanaKanjiConverter_RequestCandidates") +@MainActor public func kana_kanji_converter_request_candidates( + _ converter: UnsafeMutablePointer, + _ composingText: UnsafeMutablePointer, + _ context: UnsafePointer +) -> UnsafeMutablePointer> { + let c = Unmanaged.fromOpaque(converter).takeUnretainedValue() + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + + let options = ConvertRequestOptions.withDefaultDictionary( + requireJapanesePrediction: true, + requireEnglishPrediction: false, + keyboardLanguage: .ja_JP, + learningType: .nothing, + memoryDirectoryURL: URL(filePath: "./"), + sharedContainerURL: URL(filePath: "./"), + zenzaiMode: .on( + weight: URL(filePath: "./ggml-model-Q5_K_M.gguf"), inferenceLimit: 1, + requestRichCandidates: true, personalizationMode: nil, + versionDependentMode: .v3(.init(profile: "", leftSideContext: String(cString: context))) + ), + preloadDictionary: true, + metadata: .init(versionString: "rs-azookey-binding") + ) + + let candidates: ConversionResult = c.requestCandidates(ct.value, options: options) + + var result: [FFICandidate] = [] + + for i in 0..>.allocate(capacity: result.count) + for i in 0...allocate(capacity: 1) + pointer[i].pointee = result[i] + } + return pointer +} +@_silgen_name("KanaKanjiConverter_StopComposition") +@MainActor public func kana_kanji_converter_stop_composition( + _ converter: UnsafeMutablePointer, + _ composingText: UnsafeMutablePointer +) { + let c = Unmanaged.fromOpaque(converter).takeUnretainedValue() + c.stopComposition() +} +@_silgen_name("ComposingText_InsertAtCursorPosition") +@MainActor public func composing_text_insert_at_cursor_position( + _ composingText: UnsafeMutablePointer, + _ text: UnsafePointer, +) { + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + let str = String(cString: text) + ct.value.insertAtCursorPosition(str, inputStyle: .roman2kana) +} +@_silgen_name("ComposingText_DeleteForwardFromCursorPosition") +@MainActor public func composing_text_delete_forward_from_cursor_position( + _ composingText: UnsafeMutablePointer, + _ count: Int32 +) { + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + ct.value.deleteForwardFromCursorPosition(count: Int(count)) +} +@_silgen_name("ComposingText_DeleteBackwardFromCursorPosition") +@MainActor public func composing_text_delete_backward_from_cursor_position( + _ composingText: UnsafeMutablePointer, + _ count: Int32 +) { + let ct = Unmanaged.fromOpaque(composingText).takeUnretainedValue() + ct.value.deleteBackwardFromCursorPosition(count: Int(count)) +} + +*/ +unsafe extern "C" { + pub fn CreateKanaKanjiConverter() -> *mut c_void; + pub fn DestroyKanaKanjiConverter(converter: *mut c_void); + pub fn CreateComposingText() -> *mut c_void; + pub fn DestroyComposingText(composingText: *mut c_void); + pub fn KanaKanjiConverter_RequestCandidates( + converter: *mut c_void, + composingText: *mut c_void, + lengthPtr: *mut c_int, + context: *const libc::c_char, + ) -> *mut *mut FFICandidate; + pub fn KanaKanjiConverter_StopComposition(converter: *mut c_void); + pub fn ComposingText_InsertAtCursorPosition( + composingText: *mut c_void, + text: *const libc::c_char, + ); + pub fn ComposingText_DeleteForwardFromCursorPosition( + composingText: *mut c_void, + count: libc::c_int, + ); + pub fn ComposingText_DeleteBackwardFromCursorPosition( + composingText: *mut c_void, + count: libc::c_int, + ); +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +struct FFICandidate { + text: *mut libc::c_char, + corresponding_count: libc::c_int, +} + +#[derive(Debug, Clone)] +pub struct Candidate { + pub text: String, + pub corresponding_count: i32, +} + +pub struct KanaKanjiConverter { + pub converter: *mut c_void, +} + +pub struct ComposingText { + pub composing_text: *mut c_void, +} + +impl KanaKanjiConverter { + pub fn new() -> Self { + unsafe { + let converter = CreateKanaKanjiConverter(); + if converter.is_null() { + panic!("Failed to create KanaKanjiConverter"); + } + Self { converter } + } + } + + pub fn request_candidates( + &self, + composing_text: &ComposingText, + context: &str, + ) -> Vec { + unsafe { + let c_str = std::ffi::CString::new(context).expect("CString::new failed"); + let mut length: c_int = 0; + let candidates_ptr = KanaKanjiConverter_RequestCandidates( + self.converter, + composing_text.composing_text, + &mut length, + c_str.as_ptr(), + ); + if candidates_ptr.is_null() { + panic!("Failed to get candidates"); + } + + let mut candidates = Vec::new(); + for i in 0..length { + let candidate = *(*candidates_ptr.offset(i as isize)); + let text = std::ffi::CStr::from_ptr(candidate.text) + .to_string_lossy() + .into_owned(); + let corresponding_count = candidate.corresponding_count; + candidates.push(Candidate { + text, + corresponding_count, + }); + } + candidates + } + } + + pub fn stop_composition(&self) { + unsafe { + KanaKanjiConverter_StopComposition(self.converter); + } + } +} + +impl Drop for KanaKanjiConverter { + fn drop(&mut self) { + unsafe { + DestroyKanaKanjiConverter(self.converter); + } + } +} + +impl ComposingText { + pub fn new() -> Self { + unsafe { + let composing_text = CreateComposingText(); + if composing_text.is_null() { + panic!("Failed to create ComposingText"); + } + Self { composing_text } + } + } + + pub fn insert_at_cursor_position(&self, text: &str) { + unsafe { + let c_str = std::ffi::CString::new(text).expect("CString::new failed"); + ComposingText_InsertAtCursorPosition(self.composing_text, c_str.as_ptr()); + } + } + + pub fn delete_forward_from_cursor_position(&self, count: i32) { + unsafe { + ComposingText_DeleteForwardFromCursorPosition(self.composing_text, count); + } + } + + pub fn delete_backward_from_cursor_position(&self, count: i32) { + unsafe { + ComposingText_DeleteBackwardFromCursorPosition(self.composing_text, count); + } + } +} + +impl Drop for ComposingText { + fn drop(&mut self) { + unsafe { + DestroyComposingText(self.composing_text); + } + } } diff --git a/src/main.rs b/src/main.rs index d23b6a8..c5fab11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,13 @@ +use azookey_binding::{ComposingText, KanaKanjiConverter}; + fn main() { - unsafe { azookey_binding::HelloSwift() }; + let input = "seido"; + let kana_kanji_converter = KanaKanjiConverter::new(); + + let composing_text = ComposingText::new(); + composing_text.insert_at_cursor_position(input); + + let result = kana_kanji_converter.request_candidates(&composing_text, ""); + + println!("{} -> {:?}", input, result); }