KMMergeWindowController.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. //
  2. // KMMergeWindowController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by lizhe on 2023/11/8.
  6. //
  7. import Cocoa
  8. typealias KMMergeWindowControllerCancelAction = (_ controller: KMMergeWindowController) -> Void
  9. typealias KMMergeWindowControllerAddFilesAction = (_ controller: KMMergeWindowController) -> Void
  10. typealias KMMergeWindowControllerMergeAction = (_ controller: KMMergeWindowController, _ filePath: String) -> Void
  11. typealias KMMergeWindowControllerClearAction = (_ controller: KMMergeWindowController) -> Void
  12. class KMMergeWindowController: KMNBaseWindowController {
  13. @IBOutlet weak var mergeView: KMMergeView!
  14. var cancelAction: KMCommonBlock?
  15. var oldPDFDocument: PDFDocument = PDFDocument()
  16. var password: String = ""
  17. var oriDucumentUrl: URL? {
  18. didSet {
  19. oldPDFDocument = PDFDocument(url: oriDucumentUrl!)!
  20. oldPDFDocument.unlock(withPassword: self.password)
  21. }
  22. }
  23. var type: KMMergeViewType = .add
  24. var pageIndex: Int?
  25. var mergeAction: KMMergeWindowControllerMergeAction?
  26. convenience init(document: PDFDocument, password: String) {
  27. self.init(windowNibName: "KMMergeWindowController")
  28. self.password = password
  29. }
  30. override func windowDidLoad() {
  31. super.windowDidLoad()
  32. self.window!.title = NSLocalizedString("Merge PDF Files", comment: "");
  33. // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
  34. self.mergeView.type = self.type
  35. mergeView.addFilesAction = { [weak self] view in
  36. guard let self = self else { return }
  37. self.addFile()
  38. }
  39. mergeView.clearAction = { view in
  40. }
  41. mergeView.mergeAction = { [weak self] view, files, size in
  42. guard let self = self else { return }
  43. self.mergeFiles(files: files, size: size)
  44. }
  45. mergeView.cancelAction = { [weak self] view in
  46. guard let self = self else { return }
  47. self._clearImageData()
  48. self.cancelAction?(self)
  49. }
  50. //原始数据加载
  51. if oriDucumentUrl != nil && oriDucumentUrl?.path.count != 0 {
  52. let file = KMFileAttribute()
  53. file.filePath = oriDucumentUrl?.path ?? ""
  54. file.password = self.password
  55. self.mergeView.files = [file]
  56. }
  57. }
  58. }
  59. extension KMMergeWindowController {
  60. func addFile() {
  61. var size = 0.0
  62. let files = self.mergeView.files
  63. for file in files {
  64. size = size + file.fileSize
  65. }
  66. if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) {
  67. let winC = KMPurchaseCompareWindowController.sharedInstance()
  68. if self.kEventTag == 1 {
  69. winC?.kEventName = "Onbrd_Merge_BuyNow"
  70. } else {
  71. winC?.kEventName = "Reading_Merge_BuyNow"
  72. }
  73. winC?.showWindow(nil)
  74. return
  75. }
  76. let openPanel = NSOpenPanel()
  77. openPanel.allowedFileTypes = KMTools.imageExtensions + KMTools.pdfExtensions
  78. openPanel.allowsMultipleSelection = true
  79. openPanel.message = NSLocalizedString("Select files to merge. To select multiple files press cmd ⌘ button on keyboard and click on the target files one by one.", comment: "")
  80. openPanel.beginSheetModal(for: self.window!) { (result) in
  81. if result == NSApplication.ModalResponse.OK {
  82. var array: [URL] = []
  83. for fileURL in openPanel.urls {
  84. if KMTools.isImageType(fileURL.pathExtension) {
  85. if let image = NSImage(contentsOf: fileURL) {
  86. if let page = PDFPage(image: image) {
  87. let document = PDFDocument()
  88. document.insert(page, at: 0)
  89. var path = self._saveImagePath() + "/" + fileURL.deletingPathExtension().lastPathComponent + ".pdf"
  90. path = KMTools.getUniqueFilePath(filePath: path)
  91. let result = document.write(toFile: path)
  92. if result {
  93. let file = KMFileAttribute()
  94. file.filePath = path
  95. file.oriFilePath = fileURL.path
  96. file.myPDFDocument = document
  97. let attribe = try?FileManager.default.attributesOfItem(atPath: fileURL.path)
  98. let fileSize = attribe?[FileAttributeKey.size] as? CGFloat ?? 0
  99. size = fileSize + size
  100. if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) {
  101. KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
  102. return
  103. }
  104. self.mergeView.files.append(file)
  105. }
  106. }
  107. } else {
  108. Task {
  109. _ = await KMAlertTool.runModel(message: NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: ""))
  110. }
  111. }
  112. } else {
  113. array.append(fileURL)
  114. }
  115. }
  116. let attribe = try?FileManager.default.attributesOfItem(atPath: openPanel.urls.first!.path)
  117. let fileSize = attribe?[FileAttributeKey.size] as? CGFloat ?? 0
  118. size = fileSize + size
  119. if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) {
  120. KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
  121. return
  122. }
  123. self.mergeView.addFilePaths(urls: array)
  124. }
  125. }
  126. }
  127. private func _saveImagePath() -> String {
  128. let rootPath = KMDataManager.fetchAppSupportOfBundleIdentifierDirectory()
  129. let path = rootPath.appendingPathComponent("Merge").path
  130. if FileManager.default.fileExists(atPath: path) == false {
  131. try?FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false)
  132. }
  133. return path
  134. }
  135. private func _clearImageData() {
  136. let path = self._saveImagePath()
  137. if FileManager.default.fileExists(atPath: path) {
  138. try?FileManager.default.removeItem(atPath: path)
  139. }
  140. }
  141. func mergeFiles(files: [KMFileAttribute], size: CGSize = .zero) {
  142. var size = 0.0
  143. for file in files {
  144. size = size + file.fileSize
  145. }
  146. if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) {
  147. KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
  148. return
  149. }
  150. var filesCount = 1
  151. if self.oriDucumentUrl != nil {
  152. filesCount = 0
  153. }
  154. if files.count <= filesCount {
  155. let alert = NSAlert.init()
  156. alert.alertStyle = .critical
  157. alert.messageText = NSLocalizedString("To start merging, please select at least 2 files.", comment: "")
  158. alert.runModal()
  159. return
  160. }
  161. var rootPDFOutlineArray: [PDFOutline] = []
  162. var allPage = true //只有是全部才支持大纲的合并
  163. for file in files {
  164. if file.fetchSelectPages().count == 0 {
  165. let alert = NSAlert.init()
  166. alert.alertStyle = .critical
  167. alert.messageText = "\(file.filePath.lastPathComponent) + \(NSLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: ""))"
  168. alert.runModal()
  169. return
  170. }
  171. allPage = file.bAllPage
  172. /*防止文件被地址变换后crash*/
  173. guard let tDocument = PDFDocument(url: NSURL(fileURLWithPath: file.filePath) as URL) else {
  174. print("文件不存在")
  175. let alert = NSAlert.init()
  176. alert.alertStyle = .critical
  177. alert.messageText = "\(file.filePath.lastPathComponent) + \(NSLocalizedString("File Not Exist", comment: ""))"
  178. alert.runModal()
  179. return
  180. }
  181. var outlineArray: [PDFOutline] = []
  182. tDocument.unlock(withPassword: file.password)
  183. if tDocument.outlineRoot != nil {
  184. rootPDFOutlineArray.append((tDocument.outlineRoot)!)
  185. self.fetchAllOfChildren((tDocument.outlineRoot)!, containerArray: &outlineArray)
  186. outlineArray.removeObject((tDocument.outlineRoot)!)
  187. } else {
  188. let rootOutline = PDFOutline.init()
  189. tDocument.outlineRoot = rootOutline
  190. if tDocument.outlineRoot != nil {
  191. rootPDFOutlineArray.append(tDocument.outlineRoot!)
  192. }
  193. }
  194. for number in file.fetchSelectPages() {
  195. let page = tDocument.page(at: number - 1)
  196. self.oldPDFDocument.insert(page!, at: self.oldPDFDocument.pageCount)
  197. }
  198. }
  199. var theFilepath = files.first?.filePath
  200. if let filepath = files.first?.oriFilePath {
  201. theFilepath = filepath
  202. }
  203. let fileName = (theFilepath?.deletingPathExtension.lastPathComponent ?? "") + "_Merged"
  204. DispatchQueue.main.async {
  205. if self.oldPDFDocument.outlineRoot == nil {
  206. self.oldPDFDocument.outlineRoot = PDFOutline.init()
  207. }
  208. var insertIndex = 0
  209. for i in 0..<rootPDFOutlineArray.count {
  210. let rootOutline = rootPDFOutlineArray[i]
  211. for j in 0..<rootOutline.numberOfChildren {
  212. self.oldPDFDocument.outlineRoot?.insertChild(rootOutline.child(at: j)!, at: insertIndex)
  213. insertIndex = insertIndex + 1
  214. }
  215. }
  216. self.handleReDraw()
  217. if self.oriDucumentUrl != nil {
  218. let newPath = self.oldPDFDocument.documentURL!.path
  219. var options: [PDFDocumentWriteOption : Any] = [:]
  220. var success = false
  221. let password = self.password
  222. let pdf = self.oldPDFDocument
  223. let savePanelAccessoryViewController = KMSavePanelAccessoryController.init()
  224. let savePanel = NSSavePanel()
  225. savePanel.nameFieldStringValue = fileName
  226. savePanel.allowedFileTypes = ["pdf"]
  227. savePanel.accessoryView = savePanelAccessoryViewController.view
  228. savePanel.beginSheetModal(for: self.window!) { result in
  229. if result == .OK {
  230. self._clearImageData()
  231. self.cancelAction?()
  232. var outputSavePanel = savePanel.url?.path ?? ""
  233. DispatchQueue.main.async {
  234. var success = false
  235. if pdf.isEncrypted {
  236. options.updateValue(password, forKey: .userPasswordOption)
  237. options.updateValue(password, forKey: .ownerPasswordOption)
  238. success = pdf.write(toFile: outputSavePanel, withOptions: options)
  239. } else {
  240. success = pdf.write(toFile: outputSavePanel)
  241. }
  242. if success {
  243. if savePanelAccessoryViewController.needOpen {
  244. NSDocumentController.shared.openDocument(withContentsOf: savePanel.url!, display: true) { document, open, error in
  245. }
  246. } else {
  247. NSWorkspace.shared.activateFileViewerSelecting([NSURL(fileURLWithPath: outputSavePanel) as URL])
  248. }
  249. } else {
  250. let alert = NSAlert.init()
  251. alert.alertStyle = .critical
  252. alert.messageText = "\(String(describing: files.first?.filePath.lastPathComponent)) + \(NSLocalizedString("Failed to merge!", comment: ""))"
  253. alert.runModal()
  254. }
  255. }
  256. }
  257. }
  258. } else {
  259. let savePanelAccessoryViewController = KMSavePanelAccessoryController.init()
  260. let savePanel = NSSavePanel()
  261. savePanel.nameFieldStringValue = fileName
  262. savePanel.allowedFileTypes = ["pdf"]
  263. savePanel.accessoryView = savePanelAccessoryViewController.view
  264. savePanel.beginSheetModal(for: self.window!) { result in
  265. if result == .OK {
  266. self._clearImageData()
  267. self.cancelAction?()
  268. var outputSavePanel = savePanel.url?.path
  269. DispatchQueue.main.async {
  270. var success = self.oldPDFDocument.write(toFile: outputSavePanel!)
  271. if !success {
  272. success = ((try?self.oldPDFDocument.dataRepresentation()?.write(to: URL(string: outputSavePanel!)!)) != nil)
  273. }
  274. if success {
  275. if savePanelAccessoryViewController.needOpen {
  276. NSDocumentController.shared.openDocument(withContentsOf: savePanel.url!, display: true) { document, open, error in
  277. }
  278. } else {
  279. NSWorkspace.shared.activateFileViewerSelecting([NSURL(fileURLWithPath: outputSavePanel!) as URL])
  280. }
  281. } else {
  282. let alert = NSAlert.init()
  283. alert.alertStyle = .critical
  284. alert.messageText = "\(String(describing: files.first?.filePath.lastPathComponent)) + \(NSLocalizedString("Failed to merge!", comment: ""))"
  285. alert.runModal()
  286. }
  287. }
  288. }
  289. }
  290. }
  291. }
  292. }
  293. func fetchAllOfChildren(_ aOutline: PDFOutline, containerArray aMArray: inout [PDFOutline]) {
  294. if !aMArray.contains(aOutline) {
  295. aMArray.append(aOutline)
  296. }
  297. for i in 0..<aOutline.numberOfChildren {
  298. if let childOutline = aOutline.child(at: i) {
  299. aMArray.append(childOutline)
  300. fetchAllOfChildren(childOutline, containerArray: &aMArray)
  301. }
  302. }
  303. }
  304. func handleReDraw() {
  305. if mergeView.originalSizeButton.state == .on {
  306. } else {
  307. let size = self.mergeView.newPageSize
  308. if size.width < 0 {
  309. return
  310. }
  311. var pagesArray: [PDFPage] = []
  312. let pageCount = self.oldPDFDocument.pageCount
  313. for i in 0..<pageCount {
  314. pagesArray.append(self.oldPDFDocument.page(at: 0)!)
  315. self.oldPDFDocument.removePage(at: 0)
  316. }
  317. for i in 0..<pageCount {
  318. let page: KMMergePDFPage = KMMergePDFPage.init()
  319. page.setBounds(NSMakeRect(0, 0, size.width, size.height), for: .mediaBox)
  320. page.drawingPage = pagesArray[i]
  321. self.oldPDFDocument.insert(page, at: i)
  322. }
  323. if self.oldPDFDocument.outlineRoot != nil {
  324. let childCount = self.oldPDFDocument.outlineRoot?.numberOfChildren
  325. var outlineArray: [PDFOutline] = []
  326. for i in 0..<childCount! {
  327. outlineArray.append((self.oldPDFDocument.outlineRoot?.child(at: i))!)
  328. }
  329. for outline in outlineArray {
  330. outline.removeFromParent()
  331. }
  332. }
  333. }
  334. }
  335. }
  336. class KMMergePDFPage: PDFPage {
  337. var drawingPage: PDFPage?
  338. override func draw(with box: PDFDisplayBox, to context: CGContext) {
  339. super.draw(with: box, to: context)
  340. let pageSize = self.bounds(for: .cropBox).size
  341. self.drawPage(with: context, page: self.drawingPage!, pageSize: pageSize)
  342. }
  343. func drawPage(with context: CGContext, page: PDFPage, pageSize: CGSize) {
  344. var originalSize = page.bounds(for: .cropBox).size
  345. // 如果页面的旋转角度为90或者270,宽高交换
  346. if page.rotation % 180 != 0 {
  347. originalSize = CGSize(width: originalSize.height, height: originalSize.width)
  348. }
  349. let wRatio = pageSize.width / originalSize.width
  350. let hRatio = pageSize.height / originalSize.height
  351. let ratio = min(wRatio, hRatio)
  352. context.saveGState()
  353. let xTransform = (pageSize.width - originalSize.width * ratio) / 2
  354. let yTransform = (pageSize.height - originalSize.height * ratio) / 2
  355. context.translateBy(x: xTransform, y: yTransform)
  356. context.scaleBy(x: ratio, y: ratio)
  357. if #available(macOS 10.12, *) {
  358. page.draw(with: .cropBox, to: context)
  359. page.transformContext(for: .cropBox)
  360. } else {
  361. NSGraphicsContext.saveGraphicsState()
  362. NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false)
  363. page.draw(with: .cropBox)
  364. NSGraphicsContext.restoreGraphicsState()
  365. page.transformContext(for: .cropBox)
  366. }
  367. context.restoreGState()
  368. }
  369. }