KMImageToolTipContext.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. //
  2. // KMImageToolTipContext.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2023/11/23.
  6. //
  7. import Foundation
  8. let TEXT_MARGIN_X = 2.0
  9. let TEXT_MARGIN_Y = 2.0
  10. let SKToolTipWidthKey = "SKToolTipWidth"
  11. let SKToolTipHeightKey = "SKToolTipHeight"
  12. @objc protocol KMImageToolTipContext: NSObjectProtocol {
  13. func toolTipImage() -> NSImage?
  14. }
  15. extension NSAttributedString: KMImageToolTipContext {
  16. // func toolTipImage() -> NSImage? {
  17. // return nil
  18. // }
  19. func toolTipImage() -> NSImage? {
  20. let backgroundColor: NSColor = {
  21. if #available(OSX 10.9, *) {
  22. return NSColor(calibratedRed: 0.95, green: 0.95, blue: 0.95, alpha: 1.0)
  23. } else {
  24. return NSColor(calibratedRed: 1.0, green: 1.0, blue: 0.75, alpha: 1.0)
  25. }
  26. }()
  27. let width = UserDefaults.standard.double(forKey: SKToolTipWidthKey) - 2.0 * TEXT_MARGIN_X
  28. let height = UserDefaults.standard.double(forKey: SKToolTipHeightKey) - 2.0 * TEXT_MARGIN_Y
  29. var textRect = self.boundingRect(with: NSSize(width: width, height: height), options: .usesLineFragmentOrigin)
  30. textRect.origin = NSPoint(x: TEXT_MARGIN_X, y: TEXT_MARGIN_Y)
  31. textRect.size.height = min(textRect.height, height)
  32. textRect = NSInsetRect(NSIntegralRect(textRect), -TEXT_MARGIN_X, -TEXT_MARGIN_Y)
  33. let image = NSImage(size: textRect.size, flipped: false) { rect in
  34. backgroundColor.setFill()
  35. rect.fill()
  36. self.draw(with: NSRect(x: TEXT_MARGIN_X, y: TEXT_MARGIN_Y, width: rect.width - 2 * TEXT_MARGIN_X, height: rect.height - 2 * TEXT_MARGIN_Y), options: .usesLineFragmentOrigin)
  37. return true
  38. }
  39. return image
  40. }
  41. }
  42. var _km_dest_labelAttributes: [NSAttributedString.Key : Any]?
  43. var _km_dest_labelColor: NSColor?
  44. var _km_text_margin_x: CGFloat = 2
  45. var _km_text_margin_y: CGFloat = 2
  46. extension CPDFDestination: KMImageToolTipContext {
  47. func toolTipImage() -> NSImage? {
  48. return self._toolTipImage(offset: NSMakePoint(-50.0, 20.0))
  49. }
  50. @objc internal func _toolTipImage(offset: NSPoint) -> NSImage? {
  51. guard let page = self.page() else {
  52. return nil
  53. }
  54. if _km_dest_labelAttributes == nil {
  55. let style = NSMutableParagraphStyle()
  56. style.lineBreakMode = .byClipping
  57. _km_dest_labelAttributes = [.font : NSFont.boldSystemFont(ofSize: 11), .foregroundColor : NSColor.white, .paragraphStyle : style]
  58. }
  59. let attri = _km_dest_labelAttributes ?? [:]
  60. if _km_dest_labelColor == nil {
  61. _km_dest_labelColor = NSColor(calibratedWhite: 0.5, alpha: 0.8)
  62. }
  63. let labelColor = _km_dest_labelColor ?? NSColor(calibratedWhite: 0.5, alpha: 0.8)
  64. // NSImage *pageImage = [page thumbnailWithSize:0.0 forBox:kPDFDisplayBoxCropBox shadowBlurRadius:0.0 readingBar:nil];
  65. let pageImage = page.thumbnail(of: page.bounds(for: .cropBox).size)
  66. let pageSize = page.bounds(for: .cropBox)
  67. let pageImageRect = NSMakeRect(0, 0, pageSize.width, pageSize.height)
  68. let bounds = page.bounds(for: .cropBox)
  69. var sourceRect: NSRect = .zero
  70. let selection = page.selection(for: bounds)
  71. let transform = page.km_affineTransform(for: .cropBox)
  72. // sourceRect.size.width = [[NSUserDefaults standardUserDefaults] doubleForKey:SKToolTipWidthKey];
  73. sourceRect.size.width = 400
  74. // sourceRect.size.height = [[NSUserDefaults standardUserDefaults] doubleForKey:SKToolTipHeightKey];
  75. sourceRect.size.height = 120
  76. sourceRect.origin = KMAddPoints(transform.transform(self.point), offset)
  77. sourceRect.origin.y -= NSHeight(sourceRect)
  78. if let data = selection?.hasCharacters(), data {
  79. var selBounds = selection?.bounds(for: page) ?? .zero
  80. let blPoint = KMBottomLeftPoint(selBounds)
  81. let trPoint = KMTopRightPoint(selBounds)
  82. selBounds = KMRectFromPoints(aPoint: blPoint, bPoint: trPoint)
  83. let top = ceil(fmax(NSMaxY(selBounds), NSMinY(selBounds) + NSHeight(sourceRect)))
  84. let left = floor(fmin(NSMinX(selBounds), NSMaxX(selBounds) - NSWidth(sourceRect)))
  85. if (top < NSMaxY(sourceRect)) {
  86. sourceRect.origin.y = top - NSHeight(sourceRect)
  87. }
  88. if (left > NSMinX(sourceRect)) {
  89. sourceRect.origin.x = left
  90. }
  91. }
  92. sourceRect = KMConstrainRect(rect: sourceRect, boundary: pageImageRect)
  93. let labelString = NSAttributedString(string: String(format: KMLocalizedString("Page %@"), page.km_displayLabel), attributes: attri)
  94. var labelRect = labelString.boundingRect(with: .zero, options: [.usesLineFragmentOrigin])
  95. labelRect.size.width = floor(NSWidth(labelRect))
  96. labelRect.size.height = 2.0 * floor(0.5 * NSHeight(labelRect)) // make sure the cap radius is integral
  97. labelRect.origin.x = NSWidth(sourceRect) - NSWidth(labelRect) - 0.5 * NSHeight(labelRect) - _km_text_margin_x
  98. labelRect.origin.y = _km_text_margin_y
  99. labelRect = NSIntegralRect(labelRect)
  100. let image = NSImage.bitmapImage(with: sourceRect.size) { rect in
  101. pageImage?.draw(in: rect, from: sourceRect, operation: .copy, fraction: 1)
  102. let radius = 0.5 * NSHeight(labelRect)
  103. let path = NSBezierPath()
  104. path.move(to: KMTopLeftPoint(labelRect))
  105. path.appendArc(withCenter: NSMakePoint(NSMinX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: 90.0, endAngle: 270.0)
  106. path.appendArc(withCenter: NSMakePoint(NSMaxX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: -90, endAngle: 90)
  107. path.close()
  108. labelColor.setFill()
  109. path.fill()
  110. labelString.draw(with: labelRect, options: [.usesLineFragmentOrigin])
  111. }
  112. return image
  113. }
  114. }
  115. extension CPDFAnnotation: KMImageToolTipContext {
  116. func toolTipImage() -> NSImage? {
  117. if self.isLink() {
  118. if let destination: CPDFDestination = self.linkDestination() {
  119. var image = destination._toolTipImage(offset: .zero)
  120. if image == nil{
  121. let url = self.linkURL()
  122. if url != nil{
  123. let attrString = toolTipAttributedString(url?.absoluteString ?? "")
  124. if attrString.length > 0 {
  125. image = attrString.toolTipImage()
  126. }
  127. }
  128. }
  129. if image != nil {
  130. return image
  131. }
  132. }else{
  133. return nil
  134. }
  135. }
  136. var attrString = self.text()
  137. var string = attrString?.string
  138. var i = 0
  139. var l = string?.count
  140. if l == 0 || ((string?.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines)) == nil) {
  141. string = self.string()
  142. l = string?.count
  143. if l ?? 0 > 0 {
  144. attrString = toolTipAttributedString(string ?? "")
  145. }else {
  146. attrString = nil
  147. }
  148. }
  149. attrString = KMOCTool.transformAttr(attrString ?? NSAttributedString(string: ""), with: string ?? "")
  150. if attrString?.length ?? 0 > 0 {
  151. return attrString?.toolTipImage()
  152. }else{
  153. return nil
  154. }
  155. }
  156. }
  157. func toolTipAttributedString(_ string: String) -> NSAttributedString {
  158. var attributes: [NSAttributedString.Key: Any]? = nil
  159. if attributes == nil {
  160. attributes = [NSAttributedString.Key.font: NSFont.toolTipsFont(ofSize: 11.0), NSAttributedString.Key.paragraphStyle: NSParagraphStyle.defaultClippingParagraphStyle]
  161. }
  162. return NSAttributedString(string: string, attributes: attributes)
  163. }
  164. extension CPDFPage: KMImageToolTipContext {
  165. func toolTipImage() -> NSImage? {
  166. return nil
  167. }
  168. }
  169. extension CPDFBookmark: KMImageToolTipContext {
  170. func toolTipImage() -> NSImage? {
  171. guard let page = self.document?.page(at: UInt(self.pageIndex)) else {
  172. return nil
  173. }
  174. let image = page.thumbnail(of: page.size)
  175. image?.size = .init(width: 164, height: 224)
  176. let attri = _km_dest_labelAttributes ?? [:]
  177. let sourceRect = NSMakeRect(0, 0, 164, 224)
  178. let labelString = NSAttributedString(string: String(format: KMLocalizedString("Page %@"), page.km_displayLabel), attributes: attri)
  179. var labelRect = labelString.boundingRect(with: .zero, options: [.usesLineFragmentOrigin])
  180. labelRect.size.width = floor(NSWidth(labelRect)) + 10
  181. // labelRect.size.height = 2.0 * floor(0.5 * NSHeight(labelRect)) // make sure the cap radius is integral
  182. labelRect.size.height = 20
  183. // labelRect.origin.x = NSWidth(sourceRect) - NSWidth(labelRect) - 0.5 * NSHeight(labelRect) - _km_text_margin_x
  184. labelRect.origin.x = 8
  185. labelRect.origin.y = 8
  186. labelRect = NSIntegralRect(labelRect)
  187. let labelColor = _km_dest_labelColor ?? NSColor(calibratedWhite: 0.5, alpha: 0.8)
  188. let kimage = NSImage.bitmapImage(with: sourceRect.size) { rect in
  189. image?.draw(in: rect, from: sourceRect, operation: .copy, fraction: 1)
  190. let radius = 0.5 * NSHeight(labelRect)
  191. let path = NSBezierPath()
  192. path.move(to: KMTopLeftPoint(labelRect))
  193. path.appendArc(withCenter: NSMakePoint(NSMinX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: 90.0, endAngle: 270.0)
  194. path.appendArc(withCenter: NSMakePoint(NSMaxX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: -90, endAngle: 90)
  195. path.close()
  196. labelColor.setFill()
  197. path.fill()
  198. labelString.draw(with: labelRect.insetBy(dx: 0, dy: 2), options: [.usesLineFragmentOrigin])
  199. }
  200. return kimage
  201. }
  202. }
  203. extension NSParagraphStyle {
  204. static var defaultClippingParagraphStyle: NSParagraphStyle = {
  205. let tmpParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
  206. tmpParagraphStyle.lineBreakMode = .byClipping
  207. return tmpParagraphStyle.copy() as! NSParagraphStyle
  208. }()
  209. static var defaultTruncatingTailParagraphStyle: NSParagraphStyle = {
  210. let tmpParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
  211. tmpParagraphStyle.lineBreakMode = .byTruncatingTail
  212. return tmpParagraphStyle.copy() as! NSParagraphStyle
  213. }()
  214. }
  215. //extension NSParagraphStyle{
  216. // static var defaultClippingParagraphStyle: NSParagraphStyle {
  217. // var style: NSParagraphStyle? = nil
  218. // if style == nil {
  219. // let tmpParagraphStyle = (NSParagraphStyle.default as! NSMutableParagraphStyle).mutableCopy() as! NSMutableParagraphStyle
  220. // tmpParagraphStyle.lineBreakMode = .byClipping
  221. // style = tmpParagraphStyle.copy() as? NSParagraphStyle
  222. // }
  223. // return style!
  224. // }
  225. //
  226. // static var defaultTruncatingTailParagraphStyle: NSParagraphStyle {
  227. // var style: NSParagraphStyle? = nil
  228. // if style == nil {
  229. // let tmpParagraphStyle = (NSParagraphStyle.default as! NSMutableParagraphStyle).mutableCopy() as! NSMutableParagraphStyle
  230. // tmpParagraphStyle.lineBreakMode = .byTruncatingTail
  231. // style = tmpParagraphStyle.copy() as? NSParagraphStyle
  232. // }
  233. // return style!
  234. // }
  235. //}