SOURCE RECORD · S12

S12 · 轻扫PDF / Scanly PDF

On-device Vision OCR

公开摘录 · 截取原件于 2026-09-12 / Captured 2026-09-12

保留原行号。个人/账户信息如有删除,已用 REDACTED 标识。原文件 SHA-256 只用于识别截取时的本机原文件;不能证明编写或提交时间,也不等于脱敏下载文件的哈希。

Original line numbers are retained. Redactions are explicitly marked. The original full-file hash identifies a captured local file, not its creation/submission time or the redacted download. Public-file hashes are listed separately. This is selected source, not a whole-repository or binary absence finding.

Project key: scan
Original relative path: ScanlyPDF/Services/OCRService.swift
Captured: 2026-09-12T18:57:15+08:00
Original full-file SHA256: 4466d6c697543b42c508f5baf27ba70a6c722b892e0826047c2519b32f9f10c3
Evidence level: local source excerpt
Build correspondence: not yet established with an Apple-received binary.
Line numbers refer to the captured original file. Gaps are explicitly marked. No source code was changed.

Original lines 1 to 31

0001  import PDFKit
0002  import UIKit
0003  import Vision
0004  
0005  actor OCRService {
0006      struct PageResult: Identifiable, Sendable { let id: Int; let text: String }
0007      struct LayoutPageResult: Identifiable, Sendable { let id: Int; let observations: [TextObservation] }
0008      struct TextObservation: Identifiable, Sendable {
0009          let id = UUID()
0010          let text: String
0011          /// Top-left normalized coordinates, matching the paper editor model.
0012          let rect: CGRect
0013      }
0014  
0015      func recognize(image: UIImage, languages: [String] = ["zh-Hans", "zh-Hant", "en-US", "ja-JP", "ko-KR", "es-ES", "fr-FR", "de-DE"]) async throws -> String {
0016          guard let cgImage = image.cgImage else { throw AppError.invalidFile }
0017          return try await withCheckedThrowingContinuation { continuation in
0018              let request = VNRecognizeTextRequest { request, error in
0019                  if let error { continuation.resume(throwing: error); return }
0020                  let text = (request.results as? [VNRecognizedTextObservation])?.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n") ?? ""
0021                  continuation.resume(returning: text)
0022              }
0023              request.recognitionLevel = .accurate
0024              request.usesLanguageCorrection = true
0025              let supported = (try? request.supportedRecognitionLanguages()) ?? []
0026              request.recognitionLanguages = languages.filter(supported.contains)
0027              if request.recognitionLanguages.isEmpty { request.recognitionLanguages = Array(supported.prefix(3)) }
0028              do { try VNImageRequestHandler(cgImage: cgImage).perform([request]) }
0029              catch { continuation.resume(throwing: error) }
0030          }
0031      }