// // KMBatchManager.swift // PDF Master // // Created by lizhe on 2023/2/17. // import Cocoa enum KMBatchManagerSate: Int, CaseIterable { case unknow = 0 case processing case complete case error } let kBacthFilesProcessNotification = "kBacthFilesProcessNotification" let kBacthProcessNotification = "kBacthProcessNotification" class KMBatchManager: NSObject { public static let manager = KMBatchManager() fileprivate(set) var state: KMBatchManagerSate = .unknow var filesData: [KMBatchProcessingTableViewModel] = [] var batchFilesData: [KMBatchProcessingTableViewModel] { get { var resultArray:[KMBatchProcessingTableViewModel] = [] for item in filesData { if !item.isLock { resultArray.append(item) } } return resultArray } } func batch(type: KMBatchCollectionViewType, data: KMBatchSettingItemViewModel) { let panel = NSOpenPanel() panel.canChooseFiles = false panel.canChooseDirectories = true panel.canCreateDirectories = true panel.beginSheetModal(for: NSWindow.currentWindow()) { response in if response == .cancel { return } let outputFolderPath = (panel.url?.path)! // self.batchUnkown() switch type { case .convertPDF: self.convertPDFExport(data: data, outputFolderPath: outputFolderPath) break case .OCR: self.convertOCRExport(data: data, outputFolderPath: outputFolderPath) break case .compress: self.compressExport(data: data, outputFolderPath: outputFolderPath) break case .security: self.securityExport(data: data, outputFolderPath: outputFolderPath) break case .watermark: self.waterMarkApplay(data: data, outputFolderPath: outputFolderPath) break case .background: self.backgroundApplay(data: data, outputFolderPath: outputFolderPath) break case .headerAndFooter: self.headAndFooterApplay(data: data, outputFolderPath: outputFolderPath) break case .batesNumber: self.batesApplay(data: data, outputFolderPath: outputFolderPath) break case .batchRemove: self.removeApplay(data: data, outputFolderPath: outputFolderPath) break case .imageToPDF: self.imageToPDFExport(data: data, outputFolderPath: outputFolderPath) break default: KMPrint("找不到") break } // self.batchProgress() } } } //MARK: 批量 extension KMBatchManager { //MARK: 转档 func convertPDFExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) { self.convertFile(outputFolderPath: outputFolderPath, data: data, filesData: self.batchFilesData) } func convertFile(outputFolderPath: String, data: KMBatchSettingItemViewModel, filesData: [KMBatchProcessingTableViewModel]) { guard !filesData.isEmpty else { return } func processFile(at index: Int) { guard index < filesData.count else { self.batchSuccess() return } let item = filesData[index] let filePath = item.filePath let document = self.fetchDocument(filePath: filePath, model: item) let settingData = data as? KMBatchConvertPDFViewModel ?? KMBatchConvertPDFViewModel() var path = self.fetchFilePath(type: .convertPDF, filePath: filePath, outputFolderPath: outputFolderPath) let convert = self.addConvertParameter(settingData) let pageCount = document.pageCount // 获取页面 let pages: [Int] = Array(1...Int(pageCount)) convert.outputFolderPath = outputFolderPath convert.filePath = filePath convert.password = item.password convert.outputFileName = path.deletingPathExtension.lastPathComponent convert.pages = pages convert.isAllowOCR = settingData.needRecognizeText convert.ocrLanguage = settingData.languageType item.state = .clock KMPDFConvertManager.defaultManager.convert(convert: convert, progress: { [unowned self] progressValue in print("转档进度 - \(progressValue)") let progress = Float(progressValue) / Float(pageCount) self.itemProgress(item: item, processValue: progress) }, completion: { [unowned self] finished, error in if finished { if FileManager.default.fileExists(atPath: outputFolderPath) { NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: outputFolderPath)]) } self.itemSuccess(item: item) } else { self.itemFailure(item: item, error: error! as NSError) } processFile(at: index + 1) }) } // 开始处理第一个文件 processFile(at: 0) } func addConvertParameter(_ data: KMBatchConvertPDFViewModel) -> KMPDFConvert { let settingData = data var convert = KMPDFConvert() switch settingData.convertPDFType { case .word: convert = KMPDFConvertWord() if settingData.layoutSettingType == .flowingText { convert.isAllInOneSheet = false } else { convert.isAllInOneSheet = true } case .excel: convert = KMPDFConvertExcel() if settingData.excelSetting == .separate { convert.isAllInOneSheet = false convert.isExtractTable = false } else if settingData.excelSetting == .format { convert.isAllInOneSheet = true convert.isExtractTable = false } else if settingData.excelSetting == .tables { convert.isAllInOneSheet = false convert.isExtractTable = true switch settingData.excelTablesType { case .oneTable: convert.extractTableIndex = 0 case .pageTable: convert.extractTableIndex = 1 case .allTable: convert.extractTableIndex = 2 default: KMPrint("未找到") } } case .ppt: convert = KMPDFConvertPPT() case .csv: convert = KMPDFConvertCSV() if settingData.csvOnlyTables { convert.isExtractTable = true switch settingData.excelTablesType { case .oneTable: convert.extractTableIndex = 0 case .pageTable: convert.extractTableIndex = 1 case .allTable: convert.extractTableIndex = 2 default: KMPrint("未找到") } } else { convert.isExtractTable = false } case .image, .jpeg, .jpg, .jpeg2000, .bmp, .tiff, .png, .tga: convert = KMPDFConvertImage() convert.convertType = data.imageType var dpi: Int = 150 if data.imageDpiIndex == 0 { dpi = 50 } else if data.imageDpiIndex == 1 { dpi = 72 } else if data.imageDpiIndex == 2 { dpi = 96 } else if data.imageDpiIndex == 3 { dpi = 150 } else if data.imageDpiIndex == 4 { dpi = 300 } else if data.imageDpiIndex == 5 { dpi = 600 } else { dpi = 150 } if (data.imageType == .jpeg) { (convert as! KMPDFConvertImage).imageType = .JPEG (convert as! KMPDFConvertImage).imageDpi = dpi } else if (data.imageType == .png) { (convert as! KMPDFConvertImage).imageType = .PNG (convert as! KMPDFConvertImage).imageDpi = dpi } else { (convert as! KMPDFConvertImage).imageDpi = 150 } case .html: convert = KMPDFConvertHTML() case .rtf: convert = KMPDFConvertRTF() case .json: convert = KMPDFConvertJson() if settingData.jsonType == .extractText { convert.isAllInOneSheet = false } else { convert.isAllInOneSheet = true } case .text: convert = KMPDFConvertText() default: KMPrint("不清楚") } return convert } //MARK: OCR func convertOCRExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) { self.convertOCR(outputFolderPath: outputFolderPath, data: data as! KMOCRModel, filesData: self.batchFilesData) } func convertOCR(outputFolderPath: String, data: KMOCRModel, filesData: [KMBatchProcessingTableViewModel]?) { guard let filesData = filesData else { return } for i in 0.. = document!.watermarks() ?? [] for model in array { document!.removeWatermark(model) } } if (FileManager.default.fileExists(atPath: path)) { try?FileManager.default.removeItem(atPath: path) } let result = document!.write(to: URL(fileURLWithPath: path)) if (result) { KMPrint("removeFile成功") self.itemSuccess(item: item) } else { KMPrint("removeFile失败") self.itemFailure(item: item, error: nil) } if i == filesData.count - 1 { self.batchSuccess() } NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: outputFolderPath)]) } // } } } } //MARK: 图片转PDF func imageToPDFExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) { self.imageToPDFFile(outputFolderPath: outputFolderPath, data: data as! KMBatchImageToPDFModel, filesData: self.batchFilesData) } func imageToPDFFile(outputFolderPath: String, data: KMBatchImageToPDFModel, filesData: [KMBatchProcessingTableViewModel]) { if filesData.count != 0 { self.batchProgress() if data.isNewPDF { if data.isMergeAll { let item = filesData[0] var path = self.fetchFilePath(type: .imageToPDF, filePath: item.filePath, outputFolderPath: outputFolderPath) var pdfDocument = CPDFDocument() for item in filesData { pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0) } if data.isOCR { let model = KMOCRModel() model.showType = .page model.saveAsPDF = true model.ocrType = data.ocrType model.languageType = data.languageType model.needTxT = data.isExtractText model.pageRangeType = .all //计算需要处理的页面 let pages:[Int] = KMOCRManager.fetchPageIndex(document: pdfDocument!, model: model) model.pageRange = pages self.batchProgress() self.convertOCR(outputFolderPath: outputFolderPath, document: pdfDocument!, fileName: path.deletingPathExtension.lastPathComponent, data: model) { progress in self.batchProgress() } complete: { document, text, error in self.batchSuccess() } } else { let success = pdfDocument?.write(toFile: path) if success != nil { for item in filesData { self.itemSuccess(item: item) } self.batchSuccess() } else { self.batchFailure() } } } else { processFile(at: 0, outputFolderPath: outputFolderPath, data: data) } } else { var fileName = data.selectFilePath.deletingPathExtension.lastPathComponent let path = self.fetchFilePath(type: .imageToPDF, filePath: data.selectFilePath, outputFolderPath: outputFolderPath) var pdfDocument = CPDFDocument(url: NSURL(fileURLWithPath: data.selectFilePath) as URL) let count: Int = Int(pdfDocument?.pageCount ?? 0) for item in filesData { pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0) } if data.isOCR { let model = KMOCRModel() model.showType = .page model.saveAsPDF = true model.ocrType = data.ocrType model.languageType = data.languageType model.needTxT = data.isExtractText model.pageRangeType = .all //计算需要处理的页面 let pages:[Int] = KMOCRManager.fetchPageIndex(document: pdfDocument!, model: model) model.pageRange = pages self.convertOCR(outputFolderPath: outputFolderPath, document: pdfDocument!, fileName: fileName, data: model) { progress in } complete: { document, text, error in if (error != nil) { self.batchFailure() } else { for item in filesData { self.itemSuccess(item: item) } self.batchSuccess() } } } else { let success = pdfDocument?.write(toFile: path) if success != nil { for item in filesData { self.itemSuccess(item: item) } self.batchSuccess() } else { self.batchFailure() } } } } } func processFile(at index: Int, outputFolderPath: String, data: KMBatchImageToPDFModel) { guard index < filesData.count else { self.batchSuccess() return } let item = filesData[index] if data.isOCR { let path = self.fetchFilePath(type: .imageToPDF, filePath: item.filePath, outputFolderPath: outputFolderPath) let pdfDocument = CPDFDocument() pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0) let model = KMOCRModel() model.showType = .page model.saveAsPDF = true model.ocrType = data.ocrType model.languageType = data.languageType model.needTxT = data.isExtractText model.pageRangeType = .all let pages: [Int] = KMOCRManager.fetchPageIndex(document: pdfDocument!, model: model) model.pageRange = pages self.itemProgress(item: item, processValue: 0) self.convertOCR(outputFolderPath: outputFolderPath, document: pdfDocument!, fileName: path.deletingPathExtension.lastPathComponent, data: model) { [unowned self] progress in self.itemProgress(item: item, processValue: progress) } complete: { [unowned self] document, text, error in self.itemSuccess(item: filesData[index]) processFile(at: index + 1, outputFolderPath: outputFolderPath, data: data) } } else { let path = self.fetchFilePath(type: .imageToPDF, filePath: item.filePath, outputFolderPath: outputFolderPath) let pdfDocument = CPDFDocument() pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0) let success = pdfDocument?.write(toFile: path) if success != nil { self.itemSuccess(item: item) processFile(at: index + 1, outputFolderPath: outputFolderPath, data: data) } else { self.itemFailure(item: item, error: nil) } } } } //MARK: private extension KMBatchManager { func fetchValidPageIndexString(_ document: CPDFDocument, model: KMBatchProcessingTableViewModel) -> String? { if model.pageRange == .all { let pages = Array(0.. CPDFDocument { var document = CPDFDocument(url: URL(fileURLWithPath: filePath)) if model.password.count != 0 { document?.unlock(withPassword: model.password) } if model.pageRange == .all { } else { let data = KMOCRModel() data.pageRangeType = model.pageRange data.pageRangeString = model.pageRangeString let pages:[Int] = KMOCRManager.fetchPageIndex(document: document!, model: data) var tempDocument = CPDFDocument() for i in 0.. String { let floderPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("BatchTemp") let filePath = floderPath?.stringByAppendingPathComponent("\(fileName).pdf") if let data = floderPath, !FileManager.default.fileExists(atPath: data) { try?FileManager.default.createDirectory(atPath: data, withIntermediateDirectories: false) } if let data = filePath, !FileManager.default.fileExists(atPath: data) { FileManager.default.createFile(atPath: data, contents: nil) } return filePath ?? "" } func removeTempFilePath(filePath: String) { let fileName = filePath.deletingPathExtension.lastPathComponent let path = self.fetchTempFilePath(fileName: fileName) if (FileManager.default.fileExists(atPath: path)) { try?FileManager.default.removeItem(atPath: path) } } func fetchFilePath(type: KMBatchCollectionViewType, filePath: String, outputFolderPath: String) -> String { var fileName = filePath.deletingPathExtension.lastPathComponent if fileName.isEmpty { fileName = NSLocalizedString("Untitled", comment: "") } var path = outputFolderPath + "/" + fileName + ".pdf" // // 检查文件是否已存在,如果存在,则添加数字后缀 // var finalPath = path // var count = 1 // while FileManager.default.fileExists(atPath: finalPath) { // let newFileName = "\(fileName) \(count)" // finalPath = outputFolderPath + "/" + newFileName + ".pdf" // count += 1 // } // 使用最终路径进行保存或其他操作 // path = finalPath return path } } //MARK: Alert extension KMBatchManager { func batchUnkown() { self.state = .unknow NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil) } func batchProgress() { for item in filesData { self.itemProgress(item: item, processValue: 0) } self.state = .processing NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil) } func batchSuccess() { self.state = .complete NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil) } func batchFailure() { self.state = .error NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil) } func itemProgress(item: KMBatchProcessingTableViewModel, processValue: Float) { if processValue > 0.7 { item.state = .loading70 } else { item.state = .loading } NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item) } func itemSuccess(item: KMBatchProcessingTableViewModel) { self.removeTempFilePath(filePath: item.filePath) item.state = .success NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item) } func itemFailure(item: KMBatchProcessingTableViewModel, error: NSError?) { self.removeTempFilePath(filePath: item.filePath) item.state = .error NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item) guard let error = error else { return } var errorString = "" let myError: NSError = error as NSError if myError.code == 1 { errorString = NSLocalizedString("Password required or incorrect password. Please re-enter your password and try again", comment: "") } else if myError.code == 2 { errorString = NSLocalizedString("The license doesn't allow the permission", comment: "") } else if myError.code == 3 { errorString = NSLocalizedString("Malloc failure", comment: "") } else if myError.code == 4 { errorString = NSLocalizedString("Unknown error in processing conversion. Please try again later", comment: "") } else if myError.code == 5 { errorString = NSLocalizedString("Unknown error in processing PDF. Please try again later", comment: "") } else if myError.code == 6 { errorString = NSLocalizedString("File not found or could not be opened. Check if your file exists or choose another file to convert", comment: "") } else if myError.code == 7 { errorString = NSLocalizedString("File not in PDF format or corruptead. Change a PDF file and try again", comment: "") } else if myError.code == 8 { errorString = NSLocalizedString("Unsupported security scheme", comment: "") } else if myError.code == 9 { errorString = NSLocalizedString("Page not found or content error", comment: "") } else { errorString = NSLocalizedString("Table not found", comment: "") } let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Conversion Failed", comment: "") alert.informativeText = errorString alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) alert.runModal() } }