// // KMEditPDfHanddler.swift // PDF Reader Pro // // Created by tangchao on 2024/6/16. // import Cocoa // EditPDF处理对象 class KMEditPDfHanddler: NSObject { weak var viewC: KMMainViewController? weak var listView: CPDFListView? { get { return self.viewC?.listView } } var annotationType: CAnnotationType { get { return self.listView?.annotationType ?? .unkown } } weak var rightViewC: KMRightSideViewController? { get { return self.viewC?.rightSideViewController } } var subViewType: RightSubViewType { get { return self.rightViewC?.subViewType ?? .None } } // var toolMode: CToolMode { // get { // return // } // } var isEditImage: Bool { get { return self.listView?.isEditImage ?? false } } var isEditing: Bool { get { return self.listView?.isEditing() ?? false } } var editingConfig: CPDFEditingConfig? { get { return self.listView?.editingConfig() } } var editingAreas: [CPDFEditArea] { get { return self.listView?.editingAreas() as? [CPDFEditArea] ?? [] } } func enterEditPDF() { self.listView?.updateActiveAnnotations([]) self.listView?.setNeedsDisplayForVisiblePages() self.listView?.commitEditFormText() self.listView?.annotationType = .editTextImage // 设置边框颜色 self.editingConfig?.editingBorderColor = .clear // 设置边框宽度 // self.editingConfig?.editingBorderWidth = 10 // 内容与边框的间距 // self.editingConfig?.editAreaMargin = .init(floatLiteral: 5) // 设置选中块边框颜色 // self.editingConfig?.editingSelectionBorderColor = .red // 显示hover边框 self.editingConfig?.isShowMouseAreaHover = true // hover // 边框宽度 // self.editingConfig?.mouseHoverBorderWidth = 1 // 边框颜色 self.editingConfig?.mouseHoverBorderColor = .purple // 边框虚线设置 self.editingConfig?.mouseHoverBorderDashPattern = [3,3,3] // 块填充颜色(拖拽中) // self.editingConfig?.editAreaMoveFillColor = .cyan // 是否显示位置辅助线 self.editingConfig?.isShowEditingAreaHover = true // 辅助线颜色 // self.editingConfig?.editingHoverBorderColor = .brown // 支持多选 self.editingConfig?.isSupportMultipleSelectEditingArea = true // 图片是否显示8个操作点 self.editingConfig?.isDrawRectWithDot = true } func fontAddAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } guard let area = editingAreas.last else { return } if area.isTextArea() == false { return } if let fontSize = self.listView?.editingSelectionFontSize(with: area as! CPDFEditTextArea) { self.listView?.setEditingSelectionFontSize(fontSize+1, with: area as! CPDFEditTextArea, isAutoSize: false) } } func fontReduceAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } guard let area = editingAreas.last else { return } if area.isTextArea() == false { return } if let fontSize = self.listView?.editingSelectionFontSize(with: area as! CPDFEditTextArea) { self.listView?.setEditingSelectionFontSize(fontSize-1, with: area as! CPDFEditTextArea, isAutoSize: false) } } func fontBoldAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } guard let area = editingAreas.last else { return } if area.isTextArea() == false { return } self.listView?.setCurrentSelectionIsBold(true, with: area as! CPDFEditTextArea) } func fontItalicAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } guard let area = editingAreas.last else { return } if area.isTextArea() == false { return } self.listView?.setCurrentSelectionIsItalic(true, with: area as! CPDFEditTextArea) } func textAlignmentAction() { KMPrint("textAlignmentAction") } func leftRotateAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } guard let area = editingAreas.last as? CPDFEditImageArea else { return } // if let ang = self.listView?.getRotateWith(area) { // self.listView?.rotate(with: area, rotate: ang-90) // } // FMTrackEventManager.defaultManager.trackEvent(event: "SubTbr_PageEdit", withProperties: ["SubTbr_Btn": "Btn_SubTbr_PageEdit_Rotate"]) self.listView?.rotate(with: area, rotate: -90) // if self.listView.editingAreas()!.count == 1 && (self.listView.editingAreas()!.first is CPDFEditImageArea) { // self.listView.selectImageAreas = self.listView.editingAreas()!.first as? CPDFEditImageArea // } // self.editImageView.image = self.listView.selectImageAreas.thumbnailImage // self.delegate?.editImagePropertyViewControllerDidChanged(controller: self, type: .rotate) } func rightRotateAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } guard let area = editingAreas.last as? CPDFEditImageArea else { return } self.listView?.rotate(with: area, rotate: 90) } func reverseXAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } for area in editingAreas { if let data = area as? CPDFEditImageArea { self.listView?.horizontalMirror(with: data) } } } func reverseYAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } for area in editingAreas { if let data = area as? CPDFEditImageArea { self.listView?.verticalMirror(with: data) } } } func cropAction() { self.listView?.isEditImage = true let editingAreas = self.editingAreas if editingAreas.isEmpty { return } for area in editingAreas { if let data = area as? CPDFEditImageArea { self.listView?.enterCrop(with: data) } } } func replaceAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } let panel = NSOpenPanel() panel.allowsMultipleSelection = false panel.allowedFileTypes = ["png","jpg"] panel.beginSheetModal(for: NSApp.mainWindow!) { response in if response == .OK { let openPath = panel.url?.path for area in editingAreas { if let data = area as? CPDFEditImageArea { // self.listView?.replace(data, imagePath: openPath!) self.listView?.replace(data, imagePath: openPath!, rect: data.bounds) } } } } } func exportAction() { let editingAreas = self.editingAreas if editingAreas.isEmpty { return } var imagesAreas: [CPDFEditImageArea] = [] for area in editingAreas { if let data = area as? CPDFEditImageArea { imagesAreas.append(data) } } if imagesAreas.count == 1 { let panel = NSSavePanel() panel.nameFieldStringValue = "\(NSLocalizedString("Untitled", comment: "")).jpg" panel.isExtensionHidden = true let response = panel.runModal() if response == .OK { let url = panel.url if FileManager.default.fileExists(atPath: url!.path) { try?FileManager.default.removeItem(atPath: url!.path) } let result = self.listView?.extractImage(with: imagesAreas.first, toImagePath: url!.path) ?? false if result { NSWorkspace.shared.activateFileViewerSelecting([url!]) } } } else if imagesAreas.count > 1 { let panel = NSOpenPanel() panel.canChooseFiles = false panel.canChooseDirectories = true panel.canCreateDirectories = true panel.allowsMultipleSelection = false panel.beginSheetModal(for: NSApp.mainWindow!) { response in if response == .OK { let outputURL = panel.url let s = self.listView?.document?.documentURL.lastPathComponent let folderPath = (self.listView?.document?.documentURL.deletingPathExtension().lastPathComponent ?? "") + "_extract" var filePath = outputURL?.path.stringByAppendingPathComponent(folderPath) var i = 1 let testFilePath = filePath while FileManager.default.fileExists(atPath: filePath!) { filePath = testFilePath! + "\(i)" i = i + 1 } try? FileManager.default.createDirectory(atPath: filePath!, withIntermediateDirectories: false, attributes: nil) var saveURLs : [URL] = [] for j in 0 ... imagesAreas.count-1 { let documentFileName = self.listView?.document?.documentURL.deletingPathExtension().lastPathComponent ?? "" var outPath = filePath! outPath = outPath.stringByAppendingPathComponent(documentFileName) outPath = outPath + "page \(j+1)" outPath = outPath.stringByAppendingPathExtension("jpg") let result = self.listView?.extractImage(with: imagesAreas[j], toImagePath: outPath) ?? false if result { saveURLs.append(URL(fileURLWithPath: outPath)) } } NSWorkspace.shared.activateFileViewerSelecting(saveURLs) } } } } func alignmentAction() { KMPrint("alignmentAction") } func showPopWindow(positionRect: NSRect) { let win = KMEditPDFPopToolBarWindow.shared let areas = self.editingAreas win.isMultiple = areas.count > 1 var hasText = false var hasImage = false for area in areas { if area is CPDFEditTextArea { hasText = true } if area is CPDFEditImageArea { hasImage = true } } var style: KMEditPDFToolbarStyle = [] if hasText { style.insert(.text) } if hasImage { style.insert(.image) } win.style = style win.show(relativeTo: positionRect, of: self.viewC!.listView, preferredEdge: .maxY) self.viewC?.view.window?.addChildWindow(win, ordered: .above) win.itemClick = { [weak self] itemKey, obj in if itemKey == .fontAdd { self?.fontAddAction() } else if itemKey == .fontReduce { self?.fontReduceAction() } else if itemKey == .fontBold { self?.fontBoldAction() } else if itemKey == .fontItalic { self?.fontItalicAction() } else if itemKey == .textAlignment { self?.textAlignmentAction() } // 图片 else if itemKey == .leftRotate { self?.leftRotateAction() } else if itemKey == .rightRotate { self?.rightRotateAction() } else if itemKey == .reverseX { self?.reverseXAction() } else if itemKey == .reverseY { self?.reverseYAction() } else if itemKey == .crop { self?.cropAction() } else if itemKey == .replace { self?.replaceAction() } else if itemKey == .export { self?.exportAction() } // 对齐 else if itemKey == .alignmentLeft { self?.alignmentAction() } else if itemKey == .alignmentCenterX { self?.alignmentAction() } else if itemKey == .alignmentRight { self?.alignmentAction() } else if itemKey == .alignmentjustifiedX { self?.alignmentAction() } else if itemKey == .alignmentTop { self?.alignmentAction() } else if itemKey == .alignmentCenterY { self?.alignmentAction() } else if itemKey == .alignmentBottom { self?.alignmentAction() } else if itemKey == .alignmentjustifiedY { self?.alignmentAction() } } // 显示新手引导 if let toolbarView = (win.contentViewController as? KMEditPDFPopToolBarController)?.toolbarView { if let view = toolbarView.subviews.last { self.showGuideView(view) } } } func showGuideView(_ view: NSView) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { if KMGuideInfoWindowController.availableShow(.editPDFPopWindow) { let guideWC = KMGuideInfoWindowController.currentWC() // guard let guideWC = guideInfoWindowController else { return } guideWC.type = .editPDFPopWindow var viewFrame = view.superview?.convert(view.frame, to: view.window?.contentView) ?? .zero viewFrame.origin.x += (view.window?.frame.origin.x ?? 0) viewFrame.origin.x -= (self.viewC?.view.window?.frame.origin.x ?? 0) viewFrame.origin.y += (view.window?.frame.origin.y ?? 0) viewFrame.origin.x -= (self.viewC?.view.window?.frame.origin.y ?? 0) viewFrame.origin.x -= KMEditPDFPopGuideView.kWidth viewFrame.origin.y -= KMEditPDFPopGuideView.kHeight viewFrame.origin.y += 26 guideWC.digitalBoxRect = viewFrame var beh = view.window?.collectionBehavior ?? [] beh.insert(.canJoinAllSpaces) guideWC.window?.collectionBehavior = beh let view_ = self.viewC?.view var rect = view_?.window?.frame ?? .zero rect.size.height -= 20 guideWC.window?.setFrame(rect, display: false) guideWC.window?.minSize = rect.size guideWC.window?.maxSize = rect.size view_?.window?.addChildWindow(guideWC.window!, ordered: .above) guideWC.show() guideWC.settingCallback = { KMPreferenceController.shared.showWindow(nil) } } } } } extension KMEditPDfHanddler: CPDFViewDelegate { // 编辑区块已经改变 func pdfViewEditingAreaDidChanged(_ pdfView: CPDFView!) { let isEdited = self.listView?.isEdited() ?? false if isEdited { // 记录编辑状态 self.viewC?.recordIsPDFDocumentEdited(type: .editText) } if annotationType != .addText { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "kPDFViewEditingAreaDidChanged"), object: self.listView?.document) } let areas = self.listView?.editingAreas() as? [CPDFEditArea] ?? [] if areas.isEmpty { let toolMode = self.listView?.toolMode ?? .none let annotationType = self.annotationType if toolMode == .editPDFToolMode { if annotationType == .addImage || annotationType == .addText { if self.isEditImage { self.viewC?.menuItemEditingClick_CropImage(sender: NSMenuItem()) } else { // if self.listView.annotationType == .addImage { // self.closeRightPane() // } if annotationType == .addImage { if self.rightViewC?.eidtPDFImageProperty != nil { self.rightViewC?.eidtPDFImageProperty.reloadData() } } // self.openRightPane() } } else { self.viewC?.closeRightPane() } } else { self.rightViewC?.isHidden = true self.viewC?.closeRightPane() if self.subViewType == .EditPDFAddText && annotationType == .addText { self.rightViewC?.eidtPDFTextProperty.initData() } } return } self.viewC?.model.isPDFTextImageEdited = true let subViewType = self.rightViewC?.subViewType ?? .None if self.annotationType == .addImage { var isImageArea = false for i in 0 ..< areas.count { if areas[i] is CPDFEditImageArea { isImageArea = true } } if isImageArea { self.rightViewC?.isHidden = false if self.subViewType == .EditPDFAddImage { self.rightViewC?.subViewType = .EditPDFAddImage self.rightViewC?.eidtPDFImageProperty.reloadData() } self.viewC?.openRightPane() } else { self.rightViewC?.isHidden = true self.viewC?.closeRightPane() } } else if self.subViewType == .EditPDFAddText && annotationType == .addText { self.rightViewC?.isHidden = false let count = self.listView?.editingSelectionString()?.count ?? 0 if count != 0 { self.rightViewC?.eidtPDFTextProperty.reloadData() } else { self.rightViewC?.eidtPDFTextProperty.refreshSelectAreaProperty(needDefaultData: true) } self.viewC?.openRightPane() } else { var textsAreas : [CPDFEditTextArea] = [] var imagesAreas : [CPDFEditImageArea] = [] let count = self.listView?.editingAreas()?.count ?? 0 if count < 1 { return } for i in 0 ..< areas.count { if areas[i] is CPDFEditTextArea { textsAreas.append(areas[i] as! CPDFEditTextArea) } if areas[i] is CPDFEditImageArea { imagesAreas.append(areas[i] as! CPDFEditImageArea) } } if textsAreas.count > 0 && textsAreas.count == areas.count { self.rightViewC?.isHidden = false self.rightViewC?.subViewType = .EditPDFAddText self.rightViewC?.eidtPDFTextProperty?.reloadData() self.viewC?.openRightPane() } else if imagesAreas.count > 0 { self.rightViewC?.isHidden = false self.rightViewC?.subViewType = .EditPDFAddImage self.rightViewC?.eidtPDFImageProperty?.reloadData() self.viewC?.openRightPane() } } var flag: CPDFEditArea? for area in areas { if flag == nil { flag = area continue } if let data = flag, data.bounds.maxY < area.bounds.maxY { flag = area } } if let data = flag { self.showPopWindow(positionRect: data.bounds) } } func pdfViewEditingCropBoundsDidChanged(_ pdfView: CPDFView!, editing editArea: CPDFEditArea!) { if editArea != nil && (editArea is CPDFEditImageArea){ self.listView?.cropAreas = editArea as? CPDFEditImageArea } } func pdfViewEditingAddImageArea(_ pdfView: CPDFView!, add page: CPDFPage!, add rect: CGRect) { if self.isEditImage { self.viewC?.menuItemEditingClick_CropImage(sender: NSMenuItem()) } else { let panel = NSOpenPanel() panel.allowsMultipleSelection = false panel.allowedFileTypes = ["png","jpg"] panel.beginSheetModal(for: NSApp.mainWindow!) { response in if response == .OK { var filePath = panel.url?.path var image = NSImage.init(contentsOf: panel.url!) //图片自适应范围 if image != nil { var imageRect = rect let imageSize = image!.size var previewSize = rect.size var isChangeSize = false if previewSize.width == 0 && previewSize.height == 0 { previewSize = CGSize(width: 500, height: 500) isChangeSize = true } let scale = min(previewSize.width / imageSize.width, previewSize.height / imageSize.height) let newSize = CGSize(width: imageSize.width * scale, height: imageSize.height * scale) if isChangeSize { imageRect.origin.x = imageRect.origin.x - newSize.width / 2 imageRect.origin.y = imageRect.origin.y - newSize.height / 2 } else { imageRect.origin.x = imageRect.origin.x + imageRect.width / 2 - newSize.width / 2 imageRect.origin.y = imageRect.origin.y + imageRect.height / 2 - newSize.height / 2 } imageRect.size = newSize let limitWidth = 1920.0 if imageSize.width > limitWidth || imageSize.height > limitWidth { filePath = KMImageOptimization.needCompressImageLosslessly(image: image!, targetSize: CGSize(width: limitWidth, height: limitWidth), maxSizeInBytes: 1024 * 1024 * 5, targetCompression: 1.0) } //自适应page let pageRect = self.listView?.currentPage().bounds ?? .zero if imageRect.width > pageRect.width || imageRect.height > pageRect.height { let pageScale = min(pageRect.width / imageSize.width, pageRect.height / imageSize.height) imageRect = CGRect(x: imageRect.origin.x, y: imageRect.origin.y, width: imageRect.width * pageScale, height: imageRect.height * pageScale) } if imageRect.origin.x < 0 { imageRect.origin.x = 5 } if imageRect.origin.y < 0 { imageRect.origin.y = 5 } if imageRect.origin.x + imageRect.width > pageRect.width || imageRect.origin.y + imageRect.height > pageRect.height { let offsetX = imageRect.origin.x + imageRect.width - pageRect.width let offsetY = imageRect.origin.y + imageRect.height - pageRect.height imageRect.origin.x = imageRect.origin.x - offsetX - 5 imageRect.origin.y = imageRect.origin.y - offsetY - 5 } DispatchQueue.main.async { self.listView?.createImagePath(filePath, rect: imageRect, page: pdfView.currentPage()) self.viewC?.model.isPDFTextImageEdited = true self.viewC?.recordIsPDFDocumentEdited(type: .editImage) self.showPopWindow(positionRect: imageRect) } } } } } } func pdfViewEditingAddTextArea(_ pdfView: CPDFView!, add page: CPDFPage!, add rect: CGRect) { let window = KMEditPDFPopToolBarWindow.shared if (window.isVisible) { window.orderOut(nil) let areas = self.listView?.editingAreas() as? [CPDFEditArea] ?? [] if let area = areas.last { if let data = area as? CPDFEditTextArea { if let str = data.editTextAreaString(), str.isEmpty { self.listView?.remove(with: [area]) } } } return } var newRect = rect if CGSizeEqualToSize(rect.size, .zero) { newRect = CGRect(x: rect.origin.x, y: rect.origin.y - 12, width: 20, height: 12) } else { newRect = CGRect(x: rect.origin.x, y: rect.origin.y + rect.size.height - 12, width: rect.size.width, height: 12) } let model = KMEditPDFTextManager.manager.fetchUserDefaultData(type: .commonly) let fontName = KMEditPDFTextManager.manager.fetchFontName(fontName: model.fontName) let fontSize = model.fontSize let fontColor = model.color let fontAlign = model.alignment let fontStyle = KMEditPDFTextManager.manager.fetchFontStyle(fontName: model.fontName) NSColorPanel.shared.color = fontColor let font = KMEditPDFTextManager.manager.fetchFont(fontName: fontName, style: fontStyle, size: fontSize) let attri = CEditAttributes() attri.font = font attri.fontColor = fontColor attri.alignment = fontAlign self.listView?.createStringBounds(newRect, with: attri, page: page) // self.rightViewC != nil && if self.subViewType == .EditPDFAddText && self.annotationType == .addText { self.rightViewC?.eidtPDFTextProperty.refreshSelectAreaProperty(needDefaultData: true) } self.showPopWindow(positionRect: newRect) } // 文本区块 选中文本已经变化 func pdfViewEditingSelectionDidChanged(_ pdfView: CPDFView!) { // self.viewC?.rightSideViewController != nil && if self.subViewType == .EditPDFAddText { self.rightViewC?.eidtPDFTextProperty.reloadData() self.rightViewC?.eidtPDFTextProperty.updateTextTextPresuppositionState() } } func pdfViewMobileEditingBegan(_ point: CGPoint, for pdfView: CPDFView!, forEditing editingAreas: [CPDFEditArea]!) { KMPrint("pdfViewMobileEditingBegan") } func pdfViewMobileEditingMove(_ point: CGPoint, for pdfView: CPDFView!, forEditing editingAreas: [CPDFEditArea]!) { KMPrint("pdfViewMobileEditingMove") } func pdfViewMobileEditingEnd(_ point: CGPoint, for pdfView: CPDFView!, forEditing editingAreas: [CPDFEditArea]!) { KMPrint("pdfViewMobileEditingEnd") } }