KMMergeWindowController.swift 16 KB

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