// // KMImageToolTipContext.swift // PDF Reader Pro // // Created by tangchao on 2023/11/23. // import Foundation let TEXT_MARGIN_X = 2.0 let TEXT_MARGIN_Y = 2.0 let SKToolTipWidthKey = "SKToolTipWidth" let SKToolTipHeightKey = "SKToolTipHeight" @objc protocol KMImageToolTipContext: NSObjectProtocol { func toolTipImage() -> NSImage? } extension NSAttributedString: KMImageToolTipContext { // func toolTipImage() -> NSImage? { // return nil // } func toolTipImage() -> NSImage? { let backgroundColor: NSColor = { if #available(OSX 10.9, *) { return NSColor(calibratedRed: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) } else { return NSColor(calibratedRed: 1.0, green: 1.0, blue: 0.75, alpha: 1.0) } }() let width = UserDefaults.standard.double(forKey: SKToolTipWidthKey) - 2.0 * TEXT_MARGIN_X let height = UserDefaults.standard.double(forKey: SKToolTipHeightKey) - 2.0 * TEXT_MARGIN_Y var textRect = self.boundingRect(with: NSSize(width: width, height: height), options: .usesLineFragmentOrigin) textRect.origin = NSPoint(x: TEXT_MARGIN_X, y: TEXT_MARGIN_Y) textRect.size.height = min(textRect.height, height) textRect = NSInsetRect(NSIntegralRect(textRect), -TEXT_MARGIN_X, -TEXT_MARGIN_Y) let image = NSImage(size: textRect.size, flipped: false) { rect in backgroundColor.setFill() rect.fill() 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) return true } return image } } var _km_dest_labelAttributes: [NSAttributedString.Key : Any]? var _km_dest_labelColor: NSColor? var _km_text_margin_x: CGFloat = 2 var _km_text_margin_y: CGFloat = 2 extension CPDFDestination: KMImageToolTipContext { func toolTipImage() -> NSImage? { return self._toolTipImage(offset: NSMakePoint(-50.0, 20.0)) } @objc internal func _toolTipImage(offset: NSPoint) -> NSImage? { guard let page = self.page() else { return nil } if _km_dest_labelAttributes == nil { let style = NSMutableParagraphStyle() style.lineBreakMode = .byClipping _km_dest_labelAttributes = [.font : NSFont.boldSystemFont(ofSize: 11), .foregroundColor : NSColor.white, .paragraphStyle : style] } let attri = _km_dest_labelAttributes ?? [:] if _km_dest_labelColor == nil { _km_dest_labelColor = NSColor(calibratedWhite: 0.5, alpha: 0.8) } let labelColor = _km_dest_labelColor ?? NSColor(calibratedWhite: 0.5, alpha: 0.8) // NSImage *pageImage = [page thumbnailWithSize:0.0 forBox:kPDFDisplayBoxCropBox shadowBlurRadius:0.0 readingBar:nil]; let pageImage = page.thumbnail(of: page.bounds(for: .cropBox).size) let pageSize = page.bounds(for: .cropBox) let pageImageRect = NSMakeRect(0, 0, pageSize.width, pageSize.height) let bounds = page.bounds(for: .cropBox) var sourceRect: NSRect = .zero let selection = page.selection(for: bounds) let transform = page.km_affineTransform(for: .cropBox) // sourceRect.size.width = [[NSUserDefaults standardUserDefaults] doubleForKey:SKToolTipWidthKey]; sourceRect.size.width = 400 // sourceRect.size.height = [[NSUserDefaults standardUserDefaults] doubleForKey:SKToolTipHeightKey]; sourceRect.size.height = 120 sourceRect.origin = KMAddPoints(transform.transform(self.point), offset) sourceRect.origin.y -= NSHeight(sourceRect) if let data = selection?.hasCharacters(), data { var selBounds = selection?.bounds(for: page) ?? .zero let blPoint = KMBottomLeftPoint(selBounds) let trPoint = KMTopRightPoint(selBounds) selBounds = KMRectFromPoints(aPoint: blPoint, bPoint: trPoint) let top = ceil(fmax(NSMaxY(selBounds), NSMinY(selBounds) + NSHeight(sourceRect))) let left = floor(fmin(NSMinX(selBounds), NSMaxX(selBounds) - NSWidth(sourceRect))) if (top < NSMaxY(sourceRect)) { sourceRect.origin.y = top - NSHeight(sourceRect) } if (left > NSMinX(sourceRect)) { sourceRect.origin.x = left } } sourceRect = KMConstrainRect(rect: sourceRect, boundary: pageImageRect) let labelString = NSAttributedString(string: String(format: KMLocalizedString("Page %@"), page.km_displayLabel), attributes: attri) var labelRect = labelString.boundingRect(with: .zero, options: [.usesLineFragmentOrigin]) labelRect.size.width = floor(NSWidth(labelRect)) labelRect.size.height = 2.0 * floor(0.5 * NSHeight(labelRect)) // make sure the cap radius is integral labelRect.origin.x = NSWidth(sourceRect) - NSWidth(labelRect) - 0.5 * NSHeight(labelRect) - _km_text_margin_x labelRect.origin.y = _km_text_margin_y labelRect = NSIntegralRect(labelRect) let image = NSImage.bitmapImage(with: sourceRect.size) { rect in pageImage?.draw(in: rect, from: sourceRect, operation: .copy, fraction: 1) let radius = 0.5 * NSHeight(labelRect) let path = NSBezierPath() path.move(to: KMTopLeftPoint(labelRect)) path.appendArc(withCenter: NSMakePoint(NSMinX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: 90.0, endAngle: 270.0) path.appendArc(withCenter: NSMakePoint(NSMaxX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: -90, endAngle: 90) path.close() labelColor.setFill() path.fill() labelString.draw(with: labelRect, options: [.usesLineFragmentOrigin]) } return image } } extension CPDFAnnotation: KMImageToolTipContext { func toolTipImage() -> NSImage? { if self.isLink() { if let destination: CPDFDestination = self.linkDestination() { var image = destination._toolTipImage(offset: .zero) if image == nil{ let url = self.linkURL() if url != nil{ let attrString = toolTipAttributedString(url?.absoluteString ?? "") if attrString.length > 0 { image = attrString.toolTipImage() } } } if image != nil { return image } }else{ return nil } } var attrString = self.text() var string = attrString?.string var i = 0 var l = string?.count if l == 0 || ((string?.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines)) == nil) { string = self.string() l = string?.count if l ?? 0 > 0 { attrString = toolTipAttributedString(string ?? "") }else { attrString = nil } } attrString = KMOCTool.transformAttr(attrString ?? NSAttributedString(string: ""), with: string ?? "") if attrString?.length ?? 0 > 0 { return attrString?.toolTipImage() }else{ return nil } } } func toolTipAttributedString(_ string: String) -> NSAttributedString { var attributes: [NSAttributedString.Key: Any]? = nil if attributes == nil { attributes = [NSAttributedString.Key.font: NSFont.toolTipsFont(ofSize: 11.0), NSAttributedString.Key.paragraphStyle: NSParagraphStyle.defaultClippingParagraphStyle] } return NSAttributedString(string: string, attributes: attributes) } extension CPDFPage: KMImageToolTipContext { func toolTipImage() -> NSImage? { return nil } } extension CPDFBookmark: KMImageToolTipContext { func toolTipImage() -> NSImage? { guard let page = self.document?.page(at: UInt(self.pageIndex)) else { return nil } let image = page.thumbnail(of: page.size) image?.size = .init(width: 164, height: 224) let attri = _km_dest_labelAttributes ?? [:] let sourceRect = NSMakeRect(0, 0, 164, 224) let labelString = NSAttributedString(string: String(format: KMLocalizedString("Page %@"), page.km_displayLabel), attributes: attri) var labelRect = labelString.boundingRect(with: .zero, options: [.usesLineFragmentOrigin]) labelRect.size.width = floor(NSWidth(labelRect)) + 10 // labelRect.size.height = 2.0 * floor(0.5 * NSHeight(labelRect)) // make sure the cap radius is integral labelRect.size.height = 20 // labelRect.origin.x = NSWidth(sourceRect) - NSWidth(labelRect) - 0.5 * NSHeight(labelRect) - _km_text_margin_x labelRect.origin.x = 8 labelRect.origin.y = 8 labelRect = NSIntegralRect(labelRect) let labelColor = _km_dest_labelColor ?? NSColor(calibratedWhite: 0.5, alpha: 0.8) let kimage = NSImage.bitmapImage(with: sourceRect.size) { rect in image?.draw(in: rect, from: sourceRect, operation: .copy, fraction: 1) let radius = 0.5 * NSHeight(labelRect) let path = NSBezierPath() path.move(to: KMTopLeftPoint(labelRect)) path.appendArc(withCenter: NSMakePoint(NSMinX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: 90.0, endAngle: 270.0) path.appendArc(withCenter: NSMakePoint(NSMaxX(labelRect), NSMidY(labelRect)), radius: radius, startAngle: -90, endAngle: 90) path.close() labelColor.setFill() path.fill() labelString.draw(with: labelRect.insetBy(dx: 0, dy: 2), options: [.usesLineFragmentOrigin]) } return kimage } } extension NSParagraphStyle { static var defaultClippingParagraphStyle: NSParagraphStyle = { let tmpParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle tmpParagraphStyle.lineBreakMode = .byClipping return tmpParagraphStyle.copy() as! NSParagraphStyle }() static var defaultTruncatingTailParagraphStyle: NSParagraphStyle = { let tmpParagraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle tmpParagraphStyle.lineBreakMode = .byTruncatingTail return tmpParagraphStyle.copy() as! NSParagraphStyle }() } //extension NSParagraphStyle{ // static var defaultClippingParagraphStyle: NSParagraphStyle { // var style: NSParagraphStyle? = nil // if style == nil { // let tmpParagraphStyle = (NSParagraphStyle.default as! NSMutableParagraphStyle).mutableCopy() as! NSMutableParagraphStyle // tmpParagraphStyle.lineBreakMode = .byClipping // style = tmpParagraphStyle.copy() as? NSParagraphStyle // } // return style! // } // // static var defaultTruncatingTailParagraphStyle: NSParagraphStyle { // var style: NSParagraphStyle? = nil // if style == nil { // let tmpParagraphStyle = (NSParagraphStyle.default as! NSMutableParagraphStyle).mutableCopy() as! NSMutableParagraphStyle // tmpParagraphStyle.lineBreakMode = .byTruncatingTail // style = tmpParagraphStyle.copy() as? NSParagraphStyle // } // return style! // } //}