f2effa4c06ab122b3ba51eafb0b460c1977d482d
[WebKit.git] / Source / WebCore / platform / ios / DragImageIOS.mm
1 /*
2  * Copyright (C) 2014 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 PLATFORM(IOS_FAMILY)
30
31 #import "Document.h"
32 #import "Element.h"
33 #import "FloatRoundedRect.h"
34 #import "FontCascade.h"
35 #import "FontPlatformData.h"
36 #import "Frame.h"
37 #import "GeometryUtilities.h"
38 #import "GraphicsContext.h"
39 #import "Image.h"
40 #import "NotImplemented.h"
41 #import "Page.h"
42 #import "Range.h"
43 #import "StringTruncator.h"
44 #import "TextIndicator.h"
45 #import "TextRun.h"
46 #import <CoreGraphics/CoreGraphics.h>
47 #import <CoreText/CoreText.h>
48 #import <UIKit/UIColor.h>
49 #import <UIKit/UIFont.h>
50 #import <UIKit/UIGraphicsImageRenderer.h>
51 #import <UIKit/UIImage.h>
52 #import <wtf/NeverDestroyed.h>
53 #import <wtf/SoftLinking.h>
54
55 IGNORE_CLANG_WARNINGS_BEGIN("nullability-completeness")
56
57 SOFT_LINK_FRAMEWORK(UIKit)
58 SOFT_LINK_CLASS(UIKit, UIFont)
59 SOFT_LINK_CLASS(UIKit, UIGraphicsImageRenderer)
60 SOFT_LINK(UIKit, UIGraphicsBeginImageContextWithOptions, void, (CGSize size, BOOL opaque, CGFloat scale), (size, opaque, scale))
61 SOFT_LINK(UIKit, UIGraphicsGetCurrentContext, CGContextRef, (void), ())
62 SOFT_LINK(UIKit, UIGraphicsGetImageFromCurrentImageContext, UIImage *, (void), ())
63 SOFT_LINK(UIKit, UIGraphicsEndImageContext, void, (void), ())
64
65 IGNORE_CLANG_WARNINGS_END
66
67 namespace WebCore {
68
69 #if ENABLE(DRAG_SUPPORT)
70
71 IntSize dragImageSize(DragImageRef image)
72 {
73     return IntSize(CGImageGetWidth(image.get()), CGImageGetHeight(image.get()));
74 }
75
76 DragImageRef scaleDragImage(DragImageRef image, FloatSize scale)
77 {
78     CGSize imageSize = CGSizeMake(scale.width() * CGImageGetWidth(image.get()), scale.height() * CGImageGetHeight(image.get()));
79     CGRect imageRect = { CGPointZero, imageSize };
80
81     RetainPtr<UIGraphicsImageRenderer> render = adoptNS([allocUIGraphicsImageRendererInstance() initWithSize:imageSize]);
82     UIImage *imageCopy = [render.get() imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
83         CGContextRef context = rendererContext.CGContext;
84         CGContextTranslateCTM(context, 0, imageSize.height);
85         CGContextScaleCTM(context, 1, -1);
86         CGContextDrawImage(context, imageRect, image.get());
87     }];
88     return imageCopy.CGImage;
89 }
90
91 static float maximumAllowedDragImageArea = 600 * 1024;
92
93 DragImageRef createDragImageFromImage(Image* image, ImageOrientationDescription orientation)
94 {
95     if (!image || !image->width() || !image->height())
96         return nil;
97
98     float adjustedImageScale = 1;
99     CGSize imageSize(image->size());
100     if (imageSize.width * imageSize.height > maximumAllowedDragImageArea) {
101         auto adjustedSize = roundedIntSize(sizeWithAreaAndAspectRatio(maximumAllowedDragImageArea, imageSize.width / imageSize.height));
102         adjustedImageScale = adjustedSize.width() / imageSize.width;
103         imageSize = adjustedSize;
104     }
105
106     RetainPtr<UIGraphicsImageRenderer> render = adoptNS([allocUIGraphicsImageRendererInstance() initWithSize:imageSize]);
107     UIImage *imageCopy = [render.get() imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
108         GraphicsContext context(rendererContext.CGContext);
109         context.translate(0, imageSize.height);
110         context.scale({ adjustedImageScale, -adjustedImageScale });
111         ImagePaintingOptions paintingOptions;
112         paintingOptions.m_orientationDescription = orientation;
113         context.drawImage(*image, FloatPoint(), paintingOptions);
114     }];
115     return imageCopy.CGImage;
116 }
117
118 void deleteDragImage(DragImageRef)
119 {
120 }
121
122 static const TextIndicatorOptions defaultLinkIndicatorOptions = TextIndicatorOptionTightlyFitContent | TextIndicatorOptionRespectTextColor | TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges | TextIndicatorOptionExpandClipBeyondVisibleRect | TextIndicatorOptionComputeEstimatedBackgroundColor;
123
124 static FontCascade cascadeForSystemFont(CGFloat size)
125 {
126     UIFont *font = [getUIFontClass() systemFontOfSize:size];
127     return FontCascade(FontPlatformData(CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize, nil), font.pointSize));
128 }
129
130 DragImageRef createDragImageForLink(Element& linkElement, URL& url, const String& title, TextIndicatorData& indicatorData, FontRenderingMode, float)
131 {
132     // FIXME: Most of this can go away once we can use UIURLDragPreviewView unconditionally.
133     static const CGFloat dragImagePadding = 10;
134     static const auto titleFontCascade = makeNeverDestroyed(cascadeForSystemFont(16));
135     static const auto urlFontCascade = makeNeverDestroyed(cascadeForSystemFont(14));
136
137     String topString(title.stripWhiteSpace());
138     String bottomString([(NSURL *)url absoluteString]);
139     if (topString.isEmpty()) {
140         topString = bottomString;
141         bottomString = emptyString();
142     }
143
144     static CGFloat maxTextWidth = 320;
145     auto truncatedTopString = StringTruncator::rightTruncate(topString, maxTextWidth, titleFontCascade);
146     auto truncatedBottomString = StringTruncator::centerTruncate(bottomString, maxTextWidth, urlFontCascade);
147     CGFloat textWidth = std::max(StringTruncator::width(truncatedTopString, titleFontCascade), StringTruncator::width(truncatedBottomString, urlFontCascade));
148     CGFloat textHeight = truncatedBottomString.isEmpty() ? 22 : 44;
149
150     CGRect imageRect = CGRectMake(0, 0, textWidth + 2 * dragImagePadding, textHeight + 2 * dragImagePadding);
151
152     RetainPtr<UIGraphicsImageRenderer> render = adoptNS([allocUIGraphicsImageRendererInstance() initWithSize:imageRect.size]);
153     UIImage *image = [render.get() imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
154         GraphicsContext context(rendererContext.CGContext);
155         context.translate(0, CGRectGetHeight(imageRect));
156         context.scale({ 1, -1 });
157         context.fillRoundedRect(FloatRoundedRect(imageRect, FloatRoundedRect::Radii(4)), { 255, 255, 255 });
158         titleFontCascade.get().drawText(context, TextRun(truncatedTopString), FloatPoint(dragImagePadding, 18 + dragImagePadding));
159         if (!truncatedBottomString.isEmpty())
160             urlFontCascade.get().drawText(context, TextRun(truncatedBottomString), FloatPoint(dragImagePadding, 40 + dragImagePadding));
161     }];
162
163     auto linkRange = rangeOfContents(linkElement);
164     if (auto textIndicator = TextIndicator::createWithRange(linkRange, defaultLinkIndicatorOptions, TextIndicatorPresentationTransition::None, FloatSize()))
165         indicatorData = textIndicator->data();
166
167     return image.CGImage;
168 }
169
170 DragImageRef createDragImageIconForCachedImageFilename(const String&)
171 {
172     notImplemented();
173     return nullptr;
174 }
175
176 DragImageRef platformAdjustDragImageForDeviceScaleFactor(DragImageRef image, float)
177 {
178     // On iOS, we just create the drag image at the right device scale factor, so we don't need to scale it by 1 / deviceScaleFactor later.
179     return image;
180 }
181
182 static TextIndicatorOptions defaultSelectionDragImageTextIndicatorOptions = TextIndicatorOptionExpandClipBeyondVisibleRect | TextIndicatorOptionPaintAllContent | TextIndicatorOptionUseSelectionRectForSizing | TextIndicatorOptionComputeEstimatedBackgroundColor;
183
184 DragImageRef createDragImageForSelection(Frame& frame, TextIndicatorData& indicatorData, bool forceBlackText)
185 {
186     if (auto document = frame.document())
187         document->updateLayout();
188
189     TextIndicatorOptions options = defaultSelectionDragImageTextIndicatorOptions;
190     if (!forceBlackText)
191         options |= TextIndicatorOptionRespectTextColor;
192
193     auto textIndicator = TextIndicator::createWithSelectionInFrame(frame, options, TextIndicatorPresentationTransition::None, FloatSize());
194     if (!textIndicator)
195         return nullptr;
196
197     auto image = textIndicator->contentImage();
198     if (image)
199         indicatorData = textIndicator->data();
200     else
201         return nullptr;
202
203     FloatRect imageRect(0, 0, image->width(), image->height());
204     if (auto page = frame.page())
205         imageRect.scale(1 / page->deviceScaleFactor());
206
207
208     RetainPtr<UIGraphicsImageRenderer> render = adoptNS([allocUIGraphicsImageRendererInstance() initWithSize:imageRect.size()]);
209     UIImage *finalImage = [render.get() imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
210         GraphicsContext context(rendererContext.CGContext);
211         // FIXME: The context flip here should not be necessary, and suggests that somewhere else in the regular
212         // drag initiation flow, we unnecessarily flip the graphics context.
213         context.translate(0, imageRect.height());
214         context.scale({ 1, -1 });
215         context.drawImage(*image, imageRect);
216     }];
217
218     return finalImage.CGImage;
219 }
220
221 DragImageRef dissolveDragImageToFraction(DragImageRef image, float)
222 {
223     notImplemented();
224     return image;
225 }
226
227 DragImageRef createDragImageForRange(Frame& frame, Range& range, bool forceBlackText)
228 {
229     if (auto document = frame.document())
230         document->updateLayout();
231
232     if (range.collapsed())
233         return nil;
234
235     TextIndicatorOptions options = defaultSelectionDragImageTextIndicatorOptions;
236     if (!forceBlackText)
237         options |= TextIndicatorOptionRespectTextColor;
238
239     auto textIndicator = TextIndicator::createWithRange(range, options, TextIndicatorPresentationTransition::None);
240     if (!textIndicator || !textIndicator->contentImage())
241         return nil;
242
243     auto& image = *textIndicator->contentImage();
244     auto render = adoptNS([allocUIGraphicsImageRendererInstance() initWithSize:image.size()]);
245     UIImage *finalImage = [render.get() imageWithActions:[&image](UIGraphicsImageRendererContext *rendererContext) {
246         GraphicsContext context(rendererContext.CGContext);
247         context.drawImage(image, FloatPoint());
248     }];
249
250     return finalImage.CGImage;
251 }
252
253 DragImageRef createDragImageForColor(const Color& color, const FloatRect& elementRect, float pageScaleFactor, Path& visiblePath)
254 {
255     FloatRect imageRect { 0, 0, elementRect.width() * pageScaleFactor, elementRect.height() * pageScaleFactor };
256     FloatRoundedRect swatch { imageRect, FloatRoundedRect::Radii(ColorSwatchCornerRadius * pageScaleFactor) };
257
258     auto render = adoptNS([allocUIGraphicsImageRendererInstance() initWithSize:imageRect.size()]);
259     UIImage *image = [render imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) {
260         GraphicsContext context { rendererContext.CGContext };
261         context.translate(0, CGRectGetHeight(imageRect));
262         context.scale({ 1, -1 });
263         context.fillRoundedRect(swatch, color);
264     }];
265
266     visiblePath.addRoundedRect(swatch);
267     return image.CGImage;
268 }
269
270 #else
271
272 void deleteDragImage(RetainPtr<CGImageRef>)
273 {
274     // Since this is a RetainPtr, there's nothing additional we need to do to
275     // delete it. It will be released when it falls out of scope.
276 }
277
278 // FIXME: fix signature of dragImageSize() to avoid copying the argument.
279 IntSize dragImageSize(RetainPtr<CGImageRef> image)
280 {
281     return IntSize(CGImageGetWidth(image.get()), CGImageGetHeight(image.get()));
282 }
283
284 RetainPtr<CGImageRef> scaleDragImage(RetainPtr<CGImageRef>, FloatSize)
285 {
286     return nullptr;
287 }
288
289 RetainPtr<CGImageRef> createDragImageFromImage(Image*, ImageOrientationDescription)
290 {
291     return nullptr;
292 }
293
294 DragImageRef createDragImageForRange(Frame&, Range&, bool)
295 {
296     return nullptr;
297 }
298
299 #endif
300
301 } // namespace WebCore
302
303 #endif // PLATFORM(IOS_FAMILY)