// // KMMergeWindowController.swift // PDF Reader Pro // // Created by lizhe on 2023/11/8. // import Cocoa typealias KMMergeWindowControllerCancelAction = (_ controller: KMMergeWindowController) -> Void typealias KMMergeWindowControllerAddFilesAction = (_ controller: KMMergeWindowController) -> Void typealias KMMergeWindowControllerMergeAction = (_ controller: KMMergeWindowController, _ filePath: String) -> Void typealias KMMergeWindowControllerClearAction = (_ controller: KMMergeWindowController) -> Void class KMMergeWindowController: KMBaseWindowController { @IBOutlet weak var mergeView: KMMergeView! // var cancelAction: KMMergeWindowControllerCancelAction? var oldPDFDocument: PDFDocument = PDFDocument() var password: String = "" var oriDucumentUrl: URL? { didSet { oldPDFDocument = PDFDocument(url: oriDucumentUrl!)! oldPDFDocument.unlock(withPassword: self.password) } } var type: KMMergeViewType = .add var pageIndex: Int? var mergeAction: KMMergeWindowControllerMergeAction? // - (id)initWithPDFDocument:(PDFDocument *)document password:(NSString *)password // { // if (self = [super initWithWindowNibName:@"KMPDFEditAppendWindow"]) { // // // self.PDFDocument = document; // self.PDFDocument = [[PDFDocument alloc] init]; // self.editType = KMPDFPageEditAppend; // _lockFilePathArr = [[NSMutableArray alloc] init]; // _files = [[NSMutableArray alloc] init]; // // KMFileAttribute *file = [[KMFileAttribute alloc] init]; // file.myPDFDocument = document; // file.filePath = document.documentURL.path; // file.oriFilePath = self.oriDucumentUrl.path; // if (password && password.length > 0) { // file.password = password; // file.isLocked = YES; // } // [self.files addObject:file]; // } // return self; // } convenience init(document: PDFDocument, password: String) { self.init(windowNibName: "KMMergeWindowController") self.password = password } override func windowDidLoad() { super.windowDidLoad() self.window!.title = NSLocalizedString("Merge PDF Files", comment: ""); // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. self.mergeView.type = self.type mergeView.addFilesAction = { [unowned self] view in self.addFile() } mergeView.clearAction = { [unowned self] view in } mergeView.mergeAction = { [unowned self] view, files, size in mergeFiles(files: files, size: size) } mergeView.cancelAction = { [unowned self] view in cancelAction?(self) } } } extension KMMergeWindowController { func addFile() { var size = 0.0 let files = self.mergeView.files for file in files { size = size + file.fileSize } if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } let openPanel = NSOpenPanel() openPanel.allowedFileTypes = ["pdf"] if KMPurchaseManager.manager.state == .subscription { openPanel.allowsMultipleSelection = true 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: "") } else { openPanel.allowsMultipleSelection = false openPanel.message = NSLocalizedString("Select files to merge, only one file can be selected at a time.", comment: "") } openPanel.beginSheetModal(for: self.window!) { (result) in if result == NSApplication.ModalResponse.OK { var array: [URL] = [] for fileURL in openPanel.urls { array.append(fileURL) } let attribe = try?FileManager.default.attributesOfItem(atPath: openPanel.urls.first!.path) let fileSize = attribe?[FileAttributeKey.size] as? CGFloat ?? 0 size = fileSize + size if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } self.mergeView.addFilePaths(urls: array) } } } func mergeFiles(files: [KMFileAttribute], size: CGSize = CGSizeZero) { var size = 0.0 for file in files { size = size + file.fileSize } if !IAPProductsManager.default().isAvailableAllFunction() && (files.count >= 2 || size > 20 * 1024 * 1024) { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } var filesCount = 1 if self.oriDucumentUrl != nil { filesCount = 0 } if files.count <= filesCount { let alert = NSAlert.init() alert.alertStyle = .critical alert.messageText = NSLocalizedString("To start merging, please select at least 2 files.", comment: "") alert.runModal() return } // _isSuccessfully = NO; // [self.nCancelVC setEnabled:NO]; // self.canMerge = NO; // var rootPDFOutlineArray: [PDFOutline] = [] var allPage = true //只有是全部才支持大纲的合并 for file in files { if file.fetchSelectPages().count == 0 { let alert = NSAlert.init() alert.alertStyle = .critical alert.messageText = "\(file.filePath.lastPathComponent) + \(NSLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: ""))" alert.runModal() return } allPage = file.bAllPage /*防止文件被地址变换后crash*/ guard let tDocument = PDFDocument(url: NSURL(fileURLWithPath: file.filePath) as URL) else { print("文件不存在") let alert = NSAlert.init() alert.alertStyle = .critical alert.messageText = "\(file.filePath.lastPathComponent) + \(NSLocalizedString("Failed to merge!", comment: ""))" alert.runModal() return } var outlineArray: [PDFOutline] = [] // if file.isLocked { tDocument.unlock(withPassword: file.password) // } if tDocument.outlineRoot != nil { rootPDFOutlineArray.append((tDocument.outlineRoot)!) self.fetchAllOfChildren((tDocument.outlineRoot)!, containerArray: &outlineArray) outlineArray.removeObject((tDocument.outlineRoot)!) } else { let rootOutline = PDFOutline.init() tDocument.outlineRoot = rootOutline if tDocument.outlineRoot != nil { rootPDFOutlineArray.append(tDocument.outlineRoot!) } } for number in file.fetchSelectPages() { let page = tDocument.page(at: number - 1) // if pageIndex != nil { // self.oldPDFDocument.insert(page!, at: pageIndex!) // pageIndex = pageIndex! + 1 // } else { self.oldPDFDocument.insert(page!, at: self.oldPDFDocument.pageCount) // } // self.insertIndexSet.addIndex:(self.pdfDocument.pageCount - 1) } } let fileName = (files.first?.filePath.deletingPathExtension.lastPathComponent ?? "") + "_Merged" DispatchQueue.main.async { self.oldPDFDocument.outlineRoot = PDFOutline.init() // if allPage { var insertIndex = 0 for i in 0..<rootPDFOutlineArray.count { let rootOutline = rootPDFOutlineArray[i] for j in 0..<rootOutline.numberOfChildren { self.oldPDFDocument.outlineRoot?.insertChild(rootOutline.child(at: j)!, at: insertIndex) insertIndex = insertIndex + 1 } } self.handleReDraw() if self.oriDucumentUrl != nil { let newPath = self.oldPDFDocument.documentURL!.path var options: [PDFDocumentWriteOption : Any] = [:] var success = false let password = self.password let pdf = self.oldPDFDocument // if pdf.isEncrypted { // options.updateValue(password, forKey: .userPasswordOption) // options.updateValue(password, forKey: .ownerPasswordOption) // success = pdf.write(toFile: newPath, withOptions: options) // } else { // success = pdf.write(toFile: newPath) // } // var success = self.oldPDFDocument.write(toFile: self.oldPDFDocument.documentURL!.path) // if success { let savePanelAccessoryViewController = KMSavePanelAccessoryController.init() let savePanel = NSSavePanel() savePanel.nameFieldStringValue = fileName savePanel.allowedFileTypes = ["pdf"] savePanel.accessoryView = savePanelAccessoryViewController.view // self.savePanelAccessoryViewController = savePanelAccessoryViewController; savePanel.beginSheetModal(for: self.window!) { result in if result == .OK { self.cancelAction?() var outputSavePanel = savePanel.url?.path ?? "" DispatchQueue.main.async { var success = false if pdf.isEncrypted { options.updateValue(password, forKey: .userPasswordOption) options.updateValue(password, forKey: .ownerPasswordOption) success = pdf.write(toFile: outputSavePanel, withOptions: options) } else { success = pdf.write(toFile: outputSavePanel) } if success { if savePanelAccessoryViewController.needOpen { NSDocumentController.shared.openDocument(withContentsOf: savePanel.url!, display: true) { document, open, error in } } else { NSWorkspace.shared.activateFileViewerSelecting([NSURL(fileURLWithPath: outputSavePanel) as URL]) } } else { let alert = NSAlert.init() alert.alertStyle = .critical alert.messageText = "\(String(describing: files.first?.filePath.lastPathComponent)) + \(NSLocalizedString("Failed to merge!", comment: ""))" alert.runModal() } } } } // self.mergeAction?(self, self.oldPDFDocument.documentURL!.path) // } else { // print("合并失败") // } } else { let savePanelAccessoryViewController = KMSavePanelAccessoryController.init() let savePanel = NSSavePanel() savePanel.nameFieldStringValue = fileName savePanel.allowedFileTypes = ["pdf"] savePanel.accessoryView = savePanelAccessoryViewController.view // self.savePanelAccessoryViewController = savePanelAccessoryViewController; savePanel.beginSheetModal(for: self.window!) { result in if result == .OK { self.cancelAction?() var outputSavePanel = savePanel.url?.path DispatchQueue.main.async { var success = self.oldPDFDocument.write(toFile: outputSavePanel!) if !success { success = ((try?self.oldPDFDocument.dataRepresentation()?.write(to: URL(string: outputSavePanel!)!)) != nil) } if success { if savePanelAccessoryViewController.needOpen { NSDocumentController.shared.openDocument(withContentsOf: savePanel.url!, display: true) { document, open, error in } } else { NSWorkspace.shared.activateFileViewerSelecting([NSURL(fileURLWithPath: outputSavePanel!) as URL]) } } else { let alert = NSAlert.init() alert.alertStyle = .critical alert.messageText = "\(String(describing: files.first?.filePath.lastPathComponent)) + \(NSLocalizedString("Failed to merge!", comment: ""))" alert.runModal() } } } } } // } } } func fetchAllOfChildren(_ aOutline: PDFOutline, containerArray aMArray: inout [PDFOutline]) { if !aMArray.contains(aOutline) { aMArray.append(aOutline) } for i in 0..<aOutline.numberOfChildren { if let childOutline = aOutline.child(at: i) { aMArray.append(childOutline) fetchAllOfChildren(childOutline, containerArray: &aMArray) } } } func handleReDraw() { if mergeView.originalSizeButton.state == .on { } else { let size = self.mergeView.newPageSize if size.width < 0 { return } var pagesArray: [PDFPage] = [] let pageCount = self.oldPDFDocument.pageCount for i in 0..<pageCount { pagesArray.append(self.oldPDFDocument.page(at: 0)!) self.oldPDFDocument.removePage(at: 0) } for i in 0..<pageCount { let page: KMMergePDFPage = KMMergePDFPage.init() page.setBounds(CGRectMake(0, 0, size.width, size.height), for: .mediaBox) page.drawingPage = pagesArray[i] self.oldPDFDocument.insert(page, at: i) } if self.oldPDFDocument.outlineRoot != nil { let childCount = self.oldPDFDocument.outlineRoot?.numberOfChildren var outlineArray: [PDFOutline] = [] for i in 0..<childCount! { outlineArray.append((self.oldPDFDocument.outlineRoot?.child(at: i))!) } for outline in outlineArray { outline.removeFromParent() } } } } } class KMMergePDFPage: PDFPage { var drawingPage: PDFPage? override func draw(with box: PDFDisplayBox, to context: CGContext) { super.draw(with: box, to: context) let pageSize = self.bounds(for: .cropBox).size self.drawPage(with: context, page: self.drawingPage!, pageSize: pageSize) } func drawPage(with context: CGContext, page: PDFPage, pageSize: CGSize) { var originalSize = page.bounds(for: .cropBox).size // 如果页面的旋转角度为90或者270,宽高交换 if page.rotation % 180 != 0 { originalSize = CGSize(width: originalSize.height, height: originalSize.width) } let wRatio = pageSize.width / originalSize.width let hRatio = pageSize.height / originalSize.height let ratio = min(wRatio, hRatio) context.saveGState() let xTransform = (pageSize.width - originalSize.width * ratio) / 2 let yTransform = (pageSize.height - originalSize.height * ratio) / 2 context.translateBy(x: xTransform, y: yTransform) context.scaleBy(x: ratio, y: ratio) if #available(macOS 10.12, *) { page.draw(with: .cropBox, to: context) page.transformContext(for: .cropBox) } else { NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false) page.draw(with: .cropBox) NSGraphicsContext.restoreGraphicsState() page.transformContext(for: .cropBox) } context.restoreGState() } }