KMMergeWindowController.swift 18 KB

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