// // KMCompressManager.swift // PDF Reader Pro // // Created by lizhe on 2024/12/17. // import Cocoa import ComPDFKit import KMComponentLibrary typealias KMCompressManagerCancelAction = () -> Void class KMCompressManager: NSObject { static let shared = KMCompressManager() var progressWindow: ComponentProgressWindowController? var isCancelCompress = false var cancelAction: KMCompressManagerCancelAction? func compress(documentURL: URL, password: String = "", limit: Bool = false, model: KMCompressSettingModel, view: NSView?, completionHandler: (@escaping (Bool, URL) -> Void)) { DispatchQueue.main.async { NSPanel.savePanel(NSWindow.currentWindow(), true) { panel in let url: URL = documentURL panel.nameFieldStringValue = ""+url.deletingPathExtension().lastPathComponent+"_Compressed" panel.allowedFileTypes = ["pdf"] } completion: { response, url, isOpen in if (response == .cancel) { return } DispatchQueue.global().async { DispatchQueue.main.async { self.showLoadingWindow(window: view?.window) } self.compress(documentURL: documentURL, fileURL: url!, password: password, limit: limit, model: model) { [unowned self] currentPage, totalPages in DispatchQueue.main.async { self.updateLoadingProgress(value: Float(CGFloat(currentPage) / CGFloat(totalPages))) } } cancelHandler: { return false } completionHandler: { [unowned self] isFinish in DispatchQueue.main.async { self.dismissLoadiingWindow(window: view?.window) if (isFinish) { if isOpen { /// 开启文档 NSDocumentController.shared.km_safe_openDocument(withContentsOf: url!, display: true) { _, _, _ in } } else { NSWorkspace.shared.activateFileViewerSelecting([url!]) } } completionHandler(isFinish, url!) } } } } } } func compress(documentURL: URL, fileURL: URL, password: String = "", limit: Bool, model: KMCompressSettingModel, progressHandler: (@escaping (Float, Float) -> Void), cancelHandler: (@escaping () -> Bool), completionHandler: (@escaping (Bool) -> Void)) { DispatchQueue.global().async { let docuemt = CPDFDocument.init(url: documentURL) if (docuemt?.isLocked)! && password.count != 0 { docuemt?.unlock(withPassword: password) } if (limit) { if let _document = docuemt, let _ = KMTools.saveWatermarkDocumentForCompress(document: _document, to: fileURL, imageQuality: 20) { } } else if (docuemt != nil) { self.compress(document: docuemt!, fileURL: fileURL, model: model, tProgressHandler: progressHandler, tCancelHandler: cancelHandler, tCompletionHandler: completionHandler) } } } func compress(document: CPDFDocument, fileURL: URL, model: KMCompressSettingModel, tProgressHandler: (@escaping (Float, Float) -> Void), tCancelHandler: (@escaping () -> Bool), tCompletionHandler: (@escaping (Bool) -> Void)) { let url = fileURL // 创建 imageOption 对象 var imageOption = CPDFOptimizeImageOption() imageOption.uperPpi = Int32(model.ppi); imageOption.targetPpi = Int32(model.maxPpi); imageOption.compAlg = .CPDFCOMP_ALG_JPEG2000 imageOption.blockSize = 256 if model.imageQualityType == .hight { imageOption.quality = 100; } else if model.imageQualityType == .medium { imageOption.quality = 60; } else if model.imageQualityType == .low { imageOption.quality = 20; } // 创建 optimizeFlag var optimizeFlag: Int = 0 if model.fontUnembed { } else { optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMEMBFONT.rawValue) } model.objectOptions.contains(.formAndAction) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMFORMCOMMITIMPORTRESETACTION.rawValue) : nil model.objectOptions.contains(.javaScript) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMJSACTION.rawValue) : nil model.objectOptions.contains(.thumbnails) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMPAGETHUMBNAIL.rawValue) : nil model.objectOptions.contains(.documentTags) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMLABEL.rawValue) : nil model.objectOptions.contains(.bookmarks) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMBK.rawValue) : nil if model.userDataOptions.contains(.commentsFormAndMultimedia) { optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMMULMEDIA.rawValue) optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMFORM.rawValue) // optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMANNOT.rawValue) } if model.userDataOptions.contains(.documentInfomationAndMetadata) { optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMDOCINFO.rawValue) optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMMEDTADATA.rawValue) } model.userDataOptions.contains(.allObject) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMOBJDATA.rawValue) : nil model.userDataOptions.contains(.fileAttachments) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMFILEATTACHMENT.rawValue) : nil if model.userDataOptions.contains(.hiddenLayerContentAndFlattenVisibleLayers) { optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMHIDERLAYER.rawValue) optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_MERGEVISIBLELAYER.rawValue) } model.otherDataOptions.contains(.invalidBookmarks) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMINVABK.rawValue) : nil model.otherDataOptions.contains(.invalidLinks) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMINVALINK.rawValue) : nil model.otherDataOptions.contains(.unrefrencedNamedDestinations) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMUNUSEDTARGET.rawValue) : nil model.otherDataOptions.contains(.pageContent) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_OPTIMIZEPAGECONTENT.rawValue) : nil optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_BCOMPRESSIMAGE.rawValue) // 开启图片压缩 optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMNOTUSE.rawValue) // 移除未使用的对象 optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMEPTOBJ.rawValue) // 移除空白对象 //数据转换 var imageOptionValue: NSValue? // 使用 `withUnsafeBytes` 获取 UnsafeRawPointer withUnsafeBytes(of: imageOption) { rawBufferPointer in guard let baseAddress = rawBufferPointer.baseAddress else { return } // 获取结构体的 Objective-C 类型编码 let objCType = KMCompressTool.encodeForStruct() // let objCType = "{CPDFOptimizeImageOption=iiiii}" imageOptionValue = NSValue(bytes: baseAddress, objCType: objCType) } // 创建字典 let options: [String: Any] = [ CPDFOptimizeCOLImageOptionKey: imageOptionValue as Any, CPDFOptimizeFlagKey: NSNumber(value: optimizeFlag) ] // 调用方法 self.isCancelCompress = false document.writeOptimize(to: url, withOptimizeOptions: options, progressHandler: { (currentPage, totalPages) in // 进度处理 print("当前页:\(currentPage), 总页数:\(totalPages)") DispatchQueue.main.async { tProgressHandler(currentPage, totalPages) } }, cancelHandler: { // 返回是否取消 let shouldCancel = self.isCancelCompress return shouldCancel // return tCancelHandler() }, completionHandler: { (finished) in // 完成处理 if finished { print("优化完成") } else { print("优化未完成") } DispatchQueue.main.async { tCompletionHandler(finished) } }) document.cancelExtractImage() } } extension KMCompressManager { func showLoadingWindow(window: NSWindow?) { self.progressWindow = ComponentProgressWindowController(windowNibName: "ComponentProgressWindowController") self.progressWindow?.properties.cancelString = KMLocalizedString("Cancel") self.progressWindow?.reloadData() self.progressWindow?.setTarget(self, action: #selector(closeProgress)) window?.beginSheet((self.progressWindow?.window ?? NSWindow())) } func updateLoadingProgress(value: Float) { DispatchQueue.main.async { self.progressWindow?.properties.progress = CGFloat(value) self.progressWindow?.reloadData() } } func dismissLoadiingWindow(window: NSWindow?) { DispatchQueue.main.async { guard let progress = self.progressWindow else { return } window?.endSheet(progress.window ?? NSWindow()) } } @objc func closeProgress() { self.isCancelCompress = true cancelAction?() } }