KMCompressManager.swift 11 KB

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