| /* |
| * Copyright (C) 2007, 2009, 2012-2020 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "DragImage.h" |
| |
| #if ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) |
| |
| #import "BitmapImage.h" |
| #import "ColorMac.h" |
| #import "Element.h" |
| #import "FloatRoundedRect.h" |
| #import "FontCascade.h" |
| #import "FontDescription.h" |
| #import "FontSelector.h" |
| #import "GraphicsContext.h" |
| #import "Image.h" |
| #import "LocalDefaultSystemAppearance.h" |
| #import "Page.h" |
| #import "StringTruncator.h" |
| #import "TextIndicator.h" |
| #import "WebKitNSImageExtras.h" |
| #import <pal/spi/cg/CoreGraphicsSPI.h> |
| #import <pal/spi/cocoa/CoreTextSPI.h> |
| #import <pal/spi/cocoa/URLFormattingSPI.h> |
| #import <wtf/SoftLinking.h> |
| #import <wtf/URL.h> |
| |
| #if !HAVE(URL_FORMATTING) |
| SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(LinkPresentation) |
| #endif |
| |
| namespace WebCore { |
| |
| IntSize dragImageSize(RetainPtr<NSImage> image) |
| { |
| return (IntSize)[image size]; |
| } |
| |
| void deleteDragImage(RetainPtr<NSImage>) |
| { |
| // Since this is a RetainPtr, there's nothing additional we need to do to |
| // delete it. It will be released when it falls out of scope. |
| } |
| |
| RetainPtr<NSImage> scaleDragImage(RetainPtr<NSImage> image, FloatSize scale) |
| { |
| NSSize originalSize = [image size]; |
| NSSize newSize = NSMakeSize((originalSize.width * scale.width()), (originalSize.height * scale.height())); |
| newSize.width = roundf(newSize.width); |
| newSize.height = roundf(newSize.height); |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| [image setScalesWhenResized:YES]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| [image setSize:newSize]; |
| return image; |
| } |
| |
| RetainPtr<NSImage> dissolveDragImageToFraction(RetainPtr<NSImage> image, float delta) |
| { |
| if (!image) |
| return nil; |
| |
| RetainPtr<NSImage> dissolvedImage = adoptNS([[NSImage alloc] initWithSize:[image size]]); |
| |
| [dissolvedImage lockFocus]; |
| [image drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, [image size].width, [image size].height) operation:NSCompositingOperationCopy fraction:delta]; |
| [dissolvedImage unlockFocus]; |
| |
| return dissolvedImage; |
| } |
| |
| RetainPtr<NSImage> createDragImageFromImage(Image* image, ImageOrientation orientation) |
| { |
| if (is<BitmapImage>(*image)) { |
| BitmapImage& bitmapImage = downcast<BitmapImage>(*image); |
| |
| if (orientation == ImageOrientation::FromImage) |
| orientation = bitmapImage.orientationForCurrentFrame(); |
| |
| if (orientation != ImageOrientation::None) { |
| // Construct a correctly-rotated copy of the image to use as the drag image. |
| FloatSize imageSize = image->size(orientation); |
| RetainPtr<NSImage> rotatedDragImage = adoptNS([[NSImage alloc] initWithSize:(NSSize)(imageSize)]); |
| [rotatedDragImage lockFocus]; |
| |
| // ImageOrientation uses top-left coordinates, need to flip to bottom-left, apply... |
| CGAffineTransform transform = CGAffineTransformMakeTranslation(0, imageSize.height()); |
| transform = CGAffineTransformScale(transform, 1, -1); |
| transform = CGAffineTransformConcat(orientation.transformFromDefault(imageSize), transform); |
| |
| if (orientation.usesWidthAsHeight()) |
| imageSize = imageSize.transposedSize(); |
| |
| // ...and flip back. |
| transform = CGAffineTransformTranslate(transform, 0, imageSize.height()); |
| transform = CGAffineTransformScale(transform, 1, -1); |
| |
| RetainPtr<NSAffineTransform> cocoaTransform = adoptNS([[NSAffineTransform alloc] init]); |
| [cocoaTransform setTransformStruct:*(NSAffineTransformStruct*)&transform]; |
| [cocoaTransform concat]; |
| |
| FloatRect imageRect(FloatPoint(), imageSize); |
| [image->snapshotNSImage() drawInRect:imageRect fromRect:imageRect operation:NSCompositingOperationSourceOver fraction:1.0]; |
| |
| [rotatedDragImage unlockFocus]; |
| |
| return rotatedDragImage; |
| } |
| } |
| |
| FloatSize imageSize = image->size(); |
| auto dragImage = image->snapshotNSImage(); |
| [dragImage setSize:(NSSize)imageSize]; |
| return dragImage; |
| } |
| |
| RetainPtr<NSImage> createDragImageIconForCachedImageFilename(const String& filename) |
| { |
| NSString *extension = nil; |
| size_t dotIndex = filename.reverseFind('.'); |
| |
| if (dotIndex != notFound && dotIndex < (filename.length() - 1)) // require that a . exists after the first character and before the last |
| extension = filename.substring(dotIndex + 1); |
| else { |
| // It might be worth doing a further lookup to pull the extension from the MIME type. |
| extension = @""; |
| } |
| |
| return [[NSWorkspace sharedWorkspace] iconForFileType:extension]; |
| } |
| |
| const CGFloat linkImagePadding = 10; |
| const CGFloat linkImageDomainBaselineToTitleBaseline = 18; |
| const CGFloat linkImageCornerRadius = 5; |
| const CGFloat linkImageMaximumWidth = 400; |
| const CGFloat linkImageFontSize = 11; |
| const CFIndex linkImageTitleMaximumLineCount = 2; |
| const int linkImageShadowRadius = 0; |
| const int linkImageShadowOffsetY = 0; |
| const int linkImageDragCornerOutsetX = 6 - linkImageShadowRadius; |
| const int linkImageDragCornerOutsetY = 10 - linkImageShadowRadius + linkImageShadowOffsetY; |
| |
| IntPoint dragOffsetForLinkDragImage(DragImageRef dragImage) |
| { |
| IntSize size = dragImageSize(dragImage); |
| return { linkImageDragCornerOutsetX, size.height() + linkImageDragCornerOutsetY }; |
| } |
| |
| FloatPoint anchorPointForLinkDragImage(DragImageRef dragImage) |
| { |
| IntSize size = dragImageSize(dragImage); |
| return { -static_cast<float>(linkImageDragCornerOutsetX) / size.width(), -static_cast<float>(linkImageDragCornerOutsetY) / size.height() }; |
| } |
| |
| struct LinkImageLayout { |
| LinkImageLayout(URL&, const String& title); |
| |
| struct Label { |
| FloatPoint origin; |
| RetainPtr<CTFrameRef> frame; |
| }; |
| Vector<Label> labels; |
| |
| FloatRect boundingRect; |
| }; |
| |
| LinkImageLayout::LinkImageLayout(URL& url, const String& titleString) |
| { |
| NSString *title = nsStringNilIfEmpty(titleString); |
| NSURL *cocoaURL = url; |
| NSString *absoluteURLString = [cocoaURL absoluteString]; |
| |
| NSString *domain = absoluteURLString; |
| #if HAVE(URL_FORMATTING) |
| domain = [cocoaURL _lp_simplifiedDisplayString]; |
| #else |
| if (LinkPresentationLibrary()) |
| domain = [cocoaURL _lp_simplifiedDisplayString]; |
| #endif |
| |
| if ([title isEqualToString:absoluteURLString]) |
| title = nil; |
| |
| NSFont *titleFont = [NSFont boldSystemFontOfSize:linkImageFontSize]; |
| NSFont *domainFont = [NSFont systemFontOfSize:linkImageFontSize]; |
| |
| NSColor *titleColor = [NSColor labelColor]; |
| NSColor *domainColor = [NSColor secondaryLabelColor]; |
| |
| CGFloat maximumAvailableWidth = linkImageMaximumWidth - linkImagePadding * 2; |
| |
| CGFloat currentY = linkImagePadding; |
| CGFloat maximumUsedTextWidth = 0; |
| |
| auto buildLines = [this, maximumAvailableWidth, &maximumUsedTextWidth, ¤tY] (NSString *text, NSColor *color, NSFont *font, CFIndex maximumLines, CTLineBreakMode lineBreakMode) { |
| CTParagraphStyleSetting paragraphStyleSettings[1]; |
| paragraphStyleSettings[0].spec = kCTParagraphStyleSpecifierLineBreakMode; |
| paragraphStyleSettings[0].valueSize = sizeof(CTLineBreakMode); |
| paragraphStyleSettings[0].value = &lineBreakMode; |
| RetainPtr<CTParagraphStyleRef> paragraphStyle = adoptCF(CTParagraphStyleCreate(paragraphStyleSettings, 1)); |
| |
| NSDictionary *textAttributes = @{ |
| (id)kCTFontAttributeName: font, |
| (id)kCTForegroundColorAttributeName: color, |
| (id)kCTParagraphStyleAttributeName: (id)paragraphStyle.get() |
| }; |
| NSDictionary *frameAttributes = @{ |
| (id)kCTFrameMaximumNumberOfLinesAttributeName: @(maximumLines) |
| }; |
| |
| RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]); |
| RetainPtr<CTFramesetterRef> textFramesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText.get())); |
| |
| CFRange fitRange; |
| CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(textFramesetter.get(), CFRangeMake(0, 0), (CFDictionaryRef)frameAttributes, CGSizeMake(maximumAvailableWidth, CGFLOAT_MAX), &fitRange); |
| |
| RetainPtr<CGPathRef> textPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, textSize.width, textSize.height), nullptr)); |
| RetainPtr<CTFrameRef> textFrame = adoptCF(CTFramesetterCreateFrame(textFramesetter.get(), fitRange, textPath.get(), (CFDictionaryRef)frameAttributes)); |
| |
| CFArrayRef ctLines = CTFrameGetLines(textFrame.get()); |
| CFIndex lineCount = CFArrayGetCount(ctLines); |
| if (!lineCount) |
| return; |
| |
| Vector<CGPoint> origins(lineCount); |
| CGRect lineBounds; |
| CGFloat height = 0; |
| CTFrameGetLineOrigins(textFrame.get(), CFRangeMake(0, 0), origins.data()); |
| for (CFIndex lineIndex = 0; lineIndex < lineCount; ++lineIndex) { |
| CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex); |
| |
| lineBounds = CTLineGetBoundsWithOptions(line, 0); |
| CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line); |
| CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth; |
| maximumUsedTextWidth = std::max(maximumUsedTextWidth, lineWidthIgnoringTrailingWhitespace); |
| |
| if (lineIndex) |
| height += origins[lineIndex - 1].y - origins[lineIndex].y; |
| } |
| |
| LinkImageLayout::Label label; |
| label.frame = textFrame; |
| label.origin = FloatPoint(linkImagePadding, currentY + origins[0].y); |
| labels.append(label); |
| |
| currentY += height + lineBounds.size.height; |
| }; |
| |
| if (title) |
| buildLines(title, titleColor, titleFont, linkImageTitleMaximumLineCount, kCTLineBreakByTruncatingTail); |
| |
| if (title && domain) |
| currentY += linkImageDomainBaselineToTitleBaseline - (domainFont.ascender - domainFont.descender); |
| |
| if (domain) |
| buildLines(domain, domainColor, domainFont, 1, kCTLineBreakByTruncatingMiddle); |
| |
| currentY += linkImagePadding; |
| |
| boundingRect = FloatRect(0, 0, maximumUsedTextWidth + linkImagePadding * 2, currentY); |
| |
| // To work around blurry drag images on 1x displays, make the width and height a multiple of 2. |
| // FIXME: remove this workaround when <rdar://problem/33059739> is fixed. |
| boundingRect.setWidth((static_cast<int>(boundingRect.width()) / 2) * 2); |
| boundingRect.setHeight((static_cast<int>(boundingRect.height() / 2) * 2)); |
| } |
| |
| DragImageRef createDragImageForLink(Element& element, URL& url, const String& title, TextIndicatorData&, FontRenderingMode, float deviceScaleFactor) |
| { |
| LinkImageLayout layout(url, title); |
| |
| LocalDefaultSystemAppearance localAppearance(element.document().useDarkAppearance(element.computedStyle())); |
| |
| auto imageSize = layout.boundingRect.size(); |
| RetainPtr<NSImage> dragImage = adoptNS([[NSImage alloc] initWithSize:imageSize]); |
| [dragImage _web_lockFocusWithDeviceScaleFactor:deviceScaleFactor]; |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| GraphicsContext context((CGContextRef)[NSGraphicsContext currentContext].graphicsPort); |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| context.fillRoundedRect(FloatRoundedRect(layout.boundingRect, FloatRoundedRect::Radii(linkImageCornerRadius)), colorFromNSColor([NSColor controlBackgroundColor])); |
| |
| for (const auto& label : layout.labels) { |
| GraphicsContextStateSaver saver(context); |
| context.translate(label.origin.x(), layout.boundingRect.height() - label.origin.y() - linkImagePadding); |
| CTFrameDraw(label.frame.get(), context.platformContext()); |
| } |
| |
| [dragImage unlockFocus]; |
| |
| return dragImage; |
| } |
| |
| DragImageRef createDragImageForColor(const Color& color, const FloatRect&, float, Path&) |
| { |
| auto dragImage = adoptNS([[NSImage alloc] initWithSize:NSMakeSize(ColorSwatchWidth, ColorSwatchWidth)]); |
| |
| [dragImage lockFocus]; |
| |
| NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0, 0, ColorSwatchWidth, ColorSwatchWidth) xRadius:ColorSwatchCornerRadius yRadius:ColorSwatchCornerRadius]; |
| [path setLineWidth:ColorSwatchStrokeSize]; |
| |
| [nsColor(color) setFill]; |
| [path fill]; |
| |
| [[NSColor quaternaryLabelColor] setStroke]; |
| [path stroke]; |
| |
| [dragImage unlockFocus]; |
| |
| return dragImage; |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) |