// // KMConvertBaseWindowController.swift // PDF Reader Pro // // Created by tangchao on 2022/12/5. // import PDFKit import KMComponentLibrary class KMConvertBaseWindowController: KMNBaseWindowController { @IBOutlet weak var titleLabel: NSTextField! @IBOutlet weak var leftBox: NSBox! @IBOutlet weak var rightScrollView: NSScrollView! @IBOutlet weak var batchButton: NSButton! @IBOutlet weak var canelBox: NSBox! @IBOutlet weak var convertBox: NSBox! @IBOutlet weak var rightBoxHeightConst: NSLayoutConstraint! private var cancelButton_: ComponentButton? private var convertButton_: ComponentButton? private var batchButton_: ComponentButton? var batchAction:(() -> Void)? var documentModel: KMDocumentModel? var currentPageIndex: Int = 1 var pageRange = KMPageRange.all var pageRangeString: String = "" var settingView: KMConvertSettingView? var subType: Int = 0 var fileExtension: String { get { return "" } } var progressController: SKProgressController? var pdfPreView: KMNPreView? private var convert: KMPDFConvert? private var ocrLanguage_: COCRLanguage = .english var oriDocumentUrl: URL? deinit { KMPrint("KMConvertBaseWindowController 已释放") } convenience init() { self.init(windowNibName: "KMConvertBaseWindowController") } override func awakeFromNib() { super.awakeFromNib() DispatchQueue.main.async { self.window?.makeFirstResponder(nil) } } override func windowDidLoad() { super.windowDidLoad() let preView = KMNPreView.createFromNib() leftBox.contentView = preView pdfPreView = preView pdfPreView?.contentInset = .init(top: 6, left: 6, bottom: 16, right: 6) if (documentModel != nil) { pdfPreView?.setFileUrl(self.documentModel!.documentURL, password: self.documentModel?.password) pdfPreView?.pdfView?.go(toPageIndex: self.documentModel?.currentIndex ?? 0, animated: false) } if let pageCount = pdfPreView?.pageCount { settingView?.pageCount = Int(pageCount) } pdfPreView?.reloadUI() updateViewColor() } override func updateUILanguage() { super.updateUILanguage() KMMainThreadExecute { self.batchButton_?.properties.buttonText = KMLocalizedString("Batch") self.batchButton_?.reloadData() } } override func initContentView() { super.initContentView() canelBox.fillColor = .clear cancelButton_?.keyEquivalent = KMKeyEquivalent.esc.string() convertBox.fillColor = .clear convertButton_ = ComponentButton() convertBox.contentView = convertButton_ convertButton_?.properties = ComponentButtonProperty(type: .primary, size: .s, state: .normal, isDisable: false, buttonText: KMLocalizedString("Convert")) convertButton_?.setTarget(self, action: #selector(convertButtonAction)) convertButton_?.keyEquivalent = KMKeyEquivalent.enter rightScrollView.hasVerticalScroller = false rightScrollView.hasHorizontalScroller = false titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-medium") leftBox.borderWidth = 1 leftBox.cornerRadius = ComponentLibrary.shared.getComponentValueFromKey("radius/m") as? CGFloat ?? 0 let view = initSettingView() settingView = view rightScrollView.documentView = view rightScrollView.verticalScrollElasticity = .none rightScrollView.horizontalScrollElasticity = .none view?.km_add_inset_constraint() view?.currentLanguage = self.getCurrentLanguage() view?.lanugageDidSelected = { [weak self] value, _ in guard let index = value as? Int else { return } self?.saveLanugageSelectedIndex(index: index) } view?.pageRangeDidChange = { [weak self] pageRange in self?.pageRange = pageRange if (pageRange != .custom) { self?._updatePreView(pageRange: pageRange) } } view?.pageRangeDidInputFinishCallback = { [unowned self] string in self.pageRangeString = string let array = self.findSelectPage(pageRangeString: string) if array.count == 0 { let alert = NSAlert() alert.alertStyle = .warning alert.messageText = KMLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: "") alert.runModal() self.settingView?.pageRangeItemView?.pageSizeVC?.stringValue = "" } else { self._updatePreView(pageRange: .custom, pageString: string) } } settingView?.tipView?.isHidden = true cancelButton_ = ComponentButton() cancelButton_?.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, state: .normal, buttonText: KMLocalizedString("Cancel")) cancelButton_?.setTarget(self, action: #selector(cancelButtonAction)) canelBox.contentView = cancelButton_ batchButton.title = "" batchButton_ = ComponentButton() batchButton_?.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, state: .normal, buttonText: KMLocalizedString("Batch")) batchButton_?.setTarget(self, action: #selector(batchButtonAction)) batchButton.addSubview(self.batchButton_!) batchButton_?.frame = batchButton.bounds batchButton_?.autoresizingMask = [.width, .height] } func initSettingView() -> KMConvertSettingView? { let settingView = KMConvertSettingView.createFromNib() return settingView } public func initConvert() -> KMPDFConvert { return KMPDFConvert() } func setupContentHeight(height: CGFloat, animated: Bool = false) { // 370 if (animated) { rightBoxHeightConst.animator().constant = height } else { rightBoxHeightConst.constant = height } } func updateViewColor() { KMMainThreadExecute { self.titleLabel.textColor = KMNColorTools.colorText_1() self.leftBox.fillColor = KMNColorTools.colorFill_4() self.leftBox.borderColor = KMNColorTools.colorBorder_4() self.pdfPreView?.pdfView?.backgroundColor = KMNColorTools.colorFill_4() self.settingView?.updateViewColor() } } @objc func batchButtonAction() { self._clearData() own_closeEndSheet() self.batchAction?() } @objc func cancelButtonAction() { self._clearData() own_closeEndSheet() } @objc func convertButtonAction() { window?.makeFirstResponder(nil) if (pageRange == .custom) { let array = findSelectPage(pageRangeString: pageRangeString) if (array.count <= 0) { window?.makeFirstResponder(settingView?.pageRangeItemView?.pageSizeVC?.textField) return } } var pages: [Int] = [] let filePath = getConvertFileSavePath() if (FileManager.default.fileExists(atPath: filePath)) { try?FileManager.default.removeItem(atPath: filePath) } let document = pdfPreView?.pdfPreView.pdfView.document if ((document?.writeDecrypt(to: URL(fileURLWithPath: filePath)))!) { for i in 0 ..< document!.pageCount { pages.append(Int(i)+1) } } if (pages.count <= 0) { let alert = NSAlert() alert.alertStyle = .warning alert.messageText = KMLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: "") alert.runModal() return } _convertButtonAction() } func convertModelAppendParams(convert: KMPDFConvert) -> () { if let isAllowOCR = settingView?.ocrEnabled { convert.isAllowOCR = isAllowOCR if let idx = settingView?.ocrItemView?.languageIndex { convert.ocrLanguage = _getOCRLanguage(idx: idx) } } else { convert.isAllowOCR = false } } func getConvertFileSavePath() -> String { var path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last path?.append("/") path?.append(Bundle.main.bundleIdentifier!) if (FileManager.default.fileExists(atPath: path!) == false) { try?FileManager.default.createDirectory(atPath: path!, withIntermediateDirectories: false) } path?.append("/") path?.append("convert.pdf") return path! } func isValidPagesString(pagesString: String)-> Bool { var valid = false for ch in pagesString { if ch != "0" && ch != "1" && ch != "2" && ch != "3" && ch != "4" && ch != "5" && ch != "6" && ch != "7" && ch != "8" && ch != "9" && ch != "," && ch != "-" { valid = false break } else { valid = true } } return valid } func findSelectPage(pageRangeString: String) -> ([Int]) { if !isValidPagesString(pagesString: pageRangeString) { return [] } var result: [Int] = [] let array = pageRangeString.components(separatedBy: ",") for string in array { if string.isEmpty { return [] } else { let pages = string .components(separatedBy: "-") if pages.count > 2 { return [] } else if pages.count == 1 { let page = pages[0] if page.isEmpty || Int(page)! > documentModel!.pageCount || Int(page)! == 0 { return [] } else { var hasSame: Bool = false for i in result { if i == Int(page)! { hasSame = true return [] } } if !hasSame { result.append(Int(page)!) } } } else if pages.count == 2 { let page1 = pages[0] let page2 = pages[1] if page1.isEmpty || page2.isEmpty || Int(page1)! >= Int(page2)! || Int(page2)! > documentModel!.pageCount || Int(page1)! == 0 { return [] } else { var hasSame: Bool = false for i in Int(page1)! ... Int(page2)! { for j in result { if j == i { hasSame = true return [] } } } if !hasSame { for i in Int(page1)! ... Int(page2)! { result.append(i) } } } } } } return result } override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) window?.makeFirstResponder(nil) } // MARK: - Private Methods private func _updatePreView(pageRange: KMPageRange, pageString: String = "") { let pages = getPages(pageRange, pageString: pageString) if (pages == nil || pages!.isEmpty) { let alert = NSAlert() alert.alertStyle = .warning alert.messageText = KMLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: "") alert.runModal() return } var indexs = IndexSet() let document = pdfPreView?.pdfPreView.pdfView.document for i in 0 ..< document!.pageCount { indexs.insert(IndexSet.Element(i)) } document?.removePage(at: indexs) for page in pages! { document?.insertPageObject(page, at: document?.pageCount ?? 0) } DispatchQueue.main.async { [self] in let prePDFView = pdfPreView?.pdfPreView.pdfView prePDFView?.layoutDocumentView() prePDFView?.setNeedsDisplayForVisiblePages() prePDFView?.goToFirstPage(nil) currentPageIndex = 1 pdfPreView?.reloadUI() } } private func getPageIndexs(_ pageRange: KMPageRange, pageString: String = "") -> IndexSet? { guard let pageCount = documentModel?.pageCount, pageCount > 0 else { return nil } var indexs = IndexSet() if (pageRange == .all) { for i in 0 ..< pageCount { indexs.insert(i) } } else if (pageRange == .odd) { for i in 0 ..< pageCount { if ((i % 2) == 1) { continue } indexs.insert(i) } } else if (pageRange == .even) { for i in 0 ..< pageCount { if ((i % 2) == 0) { continue } indexs.insert(i) } } else if (pageRange == .current) { if (currentPageIndex >= 1 && currentPageIndex <= pageCount) { indexs.insert(currentPageIndex-1) } } else if (pageRange == .custom) { let array = findSelectPage(pageRangeString: pageString) if array.count == 0 { } else { for i in 0 ..< array.count { let index = array[i] if (index > pageCount || index < 1) { continue } indexs.insert(index-1) } } } return indexs } private func getPages(_ pageRange: KMPageRange, pageString: String = "") -> [CPDFPage]? { guard let pageCount = documentModel?.pageCount, pageCount > 0 else { return nil } var pages: [CPDFPage] = [] if (pageRange == .all) { for i in 0 ..< pageCount { let page = documentModel!.document?.page(at: UInt(i)) if (page == nil) { continue } pages.append(page!) } } else if (pageRange == .odd) { for i in 0 ..< pageCount { if ((i % 2) == 1) { continue } let page = documentModel!.document?.page(at: UInt(i)) if (page == nil) { continue } pages.append(page!) } } else if (pageRange == .even) { for i in 0 ..< pageCount { if ((i % 2) == 0) { continue } let page = documentModel!.document?.page(at: UInt(i)) if (page == nil) { continue } pages.append(page!) } } else if (pageRange == .current) { let page = pdfPreView?.pdfPreView.pdfView.currentPage() if (page != nil) { pages.append(page!) } } else if (pageRange == .custom) { let array = findSelectPage(pageRangeString: pageString) if array.count == 0 { } else { for i in 0 ..< array.count { let index = array[i] if (index > pageCount || index < 1) { continue } let page = documentModel?.document?.page(at: UInt(index-1)) if (page == nil) { continue } pages.append(page!) } } } return pages } private func _convertButtonAction() { var pages: [Int] = [] let filePath = getConvertFileSavePath() if (FileManager.default.fileExists(atPath: filePath)) { try?FileManager.default.removeItem(atPath: filePath) } let document = pdfPreView?.pdfPreView.pdfView.document if ((document?.writeDecrypt(to: URL(fileURLWithPath: filePath)))!) { for i in 0 ..< document!.pageCount { pages.append(Int(i)+1) } } if (pages.count <= 0) { let alert = NSAlert() alert.alertStyle = .warning alert.messageText = KMLocalizedString("Invalid page range or the page number is out of range. Please try again.", comment: "") alert.runModal() return } DispatchQueue.main.async { NSPanel.savePanel(self.window!) { panel in var url: URL = (self.pdfPreView?.pdfPreView.pdfView.document.documentURL)! if (self.oriDocumentUrl != nil) { url = self.oriDocumentUrl! } let name = url.deletingPathExtension().lastPathComponent panel.nameFieldStringValue = name panel.allowedFileTypes = [self.fileExtension] // 检查文件是否已存在 var finalURL = panel.directoryURL?.appendingPathComponent(panel.nameFieldStringValue).appendingPathExtension(self.fileExtension) var count = 1 while FileManager.default.fileExists(atPath: finalURL?.path ?? "") { // 如果文件已存在,给文件名添加后缀数字 let newName = "\(name)(\(count))" panel.nameFieldStringValue = newName finalURL = panel.directoryURL?.appendingPathComponent(newName).appendingPathExtension(self.fileExtension) count += 1 } } completion: { response, url in if (response == .cancel) { return } let outputFolderPath = url!.deletingLastPathComponent().path let convert = self.initConvert() self.convert = convert convert.password = self.documentModel!.password convert.outputFolderPath = outputFolderPath convert.filePath = filePath convert.pages = pages var fileName = url!.deletingPathExtension().lastPathComponent if (self.fileExtension.isEmpty) { fileName = url!.lastPathComponent } convert.outputFileName = fileName self.convertModelAppendParams(convert: convert) DispatchQueue.main.async { self.showProgressWindow() self.progressController?.maxValue = Double(convert.pages.count) } DispatchQueue.global().async { KMPDFConvertManager.defaultManager.convert(convert: convert, progress: { index in DispatchQueue.main.async { self.progressController?.increment(by: 1.0) } }) { [unowned self] finished, error in self.hiddenProgressWindow() // 清除临时文件 if (FileManager.default.fileExists(atPath: filePath)) { try?FileManager.default.removeItem(atPath: filePath) } if finished { self._clearData() cancelButtonAction() if FileManager.default.fileExists(atPath: convert.outputFilePath) { NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: convert.outputFilePath)]) } } else { var errorString = "" let myError: NSError? = error as? NSError if myError?.code == 1 { errorString = KMLocalizedString("Password required or incorrect password. Please re-enter your password and try again", comment: "") } else if myError?.code == 2 { errorString = KMLocalizedString("The license doesn't allow the permission", comment: "") } else if myError?.code == 3 { errorString = KMLocalizedString("Malloc failure", comment: "") } else if myError?.code == 4 { errorString = KMLocalizedString("Unknown error in processing conversion. Please try again later", comment: "") } else if myError?.code == 5 { errorString = KMLocalizedString("Unknown error in processing PDF. Please try again later", comment: "") } else if myError?.code == 6 { errorString = KMLocalizedString("File not found or could not be opened. Check if your file exists or choose another file to convert", comment: "") } else if myError?.code == 7 { errorString = KMLocalizedString("File not in PDF format or corruptead. Change a PDF file and try again", comment: "") } else if myError?.code == 8 { errorString = KMLocalizedString("Unsupported security scheme", comment: "") } else if myError?.code == 9 { errorString = KMLocalizedString("Page not found or content error", comment: "") } else { errorString = KMLocalizedString("Table not found", comment: "") } let alert = NSAlert() alert.alertStyle = .critical alert.messageText = KMLocalizedString("Conversion Failed", comment: "") alert.informativeText = errorString alert.addButton(withTitle: KMLocalizedString("OK", comment: "")) alert.runModal() } } } } } } private func _getOCRLanguage(idx: Int) -> COCRLanguage { switch (idx) { case 0: //chinese self.ocrLanguage_ = .chinese break case 1: //chinese zht self.ocrLanguage_ = .chineseTraditional break case 2: //English self.ocrLanguage_ = .english break case 3: //Japanese self.ocrLanguage_ = .japanese break case 4: //Kroean self.ocrLanguage_ = .korean break case 5: //Serbian self.ocrLanguage_ = .serbian break case 6: //Occitan self.ocrLanguage_ = .occitan break case 7: //Danish self.ocrLanguage_ = .danish break case 8: //German self.ocrLanguage_ = .german break case 9: //French self.ocrLanguage_ = .french break case 10: //Italian self.ocrLanguage_ = .italian break case 11: //Spanish self.ocrLanguage_ = .spanish break case 12: //Portuguese self.ocrLanguage_ = .portuguese break case 13: //Maori self.ocrLanguage_ = .maori break case 14: //Malay self.ocrLanguage_ = .malay break case 15: //Malay self.ocrLanguage_ = .maltese break case 16: //Dutch self.ocrLanguage_ = .dutch break case 17: //Norwegian self.ocrLanguage_ = .norwegian break case 18: //Polish self.ocrLanguage_ = .polish break case 19: //Romanian self.ocrLanguage_ = .romanian break case 20: //Slovak self.ocrLanguage_ = .slovak break case 21: //Slovenian self.ocrLanguage_ = .slovenian break case 22: //Albanian self.ocrLanguage_ = .albanian break case 23: //Swedish self.ocrLanguage_ = .swedish break case 24: //Swahili self.ocrLanguage_ = .swahili break case 25: //Tagalog self.ocrLanguage_ = .tagalog break case 26: //Turish self.ocrLanguage_ = .turish break case 27: //Uzbek self.ocrLanguage_ = .uzbek break case 28: //Vietnamese self.ocrLanguage_ = .vietnamese break case 29: //Afrikaans self.ocrLanguage_ = .afrikaans break case 30: //Azerbaijani self.ocrLanguage_ = .azerbaijani break case 31: //Bosnian self.ocrLanguage_ = .bosnian break case 32: //Czech self.ocrLanguage_ = .czech break case 33: //Welsh self.ocrLanguage_ = .welsh break case 34: //Estonian self.ocrLanguage_ = .estonian break case 35: //Irish self.ocrLanguage_ = .irish break case 36: //Croatian self.ocrLanguage_ = .croatian break case 37: //Hungarian self.ocrLanguage_ = .hungarian break case 38: //Indonesian self.ocrLanguage_ = .indonesian break case 39: //Icelandic self.ocrLanguage_ = .icelandic break case 40: //Kurdish self.ocrLanguage_ = .kurdish break case 41: //Lithuanian self.ocrLanguage_ = .lithuanian break case 42: //Latvian self.ocrLanguage_ = .latvian break case 43: //Marathi self.ocrLanguage_ = .marathi break case 44: //Nepali self.ocrLanguage_ = .nepali break case 45: //Latvia self.ocrLanguage_ = .latvian break case 46: //Bihari self.ocrLanguage_ = .bihari break case 47: //Maithili self.ocrLanguage_ = .maithili break case 48: //Angika self.ocrLanguage_ = .angika break case 49: //Bhojpuri self.ocrLanguage_ = .bhojpuri break case 50: //Magahi self.ocrLanguage_ = .magahi break case 51: //Nagpur self.ocrLanguage_ = .nagpur break case 52: //Newari self.ocrLanguage_ = .newari break case 53: //GoanKonkani self.ocrLanguage_ = .goanKonkani break case 54: //SaudiArabia self.ocrLanguage_ = .saudiArabia break default: self.ocrLanguage_ = .english break } return self.ocrLanguage_ } // MARK: - Notifation Methods // MARK: Progress func showProgressWindow() { self.hiddenProgressWindow() let progress = SKProgressController() progress.window?.backgroundColor = NSColor.km_init(hex: "#36383B") progress.window?.contentView?.wantsLayer = true progress.window?.contentView?.layer?.backgroundColor = NSColor.km_init(hex: "#36383B").cgColor progress.progressField.textColor = NSColor.white progress.message = KMLocalizedString("Converting...", comment: "") progress.closeBlock = { [weak self] in if let convert = self?.convert { KMPDFConvertManager.defaultManager.cancel(convert: convert) } } self.progressController = progress self.window?.beginSheet(progress.window!) } func hiddenProgressWindow() { if (self.progressController != nil) { self.window?.endSheet((self.progressController?.window)!) self.progressController = nil } } func getCurrentLanguage() -> String { let array: [String] = UserDefaults.standard.object(forKey: "AppleLanguages") as! [String] return array.first! } /// 存储用户的选择的语言 func saveLanugageSelectedIndex(index: Int) { UserDefaults.standard.setValue("\(index)", forKey: kKMConvertLanugageSelectedIndex) UserDefaults.standard.synchronize() } private func _clearData() { if let _ = self.oriDocumentUrl { if let data = self.pdfPreView?.pdfView?.document.documentURL.path, FileManager.default.fileExists(atPath: data) { try?FileManager.default.removeItem(atPath: data) } } } }