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