// // KMImageToPDFManager.swift // PDF Master // // Created by lizhe on 2022/12/6. // import Cocoa import ComPDFKit typealias KMImageToPDFManagerCompletion = (_ success: Bool, _ savePath: String?, _ errors: Array?, _ OCRerrors: Array?) -> Void typealias KMImageToPDFManagerProgress = (_ status: KMImageToPDFStatus) -> Void enum KMImageToPDFStatus: String { case unknow case prepareConversion case conversioning } class KMImageToPDFManager: NSObject { //回调 var completion: KMImageToPDFManagerCompletion? = nil var progress: KMImageToPDFManagerProgress? /** 参数 */ var status: KMImageToPDFStatus = .unknow //当前manager状态 var model: KMImageToPDFChooseModel? //内部数据 var conversionIndex: Int? //转换下标 //懒加载数组 lazy var errors: Array = [] lazy var OCRerrors: Array = [] //单例 public static let manager = KMImageToPDFManager() //MARK: public /** 导出PDF */ func exportPDF(model: KMImageToPDFChooseModel, completion: KMImageToPDFManagerCompletion?, progress: @escaping KMImageToPDFManagerProgress) { if status == .unknow { self.progress = progress //变量 self.model = model self.completion = completion //移除缓存数据 self.errors.removeAll() self.OCRerrors.removeAll() //基本参数 // let exportFilePath = self.model?.exportFilePath // let password = self.model?.password let options = self.model?.options! let exportPDFType = self.model?.exportPDFType if (options!.contains(.OCR)) { //TODO: OCR部分待SDK提供 KMPrint("OCR 暂未实现") } else if (options!.contains(.PDF)) { if (exportPDFType == .new) { self.creatPDF(model: self.model!, completion: completion) } else if (exportPDFType == .merge) { self.mergePDF(model: self.model!, completion: completion) } else if (exportPDFType == .insert) { self.mergePDF(model: self.model!, completion: completion) } } else { if completion != nil { completion!(false, model.exportFilePath, model.filePaths(), nil) } } } else { if completion != nil { completion!(false, model.exportFilePath, model.filePaths(), nil) } } } func cancel () { } //MARK: private /** 创建PDF */ private func creatPDF(model: KMImageToPDFChooseModel, completion: KMImageToPDFManagerCompletion?) { if status == .unknow { status = .conversioning let savePath = model.exportFilePath for imageFilePath in model.imageFilePaths! { let success = creatPDFConversion(filePath: imageFilePath.filePath, savePath: savePath!) KMPrint(success) if success == false { imageFilePath.state = .error self.errors.append(imageFilePath.filePath) } else { imageFilePath.state = .success } if (self.progress != nil) { self.progress!(self.status) } } status = .unknow if (self.progress != nil) { self.progress!(self.status) } if completion != nil { completion!(true, model.exportFilePath, self.errors, nil) } } else { if completion != nil { completion!(false, model.exportFilePath, model.filePaths(), nil) } } } private func creatPDFConversion(filePath: String, savePath: String) -> Bool { let imageName = NSString(string: NSString(string: filePath).lastPathComponent).deletingPathExtension let path = self.fetchDifferentFilePath(filePath: savePath + "/" + imageName + ".pdf") let document = CPDFDocument.init() var success = false // //系统 // let document = PDFDocument.init() // let pdfPage = PDFPage.init(image: NSImage(contentsOfFile: filePath)!) // document.insert(pdfPage!, at: 0) // success = document.write(toFile: path) let jpgPath = self.imageToJPG(filePath: filePath, savePath: savePath) //FIXME: 无法插入图片 let image = NSImage(contentsOfFile: jpgPath) if image != nil { let insertPageSuccess = document?.insertPage(image!.size, withImage: jpgPath, at: document!.pageCount) if insertPageSuccess != nil { KMPrint("插入成功") //信号量控制异步 let semaphore = DispatchSemaphore(value: 0) DispatchQueue.global().async { success = ((document?.write(toFile: path)) != nil) semaphore.signal() } semaphore.wait() } else { KMPrint("插入失败") } } else { KMPrint("插入失败") } return success } /** 合并PDF */ private func mergePDF(model: KMImageToPDFChooseModel, completion: KMImageToPDFManagerCompletion?) { if status == .unknow { status = .conversioning //基本参数 let exportFilePath = self.model?.exportFilePath let insetFilePath = self.model?.insertFilePath let password = self.model?.password let exportPDFType = self.model?.exportPDFType let imageFilePaths = self.model?.imageFilePaths var conversionIndex: Int = 0 var document: CPDFDocument! /** * 获取文件路径 * 如果是插入无需调整 * 如果是新增 需排除文件同名 */ let filePath = (exportPDFType == .insert) ? insetFilePath : self.fetchDifferentFilePath(filePath: (exportFilePath?.appending("/Untitled.pdf"))!) //如果是插入现有PDF,需输入密码 if exportPDFType == .insert { document = CPDFDocument.init(url: URL(fileURLWithPath: filePath!))! document.unlock(withPassword: password!) } else if exportPDFType == .merge { document = CPDFDocument.init() } //遍历图片文件 for imageData in imageFilePaths! { imageData.state = .loading var isDirectory: ObjCBool = false if (FileManager.default.fileExists(atPath: imageData.filePath, isDirectory: &isDirectory) && !isDirectory.boolValue) { let image = NSImage(contentsOfFile: imageData.filePath) if image != nil { let success = document?.insertPage(image!.size, withImage: imageData.filePath, at: UInt(conversionIndex)) if success! { imageData.state = .success } else { imageData.state = .error if image?.size.width != 0 && image?.size.height != 0 { document.insertPage(image!.size, at: UInt(conversionIndex)) } else { document.insertPage(CGSizeZero, at: UInt(conversionIndex)) } } } else { imageData.state = .error document.insertPage(CGSizeZero, at: UInt(conversionIndex)) } } conversionIndex += 1 if (self.progress != nil) { self.progress!(self.status) } } //异步处理 DispatchQueue.global().async { [unowned self] in var success = false if document!.pageCount >= 1 { if (document!.isEncrypted) { var attributes: [CPDFDocumentWriteOption : String] = [:] attributes[CPDFDocumentWriteOption.ownerPasswordOption] = password attributes[CPDFDocumentWriteOption.userPasswordOption] = password success = document.write(to: URL(string: filePath!), withOptions: attributes) } else { success = document.write(toFile: filePath!) } } if !success { self.errors.append(NSString(string: filePath!).lastPathComponent) } self.status = .unknow // 回到主线程异步 DispatchQueue.main.async { if (self.progress != nil) { self.progress!(self.status) } if completion != nil { completion!(true, filePath, self.errors, self.OCRerrors) } } } } else { if completion != nil { completion!(false, model.exportFilePath, model.filePaths(), nil) } } } //获取不同文件路径 func fetchDifferentFilePath(filePath: String) -> String { var resultFilePath = filePath var index: Int = 0 while (FileManager.default.fileExists(atPath: resultFilePath)) { index += 1 let path = NSString(string: filePath).deletingPathExtension + "(" + String(index) + ")" resultFilePath = NSString(string: path).appendingPathExtension(NSString(string: filePath).pathExtension)! } return resultFilePath; } // 图片转PNG func imageToJPG(filePath: String, savePath: String) -> String { if NSString(string: NSString(string: filePath).lastPathComponent).pathExtension == "png" || NSString(string: NSString(string: filePath).lastPathComponent).pathExtension == "PNG" { let imageName = NSString(string: NSString(string: filePath).lastPathComponent).deletingPathExtension let jpgPath = self.fetchDifferentFilePath(filePath: savePath + "/" + imageName + ".jpg") if (!FileManager.default.fileExists(atPath: jpgPath as String)) { FileManager.default.createFile(atPath: jpgPath as String, contents: nil) } // 加载 PNG 图像 guard let pngImage = NSImage(contentsOfFile: filePath) else { KMPrint("Failed to load PNG image") return filePath } // 创建 NSBitmapImageRep 对象,并将 PNG 图像绘制到其中 let bitmap = NSBitmapImageRep(data: pngImage.tiffRepresentation!) let rect = NSRect(origin: .zero, size: bitmap!.size) bitmap?.draw(in: rect) // 将 PNG 图像数据转换为 JPG 图像数据 guard let jpgData = bitmap?.representation(using: .jpeg, properties: [:]) else { KMPrint("Failed to convert PNG to JPG") return filePath } // 保存 JPG 图像数据到文件 let fileURL = URL(fileURLWithPath: jpgPath) do { try jpgData.write(to: fileURL) KMPrint("JPG image saved successfully") return fileURL.path } catch { KMPrint("Failed to save JPG image: \(error.localizedDescription)") return filePath } } return filePath } }