Browse Source

[图片转PDF] - UI

jiajie 1 year ago
parent
commit
e76d9047af

+ 24 - 0
PDF Office/PDF Master.xcodeproj/project.pbxproj

@@ -3308,6 +3308,15 @@
 		BB9E2F732A495BCD000DC68D /* KMConvertSettingLimitTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9E2F722A495BCD000DC68D /* KMConvertSettingLimitTipView.swift */; };
 		BB9E2F742A495BCD000DC68D /* KMConvertSettingLimitTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9E2F722A495BCD000DC68D /* KMConvertSettingLimitTipView.swift */; };
 		BB9E2F752A495BCD000DC68D /* KMConvertSettingLimitTipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9E2F722A495BCD000DC68D /* KMConvertSettingLimitTipView.swift */; };
+		BB9EA14F2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9EA14E2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift */; };
+		BB9EA1502B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9EA14E2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift */; };
+		BB9EA1512B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9EA14E2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift */; };
+		BB9EA1532B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB9EA1522B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib */; };
+		BB9EA1542B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB9EA1522B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib */; };
+		BB9EA1552B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB9EA1522B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib */; };
+		BB9EA1572B1EEAAC00EAFD9B /* KMImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9EA1562B1EEAAC00EAFD9B /* KMImageModel.swift */; };
+		BB9EA1582B1EEAAC00EAFD9B /* KMImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9EA1562B1EEAAC00EAFD9B /* KMImageModel.swift */; };
+		BB9EA1592B1EEAAC00EAFD9B /* KMImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB9EA1562B1EEAAC00EAFD9B /* KMImageModel.swift */; };
 		BBA00AC42B157C880043D903 /* KMToolbarZoomItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA00AC32B157C880043D903 /* KMToolbarZoomItemView.swift */; };
 		BBA00AC52B157C880043D903 /* KMToolbarZoomItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA00AC32B157C880043D903 /* KMToolbarZoomItemView.swift */; };
 		BBA00AC62B157C880043D903 /* KMToolbarZoomItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA00AC32B157C880043D903 /* KMToolbarZoomItemView.swift */; };
@@ -5414,6 +5423,9 @@
 		BB9DCC982A0A10400024A6F1 /* ComDocumentAIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ComDocumentAIKit.framework; sourceTree = "<group>"; };
 		BB9DCC9E2A0A2B0A0024A6F1 /* KMConvertSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMConvertSettingView.swift; sourceTree = "<group>"; };
 		BB9E2F722A495BCD000DC68D /* KMConvertSettingLimitTipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMConvertSettingLimitTipView.swift; sourceTree = "<group>"; };
+		BB9EA14E2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMBatchOperateImageToPDFViewController.swift; sourceTree = "<group>"; };
+		BB9EA1522B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KMBatchOperateImageToPDFViewController.xib; sourceTree = "<group>"; };
+		BB9EA1562B1EEAAC00EAFD9B /* KMImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMImageModel.swift; sourceTree = "<group>"; };
 		BBA00AC32B157C880043D903 /* KMToolbarZoomItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMToolbarZoomItemView.swift; sourceTree = "<group>"; };
 		BBA00AC82B1604D30043D903 /* KMBotaTableRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMBotaTableRowView.swift; sourceTree = "<group>"; };
 		BBA19F3129ADAC81001A285A /* signPicture_hover.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = signPicture_hover.pdf; sourceTree = "<group>"; };
@@ -10188,6 +10200,8 @@
 				BB6347AF2AF1F0BB00F5438E /* KMBatchOperateConvertViewController.xib */,
 				BBAC26A32AFE134300563A08 /* KMToolbarItemPopViewController.swift */,
 				BBAC26A72AFE138700563A08 /* KMToolbarItemPopViewController.xib */,
+				BB9EA14E2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift */,
+				BB9EA1522B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib */,
 			);
 			path = VC;
 			sourceTree = "<group>";
@@ -10384,6 +10398,7 @@
 				BB90E4F52AF3B71800B04B9F /* KMPDFWatermarkData.swift */,
 				BBB612A82AF48952000F3724 /* KMBackgroundObject.swift */,
 				BBFDFA922AF328B200E08AA2 /* KMBatchOperateManager.swift */,
+				BB9EA1562B1EEAAC00EAFD9B /* KMImageModel.swift */,
 			);
 			path = Data;
 			sourceTree = "<group>";
@@ -11105,6 +11120,7 @@
 				9F0201992A1F352100C9B673 /* KMAITranslationConfirmWindowController.xib in Resources */,
 				BB89721B294AED6C0045787C /* KMWatermarkAdjectivePreViewBaseController.xib in Resources */,
 				89E4E72F2963FBA2002DBA6F /* KMPropertiesViewPopController.xib in Resources */,
+				BB9EA1532B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib in Resources */,
 				9F8539E32947126000DF644E /* KMChromiumToolbar.xib in Resources */,
 				AD1CA41C2A061CE10070541F /* KMAnnotationScreenTypeViewItem.xib in Resources */,
 				9F8539F62947137500DF644E /* sadfavicon.png in Resources */,
@@ -11645,6 +11661,7 @@
 				BB853CAF2AF8FA67009C20C1 /* KMHeaderFooterManagerWindowController.xib in Resources */,
 				AD85D1A82AF09864000F4D28 /* KMHomeQuickToolsWindowController.xib in Resources */,
 				BB69C95D299116FD0001A9B1 /* five_line_score.pdf in Resources */,
+				BB9EA1542B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib in Resources */,
 				ADE86ABB2B0343F100414DFA /* KMWatermarkView.xib in Resources */,
 				8942F7FC2926089200389627 /* KMSignatureViewController.xib in Resources */,
 				9F080B18298CFDB300FC27DA /* KMTextImageButtonVC.xib in Resources */,
@@ -11889,6 +11906,7 @@
 				8997011B28F40898009AF911 /* KMThumbnailViewController.xib in Resources */,
 				BB89721D294AED6C0045787C /* KMWatermarkAdjectivePreViewBaseController.xib in Resources */,
 				BB0B2CDA2B04AE560088FFD8 /* LeftSideView.xib in Resources */,
+				BB9EA1552B1ECD0F00EAFD9B /* KMBatchOperateImageToPDFViewController.xib in Resources */,
 				9FA693AD2987C0590055488A /* KMStepperView.xib in Resources */,
 				BB89721D294AED6C0045787C /* KMWatermarkAdjectivePreViewBaseController.xib in Resources */,
 				AD2D74BB29F0D3A100EDC5E4 /* KMCancellationSuccessView.xib in Resources */,
@@ -12359,6 +12377,7 @@
 				AD1CA4112A061CCD0070541F /* KMAnnotationScreenColorViewItem.swift in Sources */,
 				BBF8A4012AE8B4E200788BAC /* KMBatchBaseParameter.swift in Sources */,
 				AD867FC129DFC39400F00440 /* KMBOTAAnnotationItem.swift in Sources */,
+				BB9EA1572B1EEAAC00EAFD9B /* KMImageModel.swift in Sources */,
 				BBDA8A6D2A31F9A6006A2C4E /* KMDesignStepperView.swift in Sources */,
 				BBFDFA9E2AF3814000E08AA2 /* KMTextHintWindowController.swift in Sources */,
 				ADE86AF72B0AF59A00414DFA /* KMCompareContentSettingView.swift in Sources */,
