├── .gitignore ├── README.md ├── Scanverter.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── Scanverter ├── Helpers │ ├── Constants.swift │ ├── DataManager.swift │ ├── ImageResizer.swift │ ├── PDFGenerator.swift │ ├── PhotoCaptureHandler.swift │ ├── SwiftUIExtensions.swift │ ├── UIKitExtensions.swift │ └── VNRectangleFeature.swift ├── Models │ ├── AlertError.swift │ ├── Detection.swift │ ├── DocFile.swift │ ├── EditTool.swift │ ├── Folder.swift │ ├── Language.swift │ ├── Option.swift │ ├── PDF.swift │ ├── Photo.swift │ ├── ScannedDoc.swift │ └── Translation.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Services │ ├── BiometricAuthentication.swift │ ├── CameraService.swift │ ├── ServiceLocator.swift │ └── TranslationService.swift ├── SupportingFiles │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── addPageButton.imageset │ │ │ ├── Contents.json │ │ │ ├── addPageButton.png │ │ │ ├── addPageButton@2x.png │ │ │ └── addPageButton@3x.png │ │ ├── add_button_filled.imageset │ │ │ ├── Contents.json │ │ │ ├── add_button_filled.png │ │ │ └── add_button_filled@2x.png │ │ ├── add_folder_button.imageset │ │ │ ├── Contents.json │ │ │ ├── add_folder_button.png │ │ │ └── add_folder_button@2x.png │ │ ├── captureButton.imageset │ │ │ ├── Contents.json │ │ │ ├── captureButton.png │ │ │ ├── captureButton@2x.png │ │ │ └── captureButton@3x.png │ │ ├── cropButton.imageset │ │ │ ├── Contents.json │ │ │ ├── cropButton.png │ │ │ ├── cropButton@2x.png │ │ │ └── cropButton@3x.png │ │ ├── default.imageset │ │ │ ├── Contents.json │ │ │ └── default.png │ │ ├── deletePageButton.imageset │ │ │ ├── Contents.json │ │ │ ├── deletePageButton.png │ │ │ ├── deletePageButton@2x.png │ │ │ └── deletePageButton@3x.png │ │ ├── error.imageset │ │ │ ├── Contents.json │ │ │ └── error.png │ │ ├── filterButton.imageset │ │ │ ├── Contents.json │ │ │ ├── filterButton.png │ │ │ ├── filterButton@2x.png │ │ │ └── filterButton@3x.png │ │ ├── folder_icon.imageset │ │ │ ├── Contents.json │ │ │ ├── folder_icon.png │ │ │ ├── folder_icon@2x.png │ │ │ └── folder_icon@3x.png │ │ ├── folder_image.imageset │ │ │ ├── Contents.json │ │ │ ├── folder_image.png │ │ │ ├── folder_image@2x.png │ │ │ └── folder_image@3x.png │ │ ├── imageSave.imageset │ │ │ ├── Contents.json │ │ │ ├── imageSave.png │ │ │ ├── imageSave@2x.png │ │ │ └── imageSave@3x.png │ │ ├── lock_icon.imageset │ │ │ ├── Contents.json │ │ │ ├── lock_icon.png │ │ │ └── lock_icon@2x.png │ │ ├── more_options.imageset │ │ │ ├── Contents.json │ │ │ └── more_options.png │ │ ├── ocrButton.imageset │ │ │ ├── Contents.json │ │ │ ├── ocr_button.png │ │ │ ├── ocr_button@2x.png │ │ │ └── ocr_button@3x.png │ │ ├── pdfSave.imageset │ │ │ ├── Contents.json │ │ │ ├── pdfSave.png │ │ │ ├── pdfSave@2x.png │ │ │ └── pdfSave@3x.png │ │ ├── pdf_icon.imageset │ │ │ ├── Contents.json │ │ │ ├── pdf_icon.png │ │ │ ├── pdf_icon@2x.png │ │ │ └── pdf_icon@3x.png │ │ ├── saveButton.imageset │ │ │ ├── Contents.json │ │ │ ├── saveButton.png │ │ │ ├── saveButton@2x.png │ │ │ └── saveButton@3x.png │ │ └── success.imageset │ │ │ ├── Contents.json │ │ │ └── sucess.png │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── imageWithText.imageset │ │ │ ├── Contents.json │ │ │ └── imageWithText.jpg │ │ └── slideWithText.imageset │ │ │ ├── Contents.json │ │ │ └── slideWithText.png │ └── Info.plist ├── System │ ├── AppContainer.swift │ └── ScanverterApp.swift ├── Views │ ├── Alerts │ │ ├── CreateFolderView.swift │ │ └── CustomAlert.swift │ ├── Camera │ │ ├── CameraPreview.swift │ │ ├── CameraView.swift │ │ └── ViewModels │ │ │ └── CameraViewModel.swift │ ├── Collections │ │ ├── Cells │ │ │ ├── EditImageCell.swift │ │ │ ├── EditToolCell.swift │ │ │ ├── FileGridCell.swift │ │ │ ├── FileListCell.swift │ │ │ ├── GridCell.swift │ │ │ └── ListCell.swift │ │ ├── Custom │ │ │ ├── DataSource │ │ │ │ └── PhotoCollectionDataSource.swift │ │ │ └── Presentation │ │ │ │ ├── EditorView.swift │ │ │ │ ├── OCRResultsView.swift │ │ │ │ └── PhotoCollectionView.swift │ │ ├── DataSource │ │ │ ├── EditCellDataSource.swift │ │ │ ├── EditToolCellDataSource.swift │ │ │ ├── FileCellDataSource.swift │ │ │ └── FolderCellDataSource.swift │ │ └── Generic │ │ │ └── CollectionView.swift │ ├── CustomViews │ │ ├── ActivityIndicator.swift │ │ ├── CustomProgressConfig.swift │ │ ├── CustomProgressView.swift │ │ ├── CustomTextView.swift │ │ ├── ImagePicker.swift │ │ ├── PDFViewer.swift │ │ ├── PageView.swift │ │ ├── Pager.swift │ │ ├── SearchBar.swift │ │ └── TextScannerView.swift │ ├── Main │ │ ├── DataSources │ │ │ ├── DocsDataSource.swift │ │ │ ├── FoldersDataSource.swift │ │ │ ├── SearchDataSource.swift │ │ │ ├── SettingsDataSource.swift │ │ │ └── TextRecognizer.swift │ │ └── Screens │ │ │ ├── CurrentScreen.swift │ │ │ ├── FolderDetailScreen.swift │ │ │ ├── FoldersScreen.swift │ │ │ ├── MainTabBarView.swift │ │ │ ├── SettingsScreen.swift │ │ │ └── SettingsScreenTempleate.swift │ ├── Modals │ │ ├── ModalCamera.swift │ │ ├── ModalScreen.swift │ │ ├── ModalSearchScreen.swift │ │ └── TextRecognizerScreen.swift │ ├── Navigation │ │ ├── NavigationStack.swift │ │ └── ScreenDetails.swift │ ├── Protocols │ │ └── ContainerView.swift │ ├── Settings │ │ └── SettingsViews.swift │ └── Tabs │ │ ├── EditorTabBar.swift │ │ ├── ModalScreenShowTabButton.swift │ │ ├── Tab.swift │ │ ├── TabBar.swift │ │ ├── TabBarItem.swift │ │ └── ViewModels │ │ └── EditorTabBarDataSource.swift └── tessdata │ ├── deu.lstm │ ├── deu.lstm-number-dawg │ ├── deu.lstm-punc-dawg │ ├── deu.lstm-recoder │ ├── deu.lstm-unicharset │ ├── deu.lstm-word-dawg │ ├── deu.traineddata │ ├── deu.version │ ├── eng.lstm │ ├── eng.lstm-number-dawg │ ├── eng.lstm-punc-dawg │ ├── eng.lstm-recoder │ ├── eng.lstm-unicharset │ ├── eng.lstm-word-dawg │ ├── eng.traineddata │ ├── eng.version │ ├── fra.lstm │ ├── fra.lstm-number-dawg │ ├── fra.lstm-punc-dawg │ ├── fra.lstm-recoder │ ├── fra.lstm-unicharset │ ├── fra.lstm-word-dawg │ ├── fra.traineddata │ ├── fra.version │ ├── ita.lstm │ ├── ita.lstm-number-dawg │ ├── ita.lstm-punc-dawg │ ├── ita.lstm-recoder │ ├── ita.lstm-unicharset │ ├── ita.lstm-word-dawg │ ├── ita.traineddata │ ├── ita.version │ ├── rus.lstm │ ├── rus.lstm-number-dawg │ ├── rus.lstm-punc-dawg │ ├── rus.lstm-recoder │ ├── rus.lstm-unicharset │ ├── rus.lstm-word-dawg │ ├── rus.traineddata │ ├── rus.version │ ├── spa.lstm │ ├── spa.lstm-number-dawg │ ├── spa.lstm-punc-dawg │ ├── spa.lstm-recoder │ ├── spa.lstm-unicharset │ ├── spa.lstm-word-dawg │ ├── spa.traineddata │ └── spa.version └── screenshots ├── biomethric_on_folder_locking.png ├── camera_screen.png ├── faceid_permission_request.png ├── folder_details.png ├── folders_grid.png ├── main_screen_with_folders.png ├── recognition_result.png ├── settings_stubs.png ├── tesseract_recognition.png ├── visionkit_pdf_view.png ├── visionkit_result.png └── visionkit_scan.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/xcode,swift,swiftpm,macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift,swiftpm,macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### Swift ### 34 | # Xcode 35 | # 36 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 37 | 38 | ## User settings 39 | xcuserdata/ 40 | 41 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 42 | *.xcscmblueprint 43 | *.xccheckout 44 | 45 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 46 | build/ 47 | DerivedData/ 48 | *.moved-aside 49 | *.pbxuser 50 | !default.pbxuser 51 | *.mode1v3 52 | !default.mode1v3 53 | *.mode2v3 54 | !default.mode2v3 55 | *.perspectivev3 56 | !default.perspectivev3 57 | 58 | ## Obj-C/Swift specific 59 | *.hmap 60 | 61 | ## App packaging 62 | *.ipa 63 | *.dSYM.zip 64 | *.dSYM 65 | 66 | ## Playgrounds 67 | timeline.xctimeline 68 | playground.xcworkspace 69 | 70 | # Swift Package Manager 71 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 72 | # Packages/ 73 | # Package.pins 74 | # Package.resolved 75 | # *.xcodeproj 76 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 77 | # hence it is not needed unless you have added a package configuration file to your project 78 | # .swiftpm 79 | 80 | .build/ 81 | 82 | # CocoaPods 83 | # We recommend against adding the Pods directory to your .gitignore. However 84 | # you should judge for yourself, the pros and cons are mentioned at: 85 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 86 | # Pods/ 87 | # Add this line if you want to avoid checking in source code from the Xcode workspace 88 | # *.xcworkspace 89 | 90 | # Carthage 91 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 92 | # Carthage/Checkouts 93 | 94 | Carthage/Build/ 95 | 96 | # Accio dependency management 97 | Dependencies/ 98 | .accio/ 99 | 100 | # fastlane 101 | # It is recommended to not store the screenshots in the git repo. 102 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 103 | # For more information about the recommended setup visit: 104 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 105 | 106 | fastlane/report.xml 107 | fastlane/Preview.html 108 | fastlane/screenshots/**/*.png 109 | fastlane/test_output 110 | 111 | # Code Injection 112 | # After new code Injection tools there's a generated folder /iOSInjectionProject 113 | # https://github.com/johnno1962/injectionforxcode 114 | 115 | iOSInjectionProject/ 116 | 117 | ### SwiftPM ### 118 | Packages 119 | xcuserdata 120 | #*.xcodeproj 121 | 122 | 123 | ### Xcode ### 124 | # Xcode 125 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 126 | 127 | 128 | 129 | 130 | ## Gcc Patch 131 | /*.gcno 132 | 133 | ### Xcode Patch ### 134 | #*.xcodeproj/* 135 | #!*.xcodeproj/project.pbxproj 136 | !*.xcodeproj/xcshareddata/ 137 | !*.xcworkspace/contents.xcworkspacedata 138 | **/xcshareddata/WorkspaceSettings.xcsettings 139 | 140 | # End of https://www.toptal.com/developers/gitignore/api/xcode,swift,swiftpm,macos 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ScanverterApp 2 | 3 | * captures photos (vision kit vs plain camera as service) 4 | * converts them to pdf (and also saves them as images in photo library) 5 | * text recognition with tesseract (vision kit's one disabled) 6 | * SwiftUI with Combine 7 | * SwiftyTesseract, ExyteGrid 8 | * MVVM architecture for presentation layer 9 | * SOA for business logic 10 | * translation (google) service disabled 11 | * not finished yet 12 | * iOS 14 13 | 14 | ### Screenshots 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
-------------------------------------------------------------------------------- /Scanverter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Scanverter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Scanverter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "ExyteGrid", 6 | "repositoryURL": "https://github.com/exyte/Grid", 7 | "state": { 8 | "branch": null, 9 | "revision": "f6a448eca2188b16ffcc9d42fda39e6a8a62d127", 10 | "version": "1.1.0" 11 | } 12 | }, 13 | { 14 | "package": "libtesseract", 15 | "repositoryURL": "https://github.com/SwiftyTesseract/libtesseract.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "d8b5fab45f5dc0b1d34b0ca7bf6620b72426adf1", 19 | "version": "0.1.0" 20 | } 21 | }, 22 | { 23 | "package": "PopupView", 24 | "repositoryURL": "https://github.com/dartsyms/PopupView", 25 | "state": { 26 | "branch": null, 27 | "revision": "4cb236c8b8e9de5e93d4ba23354ad64427418935", 28 | "version": "0.0.12" 29 | } 30 | }, 31 | { 32 | "package": "SwiftUIVisualEffects", 33 | "repositoryURL": "https://github.com/lucasbrown/swiftui-visual-effects.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "b26f8cebd55ff60ed8953768aa818dfb005b5838", 37 | "version": "1.0.3" 38 | } 39 | }, 40 | { 41 | "package": "SwiftyTesseract", 42 | "repositoryURL": "https://github.com/dartsyms/SwiftyTesseract", 43 | "state": { 44 | "branch": null, 45 | "revision": "1b4f0ef0c2ba08ae92254af730f2013af4ebdc4f", 46 | "version": "4.0.0" 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Scanverter/Helpers/Constants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreGraphics 3 | import UIKit 4 | 5 | public enum ModalOption { 6 | case mic, camera, search, none 7 | } 8 | 9 | public struct Constants { 10 | static let editTools = [ 11 | // EditTool.init(.add, image: UIImage(named: "addPageButton")!), 12 | EditTool.init(.crop, image: UIImage(named: "cropButton")!), 13 | EditTool.init(.delete, image: UIImage(named: "deletePageButton")!), 14 | // EditTool.init(.save(.image), image: UIImage(named: "imageSave")!), 15 | EditTool.init(.save(.pdf), image: UIImage(named: "pdfSave")!), 16 | EditTool.init(.ocr, image: UIImage(named: "ocrButton")!) 17 | ] 18 | 19 | static var mockedDocs: [ScannedDoc] { 20 | var docs: [ScannedDoc] = .init() 21 | let names = ["imageWithText", "slideWithText"] 22 | for _ in 0..<2 { 23 | names.forEach { 24 | docs.append(ScannedDoc(image: UIImage(named: $0)!.cgImage!, date: Date())) 25 | } 26 | } 27 | return docs 28 | } 29 | 30 | static let googleTranslationApiKey = "XXX-XXXXXX-XXXXXXXXX" 31 | } 32 | -------------------------------------------------------------------------------- /Scanverter/Helpers/ImageResizer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | enum ImageResizingError: Error { 5 | case cannotRetrieveFromURL 6 | case cannotRetrieveFromData 7 | } 8 | 9 | public struct ImageResizer { 10 | var targetWidth: CGFloat 11 | 12 | public func resize(at url: URL) -> UIImage? { 13 | guard let image = UIImage(contentsOfFile: url.path) else { 14 | return nil 15 | } 16 | 17 | return self.resize(image: image) 18 | } 19 | 20 | public func resize(image: UIImage) -> UIImage { 21 | let originalSize = image.size 22 | let targetSize = CGSize(width: targetWidth, height: targetWidth * originalSize.height / originalSize.width) 23 | let renderer = UIGraphicsImageRenderer(size: targetSize) 24 | return renderer.image { context in 25 | image.draw(in: CGRect(origin: .zero, size: targetSize)) 26 | } 27 | } 28 | 29 | public func resize(data: Data) -> UIImage? { 30 | guard let image = UIImage(data: data) else {return nil} 31 | return resize(image: image ) 32 | } 33 | } 34 | 35 | struct MemorySizer { 36 | static func size(of data: Data) -> String { 37 | let bcf = ByteCountFormatter() 38 | bcf.allowedUnits = [.useMB] //[] 39 | bcf.countStyle = .file 40 | let string = bcf.string(fromByteCount: Int64(data.count)) 41 | return string 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Scanverter/Helpers/PDFGenerator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PDFKit 3 | import Combine 4 | 5 | protocol DocGenerator { 6 | func generatePDF() -> AnyPublisher 7 | func getPDFdata() -> AnyPublisher 8 | var pages: [UIImage] { get set } 9 | } 10 | 11 | class PDFGenerator: NSObject, DocGenerator { 12 | var pages: [UIImage] 13 | private let pdfDocument = PDFDocument() 14 | private var pdfPage: PDFPage? 15 | 16 | var totalPages: Int { return pages.count } 17 | 18 | init(pages: [UIImage]) { 19 | self.pages = pages 20 | } 21 | 22 | func generatePDF() -> AnyPublisher { 23 | return Future { promise in 24 | DispatchQueue.global(qos: .background).async { 25 | for (index, image) in self.pages.enumerated() { 26 | print("Scanned pages in generator: \(self.pages)") 27 | // let A4paperSize = CGSize(width: 595, height: 842) 28 | // let bounds = CGRect.init(origin: .zero, size: A4paperSize) 29 | // let coreImage = image.cgImage! 30 | // let editedImage = UIImage(cgImage: coreImage) 31 | self.pdfPage = PDFPage(image: image) 32 | // self.pdfPage?.setBounds(bounds, for: .cropBox) 33 | self.pdfDocument.insert(self.pdfPage!, at: index) 34 | } 35 | promise(.success(self.pdfDocument)) 36 | } 37 | }.eraseToAnyPublisher() 38 | } 39 | 40 | func getPDFdata() -> AnyPublisher { 41 | return Future { promise in 42 | DispatchQueue.global(qos: .background).async { [weak self] in 43 | let data = self?.pdfDocument.dataRepresentation() 44 | promise(.success(data)) 45 | } 46 | }.eraseToAnyPublisher() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Scanverter/Helpers/SwiftUIExtensions.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | func navigatePush(whenTrue toggle: Binding) -> some View { 5 | NavigationLink(destination: self, isActive: toggle) { EmptyView() } 6 | } 7 | 8 | func navigatePush(when binding: Binding, matches: H) -> some View { 9 | NavigationLink(destination: self, tag: matches, selection: Binding(binding)) { EmptyView() } 10 | } 11 | 12 | func navigatePush(when binding: Binding, matches: H) -> some View { 13 | NavigationLink(destination: self, tag: matches, selection: binding) { EmptyView() } 14 | } 15 | } 16 | 17 | extension Binding { 18 | func didSet(execute: @escaping (Value) -> Void) -> Binding { 19 | return Binding( 20 | get: { return self.wrappedValue }, 21 | set: { self.wrappedValue = $0; execute($0) } 22 | ) 23 | } 24 | } 25 | 26 | 27 | extension Color { 28 | init(hex: String) { 29 | let scanner = Scanner(string: hex) 30 | var rgbValue: UInt64 = 0 31 | scanner.scanHexInt64(&rgbValue) 32 | 33 | let r = (rgbValue & 0xff0000) >> 16 34 | let g = (rgbValue & 0xff00) >> 8 35 | let b = rgbValue & 0xff 36 | 37 | self.init(red: Double(r) / 0xff, green: Double(g) / 0xff, blue: Double(b) / 0xff) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Scanverter/Helpers/VNRectangleFeature.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreGraphics 3 | import CoreImage 4 | 5 | class VNRectangleFeature: CIFeature { 6 | open var topLeft: CGPoint = .zero 7 | open var topRight: CGPoint = .zero 8 | open var bottomLeft: CGPoint = .zero 9 | open var bottomRight: CGPoint = .zero 10 | 11 | class func setValue(topLeft: CGPoint, topRight: CGPoint, bottomLeft: CGPoint, bottomRight: CGPoint) -> VNRectangleFeature { 12 | let object: VNRectangleFeature = VNRectangleFeature() 13 | object.topLeft = topLeft 14 | object.topRight = topRight 15 | object.bottomLeft = bottomLeft 16 | object.bottomRight = bottomRight 17 | return object 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Scanverter/Models/AlertError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct AlertError { 4 | public var title: String = "" 5 | public var message: String = "" 6 | public var primaryButtonTitle = "Accept" 7 | public var secondaryButtonTitle: String? 8 | public var primaryAction: (() -> ())? 9 | public var secondaryAction: (() -> ())? 10 | 11 | public init(title: String = "", 12 | message: String = "", 13 | primaryButtonTitle: String = "Accept", 14 | secondaryButtonTitle: String? = nil, 15 | primaryAction: (() -> ())? = nil, 16 | secondaryAction: (() -> ())? = nil) { 17 | 18 | self.title = title 19 | self.message = message 20 | self.primaryAction = primaryAction 21 | self.primaryButtonTitle = primaryButtonTitle 22 | self.secondaryAction = secondaryAction 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Scanverter/Models/Detection.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Detections: Codable { 4 | var items: [Detection] 5 | 6 | init(items: [Detection]) { 7 | self.items = items 8 | } 9 | 10 | enum CodingKeys: String, CodingKey, CaseIterable { 11 | case items = "detections" 12 | } 13 | 14 | public init(from decoder: Decoder) throws { 15 | let container = try decoder.container(keyedBy: CodingKeys.self) 16 | self.items = container.decodeSafelyIfPresent([Detection].self, forKey: .items) ?? [] 17 | } 18 | } 19 | 20 | public struct Detection: Codable { 21 | let language: String? 22 | let isReliable: Bool? 23 | let confidence: Float? 24 | 25 | init(language: String?, isReliable: Bool?, confidence: Float?) { 26 | self.language = language 27 | self.isReliable = isReliable 28 | self.confidence = confidence 29 | } 30 | 31 | enum CodingKeys: String, CodingKey, CaseIterable { 32 | case language, isReliable, confidence 33 | } 34 | 35 | public init(from decoder: Decoder) throws { 36 | let container = try decoder.container(keyedBy: CodingKeys.self) 37 | self.language = container.decodeSafelyIfPresent(String.self, forKey: .language) 38 | self.isReliable = container.decodeSafelyIfPresent(Bool.self, forKey: .isReliable) 39 | self.confidence = container.decodeSafelyIfPresent(Float.self, forKey: .confidence) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Scanverter/Models/DocFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | 4 | struct DocFile: Codable { 5 | var name: String 6 | var date: Date 7 | var uid: UUID 8 | var parent: Folder 9 | } 10 | 11 | extension DocFile: Equatable, Hashable { 12 | static func == (lhs: DocFile, rhs: DocFile) -> Bool { 13 | return lhs.uid == rhs.uid 14 | } 15 | 16 | func hash(into hasher: inout Hasher) { 17 | hasher.combine(uid) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Scanverter/Models/EditTool.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Foundation 3 | 4 | struct EditTool { 5 | let uid: String = UUID().uuidString 6 | let type: EditType 7 | let image: UIImage 8 | init(_ type: EditType, image: UIImage) { 9 | self.type = type 10 | self.image = image 11 | } 12 | } 13 | 14 | extension EditTool: Identifiable { 15 | var id: String { 16 | return uid 17 | } 18 | } 19 | 20 | extension EditTool: Equatable, Hashable { 21 | static func == (lhs: EditTool, rhs: EditTool) -> Bool { 22 | return lhs.uid == rhs.uid 23 | } 24 | 25 | func hash(into hasher: inout Hasher) { 26 | hasher.combine(uid) 27 | } 28 | } 29 | 30 | enum DocumentOrigin { 31 | case camera 32 | case photos 33 | } 34 | 35 | enum ProgressViewStyle { 36 | case status 37 | case progress 38 | case none 39 | } 40 | 41 | enum EditType { 42 | case add 43 | case crop 44 | case delete 45 | case save(SaveType) 46 | case ocr 47 | 48 | var tool: String { 49 | switch self { 50 | case .add: 51 | return "Add" 52 | case .crop: 53 | return "Crop" 54 | case .delete: 55 | return "Delete" 56 | case .save: 57 | return "Save" 58 | case .ocr: 59 | return "OCR" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Scanverter/Models/Folder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | import PDFKit 4 | 5 | struct Folder: Codable { 6 | var name: String 7 | var date: Date 8 | var isPasswordProtected: Bool 9 | var uid: UUID 10 | var files: [DocFile] 11 | var selected: Bool = false 12 | 13 | @discardableResult 14 | func save() -> AnyPublisher { 15 | DataManager.save(self, withName: uid.uuidString) 16 | } 17 | 18 | @discardableResult 19 | func delete(isDirectory: Bool = false) -> AnyPublisher { 20 | return DataManager.delete(file: uid.uuidString, isDirectory: isDirectory) 21 | } 22 | 23 | mutating func toggleSelection() { 24 | selected.toggle() 25 | } 26 | } 27 | 28 | extension Folder: Equatable, Hashable { 29 | static func == (lhs: Folder, rhs: Folder) -> Bool { 30 | return lhs.uid == rhs.uid 31 | } 32 | 33 | func hash(into hasher: inout Hasher) { 34 | hasher.combine(uid) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scanverter/Models/Language.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Languages: Codable { 4 | var items: [Language] 5 | 6 | init(items: [Language]) { 7 | self.items = items 8 | } 9 | 10 | enum CodingKeys: String, CodingKey, CaseIterable { 11 | case items = "languages" 12 | } 13 | 14 | public init(from decoder: Decoder) throws { 15 | let container = try decoder.container(keyedBy: CodingKeys.self) 16 | self.items = container.decodeSafelyIfPresent([Language].self, forKey: .items) ?? [] 17 | } 18 | } 19 | 20 | 21 | public struct Language: Codable { 22 | public let language: String? 23 | public let name: String? 24 | 25 | init(language: String?, name: String?) { 26 | self.language = language 27 | self.name = name 28 | } 29 | 30 | enum CodingKeys: String, CodingKey, CaseIterable { 31 | case language, name 32 | } 33 | 34 | public init(from decoder: Decoder) throws { 35 | let container = try decoder.container(keyedBy: CodingKeys.self) 36 | self.language = container.decodeSafelyIfPresent(String.self, forKey: .language) 37 | self.name = container.decodeSafelyIfPresent(String.self, forKey: .name) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Scanverter/Models/PDF.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | struct PDF { 4 | var name: String 5 | var page: UIImage 6 | } 7 | -------------------------------------------------------------------------------- /Scanverter/Models/Photo.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public struct Photo: Identifiable, Equatable { 4 | public var id: String 5 | public var originalData: Data 6 | 7 | public init(id: String = UUID().uuidString, originalData: Data) { 8 | self.id = id 9 | self.originalData = originalData 10 | } 11 | } 12 | 13 | extension Photo { 14 | public var compressedData: Data? { 15 | ImageResizer(targetWidth: 800).resize(data: originalData)?.jpegData(compressionQuality: 0.5) 16 | } 17 | public var thumbnailData: Data? { 18 | ImageResizer(targetWidth: 100).resize(data: originalData)?.jpegData(compressionQuality: 0.5) 19 | } 20 | public var thumbnailImage: UIImage? { 21 | guard let data = thumbnailData else { return nil } 22 | return UIImage(data: data) 23 | } 24 | public var image: UIImage? { 25 | guard let data = compressedData else { return nil } 26 | return UIImage(data: data) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Scanverter/Models/ScannedDoc.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreGraphics 3 | 4 | struct ScannedDoc { 5 | let uid: String = UUID().uuidString 6 | var image: CGImage 7 | var date: Date 8 | } 9 | 10 | extension ScannedDoc: Equatable, Hashable { 11 | static func == (lhs: ScannedDoc, rhs: ScannedDoc) -> Bool { 12 | return lhs.uid == rhs.uid 13 | } 14 | 15 | func hash(into hasher: inout Hasher) { 16 | hasher.combine(uid) 17 | } 18 | } 19 | 20 | extension ScannedDoc: Identifiable { 21 | var id: String { 22 | return uid 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Scanverter/Models/Translation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Translations: Codable { 4 | var items: [Translation] 5 | 6 | init(items: [Translation]) { 7 | self.items = items 8 | } 9 | 10 | enum CodingKeys: String, CodingKey, CaseIterable { 11 | case items = "translations" 12 | } 13 | 14 | public init(from decoder: Decoder) throws { 15 | let container = try decoder.container(keyedBy: CodingKeys.self) 16 | self.items = container.decodeSafelyIfPresent([Translation].self, forKey: .items) ?? [] 17 | } 18 | } 19 | 20 | 21 | public struct Translation: Codable { 22 | var text: String? 23 | 24 | init(text: String?) { 25 | self.text = text 26 | } 27 | 28 | enum CodingKeys: String, CodingKey, CaseIterable { 29 | case text = "translatedText" 30 | } 31 | 32 | public init(from decoder: Decoder) throws { 33 | let container = try decoder.container(keyedBy: CodingKeys.self) 34 | self.text = container.decodeSafelyIfPresent(String.self, forKey: .text) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scanverter/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Scanverter/Services/BiometricAuthentication.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | import LocalAuthentication 4 | 5 | // TODO: port to combine 6 | 7 | protocol TouchIdentification { 8 | func authenticateUser(_ completion: @escaping (String?) -> ()) 9 | } 10 | 11 | class BiometricAuthentication: TouchIdentification { 12 | fileprivate let context = LAContext() 13 | 14 | fileprivate func canEvaluatePolicy() -> Bool { 15 | return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) 16 | } 17 | 18 | func authenticateUser(_ completion: @escaping (String?) -> ()) { 19 | guard canEvaluatePolicy() else { 20 | completion("Touch ID not available") 21 | return 22 | } 23 | 24 | let reason = NSLocalizedString("Touch ID needed to enable secure access only", comment: "") 25 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { (success, error) in 26 | if error != nil { 27 | let message: String 28 | switch error! { 29 | case LAError.authenticationFailed: 30 | message = "There was a problem verifying your identity." 31 | case LAError.userCancel: 32 | message = "You pressed cancel." 33 | case LAError.userFallback: 34 | message = "You pressed password." 35 | case LAError.biometryNotAvailable: 36 | message = "Face ID/Touch ID is not available." 37 | case LAError.biometryNotEnrolled: 38 | message = "Face ID/Touch ID is not set up." 39 | case LAError.biometryLockout: 40 | message = "Face ID/Touch ID is locked." 41 | default: 42 | message = "Face ID/Touch ID may not be configured" 43 | } 44 | completion(message) 45 | return 46 | } 47 | if success { 48 | DispatchQueue.main.async { 49 | completion(nil) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Scanverter/Services/ServiceLocator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | typealias Services = [ObjectIdentifier: Any] 4 | 5 | protocol ServiceLocator { 6 | func resolve(type: T.Type) -> T? 7 | func register(_ service: T) 8 | } 9 | 10 | final class Resolver: ServiceLocator { 11 | public static let shared = Resolver() 12 | 13 | private var services: Services = [:] 14 | 15 | func register(_ service: T) { 16 | services[key(for: T.self)] = service 17 | } 18 | 19 | func resolve(type: T.Type) -> T? { 20 | return services[key(for: T.self)] as? T 21 | } 22 | 23 | private func key(for type: T.Type) -> ObjectIdentifier { 24 | return ObjectIdentifier(T.self) 25 | } 26 | } 27 | 28 | @propertyWrapper 29 | struct Injected { 30 | private var service: T! 31 | public var container: ServiceLocator? = nil 32 | public var name: String? 33 | 34 | public init() {} 35 | 36 | public init(name: String? = nil, container: ServiceLocator? = nil) { 37 | self.name = name 38 | self.container = container 39 | } 40 | 41 | public var wrappedValue: T { 42 | mutating get { 43 | if self.service == nil { 44 | self.service = container?.resolve(type: T.self) ?? Resolver.shared.resolve(type: T.self) 45 | } 46 | return service 47 | } 48 | mutating set { 49 | service = newValue 50 | } 51 | } 52 | 53 | public var projectedValue: Injected { 54 | get { return self } 55 | mutating set { self = newValue } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "addPageButton.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "addPageButton@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "addPageButton@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/addPageButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/addPageButton.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/addPageButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/addPageButton@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/addPageButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/addPageButton.imageset/addPageButton@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/add_button_filled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "add_button_filled.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "add_button_filled@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/add_button_filled.imageset/add_button_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/add_button_filled.imageset/add_button_filled.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/add_button_filled.imageset/add_button_filled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/add_button_filled.imageset/add_button_filled@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/add_folder_button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "add_folder_button.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "add_folder_button@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/add_folder_button.imageset/add_folder_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/add_folder_button.imageset/add_folder_button.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/add_folder_button.imageset/add_folder_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/add_folder_button.imageset/add_folder_button@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "captureButton.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "captureButton@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "captureButton@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/captureButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/captureButton.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/captureButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/captureButton@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/captureButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/captureButton.imageset/captureButton@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cropButton.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "cropButton@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "cropButton@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/cropButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/cropButton.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/cropButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/cropButton@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/cropButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/cropButton.imageset/cropButton@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/default.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "default.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/default.imageset/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/default.imageset/default.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "deletePageButton.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "deletePageButton@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "deletePageButton@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/deletePageButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/deletePageButton.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/deletePageButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/deletePageButton@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/deletePageButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/deletePageButton.imageset/deletePageButton@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/error.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "error.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/error.imageset/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/error.imageset/error.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "filterButton.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "filterButton@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "filterButton@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/filterButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/filterButton.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/filterButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/filterButton@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/filterButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/filterButton.imageset/filterButton@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "folder_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "folder_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "folder_icon@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/folder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/folder_icon.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/folder_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/folder_icon@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/folder_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/folder_icon.imageset/folder_icon@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "folder_image.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "folder_image@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "folder_image@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/folder_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/folder_image.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/folder_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/folder_image@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/folder_image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/folder_image.imageset/folder_image@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageSave.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "imageSave@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "imageSave@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/imageSave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/imageSave.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/imageSave@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/imageSave@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/imageSave@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/imageSave.imageset/imageSave@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/lock_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "lock_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "lock_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/lock_icon.imageset/lock_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/lock_icon.imageset/lock_icon.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/lock_icon.imageset/lock_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/lock_icon.imageset/lock_icon@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/more_options.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "more_options.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/more_options.imageset/more_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/more_options.imageset/more_options.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ocr_button.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ocr_button@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ocr_button@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/ocr_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/ocr_button.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/ocr_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/ocr_button@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/ocr_button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/ocrButton.imageset/ocr_button@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pdfSave.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "pdfSave@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "pdfSave@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/pdfSave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/pdfSave.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/pdfSave@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/pdfSave@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/pdfSave@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/pdfSave.imageset/pdfSave@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pdf_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "pdf_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "pdf_icon@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/pdf_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/pdf_icon.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/pdf_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/pdf_icon@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/pdf_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/pdf_icon.imageset/pdf_icon@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "saveButton.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "saveButton@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "saveButton@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/saveButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/saveButton.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/saveButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/saveButton@2x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/saveButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/saveButton.imageset/saveButton@3x.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/success.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "sucess.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Assets.xcassets/success.imageset/sucess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Assets.xcassets/success.imageset/sucess.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Images.xcassets/imageWithText.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageWithText.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Images.xcassets/imageWithText.imageset/imageWithText.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Images.xcassets/imageWithText.imageset/imageWithText.jpg -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Images.xcassets/slideWithText.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "slideWithText.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Images.xcassets/slideWithText.imageset/slideWithText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/SupportingFiles/Images.xcassets/slideWithText.imageset/slideWithText.png -------------------------------------------------------------------------------- /Scanverter/SupportingFiles/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | NSCameraUsageDescription 50 | Scanverter app needs access to camera to take pictures. 51 | NSPhotoLibraryUsageDescription 52 | Scanverter needs acces to photo library to save pics. 53 | NSPhotoLibraryAddUsageDescription 54 | Scanverter wants to use photo lib additions. 55 | NSFaceIDUsageDescription 56 | The app needs access to Face ID to handle folder secure locking feature 57 | 58 | 59 | -------------------------------------------------------------------------------- /Scanverter/System/AppContainer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class AppContainer { 4 | static func makeDefault() { 5 | Resolver.shared.register(PDFGenerator(pages: []) as DocGenerator) 6 | Resolver.shared.register(BiometricAuthentication() as TouchIdentification) 7 | Resolver.shared.register(GoogleTranslation(apiKey: Constants.googleTranslationApiKey) as TranslationService) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scanverter/System/ScanverterApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct ScanverterApp: App { 5 | init() { 6 | AppContainer.makeDefault() 7 | } 8 | 9 | var body: some Scene { 10 | WindowGroup { 11 | MainTabBarView() 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Scanverter/Views/Alerts/CreateFolderView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CreateFolderView: View { 4 | @Binding var showCreateDirectoryModal: Bool 5 | @Binding var folderName: String 6 | @Binding var isSecured: Bool 7 | 8 | @State private var canUseSecureLock: Bool = false 9 | @State var isEditing: Bool = false 10 | 11 | @State var showAlert: Bool = false 12 | @State var alertTitle: String = "" 13 | @State var alertMessage: String = "" 14 | 15 | private var onDismiss: (() -> Void)? = nil 16 | 17 | init(showCreateDirectoryModal: Binding, 18 | folderName: Binding, 19 | isSecured: Binding, 20 | onDismiss: @escaping () -> Void) { 21 | self._showCreateDirectoryModal = showCreateDirectoryModal 22 | self._folderName = folderName 23 | self._isSecured = isSecured 24 | self.onDismiss = onDismiss 25 | } 26 | 27 | var body: some View { 28 | ZStack { 29 | RoundedRectangle(cornerRadius: 0) 30 | .fill(Color(UIColor.systemBackground)) 31 | .frame(width: 360, height: 320) 32 | .zIndex(0) 33 | Circle() 34 | .trim(from: 0.5, to: 1) 35 | .fill(Color(UIColor.systemBackground)) 36 | .frame(width: 140, height: 140) 37 | .overlay(Circle() 38 | .stroke(Color(UIColor.label), lineWidth: 3)) 39 | .offset(y: -160) 40 | .zIndex(1) 41 | Image("folder_icon") 42 | .resizable() 43 | .foregroundColor(.blue) 44 | .frame(width: 100, height: 100, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) 45 | .offset(y: -160) 46 | .zIndex(2) 47 | 48 | VStack { 49 | HStack { 50 | Spacer() 51 | Button(action: { 52 | self.showCreateDirectoryModal = false 53 | }, label: { 54 | Image(systemName: "xmark.circle") 55 | .resizable() 56 | .foregroundColor(Color(UIColor.systemGray)) 57 | .frame(width: 40, height: 40, alignment: .leading) 58 | 59 | }) 60 | }.offset(x: -40, y: -10) 61 | HStack { 62 | Text("Folder Name") 63 | .font(.headline) 64 | .foregroundColor(Color(UIColor.label)) 65 | .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) 66 | .offset(x: 40, y: 0) 67 | Spacer() 68 | } 69 | TextField("Enter name", text: $folderName) { isEditing in 70 | self.isEditing = isEditing 71 | } onCommit: { 72 | 73 | } 74 | .textFieldStyle(RoundedBorderTextFieldStyle()) 75 | .autocapitalization(.none) 76 | .disableAutocorrection(true) 77 | .padding(EdgeInsets(top: 0, leading: 40, bottom: 20, trailing: 40)) 78 | 79 | HStack { 80 | Text("Secure Lock") 81 | .font(.headline) 82 | .foregroundColor(Color(UIColor.label)) 83 | .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) 84 | .offset(x: 40, y: 0) 85 | Spacer() 86 | Toggle("", isOn: $isSecured.didSet(execute: { state in 87 | checkIfCanBeLocked() 88 | })) 89 | .toggleStyle(SwitchToggleStyle(tint: Color(UIColor.systemBlue))) 90 | .offset(x: -40, y: 0) 91 | } 92 | 93 | Button(action: { 94 | withAnimation(.easeOut(duration: 0.25)) { 95 | self.showCreateDirectoryModal = false 96 | onDismiss?() 97 | } 98 | }) { 99 | Text("Create Folder") 100 | } 101 | .frame(width: 290, height: 20) 102 | .padding() 103 | .foregroundColor(.white) 104 | .background(LinearGradient(gradient: Gradient(colors: [Color(UIColor.systemBlue), Color(UIColor.systemBlue)]), startPoint: .leading, endPoint: .trailing)) 105 | .cornerRadius(6) 106 | .padding(EdgeInsets(top: 20, leading: 10, bottom: 20, trailing: 10)) 107 | } 108 | } 109 | .padding(.top, 60) 110 | } 111 | 112 | private func checkIfCanBeLocked() { 113 | if !canUseSecureLock { 114 | let bioAuthentication = BiometricAuthentication() 115 | bioAuthentication.authenticateUser { (errorMessage) in 116 | if errorMessage != nil { 117 | canUseSecureLock = false 118 | isSecured = false 119 | alertTitle = "Unable To Authenticate 😬" 120 | alertMessage = errorMessage! 121 | showAlert = true 122 | } else { 123 | canUseSecureLock = true 124 | isSecured = true 125 | alertTitle = "Secure Access Enabled 😃" 126 | alertMessage = "To access this folder you will be required to use face id" 127 | showAlert = true 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | struct CreateFolderView_Previews: PreviewProvider { 135 | static var previews: some View { 136 | CreateFolderView(showCreateDirectoryModal: .constant(true), 137 | folderName: .constant(""), 138 | isSecured: .constant(false), 139 | onDismiss: {}) 140 | .preferredColorScheme(.light) 141 | 142 | CreateFolderView(showCreateDirectoryModal: .constant(true), 143 | folderName: .constant(""), 144 | isSecured: .constant(false), 145 | onDismiss: {}) 146 | .preferredColorScheme(.dark) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Scanverter/Views/Alerts/CustomAlert.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CustomAlert: View { 4 | @Binding var showingAlert: Bool 5 | @Binding var title: String 6 | @Binding var message: String 7 | 8 | var body: some View { 9 | alert(isPresented: $showingAlert, content: { 10 | Alert(title: Text(title), message: Text(message), dismissButton: .default(Text("OK"))) 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Scanverter/Views/Camera/CameraPreview.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import AVFoundation 3 | 4 | struct CameraPreview_Previews: PreviewProvider { 5 | static var previews: some View { 6 | CameraPreview(session: AVCaptureSession()) 7 | .frame(height: 300) 8 | } 9 | } 10 | 11 | struct CameraPreview: UIViewRepresentable { 12 | class VideoPreviewView: UIView { 13 | override class var layerClass: AnyClass { 14 | AVCaptureVideoPreviewLayer.self 15 | } 16 | 17 | var videoPreviewLayer: AVCaptureVideoPreviewLayer { 18 | return layer as! AVCaptureVideoPreviewLayer 19 | } 20 | } 21 | 22 | let session: AVCaptureSession 23 | 24 | func makeUIView(context: Context) -> VideoPreviewView { 25 | let view = VideoPreviewView() 26 | view.backgroundColor = .black 27 | view.videoPreviewLayer.cornerRadius = 0 28 | view.videoPreviewLayer.session = session 29 | view.videoPreviewLayer.connection?.videoOrientation = .portrait 30 | 31 | return view 32 | } 33 | 34 | func updateUIView(_ uiView: VideoPreviewView, context: Context) { 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Scanverter/Views/Camera/CameraView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | import AVFoundation 4 | 5 | struct CameraView_Previews: PreviewProvider { 6 | static var previews: some View { 7 | CameraView(model: CameraViewModel()) 8 | } 9 | } 10 | 11 | struct CameraView: View { 12 | @StateObject var model: CameraViewModel 13 | @State var currentZoomFactor: CGFloat = 1.0 14 | 15 | var captureButton: some View { 16 | Button(action: { 17 | model.capturePhoto() 18 | }, label: { 19 | Circle() 20 | .foregroundColor(.white) 21 | .frame(width: 80, height: 80, alignment: .center) 22 | .overlay( 23 | Circle() 24 | .stroke(Color.black.opacity(0.8), lineWidth: 2) 25 | .frame(width: 65, height: 65, alignment: .center) 26 | ) 27 | }) 28 | } 29 | 30 | var capturedPhotoThumbnail: some View { 31 | Group { 32 | if model.photo != nil { 33 | Image(uiImage: model.photo.image!) 34 | .resizable() 35 | .aspectRatio(contentMode: .fill) 36 | .frame(width: 60, height: 60) 37 | .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) 38 | .animation(.spring()) 39 | 40 | } else { 41 | RoundedRectangle(cornerRadius: 10) 42 | .frame(width: 60, height: 60, alignment: .center) 43 | .foregroundColor(.black) 44 | } 45 | } 46 | } 47 | 48 | var flipCameraButton: some View { 49 | Button(action: { 50 | model.flipCamera() 51 | }, label: { 52 | Circle() 53 | .foregroundColor(Color.gray.opacity(0.2)) 54 | .frame(width: 45, height: 45, alignment: .center) 55 | .overlay( 56 | Image(systemName: "camera.rotate.fill") 57 | .foregroundColor(.white)) 58 | }) 59 | } 60 | 61 | var body: some View { 62 | GeometryReader { reader in 63 | ZStack { 64 | Color.black.edgesIgnoringSafeArea(.all) 65 | 66 | VStack { 67 | Button(action: { 68 | model.switchFlash() 69 | }, label: { 70 | Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill") 71 | .font(.system(size: 20, weight: .medium, design: .default)) 72 | }) 73 | .accentColor(model.isFlashOn ? .yellow : .white) 74 | .padding(.top, 40) 75 | 76 | CameraPreview(session: model.session) 77 | .gesture( 78 | DragGesture().onChanged { (val) in 79 | if abs(val.translation.height) > abs(val.translation.width) { 80 | let percentage: CGFloat = -(val.translation.height / reader.size.height) 81 | let calc = currentZoomFactor + percentage 82 | let zoomFactor: CGFloat = min(max(calc, 1), 5) 83 | currentZoomFactor = zoomFactor 84 | model.zoom(with: zoomFactor) 85 | } 86 | } 87 | ) 88 | .onAppear { 89 | model.configure() 90 | } 91 | .alert(isPresented: $model.showAlertError, content: { 92 | Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: { 93 | model.alertError.primaryAction?() 94 | })) 95 | }) 96 | .overlay( 97 | Group { 98 | if model.willCapturePhoto { 99 | Color.black 100 | } 101 | } 102 | ) 103 | .animation(.easeInOut) 104 | 105 | HStack { 106 | capturedPhotoThumbnail 107 | Spacer() 108 | captureButton 109 | .opacity(UIDevice.isSimulator ? 0.4 : 1) 110 | .disabled(UIDevice.isSimulator) 111 | Spacer() 112 | flipCameraButton 113 | .opacity(UIDevice.isSimulator ? 0.4 : 1) 114 | .disabled(UIDevice.isSimulator) 115 | } 116 | .padding(.horizontal, 20) 117 | .padding(.bottom, 20) 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Scanverter/Views/Camera/ViewModels/CameraViewModel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Combine 3 | import AVFoundation 4 | 5 | final class CameraViewModel: ObservableObject { 6 | private let service = CameraService() 7 | 8 | @Published var photo: Photo! 9 | @Published var showAlertError = false 10 | @Published var isFlashOn = false 11 | @Published var willCapturePhoto = false 12 | @Published var scannedDocs: [ScannedDoc] = .init() 13 | @Published var isPresentingImagePicker: Bool = false 14 | 15 | var alertError: AlertError! 16 | var session: AVCaptureSession 17 | 18 | public let publisher = PassthroughSubject() 19 | private var showOneLevelIn: Bool = false { 20 | willSet { 21 | publisher.send(showOneLevelIn) 22 | } 23 | } 24 | 25 | private var subscriptions = Set() 26 | 27 | init() { 28 | self.session = service.session 29 | 30 | service.$photo.sink { [weak self] photo in 31 | guard let pic = photo else { return } 32 | self?.photo = pic 33 | guard let img = pic.image?.cgImage else { return } 34 | self?.scannedDocs.append(ScannedDoc(image: img, date: Date())) 35 | self?.showOneLevelIn = true 36 | DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { 37 | self?.showOneLevelIn = true 38 | } 39 | } 40 | .store(in: &self.subscriptions) 41 | 42 | service.$shouldShowAlertView.sink { [weak self] val in 43 | self?.alertError = self?.service.alertError 44 | self?.showAlertError = val 45 | } 46 | .store(in: &self.subscriptions) 47 | 48 | service.$flashMode.sink { [weak self] mode in 49 | self?.isFlashOn = mode == .on 50 | } 51 | .store(in: &self.subscriptions) 52 | 53 | service.$willCapturePhoto.sink { [weak self] val in 54 | self?.willCapturePhoto = val 55 | } 56 | .store(in: &self.subscriptions) 57 | } 58 | 59 | func configure() { 60 | service.checkForPermissions() 61 | service.configure() 62 | } 63 | 64 | func capturePhoto() { 65 | service.capturePhoto() 66 | } 67 | 68 | func flipCamera() { 69 | service.changeCamera() 70 | } 71 | 72 | func zoom(with factor: CGFloat) { 73 | service.set(zoom: factor) 74 | } 75 | 76 | func switchFlash() { 77 | service.flashMode = service.flashMode == .on ? .off : .on 78 | } 79 | 80 | func didSelectImage(_ image: UIImage?) { 81 | isPresentingImagePicker = false 82 | guard image != nil, let picData = image!.pngData() else { return } 83 | self.photo = Photo(originalData: picData) 84 | scannedDocs.append(ScannedDoc(image: image!.cgImage!, date: Date())) 85 | showOneLevelIn = true 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Cells/EditImageCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct EditImageCell_Previews: PreviewProvider { 4 | static var previews: some View { 5 | EditImageCell(dataSource: EditCellDataSource(scannedDoc: ScannedDoc(image: UIImage(named: "slideWithText")!.cgImage!, date: Date()))) 6 | } 7 | } 8 | 9 | struct EditImageCell: View { 10 | @StateObject var dataSource: EditCellDataSource 11 | 12 | var body: some View { 13 | VStack(alignment: .center) { 14 | Image(uiImage: UIImage(cgImage: dataSource.scannedDoc.image)) 15 | .resizable() 16 | .aspectRatio(contentMode: .fit) 17 | .padding() 18 | 19 | Text(dataSource.scannedDoc.date.toString) 20 | .foregroundColor(Color(UIColor.systemGray)) 21 | .font(.headline) 22 | } 23 | .padding() 24 | .frame(width: UIScreen.main.bounds.width - 40, height: UIScreen.main.bounds.height - 80) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Cells/EditToolCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct EditToolCell_Previews: PreviewProvider { 4 | static var previews: some View { 5 | let tools = [ 6 | // EditTool.init(.add, image: UIImage(named: "addPageButton")!), 7 | EditTool.init(.crop, image: UIImage(named: "cropButton")!), 8 | EditTool.init(.delete, image: UIImage(named: "deletePageButton")!), 9 | EditTool.init(.save(.image), image: UIImage(named: "imageSave")!), 10 | EditTool.init(.save(.pdf), image: UIImage(named: "pdfSave")!), 11 | EditTool.init(.ocr, image: UIImage(named: "ocrButton")!) 12 | ] 13 | EditToolCell(dataSource: EditToolCellDataSource(tool: EditTool(tools.last!.type, image: tools.last!.image)), 14 | photoDataSource: PhotoCollectionDataSource(scannedDocs: Constants.mockedDocs)) 15 | } 16 | } 17 | 18 | struct EditToolCell: View { 19 | @StateObject var dataSource: EditToolCellDataSource 20 | @StateObject var photoDataSource: PhotoCollectionDataSource 21 | 22 | var body: some View { 23 | VStack(alignment: .center) { 24 | Image(uiImage: dataSource.editTool.image) 25 | .resizable() 26 | .aspectRatio(contentMode: .fit) 27 | .frame(maxWidth: 60, maxHeight: 60) 28 | Text(dataSource.editTool.type.tool) 29 | .font(.headline) 30 | .fontWeight(.semibold) 31 | .foregroundColor(Color(UIColor.themeIndigoDark())) 32 | .offset(x: 0, y: -10) 33 | }.onTapGesture { 34 | switch dataSource.editTool.type { 35 | case .add: 36 | print("Add doc") 37 | case .crop: 38 | photoDataSource.makeCrop() 39 | case .delete: 40 | print("delete doc") 41 | photoDataSource.deletePage() 42 | case .save(let type): 43 | print("save") 44 | photoDataSource.save(as: type) 45 | case .ocr: 46 | photoDataSource.makeTextRecognition() 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Cells/FileGridCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct FileGridCell_Previews: PreviewProvider { 4 | static var previews: some View { 5 | FileGridCell(dataSource: FileCellDataSource(file: DocFile(name: "Some pdf", 6 | date: Date(), 7 | uid: UUID(), 8 | parent: Folder(name: "Some folder", 9 | date: Date(), 10 | isPasswordProtected: false, 11 | uid: UUID(), 12 | files: [])))) 13 | } 14 | } 15 | 16 | struct FileGridCell: View { 17 | @StateObject var dataSource: FileCellDataSource 18 | var body: some View { 19 | VStack(alignment: .center) { 20 | Image(systemName: "newspaper.fill") 21 | .resizable() 22 | .scaledToFit() 23 | .foregroundColor(Color(UIColor.systemBlue)) 24 | .frame(maxWidth: 80, maxHeight: 60) 25 | Text(dataSource.file.name.split(separator: ".").dropLast().joined()) 26 | .font(.subheadline) 27 | .fontWeight(.semibold) 28 | .lineLimit(1) 29 | .truncationMode(.tail) 30 | .foregroundColor(Color(UIColor.label)) 31 | .padding([.leading, .trailing], 5) 32 | VStack(alignment: .center) { 33 | Text(dataSource.file.date.toString) 34 | .font(.caption) 35 | .fontWeight(.regular) 36 | .foregroundColor(Color(UIColor.systemGray)) 37 | .padding([.bottom], 5) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Cells/FileListCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct FileListCell_Previews: PreviewProvider { 4 | static var previews: some View { 5 | FileListCell(dataSource: FileCellDataSource(file: DocFile(name: "Some pdf", 6 | date: Date(), 7 | uid: UUID(), 8 | parent: Folder(name: "Some folder", 9 | date: Date(), 10 | isPasswordProtected: false, 11 | uid: UUID(), 12 | files: [])))) 13 | } 14 | } 15 | 16 | struct FileListCell: View { 17 | @StateObject var dataSource: FileCellDataSource 18 | 19 | var body: some View { 20 | HStack(alignment: .center) { 21 | Image(systemName: "newspaper.fill") 22 | .resizable() 23 | .foregroundColor(Color(UIColor.systemBlue)) 24 | .frame(maxWidth: 80, maxHeight: 60) 25 | VStack(alignment: .leading, spacing: 15) { 26 | Text(dataSource.file.name) 27 | .font(.headline) 28 | .fontWeight(.semibold) 29 | .foregroundColor(Color(UIColor.label)) 30 | .padding([.top, .leading, .trailing, .bottom], 5) 31 | .offset(x: 0, y: /*@START_MENU_TOKEN@*/10.0/*@END_MENU_TOKEN@*/) 32 | HStack { 33 | Text(dataSource.file.date.toString) 34 | .font(.caption) 35 | .fontWeight(.regular) 36 | .foregroundColor(Color(UIColor.systemGray)) 37 | .padding([.leading, .bottom], 5) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Cells/GridCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct GridCell_Previews: PreviewProvider { 4 | static var previews: some View { 5 | GridCell(dataSource: FolderCellDataSource(folder: Folder(name: "TestFolder", date: Date(), isPasswordProtected: false, uid: UUID(), files: [])), folderSelector: FolderSelector()) 6 | } 7 | } 8 | 9 | struct GridCell: View { 10 | @StateObject var dataSource: FolderCellDataSource 11 | 12 | @State private var isSelected: Bool = false 13 | 14 | var folderSelector: FolderSelector 15 | 16 | var body: some View { 17 | VStack(alignment: .center) { 18 | Image(systemName: "folder.fill") 19 | .resizable() 20 | .scaledToFit() 21 | .foregroundColor(isSelected ? Color(UIColor.systemRed) : Color(UIColor.systemBlue)) 22 | .frame(maxWidth: 80, maxHeight: 60) 23 | Text(dataSource.folder.name) 24 | .font(.subheadline) 25 | .fontWeight(.semibold) 26 | .lineLimit(1) 27 | .truncationMode(.tail) 28 | .foregroundColor(Color(UIColor.label)) 29 | .padding([.leading, .trailing], 5) 30 | VStack(alignment: .center) { 31 | Text(dataSource.folder.date.toString) 32 | .font(.caption) 33 | .fontWeight(.regular) 34 | .foregroundColor(Color(UIColor.systemGray)) 35 | Text("(\(dataSource.folder.files.count) \(dataSource.folder.files.count == 1 ? "item" : "items"))") 36 | .font(.caption) 37 | .fontWeight(.regular) 38 | .foregroundColor(Color(UIColor.systemGray)) 39 | .padding([.bottom], 5) 40 | } 41 | } 42 | .onReceive(folderSelector.publisher) { selection in 43 | isSelected = selection.id == dataSource.folder.uid.uuidString && selection.selected 44 | } 45 | } 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Cells/ListCell.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ListCell_Previews: PreviewProvider { 4 | static var previews: some View { 5 | ListCell(dataSource: FolderCellDataSource(folder: Folder(name: "TestFolder", date: Date(), isPasswordProtected: false, uid: UUID(), files: [])), folderSelector: FolderSelector()) 6 | } 7 | } 8 | 9 | struct ListCell: View { 10 | @StateObject var dataSource: FolderCellDataSource 11 | 12 | @State private var isSelected: Bool = false 13 | 14 | var folderSelector: FolderSelector 15 | 16 | var body: some View { 17 | HStack(alignment: .center) { 18 | Image(systemName: "folder.fill") 19 | .resizable() 20 | .foregroundColor(isSelected ? Color(UIColor.systemRed) : Color(UIColor.systemBlue)) 21 | .frame(maxWidth: 80, maxHeight: 60) 22 | VStack(alignment: .leading, spacing: 15) { 23 | Text(dataSource.folder.name) 24 | .font(.headline) 25 | .fontWeight(.semibold) 26 | .foregroundColor(Color(UIColor.label)) 27 | .padding([.top, .leading, .trailing, .bottom], 5) 28 | .offset(x: 0, y: /*@START_MENU_TOKEN@*/10.0/*@END_MENU_TOKEN@*/) 29 | HStack { 30 | Text(dataSource.folder.date.toString) 31 | .font(.caption) 32 | .fontWeight(.regular) 33 | .foregroundColor(Color(UIColor.systemGray)) 34 | .padding([.leading, .bottom], 5) 35 | Text("(\(dataSource.folder.files.count) \(dataSource.folder.files.count == 1 ? "item" : "items"))") 36 | .font(.caption) 37 | .fontWeight(.regular) 38 | .foregroundColor(Color(UIColor.systemGray)) 39 | .padding([.trailing, .bottom], 5) 40 | } 41 | } 42 | } 43 | .onReceive(folderSelector.publisher) { selection in 44 | isSelected = selection.id == dataSource.folder.uid.uuidString && selection.selected 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Custom/Presentation/EditorView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PDFKit 3 | import Combine 4 | 5 | struct EditorView: View { 6 | @EnvironmentObject var navStack: NavigationStack 7 | @StateObject var dataSource: PhotoCollectionDataSource 8 | 9 | @State var hudVisible = false 10 | @State var hudConfig = CustomProgressConfig() 11 | @Binding var backToCamera: Bool 12 | 13 | @State var goToOCRResults: Bool = false 14 | @State var recognizedText: String = "" 15 | @State var isPresentingFolderChooser: Bool = false 16 | 17 | @State private var pdfToSave: PDFDocument? 18 | @State private var folderToSaveIn: Folder? 19 | 20 | @State private var saveRequest: AnyCancellable? 21 | 22 | var body: some View { 23 | ZStack { 24 | VStack { 25 | if dataSource.scannedDocs.isEmpty { 26 | VStack { 27 | HStack(alignment: .center) { 28 | Text($dataSource.pageTitle.wrappedValue) 29 | .foregroundColor(Color(UIColor.label)) 30 | .padding(.top, 40) 31 | } 32 | Spacer() 33 | } 34 | .padding(EdgeInsets(top: 20, leading: 4, bottom: 10, trailing: 4)) 35 | } else { 36 | PhotoPager(dataSource: dataSource, 37 | cells: .init(array: dataSource.scannedDocs.map({ EditImageCell(dataSource: EditCellDataSource(scannedDoc: $0)) }))) 38 | } 39 | 40 | EditorTabBar(dataSource: EditTabBarDataSource(tools: Constants.editTools), photoDataSource: dataSource) 41 | } 42 | goBackButton 43 | .fullScreenCover(isPresented: $dataSource.isPresentingImagePicker, content: { 44 | ImagePicker(sourceType: dataSource.sourceType, completionHandler: dataSource.didSelectImage) 45 | }) 46 | showPhotoLibrary 47 | .sheet(isPresented: $isPresentingFolderChooser, onDismiss: { 48 | savePDFDocOnDismiss(asFileNamed: "\(folderToSaveIn?.name ?? "folder")-\(Date().toFileNameString)") 49 | }) { FoldersScreen(selectedFolder: $folderToSaveIn, calledFromSaving: true) } 50 | CustomProgressView($hudVisible, config: hudConfig) 51 | PushView(destination: OCRResultsView(message: $recognizedText), isActive: $goToOCRResults) { 52 | EmptyView() 53 | }.hidden() 54 | } 55 | .onReceive(dataSource.progressPublisher) { data in 56 | switch data.showProgressType { 57 | case .error: 58 | hudConfig.title = "Error!" 59 | hudConfig.errorImage = "xmark.circle" 60 | case .info: 61 | hudConfig.title = "Info" 62 | hudConfig.warningImage = "info.circle" 63 | case .success: 64 | hudConfig.title = "Success" 65 | hudConfig.successImage = "checkmark.circle" 66 | } 67 | hudConfig.caption = data.progressViewMessage 68 | hudVisible = data.showProgressView 69 | } 70 | .onReceive(dataSource.pdfGenerationPublisher) { data in 71 | pdfToSave = data.pdfDoc 72 | isPresentingFolderChooser = data.isPresentingFolderChooser 73 | } 74 | .onReceive(dataSource.dismissPublisher) { _ in 75 | print("Should be poped to camera") 76 | DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { 77 | backToCamera = true 78 | navStack.pop() 79 | } 80 | } 81 | .onReceive(dataSource.ocrResultPublisher) { result in 82 | recognizedText = result.recognizedText 83 | goToOCRResults = result.goToOCRResults 84 | } 85 | .onReceive(dataSource.selectionPublisher, perform: { _ in 86 | dataSource.currentPage = dataSource.selection 87 | print("Selection \(dataSource.selection), current page \(dataSource.currentPage)") 88 | }) 89 | .onDisappear { 90 | saveRequest?.cancel() 91 | saveRequest = nil 92 | } 93 | .edgesIgnoringSafeArea(.all) 94 | } 95 | 96 | private var showPhotoLibrary: some View { 97 | HStack { 98 | Spacer() 99 | Button(action: { 100 | dataSource.isPresentingImagePicker = true 101 | }, label: { 102 | Image(systemName: "photo.on.rectangle.angled") 103 | .resizable() 104 | .frame(width: 40, height: 30, alignment: .leading) 105 | .foregroundColor(.secondary) 106 | }) 107 | }.offset(x: -20, y: -380) 108 | } 109 | 110 | private var goBackButton: some View { 111 | HStack { 112 | Spacer() 113 | Button(action: { 114 | dataSource.recognitionRequest?.cancel() 115 | backToCamera = true 116 | navStack.pop() 117 | }, label: { 118 | HStack { 119 | Text("Back to camera") 120 | .font(.headline) 121 | .foregroundColor(.secondary) 122 | } 123 | }) 124 | }.offset(x: -275, y: -380) 125 | } 126 | 127 | private func savePDFDocOnDismiss(asFileNamed named: String) { 128 | if let doc = pdfToSave, let folder = folderToSaveIn { 129 | saveRequest = dataSource.save(pdfDoc: doc, namedAs: named, in: folder) 130 | .receive(on: DispatchQueue.main) 131 | .sink { saved in 132 | DispatchQueue.main.async { 133 | self.dataSource.selectedImages.removeAll() 134 | } 135 | print("pdf file saved in folder") 136 | } 137 | } 138 | } 139 | } 140 | 141 | struct PhotoPager: View { 142 | @ObservedObject var dataSource: PhotoCollectionDataSource 143 | @ObservedObject var cells: ObservableArray 144 | 145 | var body: some View { 146 | return VStack { 147 | HStack(alignment: .center) { 148 | Text($dataSource.pageTitle.wrappedValue) 149 | .foregroundColor(Color(UIColor.label)) 150 | .padding(.top, 40) 151 | } 152 | Pager(pages: cells, currentPage: $dataSource.selection) 153 | .tabViewStyle(PageTabViewStyle()) 154 | .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) 155 | } 156 | .padding(EdgeInsets(top: 20, leading: 4, bottom: 10, trailing: 4)) 157 | .border(Color(UIColor.systemPurple).opacity(0.8), width: 1) 158 | } 159 | } 160 | 161 | struct EditorView_Previews: PreviewProvider { 162 | static var mockedDocs: [ScannedDoc] { 163 | var docs: [ScannedDoc] = .init() 164 | let names = ["imageWithText", "slideWithText"] 165 | for _ in 0..<2 { 166 | names.forEach { 167 | docs.append(ScannedDoc(image: UIImage(named: $0)!.cgImage!, date: Date())) 168 | } 169 | } 170 | return docs 171 | } 172 | 173 | static var previews: some View { 174 | EditorView(dataSource: PhotoCollectionDataSource(scannedDocs: mockedDocs), backToCamera: .constant(false)) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Custom/Presentation/OCRResultsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct OCRResultsView: View { 4 | @EnvironmentObject var navStack: NavigationStack 5 | 6 | @Binding var message: String 7 | @State private var textStyle = UIFont.TextStyle.title2 8 | 9 | var body: some View { 10 | VStack { 11 | ZStack(alignment: .topTrailing) { 12 | TextView(text: $message, textStyle: $textStyle) 13 | .padding(.horizontal) 14 | .padding(.top, 60) 15 | .padding(.leading, 40) 16 | HStack { 17 | Button(action: { 18 | self.navStack.pop() 19 | }, label: { 20 | Image(systemName: "chevron.backward") 21 | .imageScale(.large) 22 | .frame(width: 40, height: 40) 23 | .foregroundColor(Color(UIColor.systemBackground)) 24 | .background(Color(UIColor.systemBlue)) 25 | .clipShape(Circle()) 26 | }) 27 | .padding() 28 | Spacer() 29 | Button(action: { 30 | self.textStyle = (self.textStyle == .title2) ? .headline : .title2 31 | }) { 32 | Image(systemName: "textformat") 33 | .imageScale(.large) 34 | .frame(width: 40, height: 40) 35 | .foregroundColor(Color(UIColor.systemBackground)) 36 | .background(Color(UIColor.systemBlue)) 37 | .clipShape(Circle()) 38 | 39 | } 40 | .padding() 41 | } 42 | } 43 | } 44 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 45 | .background(Color(.systemBackground).opacity(0.2)) 46 | .navigationBarTitle("Results", displayMode: .inline) 47 | .edgesIgnoringSafeArea(.bottom) 48 | } 49 | } 50 | 51 | struct OCRResultsView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | OCRResultsView(message: .constant("But how do you present it? Since SwiftUI is a declarative UI framework, you don’t present is by reacting to a user action in a callback. Instead, you declare under which state it should be presented. Remember: A SwiftUI view is a function of its state.")) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/Custom/Presentation/PhotoCollectionView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PhotoCollectionView: View { 4 | @StateObject var dataSource: PhotoCollectionDataSource 5 | 6 | typealias DocRow = CollectionRow 7 | typealias ToolRow = CollectionRow 8 | 9 | @State var docRows: [DocRow] = .init() 10 | @State var toolsRow: [ToolRow] = .init() 11 | 12 | private func composeElements() { 13 | // let result = dataSource.scannedDocs.chunked(into: 5) 14 | let result = mockedDocs.chunked(into: 5) 15 | var section = 0 16 | result.forEach { 17 | docRows.append(DocRow(section: section, items: $0)) 18 | section += 1 19 | } 20 | } 21 | 22 | private func composeTools() { 23 | toolsRow.append(ToolRow(section: 0, items: dataSource.tools)) 24 | } 25 | 26 | private var mockedDocs: [ScannedDoc] { 27 | var docs: [ScannedDoc] = .init() 28 | let names = ["imageWithText", "slideWithText"] 29 | for _ in 0..<2 { 30 | names.forEach { 31 | docs.append(ScannedDoc(image: UIImage(named: $0)!.cgImage!, date: Date())) 32 | } 33 | } 34 | return docs 35 | } 36 | 37 | var body: some View { 38 | VStack { 39 | CollectionView(rows: docRows) { sectionIndex, layoutEnvironment in 40 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1.0)) 41 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 42 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(0.5)) 43 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3) 44 | let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 45 | heightDimension: .absolute(40)), 46 | elementKind: UICollectionView.elementKindSectionHeader, 47 | alignment: .topLeading) 48 | let section = NSCollectionLayoutSection(group: group) 49 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) 50 | section.interGroupSpacing = 10 51 | section.boundarySupplementaryItems = [header] 52 | return section 53 | } cell: { indexPath, item in 54 | EditImageCell(dataSource: EditCellDataSource(scannedDoc: item)) 55 | } supplementaryView: { kind, indexPath in 56 | Text("Section \(indexPath.section)") 57 | } 58 | .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height - 90) 59 | .ignoresSafeArea(.all) 60 | .onAppear(perform: composeElements) 61 | 62 | Spacer() 63 | 64 | CollectionView(rows: toolsRow) { sectionIndex, layoutEnvironment in 65 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.25), heightDimension: .fractionalHeight(1.0)) 66 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 67 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1)) 68 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 69 | let section = NSCollectionLayoutSection(group: group) 70 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) 71 | section.orthogonalScrollingBehavior = .none 72 | return section 73 | } cell: { indexPath, item in 74 | EditToolCell(dataSource: EditToolCellDataSource(tool: item), photoDataSource: dataSource) 75 | } supplementaryView: { kind, indexPath in 76 | Text("Section \(indexPath.section)") 77 | } 78 | .frame(maxWidth: .infinity, maxHeight: 90) 79 | .ignoresSafeArea(.all) 80 | .onAppear(perform: composeTools) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/DataSource/EditCellDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | final class EditCellDataSource: ObservableObject { 4 | @Published private(set) var scannedDoc: ScannedDoc 5 | 6 | init(scannedDoc: ScannedDoc) { 7 | self.scannedDoc = scannedDoc 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/DataSource/EditToolCellDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | final class EditToolCellDataSource: ObservableObject { 4 | @Published private(set) var editTool: EditTool 5 | 6 | init(tool: EditTool) { 7 | self.editTool = tool 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/DataSource/FileCellDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | final class FileCellDataSource: ObservableObject { 4 | @Published private(set) var file: DocFile 5 | 6 | init(file: DocFile) { 7 | self.file = file 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scanverter/Views/Collections/DataSource/FolderCellDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | final class FolderCellDataSource: ObservableObject { 4 | @Published private(set) var folder: Folder 5 | 6 | init(folder: Folder) { 7 | self.folder = folder 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/ActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ActivityIndicator: UIViewRepresentable { 4 | typealias UIViewType = UIActivityIndicatorView 5 | 6 | let style: UIActivityIndicatorView.Style 7 | 8 | func makeUIView(context: UIViewRepresentableContext) -> ActivityIndicator.UIViewType { 9 | return UIActivityIndicatorView(style: style) 10 | } 11 | 12 | func updateUIView(_ uiView: ActivityIndicator.UIViewType, context: UIViewRepresentableContext) { 13 | uiView.startAnimating() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/CustomProgressConfig.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct CustomProgressConfig: Hashable { 4 | var type = CustomProgressType.loading 5 | var title: String? 6 | var caption: String? 7 | 8 | var minSize: CGSize 9 | var cornerRadius: CGFloat 10 | 11 | var backgroundColor: Color 12 | 13 | var titleForegroundColor: Color 14 | var captionForegroundColor: Color 15 | 16 | var shadowColor: Color 17 | var shadowRadius: CGFloat 18 | 19 | var borderColor: Color 20 | var borderWidth: CGFloat 21 | 22 | var lineWidth: CGFloat 23 | 24 | // indefinite animated view and image share the size 25 | var imageViewSize: CGSize 26 | var imageViewForegroundColor: Color 27 | 28 | var successImage: String 29 | var warningImage: String 30 | var errorImage: String 31 | 32 | // Auto hide 33 | var shouldAutoHide: Bool 34 | var allowsTapToHide: Bool 35 | var autoHideInterval: TimeInterval 36 | 37 | // Haptics 38 | var hapticsEnabled: Bool 39 | 40 | public init( 41 | type: CustomProgressType = .loading, 42 | title: String? = nil, 43 | caption: String? = nil, 44 | minSize: CGSize = CGSize(width: 100.0, height: 100.0), 45 | cornerRadius: CGFloat = 12.0, 46 | backgroundColor: Color = .clear, 47 | titleForegroundColor: Color = .primary, 48 | captionForegroundColor: Color = .secondary, 49 | shadowColor: Color = .clear, 50 | shadowRadius: CGFloat = 0.0, 51 | borderColor: Color = .clear, 52 | borderWidth: CGFloat = 0.0, 53 | lineWidth: CGFloat = 10.0, 54 | imageViewSize: CGSize = CGSize(width: 100, height: 100), 55 | imageViewForegroundColor: Color = .primary, 56 | successImage: String = "checkmark.circle", 57 | warningImage: String = "exclamationmark.circle", 58 | errorImage: String = "xmark.circle", 59 | shouldAutoHide: Bool = false, 60 | allowsTapToHide: Bool = false, 61 | autoHideInterval: TimeInterval = 10.0, 62 | hapticsEnabled: Bool = true 63 | ) { 64 | self.type = type 65 | 66 | self.title = title 67 | self.caption = caption 68 | 69 | self.minSize = minSize 70 | self.cornerRadius = cornerRadius 71 | 72 | self.backgroundColor = backgroundColor 73 | 74 | self.titleForegroundColor = titleForegroundColor 75 | self.captionForegroundColor = captionForegroundColor 76 | 77 | self.shadowColor = shadowColor 78 | self.shadowRadius = shadowRadius 79 | 80 | self.borderColor = borderColor 81 | self.borderWidth = borderWidth 82 | 83 | self.lineWidth = lineWidth 84 | 85 | self.imageViewSize = imageViewSize 86 | self.imageViewForegroundColor = imageViewForegroundColor 87 | 88 | self.successImage = successImage 89 | self.warningImage = warningImage 90 | self.errorImage = errorImage 91 | 92 | self.shouldAutoHide = shouldAutoHide 93 | self.allowsTapToHide = allowsTapToHide 94 | self.autoHideInterval = autoHideInterval 95 | 96 | self.hapticsEnabled = hapticsEnabled 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/CustomProgressView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import SwiftUIVisualEffects 3 | 4 | public enum CustomProgressType { 5 | case loading 6 | case success 7 | case warning 8 | case error 9 | } 10 | 11 | private struct IndefiniteAnimatedView: View { 12 | var animatedViewSize: CGSize 13 | var animatedViewForegroundColor: Color 14 | 15 | var lineWidth: CGFloat 16 | 17 | @State private var isAnimating = false 18 | 19 | private var foreverAnimation: Animation { 20 | Animation.linear(duration: 2.0) 21 | .repeatForever(autoreverses: false) 22 | } 23 | 24 | var body: some View { 25 | let gradient = Gradient(colors: [animatedViewForegroundColor, .clear]) 26 | let radGradient = AngularGradient(gradient: gradient, center: .center, angle: .degrees(-5)) 27 | 28 | Circle() 29 | .trim(from: 0.0, to: 0.97) 30 | .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)) 31 | .fill(radGradient) 32 | .frame(width: animatedViewSize.width-lineWidth/2, height: animatedViewSize.height-lineWidth/2) 33 | .rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0.0)) 34 | .animation(self.isAnimating ? foreverAnimation : .default) 35 | .padding(lineWidth/2) 36 | .onAppear { 37 | self.isAnimating = true 38 | } 39 | .onDisappear { 40 | self.isAnimating = false 41 | } 42 | } 43 | } 44 | 45 | private struct ImageView: View { 46 | var type: CustomProgressType 47 | 48 | var imageViewSize: CGSize 49 | var imageViewForegroundColor: Color 50 | 51 | var successImage: String 52 | var warningImage: String 53 | var errorImage: String 54 | 55 | var body: some View { 56 | imageForHUDType? 57 | .resizable() 58 | .frame(width: imageViewSize.width, height: imageViewSize.height) 59 | .foregroundColor(imageViewForegroundColor.opacity(0.8)) 60 | } 61 | 62 | var imageForHUDType: Image? { 63 | switch type { 64 | case .success: 65 | return Image(systemName: successImage) 66 | case .warning: 67 | return Image(systemName: warningImage) 68 | case .error: 69 | return Image(systemName: errorImage) 70 | default: 71 | return nil 72 | } 73 | } 74 | } 75 | 76 | private struct LabelView: View { 77 | var title: String? 78 | var caption: String? 79 | 80 | var body: some View { 81 | VStack(spacing: 4) { 82 | if let title = title { 83 | Text(title) 84 | .font(.system(size: 21.0, weight: .semibold)) 85 | .lineLimit(1) 86 | .foregroundColor(.primary) 87 | } 88 | if let caption = caption { 89 | Text(caption) 90 | .lineLimit(2) 91 | .font(.headline) 92 | .foregroundColor(.secondary) 93 | } 94 | } 95 | .multilineTextAlignment(.center) 96 | .vibrancyEffect() 97 | .vibrancyEffectStyle(.fill) 98 | } 99 | } 100 | 101 | public struct CustomProgressView: View { 102 | @Binding var isVisible: Bool 103 | var config: CustomProgressConfig 104 | 105 | @Environment(\.colorScheme) private var colorScheme 106 | 107 | public init(_ isVisible: Binding, config: CustomProgressConfig) { 108 | self._isVisible = isVisible 109 | self.config = config 110 | } 111 | 112 | public var body: some View { 113 | let hideTimer = Timer.publish(every: config.autoHideInterval, on: .main, in: .common).autoconnect() 114 | 115 | GeometryReader { geometry in 116 | ZStack { 117 | if isVisible { 118 | config.backgroundColor 119 | .edgesIgnoringSafeArea(.all) 120 | 121 | ZStack { 122 | Color.white 123 | .blurEffect() 124 | .blurEffectStyle(.systemChromeMaterial) 125 | 126 | VStack(spacing: 20) { 127 | if config.type == .loading { 128 | IndefiniteAnimatedView(animatedViewSize: config.imageViewSize, 129 | animatedViewForegroundColor: config.imageViewForegroundColor, 130 | lineWidth: config.lineWidth) 131 | } else { 132 | ImageView(type: config.type, 133 | imageViewSize: config.imageViewSize, 134 | imageViewForegroundColor: config.imageViewForegroundColor, 135 | successImage: config.successImage, 136 | warningImage: config.warningImage, 137 | errorImage: config.errorImage) 138 | } 139 | LabelView(title: config.title, caption: config.caption) 140 | }.padding() 141 | } 142 | .overlay( 143 | // Fix required since .border can not be used with 144 | // RoundedRectangle clip shape 145 | RoundedRectangle(cornerRadius: config.cornerRadius) 146 | .stroke(config.borderColor, lineWidth: config.borderWidth) 147 | ) 148 | .aspectRatio(1, contentMode: .fit) 149 | .padding(geometry.size.width / 7) 150 | .shadow(color: config.shadowColor, radius: config.shadowRadius) 151 | } 152 | } 153 | .animation(.spring()) 154 | .onTapGesture { 155 | if config.allowsTapToHide { 156 | withAnimation { 157 | isVisible = false 158 | } 159 | } 160 | } 161 | .onReceive(hideTimer) { _ in 162 | if config.shouldAutoHide { 163 | withAnimation { 164 | isVisible = false 165 | } 166 | } 167 | // Only one call required 168 | hideTimer.upstream.connect().cancel() 169 | } 170 | .onAppear { 171 | if config.hapticsEnabled { 172 | generateHapticNotification(for: config.type) 173 | } 174 | } 175 | } 176 | } 177 | 178 | func generateHapticNotification(for type: CustomProgressType) { 179 | let generator = UINotificationFeedbackGenerator() 180 | generator.prepare() 181 | 182 | switch type { 183 | case .success: 184 | generator.notificationOccurred(.success) 185 | case .warning: 186 | generator.notificationOccurred(.warning) 187 | case .error: 188 | generator.notificationOccurred(.error) 189 | default: 190 | return 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/CustomTextView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TextView: UIViewRepresentable { 4 | 5 | @Binding var text: String 6 | @Binding var textStyle: UIFont.TextStyle 7 | 8 | func makeUIView(context: Context) -> UITextView { 9 | let textView = UITextView() 10 | 11 | textView.delegate = context.coordinator 12 | textView.font = UIFont.preferredFont(forTextStyle: textStyle) 13 | textView.autocapitalizationType = .sentences 14 | textView.isSelectable = true 15 | textView.isUserInteractionEnabled = true 16 | textView.textAlignment = .justified 17 | 18 | return textView 19 | } 20 | 21 | func updateUIView(_ uiView: UITextView, context: Context) { 22 | uiView.text = text 23 | uiView.font = UIFont.preferredFont(forTextStyle: textStyle) 24 | } 25 | 26 | func makeCoordinator() -> Coordinator { 27 | Coordinator($text) 28 | } 29 | 30 | class Coordinator: NSObject, UITextViewDelegate { 31 | var text: Binding 32 | 33 | init(_ text: Binding) { 34 | self.text = text 35 | } 36 | 37 | func textViewDidChange(_ textView: UITextView) { 38 | self.text.wrappedValue = textView.text 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import UIKit 3 | 4 | struct ImagePicker: UIViewControllerRepresentable { 5 | typealias UIViewControllerType = UIImagePickerController 6 | typealias SourceType = UIImagePickerController.SourceType 7 | 8 | let sourceType: SourceType 9 | let completionHandler: (UIImage?) -> Void 10 | 11 | func makeUIViewController(context: Context) -> UIImagePickerController { 12 | let viewController = UIImagePickerController() 13 | viewController.delegate = context.coordinator 14 | viewController.sourceType = sourceType 15 | return viewController 16 | } 17 | 18 | func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} 19 | 20 | func makeCoordinator() -> Coordinator { 21 | return Coordinator(completionHandler: completionHandler) 22 | } 23 | 24 | final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 25 | let completionHandler: (UIImage?) -> Void 26 | 27 | init(completionHandler: @escaping (UIImage?) -> Void) { 28 | self.completionHandler = completionHandler 29 | } 30 | 31 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { 32 | let image: UIImage? = { 33 | if let image = info[.editedImage] as? UIImage { 34 | return image 35 | } 36 | return info[.originalImage] as? UIImage 37 | }() 38 | completionHandler(image) 39 | } 40 | 41 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 42 | completionHandler(nil) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/PDFViewer.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import PDFKit 3 | 4 | struct PDFViewverUI: View { 5 | @EnvironmentObject var navStack: NavigationStack 6 | 7 | var url: URL 8 | 9 | var body: some View { 10 | VStack { 11 | PDFViewer(url) 12 | } 13 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 14 | .navigationBarTitle("PDF Viewer", displayMode: .inline) 15 | .navigationBarItems(leading: backButton, 16 | trailing: trailingGroup) 17 | .edgesIgnoringSafeArea(.bottom) 18 | 19 | } 20 | 21 | private var backButton: some View { 22 | Button(action: { self.navStack.pop() }, label: { 23 | HStack { 24 | Image(systemName: "chevron.backward") 25 | .foregroundColor(Color(UIColor.label)) 26 | Text("Back") 27 | .font(.callout) 28 | .foregroundColor(Color(UIColor.label)) 29 | } 30 | }) 31 | } 32 | 33 | private var trailingGroup: some View { 34 | Button(action: {}, label: { }) 35 | } 36 | } 37 | 38 | struct PDFViewer: UIViewRepresentable { 39 | var url: URL 40 | 41 | init(_ url: URL) { 42 | self.url = url 43 | } 44 | 45 | func makeUIView(context: Context) -> UIView { 46 | let pdfView = PDFView() 47 | pdfView.document = PDFDocument(url: url) 48 | pdfView.autoScales = true 49 | return pdfView 50 | } 51 | 52 | func updateUIView(_ uiView: UIView, context: Context) {} 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/PageView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PageView_Previews: PreviewProvider { 4 | static var previews: some View { 5 | PageView(selection: .constant(0), content: { Text("Some text") }) 6 | } 7 | } 8 | 9 | struct PageView: View where SelectionValue: Hashable, Content: View { 10 | @Binding var selection: SelectionValue 11 | private let indexDisplayMode: PageTabViewStyle.IndexDisplayMode 12 | private let indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode 13 | private let content: () -> Content 14 | 15 | init(selection: Binding, 16 | indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic, 17 | indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode = .automatic, 18 | @ViewBuilder content: @escaping () -> Content) { 19 | 20 | self._selection = selection 21 | self.indexDisplayMode = indexDisplayMode 22 | self.indexBackgroundDisplayMode = indexBackgroundDisplayMode 23 | self.content = content 24 | } 25 | 26 | var body: some View { 27 | TabView(selection: $selection) { content() } 28 | .tabViewStyle(PageTabViewStyle(indexDisplayMode: indexDisplayMode)) 29 | .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: indexBackgroundDisplayMode)) 30 | } 31 | } 32 | 33 | extension PageView where SelectionValue == Int { 34 | init(selection: Binding, indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic, 35 | indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode = .automatic, 36 | @ViewBuilder content: @escaping () -> Content) { 37 | 38 | self._selection = selection 39 | self.indexDisplayMode = indexDisplayMode 40 | self.indexBackgroundDisplayMode = indexBackgroundDisplayMode 41 | self.content = content 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/Pager.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct Pager: UIViewControllerRepresentable { 4 | var pages: ObservableArray 5 | @Binding var currentPage: Int 6 | 7 | func makeUIViewController(context: Context) -> UIPageViewController { 8 | let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal) 9 | 10 | pageViewController.dataSource = context.coordinator 11 | pageViewController.delegate = context.coordinator 12 | 13 | return pageViewController 14 | } 15 | 16 | func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) { 17 | var direction: UIPageViewController.NavigationDirection = .forward 18 | var animated: Bool = false 19 | 20 | if let previousViewController = pageViewController.viewControllers?.first, 21 | let previousPage = context.coordinator.controllers.firstIndex(of: previousViewController) { 22 | direction = (currentPage >= previousPage) ? .forward : .reverse 23 | animated = (currentPage != previousPage) 24 | } 25 | 26 | let currentViewController = context.coordinator.controllers[currentPage] 27 | pageViewController.setViewControllers([currentViewController], direction: direction, animated: animated) 28 | } 29 | 30 | func makeCoordinator() -> Coordinator { 31 | return Coordinator(parent: self, pages: pages) 32 | } 33 | 34 | class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { 35 | 36 | var parent: Pager 37 | var controllers: [UIViewController] 38 | 39 | init(parent: Pager, pages: ObservableArray) { 40 | self.parent = parent 41 | self.controllers = pages.array.map({ 42 | let hostingController = UIHostingController(rootView: $0) 43 | hostingController.view.backgroundColor = .clear 44 | return hostingController 45 | }) 46 | } 47 | 48 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { 49 | guard let index = controllers.firstIndex(of: viewController) else { 50 | return nil 51 | } 52 | if index == 0 { 53 | return nil 54 | } 55 | return controllers[index - 1] 56 | } 57 | 58 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { 59 | guard let index = controllers.firstIndex(of: viewController) else { 60 | return nil 61 | } 62 | if index + 1 == controllers.count { 63 | return nil 64 | } 65 | return controllers[index + 1] 66 | } 67 | 68 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { 69 | if completed, let currentViewController = pageViewController.viewControllers?.first, 70 | let currentIndex = controllers.firstIndex(of: currentViewController) { 71 | parent.currentPage = currentIndex 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/SearchBar.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SearchBar: UIViewRepresentable { 4 | @Binding var text: String 5 | 6 | var placeholder: String 7 | 8 | class Coordinator: NSObject, UISearchBarDelegate { 9 | @Binding var text: String 10 | 11 | init(text: Binding) { 12 | self._text = text 13 | } 14 | 15 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 16 | self.text = searchText 17 | } 18 | } 19 | 20 | func makeCoordinator() -> SearchBar.Coordinator { 21 | return Coordinator(text: $text) 22 | } 23 | 24 | func makeUIView(context: UIViewRepresentableContext) -> UISearchBar { 25 | let searchBar = UISearchBar(frame: .zero) 26 | searchBar.delegate = context.coordinator 27 | searchBar.placeholder = placeholder 28 | searchBar.searchBarStyle = .minimal 29 | searchBar.autocapitalizationType = .none 30 | return searchBar 31 | } 32 | 33 | func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext) { 34 | uiView.text = text 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scanverter/Views/CustomViews/TextScannerView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Vision 3 | import VisionKit 4 | 5 | typealias recognizingHandler = ([String]?) -> Void 6 | 7 | struct TextScannerView: UIViewControllerRepresentable { 8 | private let completionHandler: recognizingHandler 9 | 10 | init(completion: @escaping recognizingHandler) { 11 | self.completionHandler = completion 12 | } 13 | 14 | typealias UIViewControllerType = VNDocumentCameraViewController 15 | 16 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> VNDocumentCameraViewController { 17 | let viewController = VNDocumentCameraViewController() 18 | viewController.delegate = context.coordinator 19 | return viewController 20 | } 21 | 22 | func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: UIViewControllerRepresentableContext) {} 23 | 24 | func makeCoordinator() -> Coordinator { 25 | return Coordinator(completion: completionHandler) 26 | } 27 | 28 | final class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate { 29 | private let completionHandler: ([String]?) -> Void 30 | 31 | init(completion: @escaping ([String]?) -> Void) { 32 | self.completionHandler = completion 33 | } 34 | 35 | func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) { 36 | print("Document camera view controller did finish with ", scan) 37 | let recognizer = TextRecognizer(cameraScan: scan) 38 | recognizer.recognizeText(withCompletionHandler: completionHandler) 39 | } 40 | 41 | func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) { 42 | completionHandler(nil) 43 | } 44 | 45 | func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) { 46 | print("Document camera view controller did finish with error ", error) 47 | completionHandler(nil) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/DataSources/DocsDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import PDFKit 3 | 4 | typealias Files = [DocFile] 5 | 6 | final class DocsDataSource: ObservableObject { 7 | @Published var files: Files = .init() 8 | 9 | private var subscriptions: Set = .init() 10 | 11 | init(files: Files) { 12 | self.files = files 13 | } 14 | 15 | @discardableResult 16 | func remove(indexSet: IndexSet?) -> AnyPublisher { 17 | return Future { promise in 18 | 19 | }.eraseToAnyPublisher() 20 | } 21 | 22 | func getUrl(forDoc doc: DocFile) -> URL? { 23 | return DataManager.getUrlForDoc(fromFolder: doc.parent.uid.uuidString, withFileName: doc.name) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/DataSources/FoldersDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | import Foundation 3 | import PDFKit 4 | 5 | struct FolderSelection { 6 | var id: String = "" 7 | var selected: Bool = false 8 | } 9 | 10 | final class FolderSelector: ObservableObject { 11 | public let publisher = PassthroughSubject() 12 | var selection: FolderSelection = FolderSelection() { 13 | willSet { 14 | publisher.send(selection) 15 | } 16 | } 17 | } 18 | 19 | final class FoldersDataSource: ObservableObject { 20 | @Published var folders: [Folder] = .init() 21 | var folderSelector: FolderSelector = .init() 22 | 23 | private var subscriptions: Set = .init() 24 | 25 | func loadFolders() { 26 | folders.removeAll() 27 | DataManager.loadAll(Folder.self).forEach { folders.append($0) } 28 | folders.sort { $0.date < $1.date } 29 | print(folders) 30 | } 31 | 32 | @discardableResult 33 | func createNewFolder(withName name: String, secureLock: Bool = false) -> AnyPublisher { 34 | return Future { promise in 35 | let folder = Folder(name: name, date: Date(), isPasswordProtected: secureLock, uid: UUID(), files: []) 36 | folder.save() 37 | .receive(on: DispatchQueue.main) 38 | .sink { saved in 39 | if saved { 40 | self.loadFolders() 41 | } 42 | promise(.success(saved)) 43 | } 44 | .store(in: &self.subscriptions) 45 | }.eraseToAnyPublisher() 46 | } 47 | 48 | @discardableResult 49 | func remove(indexSet: IndexSet?) -> AnyPublisher { 50 | return Future { promise in 51 | guard let index = indexSet?.first else { promise(.success(false)); return } 52 | let folder = self.folders[index] 53 | folder.delete(isDirectory: true) 54 | .receive(on: DispatchQueue.main) 55 | .sink { deleted in 56 | if deleted { 57 | self.folders.remove(at: index) 58 | } 59 | promise(.success(deleted)) 60 | } 61 | .store(in: &self.subscriptions) 62 | }.eraseToAnyPublisher() 63 | } 64 | 65 | func setSelected(folder: Folder) { 66 | for var item in self.folders { 67 | if item.uid == folder.uid { 68 | item.selected.toggle() 69 | } else { 70 | item.selected = false 71 | } 72 | item.save() 73 | .receive(on: DispatchQueue.main) 74 | .sink { _ in 75 | self.loadFolders() 76 | } 77 | .store(in: &subscriptions) 78 | } 79 | folderSelector.selection = FolderSelection(id: folder.uid.uuidString, selected: !folder.selected) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/DataSources/SearchDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | final class SearchDataSource: ObservableObject { 4 | @Published var files: [DocFile] = .init() 5 | 6 | func loadAll() { 7 | let folders = DataManager.loadAll(Folder.self) 8 | folders 9 | .map { $0.files } 10 | .flatMap { $0 } 11 | .forEach { file in files.append(file)} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/DataSources/SettingsDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | class SettingsDataSource: ObservableObject { 4 | var didChange = PassthroughSubject() 5 | 6 | @Published var isBluetoothOn = false { didSet { update() } } 7 | 8 | @Published var types = ["Off","On"] 9 | @Published var type = 0 { didSet { update() } } 10 | 11 | @Published var isToggleOn = false { didSet { update() } } 12 | 13 | func update() { 14 | didChange.send(()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/DataSources/TextRecognizer.swift: -------------------------------------------------------------------------------- 1 | import Vision 2 | import VisionKit 3 | import PDFKit 4 | import Combine 5 | 6 | final class TextRecognizer { 7 | let cameraScan: VNDocumentCameraScan 8 | 9 | private var subscriptions: Set = .init() 10 | 11 | init(cameraScan: VNDocumentCameraScan) { 12 | self.cameraScan = cameraScan 13 | } 14 | 15 | private let queue = DispatchQueue(label: "ru.otus.scanverter", qos: .default, attributes: [], autoreleaseFrequency: .workItem) 16 | 17 | func recognizeText(withCompletionHandler completionHandler: @escaping ([String]) -> Void) { 18 | queue.async { 19 | let images = (0.. String in 22 | let handler = VNImageRequestHandler(cgImage: image, options: [:]) 23 | do { 24 | try handler.perform([request]) 25 | guard let observations = request.results as? [VNRecognizedTextObservation] else { return "" } 26 | return observations.compactMap({ $0.topCandidates(1).first?.string }).joined(separator: "\n") 27 | } 28 | catch { 29 | print(error) 30 | return "" 31 | } 32 | } 33 | self.generatePdf() 34 | DispatchQueue.main.async { 35 | completionHandler(textPerPage) 36 | } 37 | } 38 | } 39 | 40 | func generatePdf() { 41 | let pdfDocument = PDFDocument() 42 | for i in 0 ..< self.cameraScan.pageCount { 43 | if let image = self.cameraScan.imageOfPage(at: i).resize(toWidth: UIScreen.main.bounds.width - 40) { 44 | print("image size is \(image.size.width), \(image.size.height)") 45 | let pdfPage = PDFPage(image: image) 46 | pdfDocument.insert(pdfPage!, at: i) 47 | } 48 | } 49 | var folderToSave = Folder(name: "ScannedByVision", date: Date(), isPasswordProtected: false, uid: UUID(), files: []) 50 | folderToSave.save() 51 | .receive(on: DispatchQueue.main) 52 | .sink { done in 53 | if !done { return } 54 | let file = DocFile(name: "vk-\(Date().toFileNameString)", date: Date(), uid: UUID(), parent: folderToSave) 55 | DataManager.save(pdf: pdfDocument, withFileName: file.name, inFolder: folderToSave.uid.uuidString) 56 | .receive(on: DispatchQueue.main) 57 | .sink { saved in 58 | folderToSave.files.append(file) 59 | folderToSave.save() 60 | } 61 | .store(in: &self.subscriptions) 62 | } 63 | .store(in: &subscriptions) 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/Screens/CurrentScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CurrentScreen: View { 4 | @Binding var currentView: Tab 5 | 6 | var body: some View { 7 | VStack { 8 | if self.currentView == .folders { 9 | FoldersScreen(selectedFolder: .constant(nil), calledFromSaving: false) 10 | } else { 11 | SettingsScreen() 12 | } 13 | } 14 | } 15 | } 16 | 17 | struct CurrentScreen_Previews: PreviewProvider { 18 | static var previews: some View { 19 | CurrentScreen(currentView: .constant(.folders)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/Screens/FolderDetailScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import ExyteGrid 3 | import PDFKit 4 | 5 | struct FolderDetailScreen: View { 6 | @EnvironmentObject var navStack: NavigationStack 7 | 8 | @StateObject var dataSource: DocsDataSource 9 | @State private var flow: GridFlow = .rows 10 | 11 | @State private var showDeleteAlert: Bool = false 12 | @State private var offsets: IndexSet? 13 | 14 | var body: some View { 15 | NavigationStackView { 16 | GeometryReader { geometry in 17 | ZStack { 18 | VStack { 19 | if flow == .rows { 20 | filesGrid 21 | } else { 22 | filesList 23 | } 24 | } 25 | } 26 | .alert(isPresented: self.$showDeleteAlert) { 27 | Alert(title: Text("Deletion Alert!"), 28 | message: Text("You're about to delete the file."), 29 | primaryButton: .destructive(Text("Delete")) { 30 | dataSource.remove(indexSet: offsets) 31 | }, 32 | secondaryButton: .cancel()) 33 | } 34 | } 35 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 36 | .navigationBarTitle("Files (Scanned)", displayMode: .inline) 37 | .navigationBarItems(leading: backButton, 38 | trailing: trailingGroup) 39 | .edgesIgnoringSafeArea(.bottom) 40 | } 41 | } 42 | 43 | private var backButton: some View { 44 | Button(action: { self.navStack.pop() }, label: { 45 | HStack { 46 | Image(systemName: "chevron.backward") 47 | .foregroundColor(Color(UIColor.label)) 48 | Text("Back") 49 | .font(.callout) 50 | .foregroundColor(Color(UIColor.label)) 51 | } 52 | }) 53 | } 54 | 55 | private var trailingGroup: some View { 56 | Button(action: {}, label: { }) 57 | } 58 | 59 | private var filesGrid: some View { 60 | VStack { 61 | Grid(tracks: 3) { 62 | ForEach(dataSource.files, id: \.uid) { item in 63 | VStack { 64 | PushView(destination: PDFViewverUI(url: dataSource.getUrl(forDoc: item)!)) { 65 | FileGridCell(dataSource: FileCellDataSource(file: item)) 66 | } 67 | } 68 | .padding(EdgeInsets(top: 20, leading: 4, bottom: 0, trailing: 4)) 69 | } 70 | } 71 | .padding(.top, 120) 72 | .gridContentMode(.scroll) 73 | .gridFlow(.rows) 74 | Spacer() 75 | 76 | } 77 | } 78 | 79 | private var filesList: some View { 80 | List { 81 | ForEach(dataSource.files, id: \.uid) { item in 82 | HStack { 83 | FileListCell(dataSource: FileCellDataSource(file: item)) 84 | } 85 | .padding(EdgeInsets(top: 20, leading: 4, bottom: 0, trailing: 2)) 86 | } 87 | .onDelete(perform: delete) 88 | } 89 | .padding(.top, 90) 90 | .listStyle(PlainListStyle()) 91 | } 92 | 93 | func delete(at offsets: IndexSet) { 94 | self.showDeleteAlert = true 95 | self.offsets = offsets 96 | } 97 | } 98 | 99 | struct FolderDetailScreen_Previews: PreviewProvider { 100 | static var previews: some View { 101 | DetailScreen() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/Screens/MainTabBarView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct MainTabBarView: View { 4 | @State private var currentView: Tab = .folders 5 | @State private var showCameraModal: Bool = false 6 | @State private var showMicModal: Bool = false 7 | @State private var showSearchModal: Bool = false 8 | 9 | init() { 10 | UINavigationBar.appearance().backgroundColor = .clear 11 | } 12 | 13 | var body: some View { 14 | NavigationView { 15 | VStack { 16 | CurrentScreen(currentView: self.$currentView) 17 | .fullScreenCover(isPresented: self.$showMicModal, content: TextRecognizerScreen.init) 18 | TabBar(currentView: self.$currentView, 19 | showCameraModal: self.$showCameraModal, 20 | showMicModal: self.$showMicModal, 21 | showSearchModal: self.$showSearchModal) 22 | .fullScreenCover(isPresented: self.$showCameraModal, content: { ModalCamera(model: CameraViewModel()) }) 23 | } 24 | .edgesIgnoringSafeArea(.all) 25 | } 26 | .background(Color(.white)) 27 | .navigationViewStyle(StackNavigationViewStyle()) 28 | .fullScreenCover(isPresented: self.$showSearchModal, content: ModalSearchScreen.init) 29 | } 30 | } 31 | 32 | struct ContentView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | MainTabBarView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/Screens/SettingsScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SettingsScreen : View { 4 | @ObservedObject var settings = SettingsDataSource() 5 | 6 | var body: some View { 7 | Form { 8 | Section { 9 | SignInView() 10 | } 11 | Section { 12 | BluetoothView(bluetooth: settings) 13 | WiFiView(wifi: settings) 14 | } 15 | ForEach(Option.options,id: \.id) { settingOption in 16 | OptionRow(option: settingOption) 17 | } 18 | } 19 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 20 | .background(Color(.purple).opacity(0.2)) 21 | .navigationBarTitle("Settings", displayMode: .inline) 22 | .navigationBarItems(leading: Button(action: {}, label: { }), 23 | trailing: Button(action: {}, label: { })) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scanverter/Views/Main/Screens/SettingsScreenTempleate.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SettingsTemplateScreen: View { 4 | var body: some View { 5 | VStack { 6 | Spacer() 7 | HStack { 8 | Spacer() 9 | Text("Settings Screen") 10 | .font(.system(size: 20)) 11 | .bold() 12 | Spacer() 13 | } 14 | Spacer() 15 | } 16 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 17 | .background(Color(.purple).opacity(0.2)) 18 | .navigationBarTitle("Settings", displayMode: .inline) 19 | .navigationBarItems(leading: Button(action: {}, label: { }), 20 | trailing: Button(action: {}, label: { })) 21 | } 22 | } 23 | 24 | struct SettingsScreen_Previews: PreviewProvider { 25 | static var previews: some View { 26 | SettingsTemplateScreen() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Scanverter/Views/Modals/ModalCamera.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ModalCamera: View { 4 | @Environment(\.presentationMode) var presentationMode 5 | @ObservedObject var model: CameraViewModel 6 | @State private var showOneLevelIn: Bool = false 7 | @State private var fromEditor: Bool = false 8 | 9 | init(model: CameraViewModel) { 10 | self.model = model 11 | } 12 | 13 | private var showPhotoLibrary: some View { 14 | HStack { 15 | Spacer() 16 | Button(action: { 17 | model.isPresentingImagePicker = true 18 | }, label: { 19 | Image(systemName: "photo.on.rectangle.angled") 20 | .resizable() 21 | .frame(width: 40, height: 30, alignment: .leading) 22 | .foregroundColor(.gray) 23 | }) 24 | } 25 | .offset(x: -280, y: 0) 26 | .fullScreenCover(isPresented: $model.isPresentingImagePicker, content: { 27 | ImagePicker(sourceType: .photoLibrary, completionHandler: model.didSelectImage) 28 | }) 29 | } 30 | 31 | var body: some View { 32 | NavigationStackView { 33 | ZStack { 34 | CameraView(model: model) 35 | HStack { 36 | showPhotoLibrary 37 | Spacer() 38 | Button(action: { 39 | presentationMode.wrappedValue.dismiss() 40 | }, label: { 41 | Image(systemName: "xmark.circle") 42 | .resizable() 43 | .frame(width: 35, height: 35, alignment: .leading) 44 | .foregroundColor(.gray) 45 | }) 46 | }.offset(x: -20, y: -350) 47 | Text("Not working in simulator!") 48 | .foregroundColor(.gray) 49 | .opacity(UIDevice.isSimulator ? 1 : 0) 50 | PushView(destination: EditorView(dataSource: PhotoCollectionDataSource(scannedDocs: model.scannedDocs), backToCamera: $fromEditor), isActive: $showOneLevelIn) { 51 | EmptyView() 52 | }.hidden() 53 | }.onReceive(model.publisher) { status in 54 | showOneLevelIn = status 55 | } 56 | .onAppear { 57 | if fromEditor { 58 | presentationMode.wrappedValue.dismiss() 59 | } 60 | } 61 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 62 | .edgesIgnoringSafeArea(.all) 63 | } 64 | } 65 | } 66 | 67 | struct ModalCamera_Previews: PreviewProvider { 68 | static var previews: some View { 69 | ModalCamera(model: CameraViewModel()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Scanverter/Views/Modals/ModalScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ModalScreen: View { 4 | @Environment(\.presentationMode) var presentationMode 5 | 6 | var body: some View { 7 | ZStack { 8 | VStack { 9 | Spacer() 10 | HStack { 11 | Spacer() 12 | Text("Modal Screen").font(.system(size: 20)).bold() 13 | // EditorView(dataSource: PhotoCollectionDataSource(scannedDocs: mockedDocs)) 14 | Spacer() 15 | } 16 | Spacer() 17 | } 18 | HStack { 19 | Spacer() 20 | Button(action: { 21 | presentationMode.wrappedValue.dismiss() 22 | }, label: { 23 | Image(systemName: "xmark.circle") 24 | .resizable() 25 | .frame(width: 25, height: 25, alignment: .leading) 26 | .foregroundColor(.gray) 27 | 28 | }) 29 | }.offset(x: -30, y: -380) 30 | } 31 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 32 | .edgesIgnoringSafeArea(.all) 33 | } 34 | 35 | private var mockedDocs: [ScannedDoc] { 36 | var docs: [ScannedDoc] = .init() 37 | let names = ["imageWithText", "slideWithText"] 38 | for _ in 0..<2 { 39 | names.forEach { 40 | docs.append(ScannedDoc(image: UIImage(named: $0)!.cgImage!, date: Date())) 41 | } 42 | } 43 | return docs 44 | } 45 | } 46 | 47 | struct ModalScreen_Previews: PreviewProvider { 48 | static var previews: some View { 49 | ModalScreen() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Scanverter/Views/Modals/ModalSearchScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ModalSearchScreen: View { 4 | @Environment(\.presentationMode) var presentationMode 5 | @StateObject var dataSource: SearchDataSource = .init() 6 | 7 | @State private var searchText : String = "" 8 | 9 | var body: some View { 10 | ZStack { 11 | VStack { 12 | HStack { 13 | SearchBar(text: $searchText, placeholder: "Search files") 14 | .padding(EdgeInsets(top: 40, leading: 10, bottom: 20, trailing: 0)) 15 | Spacer() 16 | Button(action: { 17 | presentationMode.wrappedValue.dismiss() 18 | }, label: { 19 | Image(systemName: "xmark.circle") 20 | .resizable() 21 | .frame(width: 25, height: 25, alignment: .leading) 22 | .foregroundColor(.gray) 23 | }) 24 | .padding(EdgeInsets(top: 40, leading: 0, bottom: 20, trailing: 20)) 25 | } 26 | 27 | List { 28 | ForEach(dataSource.files.filter { 29 | self.searchText.isEmpty ? true : $0.name.lowercased().contains(self.searchText.lowercased()) 30 | }, id: \.self) { item in 31 | Text(item.name) 32 | } 33 | }.navigationBarTitle(Text("File Search")) 34 | } 35 | .onAppear { dataSource.loadAll() } 36 | } 37 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 38 | .edgesIgnoringSafeArea(.all) 39 | } 40 | } 41 | 42 | struct ModalSearchScreen_Previews: PreviewProvider { 43 | static var previews: some View { 44 | ModalSearchScreen() 45 | .previewDevice("iPhone 11 Pro") 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Scanverter/Views/Modals/TextRecognizerScreen.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import VisionKit 3 | import PDFKit 4 | 5 | struct TextRecognizerScreen: View { 6 | @Environment(\.presentationMode) var presentationMode 7 | 8 | @State private var isShowingScannerSheet = false 9 | @State private var text: String = "" 10 | 11 | @State var isPresentingFolderChooser: Bool = false 12 | @State private var folderToSaveIn: Folder? 13 | @State private var savedScan: VNDocumentCameraScan? 14 | 15 | var body: some View { 16 | ZStack { 17 | VStack(spacing: 32) { 18 | Text("Vision Kit Example") 19 | HStack(spacing: 55) { 20 | saveToPdf 21 | .sheet(isPresented: $isPresentingFolderChooser, onDismiss: {}) { 22 | FoldersScreen(selectedFolder: $folderToSaveIn, calledFromSaving: true) 23 | } 24 | .opacity(0) 25 | Spacer() 26 | scanButton 27 | .opacity(UIDevice.isSimulator ? 0.4 : 1) 28 | .disabled(UIDevice.isSimulator) 29 | Spacer() 30 | closeButton 31 | } 32 | .offset(x: -30, y: 0) 33 | Text("Not working in simulator!").opacity(UIDevice.isSimulator ? 1 : 0) 34 | Spacer() 35 | Text(text) 36 | .font(.largeTitle) 37 | .fontWeight(.heavy) 38 | .lineLimit(nil) 39 | Spacer() 40 | } 41 | .sheet(isPresented: self.$isShowingScannerSheet) { self.makeScannerView() } 42 | } 43 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 44 | .edgesIgnoringSafeArea(.all) 45 | } 46 | 47 | private var scanButton: some View { 48 | Button(action: openCamera) { 49 | Text("Scan") 50 | .foregroundColor(.white) 51 | } 52 | .frame(width: 40, height: 20) 53 | .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) 54 | .background(Color(UIColor.darkGray)) 55 | .cornerRadius(3.0) 56 | } 57 | 58 | private var closeButton: some View { 59 | Button(action: { 60 | NotificationCenter.default.post(name: NSNotification.Name("TextRecognizerScreenDismissed"), object: nil) 61 | presentationMode.wrappedValue.dismiss() 62 | }, label: { 63 | Image(systemName: "xmark.circle") 64 | .resizable() 65 | .frame(width: 30, height: 30, alignment: .leading) 66 | .foregroundColor(Color(UIColor.systemGray2)) 67 | }) 68 | } 69 | 70 | private var saveToPdf: some View { 71 | HStack { 72 | Spacer() 73 | Button(action: { 74 | isPresentingFolderChooser = true 75 | }, label: { 76 | Image(systemName: "square.and.arrow.down") 77 | .resizable() 78 | .frame(width: 30, height: 30, alignment: .leading) 79 | .foregroundColor(.gray) 80 | }) 81 | } 82 | } 83 | 84 | private func openCamera() { 85 | isShowingScannerSheet = true 86 | } 87 | 88 | private func makeScannerView() -> TextScannerView { 89 | TextScannerView(completion: { textPerPage in 90 | if let text = textPerPage?.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines) { 91 | // self.text = text 92 | self.text = "Saved" 93 | } 94 | self.isShowingScannerSheet = false 95 | }) 96 | } 97 | } 98 | 99 | struct TextRecognizerScreen_Previews: PreviewProvider { 100 | static var previews: some View { 101 | TextRecognizerScreen() 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /Scanverter/Views/Navigation/ScreenDetails.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct DetailScreen: View { 4 | @EnvironmentObject var navStack: NavigationStack 5 | var body: some View { 6 | VStack { 7 | Spacer() 8 | HStack { 9 | Spacer() 10 | VStack { 11 | Text("Detail Screen") 12 | .font(.system(size: 20)) 13 | .bold() 14 | 15 | Button(action: { 16 | self.navStack.pop() 17 | }, label: { 18 | Text("Back to camera") 19 | }) 20 | } 21 | Spacer() 22 | } 23 | Spacer() 24 | } 25 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) 26 | .background(Color(.green).opacity(0.2)) 27 | .navigationBarTitle("Detail Screen", displayMode: .inline) 28 | .edgesIgnoringSafeArea(.bottom) 29 | } 30 | } 31 | 32 | struct DetailScreen_Previews: PreviewProvider { 33 | static var previews: some View { 34 | DetailScreen() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Scanverter/Views/Protocols/ContainerView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | protocol ContainerView: View { 4 | associatedtype Content 5 | init(content: @escaping () -> Content) 6 | } 7 | 8 | extension ContainerView { 9 | init(@ViewBuilder _ content: @escaping () -> Content) { 10 | self.init(content: content) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Scanverter/Views/Tabs/EditorTabBar.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct EditorTabBar: View { 4 | @StateObject var dataSource: EditTabBarDataSource 5 | @StateObject var photoDataSource: PhotoCollectionDataSource 6 | 7 | var body: some View { 8 | HStack(spacing: 40) { 9 | ForEach(dataSource.editTools) { tool in 10 | VStack { 11 | EditToolCell(dataSource: EditToolCellDataSource(tool: tool), photoDataSource: photoDataSource) 12 | .padding(.top, 6) 13 | .padding(.bottom, 6) 14 | } 15 | } 16 | } 17 | .frame(minHeight: 70) 18 | } 19 | } 20 | 21 | struct EditorTabBar_Previews: PreviewProvider { 22 | static var previews: some View { 23 | EditorTabBar(dataSource: EditTabBarDataSource(tools: Constants.editTools), 24 | photoDataSource: PhotoCollectionDataSource(scannedDocs: Constants.mockedDocs)) 25 | .preferredColorScheme(.dark) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Scanverter/Views/Tabs/ModalScreenShowTabButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct ModalScreenShowTabButton: View { 4 | let imageName: String 5 | let radius: CGFloat 6 | let action: () -> Void 7 | 8 | public init(name: String, radius: CGFloat, action: @escaping () -> Void) { 9 | self.imageName = name 10 | self.radius = radius 11 | self.action = action 12 | } 13 | 14 | public var body: some View { 15 | VStack(spacing: 0) { 16 | Image(systemName: imageName) 17 | .resizable() 18 | .aspectRatio(contentMode: .fit) 19 | .frame(width: radius, height: radius, alignment: .center) 20 | .foregroundColor(.primary) 21 | } 22 | .frame(width: radius, height: radius) 23 | .onTapGesture(perform: action) 24 | } 25 | } 26 | 27 | struct ModalScreenShowTabButton_Previews_Previews: PreviewProvider { 28 | static var previews: some View { 29 | ModalScreenShowTabButton(name: "gears", radius: 55) { } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Scanverter/Views/Tabs/Tab.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Tab: Int { 4 | case folders 5 | case settings 6 | 7 | var index: Int { 8 | switch self { 9 | case .folders: 10 | return 1 11 | case .settings: 12 | return 2 13 | } 14 | } 15 | 16 | static func tabByIndex(_ index: Int) -> Tab { 17 | switch index { 18 | case 1: 19 | return folders 20 | case 2: 21 | return settings 22 | default: 23 | return folders 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Scanverter/Views/Tabs/TabBar.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TabBar_Previews: PreviewProvider { 4 | static var previews: some View { 5 | TabBar(currentView: .constant(.folders), showCameraModal: .constant(false), showMicModal: .constant(false), showSearchModal: .constant(false)) 6 | .preferredColorScheme(.dark) 7 | } 8 | } 9 | 10 | struct TabBar: View { 11 | @Binding var currentView: Tab 12 | @Binding var showCameraModal: Bool 13 | @Binding var showMicModal: Bool 14 | @Binding var showSearchModal: Bool 15 | 16 | var body: some View { 17 | HStack(spacing: 30) { 18 | VStack { 19 | TabBarItem(currentView: self.$currentView, 20 | imageName: "folder", 21 | paddingEdges: .leading, 22 | tab: .folders) 23 | Text("Folders") 24 | .foregroundColor(.primary) 25 | .font(.caption2) 26 | }.padding(.leading, 5) 27 | 28 | HStack(spacing: 40) { 29 | VStack { 30 | ModalScreenShowTabButton(name: "eye", radius: 20) { 31 | self.showMicModal.toggle() 32 | } 33 | .padding(.top, 4) 34 | .padding(.bottom, 4) 35 | Text("Vision") 36 | .foregroundColor(.primary) 37 | .font(.caption2) 38 | } 39 | VStack { 40 | ModalScreenShowTabButton(name: "camera.fill", radius: 20) { 41 | self.showCameraModal.toggle() 42 | } 43 | .padding(.top, 4) 44 | .padding(.bottom, 4) 45 | Text("CamScan") 46 | .foregroundColor(.primary) 47 | .font(.caption2) 48 | } 49 | VStack { 50 | ModalScreenShowTabButton(name: "magnifyingglass", radius: 20) { 51 | self.showSearchModal.toggle() 52 | }.foregroundColor(Color(.white)) 53 | .padding(.top, 4) 54 | .padding(.bottom, 4) 55 | Text("Search") 56 | .foregroundColor(.primary) 57 | .font(.caption2) 58 | } 59 | } 60 | 61 | VStack { 62 | TabBarItem(currentView: self.$currentView, 63 | imageName: "gearshape", 64 | paddingEdges: .trailing, 65 | tab: .settings) 66 | Text("Settings") 67 | .foregroundColor(.primary) 68 | .font(.caption2) 69 | }.padding(.trailing, 5) 70 | } 71 | .frame(minHeight: 70) 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /Scanverter/Views/Tabs/TabBarItem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Combine 3 | import SwiftUI 4 | 5 | struct TabBarItem: View { 6 | @Binding var currentView: Tab 7 | let imageName: String 8 | let paddingEdges: Edge.Set 9 | let tab: Tab 10 | 11 | var body: some View { 12 | VStack(spacing: 0) { 13 | Image(systemName: self.currentView == tab ? "\(imageName).fill" : imageName) 14 | .resizable() 15 | .aspectRatio(contentMode: .fit) 16 | .padding(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0)) 17 | .frame(width: 30, height: 30, alignment: .center) 18 | .foregroundColor(self.currentView == tab ? Color(.systemBlue) : Color(.label)) 19 | .cornerRadius(6) 20 | } 21 | .frame(width: 30, height: 30) 22 | .onTapGesture { self.currentView = self.tab } 23 | } 24 | } 25 | 26 | struct TabBarItem_Previews: PreviewProvider { 27 | static var previews: some View { 28 | TabBarItem(currentView: .constant(.settings), imageName: "camera", paddingEdges: .leading, tab: .settings) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Scanverter/Views/Tabs/ViewModels/EditorTabBarDataSource.swift: -------------------------------------------------------------------------------- 1 | import Combine 2 | 3 | final class EditTabBarDataSource: ObservableObject { 4 | @Published private(set) var editTools: [EditTool] 5 | 6 | init(tools: [EditTool]) { 7 | self.editTools = tools 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.lstm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/deu.lstm -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.lstm-number-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/deu.lstm-number-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.lstm-punc-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/deu.lstm-punc-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.lstm-recoder: -------------------------------------------------------------------------------- 1 | trr  2 |     !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopq -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.lstm-unicharset: -------------------------------------------------------------------------------- 1 | 116 2 | NULL 0 Common 0 3 | Joined 7 0,255,0,255,0,0,0,0,0,0 Latin 1 0 1 Joined # Joined [4a 6f 69 6e 65 64 ]a 4 | |Broken|0|1 f 0,255,0,255,0,0,0,0,0,0 Common 2 10 2 |Broken|0|1 # Broken 5 | M 5 0,255,0,255,0,0,0,0,0,0 Latin 86 0 3 M # M [4d ]A 6 | Ö 5 0,255,0,255,0,0,0,0,0,0 Latin 102 0 4 Ö # Ö [d6 ]A 7 | C 5 0,255,0,255,0,0,0,0,0,0 Latin 88 0 5 C # C [43 ]A 8 | H 5 0,255,0,255,0,0,0,0,0,0 Latin 95 0 6 H # H [48 ]A 9 | T 5 0,255,0,255,0,0,0,0,0,0 Latin 97 0 7 T # T [54 ]A 10 | E 5 0,255,0,255,0,0,0,0,0,0 Latin 85 0 8 E # E [45 ]A 11 | ! 10 0,255,0,255,0,0,0,0,0,0 Common 9 10 9 ! # ! [21 ]p 12 | I 5 0,255,0,255,0,0,0,0,0,0 Latin 96 0 10 I # I [49 ]A 13 | N 5 0,255,0,255,0,0,0,0,0,0 Latin 90 0 11 N # N [4e ]A 14 | V 5 0,255,0,255,0,0,0,0,0,0 Latin 113 0 12 V # V [56 ]A 15 | D 5 0,255,0,255,0,0,0,0,0,0 Latin 105 0 13 D # D [44 ]A 16 | , 10 0,255,0,255,0,0,0,0,0,0 Common 14 6 14 , # , [2c ]p 17 | S 5 0,255,0,255,0,0,0,0,0,0 Latin 94 0 15 S # S [53 ]A 18 | A 5 0,255,0,255,0,0,0,0,0,0 Latin 89 0 16 A # A [41 ]A 19 | O 5 0,255,0,255,0,0,0,0,0,0 Latin 101 0 17 O # O [4f ]A 20 | L 5 0,255,0,255,0,0,0,0,0,0 Latin 98 0 18 L # L [4c ]A 21 | K 5 0,255,0,255,0,0,0,0,0,0 Latin 100 0 19 K # K [4b ]A 22 | R 5 0,255,0,255,0,0,0,0,0,0 Latin 87 0 20 R # R [52 ]A 23 | B 5 0,255,0,255,0,0,0,0,0,0 Latin 93 0 21 B # B [42 ]A 24 | Ü 5 0,255,0,255,0,0,0,0,0,0 Latin 99 0 22 Ü # Ü [dc ]A 25 | F 5 0,255,0,255,0,0,0,0,0,0 Latin 92 0 23 F # F [46 ]A 26 | P 5 0,255,0,255,0,0,0,0,0,0 Latin 106 0 24 P # P [50 ]A 27 | W 5 0,255,0,255,0,0,0,0,0,0 Latin 104 0 25 W # W [57 ]A 28 | G 5 0,255,0,255,0,0,0,0,0,0 Latin 103 0 26 G # G [47 ]A 29 | - 10 0,255,0,255,0,0,0,0,0,0 Common 27 3 27 - # - [2d ]p 30 | U 5 0,255,0,255,0,0,0,0,0,0 Latin 91 0 28 U # U [55 ]A 31 | 1 8 0,255,0,255,0,0,0,0,0,0 Common 29 2 29 1 # 1 [31 ]0 32 | 7 8 0,255,0,255,0,0,0,0,0,0 Common 30 2 30 7 # 7 [37 ]0 33 | / 10 0,255,0,255,0,0,0,0,0,0 Common 31 6 31 / # / [2f ]p 34 | ( 10 0,255,0,255,0,0,0,0,0,0 Common 32 10 34 ( # ( [28 ]p 35 | . 10 0,255,0,255,0,0,0,0,0,0 Common 33 6 33 . # . [2e ]p 36 | ) 10 0,255,0,255,0,0,0,0,0,0 Common 34 10 32 ) # ) [29 ]p 37 | : 10 0,255,0,255,0,0,0,0,0,0 Common 35 6 35 : # : [3a ]p 38 | Z 5 0,255,0,255,0,0,0,0,0,0 Latin 112 0 36 Z # Z [5a ]A 39 | [ 10 0,255,0,255,0,0,0,0,0,0 Common 37 10 62 [ # [ [5b ]p 40 | Y 5 0,255,0,255,0,0,0,0,0,0 Latin 107 0 38 Y # Y [59 ]A 41 | J 5 0,255,0,255,0,0,0,0,0,0 Latin 114 0 39 J # J [4a ]A 42 | 3 8 0,255,0,255,0,0,0,0,0,0 Common 40 2 40 3 # 3 [33 ]0 43 | 5 8 0,255,0,255,0,0,0,0,0,0 Common 41 2 41 5 # 5 [35 ]0 44 | 4 8 0,255,0,255,0,0,0,0,0,0 Common 42 2 42 4 # 4 [34 ]0 45 | 8 8 0,255,0,255,0,0,0,0,0,0 Common 43 2 43 8 # 8 [38 ]0 46 | ? 10 0,255,0,255,0,0,0,0,0,0 Common 44 10 44 ? # ? [3f ]p 47 | X 5 0,255,0,255,0,0,0,0,0,0 Latin 111 0 45 X # X [58 ]A 48 | ' 10 0,255,0,255,0,0,0,0,0,0 Common 46 10 46 ' # ' [27 ]p 49 | Ä 5 0,255,0,255,0,0,0,0,0,0 Latin 108 0 47 Ä # Ä [c4 ]A 50 | " 10 0,255,0,255,0,0,0,0,0,0 Common 48 10 48 " # " [22 ]p 51 | @ 10 0,255,0,255,0,0,0,0,0,0 Common 49 10 49 @ # @ [40 ]p 52 | 0 8 0,255,0,255,0,0,0,0,0,0 Common 50 2 50 0 # 0 [30 ]0 53 | 2 8 0,255,0,255,0,0,0,0,0,0 Common 51 2 51 2 # 2 [32 ]0 54 | + 0 0,255,0,255,0,0,0,0,0,0 Common 52 3 52 + # + [2b ] 55 | * 10 0,255,0,255,0,0,0,0,0,0 Common 53 10 53 * # * [2a ]p 56 | 6 8 0,255,0,255,0,0,0,0,0,0 Common 54 2 54 6 # 6 [36 ]0 57 | “ 10 0,255,0,255,0,0,0,0,0,0 Common 55 10 55 " # “ [201c ]p 58 | 9 8 0,255,0,255,0,0,0,0,0,0 Common 56 2 56 9 # 9 [39 ]0 59 | ” 10 0,255,0,255,0,0,0,0,0,0 Common 57 10 57 " # ” [201d ]p 60 | „ 10 0,255,0,255,0,0,0,0,0,0 Common 58 10 58 „ # „ [201e ]p 61 | \ 10 0,255,0,255,0,0,0,0,0,0 Common 59 10 59 \ # \ [5c ]p 62 | { 10 0,255,0,255,0,0,0,0,0,0 Common 60 10 61 { # { [7b ]p 63 | } 10 0,255,0,255,0,0,0,0,0,0 Common 61 10 60 } # } [7d ]p 64 | ] 10 0,255,0,255,0,0,0,0,0,0 Common 62 10 37 ] # ] [5d ]p 65 | _ 10 0,255,0,255,0,0,0,0,0,0 Common 63 10 63 _ # _ [5f ]p 66 | ; 10 0,255,0,255,0,0,0,0,0,0 Common 64 10 64 ; # ; [3b ]p 67 | » 10 0,255,0,255,0,0,0,0,0,0 Common 65 10 66 » # » [bb ]p 68 | « 10 0,255,0,255,0,0,0,0,0,0 Common 66 10 65 « # « [ab ]p 69 | Q 5 0,255,0,255,0,0,0,0,0,0 Latin 109 0 67 Q # Q [51 ]A 70 | % 10 0,255,0,255,0,0,0,0,0,0 Common 68 4 68 % # % [25 ]p 71 | & 10 0,255,0,255,0,0,0,0,0,0 Common 69 10 69 & # & [26 ]p 72 | < 0 0,255,0,255,0,0,0,0,0,0 Common 70 10 73 < # < [3c ] 73 | = 0 0,255,0,255,0,0,0,0,0,0 Common 71 10 71 = # = [3d ] 74 | | 0 0,255,0,255,0,0,0,0,0,0 Common 72 10 72 | # | [7c ] 75 | > 0 0,255,0,255,0,0,0,0,0,0 Common 73 10 70 > # > [3e ] 76 | ® 0 0,255,0,255,0,0,0,0,0,0 Common 74 10 74 ® # ® [ae ] 77 | # 10 0,255,0,255,0,0,0,0,0,0 Common 75 4 75 # # # [23 ]p 78 | ‚ 10 0,255,0,255,0,0,0,0,0,0 Common 76 10 76 ‚ # ‚ [201a ]p 79 | $ 0 0,255,0,255,0,0,0,0,0,0 Common 77 4 77 $ # $ [24 ] 80 | £ 0 0,255,0,255,0,0,0,0,0,0 Common 78 4 78 £ # £ [a3 ] 81 | € 0 0,255,0,255,0,0,0,0,0,0 Common 79 4 79 € # € [20ac ] 82 | ’ 10 0,255,0,255,0,0,0,0,0,0 Common 80 10 80 ' # ’ [2019 ]p 83 | — 10 0,255,0,255,0,0,0,0,0,0 Common 81 10 81 - # — [2014 ]p 84 | ° 0 0,255,0,255,0,0,0,0,0,0 Common 82 4 82 ° # ° [b0 ] 85 | © 0 0,255,0,255,0,0,0,0,0,0 Common 83 10 83 © # © [a9 ] 86 | ‘ 10 0,255,0,255,0,0,0,0,0,0 Common 84 10 84 ' # ‘ [2018 ]p 87 | e 3 0,255,0,255,0,0,0,0,0,0 Latin 8 0 85 e # e [65 ]a 88 | m 3 0,255,0,255,0,0,0,0,0,0 Latin 3 0 86 m # m [6d ]a 89 | r 3 0,255,0,255,0,0,0,0,0,0 Latin 20 0 87 r # r [72 ]a 90 | c 3 0,255,0,255,0,0,0,0,0,0 Latin 5 0 88 c # c [63 ]a 91 | a 3 0,255,0,255,0,0,0,0,0,0 Latin 16 0 89 a # a [61 ]a 92 | n 3 0,255,0,255,0,0,0,0,0,0 Latin 11 0 90 n # n [6e ]a 93 | u 3 0,255,0,255,0,0,0,0,0,0 Latin 28 0 91 u # u [75 ]a 94 | f 3 0,255,0,255,0,0,0,0,0,0 Latin 23 0 92 f # f [66 ]a 95 | b 3 0,255,0,255,0,0,0,0,0,0 Latin 21 0 93 b # b [62 ]a 96 | s 3 0,255,0,255,0,0,0,0,0,0 Latin 15 0 94 s # s [73 ]a 97 | h 3 0,255,0,255,0,0,0,0,0,0 Latin 6 0 95 h # h [68 ]a 98 | i 3 0,255,0,255,0,0,0,0,0,0 Latin 10 0 96 i # i [69 ]a 99 | t 3 0,255,0,255,0,0,0,0,0,0 Latin 7 0 97 t # t [74 ]a 100 | l 3 0,255,0,255,0,0,0,0,0,0 Latin 18 0 98 l # l [6c ]a 101 | ü 3 0,255,0,255,0,0,0,0,0,0 Latin 22 0 99 ü # ü [fc ]a 102 | k 3 0,255,0,255,0,0,0,0,0,0 Latin 19 0 100 k # k [6b ]a 103 | o 3 0,255,0,255,0,0,0,0,0,0 Latin 17 0 101 o # o [6f ]a 104 | ö 3 0,255,0,255,0,0,0,0,0,0 Latin 4 0 102 ö # ö [f6 ]a 105 | g 3 0,255,0,255,0,0,0,0,0,0 Latin 26 0 103 g # g [67 ]a 106 | w 3 0,255,0,255,0,0,0,0,0,0 Latin 25 0 104 w # w [77 ]a 107 | d 3 0,255,0,255,0,0,0,0,0,0 Latin 13 0 105 d # d [64 ]a 108 | p 3 0,255,0,255,0,0,0,0,0,0 Latin 24 0 106 p # p [70 ]a 109 | y 3 0,255,0,255,0,0,0,0,0,0 Latin 38 0 107 y # y [79 ]a 110 | ä 3 0,255,0,255,0,0,0,0,0,0 Latin 47 0 108 ä # ä [e4 ]a 111 | q 3 0,255,0,255,0,0,0,0,0,0 Latin 67 0 109 q # q [71 ]a 112 | ß 3 0,255,0,255,0,0,0,0,0,0 Latin 110 0 110 ß # ß [df ]a 113 | x 3 0,255,0,255,0,0,0,0,0,0 Latin 45 0 111 x # x [78 ]a 114 | z 3 0,255,0,255,0,0,0,0,0,0 Latin 36 0 112 z # z [7a ]a 115 | v 3 0,255,0,255,0,0,0,0,0,0 Latin 12 0 113 v # v [76 ]a 116 | j 3 0,255,0,255,0,0,0,0,0,0 Latin 39 0 114 j # j [6a ]a 117 | ı 3 0,255,0,255,0,0,0,0,0,0 Latin 10 0 115 ı # ı [131 ]a 118 | -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.lstm-word-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/deu.lstm-word-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/deu.traineddata -------------------------------------------------------------------------------- /Scanverter/tessdata/deu.version: -------------------------------------------------------------------------------- 1 | 4.00.00alpha:deu:synth20170629 -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.lstm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/eng.lstm -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.lstm-number-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/eng.lstm-number-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.lstm-punc-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/eng.lstm-punc-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.lstm-recoder: -------------------------------------------------------------------------------- 1 | pnn  2 |     !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.lstm-unicharset: -------------------------------------------------------------------------------- 1 | 112 2 | NULL 0 Common 0 3 | Joined 7 0,255,0,255,0,0,0,0,0,0 Latin 1 0 1 Joined # Joined [4a 6f 69 6e 65 64 ]a 4 | |Broken|0|1 f 0,255,0,255,0,0,0,0,0,0 Common 2 10 2 |Broken|0|1 # Broken 5 | C 5 0,255,0,255,0,0,0,0,0,0 Latin 87 0 3 C # C [43 ]A 6 | H 5 0,255,0,255,0,0,0,0,0,0 Latin 97 0 4 H # H [48 ]A 7 | E 5 0,255,0,255,0,0,0,0,0,0 Latin 92 0 5 E # E [45 ]A 8 | S 5 0,255,0,255,0,0,0,0,0,0 Latin 95 0 6 S # S [53 ]A 9 | - 10 0,255,0,255,0,0,0,0,0,0 Common 7 3 7 - # - [2d ]p 10 | R 5 0,255,0,255,0,0,0,0,0,0 Latin 103 0 8 R # R [52 ]A 11 | I 5 0,255,0,255,0,0,0,0,0,0 Latin 85 0 9 I # I [49 ]A 12 | K 5 0,255,0,255,0,0,0,0,0,0 Latin 107 0 10 K # K [4b ]A 13 | N 5 0,255,0,255,0,0,0,0,0,0 Latin 86 0 11 N # N [4e ]A 14 | G 5 0,255,0,255,0,0,0,0,0,0 Latin 96 0 12 G # G [47 ]A 15 | B 5 0,255,0,255,0,0,0,0,0,0 Latin 98 0 13 B # B [42 ]A 16 | 8 8 0,255,0,255,0,0,0,0,0,0 Common 14 2 14 8 # 8 [38 ]0 17 | 5 8 0,255,0,255,0,0,0,0,0,0 Common 15 2 15 5 # 5 [35 ]0 18 | F 5 0,255,0,255,0,0,0,0,0,0 Latin 102 0 16 F # F [46 ]A 19 | , 10 0,255,0,255,0,0,0,0,0,0 Common 17 6 17 , # , [2c ]p 20 | ( 10 0,255,0,255,0,0,0,0,0,0 Common 18 10 22 ( # ( [28 ]p 21 | / 10 0,255,0,255,0,0,0,0,0,0 Common 19 6 19 / # / [2f ]p 22 | L 5 0,255,0,255,0,0,0,0,0,0 Latin 89 0 20 L # L [4c ]A 23 | T 5 0,255,0,255,0,0,0,0,0,0 Latin 91 0 21 T # T [54 ]A 24 | ) 10 0,255,0,255,0,0,0,0,0,0 Common 22 10 18 ) # ) [29 ]p 25 | O 5 0,255,0,255,0,0,0,0,0,0 Latin 94 0 23 O # O [4f ]A 26 | Y 5 0,255,0,255,0,0,0,0,0,0 Latin 109 0 24 Y # Y [59 ]A 27 | . 10 0,255,0,255,0,0,0,0,0,0 Common 25 6 25 . # . [2e ]p 28 | D 5 0,255,0,255,0,0,0,0,0,0 Latin 106 0 26 D # D [44 ]A 29 | A 5 0,255,0,255,0,0,0,0,0,0 Latin 90 0 27 A # A [41 ]A 30 | M 5 0,255,0,255,0,0,0,0,0,0 Latin 93 0 28 M # M [4d ]A 31 | U 5 0,255,0,255,0,0,0,0,0,0 Latin 88 0 29 U # U [55 ]A 32 | P 5 0,255,0,255,0,0,0,0,0,0 Latin 105 0 30 P # P [50 ]A 33 | [ 10 0,255,0,255,0,0,0,0,0,0 Common 31 10 32 [ # [ [5b ]p 34 | ] 10 0,255,0,255,0,0,0,0,0,0 Common 32 10 31 ] # ] [5d ]p 35 | 9 8 0,255,0,255,0,0,0,0,0,0 Common 33 2 33 9 # 9 [39 ]0 36 | 7 8 0,255,0,255,0,0,0,0,0,0 Common 34 2 34 7 # 7 [37 ]0 37 | 0 8 0,255,0,255,0,0,0,0,0,0 Common 35 2 35 0 # 0 [30 ]0 38 | 1 8 0,255,0,255,0,0,0,0,0,0 Common 36 2 36 1 # 1 [31 ]0 39 | 4 8 0,255,0,255,0,0,0,0,0,0 Common 37 2 37 4 # 4 [34 ]0 40 | 2 8 0,255,0,255,0,0,0,0,0,0 Common 38 2 38 2 # 2 [32 ]0 41 | W 5 0,255,0,255,0,0,0,0,0,0 Latin 104 0 39 W # W [57 ]A 42 | 3 8 0,255,0,255,0,0,0,0,0,0 Common 40 2 40 3 # 3 [33 ]0 43 | < 0 0,255,0,255,0,0,0,0,0,0 Common 41 10 42 < # < [3c ] 44 | > 0 0,255,0,255,0,0,0,0,0,0 Common 42 10 41 > # > [3e ] 45 | " 10 0,255,0,255,0,0,0,0,0,0 Common 43 10 43 " # " [22 ]p 46 | V 5 0,255,0,255,0,0,0,0,0,0 Latin 100 0 44 V # V [56 ]A 47 | X 5 0,255,0,255,0,0,0,0,0,0 Latin 108 0 45 X # X [58 ]A 48 | ' 10 0,255,0,255,0,0,0,0,0,0 Common 46 10 46 ' # ' [27 ]p 49 | ~ 0 0,255,0,255,0,0,0,0,0,0 Common 47 10 47 ~ # ~ [7e ] 50 | ! 10 0,255,0,255,0,0,0,0,0,0 Common 48 10 48 ! # ! [21 ]p 51 | J 5 0,255,0,255,0,0,0,0,0,0 Latin 110 0 49 J # J [4a ]A 52 | Q 5 0,255,0,255,0,0,0,0,0,0 Latin 101 0 50 Q # Q [51 ]A 53 | Z 5 0,255,0,255,0,0,0,0,0,0 Latin 99 0 51 Z # Z [5a ]A 54 | + 0 0,255,0,255,0,0,0,0,0,0 Common 52 3 52 + # + [2b ] 55 | @ 10 0,255,0,255,0,0,0,0,0,0 Common 53 10 53 @ # @ [40 ]p 56 | & 10 0,255,0,255,0,0,0,0,0,0 Common 54 10 54 & # & [26 ]p 57 | ’ 10 0,255,0,255,0,0,0,0,0,0 Common 55 10 55 ' # ’ [2019 ]p 58 | = 0 0,255,0,255,0,0,0,0,0,0 Common 56 10 56 = # = [3d ] 59 | _ 10 0,255,0,255,0,0,0,0,0,0 Common 57 10 57 _ # _ [5f ]p 60 | € 0 0,255,0,255,0,0,0,0,0,0 Common 58 4 58 € # € [20ac ] 61 | ™ 0 0,255,0,255,0,0,0,0,0,0 Common 59 10 59 TM # ™ [2122 ] 62 | “ 10 0,255,0,255,0,0,0,0,0,0 Common 60 10 60 " # “ [201c ]p 63 | | 0 0,255,0,255,0,0,0,0,0,0 Common 61 10 61 | # | [7c ] 64 | ? 10 0,255,0,255,0,0,0,0,0,0 Common 62 10 62 ? # ? [3f ]p 65 | : 10 0,255,0,255,0,0,0,0,0,0 Common 63 6 63 : # : [3a ]p 66 | 6 8 0,255,0,255,0,0,0,0,0,0 Common 64 2 64 6 # 6 [36 ]0 67 | { 10 0,255,0,255,0,0,0,0,0,0 Common 65 10 66 { # { [7b ]p 68 | } 10 0,255,0,255,0,0,0,0,0,0 Common 66 10 65 } # } [7d ]p 69 | $ 0 0,255,0,255,0,0,0,0,0,0 Common 67 4 67 $ # $ [24 ] 70 | ; 10 0,255,0,255,0,0,0,0,0,0 Common 68 10 68 ; # ; [3b ]p 71 | \ 10 0,255,0,255,0,0,0,0,0,0 Common 69 10 69 \ # \ [5c ]p 72 | — 10 0,255,0,255,0,0,0,0,0,0 Common 70 10 70 - # — [2014 ]p 73 | ” 10 0,255,0,255,0,0,0,0,0,0 Common 71 10 71 " # ” [201d ]p 74 | * 10 0,255,0,255,0,0,0,0,0,0 Common 72 10 72 * # * [2a ]p 75 | # 10 0,255,0,255,0,0,0,0,0,0 Common 73 4 73 # # # [23 ]p 76 | » 10 0,255,0,255,0,0,0,0,0,0 Common 74 10 78 » # » [bb ]p 77 | ® 0 0,255,0,255,0,0,0,0,0,0 Common 75 10 75 ® # ® [ae ] 78 | % 10 0,255,0,255,0,0,0,0,0,0 Common 76 4 76 % # % [25 ]p 79 | £ 0 0,255,0,255,0,0,0,0,0,0 Common 77 4 77 £ # £ [a3 ] 80 | « 10 0,255,0,255,0,0,0,0,0,0 Common 78 10 74 « # « [ab ]p 81 | ° 0 0,255,0,255,0,0,0,0,0,0 Common 79 4 79 ° # ° [b0 ] 82 | © 0 0,255,0,255,0,0,0,0,0,0 Common 80 10 80 © # © [a9 ] 83 | § 10 0,255,0,255,0,0,0,0,0,0 Common 81 10 81 § # § [a7 ]p 84 | ¥ 0 0,255,0,255,0,0,0,0,0,0 Common 82 4 82 ¥ # ¥ [a5 ] 85 | ¢ 0 0,255,0,255,0,0,0,0,0,0 Common 83 4 83 ¢ # ¢ [a2 ] 86 | ‘ 10 0,255,0,255,0,0,0,0,0,0 Common 84 10 84 ' # ‘ [2018 ]p 87 | i 3 0,255,0,255,0,0,0,0,0,0 Latin 9 0 85 i # i [69 ]a 88 | n 3 0,255,0,255,0,0,0,0,0,0 Latin 11 0 86 n # n [6e ]a 89 | c 3 0,255,0,255,0,0,0,0,0,0 Latin 3 0 87 c # c [63 ]a 90 | u 3 0,255,0,255,0,0,0,0,0,0 Latin 29 0 88 u # u [75 ]a 91 | l 3 0,255,0,255,0,0,0,0,0,0 Latin 20 0 89 l # l [6c ]a 92 | a 3 0,255,0,255,0,0,0,0,0,0 Latin 27 0 90 a # a [61 ]a 93 | t 3 0,255,0,255,0,0,0,0,0,0 Latin 21 0 91 t # t [74 ]a 94 | e 3 0,255,0,255,0,0,0,0,0,0 Latin 5 0 92 e # e [65 ]a 95 | m 3 0,255,0,255,0,0,0,0,0,0 Latin 28 0 93 m # m [6d ]a 96 | o 3 0,255,0,255,0,0,0,0,0,0 Latin 23 0 94 o # o [6f ]a 97 | s 3 0,255,0,255,0,0,0,0,0,0 Latin 6 0 95 s # s [73 ]a 98 | g 3 0,255,0,255,0,0,0,0,0,0 Latin 12 0 96 g # g [67 ]a 99 | h 3 0,255,0,255,0,0,0,0,0,0 Latin 4 0 97 h # h [68 ]a 100 | b 3 0,255,0,255,0,0,0,0,0,0 Latin 13 0 98 b # b [62 ]a 101 | z 3 0,255,0,255,0,0,0,0,0,0 Latin 51 0 99 z # z [7a ]a 102 | v 3 0,255,0,255,0,0,0,0,0,0 Latin 44 0 100 v # v [76 ]a 103 | q 3 0,255,0,255,0,0,0,0,0,0 Latin 50 0 101 q # q [71 ]a 104 | f 3 0,255,0,255,0,0,0,0,0,0 Latin 16 0 102 f # f [66 ]a 105 | r 3 0,255,0,255,0,0,0,0,0,0 Latin 8 0 103 r # r [72 ]a 106 | w 3 0,255,0,255,0,0,0,0,0,0 Latin 39 0 104 w # w [77 ]a 107 | p 3 0,255,0,255,0,0,0,0,0,0 Latin 30 0 105 p # p [70 ]a 108 | d 3 0,255,0,255,0,0,0,0,0,0 Latin 26 0 106 d # d [64 ]a 109 | k 3 0,255,0,255,0,0,0,0,0,0 Latin 10 0 107 k # k [6b ]a 110 | x 3 0,255,0,255,0,0,0,0,0,0 Latin 45 0 108 x # x [78 ]a 111 | y 3 0,255,0,255,0,0,0,0,0,0 Latin 24 0 109 y # y [79 ]a 112 | j 3 0,255,0,255,0,0,0,0,0,0 Latin 49 0 110 j # j [6a ]a 113 | é 3 0,255,0,255,0,0,0,0,0,0 Latin 111 0 111 é # é [e9 ]a 114 | -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.lstm-word-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/eng.lstm-word-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/eng.traineddata -------------------------------------------------------------------------------- /Scanverter/tessdata/eng.version: -------------------------------------------------------------------------------- 1 | 4.00.00alpha:eng:synth20170629:[1,36,0,1Ct3,3,16Mp3,3Lfys64Lfx96Lrx96Lfx512O1c1] -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.lstm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/fra.lstm -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.lstm-number-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/fra.lstm-number-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.lstm-punc-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/fra.lstm-punc-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.lstm-recoder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/fra.lstm-recoder -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.lstm-word-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/fra.lstm-word-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/fra.traineddata -------------------------------------------------------------------------------- /Scanverter/tessdata/fra.version: -------------------------------------------------------------------------------- 1 | 4.00.00alpha:fra:synth20170629 -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.lstm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/ita.lstm -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.lstm-number-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/ita.lstm-number-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.lstm-punc-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/ita.lstm-punc-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.lstm-recoder: -------------------------------------------------------------------------------- 1 | xvv  2 |     !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.lstm-unicharset: -------------------------------------------------------------------------------- 1 | 120 2 | NULL 0 Common 0 3 | Joined 7 0,255,0,255,0,0,0,0,0,0 Latin 1 0 1 Joined # Joined [4a 6f 69 6e 65 64 ]a 4 | |Broken|0|1 f 0,255,0,255,0,0,0,0,0,0 Common 2 10 2 |Broken|0|1 # Broken 5 | S 5 0,255,0,255,0,0,0,0,0,0 Latin 101 0 3 S # S [53 ]A 6 | T 5 0,255,0,255,0,0,0,0,0,0 Latin 97 0 4 T # T [54 ]A 7 | A 5 0,255,0,255,0,0,0,0,0,0 Latin 90 0 5 A # A [41 ]A 8 | N 5 0,255,0,255,0,0,0,0,0,0 Latin 96 0 6 N # N [4e ]A 9 | O 5 0,255,0,255,0,0,0,0,0,0 Latin 87 0 7 O # O [4f ]A 10 | . 10 0,255,0,255,0,0,0,0,0,0 Common 8 6 8 . # . [2e ]p 11 | E 5 0,255,0,255,0,0,0,0,0,0 Latin 95 0 9 E # E [45 ]A 12 | P 5 0,255,0,255,0,0,0,0,0,0 Latin 102 0 10 P # P [50 ]A 13 | L 5 0,255,0,255,0,0,0,0,0,0 Latin 98 0 11 L # L [4c ]A 14 | H 5 0,255,0,255,0,0,0,0,0,0 Latin 99 0 12 H # H [48 ]A 15 | D 5 0,255,0,255,0,0,0,0,0,0 Latin 91 0 13 D # D [44 ]A 16 | G 5 0,255,0,255,0,0,0,0,0,0 Latin 94 0 14 G # G [47 ]A 17 | R 5 0,255,0,255,0,0,0,0,0,0 Latin 92 0 15 R # R [52 ]A 18 | I 5 0,255,0,255,0,0,0,0,0,0 Latin 89 0 16 I # I [49 ]A 19 | Z 5 0,255,0,255,0,0,0,0,0,0 Latin 104 0 17 Z # Z [5a ]A 20 | U 5 0,255,0,255,0,0,0,0,0,0 Latin 105 0 18 U # U [55 ]A 21 | F 5 0,255,0,255,0,0,0,0,0,0 Latin 103 0 19 F # F [46 ]A 22 | C 5 0,255,0,255,0,0,0,0,0,0 Latin 88 0 20 C # C [43 ]A 23 | 1 8 0,255,0,255,0,0,0,0,0,0 Common 21 2 21 1 # 1 [31 ]0 24 | 0 8 0,255,0,255,0,0,0,0,0,0 Common 22 2 22 0 # 0 [30 ]0 25 | * 10 0,255,0,255,0,0,0,0,0,0 Common 23 10 23 * # * [2a ]p 26 | V 5 0,255,0,255,0,0,0,0,0,0 Latin 93 0 24 V # V [56 ]A 27 | Y 5 0,255,0,255,0,0,0,0,0,0 Latin 111 0 25 Y # Y [59 ]A 28 | M 5 0,255,0,255,0,0,0,0,0,0 Latin 100 0 26 M # M [4d ]A 29 | K 5 0,255,0,255,0,0,0,0,0,0 Latin 106 0 27 K # K [4b ]A 30 | , 10 0,255,0,255,0,0,0,0,0,0 Common 28 6 28 , # , [2c ]p 31 | ) 10 0,255,0,255,0,0,0,0,0,0 Common 29 10 45 ) # ) [29 ]p 32 | " 10 0,255,0,255,0,0,0,0,0,0 Common 30 10 30 " # " [22 ]p 33 | ! 10 0,255,0,255,0,0,0,0,0,0 Common 31 10 31 ! # ! [21 ]p 34 | B 5 0,255,0,255,0,0,0,0,0,0 Latin 108 0 32 B # B [42 ]A 35 | : 10 0,255,0,255,0,0,0,0,0,0 Common 33 6 33 : # : [3a ]p 36 | 9 8 0,255,0,255,0,0,0,0,0,0 Common 34 2 34 9 # 9 [39 ]0 37 | J 5 0,255,0,255,0,0,0,0,0,0 Latin 116 0 35 J # J [4a ]A 38 | 7 8 0,255,0,255,0,0,0,0,0,0 Common 36 2 36 7 # 7 [37 ]0 39 | - 10 0,255,0,255,0,0,0,0,0,0 Common 37 3 37 - # - [2d ]p 40 | 8 8 0,255,0,255,0,0,0,0,0,0 Common 38 2 38 8 # 8 [38 ]0 41 | À 5 0,255,0,255,0,0,0,0,0,0 Latin 114 0 39 À # À [c0 ]A 42 | / 10 0,255,0,255,0,0,0,0,0,0 Common 40 6 40 / # / [2f ]p 43 | Q 5 0,255,0,255,0,0,0,0,0,0 Latin 107 0 41 Q # Q [51 ]A 44 | ' 10 0,255,0,255,0,0,0,0,0,0 Common 42 10 42 ' # ' [27 ]p 45 | ; 10 0,255,0,255,0,0,0,0,0,0 Common 43 10 43 ; # ; [3b ]p 46 | X 5 0,255,0,255,0,0,0,0,0,0 Latin 110 0 44 X # X [58 ]A 47 | ( 10 0,255,0,255,0,0,0,0,0,0 Common 45 10 29 ( # ( [28 ]p 48 | 5 8 0,255,0,255,0,0,0,0,0,0 Common 46 2 46 5 # 5 [35 ]0 49 | W 5 0,255,0,255,0,0,0,0,0,0 Latin 109 0 47 W # W [57 ]A 50 | 2 8 0,255,0,255,0,0,0,0,0,0 Common 48 2 48 2 # 2 [32 ]0 51 | “ 10 0,255,0,255,0,0,0,0,0,0 Common 49 10 49 " # “ [201c ]p 52 | ” 10 0,255,0,255,0,0,0,0,0,0 Common 50 10 50 " # ” [201d ]p 53 | ? 10 0,255,0,255,0,0,0,0,0,0 Common 51 10 51 ? # ? [3f ]p 54 | È 5 0,255,0,255,0,0,0,0,0,0 Latin 117 0 52 È # È [c8 ]A 55 | « 10 0,255,0,255,0,0,0,0,0,0 Common 53 10 56 « # « [ab ]p 56 | Ì 5 0,255,0,255,0,0,0,0,0,0 Latin 112 0 54 Ì # Ì [cc ]A 57 | [ 10 0,255,0,255,0,0,0,0,0,0 Common 55 10 58 [ # [ [5b ]p 58 | » 10 0,255,0,255,0,0,0,0,0,0 Common 56 10 53 » # » [bb ]p 59 | { 10 0,255,0,255,0,0,0,0,0,0 Common 57 10 80 { # { [7b ]p 60 | ] 10 0,255,0,255,0,0,0,0,0,0 Common 58 10 55 ] # ] [5d ]p 61 | 4 8 0,255,0,255,0,0,0,0,0,0 Common 59 2 59 4 # 4 [34 ]0 62 | = 0 0,255,0,255,0,0,0,0,0,0 Common 60 10 60 = # = [3d ] 63 | 3 8 0,255,0,255,0,0,0,0,0,0 Common 61 2 61 3 # 3 [33 ]0 64 | ° 0 0,255,0,255,0,0,0,0,0,0 Common 62 4 62 ° # ° [b0 ] 65 | _ 10 0,255,0,255,0,0,0,0,0,0 Common 63 10 63 _ # _ [5f ]p 66 | \ 10 0,255,0,255,0,0,0,0,0,0 Common 64 10 64 \ # \ [5c ]p 67 | ’ 10 0,255,0,255,0,0,0,0,0,0 Common 65 10 65 ' # ’ [2019 ]p 68 | @ 10 0,255,0,255,0,0,0,0,0,0 Common 66 10 66 @ # @ [40 ]p 69 | É 5 0,255,0,255,0,0,0,0,0,0 Latin 115 0 67 É # É [c9 ]A 70 | + 0 0,255,0,255,0,0,0,0,0,0 Common 68 3 68 + # + [2b ] 71 | — 10 0,255,0,255,0,0,0,0,0,0 Common 69 10 69 - # — [2014 ]p 72 | < 0 0,255,0,255,0,0,0,0,0,0 Common 70 10 72 < # < [3c ] 73 | 6 8 0,255,0,255,0,0,0,0,0,0 Common 71 2 71 6 # 6 [36 ]0 74 | > 0 0,255,0,255,0,0,0,0,0,0 Common 72 10 70 > # > [3e ] 75 | | 0 0,255,0,255,0,0,0,0,0,0 Common 73 10 73 | # | [7c ] 76 | Ò 5 0,255,0,255,0,0,0,0,0,0 Latin 113 0 74 Ò # Ò [d2 ]A 77 | % 10 0,255,0,255,0,0,0,0,0,0 Common 75 4 75 % # % [25 ]p 78 | © 0 0,255,0,255,0,0,0,0,0,0 Common 76 10 76 © # © [a9 ] 79 | & 10 0,255,0,255,0,0,0,0,0,0 Common 77 10 77 & # & [26 ]p 80 | # 10 0,255,0,255,0,0,0,0,0,0 Common 78 4 78 # # # [23 ]p 81 | Ù 5 0,255,0,255,0,0,0,0,0,0 Latin 119 0 79 Ù # Ù [d9 ]A 82 | } 10 0,255,0,255,0,0,0,0,0,0 Common 80 10 57 } # } [7d ]p 83 | ® 0 0,255,0,255,0,0,0,0,0,0 Common 81 10 81 ® # ® [ae ] 84 | $ 0 0,255,0,255,0,0,0,0,0,0 Common 82 4 82 $ # $ [24 ] 85 | Î 5 0,255,0,255,0,0,0,0,0,0 Latin 118 0 83 Î # Î [ce ]A 86 | € 0 0,255,0,255,0,0,0,0,0,0 Common 84 4 84 € # € [20ac ] 87 | £ 0 0,255,0,255,0,0,0,0,0,0 Common 85 4 85 £ # £ [a3 ] 88 | ‘ 10 0,255,0,255,0,0,0,0,0,0 Common 86 10 86 ' # ‘ [2018 ]p 89 | o 3 0,255,0,255,0,0,0,0,0,0 Latin 7 0 87 o # o [6f ]a 90 | c 3 0,255,0,255,0,0,0,0,0,0 Latin 20 0 88 c # c [63 ]a 91 | i 3 0,255,0,255,0,0,0,0,0,0 Latin 16 0 89 i # i [69 ]a 92 | a 3 0,255,0,255,0,0,0,0,0,0 Latin 5 0 90 a # a [61 ]a 93 | d 3 0,255,0,255,0,0,0,0,0,0 Latin 13 0 91 d # d [64 ]a 94 | r 3 0,255,0,255,0,0,0,0,0,0 Latin 15 0 92 r # r [72 ]a 95 | v 3 0,255,0,255,0,0,0,0,0,0 Latin 24 0 93 v # v [76 ]a 96 | g 3 0,255,0,255,0,0,0,0,0,0 Latin 14 0 94 g # g [67 ]a 97 | e 3 0,255,0,255,0,0,0,0,0,0 Latin 9 0 95 e # e [65 ]a 98 | n 3 0,255,0,255,0,0,0,0,0,0 Latin 6 0 96 n # n [6e ]a 99 | t 3 0,255,0,255,0,0,0,0,0,0 Latin 4 0 97 t # t [74 ]a 100 | l 3 0,255,0,255,0,0,0,0,0,0 Latin 11 0 98 l # l [6c ]a 101 | h 3 0,255,0,255,0,0,0,0,0,0 Latin 12 0 99 h # h [68 ]a 102 | m 3 0,255,0,255,0,0,0,0,0,0 Latin 26 0 100 m # m [6d ]a 103 | s 3 0,255,0,255,0,0,0,0,0,0 Latin 3 0 101 s # s [73 ]a 104 | p 3 0,255,0,255,0,0,0,0,0,0 Latin 10 0 102 p # p [70 ]a 105 | f 3 0,255,0,255,0,0,0,0,0,0 Latin 19 0 103 f # f [66 ]a 106 | z 3 0,255,0,255,0,0,0,0,0,0 Latin 17 0 104 z # z [7a ]a 107 | u 3 0,255,0,255,0,0,0,0,0,0 Latin 18 0 105 u # u [75 ]a 108 | k 3 0,255,0,255,0,0,0,0,0,0 Latin 27 0 106 k # k [6b ]a 109 | q 3 0,255,0,255,0,0,0,0,0,0 Latin 41 0 107 q # q [71 ]a 110 | b 3 0,255,0,255,0,0,0,0,0,0 Latin 32 0 108 b # b [62 ]a 111 | w 3 0,255,0,255,0,0,0,0,0,0 Latin 47 0 109 w # w [77 ]a 112 | x 3 0,255,0,255,0,0,0,0,0,0 Latin 44 0 110 x # x [78 ]a 113 | y 3 0,255,0,255,0,0,0,0,0,0 Latin 25 0 111 y # y [79 ]a 114 | ì 3 0,255,0,255,0,0,0,0,0,0 Latin 54 0 112 ì # ì [ec ]a 115 | ò 3 0,255,0,255,0,0,0,0,0,0 Latin 74 0 113 ò # ò [f2 ]a 116 | à 3 0,255,0,255,0,0,0,0,0,0 Latin 39 0 114 à # à [e0 ]a 117 | é 3 0,255,0,255,0,0,0,0,0,0 Latin 67 0 115 é # é [e9 ]a 118 | j 3 0,255,0,255,0,0,0,0,0,0 Latin 35 0 116 j # j [6a ]a 119 | è 3 0,255,0,255,0,0,0,0,0,0 Latin 52 0 117 è # è [e8 ]a 120 | î 3 0,255,0,255,0,0,0,0,0,0 Latin 83 0 118 î # î [ee ]a 121 | ù 3 0,255,0,255,0,0,0,0,0,0 Latin 79 0 119 ù # ù [f9 ]a 122 | -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.lstm-word-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/ita.lstm-word-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/ita.traineddata -------------------------------------------------------------------------------- /Scanverter/tessdata/ita.version: -------------------------------------------------------------------------------- 1 | 4.00.00alpha:ita:synth20170629:[1,48,0,1Ct3,3,16Mp3,3Lfys64Lfx64Lrx64Lfx384O1c1] -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.lstm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/rus.lstm -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.lstm-number-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/rus.lstm-number-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.lstm-punc-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/rus.lstm-punc-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.lstm-recoder: -------------------------------------------------------------------------------- 1 | }{{  2 |     !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.lstm-word-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/rus.lstm-word-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/rus.traineddata -------------------------------------------------------------------------------- /Scanverter/tessdata/rus.version: -------------------------------------------------------------------------------- 1 | 4.00.00alpha:rus:synth20170629:[1,36,0,1Ct3,3,16Mp3,3Lfys64Lfx96Lrx96Lfx512O1c1] -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.lstm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/spa.lstm -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.lstm-number-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/spa.lstm-number-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.lstm-punc-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/spa.lstm-punc-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.lstm-recoder: -------------------------------------------------------------------------------- 1 | mkk  2 |     !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.lstm-unicharset: -------------------------------------------------------------------------------- 1 | 109 2 | NULL 0 Common 0 3 | Joined 7 0,255,0,255,0,0,0,0,0,0 Latin 1 0 1 Joined # Joined [4a 6f 69 6e 65 64 ]a 4 | |Broken|0|1 f 0,255,0,255,0,0,0,0,0,0 Common 2 10 2 |Broken|0|1 # Broken 5 | A 5 0,255,0,255,0,0,0,0,0,0 Latin 77 0 3 A # A [41 ]A 6 | F 5 0,255,0,255,0,0,0,0,0,0 Latin 92 0 4 F # F [46 ]A 7 | E 5 0,255,0,255,0,0,0,0,0,0 Latin 84 0 5 E # E [45 ]A 8 | S 5 0,255,0,255,0,0,0,0,0,0 Latin 80 0 6 S # S [53 ]A 9 | . 10 0,255,0,255,0,0,0,0,0,0 Common 7 6 7 . # . [2e ]p 10 | V 5 0,255,0,255,0,0,0,0,0,0 Latin 97 0 8 V # V [56 ]A 11 | U 5 0,255,0,255,0,0,0,0,0,0 Latin 90 0 9 U # U [55 ]A 12 | N 5 0,255,0,255,0,0,0,0,0,0 Latin 82 0 10 N # N [4e ]A 13 | G 5 0,255,0,255,0,0,0,0,0,0 Latin 93 0 11 G # G [47 ]A 14 | M 5 0,255,0,255,0,0,0,0,0,0 Latin 87 0 12 M # M [4d ]A 15 | R 5 0,255,0,255,0,0,0,0,0,0 Latin 86 0 13 R # R [52 ]A 16 | C 5 0,255,0,255,0,0,0,0,0,0 Latin 78 0 14 C # C [43 ]A 17 | ” 10 0,255,0,255,0,0,0,0,0,0 Common 15 10 15 " # ” [201d ]p 18 | Í 5 0,255,0,255,0,0,0,0,0,0 Latin 102 0 16 Í # Í [cd ]A 19 | ? 10 0,255,0,255,0,0,0,0,0,0 Common 17 10 17 ? # ? [3f ]p 20 | » 10 0,255,0,255,0,0,0,0,0,0 Common 18 10 48 » # » [bb ]p 21 | I 5 0,255,0,255,0,0,0,0,0,0 Latin 89 0 19 I # I [49 ]A 22 | T 5 0,255,0,255,0,0,0,0,0,0 Latin 81 0 20 T # T [54 ]A 23 | ( 10 0,255,0,255,0,0,0,0,0,0 Common 21 10 44 ( # ( [28 ]p 24 | D 5 0,255,0,255,0,0,0,0,0,0 Latin 83 0 22 D # D [44 ]A 25 | O 5 0,255,0,255,0,0,0,0,0,0 Latin 79 0 23 O # O [4f ]A 26 | " 10 0,255,0,255,0,0,0,0,0,0 Common 24 10 24 " # " [22 ]p 27 | , 10 0,255,0,255,0,0,0,0,0,0 Common 25 6 25 , # , [2c ]p 28 | Y 5 0,255,0,255,0,0,0,0,0,0 Latin 94 0 26 Y # Y [59 ]A 29 | É 5 0,255,0,255,0,0,0,0,0,0 Latin 105 0 27 É # É [c9 ]A 30 | L 5 0,255,0,255,0,0,0,0,0,0 Latin 88 0 28 L # L [4c ]A 31 | K 5 0,255,0,255,0,0,0,0,0,0 Latin 98 0 29 K # K [4b ]A 32 | X 5 0,255,0,255,0,0,0,0,0,0 Latin 91 0 30 X # X [58 ]A 33 | 1 8 0,255,0,255,0,0,0,0,0,0 Common 31 2 31 1 # 1 [31 ]0 34 | 5 8 0,255,0,255,0,0,0,0,0,0 Common 32 2 32 5 # 5 [35 ]0 35 | 9 8 0,255,0,255,0,0,0,0,0,0 Common 33 2 33 9 # 9 [39 ]0 36 | P 5 0,255,0,255,0,0,0,0,0,0 Latin 99 0 34 P # P [50 ]A 37 | Q 5 0,255,0,255,0,0,0,0,0,0 Latin 101 0 35 Q # Q [51 ]A 38 | Z 5 0,255,0,255,0,0,0,0,0,0 Latin 103 0 36 Z # Z [5a ]A 39 | Á 5 0,255,0,255,0,0,0,0,0,0 Latin 106 0 37 Á # Á [c1 ]A 40 | 2 8 0,255,0,255,0,0,0,0,0,0 Common 38 2 38 2 # 2 [32 ]0 41 | H 5 0,255,0,255,0,0,0,0,0,0 Latin 85 0 39 H # H [48 ]A 42 | B 5 0,255,0,255,0,0,0,0,0,0 Latin 96 0 40 B # B [42 ]A 43 | W 5 0,255,0,255,0,0,0,0,0,0 Latin 107 0 41 W # W [57 ]A 44 | 6 8 0,255,0,255,0,0,0,0,0,0 Common 42 2 42 6 # 6 [36 ]0 45 | 4 8 0,255,0,255,0,0,0,0,0,0 Common 43 2 43 4 # 4 [34 ]0 46 | ) 10 0,255,0,255,0,0,0,0,0,0 Common 44 10 21 ) # ) [29 ]p 47 | J 5 0,255,0,255,0,0,0,0,0,0 Latin 95 0 45 J # J [4a ]A 48 | ¡ 10 0,255,0,255,0,0,0,0,0,0 Common 46 10 46 ¡ # ¡ [a1 ]p 49 | “ 10 0,255,0,255,0,0,0,0,0,0 Common 47 10 47 " # “ [201c ]p 50 | « 10 0,255,0,255,0,0,0,0,0,0 Common 48 10 18 « # « [ab ]p 51 | Ó 5 0,255,0,255,0,0,0,0,0,0 Latin 100 0 49 Ó # Ó [d3 ]A 52 | 0 8 0,255,0,255,0,0,0,0,0,0 Common 50 2 50 0 # 0 [30 ]0 53 | _ 10 0,255,0,255,0,0,0,0,0,0 Common 51 10 51 _ # _ [5f ]p 54 | * 10 0,255,0,255,0,0,0,0,0,0 Common 52 10 52 * # * [2a ]p 55 | € 0 0,255,0,255,0,0,0,0,0,0 Common 53 4 53 € # € [20ac ] 56 | / 10 0,255,0,255,0,0,0,0,0,0 Common 54 6 54 / # / [2f ]p 57 | 3 8 0,255,0,255,0,0,0,0,0,0 Common 55 2 55 3 # 3 [33 ]0 58 | | 0 0,255,0,255,0,0,0,0,0,0 Common 56 10 56 | # | [7c ] 59 | ! 10 0,255,0,255,0,0,0,0,0,0 Common 57 10 57 ! # ! [21 ]p 60 | - 10 0,255,0,255,0,0,0,0,0,0 Common 58 3 58 - # - [2d ]p 61 | : 10 0,255,0,255,0,0,0,0,0,0 Common 59 6 59 : # : [3a ]p 62 | Ñ 5 0,255,0,255,0,0,0,0,0,0 Latin 104 0 60 Ñ # Ñ [d1 ]A 63 | ; 10 0,255,0,255,0,0,0,0,0,0 Common 61 10 61 ; # ; [3b ]p 64 | 7 8 0,255,0,255,0,0,0,0,0,0 Common 62 2 62 7 # 7 [37 ]0 65 | ] 10 0,255,0,255,0,0,0,0,0,0 Common 63 10 68 ] # ] [5d ]p 66 | 8 8 0,255,0,255,0,0,0,0,0,0 Common 64 2 64 8 # 8 [38 ]0 67 | ' 10 0,255,0,255,0,0,0,0,0,0 Common 65 10 65 ' # ' [27 ]p 68 | = 0 0,255,0,255,0,0,0,0,0,0 Common 66 10 66 = # = [3d ] 69 | > 0 0,255,0,255,0,0,0,0,0,0 Common 67 10 70 > # > [3e ] 70 | [ 10 0,255,0,255,0,0,0,0,0,0 Common 68 10 63 [ # [ [5b ]p 71 | ¿ 10 0,255,0,255,0,0,0,0,0,0 Common 69 10 69 ¿ # ¿ [bf ]p 72 | < 0 0,255,0,255,0,0,0,0,0,0 Common 70 10 67 < # < [3c ] 73 | — 10 0,255,0,255,0,0,0,0,0,0 Common 71 10 71 - # — [2014 ]p 74 | Ú 5 0,255,0,255,0,0,0,0,0,0 Latin 108 0 72 Ú # Ú [da ]A 75 | + 0 0,255,0,255,0,0,0,0,0,0 Common 73 3 73 + # + [2b ] 76 | $ 0 0,255,0,255,0,0,0,0,0,0 Common 74 4 74 $ # $ [24 ] 77 | £ 0 0,255,0,255,0,0,0,0,0,0 Common 75 4 75 £ # £ [a3 ] 78 | % 10 0,255,0,255,0,0,0,0,0,0 Common 76 4 76 % # % [25 ]p 79 | a 3 0,255,0,255,0,0,0,0,0,0 Latin 3 0 77 a # a [61 ]a 80 | c 3 0,255,0,255,0,0,0,0,0,0 Latin 14 0 78 c # c [63 ]a 81 | o 3 0,255,0,255,0,0,0,0,0,0 Latin 23 0 79 o # o [6f ]a 82 | s 3 0,255,0,255,0,0,0,0,0,0 Latin 6 0 80 s # s [73 ]a 83 | t 3 0,255,0,255,0,0,0,0,0,0 Latin 20 0 81 t # t [74 ]a 84 | n 3 0,255,0,255,0,0,0,0,0,0 Latin 10 0 82 n # n [6e ]a 85 | d 3 0,255,0,255,0,0,0,0,0,0 Latin 22 0 83 d # d [64 ]a 86 | e 3 0,255,0,255,0,0,0,0,0,0 Latin 5 0 84 e # e [65 ]a 87 | h 3 0,255,0,255,0,0,0,0,0,0 Latin 39 0 85 h # h [68 ]a 88 | r 3 0,255,0,255,0,0,0,0,0,0 Latin 13 0 86 r # r [72 ]a 89 | m 3 0,255,0,255,0,0,0,0,0,0 Latin 12 0 87 m # m [6d ]a 90 | l 3 0,255,0,255,0,0,0,0,0,0 Latin 28 0 88 l # l [6c ]a 91 | i 3 0,255,0,255,0,0,0,0,0,0 Latin 19 0 89 i # i [69 ]a 92 | u 3 0,255,0,255,0,0,0,0,0,0 Latin 9 0 90 u # u [75 ]a 93 | x 3 0,255,0,255,0,0,0,0,0,0 Latin 30 0 91 x # x [78 ]a 94 | f 3 0,255,0,255,0,0,0,0,0,0 Latin 4 0 92 f # f [66 ]a 95 | g 3 0,255,0,255,0,0,0,0,0,0 Latin 11 0 93 g # g [67 ]a 96 | y 3 0,255,0,255,0,0,0,0,0,0 Latin 26 0 94 y # y [79 ]a 97 | j 3 0,255,0,255,0,0,0,0,0,0 Latin 45 0 95 j # j [6a ]a 98 | b 3 0,255,0,255,0,0,0,0,0,0 Latin 40 0 96 b # b [62 ]a 99 | v 3 0,255,0,255,0,0,0,0,0,0 Latin 8 0 97 v # v [76 ]a 100 | k 3 0,255,0,255,0,0,0,0,0,0 Latin 29 0 98 k # k [6b ]a 101 | p 3 0,255,0,255,0,0,0,0,0,0 Latin 34 0 99 p # p [70 ]a 102 | ó 3 0,255,0,255,0,0,0,0,0,0 Latin 49 0 100 ó # ó [f3 ]a 103 | q 3 0,255,0,255,0,0,0,0,0,0 Latin 35 0 101 q # q [71 ]a 104 | í 3 0,255,0,255,0,0,0,0,0,0 Latin 16 0 102 í # í [ed ]a 105 | z 3 0,255,0,255,0,0,0,0,0,0 Latin 36 0 103 z # z [7a ]a 106 | ñ 3 0,255,0,255,0,0,0,0,0,0 Latin 60 0 104 ñ # ñ [f1 ]a 107 | é 3 0,255,0,255,0,0,0,0,0,0 Latin 27 0 105 é # é [e9 ]a 108 | á 3 0,255,0,255,0,0,0,0,0,0 Latin 37 0 106 á # á [e1 ]a 109 | w 3 0,255,0,255,0,0,0,0,0,0 Latin 41 0 107 w # w [77 ]a 110 | ú 3 0,255,0,255,0,0,0,0,0,0 Latin 72 0 108 ú # ú [fa ]a 111 | -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.lstm-word-dawg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/spa.lstm-word-dawg -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/Scanverter/tessdata/spa.traineddata -------------------------------------------------------------------------------- /Scanverter/tessdata/spa.version: -------------------------------------------------------------------------------- 1 | 4.00.00alpha:spa:synth20170629 -------------------------------------------------------------------------------- /screenshots/biomethric_on_folder_locking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/biomethric_on_folder_locking.png -------------------------------------------------------------------------------- /screenshots/camera_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/camera_screen.png -------------------------------------------------------------------------------- /screenshots/faceid_permission_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/faceid_permission_request.png -------------------------------------------------------------------------------- /screenshots/folder_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/folder_details.png -------------------------------------------------------------------------------- /screenshots/folders_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/folders_grid.png -------------------------------------------------------------------------------- /screenshots/main_screen_with_folders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/main_screen_with_folders.png -------------------------------------------------------------------------------- /screenshots/recognition_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/recognition_result.png -------------------------------------------------------------------------------- /screenshots/settings_stubs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/settings_stubs.png -------------------------------------------------------------------------------- /screenshots/tesseract_recognition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/tesseract_recognition.png -------------------------------------------------------------------------------- /screenshots/visionkit_pdf_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/visionkit_pdf_view.png -------------------------------------------------------------------------------- /screenshots/visionkit_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/visionkit_result.png -------------------------------------------------------------------------------- /screenshots/visionkit_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dartsyms/scanverter/d6f303b7f907e84b5bafa5d11d9e0305786cc397/screenshots/visionkit_scan.png --------------------------------------------------------------------------------