KMMergeWindowController.swift 23 KB

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