Menggunakan iOS (Swift)
RustO! menyediakan dukungan kelas satu untuk iOS dan macOS melalui XCFramework universal dan pembungkus Swift yang ramah pengembang. Mendukung arsitektur Apple Silicon (arm64), Intel (x86_64), dan Simulator iOS tanpa memerlukan rantai alat kompilasi C++.
Instalasi
Anda dapat mengintegrasikan RustO! ke dalam proyek iOS Anda menggunakan Swift Package Manager (SPM) atau CocoaPods.
Metode A: Swift Package Manager (Direkomendasikan)
Melalui Xcode
- Buka proyek Anda di Xcode.
- Buka menu File > Add Package Dependencies...
- Masukkan URL repositori:
https://github.com/byrizki/rusto-rs.git - Pilih produk pustaka
RustOdan tentukan aturan versi paket yang diinginkan (misalnya0.3.2).
Melalui Package.swift
Jika Anda membuat paket modular atau pustaka Swift:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyScannerApp",
platforms: [
.iOS(.v13),
.macOS(.v11)
],
dependencies: [
.package(url: "https://github.com/byrizki/rusto-rs.git", from: "0.3.2")
],
targets: [
.target(
name: "MyScannerApp",
dependencies: [
.product(name: "RustO", package: "rusto-rs")
]
)
]
)
Paket ini secara otomatis menyediakan modul C FFI level rendah (RustOFFI) dan API Swift level tinggi (RustO).
Metode B: CocoaPods
Pada file Podfile proyek Anda:
target 'YourApp' do
use_frameworks!
# XCFramework runtime mesin OCR inti
pod 'RustO', '~> 0.3.2'
# Paket bundel model bawaan (~6 MB total)
pod 'RustO-Models-PPOCRv6-Tiny', '~> 0.3.2'
# Atau varian tingkatan model yang lebih besar:
# pod 'RustO-Models-PPOCRv6-Small'
# pod 'RustO-Models-PPOCRv5-Mobile'
end
Kemudian jalankan:
pod install
1. Konfigurasi Izin (Info.plist)
Jika mengambil gambar dari kamera atau galeri foto, tambahkan deklarasi deskripsi izin pada file Info.plist aplikasi Anda:
<key>NSCameraUsageDescription</key>
<string>Izinkan akses kamera untuk memindai dokumen dan mengekstrak teks.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Izinkan akses galeri foto untuk memilih gambar dokumen untuk dipindai.</string>
2. Inisialisasi Mesin & Resolusi Model
Opsi 1: Resolusi Model Bundel Otomatis
Jika menggunakan pod model resmi (seperti RustO-Models-PPOCRv6-Tiny), RustO! secara otomatis menemukan berkas model di dalam bundel framework:
import RustO
do {
// Secara otomatis menemukan berkas det.rten, rec.rten, dan dict.txt bawaan PP-OCRv6
let ocr = try RustO.initialize()
} catch {
print("Inisialisasi OCR gagal: \(error)")
}
Opsi 2: Menggunakan Bundel Kustom atau Jalur File Sistem
Jika Anda ingin menyertakan model kustom di dalam bundel utama aplikasi atau mengunduhnya ke folder penyimpanan lokal:
import Foundation
import RustO
func createCustomEngine() throws -> RustO {
guard let detPath = Bundle.main.path(forResource: "custom_det", ofType: "rten"),
let recPath = Bundle.main.path(forResource: "custom_rec", ofType: "rten"),
let dictPath = Bundle.main.path(forResource: "custom_dict", ofType: "txt") else {
throw NSError(domain: "RustO", code: -1, userInfo: [NSLocalizedDescriptionKey: "Berkas model tidak ditemukan di bundel"])
}
let config = InitializeConfig(
template: "ppv6",
detection: DetectionConfig(modelPath: detPath),
recognition: RecognitionConfig(
modelPath: recPath,
dictPath: dictPath
)
)
return try RustO.initialize(config: config)
}
3. Sumber Masukan Gambar
ImageSource mendukung jalur file lokal, URL, maupun buffer byte gambar di memori:
import UIKit
import RustO
// 1. Jalur berkas lokal atau file URL
let fileSource = ImageSource.uri("/path/to/invoice.png")
// 2. UIImage dikonversi menjadi data JPEG atau PNG
guard let image = UIImage(named: "receipt"),
let imageData = image.jpegData(compressionQuality: 0.95) else { return }
let memorySource = ImageSource.bytes(imageData)
4. Opsi Eksekusi (OcrRunOptions)
Sesuaikan parameter deteksi per permintaan pemindaian:
let options = OcrRunOptions(
output: .lines, // .lines, .words, atau .spatial
lineYThreshold: 0.5, // Ambang pengelompokan baris (0.0 - 1.0)
wordXThreshold: 0.4, // Ambang pemisah kolom kata (0.0 - 1.0)
textScore: 0.50, // Batas minimum skor keyakinan
maxSideLen: 1600, // Batas resolusi sisi gambar terpanjang
detection: DetectionRunOptions(
limitSideLen: 960,
limitType: "max"
),
postprocess: PostprocessRunOptions(
useDilation: true, // Dilasi morfologis untuk struk dengan cetakan tipis
unclipRatio: 1.8 // Rasio ekspansi poligon batas teks
)
)
5. Deteksi Terstruktur vs Rekonstruksi Spasial
Deteksi Baris Terstruktur
Mengembalikan koordinat kotak pembatas, titik poligon, dan skor keyakinan:
let result = try ocr.detectText(memorySource, options: OcrRunOptions(output: .lines))
if case .structured(let items) = result {
for line in items {
print("[\(String(format: "%.2f", line.score))] \(line.text)")
print(" Frame: X=\(line.frame.left), Y=\(line.frame.top), W=\(line.frame.width), H=\(line.frame.height)")
print(" Titik Poligon: \(line.boxPoints)")
}
}
Rekonstruksi Tata Letak Spasial
Menyusun ulang geometri teks multi-kolom dokumen menjadi teks rapi terformat:
let result = try ocr.detectText(memorySource, options: OcrRunOptions(output: .spatial))
if case .spatial(let layoutText) = result {
print("--- Tata Letak Spasial ---\n\(layoutText)")
}
6. Contoh Lengkap Pemindai Dokumen di SwiftUI
Selalu jalankan inferensi OCR pada antrean latar belakang (background thread) atau Swift Concurrency (Task.detached) agar tidak mengunci antarmuka utama pengguna:
import SwiftUI
import PhotosUI
import RustO
struct DocumentScannerView: View {
@State private var selectedItem: PhotosPickerItem?
@State private var previewImage: UIImage?
@State private var recognizedText: String = ""
@State private var isProcessing: Bool = false
@State private var ocrEngine: RustO?
var body: some View {
NavigationStack {
VStack(spacing: 16) {
if let previewImage {
Image(uiImage: previewImage)
.resizable()
.scaledToFit()
.frame(maxHeight: 240)
.cornerRadius(12)
}
PhotosPicker(selection: $selectedItem, matching: .images) {
Label("Pilih Dokumen", systemImage: "doc.text.viewfinder")
.font(.headline)
.frame(maxWidth: .infinity)
.padding()
.background(Color.orange)
.foregroundColor(.white)
.cornerRadius(10)
}
.onChange(of: selectedItem) { _, newItem in
Task {
guard let data = try? await newItem?.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) else { return }
previewImage = uiImage
await runOcr(data: data)
}
}
if isProcessing {
ProgressView("Mengekstrak teks dengan RustO!...")
.padding()
}
ScrollView {
Text(recognizedText.isEmpty ? "Belum ada teks yang dipindai." : recognizedText)
.font(.system(.body, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(8)
}
}
.padding()
.navigationTitle("Pemindai Dokumen RustO!")
.task {
ocrEngine = try? RustO.initialize()
}
}
}
private func runOcr(data: Data) async {
guard let engine = ocrEngine else { return }
isProcessing = true
recognizedText = ""
let resultText: String = await Task.detached(priority: .userInitiated) {
do {
let options = OcrRunOptions(output: .spatial, lineYThreshold: 0.5, wordXThreshold: 0.4)
let result = try engine.detectText(.bytes(data), options: options)
if case .spatial(let text) = result {
return text
}
return "Tidak ada teks yang terdeteksi."
} catch {
return "Kesalahan OCR: \(error.localizedDescription)"
}
}.value
recognizedText = resultText
isProcessing = false
}
}