KMCompressManager.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. //
  2. // KMCompressManager.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by lizhe on 2024/12/17.
  6. //
  7. import Cocoa
  8. import ComPDFKit
  9. import KMComponentLibrary
  10. typealias KMCompressManagerCancelAction = () -> Void
  11. class KMCompressManager: NSObject {
  12. static let shared = KMCompressManager()
  13. var progressWindow: ComponentProgressWindowController?
  14. var isCancelCompress = false
  15. var cancelAction: KMCompressManagerCancelAction?
  16. func compress(documentURL: URL,
  17. password: String = "",
  18. limit: Bool = false,
  19. model: KMCompressSettingModel,
  20. view: NSView?,
  21. completionHandler: (@escaping (Bool, URL) -> Void)) {
  22. DispatchQueue.main.async {
  23. NSPanel.savePanel(NSWindow.currentWindow(), true) { panel in
  24. let url: URL = documentURL
  25. panel.nameFieldStringValue = ""+url.deletingPathExtension().lastPathComponent+"_Compressed"
  26. panel.allowedFileTypes = ["pdf"]
  27. } completion: { response, url, isOpen in
  28. if (response == .cancel) {
  29. return
  30. }
  31. DispatchQueue.global().async {
  32. DispatchQueue.main.async {
  33. self.showLoadingWindow(window: view?.window)
  34. }
  35. self.compress(documentURL: documentURL, fileURL: url!, password: password, limit: limit, model: model) { [unowned self] currentPage, totalPages in
  36. DispatchQueue.main.async {
  37. self.updateLoadingProgress(value: Float(CGFloat(currentPage) / CGFloat(totalPages)))
  38. }
  39. } cancelHandler: {
  40. return false
  41. } completionHandler: { [unowned self] isFinish in
  42. DispatchQueue.main.async {
  43. self.dismissLoadiingWindow(window: view?.window)
  44. if (isFinish) {
  45. if isOpen { /// 开启文档
  46. NSDocumentController.shared.km_safe_openDocument(withContentsOf: url!, display: true) { _, _, _ in
  47. }
  48. } else {
  49. NSWorkspace.shared.activateFileViewerSelecting([url!])
  50. }
  51. }
  52. completionHandler(isFinish, url!)
  53. }
  54. }
  55. }
  56. }
  57. }
  58. }
  59. func compress(documentURL: URL,
  60. fileURL: URL,
  61. password: String = "",
  62. limit: Bool,
  63. model: KMCompressSettingModel,
  64. progressHandler: (@escaping (Float, Float) -> Void),
  65. cancelHandler: (@escaping () -> Bool),
  66. completionHandler: (@escaping (Bool) -> Void)) {
  67. DispatchQueue.global().async {
  68. let docuemt = CPDFDocument.init(url: documentURL)
  69. if (docuemt?.isLocked)! && password.count != 0 {
  70. docuemt?.unlock(withPassword: password)
  71. }
  72. if (limit) {
  73. if let _document = docuemt, let _ = KMTools.saveWatermarkDocumentForCompress(document: _document, to: fileURL, imageQuality: 20) {
  74. }
  75. } else if (docuemt != nil) {
  76. self.compress(document: docuemt!,
  77. fileURL: fileURL,
  78. model: model,
  79. tProgressHandler: progressHandler,
  80. tCancelHandler: cancelHandler,
  81. tCompletionHandler: completionHandler)
  82. }
  83. }
  84. }
  85. func compress(document: CPDFDocument,
  86. fileURL: URL,
  87. model: KMCompressSettingModel,
  88. tProgressHandler: (@escaping (Float, Float) -> Void),
  89. tCancelHandler: (@escaping () -> Bool),
  90. tCompletionHandler: (@escaping (Bool) -> Void)) {
  91. let url = fileURL
  92. // 创建 imageOption 对象
  93. var imageOption = CPDFOptimizeImageOption()
  94. imageOption.uperPpi = Int32(model.ppi);
  95. imageOption.targetPpi = Int32(model.maxPpi);
  96. imageOption.compAlg = .CPDFCOMP_ALG_JPEG2000
  97. imageOption.blockSize = 256
  98. if model.imageQualityType == .hight {
  99. imageOption.quality = 100;
  100. } else if model.imageQualityType == .medium {
  101. imageOption.quality = 60;
  102. } else if model.imageQualityType == .low {
  103. imageOption.quality = 20;
  104. }
  105. // 创建 optimizeFlag
  106. var optimizeFlag: Int = 0
  107. if model.fontUnembed {
  108. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMEMBFONT.rawValue)
  109. }
  110. model.objectOptions.contains(.formAndAction) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMFORMCOMMITIMPORTRESETACTION.rawValue) : nil
  111. model.objectOptions.contains(.javaScript) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMJSACTION.rawValue) : nil
  112. model.objectOptions.contains(.thumbnails) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMPAGETHUMBNAIL.rawValue) : nil
  113. model.objectOptions.contains(.documentTags) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMLABEL.rawValue) : nil
  114. model.objectOptions.contains(.bookmarks) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMBK.rawValue) : nil
  115. if model.userDataOptions.contains(.commentsFormAndMultimedia) {
  116. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMMULMEDIA.rawValue)
  117. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMFORM.rawValue)
  118. // optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMANNOT.rawValue)
  119. }
  120. if model.userDataOptions.contains(.documentInfomationAndMetadata) {
  121. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMDOCINFO.rawValue)
  122. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMMEDTADATA.rawValue)
  123. }
  124. model.userDataOptions.contains(.allObject) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMOBJDATA.rawValue) : nil
  125. model.userDataOptions.contains(.fileAttachments) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMFILEATTACHMENT.rawValue) : nil
  126. if model.userDataOptions.contains(.hiddenLayerContentAndFlattenVisibleLayers) {
  127. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMHIDERLAYER.rawValue)
  128. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_MERGEVISIBLELAYER.rawValue)
  129. }
  130. model.otherDataOptions.contains(.invalidBookmarks) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMINVABK.rawValue) : nil
  131. model.otherDataOptions.contains(.invalidLinks) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMINVALINK.rawValue) : nil
  132. model.otherDataOptions.contains(.unrefrencedNamedDestinations) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMUNUSEDTARGET.rawValue) : nil
  133. model.otherDataOptions.contains(.pageContent) ? optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_OPTIMIZEPAGECONTENT.rawValue) : nil
  134. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_BCOMPRESSIMAGE.rawValue) // 开启图片压缩
  135. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMNOTUSE.rawValue) // 移除未使用的对象
  136. optimizeFlag |= Int(CPDF_OPTIMIZE_FLAG.CPDFOPTIMIZE_FLAG_RMEPTOBJ.rawValue) // 移除空白对象
  137. //数据转换
  138. var imageOptionValue: NSValue?
  139. // 使用 `withUnsafeBytes` 获取 UnsafeRawPointer
  140. withUnsafeBytes(of: imageOption) { rawBufferPointer in
  141. guard let baseAddress = rawBufferPointer.baseAddress else {
  142. return
  143. }
  144. // 获取结构体的 Objective-C 类型编码
  145. let objCType = KMCompressTool.encodeForStruct()
  146. // let objCType = "{CPDFOptimizeImageOption=iiiii}"
  147. imageOptionValue = NSValue(bytes: baseAddress, objCType: objCType)
  148. }
  149. // 创建字典
  150. let options: [String: Any] = [
  151. CPDFOptimizeCOLImageOptionKey: imageOptionValue as Any,
  152. CPDFOptimizeFlagKey: NSNumber(value: optimizeFlag)
  153. ]
  154. // 调用方法
  155. self.isCancelCompress = false
  156. document.writeOptimize(to: url, withOptimizeOptions: options,
  157. progressHandler: { (currentPage, totalPages) in
  158. // 进度处理
  159. print("当前页:\(currentPage), 总页数:\(totalPages)")
  160. DispatchQueue.main.async {
  161. DispatchQueue.main.async {
  162. tProgressHandler(currentPage, totalPages)
  163. }
  164. }
  165. },
  166. cancelHandler: {
  167. // 返回是否取消
  168. let shouldCancel = self.isCancelCompress
  169. return shouldCancel
  170. // return tCancelHandler()
  171. },
  172. completionHandler: { (finished) in
  173. // 完成处理
  174. if finished {
  175. print("优化完成")
  176. } else {
  177. print("优化未完成")
  178. }
  179. DispatchQueue.main.async {
  180. tCompletionHandler(finished)
  181. }
  182. })
  183. document.cancelExtractImage()
  184. }
  185. func cancel() {
  186. self.isCancelCompress = true
  187. }
  188. }
  189. extension KMCompressManager {
  190. func showLoadingWindow(window: NSWindow?) {
  191. self.progressWindow = ComponentProgressWindowController(windowNibName: "ComponentProgressWindowController")
  192. self.progressWindow?.properties.cancelString = KMLocalizedString("Cancel")
  193. self.progressWindow?.reloadData()
  194. self.progressWindow?.setTarget(self, action: #selector(closeProgress))
  195. window?.beginSheet((self.progressWindow?.window ?? NSWindow()))
  196. }
  197. func updateLoadingProgress(value: Float) {
  198. DispatchQueue.main.async {
  199. self.progressWindow?.properties.progress = CGFloat(value)
  200. self.progressWindow?.reloadData()
  201. }
  202. }
  203. func dismissLoadiingWindow(window: NSWindow?) {
  204. DispatchQueue.main.async {
  205. guard let progress = self.progressWindow else { return }
  206. window?.endSheet(progress.window ?? NSWindow())
  207. }
  208. }
  209. @objc func closeProgress() {
  210. self.isCancelCompress = true
  211. cancelAction?()
  212. }
  213. }