// // KMEditImageController.swift // PDF Reader Pro // // Created by Niehaoyu on 2024/11/14. // import Cocoa import KMComponentLibrary class KMEditImageController: NSViewController { @IBOutlet var scrollView: NSScrollView! @IBOutlet var contendView: NSView! @IBOutlet var sizeBGView: NSView! @IBOutlet var sizeLabel: NSTextField! @IBOutlet var sizeSyncButton: ComponentButton! @IBOutlet var sizeWidthInput: ComponentInputNumber! @IBOutlet var sizeHeightInput: ComponentInputNumber! @IBOutlet var rotateBGView: NSView! @IBOutlet var rotateLabel: NSTextField! @IBOutlet var rotateSelect: ComponentSelect! @IBOutlet var rotateLeftButton: ComponentButton! @IBOutlet var rotateRightButton: ComponentButton! @IBOutlet var flipVerticalButton: ComponentButton! @IBOutlet var flipHorizontalButton: ComponentButton! @IBOutlet var opacityBGView: NSView! @IBOutlet var opacityLabel: NSTextField! @IBOutlet var opacitySlider: ComponentSlider! @IBOutlet var opacitySelect: ComponentSelect! @IBOutlet var cropButton: ComponentButton! @IBOutlet var replaceButton: ComponentButton! @IBOutlet var ExtrackButton: ComponentButton! private var syncChangeBounds: Bool = true //同步修改宽高 private var groupView: ComponentGroup! var pdfView: CPDFListView? { didSet { reloadData() } } var currentArea: CPDFEditImageArea? //MARK: - func override func viewDidAppear() { super.viewDidAppear() opacitySlider.reloadData() } override func viewDidLoad() { super.viewDidLoad() setupProperty() } func setupProperty() { //Size sizeLabel.stringValue = KMLocalizedString("Size") sizeLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") sizeLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") sizeSyncButton.properties = ComponentButtonProperty(type: .text_gray, size: .xxs, onlyIcon: true, icon: NSImage(named: "sync_Change_unlock"), keepPressState: true) sizeSyncButton.properties.propertyInfo.leftIcon_press = NSImage(named: "sync_Change_lock") sizeSyncButton.setTarget(self, action: #selector(sizeSyncButtonClicked(_:))) sizeSyncButton.reloadData() sizeWidthInput.properties = ComponentInputNumberProperty(alignment: .center, size: .s, state: .normal, showPrefix: true, leftIcon: NSImage(named: "w_icon"), showSuffix: false, minSize: 1, maxSize: 1000, text:"100") sizeWidthInput.delegate = self sizeHeightInput.properties = ComponentInputNumberProperty(alignment: .center, size: .s, state: .normal, showPrefix: true, leftIcon: NSImage(named: "h_icon"), showSuffix: false, minSize: 1, maxSize: 1000, text:"100") sizeHeightInput.delegate = self //Rotate rotateLabel.stringValue = KMLocalizedString("Rotate & Flip") rotateLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") rotateLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") rotateSelect.properties = ComponentSelectProperties(size: .s, state: .normal, creatable: true, text: "100", textUnit: "°", regexString: "0123456789-") if true { var opacityItems: [ComponentMenuitemProperty] = [] for string in ["0°", "45°", "-45°", "90°", "-90°"] { let item = ComponentMenuitemProperty(type: .normal, text: string) opacityItems.append(item) } rotateSelect.updateMenuItemsArr(opacityItems) } rotateSelect.delegate = self rotateLeftButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "pageEdit_rotateLeft"), keepPressState: false) rotateLeftButton.setTarget(self, action: #selector(buttonClicked(_:))) rotateRightButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "pageEdit_rotateRight"), keepPressState: false) rotateRightButton.setTarget(self, action: #selector(buttonClicked(_:))) flipVerticalButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "flipVertical"), keepPressState: false) flipVerticalButton.setTarget(self, action: #selector(buttonClicked(_:))) flipHorizontalButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "flipHorizontal"), keepPressState: false) flipHorizontalButton.setTarget(self, action: #selector(buttonClicked(_:))) //Opacity opacityLabel.stringValue = KMLocalizedString("Opacity") opacityLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") opacityLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") opacitySlider.properties = ComponentSliderProperty(size: .m, percent: 1) opacitySlider.delegate = self opacitySelect.properties = ComponentSelectProperties(size: .s, state: .normal, creatable: true, text: "100", textUnit: "%", regexString: "0123456789%") if true { var opacityItems: [ComponentMenuitemProperty] = [] for string in ["25%", "50%", "75%", "100%"] { let item = ComponentMenuitemProperty(type: .normal, text: string) opacityItems.append(item) } opacitySelect.updateMenuItemsArr(opacityItems) } opacitySelect.delegate = self cropButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Crop"), keepPressState: false) cropButton.setTarget(self, action: #selector(buttonClicked(_:))) replaceButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Replace"), keepPressState: false) replaceButton.setTarget(self, action: #selector(buttonClicked(_:))) ExtrackButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Extract"), keepPressState: false) ExtrackButton.setTarget(self, action: #selector(buttonClicked(_:))) } func reloadData() { let areas = pdfView?.km_editingImageAreas() ?? [] if areas.count == 0 { currentArea = nil } else if areas.count == 1 { currentArea = areas.first } else if areas.count == 2 { currentArea = nil } if let area = currentArea { //Size if syncChangeBounds { sizeSyncButton.properties.state = .pressed } else { sizeSyncButton.properties.state = .normal } sizeSyncButton.reloadData() let areaFrame = area.bounds sizeWidthInput.properties.text = String(format: "%.1f", areaFrame.size.width) sizeWidthInput.reloadData() sizeHeightInput.properties.text = String(format: "%.1f", areaFrame.size.height) sizeHeightInput.reloadData() //Rotate if let rotates = pdfView?.km_editAreasRotates([area]) { if rotates.count > 0 { var rotate = rotates.first ?? 1 rotateSelect.properties.text = String(format: "%.0f%@", rotate, "°") rotateSelect.reloadData() } } //Opacity if let opacitys = pdfView?.km_editAreasOpacitys([area]) { if opacitys.count > 0 { var opacity = opacitys.first ?? 1 opacity = min(1, opacity) opacity = max(0, opacity) opacitySelect.properties.text = String(format: "%.0f%@", opacity*100, "%") opacitySelect.reloadData() opacitySlider.properties.percent = opacity opacitySlider.reloadData() } } if pdfView?.isEditImage == true { cropButton.properties.buttonText = KMLocalizedString("Cancel Crop") } else { cropButton.properties.buttonText = KMLocalizedString("Crop") } cropButton.reloadData() } } //MARK: - Action @objc func sizeSyncButtonClicked(_ sender: ComponentButton) { syncChangeBounds = !syncChangeBounds if syncChangeBounds { sizeSyncButton.properties.state = .pressed } else { sizeSyncButton.properties.state = .normal } sizeSyncButton.reloadData() } @objc func buttonClicked(_ sender: ComponentButton) { if sender == rotateLeftButton { self.pdfView?.leftRotateAction() } else if sender == rotateRightButton { self.pdfView?.rightRotateAction() } else if sender == flipVerticalButton { self.pdfView?.reverseYAction() } else if sender == flipHorizontalButton { self.pdfView?.reverseXAction() } else if sender == cropButton { if pdfView?.isEditImage == true { pdfView?.cropCancelAction() } else { pdfView?.cropAction() } } else if sender == replaceButton { if let areas = self.pdfView?.km_editingImageAreas() { 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 { self.pdfView?.replace(area, imagePath: openPath!) } } } } } else if sender == ExtrackButton { extractAction() } reloadData() } func extractAction() { var menuItemArr: [ComponentMenuitemProperty] = [] let items: [(String, String)] = [("jpg", "export_jpg_Key"), ("png", "export_png_Key"), ("pdf", "export_pdf_Key")] for (i, value) in items { let properties_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString(i), identifier: value) menuItemArr.append(properties_Menuitem) } if groupView == nil { groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) } groupView.groupDelegate = self groupView?.frame = CGRectMake(0, 0, ExtrackButton.frame.size.width, 36*3+8) groupView.updateGroupInfo(menuItemArr) if let point: CGPoint = ExtrackButton.superview?.convert(ExtrackButton.frame.origin, to: self.view.window?.contentView) { groupView.showWithPoint(CGPoint(x: point.x, y: point.y - CGRectGetHeight(groupView.frame)-4), relativeTo: self.view) } } //MARK: - MouseEvent override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) view.window?.makeFirstResponder(nil) } } extension KMEditImageController: ComponentInputNumberDelegate { func componentInputNumberDidValueChanged(inputNumber: ComponentInputNumber?) { if inputNumber == sizeWidthInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] self.pdfView?.editAreaBoundUpdating = true for area in areas { var bounds = area.bounds var width = inputNumber?.properties.text?.stringToCGFloat() ?? 0 if width == 0 { return } let pageBounds = area.page?.bounds ?? .zero if width > NSWidth(pageBounds) { width = NSWidth(pageBounds) let value = String(format: "%.1f", width) } let wOffset = width-bounds.size.width bounds.origin.x = max(0, bounds.origin.x-wOffset * 0.5) bounds.origin.x = min(bounds.origin.x, pageBounds.size.width-width) if syncChangeBounds { var scale: CGFloat = 0 if bounds.size.width != 0 { scale = width / bounds.size.width } bounds.size.height *= scale bounds.size.width = width } else { bounds.size.width = width } self.pdfView?.setBoundsEditArea(area, withBounds: bounds) } } else if inputNumber == sizeHeightInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] self.pdfView?.editAreaBoundUpdating = true for area in areas { var bounds = area.bounds var height = inputNumber?.properties.text?.stringToCGFloat() ?? 0 if height == 0 { return } let pageBounds = area.page?.bounds ?? .zero if height > NSHeight(pageBounds) { height = NSHeight(pageBounds) } let hOffset = height-bounds.size.height bounds.origin.y = max(0, bounds.origin.y-hOffset * 0.5) bounds.origin.y = min(bounds.origin.y, pageBounds.size.height-height) if syncChangeBounds { // 寛高约束 var scale: CGFloat = 0 if bounds.size.height != 0 { scale = height / bounds.size.height } bounds.size.width *= scale bounds.size.height = height } else { bounds.size.height = height } self.pdfView?.setBoundsEditArea(area, withBounds: bounds) } } reloadData() } } //MARK: - ComponentSelectDelegate extension KMEditImageController: ComponentSelectDelegate { func componentSelectDidSelect(view: ComponentSelect?, menuItemProperty: ComponentMenuitemProperty?) { if view == opacitySelect { if let text = opacitySelect.properties.text { let result = text.stringByDeleteCharString("%") let opacity = result.stringToCGFloat()/100 pdfView?.setEditingAreasOpacity(opacity) reloadData() } } else if view == rotateSelect { if let text = rotateSelect.properties.text { let result = text.stringByDeleteCharString("°") let value = result.stringToCGFloat() pdfView?.rotateEditingAreas(value) reloadData() } } } func componentSelectTextDidEndEditing(_ view: ComponentSelect) { if view == opacitySelect { if let text = opacitySelect.properties.text { let result = text.stringByDeleteCharString("%") let opacity = result.stringToCGFloat()/100 pdfView?.setEditingAreasOpacity(opacity) reloadData() } } else if view == rotateSelect { if let text = rotateSelect.properties.text { let result = text.stringByDeleteCharString("°") let value = result.stringToCGFloat() pdfView?.rotateEditingAreas(value) reloadData() } } } } //MARK: - ComponentCColorDelegate extension KMEditImageController: ComponentCColorDelegate { } //MARK: - ComponentSliderDelegate extension KMEditImageController: ComponentSliderDelegate { func componentSliderDidUpdate(_ view: ComponentSlider) { let percent = view.properties.percent self.pdfView?.setEditingAreasOpacity(percent) reloadData() } } //MARK: - ComponentGroupDelegate extension KMEditImageController: ComponentGroupDelegate { func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) { if menuItemProperty?.identifier == "export_jpg_Key" { pdfView?.exportEditingImageAreasAction(format: "jpg") } else if menuItemProperty?.identifier == "export_png_Key" { pdfView?.exportEditingImageAreasAction(format: "png") } else if menuItemProperty?.identifier == "export_pdf_Key" { pdfView?.exportEditingImageAreasAction(format: "pdf") } } }