[macOS] REGRESSION: Link drag images blend into the background in Sierra and earlier
[WebKit-https.git] / Source / WebCore / platform / mac / DragImageMac.mm
1 /*
2  * Copyright (C) 2007, 2009, 2012 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "DragImage.h"
28
29 #if ENABLE(DRAG_SUPPORT) && PLATFORM(MAC)
30
31 #import "BitmapImage.h"
32 #import "CoreGraphicsSPI.h"
33 #import "CoreTextSPI.h"
34 #import "Element.h"
35 #import "FloatRoundedRect.h"
36 #import "FontCascade.h"
37 #import "FontDescription.h"
38 #import "FontSelector.h"
39 #import "GraphicsContext.h"
40 #import "Image.h"
41 #import "LinkPresentationSPI.h"
42 #import "SoftLinking.h"
43 #import "StringTruncator.h"
44 #import "TextIndicator.h"
45 #import "TextRun.h"
46 #import "URL.h"
47 #import <wtf/NeverDestroyed.h>
48
49 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
50 SOFT_LINK_PRIVATE_FRAMEWORK(LinkPresentation)
51 #endif
52
53 namespace WebCore {
54
55 IntSize dragImageSize(RetainPtr<NSImage> image)
56 {
57     return (IntSize)[image size];
58 }
59
60 void deleteDragImage(RetainPtr<NSImage>)
61 {
62     // Since this is a RetainPtr, there's nothing additional we need to do to
63     // delete it. It will be released when it falls out of scope.
64 }
65
66 RetainPtr<NSImage> scaleDragImage(RetainPtr<NSImage> image, FloatSize scale)
67 {
68     NSSize originalSize = [image size];
69     NSSize newSize = NSMakeSize((originalSize.width * scale.width()), (originalSize.height * scale.height()));
70     newSize.width = roundf(newSize.width);
71     newSize.height = roundf(newSize.height);
72 #pragma clang diagnostic push
73 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
74     [image setScalesWhenResized:YES];
75 #pragma clang diagnostic pop
76     [image setSize:newSize];
77     return image;
78 }
79     
80 RetainPtr<NSImage> dissolveDragImageToFraction(RetainPtr<NSImage> image, float delta)
81 {
82     if (!image)
83         return nil;
84
85     RetainPtr<NSImage> dissolvedImage = adoptNS([[NSImage alloc] initWithSize:[image size]]);
86     
87     [dissolvedImage lockFocus];
88     [image drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, [image size].width, [image size].height) operation:NSCompositingOperationCopy fraction:delta];
89     [dissolvedImage unlockFocus];
90
91     return dissolvedImage;
92 }
93         
94 RetainPtr<NSImage> createDragImageFromImage(Image* image, ImageOrientationDescription description)
95 {
96     FloatSize size = image->size();
97
98     if (is<BitmapImage>(*image)) {
99         ImageOrientation orientation;
100         BitmapImage& bitmapImage = downcast<BitmapImage>(*image);
101         IntSize sizeRespectingOrientation = bitmapImage.sizeRespectingOrientation();
102
103         if (description.respectImageOrientation() == RespectImageOrientation)
104             orientation = bitmapImage.orientationForCurrentFrame();
105
106         if (orientation != DefaultImageOrientation) {
107             // Construct a correctly-rotated copy of the image to use as the drag image.
108             FloatRect destRect(FloatPoint(), sizeRespectingOrientation);
109
110             RetainPtr<NSImage> rotatedDragImage = adoptNS([[NSImage alloc] initWithSize:(NSSize)(sizeRespectingOrientation)]);
111             [rotatedDragImage lockFocus];
112
113             // ImageOrientation uses top-left coordinates, need to flip to bottom-left, apply...
114             CGAffineTransform transform = CGAffineTransformMakeTranslation(0, destRect.height());
115             transform = CGAffineTransformScale(transform, 1, -1);
116             transform = CGAffineTransformConcat(orientation.transformFromDefault(sizeRespectingOrientation), transform);
117
118             if (orientation.usesWidthAsHeight())
119                 destRect = FloatRect(destRect.x(), destRect.y(), destRect.height(), destRect.width());
120
121             // ...and flip back.
122             transform = CGAffineTransformTranslate(transform, 0, destRect.height());
123             transform = CGAffineTransformScale(transform, 1, -1);
124
125             RetainPtr<NSAffineTransform> cocoaTransform = adoptNS([[NSAffineTransform alloc] init]);
126             [cocoaTransform setTransformStruct:*(NSAffineTransformStruct*)&transform];
127             [cocoaTransform concat];
128
129             [image->snapshotNSImage() drawInRect:destRect fromRect:NSMakeRect(0, 0, size.width(), size.height()) operation:NSCompositingOperationSourceOver fraction:1.0];
130
131             [rotatedDragImage unlockFocus];
132
133             return rotatedDragImage;
134         }
135     }
136
137     auto dragImage = image->snapshotNSImage();
138     [dragImage setSize:(NSSize)size];
139     return dragImage;
140 }
141     
142 RetainPtr<NSImage> createDragImageIconForCachedImageFilename(const String& filename)
143 {
144     NSString *extension = nil;
145     size_t dotIndex = filename.reverseFind('.');
146     
147     if (dotIndex != notFound && dotIndex < (filename.length() - 1)) // require that a . exists after the first character and before the last
148         extension = filename.substring(dotIndex + 1);
149     else {
150         // It might be worth doing a further lookup to pull the extension from the MIME type.
151         extension = @"";
152     }
153     
154     return [[NSWorkspace sharedWorkspace] iconForFileType:extension];
155 }
156
157 const CGFloat linkImagePadding = 10;
158 const CGFloat linkImageDomainBaselineToTitleBaseline = 18;
159 const CGFloat linkImageCornerRadius = 5;
160 const CGFloat linkImageMaximumWidth = 400;
161 const CGFloat linkImageFontSize = 11;
162 const CFIndex linkImageTitleMaximumLineCount = 2;
163 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
164 const int linkImageShadowRadius = 0;
165 const int linkImageShadowOffsetY = 0;
166 #else
167 const int linkImageShadowRadius = 9;
168 const int linkImageShadowOffsetY = -3;
169 #endif
170 const int linkImageDragCornerOutsetX = 6 - linkImageShadowRadius;
171 const int linkImageDragCornerOutsetY = 10 - linkImageShadowRadius + linkImageShadowOffsetY;
172
173 IntPoint dragOffsetForLinkDragImage(DragImageRef dragImage)
174 {
175     IntSize size = dragImageSize(dragImage);
176     return { linkImageDragCornerOutsetX, size.height() + linkImageDragCornerOutsetY };
177 }
178
179 FloatPoint anchorPointForLinkDragImage(DragImageRef dragImage)
180 {
181     IntSize size = dragImageSize(dragImage);
182     return { -static_cast<float>(linkImageDragCornerOutsetX) / size.width(), -static_cast<float>(linkImageDragCornerOutsetY) / size.height() };
183 }
184
185 struct LinkImageLayout {
186     LinkImageLayout(URL&, const String& title);
187
188     struct Label {
189         FloatPoint origin;
190         RetainPtr<CTFrameRef> frame;
191     };
192     Vector<Label> labels;
193
194     FloatRect boundingRect;
195 };
196
197 LinkImageLayout::LinkImageLayout(URL& url, const String& titleString)
198 {
199     NSString *title = nsStringNilIfEmpty(titleString);
200     NSURL *cocoaURL = url;
201     NSString *absoluteURLString = [cocoaURL absoluteString];
202
203 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
204     LinkPresentationLibrary();
205     NSString *domain = [cocoaURL _lp_simplifiedDisplayString];
206 #else
207     NSString *domain = absoluteURLString;
208 #endif
209
210     if ([title isEqualToString:absoluteURLString])
211         title = nil;
212
213     NSFont *titleFont = [NSFont boldSystemFontOfSize:linkImageFontSize];
214     NSFont *domainFont = [NSFont systemFontOfSize:linkImageFontSize];
215
216     NSColor *titleColor = [NSColor labelColor];
217     NSColor *domainColor = [NSColor secondaryLabelColor];
218
219     CGFloat maximumAvailableWidth = linkImageMaximumWidth - linkImagePadding * 2;
220
221     CGFloat currentY = linkImagePadding;
222     CGFloat maximumUsedTextWidth = 0;
223
224     auto buildLines = [this, maximumAvailableWidth, &maximumUsedTextWidth, &currentY] (NSString *text, NSColor *color, NSFont *font, CFIndex maximumLines, CTLineBreakMode lineBreakMode) {
225         CTParagraphStyleSetting paragraphStyleSettings[1];
226         paragraphStyleSettings[0].spec = kCTParagraphStyleSpecifierLineBreakMode;
227         paragraphStyleSettings[0].valueSize = sizeof(CTLineBreakMode);
228         paragraphStyleSettings[0].value = &lineBreakMode;
229         RetainPtr<CTParagraphStyleRef> paragraphStyle = CTParagraphStyleCreate(paragraphStyleSettings, 1);
230
231         NSDictionary *textAttributes = @{
232             (id)kCTFontAttributeName: font,
233             (id)kCTForegroundColorAttributeName: color,
234             (id)kCTParagraphStyleAttributeName: (id)paragraphStyle.get()
235         };
236         NSDictionary *frameAttributes = @{
237             (id)kCTFrameMaximumNumberOfLinesAttributeName: @(maximumLines)
238         };
239
240         RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
241         RetainPtr<CTFramesetterRef> textFramesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText.get()));
242
243         CFRange fitRange;
244         CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(textFramesetter.get(), CFRangeMake(0, 0), (CFDictionaryRef)frameAttributes, CGSizeMake(maximumAvailableWidth, CGFLOAT_MAX), &fitRange);
245
246         RetainPtr<CGPathRef> textPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, textSize.width, textSize.height), nullptr));
247         RetainPtr<CTFrameRef> textFrame = adoptCF(CTFramesetterCreateFrame(textFramesetter.get(), fitRange, textPath.get(), (CFDictionaryRef)frameAttributes));
248
249         CFArrayRef ctLines = CTFrameGetLines(textFrame.get());
250         CFIndex lineCount = CFArrayGetCount(ctLines);
251         if (!lineCount)
252             return;
253
254         Vector<CGPoint> origins(lineCount);
255         CGRect lineBounds;
256         CGFloat height = 0;
257         CTFrameGetLineOrigins(textFrame.get(), CFRangeMake(0, 0), origins.data());
258         for (CFIndex lineIndex = 0; lineIndex < lineCount; ++lineIndex) {
259             CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
260
261             lineBounds = CTLineGetBoundsWithOptions(line, 0);
262             CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
263             CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth;
264             maximumUsedTextWidth = std::max(maximumUsedTextWidth, lineWidthIgnoringTrailingWhitespace);
265
266             if (lineIndex)
267                 height += origins[lineIndex - 1].y - origins[lineIndex].y;
268         }
269
270         LinkImageLayout::Label label;
271         label.frame = textFrame;
272         label.origin = FloatPoint(linkImagePadding, currentY + origins[0].y);
273         labels.append(label);
274
275         currentY += height + lineBounds.size.height;
276     };
277
278     if (title)
279         buildLines(title, titleColor, titleFont, linkImageTitleMaximumLineCount, kCTLineBreakByTruncatingTail);
280
281     if (title && domain)
282         currentY += linkImageDomainBaselineToTitleBaseline - (domainFont.ascender - domainFont.descender);
283
284     if (domain)
285         buildLines(domain, domainColor, domainFont, 1, kCTLineBreakByTruncatingMiddle);
286
287     currentY += linkImagePadding;
288
289     boundingRect = FloatRect(0, 0, maximumUsedTextWidth + linkImagePadding * 2, currentY);
290 }
291
292 DragImageRef createDragImageForLink(Element&, URL& url, const String& title, TextIndicatorData&, FontRenderingMode, float)
293 {
294     LinkImageLayout layout(url, title);
295
296     auto imageSize = layout.boundingRect.size();
297 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
298     imageSize.expand(2 * linkImageShadowRadius, 2 * linkImageShadowRadius - linkImageShadowOffsetY);
299 #endif
300     RetainPtr<NSImage> dragImage = adoptNS([[NSImage alloc] initWithSize:imageSize]);
301     [dragImage lockFocus];
302
303     GraphicsContext context((CGContextRef)[NSGraphicsContext currentContext].graphicsPort);
304 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
305     context.translate(linkImageShadowRadius, linkImageShadowRadius - linkImageShadowOffsetY);
306     context.setShadow({ 0, linkImageShadowOffsetY }, linkImageShadowRadius, { 0.f, 0.f, 0.f, .25 });
307 #endif
308     context.fillRoundedRect(FloatRoundedRect(layout.boundingRect, FloatRoundedRect::Radii(linkImageCornerRadius)), Color::white);
309 #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
310     context.clearShadow();
311 #endif
312
313     for (const auto& label : layout.labels) {
314         GraphicsContextStateSaver saver(context);
315         context.translate(label.origin.x(), layout.boundingRect.height() - label.origin.y() - linkImagePadding);
316         CTFrameDraw(label.frame.get(), context.platformContext());
317     }
318
319     [dragImage unlockFocus];
320
321     return dragImage;
322 }
323    
324 } // namespace WebCore
325
326 #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(MAC)