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