KMPDFSignatureImageView.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. //
  2. // KMPDFSignatureImageView.swift
  3. // PDF Master
  4. //
  5. // Created by lizhe on 2023/10/9.
  6. //
  7. import Cocoa
  8. import Quartz
  9. let KMSignatureMaxWidth = 400.0
  10. let KMSignatureMaxHeight = 300.0
  11. @objcMembers class KMPDFSignatureImageView: NSView {
  12. var picImage: NSImage?
  13. @IBOutlet var pictureView: NSView!
  14. @IBOutlet var emptyTipLbl: NSTextField!
  15. @IBOutlet var dragButton: NSButton!
  16. @IBOutlet var emptyImg: NSImageView!
  17. var clearBackground: Bool = false {
  18. didSet {
  19. if let imageURL = imageURL, imageURL.path.count > 0 {
  20. loadImageViewPath(imageURL, isRemoveBGColor: clearBackground)
  21. }
  22. }
  23. }
  24. var trackingArea: NSTrackingArea?
  25. var imageURL: URL?
  26. var changeSignatureImageCallback: ((Bool) -> Void)?
  27. required init?(coder: NSCoder) {
  28. super.init(coder: coder)
  29. wantsLayer = true
  30. layer?.borderWidth = 1.0
  31. layer?.borderColor = NSColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.05).cgColor
  32. layer?.backgroundColor = NSColor(red: 1, green: 1, blue: 1, alpha: 0.9).cgColor
  33. registerForDraggedTypes([.fileURL])
  34. }
  35. override func awakeFromNib() {
  36. super.awakeFromNib()
  37. emptyTipLbl.stringValue = NSLocalizedString("Select image file", comment: "")
  38. emptyTipLbl.textColor = .labelColor
  39. dragButton.wantsLayer = true
  40. dragButton.setTitleColor(color: .labelColor, font: nil)
  41. pictureView.wantsLayer = true
  42. emptyImg.image = NSImage(named: "signPicture_nor")
  43. trackingArea = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways], owner: self, userInfo: nil)
  44. if let trackingArea = trackingArea {
  45. addTrackingArea(trackingArea)
  46. }
  47. }
  48. func supportedImageTypes() -> [String] {
  49. return ["jpg", "cur", "bmp", "jpeg", "gif", "png", "tiff", "tif", "ico", "icns", "tga", "psd", "eps", "hdr", "jp2", "jpc", "pict", "sgi", "pdf"]
  50. }
  51. override func draw(_ dirtyRect: NSRect) {
  52. super.draw(dirtyRect)
  53. if let picImage = picImage {
  54. var rect = NSZeroRect
  55. if let imageCIImage = CIImage(data: picImage.tiffRepresentation!) {
  56. let size = imageCIImage.extent.size
  57. let scale = min((dirtyRect.size.width - 30) / size.width, (dirtyRect.size.height - 30) / size.height)
  58. if scale > 1 {
  59. rect = NSRect(x: 0, y: 0, width: size.width, height: size.height)
  60. } else {
  61. rect = NSRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale)
  62. }
  63. picImage.draw(in: NSRect(x: (dirtyRect.size.width - rect.size.width) / 2, y: (dirtyRect.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height), from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
  64. }
  65. }
  66. }
  67. func clearImage() {
  68. picImage = nil
  69. imageURL = nil
  70. pictureView.isHidden = false
  71. emptyTipLbl.isHidden = false
  72. needsDisplay = true
  73. }
  74. func reSelectImage() {
  75. buttonItemClick_SelectPhoto(nil)
  76. }
  77. func signatureImage() -> NSImage? {
  78. var rect = CGRect.zero
  79. guard let picImage = self.picImage else {
  80. return nil
  81. }
  82. let imageCIImage = CIImage(data: picImage.tiffRepresentation!)
  83. let size = imageCIImage?.extent.size ?? CGSize.zero
  84. let scale = min(KMSignatureMaxWidth / size.width, KMSignatureMaxHeight / size.height)
  85. if scale > 1 {
  86. rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
  87. } else {
  88. rect = CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale)
  89. }
  90. let image = NSImage(size: rect.size)
  91. image.lockFocus()
  92. picImage.draw(in: rect, from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
  93. image.unlockFocus()
  94. return image
  95. }
  96. func loadImageViewPath(_ url: URL, isRemoveBGColor: Bool) {
  97. self.imageURL = url
  98. let filePath: NSString = url.path as NSString
  99. if filePath.pathExtension.lowercased() == "pdf" {
  100. if let pdf = PDFDocument(url: url), pdf.isEncrypted {
  101. return
  102. }
  103. }
  104. if let image = NSImage(contentsOfFile: filePath as String) {
  105. if isRemoveBGColor {
  106. if let imageData = image.tiffRepresentation {
  107. if let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
  108. let imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
  109. let imageWidth = image.size.width
  110. let imageHeight = image.size.height
  111. let bytesPerRow = Int(imageWidth) * 4
  112. var rgbImageBuf = [UInt32](repeating: 0, count: Int(bytesPerRow) * Int(imageHeight))
  113. let colorSpace = CGColorSpaceCreateDeviceRGB()
  114. let imageRect = CGRect(x: 0.0, y: 0.0, width: imageWidth, height: imageHeight)
  115. if let context = CGContext(data: &rgbImageBuf,
  116. width: Int(imageWidth),
  117. height: Int(imageHeight),
  118. bitsPerComponent: 8,
  119. bytesPerRow: bytesPerRow,
  120. space: colorSpace,
  121. bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue),
  122. let dataProvider = CGDataProvider(dataInfo: nil,
  123. data: &rgbImageBuf,
  124. size: Int(bytesPerRow) * Int(imageHeight),
  125. releaseData: { (info, data, size) in
  126. data.deallocate()
  127. }),
  128. let newImageRef = CGImage(width: Int(imageWidth),
  129. height: Int(imageHeight),
  130. bitsPerComponent: 8,
  131. bitsPerPixel: 32,
  132. bytesPerRow: bytesPerRow,
  133. space: colorSpace,
  134. bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue),
  135. provider: dataProvider,
  136. decode: nil,
  137. shouldInterpolate: true,
  138. intent: .defaultIntent) {
  139. let newImage = NSImage(size: CGSize(width: imageWidth, height: imageHeight))
  140. newImage.lockFocus()
  141. let imageContext = NSGraphicsContext(cgContext: context, flipped: false)
  142. NSGraphicsContext.current = imageContext
  143. context.draw(imageRef, in: imageRect)
  144. newImage.unlockFocus()
  145. self.picImage = newImage
  146. self.pictureView.isHidden = true
  147. self.emptyTipLbl.isHidden = true
  148. self.setNeedsDisplay(NSRect.zero)
  149. }
  150. }
  151. }
  152. } else {
  153. self.picImage = image
  154. self.pictureView.isHidden = true
  155. self.emptyTipLbl.isHidden = true
  156. self.setNeedsDisplay(NSRect.zero)
  157. }
  158. }
  159. }
  160. func setGradualChanging() -> CAGradientLayer {
  161. let gradientLayer = CAGradientLayer()
  162. gradientLayer.frame = dragButton.bounds
  163. gradientLayer.colors = [NSColor.lightGray.cgColor, NSColor.white.cgColor]
  164. gradientLayer.startPoint = CGPoint(x: 0, y: 0)
  165. gradientLayer.endPoint = CGPoint(x: 1, y: 1)
  166. gradientLayer.locations = [0, 1]
  167. return gradientLayer
  168. }
  169. func isDamageImage(_ image: NSImage, imagePath: String) -> Bool {
  170. let addImageAnnotation = ((try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true))?.appendingPathComponent(Bundle.main.bundleIdentifier ?? ""))?.appendingPathComponent("addImageAnnotation")
  171. if let addImageAnnotation = addImageAnnotation, !FileManager.default.fileExists(atPath: addImageAnnotation.path) {
  172. try? FileManager.default.createDirectory(atPath: addImageAnnotation.path, withIntermediateDirectories: false, attributes: nil)
  173. }
  174. if let imageData = image.tiffRepresentation, let imageRep = NSBitmapImageRep(data: imageData) {
  175. imageRep.size = image.size
  176. var imageData: Data?
  177. if imagePath.lowercased() == "png" {
  178. imageData = imageRep.representation(using: .png, properties: [:])
  179. } else {
  180. imageData = imageRep.representation(using: .jpeg, properties: [:])
  181. }
  182. if let imageData = imageData, let tagString = tagString() {
  183. let rPath = (addImageAnnotation?.appendingPathComponent(tagString))?.appendingPathExtension("png")
  184. if !FileManager.default.createFile(atPath: rPath?.path ?? "", contents: imageData, attributes: nil) {
  185. return true
  186. } else {
  187. return false
  188. }
  189. }
  190. }
  191. return false
  192. }
  193. func tagString() -> String? {
  194. let dateFormatter = DateFormatter()
  195. dateFormatter.dateFormat = "yyMMddHHmmss"
  196. return String(format: "%@%04d", dateFormatter.string(from: Date()), Int(arc4random_uniform(10000)))
  197. }
  198. @IBAction func buttonItemClick_SelectPhoto(_ sender: Any?) {
  199. let openPanel = NSOpenPanel()
  200. openPanel.allowedFileTypes = supportedImageTypes()
  201. if let accessoryView = openPanel.accessoryView as? NSButton {
  202. accessoryView.state = UserDefaults.standard.bool(forKey: "AnnotationSelectPhoto") ? .on : .off
  203. }
  204. openPanel.allowsMultipleSelection = false
  205. openPanel.beginSheetModal(for: window ?? NSApp.mainWindow!) { [weak self] (result) in
  206. if result == .OK, let url = openPanel.url {
  207. // Check if the PDF file is damaged
  208. let filePath: NSString = url.path as NSString
  209. if filePath.pathExtension.lowercased() == "pdf" {
  210. if let pdf = PDFDocument(url: url), !pdf.isEncrypted {
  211. self?.loadImageViewPath(url, isRemoveBGColor: self?.clearBackground ?? false)
  212. self?.changeSignatureImageCallback?(true)
  213. return
  214. } else {
  215. let alert = NSAlert()
  216. alert.alertStyle = .critical
  217. alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "")
  218. alert.runModal()
  219. return
  220. }
  221. }
  222. if let image = NSImage(contentsOfFile: url.path), self?.isDamageImage(image, imagePath: url.path) == true {
  223. let alert = NSAlert()
  224. alert.alertStyle = .critical
  225. alert.messageText = String(format: NSLocalizedString("The file \"%@\" could not be opened.", comment: ""), url.path.lastPathComponent)
  226. alert.informativeText = NSLocalizedString("It may be damaged or use a file format that PDF Reader Pro doesn’t recognize.", comment: "")
  227. alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
  228. alert.beginSheetModal(for: NSApp.mainWindow ?? NSApp.keyWindow!) { (returnCode) in
  229. if returnCode == .alertFirstButtonReturn {
  230. }
  231. }
  232. return
  233. }
  234. self?.loadImageViewPath(url, isRemoveBGColor: self?.clearBackground ?? false)
  235. self?.changeSignatureImageCallback?(true)
  236. }
  237. }
  238. }
  239. override func mouseEntered(with event: NSEvent) {
  240. super.mouseEntered(with: event)
  241. emptyImg.image = NSImage(named: "signPicture_hover")
  242. }
  243. override func mouseMoved(with event: NSEvent) {
  244. super.mouseMoved(with: event)
  245. if let windowContentView = window?.contentView, let convertPoint = pictureView?.convert(event.locationInWindow, from: windowContentView) {
  246. if pictureView.bounds.contains(convertPoint) {
  247. emptyImg.image = NSImage(named: "signPicture_hover")
  248. } else {
  249. emptyImg.image = NSImage(named: "signPicture_nor")
  250. }
  251. }
  252. }
  253. override func mouseExited(with event: NSEvent) {
  254. super.mouseExited(with: event)
  255. emptyImg.image = NSImage(named: "signPicture_nor")
  256. }
  257. override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
  258. let pboard = sender.draggingPasteboard
  259. if pboard.availableType(from: [.fileURL]) != nil {
  260. if let fileNames = pboard.propertyList(forType: .fileURL) as? [String], fileNames.count == 1 {
  261. if let path: NSString = fileNames.first as NSString?, supportedImageTypes().contains(path.pathExtension.lowercased()) {
  262. return .every
  263. }
  264. }
  265. }
  266. return []
  267. }
  268. override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
  269. let pboard = sender.draggingPasteboard
  270. if pboard.availableType(from: [.fileURL]) != nil {
  271. if let fileNames = pboard.propertyList(forType: .fileURL) as? [String], fileNames.count == 1 {
  272. if let path: NSString = fileNames.first as NSString?, supportedImageTypes().contains(path.pathExtension.lowercased()) {
  273. loadImageViewPath(URL(fileURLWithPath: path as String), isRemoveBGColor: clearBackground)
  274. changeSignatureImageCallback?(true)
  275. return true
  276. }
  277. }
  278. }
  279. return false
  280. }
  281. }
  282. private extension NSImage {
  283. func resized(to size: NSSize) -> NSImage {
  284. let image = NSImage(size: size)
  285. image.lockFocus()
  286. let ctx = NSGraphicsContext.current?.cgContext
  287. ctx?.interpolationQuality = .high
  288. draw(in: NSRect(origin: .zero, size: size), from: NSRect(origin: .zero, size: self.size), operation: .sourceOver, fraction: 1.0)
  289. image.unlockFocus()
  290. return image
  291. }
  292. }