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)
30#import "BitmapImage.h"
31#import "Font.h"
32#import "FontCache.h"
33#import "FontDescription.h"
34#import "FontSelector.h"
35#import "GraphicsContext.h"
36#import "Image.h"
37#import "URL.h"
38#import "ResourceResponse.h"
39#import "StringTruncator.h"
40#import "TextRun.h"
41
42namespace WebCore {
43
44IntSize dragImageSize(RetainPtr<NSImage> image)
45{
46    return (IntSize)[image.get() size];
47}
48
49void deleteDragImage(RetainPtr<NSImage>)
50{
51    // Since this is a RetainPtr, there's nothing additional we need to do to
52    // delete it. It will be released when it falls out of scope.
53}
54
55RetainPtr<NSImage> scaleDragImage(RetainPtr<NSImage> image, FloatSize scale)
56{
57    NSSize originalSize = [image.get() size];
58    NSSize newSize = NSMakeSize((originalSize.width * scale.width()), (originalSize.height * scale.height()));
59    newSize.width = roundf(newSize.width);
60    newSize.height = roundf(newSize.height);
61#pragma clang diagnostic push
62#pragma clang diagnostic ignored "-Wdeprecated-declarations"
63    [image.get() setScalesWhenResized:YES];
64#pragma clang diagnostic pop
65    [image.get() setSize:newSize];
66    return image;
67}
68
69RetainPtr<NSImage> dissolveDragImageToFraction(RetainPtr<NSImage> image, float delta)
70{
71    if (!image)
72        return nil;
73
74    RetainPtr<NSImage> dissolvedImage = adoptNS([[NSImage alloc] initWithSize:[image.get() size]]);
75
76    [dissolvedImage.get() lockFocus];
77    [image.get() drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, [image size].width, [image size].height) operation:NSCompositeCopy fraction:delta];
78    [dissolvedImage.get() unlockFocus];
79
80    return dissolvedImage;
81}
82
83RetainPtr<NSImage> createDragImageFromImage(Image* image, ImageOrientationDescription description)
84{
85    FloatSize size = image->size();
86
87    if (image->isBitmapImage()) {
88        ImageOrientation orientation;
89        BitmapImage* bitmapImage = toBitmapImage(image);
90        IntSize sizeRespectingOrientation = bitmapImage->sizeRespectingOrientation(description);
91
92        if (description.respectImageOrientation() == RespectImageOrientation)
93            orientation = bitmapImage->orientationForCurrentFrame();
94
95        if (orientation != DefaultImageOrientation) {
96            // Construct a correctly-rotated copy of the image to use as the drag image.
97            FloatRect destRect(FloatPoint(), sizeRespectingOrientation);
98
99            RetainPtr<NSImage> rotatedDragImage = adoptNS([[NSImage alloc] initWithSize:(NSSize)(sizeRespectingOrientation)]);
100            [rotatedDragImage.get() lockFocus];
101
102            // ImageOrientation uses top-left coordinates, need to flip to bottom-left, apply...
103            CGAffineTransform transform = CGAffineTransformMakeTranslation(0, destRect.height());
104            transform = CGAffineTransformScale(transform, 1, -1);
105            transform = CGAffineTransformConcat(orientation.transformFromDefault(sizeRespectingOrientation), transform);
106
107            if (orientation.usesWidthAsHeight())
108                destRect = FloatRect(destRect.x(), destRect.y(), destRect.height(), destRect.width());
109
110            // ...and flip back.
111            transform = CGAffineTransformTranslate(transform, 0, destRect.height());
112            transform = CGAffineTransformScale(transform, 1, -1);
113
114            RetainPtr<NSAffineTransform> cocoaTransform = adoptNS([[NSAffineTransform alloc] init]);
115            [cocoaTransform.get() setTransformStruct:*(NSAffineTransformStruct*)&transform];
116            [cocoaTransform.get() concat];
117
118            [image->getNSImage() drawInRect:destRect fromRect:NSMakeRect(0, 0, size.width(), size.height()) operation:NSCompositeSourceOver fraction:1.0];
119            [rotatedDragImage.get() unlockFocus];
120
121            return rotatedDragImage;
122        }
123    }
124
125    RetainPtr<NSImage> dragImage = adoptNS([image->getNSImage() copy]);
126    [dragImage.get() setSize:(NSSize)size];
127    return dragImage;
128}
129
130RetainPtr<NSImage> createDragImageIconForCachedImageFilename(const String& filename)
131{
132    NSString *extension = nil;
133    size_t dotIndex = filename.reverseFind('.');
134
135    if (dotIndex != notFound && dotIndex < (filename.length() - 1)) // require that a . exists after the first character and before the last
136        extension = filename.substring(dotIndex + 1);
137    else {
138        // It might be worth doing a further lookup to pull the extension from the MIME type.
139        extension = @"";
140    }
141
142    return [[NSWorkspace sharedWorkspace] iconForFileType:extension];
143}
144
145
146const float DragLabelBorderX = 4;
147//Keep border_y in synch with DragController::LinkDragBorderInset
148const float DragLabelBorderY = 2;
149const float DragLabelRadius = 5;
150const float LabelBorderYOffset = 2;
151
152const float MinDragLabelWidthBeforeClip = 120;
153const float MaxDragLabelWidth = 320;
154
155const float DragLinkLabelFontsize = 11;
156const float DragLinkUrlFontSize = 10;
157
158// FIXME - we should move all the functionality of NSString extras to WebCore
159
160static Font& fontFromNSFont(NSFont *font)
161{
162    static NSFont *currentFont;
163    DEPRECATED_DEFINE_STATIC_LOCAL(Font, currentRenderer, ());
164
165    if ([font isEqual:currentFont])
166        return currentRenderer;
167    if (currentFont)
168        CFRelease(currentFont);
169    currentFont = font;
170    CFRetain(currentFont);
171    FontPlatformData f(font, [font pointSize]);
172    currentRenderer = Font(f, ![[NSGraphicsContext currentContext] isDrawingToScreen]);
173    return currentRenderer;
174}
175
176static bool canUseFastRenderer(const UniChar* buffer, unsigned length)
177{
178    unsigned i;
179    for (i = 0; i < length; i++) {
180        UCharDirection direction = u_charDirection(buffer[i]);
181        if (direction == U_RIGHT_TO_LEFT || direction > U_OTHER_NEUTRAL)
182            return false;
183    }
184    return true;
185}
186
187static float widthWithFont(NSString *string, NSFont *font)
188{
189    unsigned length = [string length];
190    Vector<UniChar, 2048> buffer(length);
191
192    [string getCharacters:buffer.data()];
193
194    if (canUseFastRenderer(buffer.data(), length)) {
195        FontCachePurgePreventer fontCachePurgePreventer;
196
197        Font webCoreFont(FontPlatformData(font, [font pointSize]), ![[NSGraphicsContext currentContext] isDrawingToScreen]);
198        TextRun run(buffer.data(), length);
199        run.disableRoundingHacks();
200        return webCoreFont.width(run);
201    }
202
203    return [string sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]].width;
204}
205
206static inline CGFloat webkit_CGCeiling(CGFloat value)
207{
208    if (sizeof(value) == sizeof(float))
209        return ceilf(value);
210    return static_cast<CGFloat>(ceil(value));
211}
212
213static void drawAtPoint(NSString *string, NSPoint point, NSFont *font, NSColor *textColor)
214{
215    unsigned length = [string length];
216    Vector<UniChar, 2048> buffer(length);
217
218    [string getCharacters:buffer.data()];
219
220    if (canUseFastRenderer(buffer.data(), length)) {
221        FontCachePurgePreventer fontCachePurgePreventer;
222
223        // The following is a half-assed attempt to match AppKit's rounding rules for drawAtPoint.
224        // It's probably incorrect for high DPI.
225        // If you change this, be sure to test all the text drawn this way in Safari, including
226        // the status bar, bookmarks bar, tab bar, and activity window.
227        point.y = webkit_CGCeiling(point.y);
228
229        NSGraphicsContext *nsContext = [NSGraphicsContext currentContext];
230        CGContextRef cgContext = static_cast<CGContextRef>([nsContext graphicsPort]);
231        GraphicsContext graphicsContext(cgContext);
232
233        // Safari doesn't flip the NSGraphicsContext before calling WebKit, yet WebCore requires a flipped graphics context.
234        BOOL flipped = [nsContext isFlipped];
235        if (!flipped)
236            CGContextScaleCTM(cgContext, 1, -1);
237
238        Font webCoreFont(FontPlatformData(font, [font pointSize]), ![nsContext isDrawingToScreen], Antialiased);
239        TextRun run(buffer.data(), length);
240        run.disableRoundingHacks();
241
242        CGFloat red;
243        CGFloat green;
244        CGFloat blue;
245        CGFloat alpha;
246        [[textColor colorUsingColorSpaceName:NSDeviceRGBColorSpace] getRed:&red green:&green blue:&blue alpha:&alpha];
247        graphicsContext.setFillColor(makeRGBA(red * 255, green * 255, blue * 255, alpha * 255), ColorSpaceDeviceRGB);
248
249        webCoreFont.drawText(&graphicsContext, run, FloatPoint(point.x, (flipped ? point.y : (-1 * point.y))));
250
251        if (!flipped)
252            CGContextScaleCTM(cgContext, 1, -1);
253    } else {
254        // The given point is on the baseline.
255        if ([[NSView focusView] isFlipped])
256            point.y -= [font ascender];
257        else
258            point.y += [font descender];
259
260        [string drawAtPoint:point withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil]];
261    }
262}
263
264static void drawDoubledAtPoint(NSString *string, NSPoint textPoint, NSColor *topColor, NSColor *bottomColor, NSFont *font)
265{
266        // turn off font smoothing so translucent text draws correctly (Radar 3118455)
267        drawAtPoint(string, textPoint, font, bottomColor);
268
269        textPoint.y += 1;
270        drawAtPoint(string, textPoint, font, topColor);
271}
272
273DragImageRef createDragImageForLink(URL& url, const String& title, FontRenderingMode)
274{
275    NSString *label = nsStringNilIfEmpty(title);
276    NSURL *cocoaURL = url;
277    NSString *urlString = [cocoaURL absoluteString];
278
279    BOOL drawURLString = YES;
280    BOOL clipURLString = NO;
281    BOOL clipLabelString = NO;
282
283    if (!label) {
284        drawURLString = NO;
285        label = urlString;
286    }
287
288    NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DragLinkLabelFontsize]
289                                                           toHaveTrait:NSBoldFontMask];
290    NSFont *urlFont = [NSFont systemFontOfSize:DragLinkUrlFontSize];
291    NSSize labelSize;
292    labelSize.width = widthWithFont(label, labelFont);
293    labelSize.height = [labelFont ascender] - [labelFont descender];
294    if (labelSize.width > MaxDragLabelWidth){
295        labelSize.width = MaxDragLabelWidth;
296        clipLabelString = YES;
297    }
298
299    NSSize imageSize;
300    imageSize.width = labelSize.width + DragLabelBorderX * 2;
301    imageSize.height = labelSize.height + DragLabelBorderY * 2;
302    if (drawURLString) {
303        NSSize urlStringSize;
304        urlStringSize.width = widthWithFont(urlString, urlFont);
305        urlStringSize.height = [urlFont ascender] - [urlFont descender];
306        imageSize.height += urlStringSize.height;
307        if (urlStringSize.width > MaxDragLabelWidth) {
308            imageSize.width = std::max(MaxDragLabelWidth + DragLabelBorderY * 2, MinDragLabelWidthBeforeClip);
309            clipURLString = YES;
310        } else
311            imageSize.width = std::max(labelSize.width + DragLabelBorderX * 2, urlStringSize.width + DragLabelBorderX * 2);
312    }
313    NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease];
314    [dragImage lockFocus];
315
316    [[NSColor colorWithDeviceRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set];
317
318    // Drag a rectangle with rounded corners
319    NSBezierPath *path = [NSBezierPath bezierPath];
320    [path appendBezierPathWithOvalInRect: NSMakeRect(0, 0, DragLabelRadius * 2, DragLabelRadius * 2)];
321    [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DragLabelRadius * 2, DragLabelRadius * 2, DragLabelRadius * 2)];
322    [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DragLabelRadius * 2, imageSize.height - DragLabelRadius * 2, DragLabelRadius * 2, DragLabelRadius * 2)];
323    [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DragLabelRadius * 2, 0, DragLabelRadius * 2, DragLabelRadius * 2)];
324
325    [path appendBezierPathWithRect: NSMakeRect(DragLabelRadius, 0, imageSize.width - DragLabelRadius * 2, imageSize.height)];
326    [path appendBezierPathWithRect: NSMakeRect(0, DragLabelRadius, DragLabelRadius + 10, imageSize.height - 2 * DragLabelRadius)];
327    [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DragLabelRadius - 20, DragLabelRadius, DragLabelRadius + 20, imageSize.height - 2 * DragLabelRadius)];
328    [path fill];
329
330    NSColor *topColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.75f];
331    NSColor *bottomColor = [NSColor colorWithDeviceWhite:1.0f alpha:0.5f];
332    if (drawURLString) {
333        if (clipURLString)
334            urlString = StringTruncator::centerTruncate(urlString, imageSize.width - (DragLabelBorderX * 2), fontFromNSFont(urlFont));
335
336       drawDoubledAtPoint(urlString, NSMakePoint(DragLabelBorderX, DragLabelBorderY - [urlFont descender]), topColor, bottomColor, urlFont);
337    }
338
339    if (clipLabelString)
340        label = StringTruncator::rightTruncate(label, imageSize.width - (DragLabelBorderX * 2), fontFromNSFont(labelFont));
341    drawDoubledAtPoint(label, NSMakePoint(DragLabelBorderX, imageSize.height - LabelBorderYOffset - [labelFont pointSize]), topColor, bottomColor, labelFont);
342
343    [dragImage unlockFocus];
344
345    return dragImage;
346}
347
348} // namespace WebCore
349
350#endif // ENABLE(DRAG_SUPPORT)
351