341 lines
13 KiB
Plaintext
341 lines
13 KiB
Plaintext
/*
|
|
* Copyright (C) 2007, 2009, 2012-2020 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#import "config.h"
|
|
#import "DragImage.h"
|
|
|
|
#if ENABLE(DRAG_SUPPORT) && PLATFORM(MAC)
|
|
|
|
#import "BitmapImage.h"
|
|
#import "ColorMac.h"
|
|
#import "Element.h"
|
|
#import "FloatRoundedRect.h"
|
|
#import "FontCascade.h"
|
|
#import "FontDescription.h"
|
|
#import "FontSelector.h"
|
|
#import "GraphicsContextCG.h"
|
|
#import "Image.h"
|
|
#import "LocalDefaultSystemAppearance.h"
|
|
#import "Page.h"
|
|
#import "StringTruncator.h"
|
|
#import "TextIndicator.h"
|
|
#import "WebKitNSImageExtras.h"
|
|
#import <pal/spi/cf/CoreTextSPI.h>
|
|
#import <pal/spi/cg/CoreGraphicsSPI.h>
|
|
#import <pal/spi/cocoa/URLFormattingSPI.h>
|
|
#import <wtf/SoftLinking.h>
|
|
#import <wtf/URL.h>
|
|
|
|
#if !HAVE(URL_FORMATTING)
|
|
SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(LinkPresentation)
|
|
#endif
|
|
|
|
namespace WebCore {
|
|
|
|
IntSize dragImageSize(RetainPtr<NSImage> image)
|
|
{
|
|
return (IntSize)[image size];
|
|
}
|
|
|
|
void deleteDragImage(RetainPtr<NSImage>)
|
|
{
|
|
// Since this is a RetainPtr, there's nothing additional we need to do to
|
|
// delete it. It will be released when it falls out of scope.
|
|
}
|
|
|
|
RetainPtr<NSImage> scaleDragImage(RetainPtr<NSImage> image, FloatSize scale)
|
|
{
|
|
NSSize originalSize = [image size];
|
|
NSSize newSize = NSMakeSize((originalSize.width * scale.width()), (originalSize.height * scale.height()));
|
|
newSize.width = roundf(newSize.width);
|
|
newSize.height = roundf(newSize.height);
|
|
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
|
|
[image setScalesWhenResized:YES];
|
|
ALLOW_DEPRECATED_DECLARATIONS_END
|
|
[image setSize:newSize];
|
|
return image;
|
|
}
|
|
|
|
RetainPtr<NSImage> dissolveDragImageToFraction(RetainPtr<NSImage> image, float delta)
|
|
{
|
|
if (!image)
|
|
return nil;
|
|
|
|
RetainPtr<NSImage> dissolvedImage = adoptNS([[NSImage alloc] initWithSize:[image size]]);
|
|
|
|
[dissolvedImage lockFocus];
|
|
[image drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, [image size].width, [image size].height) operation:NSCompositingOperationCopy fraction:delta];
|
|
[dissolvedImage unlockFocus];
|
|
|
|
return dissolvedImage;
|
|
}
|
|
|
|
RetainPtr<NSImage> createDragImageFromImage(Image* image, ImageOrientation orientation)
|
|
{
|
|
if (is<BitmapImage>(*image)) {
|
|
BitmapImage& bitmapImage = downcast<BitmapImage>(*image);
|
|
|
|
if (orientation == ImageOrientation::FromImage)
|
|
orientation = bitmapImage.orientationForCurrentFrame();
|
|
|
|
if (orientation != ImageOrientation::None) {
|
|
// Construct a correctly-rotated copy of the image to use as the drag image.
|
|
FloatSize imageSize = image->size(orientation);
|
|
RetainPtr<NSImage> rotatedDragImage = adoptNS([[NSImage alloc] initWithSize:(NSSize)(imageSize)]);
|
|
[rotatedDragImage lockFocus];
|
|
|
|
// ImageOrientation uses top-left coordinates, need to flip to bottom-left, apply...
|
|
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, imageSize.height());
|
|
transform = CGAffineTransformScale(transform, 1, -1);
|
|
transform = CGAffineTransformConcat(orientation.transformFromDefault(imageSize), transform);
|
|
|
|
if (orientation.usesWidthAsHeight())
|
|
imageSize = imageSize.transposedSize();
|
|
|
|
// ...and flip back.
|
|
transform = CGAffineTransformTranslate(transform, 0, imageSize.height());
|
|
transform = CGAffineTransformScale(transform, 1, -1);
|
|
|
|
RetainPtr<NSAffineTransform> cocoaTransform = adoptNS([[NSAffineTransform alloc] init]);
|
|
[cocoaTransform setTransformStruct:*(NSAffineTransformStruct*)&transform];
|
|
[cocoaTransform concat];
|
|
|
|
FloatRect imageRect(FloatPoint(), imageSize);
|
|
[image->snapshotNSImage() drawInRect:imageRect fromRect:imageRect operation:NSCompositingOperationSourceOver fraction:1.0];
|
|
|
|
[rotatedDragImage unlockFocus];
|
|
|
|
return rotatedDragImage;
|
|
}
|
|
}
|
|
|
|
FloatSize imageSize = image->size();
|
|
auto dragImage = image->snapshotNSImage();
|
|
[dragImage setSize:(NSSize)imageSize];
|
|
return dragImage;
|
|
}
|
|
|
|
RetainPtr<NSImage> createDragImageIconForCachedImageFilename(const String& filename)
|
|
{
|
|
NSString *extension = nil;
|
|
size_t dotIndex = filename.reverseFind('.');
|
|
|
|
if (dotIndex != notFound && dotIndex < (filename.length() - 1)) // require that a . exists after the first character and before the last
|
|
extension = filename.substring(dotIndex + 1);
|
|
else {
|
|
// It might be worth doing a further lookup to pull the extension from the MIME type.
|
|
extension = @"";
|
|
}
|
|
|
|
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
|
|
return [[NSWorkspace sharedWorkspace] iconForFileType:extension];
|
|
ALLOW_DEPRECATED_DECLARATIONS_END
|
|
}
|
|
|
|
const CGFloat linkImagePadding = 10;
|
|
const CGFloat linkImageDomainBaselineToTitleBaseline = 18;
|
|
const CGFloat linkImageCornerRadius = 5;
|
|
const CGFloat linkImageMaximumWidth = 400;
|
|
const CGFloat linkImageFontSize = 11;
|
|
const CFIndex linkImageTitleMaximumLineCount = 2;
|
|
const int linkImageShadowRadius = 0;
|
|
const int linkImageShadowOffsetY = 0;
|
|
const int linkImageDragCornerOutsetX = 6 - linkImageShadowRadius;
|
|
const int linkImageDragCornerOutsetY = 10 - linkImageShadowRadius + linkImageShadowOffsetY;
|
|
|
|
IntPoint dragOffsetForLinkDragImage(DragImageRef dragImage)
|
|
{
|
|
IntSize size = dragImageSize(dragImage);
|
|
return { linkImageDragCornerOutsetX, size.height() + linkImageDragCornerOutsetY };
|
|
}
|
|
|
|
FloatPoint anchorPointForLinkDragImage(DragImageRef dragImage)
|
|
{
|
|
IntSize size = dragImageSize(dragImage);
|
|
return { -static_cast<float>(linkImageDragCornerOutsetX) / size.width(), -static_cast<float>(linkImageDragCornerOutsetY) / size.height() };
|
|
}
|
|
|
|
struct LinkImageLayout {
|
|
LinkImageLayout(URL&, const String& title);
|
|
|
|
struct Label {
|
|
FloatPoint origin;
|
|
RetainPtr<CTFrameRef> frame;
|
|
};
|
|
Vector<Label> labels;
|
|
|
|
FloatRect boundingRect;
|
|
};
|
|
|
|
LinkImageLayout::LinkImageLayout(URL& url, const String& titleString)
|
|
{
|
|
NSString *title = nsStringNilIfEmpty(titleString);
|
|
NSURL *cocoaURL = url;
|
|
NSString *absoluteURLString = [cocoaURL absoluteString];
|
|
|
|
NSString *domain = absoluteURLString;
|
|
#if HAVE(URL_FORMATTING)
|
|
domain = [cocoaURL _lp_simplifiedDisplayString];
|
|
#else
|
|
if (LinkPresentationLibrary())
|
|
domain = [cocoaURL _lp_simplifiedDisplayString];
|
|
#endif
|
|
|
|
if ([title isEqualToString:absoluteURLString])
|
|
title = nil;
|
|
|
|
NSFont *titleFont = [NSFont boldSystemFontOfSize:linkImageFontSize];
|
|
NSFont *domainFont = [NSFont systemFontOfSize:linkImageFontSize];
|
|
|
|
NSColor *titleColor = [NSColor labelColor];
|
|
NSColor *domainColor = [NSColor secondaryLabelColor];
|
|
|
|
CGFloat maximumAvailableWidth = linkImageMaximumWidth - linkImagePadding * 2;
|
|
|
|
CGFloat currentY = linkImagePadding;
|
|
CGFloat maximumUsedTextWidth = 0;
|
|
|
|
auto buildLines = [this, maximumAvailableWidth, &maximumUsedTextWidth, ¤tY] (NSString *text, NSColor *color, NSFont *font, CFIndex maximumLines, CTLineBreakMode lineBreakMode) {
|
|
CTParagraphStyleSetting paragraphStyleSettings[1];
|
|
paragraphStyleSettings[0].spec = kCTParagraphStyleSpecifierLineBreakMode;
|
|
paragraphStyleSettings[0].valueSize = sizeof(CTLineBreakMode);
|
|
paragraphStyleSettings[0].value = &lineBreakMode;
|
|
RetainPtr<CTParagraphStyleRef> paragraphStyle = adoptCF(CTParagraphStyleCreate(paragraphStyleSettings, 1));
|
|
|
|
NSDictionary *textAttributes = @{
|
|
(id)kCTFontAttributeName: font,
|
|
(id)kCTForegroundColorAttributeName: color,
|
|
(id)kCTParagraphStyleAttributeName: (id)paragraphStyle.get()
|
|
};
|
|
NSDictionary *frameAttributes = @{
|
|
(id)kCTFrameMaximumNumberOfLinesAttributeName: @(maximumLines)
|
|
};
|
|
|
|
RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
|
|
RetainPtr<CTFramesetterRef> textFramesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText.get()));
|
|
|
|
CFRange fitRange;
|
|
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(textFramesetter.get(), CFRangeMake(0, 0), (CFDictionaryRef)frameAttributes, CGSizeMake(maximumAvailableWidth, CGFLOAT_MAX), &fitRange);
|
|
|
|
RetainPtr<CGPathRef> textPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, textSize.width, textSize.height), nullptr));
|
|
RetainPtr<CTFrameRef> textFrame = adoptCF(CTFramesetterCreateFrame(textFramesetter.get(), fitRange, textPath.get(), (CFDictionaryRef)frameAttributes));
|
|
|
|
CFArrayRef ctLines = CTFrameGetLines(textFrame.get());
|
|
CFIndex lineCount = CFArrayGetCount(ctLines);
|
|
if (!lineCount)
|
|
return;
|
|
|
|
Vector<CGPoint> origins(lineCount);
|
|
CGRect lineBounds;
|
|
CGFloat height = 0;
|
|
CTFrameGetLineOrigins(textFrame.get(), CFRangeMake(0, 0), origins.data());
|
|
for (CFIndex lineIndex = 0; lineIndex < lineCount; ++lineIndex) {
|
|
CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
|
|
|
|
lineBounds = CTLineGetBoundsWithOptions(line, 0);
|
|
CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
|
|
CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth;
|
|
maximumUsedTextWidth = std::max(maximumUsedTextWidth, lineWidthIgnoringTrailingWhitespace);
|
|
|
|
if (lineIndex)
|
|
height += origins[lineIndex - 1].y - origins[lineIndex].y;
|
|
}
|
|
|
|
LinkImageLayout::Label label;
|
|
label.frame = textFrame;
|
|
label.origin = FloatPoint(linkImagePadding, currentY + origins[0].y);
|
|
labels.append(label);
|
|
|
|
currentY += height + lineBounds.size.height;
|
|
};
|
|
|
|
if (title)
|
|
buildLines(title, titleColor, titleFont, linkImageTitleMaximumLineCount, kCTLineBreakByTruncatingTail);
|
|
|
|
if (title && domain)
|
|
currentY += linkImageDomainBaselineToTitleBaseline - (domainFont.ascender - domainFont.descender);
|
|
|
|
if (domain)
|
|
buildLines(domain, domainColor, domainFont, 1, kCTLineBreakByTruncatingMiddle);
|
|
|
|
currentY += linkImagePadding;
|
|
|
|
boundingRect = FloatRect(0, 0, maximumUsedTextWidth + linkImagePadding * 2, currentY);
|
|
|
|
// To work around blurry drag images on 1x displays, make the width and height a multiple of 2.
|
|
// FIXME: remove this workaround when <rdar://problem/33059739> is fixed.
|
|
boundingRect.setWidth((static_cast<int>(boundingRect.width()) / 2) * 2);
|
|
boundingRect.setHeight((static_cast<int>(boundingRect.height() / 2) * 2));
|
|
}
|
|
|
|
DragImageRef createDragImageForLink(Element& element, URL& url, const String& title, TextIndicatorData&, FontRenderingMode, float deviceScaleFactor)
|
|
{
|
|
LinkImageLayout layout(url, title);
|
|
|
|
LocalDefaultSystemAppearance localAppearance(element.document().useDarkAppearance(element.computedStyle()));
|
|
|
|
auto imageSize = layout.boundingRect.size();
|
|
RetainPtr<NSImage> dragImage = adoptNS([[NSImage alloc] initWithSize:imageSize]);
|
|
[dragImage _web_lockFocusWithDeviceScaleFactor:deviceScaleFactor];
|
|
|
|
GraphicsContextCG context([NSGraphicsContext currentContext].CGContext);
|
|
|
|
context.fillRoundedRect(FloatRoundedRect(layout.boundingRect, FloatRoundedRect::Radii(linkImageCornerRadius)), colorFromNSColor([NSColor controlBackgroundColor]));
|
|
|
|
for (const auto& label : layout.labels) {
|
|
GraphicsContextStateSaver saver(context);
|
|
context.translate(label.origin.x(), layout.boundingRect.height() - label.origin.y() - linkImagePadding);
|
|
CTFrameDraw(label.frame.get(), context.platformContext());
|
|
}
|
|
|
|
[dragImage unlockFocus];
|
|
|
|
return dragImage;
|
|
}
|
|
|
|
DragImageRef createDragImageForColor(const Color& color, const FloatRect&, float, Path&)
|
|
{
|
|
auto dragImage = adoptNS([[NSImage alloc] initWithSize:NSMakeSize(ColorSwatchWidth, ColorSwatchWidth)]);
|
|
|
|
[dragImage lockFocus];
|
|
|
|
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0, 0, ColorSwatchWidth, ColorSwatchWidth) xRadius:ColorSwatchCornerRadius yRadius:ColorSwatchCornerRadius];
|
|
[path setLineWidth:ColorSwatchStrokeSize];
|
|
|
|
[nsColor(color) setFill];
|
|
[path fill];
|
|
|
|
[[NSColor quaternaryLabelColor] setStroke];
|
|
[path stroke];
|
|
|
|
[dragImage unlockFocus];
|
|
|
|
return dragImage;
|
|
}
|
|
|
|
} // namespace WebCore
|
|
|
|
#endif // ENABLE(DRAG_SUPPORT) && PLATFORM(MAC)
|