Vision defaulted to the first listed language and turned Chinese into
Latin garbage (你好世界 became 177WR). automaticallyDetectsLanguage fixes
it; the smoke test now covers Chinese so it cannot regress.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4-flash-0731
42 lines
1.5 KiB
Swift
42 lines
1.5 KiB
Swift
import AppKit
|
|
import Vision
|
|
|
|
final class OCRService {
|
|
static let shared = OCRService()
|
|
|
|
private let queue = DispatchQueue(label: "dev.tzgyn.kapture.ocr", qos: .userInitiated)
|
|
|
|
func recognize(_ cgImage: CGImage, completion: @escaping (String) -> Void) {
|
|
queue.async {
|
|
let request = VNRecognizeTextRequest()
|
|
request.recognitionLevel = .accurate
|
|
request.usesLanguageCorrection = true
|
|
request.recognitionLanguages = ["en-US", "zh-Hans", "zh-Hant", "ja-JP", "ko-KR"]
|
|
// Without this, Vision defaults to the first listed language and
|
|
// renders CJK as Latin garbage (verified: 你好世界 -> "177WR").
|
|
if #available(macOS 13.0, *) {
|
|
request.automaticallyDetectsLanguage = true
|
|
}
|
|
|
|
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
|
|
do {
|
|
try handler.perform([request])
|
|
} catch {
|
|
completion("")
|
|
return
|
|
}
|
|
|
|
let observations = request.results ?? []
|
|
let lines = observations
|
|
.sorted { a, b in
|
|
let ay = a.boundingBox.midY
|
|
let by = b.boundingBox.midY
|
|
if abs(ay - by) > 0.02 { return ay > by }
|
|
return a.boundingBox.minX < b.boundingBox.minX
|
|
}
|
|
.compactMap { $0.topCandidates(1).first?.string }
|
|
completion(lines.joined(separator: "\n"))
|
|
}
|
|
}
|
|
}
|