@@ -12599,6 +12618,7 @@
 				AD85D1B92AF0D2CA000F4D28 /* KMHomeQuickToolsCollectionView.swift in Sources */,
 				BBBB6CDE2AD174080035AA66 /* CPDFInkAnnotation+PDFListView.swift in Sources */,
 				9F0CB4D52986551600007028 /* KMDesignToken+Spacing.swift in Sources */,
+				BB9EA14F2B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift in Sources */,
 				BBBB6CCA2AD109F30035AA66 /* CPDFAnnotation+PDFListView.swift in Sources */,
 				BB0A55102A302DB700B6E84B /* KMTextField.swift in Sources */,
 				AD68782129A5FADC005B5210 /* KMLightMemberCache.swift in Sources */,
@@ -13502,6 +13522,7 @@
 				9FAAA326290A69920046FFCE /* KMToolSetScroller.swift in Sources */,
 				BB89724E294C1DCE0045787C /* KMWatermarkAdjectiveListTableCellView.swift in Sources */,
 				BB14700F299DC0D100784A6A /* OIDScopes.m in Sources */,
+				BB9EA1502B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift in Sources */,
 				9F0CB52E298656D900007028 /* KMDesignToken+BorderWidthRight.swift in Sources */,
 				ADD1B6E52946C00800C3FFF7 /* KMPrintChoosePageSizePosterView.swift in Sources */,
 				9F1FE4BB29406E4700E952CA /* NSImage+CTAdditions.m in Sources */,
@@ -13600,6 +13621,7 @@
 				AD0FA4F129A8580D00EDEB50 /* KMComparativeTableView.swift in Sources */,
 				BB8931922B0C9CCD0054F58B /* KMStampManagerNew.swift in Sources */,
 				AD3AAD902B1034B400DE5FE7 /* KMHeaderFooterView.swift in Sources */,
+				BB9EA1582B1EEAAC00EAFD9B /* KMImageModel.swift in Sources */,
 				BBC348032955403D008D2CD1 /* KMWatermarkFilePropertyInfoController.swift in Sources */,
 				BB147027299DC0D100784A6A /* OIDTokenRequest.m in Sources */,
 				BBFE6E592930724B00142C01 /* KMMergePageModel.swift in Sources */,
@@ -14461,6 +14483,7 @@
 				9F0CB5132986565700007028 /* KMDesignToken+BorderRadius.swift in Sources */,
 				BB86C1F428F54535005AD968 /* CPDFListView+KeyEvent.m in Sources */,
 				BB981E5B2AD4F70D001988CA /* KMWelcomeWindowController.swift in Sources */,
+				BB9EA1592B1EEAAC00EAFD9B /* KMImageModel.swift in Sources */,
 				BB276A502B0376A400AB5578 /* KMBatchOperateBaseViewController.swift in Sources */,
 				BBB9B321299A5D6D004F3235 /* KMGoogleDriveManager.m in Sources */,
 				9F1FE4DA29406E4700E952CA /* NSString+Utils.m in Sources */,
@@ -14876,6 +14899,7 @@
 				9FF94F1B29A770B500B1EF69 /* KMFillSignShapePanel.swift in Sources */,
 				BBA5429E29F13A140041BAD0 /* KMMemorandumPattern.swift in Sources */,
 				BB1BFF882AEA30B1003EB179 /* NSWindow+PopOver.swift in Sources */,
+				BB9EA1512B1ECD0400EAFD9B /* KMBatchOperateImageToPDFViewController.swift in Sources */,
 				896DD4572985FB3200ADE514 /* KMPageDisplayThemeCollectionViewItem.swift in Sources */,
 				9FD0D2B52AD5265A00DA3FF8 /* CPDFListAnnotationNoteWindowController.swift in Sources */,
 				F359916B29261F0E000D25DE /* CPDFListView+Tool.m in Sources */,

+ 47 - 0
PDF Office/PDF Master/Class/Batch/Data/KMImageModel.swift

@@ -0,0 +1,47 @@
+//
+//  KMImageModel.swift
+//  PDF Master
+//
+//  Created by liujiajie on 2023/12/5.
+//
+
+import Foundation
+
+func moniker(x: Float) -> String {
+    if x > 1048576 {
+        return "G"
+    }else if x >= 1024 {
+        return "M"
+    }else{
+        return "K"
+    }
+}
+
+func truesize(x: Float) -> Float {
+    if x > 1048576 {
+        return x / 1048576.0
+    }else if x >= 1024 {
+        return x / 1024.0
+    }else{
+        return x
+    }
+}
+
+class KMImageModel: NSObject{
+    var photoPath: String = ""
+    var photoName: String = ""
+    var photoSize: String = ""
+    var photoScale: String = ""
+    
+    init(filepath: String) {
+        super.init()
+        self.photoPath = filepath
+        let attributesDic = try? FileManager.default.attributesOfItem(atPath: filepath)
+        let fileSize = attributesDic?[FileAttributeKey.size] as? Float ?? 0
+        let size = fileSize/1024.0
+        self.photoSize = String(format: "%.1f %@", truesize(x: size), moniker(x: size))
+        self.photoName = filepath.lastPathComponent
+        let img = NSImage(contentsOfFile: filepath)
+        self.photoScale = String(format: "%.0f x %.0f", img?.size.width ?? 0, img?.size.height ?? 0)
+    }
+}

+ 362 - 1
PDF Office/PDF Master/Class/Batch/Tools/KMImageToPDFMethod.swift

@@ -7,8 +7,369 @@
 
 import Foundation
 
