| /* |
| * Copyright (C) 2003-2017 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 "GraphicsContext.h" |
| |
| #import "DisplayListRecorder.h" |
| #import "GraphicsContextCG.h" |
| #import "GraphicsContextPlatformPrivateCG.h" |
| #import "IntRect.h" |
| #import <pal/spi/cg/CoreGraphicsSPI.h> |
| #import <pal/spi/mac/NSGraphicsSPI.h> |
| #import <wtf/StdLibExtras.h> |
| |
| #if USE(APPKIT) |
| #import <AppKit/AppKit.h> |
| #endif |
| |
| #if PLATFORM(IOS) |
| #import "Color.h" |
| #import "WKGraphics.h" |
| #import "WKGraphicsInternal.h" |
| #endif |
| |
| #if PLATFORM(MAC) |
| #import "LocalCurrentGraphicsContext.h" |
| #endif |
| |
| @class NSColor; |
| |
| // FIXME: More of this should use CoreGraphics instead of AppKit. |
| // FIXME: More of this should move into GraphicsContextCG.cpp. |
| |
| namespace WebCore { |
| |
| // NSColor, NSBezierPath, and NSGraphicsContext calls do not raise exceptions |
| // so we don't block exceptions. |
| |
| #if PLATFORM(MAC) |
| |
| CGColorRef GraphicsContext::focusRingColor() |
| { |
| static CGColorRef color = [] { |
| CGFloat colorComponents[] = { 0.5, 0.75, 1.0, 1.0 }; |
| return CGColorCreate(sRGBColorSpaceRef(), colorComponents); |
| }(); |
| return color; |
| } |
| |
| static bool drawFocusRingAtTime(CGContextRef context, NSTimeInterval timeOffset) |
| { |
| CGFocusRingStyle focusRingStyle; |
| BOOL needsRepaint = NSInitializeCGFocusRingStyleForTime(NSFocusRingOnly, &focusRingStyle, timeOffset); |
| |
| // We want to respect the CGContext clipping and also not overpaint any |
| // existing focus ring. The way to do this is set accumulate to |
| // -1. According to CoreGraphics, the reasoning for this behavior has been |
| // lost in time. |
| focusRingStyle.accumulate = -1; |
| auto style = adoptCF(CGStyleCreateFocusRingWithColor(&focusRingStyle, GraphicsContext::focusRingColor())); |
| |
| CGContextSaveGState(context); |
| CGContextSetStyle(context, style.get()); |
| CGContextFillPath(context); |
| CGContextRestoreGState(context); |
| |
| return needsRepaint; |
| } |
| |
| static void drawFocusRing(CGContextRef context) |
| { |
| drawFocusRingAtTime(context, std::numeric_limits<double>::max()); |
| } |
| |
| static void drawFocusRingToContext(CGContextRef context, CGPathRef focusRingPath) |
| { |
| CGContextBeginPath(context); |
| CGContextAddPath(context, focusRingPath); |
| drawFocusRing(context); |
| } |
| |
| static bool drawFocusRingToContextAtTime(CGContextRef context, CGPathRef focusRingPath, double timeOffset) |
| { |
| UNUSED_PARAM(timeOffset); |
| CGContextBeginPath(context); |
| CGContextAddPath(context, focusRingPath); |
| return drawFocusRingAtTime(context, std::numeric_limits<double>::max()); |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| void GraphicsContext::drawFocusRing(const Path& path, float width, float offset, const Color& color) |
| { |
| #if PLATFORM(MAC) |
| if (paintingDisabled() || path.isNull()) |
| return; |
| |
| if (m_impl) { |
| m_impl->drawFocusRing(path, width, offset, color); |
| return; |
| } |
| |
| drawFocusRingToContext(platformContext(), path.platformPath()); |
| #else |
| UNUSED_PARAM(path); |
| UNUSED_PARAM(width); |
| UNUSED_PARAM(offset); |
| UNUSED_PARAM(color); |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| void GraphicsContext::drawFocusRing(const Path& path, double timeOffset, bool& needsRedraw) |
| { |
| if (paintingDisabled() || path.isNull()) |
| return; |
| |
| if (m_impl) // FIXME: implement animated focus ring drawing. |
| return; |
| |
| needsRedraw = drawFocusRingToContextAtTime(platformContext(), path.platformPath(), timeOffset); |
| } |
| |
| void GraphicsContext::drawFocusRing(const Vector<FloatRect>& rects, double timeOffset, bool& needsRedraw) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| if (m_impl) // FIXME: implement animated focus ring drawing. |
| return; |
| |
| RetainPtr<CGMutablePathRef> focusRingPath = adoptCF(CGPathCreateMutable()); |
| for (const auto& rect : rects) |
| CGPathAddRect(focusRingPath.get(), 0, CGRect(rect)); |
| |
| needsRedraw = drawFocusRingToContextAtTime(platformContext(), focusRingPath.get(), timeOffset); |
| } |
| #endif |
| |
| void GraphicsContext::drawFocusRing(const Vector<FloatRect>& rects, float width, float offset, const Color& color) |
| { |
| #if PLATFORM(MAC) |
| if (paintingDisabled()) |
| return; |
| |
| if (m_impl) { |
| m_impl->drawFocusRing(rects, width, offset, color); |
| return; |
| } |
| |
| RetainPtr<CGMutablePathRef> focusRingPath = adoptCF(CGPathCreateMutable()); |
| for (auto& rect : rects) |
| CGPathAddRect(focusRingPath.get(), 0, CGRectInset(rect, -offset, -offset)); |
| |
| drawFocusRingToContext(platformContext(), focusRingPath.get()); |
| #else |
| UNUSED_PARAM(rects); |
| UNUSED_PARAM(width); |
| UNUSED_PARAM(offset); |
| UNUSED_PARAM(color); |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| |
| static NSImage *findImage(NSString* firstChoiceName, NSString* secondChoiceName, bool& usingDot) |
| { |
| // Eventually we should be able to get rid of the secondChoiceName. For the time being we need both to keep |
| // this working on all platforms. |
| NSImage *image = [NSImage imageNamed:firstChoiceName]; |
| if (!image) |
| image = [NSImage imageNamed:secondChoiceName]; |
| ASSERT(image); // if image is not available, we want to know |
| usingDot = image; |
| return image; |
| } |
| |
| // FIXME: Should use RetainPtr instead of handwritten retain/release. |
| static NSImage *spellingImage = nullptr; |
| static NSImage *grammarImage = nullptr; |
| static NSImage *correctionImage = nullptr; |
| |
| #endif |
| |
| #if PLATFORM (IOS) |
| |
| static RetainPtr<CGPatternRef> createDotPattern(bool& usingDot, const char* resourceName) |
| { |
| RetainPtr<CGImageRef> image = adoptCF(WKGraphicsCreateImageFromBundleWithName(resourceName)); |
| ASSERT(image); // if image is not available, we want to know |
| usingDot = true; |
| return adoptCF(WKCreatePatternFromCGImage(image.get())); |
| } |
| |
| #endif |
| |
| void GraphicsContext::updateDocumentMarkerResources() |
| { |
| #if PLATFORM(MAC) |
| [spellingImage release]; |
| spellingImage = nullptr; |
| [grammarImage release]; |
| grammarImage = nullptr; |
| [correctionImage release]; |
| correctionImage = nullptr; |
| #endif |
| } |
| |
| static inline void setPatternPhaseInUserSpace(CGContextRef context, CGPoint phasePoint) |
| { |
| CGAffineTransform userToBase = getUserToBaseCTM(context); |
| CGPoint phase = CGPointApplyAffineTransform(phasePoint, userToBase); |
| |
| CGContextSetPatternPhase(context, CGSizeMake(phase.x, phase.y)); |
| } |
| |
| // WebKit on Mac is a standard platform component, so it must use the standard platform artwork for underline. |
| void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& point, float width, DocumentMarkerLineStyle style) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| // These are the same for misspelling or bad grammar. |
| int patternHeight = cMisspellingLineThickness; |
| float patternWidth = cMisspellingLinePatternWidth; |
| |
| bool usingDot; |
| #if !PLATFORM(IOS) |
| NSImage *image; |
| NSColor *fallbackColor; |
| #else |
| CGPatternRef dotPattern; |
| #endif |
| switch (style) { |
| case DocumentMarkerSpellingLineStyle: { |
| // Constants for spelling pattern color. |
| static bool usingDotForSpelling = false; |
| #if !PLATFORM(IOS) |
| if (!spellingImage) |
| spellingImage = [findImage(@"NSSpellingDot", @"SpellingDot", usingDotForSpelling) retain]; |
| image = spellingImage; |
| fallbackColor = [NSColor redColor]; |
| #else |
| static CGPatternRef spellingPattern = createDotPattern(usingDotForSpelling, "SpellingDot").leakRef(); |
| dotPattern = spellingPattern; |
| #endif |
| usingDot = usingDotForSpelling; |
| break; |
| } |
| case DocumentMarkerGrammarLineStyle: { |
| #if !PLATFORM(IOS) |
| // Constants for grammar pattern color. |
| static bool usingDotForGrammar = false; |
| if (!grammarImage) |
| grammarImage = [findImage(@"NSGrammarDot", @"GrammarDot", usingDotForGrammar) retain]; |
| usingDot = grammarImage; |
| image = grammarImage; |
| fallbackColor = [NSColor greenColor]; |
| break; |
| #else |
| ASSERT_NOT_REACHED(); |
| return; |
| #endif |
| } |
| #if PLATFORM(MAC) |
| // To support correction panel. |
| case DocumentMarkerAutocorrectionReplacementLineStyle: |
| case DocumentMarkerDictationAlternativesLineStyle: { |
| // Constants for spelling pattern color. |
| static bool usingDotForSpelling = false; |
| if (!correctionImage) |
| correctionImage = [findImage(@"NSCorrectionDot", @"CorrectionDot", usingDotForSpelling) retain]; |
| usingDot = usingDotForSpelling; |
| image = correctionImage; |
| fallbackColor = [NSColor blueColor]; |
| break; |
| } |
| #endif |
| #if PLATFORM(IOS) |
| case TextCheckingDictationPhraseWithAlternativesLineStyle: { |
| static bool usingDotForDictationPhraseWithAlternatives = false; |
| static CGPatternRef dictationPhraseWithAlternativesPattern = createDotPattern(usingDotForDictationPhraseWithAlternatives, "DictationPhraseWithAlternativesDot").leakRef(); |
| dotPattern = dictationPhraseWithAlternativesPattern; |
| usingDot = usingDotForDictationPhraseWithAlternatives; |
| break; |
| } |
| #endif // PLATFORM(IOS) |
| default: |
| #if PLATFORM(IOS) |
| // FIXME: Should remove default case so we get compile-time errors. |
| ASSERT_NOT_REACHED(); |
| #endif // PLATFORM(IOS) |
| return; |
| } |
| |
| FloatPoint offsetPoint = point; |
| |
| // Make sure to draw only complete dots. |
| if (usingDot) { |
| // allow slightly more considering that the pattern ends with a transparent pixel |
| float widthMod = fmodf(width, patternWidth); |
| if (patternWidth - widthMod > cMisspellingLinePatternGapWidth) { |
| float gapIncludeWidth = 0; |
| if (width > patternWidth) |
| gapIncludeWidth = cMisspellingLinePatternGapWidth; |
| offsetPoint.move(floor((widthMod + gapIncludeWidth) / 2), 0); |
| width -= widthMod; |
| } |
| } |
| |
| // FIXME: This code should not use NSGraphicsContext currentContext |
| // In order to remove this requirement we will need to use CGPattern instead of NSColor |
| // FIXME: This code should not be using setPatternPhaseInUserSpace, as this approach is wrong |
| // for transforms. |
| |
| // Draw underline. |
| CGContextRef context = platformContext(); |
| CGContextStateSaver stateSaver { context }; |
| |
| #if PLATFORM(IOS) |
| WKSetPattern(context, dotPattern, YES, YES); |
| #endif |
| |
| setPatternPhaseInUserSpace(context, offsetPoint); |
| |
| CGRect destinationRect = CGRectMake(offsetPoint.x(), offsetPoint.y(), width, patternHeight); |
| #if !PLATFORM(IOS) |
| if (image) { |
| CGContextClipToRect(context, destinationRect); |
| |
| // We explicitly flip coordinates so as to ensure we paint the image right-side up. We do this because |
| // -[NSImage CGImageForProposedRect:context:hints:] does not guarantee that the returned image will respect |
| // any transforms applied to the context or any specified hints. |
| CGContextTranslateCTM(context, 0, patternHeight); |
| CGContextScaleCTM(context, 1, -1); |
| |
| NSRect dotRect = NSMakeRect(offsetPoint.x(), patternHeight - offsetPoint.y(), patternWidth, patternHeight); // Adjust y position as we flipped coordinates. |
| |
| // FIXME: Rather than getting the NSImage and then picking the CGImage from it, we should do what iOS does and |
| // just load the CGImage in the first place. |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| CGImageRef cgImage = [image CGImageForProposedRect:&dotRect context:[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO] hints:nullptr]; |
| #pragma clang diagnostic pop |
| CGContextDrawTiledImage(context, NSRectToCGRect(dotRect), cgImage); |
| } else { |
| CGContextSetFillColorWithColor(context, [fallbackColor CGColor]); |
| CGContextFillRect(context, destinationRect); |
| } |
| #else |
| WKRectFillUsingOperation(context, destinationRect, kCGCompositeSover); |
| #endif |
| } |
| |
| } // namespace WebCore |