// // KMEditPDfHanddler.swift // PDF Reader Pro // // Created by tangchao on 2024/6/16. // import Cocoa @objc enum KMRightSideLastState: Int { case none = 0 case open = 1 case close = 2 } // EditPDF处理对象 class KMEditPDfHanddler: NSObject { weak var viewC: KMMainViewController? static let kRightSideLastStateKey = "KMRightSideLastStateKey" // 正在新增文本块 var addTextAreaing = false var fontSizeChanging = false var textAlignChanging = false 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] ?? [] } } var editingImageAreas: [CPDFEditImageArea] { get { var areas: [CPDFEditImageArea] = [] for area in self.editingAreas { if let data = area as? CPDFEditImageArea { areas.append(data) } } return areas } } var editingTextAreas: [CPDFEditTextArea] { get { var areas: [CPDFEditTextArea] = [] for area in self.editingAreas { if let data = area as? CPDFEditTextArea { areas.append(data) } } return areas } } var rightSideLastState: KMRightSideLastState { get { let state = KMDataManager.ud_integer(forKey: Self.kRightSideLastStateKey) return KMRightSideLastState(rawValue: state) ?? .none } set { KMDataManager.ud_set(newValue.rawValue, forKey: Self.kRightSideLastStateKey) } } private var startPoint_: NSPoint = .zero func enterEditPDF() { let toolMode = self.listView?.toolMode ?? .none if toolMode != .editPDFToolMode { // 退出 self.listView?.updateActiveAnnotations([]) self.listView?.setNeedsDisplayForVisiblePages() self.listView?.commitEditFormText() self.closeRightPane() self.clearData() return } self._addNotification() if self.rightSideLastState == .open { self.openRightPane() } else { self.closeRightPane() } 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 = NSColor(hex: "#999999") // 边框虚线设置 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 // self.editingConfig?.editingMouseSelectionBorderColor self.editingConfig?.editingMouseSelectionBorderWidth = 1 self.editingConfig?.editingMouseSelectionBorderDashPattern = [3, 3, 3] } func commitEditing() { let isEdited = self.listView?.isEdited() ?? false let isPDFTextImageEdited = self.viewC?.model.isPDFTextImageEdited ?? false if isEdited || isPDFTextImageEdited { self.listView?.commitEditing() } self.clearData() } func openRightPane() { let state = self.rightSideLastState if state == .none || state == .open { self.viewC?.openRightPane() } } func closeRightPane() { self.rightViewC?.isHidden = true self.viewC?.closeRightPane() } func showPopWindow(positionRect: NSRect, showGuide: Bool) { if self.editAreasIsEmpty() { return } let show = KMPreference.shared.editPDFPopWindowIsShow if !show { return } let win = KMEditPDFPopToolBarWindow.shared self._kRemoveChildWindow(win) let areas = self.editingAreas win.isMultiple = areas.count > 1 var hasText = false var hasImage = false var fontColors: [NSColor] = [] for area in areas { if let data = area as? CPDFEditTextArea { hasText = true if let color = self.listView?.editingSelectionFontColor(with: data) { fontColors.append(color) } } if area is CPDFEditImageArea { hasImage = true } } var style: KMEditPDFToolbarStyle = [] if hasText { style.insert(.text) } if hasImage { style.insert(.image) } win.style = style win.model.editingAreas = areas win.model.fontColors = fontColors win.model.fontNames = self._editAreasFontNames() win.model.fontSizes = self._editAreasFontSizes() win.model.fontBolds = self._editAreasFontBolds() win.model.fontItalics = self._editAreasFontItalics() win.model.textAlignments = self._editAreasTextAlignments() win.model.rotates = self._editAreasRotates() win.model.opacitys = self._editAreasOpacitys() let area = (self.listView?.editingAreas().first as? CPDFEditArea) var areaBounds = (self.listView?.convert(area!.bounds, from: area!.page) as? NSRect) ?? .zero var maxX = NSMaxX(areaBounds) var maxY = NSMaxY(areaBounds) for area in self.editingAreas { // let bounds = area.bounds let bounds = (self.listView?.convert(area.bounds, from: area.page) as? NSRect) ?? .zero areaBounds.origin.x = min(areaBounds.origin.x, bounds.origin.x) areaBounds.origin.y = min(areaBounds.origin.y, bounds.origin.y) maxX = max(maxX, NSMaxX(bounds)) areaBounds.size.width = maxX-areaBounds.origin.x maxY = max(maxY, NSMaxY(bounds)) areaBounds.size.height = maxY-areaBounds.origin.y } self.startPoint_ = self.listView?.documentView().documentVisibleRect.origin ?? .zero win.show(relativeTo: areaBounds, of: self.viewC!.listView, preferredEdge: .maxY) win.animator().alphaValue = 1 self._kAddchildwindow(win) win.itemClick = { [weak self] itemKey, obj in if itemKey == .color { self?.fontColorAction(color: obj as? NSColor) } else if itemKey == .fontStyle { self?.fontStyleAction(fontName: obj as? String) } else 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(align: obj as? NSTextAlignment ?? .left) } // 图片 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 { if let data = obj as? NSView { self?.showExportMenu(data) } } // 对齐 else if itemKey == .alignmentLeft { self?.alignmentAction(align: .Left) } else if itemKey == .alignmentCenterX { self?.alignmentAction(align: .Horizontally) } else if itemKey == .alignmentRight { self?.alignmentAction(align: .Right) } else if itemKey == .alignmentjustifiedX { self?.alignmentAction(align: .DisHorizontally) } else if itemKey == .alignmentTop { self?.alignmentAction(align: .Top) } else if itemKey == .alignmentCenterY { self?.alignmentAction(align: .Vertical) } else if itemKey == .alignmentBottom { self?.alignmentAction(align: .Bottom) } else if itemKey == .alignmentjustifiedY { self?.alignmentAction(align: .DisVertical) } } // 显示新手引导 if let toolbarView = (win.contentViewController as? KMEditPDFPopToolBarController)?.toolbarView { if showGuide { self.showGuideView(toolbarView) } } } func hiddenPopWindow() { let win = KMEditPDFPopToolBarWindow.shared win.orderOut(nil) win.setIsVisible(false) self._kRemoveChildWindow(win) } func showCropComfirmWindow() { let winC = KMEditPDFCropComfirmWindowController.shared if KMEditPDFPopToolBarWindow.shared.isVisible { let winFrame = KMEditPDFPopToolBarWindow.shared.frame let x = winFrame.origin.x + (NSWidth(winFrame)-84) * 0.5 let frame = NSMakeRect(x, winFrame.origin.y, 84, 44) winC.window?.setFrame(frame, display: true) } else { let area = (self.listView?.editingAreas().first as? CPDFEditArea) let areaBounds = (self.listView?.convert(area!.bounds, from: area!.page) as? NSRect) ?? .zero let positioningView = self.listView let winFrame = positioningView?.window?.frame ?? .zero let toView: NSView? = nil var position = positioningView?.convert(areaBounds.origin, to: toView) ?? .zero position.x += winFrame.origin.x position.y += winFrame.origin.y position.y += areaBounds.size.height position.y += 26 let x = position.x + (NSWidth(areaBounds)-84) * 0.5 let frame = NSMakeRect(x, position.y, 84, 44) winC.window?.setFrame(frame, display: true) } winC.showWindow(nil) self._kAddchildwindow(winC.window!) winC.itemAction = { [weak self] idx, _ in if idx == 0 { // 确认 self?.cropComfirmAction() } else if idx == 1 { // 取消 self?.cropCancelAction() } } } func hiddenCropComfirmWindow() { let winC = KMEditPDFCropComfirmWindowController.shared winC.window?.orderOut(nil) winC.window?.setIsVisible(false) self._kRemoveChildWindow(winC.window) } func showGuideView(_ view: NSView) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { if KMGuideInfoWindowController.availableShow(.editPDFPopWindow) { var winFrame = self.viewC?.view.window?.frame ?? .zero winFrame.size.height -= 20 guard let area = (self.listView?.editingAreas().first as? CPDFEditArea) else { return } var areaBounds = (self.listView?.convert(area.bounds, from: area.page) as? NSRect) ?? .zero var maxX = NSMaxX(areaBounds) var maxY = NSMaxY(areaBounds) for area in self.editingAreas { // let bounds = area.bounds let bounds = (self.listView?.convert(area.bounds, from: area.page) as? NSRect) ?? .zero areaBounds.origin.x = min(areaBounds.origin.x, bounds.origin.x) areaBounds.origin.y = min(areaBounds.origin.y, bounds.origin.y) maxX = max(maxX, NSMaxX(bounds)) areaBounds.size.width = maxX-areaBounds.origin.x maxY = max(maxY, NSMaxY(bounds)) areaBounds.size.height = maxY-areaBounds.origin.y } let guideWC = KMGuideInfoWindowController.currentWC() guideWC.type = .editPDFPopWindow var viewFrame = areaBounds let tmpY = areaBounds.origin.y+(areaBounds.size.height-KMEditPDFPopGuideView.kHeight+80) if tmpY < 50 { guideWC.editPDFPopWindowFlag = true viewFrame.origin.y += (areaBounds.size.height) viewFrame.origin.x += (areaBounds.size.width*0.5+KMEditPDFPopGuideView.kWidth*0.5) viewFrame.origin.y += 20 } else { guideWC.editPDFPopWindowFlag = false viewFrame.origin.y += (areaBounds.size.height-KMEditPDFPopGuideView.kHeight+80) viewFrame.origin.x += (areaBounds.size.width*0.5+KMEditPDFPopGuideView.kWidth*0.5) viewFrame.size.height = KMEditPDFPopGuideView.kHeight+80 let offsetY = NSMaxY(winFrame)-NSMaxY(viewFrame)-NSMinY(winFrame) if offsetY <= 0 { viewFrame.origin.y += offsetY } } guideWC.digitalBoxRect = viewFrame var beh = view.window?.collectionBehavior ?? [] beh.insert(.canJoinAllSpaces) guideWC.window?.collectionBehavior = beh guideWC.window?.setFrame(winFrame, display: false) guideWC.window?.minSize = winFrame.size guideWC.window?.maxSize = winFrame.size self._kAddchildwindow(guideWC.window!) guideWC.show() DispatchQueue.main.async { guideWC.interfaceThemeDidChanged(NSApp.appearance?.name ?? .aqua) } guideWC.settingCallback = { KMPreferenceController.shared.showWindow(nil) } } } } func clearData() { self.hiddenWindows() self._removeNotification() } func hiddenWindows() { self.hiddenPopWindow() self.hiddenCropComfirmWindow() KMColorPanelCloseIfNeed() } } // MARK: - Private Methods extension KMEditPDfHanddler { private func _kAddchildwindow(_ childW: NSWindow?) { guard let win = childW else { return } self.viewC?.view.window?.addChildWindow(win, ordered: .above) } private func _kRemoveChildWindow(_ childW: NSWindow?) { guard let win = childW else { return } let contains = self.viewC?.view.window?.childWindows?.contains(win) ?? false if contains { self.viewC?.view.window?.removeChildWindow(win) } } private func _addNotification() { NotificationCenter.default.addObserver(self, selector: #selector(_scrollViewDidScroll), name: NSScrollView.didLiveScrollNotification, object: self.listView?.documentView()) } private func _removeNotification() { NotificationCenter.default.removeObserver(self, name: NSScrollView.didLiveScrollNotification, object: self.listView?.documentView()) } @objc private func _scrollViewDidScroll(_ noti: Notification) { if let data = self.listView?.documentView().isEqual(to: noti.object), data { let win = KMEditPDFPopToolBarWindow.shared if win.isVisible == false { // return } guard let area = (self.listView?.editingAreas()?.first as? CPDFEditArea) else { self.hiddenPopWindow() self.hiddenCropComfirmWindow() return } let isEditImage = self.listView?.isEditImage ?? false if isEditImage { let winC = KMEditPDFCropComfirmWindowController.shared let winW: CGFloat = 84 let areaBounds = (self.listView?.convert(area.bounds, from: area.page) as? NSRect) ?? .zero let winFrame = self.listView?.window?.frame ?? .zero let view: NSView? = nil var position = self.listView?.convert(areaBounds.origin, to: view) ?? .zero position.x += winFrame.origin.x position.y += winFrame.origin.y position.y += areaBounds.size.height position.y += 26 var x = position.x + (NSWidth(areaBounds)-84) * 0.5 // let frame = NSMakeRect(x, position.y, 84, 44) // position.x += (areaBounds.size.width*0.5-win.frame.size.width*0.5) x = max(0, x) var y = max(0, position.y) let screenFrame = NSScreen.main?.frame ?? .zero if y + 44 + 40 >= screenFrame.size.height { y = screenFrame.size.height - 44 - 40 } let wframe = NSMakeRect(x, y, winW, 44) winC.window?.setFrame(wframe, display: true) if winFrame.contains(wframe) == false { self.hiddenCropComfirmWindow() } else { self.showCropComfirmWindow() } return } var areaBounds = (self.listView?.convert(area.bounds, from: area.page) as? NSRect) ?? .zero var maxX = NSMaxX(areaBounds) var maxY = NSMaxY(areaBounds) for area in self.editingAreas { // let bounds = area.bounds let bounds = (self.listView?.convert(area.bounds, from: area.page) as? NSRect) ?? .zero areaBounds.origin.x = min(areaBounds.origin.x, bounds.origin.x) areaBounds.origin.y = min(areaBounds.origin.y, bounds.origin.y) maxX = max(maxX, NSMaxX(bounds)) areaBounds.size.width = maxX-areaBounds.origin.x maxY = max(maxY, NSMaxY(bounds)) areaBounds.size.height = maxY-areaBounds.origin.y } let winFrame = self.listView?.window?.frame ?? .zero let view: NSView? = nil var position = self.listView?.convert(areaBounds.origin, to: view) ?? .zero if position.y > winFrame.height { self.hiddenPopWindow() return } position.x += winFrame.origin.x position.y += winFrame.origin.y position.y += areaBounds.size.height position.y += 26 position.x += (areaBounds.size.width*0.5-win.frame.size.width*0.5) // var x = max(0, position.x) var x = max(winFrame.origin.x, position.x) let width = win.frame.size.width let offsetX = x + width - NSMaxX(winFrame) if offsetX > 0 { // 超出右编辑 x -= offsetX } var y = max(0, position.y) // let screenFrame = NSScreen.main?.frame ?? .zero // let winMaxY = NSMaxY(winFrame) let height = NSHeight(winFrame) if y + 44 + 40-20 >= height { // if y + 44 + 40 + 40 >= screenFrame.size.height { // y = screenFrame.size.height - 44 - 40 - 40 y = height - 44 - 40 + 20 } let wframe = NSMakeRect(x, y, width, 44) win.setFrame(wframe, display: true) if winFrame.contains(wframe) == false { self.hiddenPopWindow() } } } private func _reloadData_right_text() { self.rightViewC?.eidtPDFTextProperty.handdler = self self.rightViewC?.eidtPDFTextProperty.reloadData() } private func _reloadData_right_image() { self.rightViewC?.eidtPDFImageProperty.handdler = self self.rightViewC?.eidtPDFImageProperty.reloadData() } } // MARK: - Tools extension KMEditPDfHanddler { func editAreasIsEmpty() -> Bool { return self.editingAreas.isEmpty } func editAreasHavTextArea() -> Bool { return self.editingTextAreas.isEmpty == false } func editAreasHavImageArea() -> Bool { return self.editingImageAreas.isEmpty == false } func editAreasFontColorIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasFontColors() if datas.count == 1 { return true } let color = datas.first for (i, d) in datas.enumerated() { if i == 0 { continue } if d != color { return false } } return true } func editAreasFontNameIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasFontNames() if datas.count == 1 { return true } let data = datas.first for (i, d) in datas.enumerated() { if i == 0 { continue } if d != data { return false } } return true } func editAreasFontStyleIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasFontStyles() if datas.count == 1 { return true } let data = datas.first for (i, d) in datas.enumerated() { if i == 0 { continue } if d != data { return false } } return true } func editAreasFontSizeIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasFontSizes() if datas.count == 1 { return true } let data = datas.first for (i, d) in datas.enumerated() { if i == 0 { continue } if d != data { return false } } return true } func editAreasFontBoldIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasFontBolds() if datas.count == 1 { return true } let data = datas.first for (i, d) in datas.enumerated() { if i == 0 { continue } if d != data { return false } } return true } func editAreasFontItalicIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasFontItalics() if datas.count == 1 { return true } let data = datas.first for (i, d) in datas.enumerated() { if i == 0 { continue } if d != data { return false } } return true } func editAreasTextAlignmentIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasTextAlignments() if datas.count == 1 { return true } let data = datas.first?.rawValue ?? 0 for (i, d) in datas.enumerated() { if i == 0 { continue } if d.rawValue != data { return false } } return true } func editAreasBoundsIsEqualForWidth() -> Bool { if self.editAreasIsEmpty() { return false } let rects = self._editAreasBounds() if rects.count == 1 { return true } let width = rects.first?.width ?? 0 for (i, rect) in rects.enumerated() { if i == 0 { continue } if abs(width-rect.size.width) > 0.01 { // if width != rect.size.width { return false } } return true } func editAreasBoundsIsEqualForHeight() -> Bool { if self.editAreasIsEmpty() { return false } let rects = self._editAreasBounds() if rects.count == 1 { return true } let height = rects.first?.height ?? 0 for (i, rect) in rects.enumerated() { if i == 0 { continue } // if height != rect.size.height { if abs(height-rect.size.height) > 0.01 { return false } } return true } func editAreasRotateIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } if self.editAreasHavTextArea() { return false } let datas = self._editAreasRotates() if datas.count == 1 { return true } let data = datas.first ?? 0 for (i, d) in datas.enumerated() { if i == 0 { continue } if data != d { return false } } return true } func editAreasOpacityIsEqual() -> Bool { if self.editAreasIsEmpty() { return false } let datas = self._editAreasOpacitys() if datas.count == 1 { return true } let data = datas.first ?? 0 for (i, d) in datas.enumerated() { if i == 0 { continue } if data != d { return false } } return true } private func _editAreasFontColors() -> [NSColor] { var datas: [NSColor] = [] for area in self.editingTextAreas { if let data = self.listView?.editingSelectionFontColor(with: area) { // if let data = self.listView?.editingSelectionFontColor(byRangeEdit: area) { datas.append(data) } } return datas } private func _editAreasFontStyles() -> [String] { var datas: [String] = [] for area in self.editingTextAreas { if let data = self.listView?.editingSelectionCFont(byRangeEdit: area)?.styleName { // if let data = self.listView?.editingSelectionCFont(with: area)?.familyName { datas.append(data) } } return datas } private func _editAreasFontNames() -> [String] { var datas: [String] = [] for area in self.editingTextAreas { if let data = self.listView?.editingSelectionCFont(byRangeEdit: area)?.familyName { // if let data = self.listView?.editingSelectionCFont(with: area)?.familyName { datas.append(data) } } return datas } private func _editAreasFontSizes() -> [CGFloat] { var datas: [CGFloat] = [] for area in self.editingTextAreas { if let data = self.listView?.editingSelectionFontSize(byRangeEdit: area) { datas.append(data) } } return datas } private func _editAreasFontBolds() -> [Bool] { var datas: [Bool] = [] for area in self.editingTextAreas { if let data = self.listView?.isBoldCurrentSelection(byRangeEdit: area) { datas.append(data) } } return datas } private func _editAreasFontItalics() -> [Bool] { var datas: [Bool] = [] for area in self.editingTextAreas { if let data = self.listView?.isItalicCurrentSelection(byRangeEdit: area) { datas.append(data) } } return datas } private func _editAreasTextAlignments() -> [NSTextAlignment] { var datas: [NSTextAlignment] = [] for area in self.editingTextAreas { if let data = self.listView?.currentSelectionAlignment(byRangeEdit: area) { datas.append(data) } } return datas } private func _editAreasBounds() -> [NSRect] { var rects: [NSRect] = [] for area in self.editingAreas { rects.append(area.bounds) } return rects } private func _editAreasRotates() -> [CGFloat] { var arr: [CGFloat] = [] for area in self.editingImageAreas { if let data = self.listView?.getRotateWith(area) { arr.append(data) } } return arr } private func _editAreasOpacitys() -> [CGFloat] { var arr: [CGFloat] = [] for area in self.editingAreas { if let data = self.listView?.opacityByRange(for: area) { arr.append(data) } } return arr } } // MARK: - Action extension KMEditPDfHanddler { func fontColorAction(color: NSColor?) { guard let theColor = color else { return } let areas = self.editingTextAreas for area in areas { self.listView?.setEditingSelectionFontColor(theColor, with: area) } self._reloadData_right_text() } func fontStyleAction(fontName: String?) { guard let font = CPDFFont.mappingFont(withFontString: fontName) else { return } let areas = self.editingTextAreas for area in areas { self.listView?.setEditSelectionCFont(font, with: area) } self._reloadData_right_text() } func fontAddAction() { let areas = self.editingTextAreas for area in areas { if let fontSize = self.listView?.editingSelectionFontSize(byRangeEdit: area) { self.fontSizeChanging = true self.listView?.setEditingSelectionFontSize(fontSize+1, with: area, isAutoSize: false) self.fontSizeChanging = false } } self._reloadData_right_text() } func fontReduceAction() { let areas = self.editingTextAreas for area in areas { if let fontSize = self.listView?.editingSelectionFontSize(byRangeEdit: area) { self.fontSizeChanging = true self.listView?.setEditingSelectionFontSize(fontSize-1, with: area, isAutoSize: false) self.fontSizeChanging = false } } self._reloadData_right_text() } func fontBoldAction() { let areas = self.editingTextAreas var needTip = false for area in areas { if let data = self.listView?.isBoldCurrentSelection(byRangeEdit: area) { let result = self.listView?.setCurrentSelectionIsBold(!data, with: area) if (result == nil || result == false) && needTip == false { needTip = true } } } if needTip { if let data = self.viewC?.view { _ = CustomAlertView.alertView(message: NSLocalizedString("Please reset the font weight via the drop-down box", comment: ""), fromView: data, withStyle: .black) } } self._reloadData_right_text() } func fontItalicAction() { let areas = self.editingTextAreas var needTip = false for area in areas { if let data = self.listView?.isItalicCurrentSelection(byRangeEdit: area) { let result = self.listView?.setCurrentSelectionIsItalic(!data, with: area) if (result == nil || result == false) && needTip == false { needTip = true } } } if needTip { if let data = self.viewC?.view { _ = CustomAlertView.alertView(message: NSLocalizedString("Please reset the font weight via the drop-down box", comment: ""), fromView: data, withStyle: .black) } } self._reloadData_right_text() } func textAlignmentAction(align: NSTextAlignment) { let areas = self.editingTextAreas self.textAlignChanging = true for area in areas { self.listView?.setCurrentSelectionAlignment(align, with: area) } self.textAlignChanging = false self._reloadData_right_text() } func leftRotateAction() { let areas = self.editingImageAreas for area in areas { self.listView?.rotate(with: area, rotate: -90) } } func rightRotateAction() { let areas = self.editingImageAreas for area in areas { self.listView?.rotate(with: area, rotate: 90) } } func reverseXAction() { let areas = self.editingImageAreas for area in areas { self.listView?.horizontalMirror(with: area) } } func reverseYAction() { let areas = self.editingImageAreas for area in areas { self.listView?.verticalMirror(with: area) } } func cropAction() { let areas = self.editingImageAreas if areas.isEmpty { return } self.listView?.isEditImage = true for area in areas { self.listView?.enterCrop(with: area) } self.hiddenPopWindow() Task { @MainActor in self.showCropComfirmWindow() } self.rightViewC?.eidtPDFImageProperty.handdler = self self.rightViewC?.eidtPDFImageProperty.updateButtonState(hidden: false) self.viewC?.view.window?.makeFirstResponder(self.listView) } func cropCancelAction() { self.hiddenCropComfirmWindow() let areas = self.editingImageAreas if areas.isEmpty { return } for area in areas { self.listView?.exitCrop(with: area) } self.listView?.cropAreas = nil self.listView?.isEditImage = false self.rightViewC?.eidtPDFImageProperty.handdler = self self.rightViewC?.eidtPDFImageProperty.updateButtonState(hidden: true) Task { @MainActor [weak self] in if let data = self?.listView?.selectImageAreas { self?.showPopWindow(positionRect:data.bounds, showGuide: false) } } } func cropComfirmAction() { guard let selectImageAreas = self.listView?.selectImageAreas else { self.cropCancelAction() return } self.listView?.cropEditImageArea(selectImageAreas, withBounds: self.listView?.cropAreas.cropRect ?? .zero) self.cropCancelAction() } func replaceAction() { let areas = self.editingImageAreas if areas.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 areas { // , rect: data.bounds self.listView?.replace(area, imagePath: openPath!) } } } } func showExportMenu(_ sender: NSView) { // let menuI = NSMenuItem() let submenu = NSMenu(title: "") let jpgMI = submenu.addItem(withTitle: "jpg", action: #selector(exportMenuItemAction), keyEquivalent: "") jpgMI.target = self jpgMI.tag = 1 let pngMI = submenu.addItem(withTitle: "png", action: #selector(exportMenuItemAction), keyEquivalent: "") pngMI.target = self pngMI.tag = 2 let pdfMI = submenu.addItem(withTitle: "pdf", action: #selector(exportMenuItemAction), keyEquivalent: "") pdfMI.target = self pdfMI.tag = 3 let p = NSPoint(x: NSMidX(sender.frame), y: NSMidY(sender.frame)) submenu.popUp(positioning: nil, at: p, in: sender.superview) } @objc func exportMenuItemAction(_ sender: NSMenuItem) { var format = "" if sender.tag == 1 { format = "jpg" } else if sender.tag == 2 { format = "png" } else if sender.tag == 3 { format = "pdf" } self.exportAction(format: format) } func exportAction(format: String) { let areas = self.editingImageAreas if areas.isEmpty { return } if areas.count == 1 { let panel = NSSavePanel() panel.nameFieldStringValue = "\(NSLocalizedString("Untitled", comment: "")).\(format)" 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: areas.first, toImagePath: url!.path) ?? false if result { NSWorkspace.shared.activateFileViewerSelecting([url!]) } } } else if areas.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] = [] let pageIndex = self.listView?.currentPageIndex ?? 0 for j in 0 ..< areas.count { let documentFileName = self.listView?.document?.documentURL.deletingPathExtension().lastPathComponent ?? "" var outPath = filePath! outPath = outPath.stringByAppendingPathComponent(documentFileName) outPath = outPath + "_page\(pageIndex+1)_\(j+1)" outPath = outPath.stringByAppendingPathExtension(format) let result = self.listView?.extractImage(with: areas[j], toImagePath: outPath) ?? false if result { saveURLs.append(URL(fileURLWithPath: outPath)) } } NSWorkspace.shared.activateFileViewerSelecting(saveURLs) } } } } func alignmentAction(align: CPDFActiveAreasAlignType) { KMPrint("updateFormAearsAlignMangent") let stype = align let editingAreas = self.editingAreas if editingAreas.count >= 2 { var zeroRect = NSRect.null var highestRect = NSZeroRect var widthestRect = NSZeroRect let fristArea : CPDFEditArea = editingAreas.first as! CPDFEditArea var leftestRect = fristArea.bounds var rightestRect = fristArea.bounds var topestRect = fristArea.bounds var bottomestRect = fristArea.bounds var leftestArea : CPDFEditArea = fristArea var rightestArea : CPDFEditArea = fristArea var topestArea : CPDFEditArea = fristArea var bottomestArea : CPDFEditArea = fristArea var totalWidth = 0.0 var totalHeight = 0.0 for i in 0 ... editingAreas.count-1 { let area : CPDFEditArea = editingAreas[i] as! CPDFEditArea zeroRect = zeroRect.union(area.bounds) totalWidth = totalWidth + area.bounds.width totalHeight = totalHeight + area.bounds.height if area.bounds.height > highestRect.height { highestRect = area.bounds } if area.bounds.width > widthestRect.width { widthestRect = area.bounds } if leftestRect.minX > area.bounds.minX { leftestRect = area.bounds leftestArea = area } if area.bounds.maxX > rightestRect.maxX { rightestRect = area.bounds rightestArea = area } if area.bounds.maxY > topestRect.maxY { topestRect = area.bounds topestArea = area } if bottomestRect.minY > area.bounds.minY { bottomestRect = area.bounds bottomestArea = area } } var resultAreasArray: [Any] = [] var newBoundsArray: [String] = [] if stype == .Left { for i in 0 ... editingAreas.count-1 { let areas = editingAreas[i] as! CPDFEditArea var bounds = areas.bounds bounds.origin.x = zeroRect.origin.x newBoundsArray.append(NSStringFromRect(bounds)) } resultAreasArray = editingAreas } else if stype == .Right { for i in 0 ... editingAreas.count-1 { let areas = editingAreas[i] as! CPDFEditArea var bounds = areas.bounds bounds.origin.x = zeroRect.maxX - bounds.size.width newBoundsArray.append(NSStringFromRect(bounds)) } resultAreasArray = editingAreas } else if stype == .Top { for i in 0 ... editingAreas.count-1 { let areas = editingAreas[i] as! CPDFEditArea var bounds = areas.bounds bounds.origin.y = zeroRect.maxY - bounds.size.height newBoundsArray.append(NSStringFromRect(bounds)) } resultAreasArray = editingAreas } else if stype == .Bottom { for i in 0 ... editingAreas.count-1 { let areas = editingAreas[i] as! CPDFEditArea var bounds = areas.bounds bounds.origin.y = zeroRect.minY newBoundsArray.append(NSStringFromRect(bounds)) } resultAreasArray = editingAreas } else if stype == .Horizontally { for i in 0 ... editingAreas.count-1 { let areas = editingAreas[i] as! CPDFEditArea var bounds = areas.bounds bounds.origin.y = highestRect.midY - bounds.height/2 newBoundsArray.append(NSStringFromRect(bounds)) } resultAreasArray = editingAreas } else if stype == .Vertical { for i in 0 ... editingAreas.count-1 { let areas = editingAreas[i] as! CPDFEditArea var bounds = areas.bounds bounds.origin.x = widthestRect.midX - bounds.width/2 newBoundsArray.append(NSStringFromRect(bounds)) } resultAreasArray = editingAreas } else if stype == .DisHorizontally { let middleGap = zeroRect.width - leftestRect.width - rightestRect.width let otherAreasTotalWidth = totalWidth - leftestRect.width - rightestRect.width let gap = (middleGap - otherAreasTotalWidth)/CGFloat(editingAreas.count - 1) var areasCopyArray : [CPDFEditArea] = editingAreas as! [CPDFEditArea] areasCopyArray.sorted(by: { obj1, obj2 in let area1 = obj1 let area2 = obj2 if area1.bounds.origin.x < area2.bounds.origin.x { return true } else { return false } }) if let index = areasCopyArray.firstIndex(of: leftestArea) { areasCopyArray.remove(at: index) } if let index = areasCopyArray.firstIndex(of: rightestArea) { areasCopyArray.remove(at: index) } var leftStartX = leftestRect.maxX + gap for i in 0 ..< areasCopyArray.count { let areas = areasCopyArray[i] var bounds = areas.bounds bounds.origin.x = leftStartX newBoundsArray.append(NSStringFromRect(bounds)) leftStartX = leftStartX + bounds.width + gap } resultAreasArray = areasCopyArray } else if stype == .DisVertical { let middleGap = zeroRect.height - topestRect.height - bottomestRect.height let otherAreasTotalHeight = totalHeight - topestRect.height - bottomestRect.height let gap = (middleGap - otherAreasTotalHeight)/CGFloat(editingAreas.count - 1) var areasCopyArray : [CPDFEditArea] = editingAreas as! [CPDFEditArea] areasCopyArray.sorted(by: { obj1, obj2 in let area1 = obj1 let area2 = obj2 if area1.bounds.origin.x < area2.bounds.origin.x { return true } else { return false } }) if let index = areasCopyArray.firstIndex(of: topestArea) { areasCopyArray.remove(at: index) } if let index = areasCopyArray.firstIndex(of: bottomestArea) { areasCopyArray.remove(at: index) } var bottomStartY = bottomestRect.maxY + gap for i in 0 ... areasCopyArray.count-1 { let areas = areasCopyArray[i] var bounds = areas.bounds bounds.origin.y = bottomStartY newBoundsArray.append(NSStringFromRect(bounds)) bottomStartY = bottomStartY + bounds.height + gap } resultAreasArray = areasCopyArray } var oldBounds : [String] = [] for i in 0 ..< resultAreasArray.count { let area : CPDFEditArea = resultAreasArray[i] as! CPDFEditArea oldBounds.append(NSStringFromRect(area.bounds)) self.listView?.setBoundsEditArea(area, withBounds: NSRectFromString(newBoundsArray[i])) } self.listView?.setNeedsDisplayForVisiblePages() } } } // MARK: - CPDFViewDelegate 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 { self.hiddenPopWindow() self.hiddenCropComfirmWindow() 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.handdler = self self.rightViewC?.eidtPDFImageProperty.reloadData() } } // self.openRightPane() } } else { // self.viewC?.closeRightPane() self.rightViewC?.isHidden = true } self.listView?.isEditImage = false } else { self.rightViewC?.isHidden = true self.viewC?.closeRightPane() if self.subViewType == .EditPDFAddText && annotationType == .addText { self.rightViewC?.eidtPDFTextProperty.handdler = self self.rightViewC?.eidtPDFTextProperty.initData() } } return } self.hiddenCropComfirmWindow() 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.handdler = self self.rightViewC?.eidtPDFImageProperty.reloadData() } self.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 self.rightViewC?.eidtPDFTextProperty.handdler = self if count != 0 { self.rightViewC?.eidtPDFTextProperty.reloadData() } else { self.rightViewC?.eidtPDFTextProperty.refreshSelectAreaProperty(needDefaultData: true) } self.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.handdler = self self.rightViewC?.eidtPDFTextProperty?.reloadData() self.openRightPane() } else if imagesAreas.count > 0 { self.rightViewC?.isHidden = false self.rightViewC?.subViewType = .EditPDFAddImage self.rightViewC?.eidtPDFImageProperty.handdler = self self.rightViewC?.eidtPDFImageProperty?.reloadData() self.openRightPane() } } if self.addTextAreaing == false { 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, showGuide: true) } } } 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 window = KMEditPDFPopToolBarWindow.shared if (window.isVisible) { self.listView?.updateEditing([]) self.hiddenPopWindow() self.hiddenCropComfirmWindow() return } 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, showGuide: true) } } } } } } func pdfViewEditingAddTextArea(_ pdfView: CPDFView!, add page: CPDFPage!, add rect: CGRect) { let window = KMEditPDFPopToolBarWindow.shared if (window.isVisible) { self.hiddenPopWindow() 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]) } else { self.listView?.updateEditing([]) } } } return } var newRect = rect if rect.size.equalTo(.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 cfont = CPDFFont(familyName: model.fontName, fontStyle: model.fontStyle) let fontNameZ = CPDFFont.convertAppleFont(cfont) let font = NSFont(name: fontNameZ ?? "Helvetica", size: fontSize) let attri = CEditAttributes() attri.font = font! attri.fontColor = fontColor attri.alignment = fontAlign attri.isBold = model.bold attri.isItalic = model.italic self.addTextAreaing = true self.listView?.createStringBounds(newRect, with: attri, page: page) self.addTextAreaing = false // self.rightViewC != nil && if self.subViewType == .EditPDFAddText && self.annotationType == .addText { self.rightViewC?.eidtPDFTextProperty.handdler = self self.rightViewC?.eidtPDFTextProperty.refreshSelectAreaProperty(needDefaultData: true) } self.showPopWindow(positionRect: newRect, showGuide: true) } // 文本区块 选中文本已经变化 func pdfViewEditingSelectionDidChanged(_ pdfView: CPDFView!) { // self.viewC?.rightSideViewController != nil && if self.subViewType == .EditPDFAddText { self.rightViewC?.eidtPDFTextProperty.handdler = self self.rightViewC?.eidtPDFTextProperty.reloadData() self.rightViewC?.eidtPDFTextProperty.updateTextTextPresuppositionState() self.showPopWindow(positionRect: .zero, showGuide: false) } } func pdfViewEditingOperationDidChanged(_ pdfView: CPDFView!) { let areas = self.editingAreas if areas.count == 1 { if let data = areas.first as? CPDFEditImageArea { let updating = self.listView?.editAreaBoundUpdating ?? false if updating { self.listView?.editAreaBoundUpdating = false } else { self.rightViewC?.eidtPDFImageProperty.handdler = self self.rightViewC?.eidtPDFImageProperty.reloadData() } } } } func pdfViewEditingDoubleClick(_ pdfView: CPDFView!, imageArea editArea: CPDFEditArea!) { } func pdfViewMobileEditingBegan(_ point: CGPoint, for pdfView: CPDFView!, forEditing editingAreas: [CPDFEditArea]!) { self.hiddenPopWindow() } func pdfViewMobileEditingMove(_ point: CGPoint, for pdfView: CPDFView!, forEditing editingAreas: [CPDFEditArea]!) { } func pdfViewMobileEditingEnd(_ point: CGPoint, for pdfView: CPDFView!, forEditing editingAreas: [CPDFEditArea]!) { self.showPopWindow(positionRect: .zero, showGuide: false) } func pdfViewEditingSelectCharDidChanged(_ pdfView: CPDFView!) { let areas = self.editingTextAreas if areas.isEmpty { return } if self.subViewType == .EditPDFAddText { // self.rightViewC?.eidtPDFTextProperty.reloadData() // self.rightViewC?.eidtPDFTextProperty.updateTextTextPresuppositionState() if self.addTextAreaing == false && self.fontSizeChanging == false && self.textAlignChanging == false { self.showPopWindow(positionRect: .zero, showGuide: false) self._reloadData_right_text() } } } }