-class KMImageToPDFMethod: NSObject {
+let kImageToPDFFolderPath = kTempSavePath?.stringByAppendingPathComponent("ImageToPDF")
+
+@objc(KMImageToPDFMethod)
+protocol KMImageToPDFMethodDelegate: AnyObject {
+    func imageToPDFMethod(_ method: KMImageToPDFMethod, progress: Float)
+}
+
+typealias ImageToPDFResultBlock = (_ savePath: String, _ errorArr: Array<Any>, _ errorOCRArray: Array<Any>) -> Void
+
+class KMImageToPDFMethod: NSObject, KMGOCRManagerDelegate {
+    var imageTopdfDelegate: KMImageToPDFMethodDelegate?
+    var password: String = ""
+    var convertIndex: Int = 0
+    var photoArray: Array<Any>?
+    var errorArray: NSMutableArray!
+    var isOCR = false
+    var isCreatPDF = false
+    var isMerge = false
+    var isSaveAsText = false
+    
+    var results: NSMutableArray!
+    var fileSavePath = ""
+    var OCRResultString = ""
+    
+    var completeBlock: ImageToPDFResultBlock?
+    
+    var errorOCRArray: NSMutableArray!
+    
+    var appendPDF: CPDFDocument?
+    
+    override init() {
+        super.init()
+        if !FileManager.default.fileExists(atPath: kImageToPDFFolderPath ?? "") {
+            try? FileManager.default.createDirectory(at: URL(fileURLWithPath: kImageToPDFFolderPath ?? "") , withIntermediateDirectories: false, attributes: nil)
+        }
+        self.errorArray = NSMutableArray()
+        self.results = NSMutableArray()
+        self.errorOCRArray = NSMutableArray()
+    }
+    
+    deinit {
+        NotificationCenter.default.removeObserver(self)
+        imageTopdfDelegate = nil
+    }
+    
+    func exportPDFFile(fileArray: Array<Any>, savePath: String, isOCR: Bool, isCreatPDF: Bool, isMerge: Bool, isSaveAsText: Bool, complete: @escaping ImageToPDFResultBlock) {
+        self.convertIndex = 0
+        self.photoArray = fileArray
+        self.isOCR = isOCR
+        self.isCreatPDF = isCreatPDF
+        self.isMerge = isMerge
+        self.isSaveAsText = isSaveAsText
+        self.fileSavePath = savePath
+        self.completeBlock = complete
+        self.OCRResultString = ""
+        self.errorArray.removeAllObjects()
+        
+        if !isCreatPDF {
+            appendPDF = CPDFDocument(url: URL(fileURLWithPath: savePath))
+            if ((appendPDF?.unlock(withPassword:self.password)) != nil) {
+                
+            }
+        }
+        
+        if isOCR {
+            let languages = KMGOCRManager.default().selectedLanguages.value(forKeyPath: KMGOCRLanguageCodeKey) as! [Any]
+            var images = [AnyObject]()
+            for i in 0 ..< (photoArray?.count ?? 0) {
+                let filePaht: String = photoArray?[i] as! String
+                if filePaht.count > 0 && FileManager.default.fileExists(atPath: filePaht) {
+                    let image = NSImage(contentsOfFile: filePaht)
+                    images.append(image!)
+                } else {
+                    self.errorArray.add(filePaht)
+                }
+            }
+            if images.count < 1 {
+                self.completeBlock?("", self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+                return
+            }
+            let plan = UserDefaults.standard.integer(forKey: "KMOCRCurrentPlanKey")
+            if plan == 0 {
+                KMGOCRManager.default().ocrType = .google
+            } else {
+                KMGOCRManager.default().ocrType = .apple
+            }
+            KMGOCRManager.default().delegate = self
+            KMGOCRManager.default().recognitionImages(images as! [NSImage], withLanguages: languages)
+        } else {
+            if isMerge {
+                NotificationCenter.default.addObserver(self, selector: #selector(pdfDocumentPageWrite(notification:)), name: NSNotification.Name("PDFDocumentDidBeginPageWriteNotification"), object: nil)
+                self.imageToPDFFile_MergeToOneFile(path: savePath, complete: complete)
+            } else {
+                self.imageToPDFFile_SeparateToFiles(path: savePath, complete: complete)
+            }
+        }
+    }
+    
+    func imageToPDFFile_MergeToOneFile(path: String, complete: @escaping ImageToPDFResultBlock) {
+        var pdf: CPDFDocument?
+        var newPath: String = ""
+        if self.isCreatPDF {
+            pdf = CPDFDocument()
+            newPath = path.stringByAppendingPathComponent("Untitled").stringByAppendingPathExtension("pdf")
+            newPath = getUniqueFilePath(newPath)
+        } else {
+            pdf = self.appendPDF
+        }
+        
+        for pageCount in 0..<(photoArray?.count ?? 0) {
+            var isDir: ObjCBool = false
+            let filePath = photoArray?[pageCount] as! String
+            if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDir) && !isDir.boolValue {
+                let image = NSImage(contentsOfFile: filePath)!
+                _ = pdf?.km_insertPage(image.size, withImage: filePath, at: pdf?.pageCount ?? 0)
+            }
+        }
+        
+        var isSucceed = false
+        if (pdf?.pageCount ?? 0) < 1 {
+            
+        } else {
+            let attributes = NSMutableDictionary(dictionary: pdf!.documentAttributes()!, copyItems: true)
+            if ((pdf?.isEncrypted) != nil) {
+                attributes.setValue(password, forKey: (kCGPDFContextUserPassword as NSString) as String)
+                attributes.setValue(password, forKey: (kCGPDFContextOwnerPassword as NSString) as String)
+                isSucceed = ((pdf?.write(toFile: newPath, withOptions: (attributes as? [CPDFDocumentWriteOption : Any]))) != nil)
+            } else {
+                isSucceed = pdf?.write(toFile: newPath) ?? false
+            }
+        }
+        
+        if !isSucceed {
+            self.errorArray.add((newPath as NSString).lastPathComponent)
+        }
+        
+        DispatchQueue.main.async {
+            complete(newPath, self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+        }
+        
+    }
+    func imageToPDFFile_SeparateToFiles(path: String, complete: @escaping(ImageToPDFResultBlock)) {
+        if convertIndex >= photoArray?.count ?? 0 {
+            complete(path, Array<Any>(), errorOCRArray as! Array<Any>)
+            return
+        }
+        let filePath: String = photoArray?[convertIndex] as! String
+        var savePath = path
+        var isDir: ObjCBool = false
+        if FileManager.default.fileExists(atPath: filePath , isDirectory: &isDir) && !isDir.boolValue {
+            let model = KMImageModel(filepath: filePath )
+            let tString = model.photoName.deletingPathExtension
+            
+            var newpath = path.stringByAppendingPathComponent(tString).stringByAppendingPathExtension("pdf")
+            newpath = getUniqueFilePath(newpath)
+            
+            let pdf = CPDFDocument()
+            
+            if let imag = NSImage(contentsOfFile: filePath ) {
+                _ = pdf?.km_insertPage(imag.size, withImage: filePath , at: pdf?.pageCount ?? 0)
+            }
+            
+            DispatchQueue.global().async {
+                var isSucceed = false
+                
+                isSucceed = ((pdf?.write(toFile: path)) != nil)
+                var pre: Float = 0
+                if self.photoArray?.count ?? 0 > 0{
+                    pre = Float(self.convertIndex + 1) / Float(self.photoArray?.count ?? 1)
+                }else{
+                    pre = 0
+                }
+                
+                if !isSucceed {
+                    self.errorArray.add(filePath)
+                }
+                
+                if self.convertIndex < (self.photoArray?.count ?? 0) - 1 {
+                    self.convertIndex += 1
+                    self.imageToPDFFile_SeparateToFiles(path: savePath, complete: complete)
+                    
+                    DispatchQueue.main.async {
+                        self.imageTopdfDelegate?.imageToPDFMethod(self, progress: pre)
+                    }
+                } else {
+                    DispatchQueue.main.async {
+                        complete(path, self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+                    }
+                }
+            }
+            
+        } else {
+            if convertIndex < (photoArray?.count ?? 0) - 1 {
+                self.convertIndex += 1
+                self.imageToPDFFile_SeparateToFiles(path: savePath, complete: complete)
+            } else {
+                DispatchQueue.main.async {
+                    complete(path, self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+                }
+            }
+        }
+    }
+    
+    func getUniqueFilePath(_ filePath: String) -> String {
+        var i = 0
+        var uniqueFilePath = filePath
+        let fileManager = FileManager.default
+        while fileManager.fileExists(atPath: uniqueFilePath) {
+            i += 1
+            let path = String(format: "%@(%d)", filePath.deletingPathExtension,i)
+            uniqueFilePath = path.stringByAppendingPathExtension(filePath.pathExtension)
+        }
+        
+        return uniqueFilePath
+    }
+    
+    @objc func pdfDocumentPageWrite(notification: NSNotification) {
+        if ((notification.userInfo?.isEmpty) != nil) { return }
+        let num = notification.userInfo!["PDFDocumentPageIndex"] as! NSNumber
+        let pdfDocument = notification.object as! CPDFDocument
+        
+        let pre = Float(num.intValue + 1) / (Float(pdfDocument.pageCount) * 1.0)
+        DispatchQueue.main.async {
+            self.imageTopdfDelegate?.imageToPDFMethod(self, progress: pre)
+        }
+    }
+    
+    //MARK: KMGOCRManagerDelegate
+    func gocrManagerDidStartOCR(_ manager: KMGOCRManager!) {
+        
+    }
+    func gocrManagerDidFinishOCR(_ manager: KMGOCRManager!) {
+        
+    }
+    func gocrManager(_ manager: KMGOCRManager!, didCancelOCRImageAt index: Int) {
+        
+    }
+    func gocrManager(_ manager: KMGOCRManager!, didStartOCRImageAt index: Int) {
+        
+    }
+    func gocrManager(_ manager: KMGOCRManager!, didFinishOCRImageAt index: Int, results: [KMGOCRResult]!) {
+        if (results != nil) {
+            self.dealWithResults(results, OCRImageAtIndex: index)
+        }
+    }
+    func gocrManager(_ manager: KMGOCRManager!, didFailureOCRImageAt index: Int, error: Error!) {
+        let results = Array<KMGOCRResult>()
+        self.errorOCRArray.add(self.photoArray?[index] as Any)
+        self.dealWithResults(results, OCRImageAtIndex: index)
+    }
+    
+    
+    func dealWithResults(_ rlts: [KMGOCRResult]?, OCRImageAtIndex index: Int) {
+        if isOCR {
+            if isMerge {
+                self.results.add(rlts as Any)
+                var key = index
+                if isCreatPDF {
+                    key = Int((self.appendPDF?.pageCount ?? 0) + UInt(index))
+                }
+                var contents = ""
+                if OCRResultString.count > 0 {
+                    contents = self.OCRResultString
+                }
+                var str: String = results.firstObject as! String
+                contents = contents + "\n"
+                contents = contents + "Page" + "\(key + 1)"
+                contents = contents + "\n----------\n"
+                contents = contents + str
+                self.OCRResultString = contents
+                if isCreatPDF {
+                    if index > (photoArray?.count ?? 0) - 1 {
+                        var savePath = self.fileSavePath.stringByAppendingPathComponent("Untitled OCR").stringByAppendingPathExtension("pdf")
+                        savePath = self.getUniqueFilePath(savePath)
+                        if self.isSaveAsText {
+                            var savetextPath = self.fileSavePath.stringByAppendingPathComponent("Untitled OCR").stringByAppendingPathExtension("txt")
+                            savetextPath = self.getUniqueFilePath(savetextPath)
+                            try? self.OCRResultString.write(to: URL(fileURLWithPath: savetextPath), atomically: true, encoding: .utf8)
+                            NSWorkspace.shared.selectFile(savetextPath, inFileViewerRootedAtPath: "")
+                        }
+                        KMGOCRManager.default().createPDFFile(savePath, imagePaths: self.photoArray, results: (self.results as! [Any]), scale: 1.0)
+                        DispatchQueue.main.async {
+                            self.completeBlock?(savePath, self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+                        }
+                    } else {
+                        let pre = Float(index + 1) / (Float(photoArray?.count ?? 0) * 1.0)
+                        DispatchQueue.main.async {
+                            self.imageTopdfDelegate?.imageToPDFMethod(self, progress: pre)
+                        }
+                    }
+                } else {
+                    if index >= (photoArray?.count ?? 0) - 1 {
+                        var savePath = kImageToPDFFolderPath?.stringByAppendingPathComponent("Untitled OCR").stringByAppendingPathExtension("pdf")
+                        KMGOCRManager.default().createPDFFile(savePath, imagePaths: self.photoArray, results: (self.results as! [Any]), scale: 1.0)
+                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3){
+                            if FileManager.default.fileExists(atPath: savePath ?? "") {
+                                let appPDF = self.appendPDF
+                                let newPdf = CPDFDocument.init(url: URL(fileURLWithPath: savePath ?? ""))
+                                for i in 0 ..< (newPdf?.pageCount ?? 0) {
+                                    let page = newPdf?.page(at: i)
+                                    appPDF?.insertPageObject(page, at: appPDF?.pageCount ?? 0)
+                                }
+                                var isSuccessfully = false
+                                let attributes = NSMutableDictionary(dictionary: appPDF!.documentAttributes()!, copyItems: true)
+                                if ((appPDF?.isEncrypted) != nil) {
+                                    attributes.setValue(self.password, forKey: (kCGPDFContextUserPassword as NSString) as String)
+                                    attributes.setValue(self.password, forKey: (kCGPDFContextOwnerPassword as NSString) as String)
+                                    isSuccessfully = ((appPDF?.write(toFile: self.fileSavePath, withOptions: (attributes as? [CPDFDocumentWriteOption : Any]))) != nil)
+                                } else {
+                                    isSuccessfully = appPDF?.write(toFile: self.fileSavePath) ?? false
+                                }
+                                if self.isSaveAsText {
+                                    var savetextPath = kImageToPDFFolderPath?.stringByAppendingPathComponent("Untitled OCR").stringByAppendingPathExtension("txt")
+                                    savetextPath = self.getUniqueFilePath(savetextPath ?? "")
+                                    try? self.OCRResultString.write(to: URL(fileURLWithPath: savetextPath ?? ""), atomically: true, encoding: .utf8)
+                                    NSWorkspace.shared.selectFile(savetextPath, inFileViewerRootedAtPath: "")
+                                }
+                                try? FileManager.default.removeItem(atPath: savePath ?? "")
+                                DispatchQueue.main.async {
+                                    self.completeBlock?(self.fileSavePath, self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+                                }
+                            }
+                        }
+                    }else{
+                        let pre = Float(index + 1) / (Float(photoArray?.count ?? 0) * 1.0)
+                        DispatchQueue.main.async {
+                            self.imageTopdfDelegate?.imageToPDFMethod(self, progress: pre)
+                        }
+                    }
+                }
+            } else {
+                let filePath: String = photoArray?[index] as! String
+                let model = KMImageModel(filepath: filePath)
+                let tString = model.photoName.deletingPathExtension
+                var savePath = fileSavePath.stringByAppendingPathComponent(tString).stringByAppendingPathExtension("pdf")
+                savePath = getUniqueFilePath(savePath)
+                if results == nil { results = NSMutableArray() }
+                KMGOCRManager.default().createPDFFile(savePath, imagePaths: [filePath], results: (results as! [Any]), scale: 1.0)
+                var tFileName = fileSavePath.stringByAppendingPathComponent(tString).stringByAppendingPathExtension("txt")
+                tFileName = getUniqueFilePath(tFileName)
+                if isSaveAsText {
+                    var string: String = ""
+                    if results.count > 0 {
+                        string = results.firstObject as! String
+                        try? string.write(toFile: tFileName, atomically: true, encoding: .utf8)
+                    }
+                }
+                if index < (photoArray?.count ?? 0) - 1 {
+                    let pre = Float(index + 1) / Float(photoArray?.count ?? 1)
+                    DispatchQueue.main.async {
+                        self.imageTopdfDelegate?.imageToPDFMethod(self, progress: pre)
+                    }
+                } else {
+                    DispatchQueue.main.async {
+                        self.completeBlock?(savePath, self.errorArray as! Array<Any>, self.errorOCRArray as! Array<Any>)
+                    }
+                }
+            }
+        }
+    }
+    
     static func supportedImageTypes() -> [String] {
         return ["jpg", "cur", "bmp", "jpeg", "gif", "png", "tiff", "tif", "ico", "icns", "tga", "psd", "eps", "hdr", "jp2", "jpc", "pict", "sgi"]
     }
+    
 }

+ 1 - 1
PDF Office/PDF Master/Class/Batch/WindowController/KMBatchOperateLeftViewController.swift

@@ -750,7 +750,7 @@ class KMBatchOperateLeftViewController: NSViewController,NSTableViewDelegate,NST
         
         self.files = files
         self.files.removeAll()
-        addFilesToList(addArray: arr as! NSMutableArray)
+        addFilesToList(addArray: NSMutableArray.init(array: arr))
         self.tableView.reloadData()
     }
     func reloadFile(_ file: KMBatchOperateFile) {

+ 170 - 0
PDF Office/PDF Master/Class/Batch/WindowController/VC/KMBatchOperateImageToPDFViewController.swift

@@ -0,0 +1,170 @@
+//
+//  KMBatchOperateImageToPDFViewController.swift
+//  PDF Master
+//
+//  Created by liujiajie on 2023/12/5.
+//
+
+import Cocoa
+
+class KMBatchOperateImageToPDFViewController: KMBatchOperateBaseViewController, KMImageToPDFMethodDelegate{
+   
+    
+    @IBOutlet var outputTypeLabel: NSTextField!
+    @IBOutlet var createNewPDFBtn: NSButton!
+    @IBOutlet var btnMerge: NSButton!
+    @IBOutlet var appendPDFBtn: NSButton!
+    @IBOutlet var appendTextField: NSTextField!
+    @IBOutlet var appendOtherPDFBtn: NSButton!
+    @IBOutlet var appendBackView: NSView!
+
+    @IBOutlet var ocrLabel: NSTextField!
+    @IBOutlet var ocrSelectBtn: NSButton!
+    @IBOutlet var languaeBox: NSBox!
+    @IBOutlet var languageButton: NSButton!
+    @IBOutlet var saveAsButton: NSButton!
+    @IBOutlet var planButton: NSButton!
+    @IBOutlet var selectLanguageLabel: NSTextField!
+    @IBOutlet var planBox: NSBox!
+
+    @IBOutlet var actionButton: NSButton!
+
+    var password: NSString = ""
+
+    lazy var method: KMImageToPDFMethod = {
+       let method = KMImageToPDFMethod()
+        method.imageTopdfDelegate = self
+        return method
+    }()
+    
+    override var interfaceStatus: KMBatchOperateInterfaceStatus?{
+        set{
+            super.interfaceStatus = newValue
+            if newValue == .PrepareProcess {
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+                    var files = NSMutableArray()
+                    for url in self.successFilePathURLArray! {
+                        if FileManager.default.fileExists(atPath: url.path) {
+                            files.add(url)
+                        }
+                    }
+                    if files.count > 0 {
+                        let workspace = NSWorkspace.shared
+                        workspace.activateFileViewerSelecting(files as! [URL])
+                    }
+                }
+                self.actionButton.tag = 1
+                self.actionButton.title = NSLocalizedString("Save as PDF", comment: "")
+                self.actionButton.setTitleColor(KMAppearance.Layout.w0Color())
+                self.actionButton.isEnabled = true
+                self.actionButton.layer?.backgroundColor = KMAppearance.Interactive.m0Color().cgColor
+            } else {
+                self.actionButton.tag = 0
+                self.actionButton.layer?.backgroundColor = KMAppearance.Interactive.m0Color().cgColor
+                self.actionButton.setTitleColor(KMAppearance.Layout.w0Color())
+                self.actionButton.isEnabled = false
+            }
+        }
+        get{
+            return super.interfaceStatus
+        }
+    }
+    
+    deinit {
+        NotificationCenter.default.removeObserver(self)
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        self.localizedLanguage()
+        self.configuUI()
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(OCRSelectedLanguagesChangeNotification(notification:)), name: NSNotification.Name("KMOCRSelectedLanguagesChangeNotification"), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(OCRSelectedPlanChangeNotification(notification:)), name: NSNotification.Name("KMOCRSelectedPlanChangeNotification"), object: nil)
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(themeChanged(notification:)), name: NSNotification.Name("AppleInterfaceThemeChangedNotification"), object: nil)
+    }
+    func localizedLanguage() {
+        self.outputTypeLabel.stringValue = KMLocalizedString("Output",nil)
+        self.btnMerge.title = KMLocalizedString("Merge All", nil)
+        self.createNewPDFBtn.title = KMLocalizedString("New PDF Document", nil)
+        self.appendPDFBtn.title = KMLocalizedString("Append To Existing File", nil)
+        self.appendTextField.placeholderString = KMLocalizedString("Select a File", nil)
+        self.selectLanguageLabel.stringValue = KMLocalizedString("Select OCR Language:",nil)
+        self.ocrSelectBtn.title = KMLocalizedString("OCR Plan",nil)
+        let languages = KMGOCRManager.default().selectedLanguages.value(forKeyPath: KMGOCRLanguageStringKey) as! [Any]
+        self.updateLanguageButton(languages)
+        self.actionButton.title = KMLocalizedString("Save as PDF", nil)
+        self.saveAsButton.title = KMLocalizedString("Save as TXT", nil)
+        self.OCRSelectedPlanChangeAction()
+    }
+    func configuUI() {
+        
+    }
+    func updateViewColor() {
+        if KMAppearance.isDarkMode() {
+            self.view.layer?.backgroundColor = NSColor(red: 0.055, green: 0.067, blue: 0.078, alpha: 1).cgColor
+            appendBackView.layer?.borderColor = NSColor(red: 86/255.0, green: 88/255.0, blue: 90/255.0, alpha: 1).cgColor
+            appendBackView.layer?.backgroundColor = NSColor(red: 57/255.0, green: 60/255.0, blue: 62/255.0, alpha: 1).cgColor
+        } else {
+            self.view.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor
+            appendBackView.layer?.borderColor = NSColor(red: 218/255.0, green: 219/255.0, blue: 222/255.0, alpha: 1).cgColor
+            appendBackView.layer?.backgroundColor = NSColor.white.cgColor;
+        }
+    }
+    func updateLanguageButton(_ languages: [Any]) { 
+        if languages.count < 1 {
+            self.languageButton.title = KMLocalizedString("Auto Detection", nil)
+            return
+        }
+        var languageName: String = ""
+        if languages.count > 0 {
+            for i in 0..<languages.count {
+                let language = languages[i] as? String
+                if i == 0 {
+                    languageName = language ?? ""
+                } else {
+                    languageName = languageName.appendingFormat(",%@", language ?? "")
+                }
+            }
+        } else {
+            languageName = ""
+        }
+        
+        self.languageButton.title = languageName
+    }
+    
+    
+    
+    
+    
+    //MARK: Notification
+    @objc func OCRSelectedLanguagesChangeNotification(notification: Notification) {
+        let selectedLanguages = notification.object/* as? [KMBatchOperateFile]*/
+        
+    }
+    @objc func OCRSelectedPlanChangeNotification(notification: Notification) {
+        self.OCRSelectedPlanChangeAction()
+    }
+    @objc func themeChanged(notification: Notification) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+            self.updateViewColor()
+        }
+    }
+    func OCRSelectedPlanChangeAction() {
+        let plan = UserDefaults.standard.integer(forKey: "KMOCRCurrentPlanKey")
+        if plan == 0 {
+            self.planButton.title = KMLocalizedString("Plan 1 (Online)", nil)
+        } else {
+            self.planButton.title = KMLocalizedString("Plan 2 (Offline)", nil)
+        }
+        KMGOCRManager.default().selectedLanguages = NSMutableArray()
+        self.updateLanguageButton(KMGOCRManager.default().selectedLanguages.value(forKeyPath: KMGOCRLanguageStringKey) as! [Any])
+    }
+    
+    //MARK: KMImageToPDFMethodDelegate
+    func imageToPDFMethod(_ method: KMImageToPDFMethod, progress: Float) {
+        
+    }
+
+}

+ 269 - 0
PDF Office/PDF Master/Class/Batch/WindowController/VC/KMBatchOperateImageToPDFViewController.xib

@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="KMBatchOperateImageToPDFViewController" customModule="PDF_Master" customModuleProvider="target">
+            <connections>
+                <outlet property="actionButton" destination="HkY-4k-ShL" id="8zS-qE-smL"/>
+                <outlet property="appendBackView" destination="k0c-vv-NPP" id="qiK-0Y-Nqs"/>
+                <outlet property="appendOtherPDFBtn" destination="H4C-li-Hhm" id="kmI-0Y-YVe"/>
+                <outlet property="appendPDFBtn" destination="lwQ-xY-nsX" id="GEN-0Q-t29"/>
+                <outlet property="appendTextField" destination="AKV-3R-iGQ" id="MmJ-ov-KsS"/>
+                <outlet property="btnMerge" destination="Sf1-WE-VzB" id="n9e-oY-Rpj"/>
+                <outlet property="createNewPDFBtn" destination="cYX-Yt-gnO" id="bku-Gy-hF3"/>
+                <outlet property="languaeBox" destination="0xe-05-6j8" id="GM8-Xz-c6p"/>
+                <outlet property="languageButton" destination="Que-YG-sCE" id="Obq-iW-LWJ"/>
+                <outlet property="ocrLabel" destination="hyp-2H-elp" id="7wX-ia-0Rp"/>
+                <outlet property="ocrSelectBtn" destination="DvB-9f-Xgt" id="UdG-6d-27f"/>
+                <outlet property="outputTypeLabel" destination="aPB-Pb-64w" id="BBp-lK-MNk"/>
+                <outlet property="planBox" destination="xu7-RV-DKR" id="ihz-bb-VhO"/>
+                <outlet property="planButton" destination="9go-Ka-u3z" id="nxW-wW-rQJ"/>
+                <outlet property="saveAsButton" destination="mXd-Rx-c3O" id="nnV-Rw-wSx"/>
+                <outlet property="selectLanguageLabel" destination="LnK-B0-rGp" id="Lb0-iZ-L6g"/>
+                <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customView id="Hz6-mo-xeY">
+            <rect key="frame" x="0.0" y="0.0" width="480" height="425"/>
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+            <subviews>
+                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aPB-Pb-64w">
+                    <rect key="frame" x="14" y="398" width="42" height="17"/>
+                    <textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="dr8-xu-eZp">
+                        <font key="font" metaFont="systemBold" size="14"/>
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                    </textFieldCell>
+                </textField>
+                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cYX-Yt-gnO">
+                    <rect key="frame" x="14" y="368" width="148" height="18"/>
+                    <buttonCell key="cell" type="radio" title="New PDF Document" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="8NR-LX-ScR">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <connections>
+                        <action selector="buttonClicked_CreateNewPDF:" target="-2" id="dnc-f7-GgF"/>
+                    </connections>
+                </button>
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="Sf1-WE-VzB">
+                    <rect key="frame" x="32" y="345" width="87" height="18"/>
+                    <buttonCell key="cell" type="check" title="Merge  All" bezelStyle="regularSquare" imagePosition="left" inset="2" id="Gdy-u9-gi5">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                </button>
+                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lwQ-xY-nsX">
+                    <rect key="frame" x="14" y="306" width="225" height="18"/>
+                    <buttonCell key="cell" type="radio" title="Append to existing fil or portfolio" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="BMo-hm-pTQ">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <connections>
+                        <action selector="buttonClicked_AppendOtherPDF:" target="-2" id="OC7-87-QhX"/>
+                    </connections>
+                </button>
+                <customView translatesAutoresizingMaskIntoConstraints="NO" id="k0c-vv-NPP">
+                    <rect key="frame" x="34" y="279" width="430" height="24"/>
+                    <subviews>
+                        <textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="AKV-3R-iGQ">
+                            <rect key="frame" x="1" y="1" width="400" height="22"/>
+                            <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" state="on" drawsBackground="YES" id="V8A-qR-hoa">
+                                <font key="font" metaFont="system"/>
+                                <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+                                <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                            </textFieldCell>
+                        </textField>
+                        <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="H4C-li-Hhm">
+                            <rect key="frame" x="401" y="0.0" width="28" height="24"/>
+                            <buttonCell key="cell" type="bevel" title="Button" bezelStyle="rounded" image="KMImageNameOpenFile" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="T5v-RZ-nb1">
+                                <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                <font key="font" metaFont="system"/>
+                            </buttonCell>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="28" id="cGD-0f-Kll"/>
+                            </constraints>
+                            <connections>
+                                <action selector="buttonItemClicked_AppendOtherPDF:" target="-2" id="G4A-vQ-x00"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <constraints>
+                        <constraint firstItem="H4C-li-Hhm" firstAttribute="leading" secondItem="AKV-3R-iGQ" secondAttribute="trailing" id="4NY-9U-sDf"/>
+                        <constraint firstItem="H4C-li-Hhm" firstAttribute="centerY" secondItem="k0c-vv-NPP" secondAttribute="centerY" id="5Mg-b9-ZY3"/>
+                        <constraint firstAttribute="height" constant="24" id="6ZG-u1-LnN"/>
+                        <constraint firstAttribute="bottom" secondItem="H4C-li-Hhm" secondAttribute="bottom" id="Kaw-wW-X51"/>
+                        <constraint firstAttribute="bottom" secondItem="AKV-3R-iGQ" secondAttribute="bottom" constant="1" id="R4a-Ex-V6B"/>
+                        <constraint firstItem="AKV-3R-iGQ" firstAttribute="top" secondItem="k0c-vv-NPP" secondAttribute="top" constant="1" id="Szo-Nb-Hlj"/>
+                        <constraint firstItem="AKV-3R-iGQ" firstAttribute="leading" secondItem="k0c-vv-NPP" secondAttribute="leading" constant="1" id="kGW-1Y-uow"/>
+                        <constraint firstItem="H4C-li-Hhm" firstAttribute="top" secondItem="k0c-vv-NPP" secondAttribute="top" id="viv-cf-9iS"/>
+                        <constraint firstAttribute="trailing" secondItem="H4C-li-Hhm" secondAttribute="trailing" constant="1" id="yPh-Rs-3eg"/>
+                    </constraints>
+                </customView>
+                <button tag="1" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HkY-4k-ShL">
+                    <rect key="frame" x="16" y="20" width="448" height="32"/>
+                    <buttonCell key="cell" type="bevel" title="Button" bezelStyle="rounded" alignment="center" imageScaling="proportionallyDown" inset="2" id="aGb-jV-PpH">
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="32" id="mfI-CG-DGv"/>
+                    </constraints>
+                    <connections>
+                        <action selector="buttonClicked_ImageToPDF:" target="-2" id="yfO-U1-Svw"/>
+                    </connections>
+                </button>
+                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hyp-2H-elp">
+                    <rect key="frame" x="14" y="230" width="35" height="17"/>
+                    <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="OCR" id="SLY-wg-469">
+                        <font key="font" metaFont="systemBold" size="14"/>
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                        <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                    </textFieldCell>
+                </textField>
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="mXd-Rx-c3O">
+                    <rect key="frame" x="32" y="71" width="102" height="18"/>
+                    <buttonCell key="cell" type="check" title="Save as TXT" bezelStyle="regularSquare" imagePosition="left" inset="2" id="yMl-Im-6Oy">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                </button>
+                <button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gJA-ad-BvI">
+                    <rect key="frame" x="212" y="97" width="26" height="26"/>
+                    <buttonCell key="cell" type="help" bezelStyle="helpButton" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ik9-0w-YNF">
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="21" id="Mn0-It-ogu"/>
+                        <constraint firstAttribute="height" constant="21" id="v6b-Yx-hGX"/>
+                    </constraints>
+                    <connections>
+                        <action selector="buttonClicked_Help:" target="-2" id="fm7-KZ-WtB"/>
+                    </connections>
+                </button>
+                <box boxType="custom" cornerRadius="1" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="0xe-05-6j8">
+                    <rect key="frame" x="34" y="99" width="176" height="24"/>
+                    <view key="contentView" id="Sfo-EK-dRW">
+                        <rect key="frame" x="1" y="1" width="174" height="22"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Que-YG-sCE">
+                                <rect key="frame" x="0.0" y="-2" width="170" height="24"/>
+                                <buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSDescendingSortIndicator" imagePosition="right" alignment="left" imageScaling="proportionallyDown" inset="2" id="RHP-PZ-KGD">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                </buttonCell>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="24" id="tEd-dj-ZnT"/>
+                                </constraints>
+                                <connections>
+                                    <action selector="buttonClicked_ChooseLanguage:" target="-2" id="S94-NT-iXo"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <constraints>
+                            <constraint firstItem="Que-YG-sCE" firstAttribute="top" secondItem="Sfo-EK-dRW" secondAttribute="top" id="8Me-bP-4k5"/>
+                            <constraint firstAttribute="trailing" secondItem="Que-YG-sCE" secondAttribute="trailing" constant="4" id="c3q-yB-DUv"/>
+                            <constraint firstItem="Que-YG-sCE" firstAttribute="leading" secondItem="Sfo-EK-dRW" secondAttribute="leading" id="kmn-MX-fEb"/>
+                        </constraints>
+                    </view>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="24" id="W4o-2n-HHz"/>
+                        <constraint firstAttribute="width" constant="176" id="bbE-xq-Vz0"/>
+                    </constraints>
+                </box>
+                <button translatesAutoresizingMaskIntoConstraints="NO" id="DvB-9f-Xgt">
+                    <rect key="frame" x="14" y="195" width="84" height="18"/>
+                    <buttonCell key="cell" type="check" title="OCR Plan" bezelStyle="regularSquare" imagePosition="left" inset="2" id="nDI-6p-sba">
+                        <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                        <font key="font" metaFont="system"/>
+                    </buttonCell>
+                    <connections>
+                        <action selector="buttonClicked_OCRSelect:" target="-2" id="SXt-Gi-ldi"/>
+                    </connections>
+                </button>
+                <box boxType="custom" cornerRadius="1" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="xu7-RV-DKR">
+                    <rect key="frame" x="34" y="163" width="204" height="24"/>
+                    <view key="contentView" id="U53-s6-6nW">
+                        <rect key="frame" x="1" y="1" width="202" height="22"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9go-Ka-u3z">
+                                <rect key="frame" x="0.0" y="-2" width="198" height="24"/>
+                                <buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSDescendingSortIndicator" imagePosition="right" alignment="left" imageScaling="proportionallyDown" inset="2" id="2zY-MC-UG7">
+                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                                    <font key="font" metaFont="system"/>
+                                </buttonCell>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="24" id="gou-up-daG"/>
+                                </constraints>
+                                <connections>
+                                    <action selector="buttonClicked_ChoosePlan:" target="-2" id="tqP-v0-wzB"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <constraints>
+                            <constraint firstItem="9go-Ka-u3z" firstAttribute="top" secondItem="U53-s6-6nW" secondAttribute="top" id="8MJ-ro-IHu"/>
+                            <constraint firstItem="9go-Ka-u3z" firstAttribute="leading" secondItem="U53-s6-6nW" secondAttribute="leading" id="bVd-6r-ClJ"/>
+                            <constraint firstAttribute="trailing" secondItem="9go-Ka-u3z" secondAttribute="trailing" constant="4" id="dFT-Vn-Dy4"/>
+                        </constraints>
+                    </view>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="24" id="GZN-BA-UHl"/>
+                        <constraint firstAttribute="width" constant="204" id="eca-sg-oYC"/>
+                    </constraints>
+                </box>
+                <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LnK-B0-rGp">
+                    <rect key="frame" x="32" y="131" width="37" height="16"/>
+                    <textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="Pba-1m-xRx">
+                        <font key="font" metaFont="system"/>
+                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                    </textFieldCell>
+                </textField>
+            </subviews>
+            <constraints>
+                <constraint firstItem="xu7-RV-DKR" firstAttribute="top" secondItem="DvB-9f-Xgt" secondAttribute="bottom" constant="9" id="4vj-GV-hlg"/>
+                <constraint firstItem="lwQ-xY-nsX" firstAttribute="top" secondItem="Sf1-WE-VzB" secondAttribute="bottom" constant="23" id="6Ma-jA-EeT"/>
+                <constraint firstItem="HkY-4k-ShL" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="89C-67-SEc"/>
+                <constraint firstItem="LnK-B0-rGp" firstAttribute="leading" secondItem="xu7-RV-DKR" secondAttribute="leading" id="9x7-70-cgs"/>
+                <constraint firstItem="gJA-ad-BvI" firstAttribute="leading" secondItem="0xe-05-6j8" secondAttribute="trailing" constant="5" id="F71-ii-T0z"/>
+                <constraint firstItem="0xe-05-6j8" firstAttribute="leading" secondItem="Sf1-WE-VzB" secondAttribute="leading" id="Ghm-t1-ET6"/>
+                <constraint firstItem="k0c-vv-NPP" firstAttribute="leading" secondItem="Sf1-WE-VzB" secondAttribute="leading" id="INv-ai-ab9"/>
+                <constraint firstItem="0xe-05-6j8" firstAttribute="top" secondItem="LnK-B0-rGp" secondAttribute="bottom" constant="8" id="IbM-ET-fpF"/>
+                <constraint firstItem="Sf1-WE-VzB" firstAttribute="top" secondItem="cYX-Yt-gnO" secondAttribute="bottom" constant="7" id="LkA-nk-5sh"/>
+                <constraint firstItem="cYX-Yt-gnO" firstAttribute="top" secondItem="aPB-Pb-64w" secondAttribute="bottom" constant="13" id="TeU-vK-ErS"/>
+                <constraint firstItem="aPB-Pb-64w" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="10" id="UbP-Fa-eTV"/>
+                <constraint firstItem="DvB-9f-Xgt" firstAttribute="leading" secondItem="hyp-2H-elp" secondAttribute="leading" id="XyT-9R-eIO"/>
+                <constraint firstAttribute="bottom" secondItem="HkY-4k-ShL" secondAttribute="bottom" constant="20" id="YyJ-nz-wuP"/>
+                <constraint firstItem="gJA-ad-BvI" firstAttribute="centerY" secondItem="0xe-05-6j8" secondAttribute="centerY" id="b5C-zZ-uoa"/>
+                <constraint firstItem="aPB-Pb-64w" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="16" id="cm2-wz-AP3"/>
+                <constraint firstAttribute="trailing" secondItem="k0c-vv-NPP" secondAttribute="trailing" constant="16" id="dEN-Bs-9oC"/>
+                <constraint firstItem="mXd-Rx-c3O" firstAttribute="top" secondItem="0xe-05-6j8" secondAttribute="bottom" constant="11" id="evq-KN-Xwq"/>
+                <constraint firstItem="xu7-RV-DKR" firstAttribute="leading" secondItem="Sf1-WE-VzB" secondAttribute="leading" id="fyS-0U-CJM"/>
+                <constraint firstItem="LnK-B0-rGp" firstAttribute="top" secondItem="xu7-RV-DKR" secondAttribute="bottom" constant="16" id="g6b-iG-T0X"/>
+                <constraint firstItem="HkY-4k-ShL" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="16" id="jIY-t3-U5D"/>
+                <constraint firstItem="k0c-vv-NPP" firstAttribute="top" secondItem="lwQ-xY-nsX" secondAttribute="bottom" constant="4" id="jUW-x8-4aX"/>
+                <constraint firstItem="lwQ-xY-nsX" firstAttribute="leading" secondItem="aPB-Pb-64w" secondAttribute="leading" id="kVr-oh-CY2"/>
+                <constraint firstItem="DvB-9f-Xgt" firstAttribute="top" secondItem="hyp-2H-elp" secondAttribute="bottom" constant="18" id="kxm-lu-bCL"/>
+                <constraint firstItem="hyp-2H-elp" firstAttribute="leading" secondItem="aPB-Pb-64w" secondAttribute="leading" id="mdU-8E-lPS"/>
+                <constraint firstItem="hyp-2H-elp" firstAttribute="top" secondItem="k0c-vv-NPP" secondAttribute="bottom" constant="32" id="qP4-2t-rG9"/>
+                <constraint firstItem="mXd-Rx-c3O" firstAttribute="leading" secondItem="0xe-05-6j8" secondAttribute="leading" id="rJ4-0r-4NH"/>
+                <constraint firstItem="HkY-4k-ShL" firstAttribute="top" relation="greaterThanOrEqual" secondItem="mXd-Rx-c3O" secondAttribute="bottom" constant="20" id="rqm-ei-Qo0"/>
+                <constraint firstItem="Sf1-WE-VzB" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="34" id="se3-wj-gNP"/>
+                <constraint firstItem="cYX-Yt-gnO" firstAttribute="leading" secondItem="aPB-Pb-64w" secondAttribute="leading" id="yIs-gc-xSI"/>
+            </constraints>
+            <point key="canvasLocation" x="-141" y="181"/>
+        </customView>
+    </objects>
+    <resources>
+        <image name="KMImageNameOpenFile" width="16" height="16"/>
+        <image name="NSDescendingSortIndicator" width="9" height="9"/>
+    </resources>
+</document>

+ 1 - 1
PDF Office/PDF Master/Class/Common/Base/KMBaseWindowController.swift

@@ -33,7 +33,7 @@ extension KMBaseWindowController {
     static func checkPassword(url: URL, password: String = "", completion: @escaping ((_ success: Bool, _ resultPassword: String) -> Void)) {
         let document = CPDFDocument.init(url: url)
         if document!.isLocked {
-            if let unlockSuccess = document?.unlock(withPassword: password) {
+            if (document?.unlock(withPassword: password)) != nil {
                 completion(true, password)
             } else {
                 DispatchQueue.main.async {

+ 1 - 1
PDF Office/PDF Master/Class/Home/Category/String+KMExtensions.swift

@@ -24,7 +24,7 @@ extension String {
     // 获取路径字符串后缀不带“.”
     var `customPathExtension`: String {
         if let index = self.lastIndex(of: ".") {
-            var str = String(self[index...])
+            let str = String(self[index...])
             let charaset = CharacterSet(charactersIn: ".")
             return str.trimmingCharacters(in: charaset)
         } else {

+ 81 - 0
PDF Office/PDF Master/Class/Home/ViewController/KMHomeViewController+Action.swift

@@ -884,6 +884,87 @@ extension KMHomeViewController {
     
     func fastTool_ImageToPDF() {    // 图片转PDF
 //        KMImageToPDFWindowController.openFiles(window: NSApp.mainWindow!)
+        let openPanel = NSOpenPanel()
+        openPanel.allowedFileTypes = KMImageToPDFMethod.supportedImageTypes()
+        //MARK: 允许多选还是单选,如果是付费用户允许多选
+        openPanel.allowsMultipleSelection = true
+        openPanel.message = KMLocalizedString("Select images to create a new document. To select multiple files press cmd ⌘ button on keyboard and click on the target files one by one.", nil)
+//        openPanel.canChooseFiles = false
+//        openPanel.canChooseDirectories = true
+//        openPanel.canCreateDirectories = true
+        openPanel.beginSheetModal(for: self.view.window!) { [self] (result) in
+            if result == NSApplication.ModalResponse.OK {
+                openImageToPdfWindow(urls: openPanel.urls)
+            }
+        }
+        
+    }
+    
+    func openImageToPdfWindow(urls: Array<URL>) {
+        var arr: Array<KMBatchOperateFile> = Array()
+        for fileURL in urls {
+            let img = NSImage(contentsOfFile: fileURL.path)
+            if self.isDamageImage(image: img, path: fileURL.path) {
+                let alert = NSAlert()
+                alert.alertStyle = .critical
+                alert.messageText = String(format: KMLocalizedString("The file \"%@\" could not be opened.", nil), fileURL.path.lastPathComponent)
+                alert.informativeText = NSLocalizedString("It may be damaged or use a file format that PDF Reader Pro doesn’t recognize.", comment: "")
+                alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
+                alert.beginSheetModal(for: NSApp.mainWindow!) { (response) in
+                    if response == .alertFirstButtonReturn {
+                        // Handle cancel action
+                    }
+                }
+                continue
+            }
+            let file = KMBatchOperateFile(filePath: fileURL.path, type: .CreatePDF)
+            arr.append(file)
+        }
+        let baseWindowController = KMBatchOperateBaseWindowController(windowNibName: "KMBatchOperateBaseWindowController")
+        if #available(macOS 10.13, *) {
+            baseWindowController.window?.makeKeyAndOrderFront(nil)
+        } else {
+            baseWindowController.showWindow(nil)
+        }
+        if arr.count > 0 {
+            baseWindowController.checkNeedPasswordSwitchToOperateType(operateType: .CreatePDF, files: arr)
+        }
+    }
+    
+    func isDamageImage(image: NSImage?, path: String) -> Bool {
+        if (image == nil) {
+            return true
+        }
+        let addImageAnnotation = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last!.appendingPathComponent(Bundle.main.bundleIdentifier!).appendingPathComponent("addImageAnnotation")
+//        let addImageAnnotation =  NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("addImageAnnotation")
+        if !FileManager.default.fileExists(atPath: addImageAnnotation.path) {
+            try? FileManager.default.createDirectory(atPath: addImageAnnotation.path, withIntermediateDirectories: false, attributes: nil)
+        }
+        guard let data = image!.tiffRepresentation else { return false }
+        guard let imageRep = NSBitmapImageRep(data: data) else { return false }
+        imageRep.size = image!.size
+        var imageData: Data?
+        if path.lowercased() == "png" {
+            imageData = imageRep.representation(using: .png, properties: [:])
+        } else {
+            imageData = imageRep.representation(using: .jpeg, properties: [:])
+        }
+        let rPath: URL = addImageAnnotation.appendingPathComponent(tagString()).appendingPathExtension("png")
+        if let data = imageData {
+            try?data.write(to: rPath)
+            return false
+        } else {
+            return true
+        }
+    }
+    func tagString() -> String {
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "yyMMddHHmmss"
+        let currentDate = Date()
+        let formattedDate = dateFormatter.string(from: currentDate)
+        let randomNum = Int(arc4random_uniform(10000))
+        let str = String(format: "%@%04d", dateFormatter.string(from: Date()),randomNum)
+        return str//"(formattedDate)(String(format: "%04d", randomNum))"
     }
     
     func fastTool_MergePDF() {  // MergePDF