| /* |
| * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. |
| * Copyright (C) 2008 Eric Seidel <eric@webkit.org> |
| * |
| * 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. |
| */ |
| |
| #define _USE_MATH_DEFINES 1 |
| #include "config.h" |
| #include "GraphicsContextCG.h" |
| |
| #include "AffineTransform.h" |
| #include "CoreGraphicsSPI.h" |
| #include "FloatConversion.h" |
| #include "GraphicsContextPlatformPrivateCG.h" |
| #include "ImageBuffer.h" |
| #include "ImageOrientation.h" |
| #include "Path.h" |
| #include "Pattern.h" |
| #include "ShadowBlur.h" |
| #include "SubimageCacheWithTimer.h" |
| #include "Timer.h" |
| #include "URL.h" |
| #include <wtf/MathExtras.h> |
| #include <wtf/RetainPtr.h> |
| |
| #if PLATFORM(COCOA) |
| #include "WebCoreSystemInterface.h" |
| #endif |
| |
| #if PLATFORM(WIN) |
| #include <WebKitSystemInterface/WebKitSystemInterface.h> |
| #endif |
| |
| #if PLATFORM(IOS) |
| #include <wtf/HashMap.h> |
| #endif |
| |
| // FIXME: The following using declaration should be in <wtf/HashFunctions.h>. |
| using WTF::pairIntHash; |
| |
| // FIXME: The following using declaration should be in <wtf/HashTraits.h>. |
| using WTF::GenericHashTraits; |
| |
| namespace WebCore { |
| |
| static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace) |
| { |
| CGContextSetFillColorWithColor(context, cachedCGColor(color, colorSpace)); |
| } |
| |
| static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace) |
| { |
| CGContextSetStrokeColorWithColor(context, cachedCGColor(color, colorSpace)); |
| } |
| |
| CGColorSpaceRef deviceRGBColorSpaceRef() |
| { |
| static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB(); |
| return deviceSpace; |
| } |
| |
| CGColorSpaceRef sRGBColorSpaceRef() |
| { |
| #if PLATFORM(IOS) |
| return deviceRGBColorSpaceRef(); |
| #else |
| static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); |
| #if PLATFORM(WIN) |
| // Out-of-date CG installations will not honor kCGColorSpaceSRGB. This logic avoids |
| // causing a crash under those conditions. Since the default color space in Windows |
| // is sRGB, this all works out nicely. |
| if (!sRGBSpace) |
| sRGBSpace = deviceRGBColorSpaceRef(); |
| #endif // PLATFORM(WIN) |
| return sRGBSpace; |
| #endif // PLATFORM(IOS) |
| } |
| |
| #if PLATFORM(WIN) || PLATFORM(IOS) |
| CGColorSpaceRef linearRGBColorSpaceRef() |
| { |
| // FIXME: Windows should be able to use linear sRGB, this is tracked by http://webkit.org/b/80000. |
| return deviceRGBColorSpaceRef(); |
| } |
| #endif |
| |
| void GraphicsContext::platformInit(CGContextRef cgContext) |
| { |
| m_data = new GraphicsContextPlatformPrivate(cgContext); |
| setPaintingDisabled(!cgContext); |
| if (cgContext) { |
| // Make sure the context starts in sync with our state. |
| setPlatformFillColor(fillColor(), fillColorSpace()); |
| setPlatformStrokeColor(strokeColor(), strokeColorSpace()); |
| setPlatformStrokeThickness(strokeThickness()); |
| } |
| } |
| |
| void GraphicsContext::platformDestroy() |
| { |
| delete m_data; |
| } |
| |
| CGContextRef GraphicsContext::platformContext() const |
| { |
| ASSERT(!paintingDisabled()); |
| ASSERT(m_data->m_cgContext); |
| return m_data->m_cgContext.get(); |
| } |
| |
| void GraphicsContext::savePlatformState() |
| { |
| // Note: Do not use this function within this class implementation, since we want to avoid the extra |
| // save of the secondary context (in GraphicsContextPlatformPrivateCG.h). |
| CGContextSaveGState(platformContext()); |
| m_data->save(); |
| } |
| |
| void GraphicsContext::restorePlatformState() |
| { |
| // Note: Do not use this function within this class implementation, since we want to avoid the extra |
| // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h). |
| CGContextRestoreGState(platformContext()); |
| m_data->restore(); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| void GraphicsContext::drawNativeImage(PassNativeImagePtr imagePtr, const FloatSize& imageSize, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, ImageOrientation orientation) |
| { |
| RetainPtr<CGImageRef> image(imagePtr); |
| |
| float currHeight = orientation.usesWidthAsHeight() ? CGImageGetWidth(image.get()) : CGImageGetHeight(image.get()); |
| if (currHeight <= srcRect.y()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| CGContextSaveGState(context); |
| |
| #if PLATFORM(IOS) |
| // Anti-aliasing is on by default on the iPhone. Need to turn it off when drawing images. |
| CGContextSetShouldAntialias(context, false); |
| #endif |
| |
| bool shouldUseSubimage = false; |
| |
| // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image |
| // and then set a clip to the portion that we want to display. |
| FloatRect adjustedDestRect = destRect; |
| |
| if (srcRect.size() != imageSize) { |
| CGInterpolationQuality interpolationQuality = CGContextGetInterpolationQuality(context); |
| // When the image is scaled using high-quality interpolation, we create a temporary CGImage |
| // containing only the portion we want to display. We need to do this because high-quality |
| // interpolation smoothes sharp edges, causing pixels from outside the source rect to bleed |
| // into the destination rect. See <rdar://problem/6112909>. |
| shouldUseSubimage = (interpolationQuality != kCGInterpolationNone) && (srcRect.size() != destRect.size() || !getCTM().isIdentityOrTranslationOrFlipped()); |
| float xScale = srcRect.width() / destRect.width(); |
| float yScale = srcRect.height() / destRect.height(); |
| if (shouldUseSubimage) { |
| FloatRect subimageRect = srcRect; |
| float leftPadding = srcRect.x() - floorf(srcRect.x()); |
| float topPadding = srcRect.y() - floorf(srcRect.y()); |
| |
| subimageRect.move(-leftPadding, -topPadding); |
| adjustedDestRect.move(-leftPadding / xScale, -topPadding / yScale); |
| |
| subimageRect.setWidth(ceilf(subimageRect.width() + leftPadding)); |
| adjustedDestRect.setWidth(subimageRect.width() / xScale); |
| |
| subimageRect.setHeight(ceilf(subimageRect.height() + topPadding)); |
| adjustedDestRect.setHeight(subimageRect.height() / yScale); |
| |
| #if CACHE_SUBIMAGES |
| image = subimageCache().getSubimage(image.get(), subimageRect); |
| #else |
| image = adoptCF(CGImageCreateWithImageInRect(image.get(), subimageRect)); |
| #endif |
| if (currHeight < srcRect.maxY()) { |
| ASSERT(CGImageGetHeight(image.get()) == currHeight - CGRectIntegral(srcRect).origin.y); |
| adjustedDestRect.setHeight(CGImageGetHeight(image.get()) / yScale); |
| } |
| } else { |
| adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale)); |
| adjustedDestRect.setSize(FloatSize(imageSize.width() / xScale, imageSize.height() / yScale)); |
| } |
| |
| if (!destRect.contains(adjustedDestRect)) |
| CGContextClipToRect(context, destRect); |
| } |
| |
| // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly. |
| if (!shouldUseSubimage && currHeight < imageSize.height()) |
| adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / imageSize.height()); |
| |
| #if PLATFORM(IOS) |
| // Align to pixel boundaries |
| adjustedDestRect = roundToDevicePixels(adjustedDestRect); |
| #endif |
| |
| setPlatformCompositeOperation(op, blendMode); |
| |
| // ImageOrientation expects the origin to be at (0, 0) |
| CGContextTranslateCTM(context, adjustedDestRect.x(), adjustedDestRect.y()); |
| adjustedDestRect.setLocation(FloatPoint()); |
| |
| if (orientation != DefaultImageOrientation) { |
| CGContextConcatCTM(context, orientation.transformFromDefault(adjustedDestRect.size())); |
| if (orientation.usesWidthAsHeight()) { |
| // The destination rect will have it's width and height already reversed for the orientation of |
| // the image, as it was needed for page layout, so we need to reverse it back here. |
| adjustedDestRect = FloatRect(adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.height(), adjustedDestRect.width()); |
| } |
| } |
| |
| // Flip the coords. |
| CGContextTranslateCTM(context, 0, adjustedDestRect.height()); |
| CGContextScaleCTM(context, 1, -1); |
| |
| // Adjust the color space. |
| image = Image::imageWithColorSpace(image.get(), styleColorSpace); |
| |
| // Draw the image. |
| CGContextDrawImage(context, adjustedDestRect, image.get()); |
| |
| CGContextRestoreGState(context); |
| } |
| |
| // Draws a filled rectangle with a stroked border. |
| void GraphicsContext::drawRect(const FloatRect& rect, float borderThickness) |
| { |
| // FIXME: this function does not handle patterns and gradients |
| // like drawPath does, it probably should. |
| if (paintingDisabled()) |
| return; |
| |
| ASSERT(!rect.isEmpty()); |
| |
| CGContextRef context = platformContext(); |
| |
| CGContextFillRect(context, rect); |
| |
| if (strokeStyle() != NoStroke) { |
| // We do a fill of four rects to simulate the stroke of a border. |
| Color oldFillColor = fillColor(); |
| if (oldFillColor != strokeColor()) |
| setCGFillColor(context, strokeColor(), strokeColorSpace()); |
| CGRect rects[4] = { |
| FloatRect(rect.x(), rect.y(), rect.width(), borderThickness), |
| FloatRect(rect.x(), rect.maxY() - borderThickness, rect.width(), borderThickness), |
| FloatRect(rect.x(), rect.y() + borderThickness, borderThickness, rect.height() - 2 * borderThickness), |
| FloatRect(rect.maxX() - borderThickness, rect.y() + borderThickness, borderThickness, rect.height() - 2 * borderThickness) |
| }; |
| CGContextFillRects(context, rects, 4); |
| if (oldFillColor != strokeColor()) |
| setCGFillColor(context, oldFillColor, fillColorSpace()); |
| } |
| } |
| |
| // This is only used to draw borders. |
| void GraphicsContext::drawLine(const FloatPoint& point1, const FloatPoint& point2) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| if (strokeStyle() == NoStroke) |
| return; |
| |
| float width = strokeThickness(); |
| |
| FloatPoint p1 = point1; |
| FloatPoint p2 = point2; |
| bool isVerticalLine = (p1.x() == p2.x()); |
| |
| // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic |
| // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g., |
| // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave |
| // us a perfect position, but an odd width gave us a position that is off by exactly 0.5. |
| if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) { |
| if (isVerticalLine) { |
| p1.move(0, width); |
| p2.move(0, -width); |
| } else { |
| p1.move(width, 0); |
| p2.move(-width, 0); |
| } |
| } |
| |
| if (((int)width) % 2) { |
| if (isVerticalLine) { |
| // We're a vertical line. Adjust our x. |
| p1.move(0.5f, 0.0f); |
| p2.move(0.5f, 0.0f); |
| } else { |
| // We're a horizontal line. Adjust our y. |
| p1.move(0.0f, 0.5f); |
| p2.move(0.0f, 0.5f); |
| } |
| } |
| |
| int patWidth = 0; |
| switch (strokeStyle()) { |
| case NoStroke: |
| case SolidStroke: |
| case DoubleStroke: |
| case WavyStroke: // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94112 - Needs platform support. |
| break; |
| case DottedStroke: |
| patWidth = (int)width; |
| break; |
| case DashedStroke: |
| patWidth = 3 * (int)width; |
| break; |
| } |
| |
| CGContextRef context = platformContext(); |
| |
| if (shouldAntialias()) { |
| bool willAntialias = false; |
| #if PLATFORM(IOS) |
| // Force antialiasing on for line patterns as they don't look good with it turned off (<rdar://problem/5459772>). |
| willAntialias = patWidth; |
| #endif |
| CGContextSetShouldAntialias(context, willAntialias); |
| } |
| |
| if (patWidth) { |
| CGContextSaveGState(context); |
| |
| // Do a rect fill of our endpoints. This ensures we always have the |
| // appearance of being a border. We then draw the actual dotted/dashed line. |
| setCGFillColor(context, strokeColor(), strokeColorSpace()); // The save/restore make it safe to mutate the fill color here without setting it back to the old color. |
| if (isVerticalLine) { |
| CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width)); |
| CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width)); |
| } else { |
| CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width)); |
| CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width)); |
| } |
| |
| // Example: 80 pixels with a width of 30 pixels. |
| // Remainder is 20. The maximum pixels of line we could paint |
| // will be 50 pixels. |
| int distance = (isVerticalLine ? (int)(point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width; |
| int remainder = distance % patWidth; |
| int coverage = distance - remainder; |
| int numSegments = coverage / patWidth; |
| |
| float patternOffset = 0.0f; |
| // Special case 1px dotted borders for speed. |
| if (patWidth == 1) |
| patternOffset = 1.0f; |
| else { |
| bool evenNumberOfSegments = !(numSegments % 2); |
| if (remainder) |
| evenNumberOfSegments = !evenNumberOfSegments; |
| if (evenNumberOfSegments) { |
| if (remainder) { |
| patternOffset += patWidth - remainder; |
| patternOffset += remainder / 2; |
| } else |
| patternOffset = patWidth / 2; |
| } else { |
| if (remainder) |
| patternOffset = (patWidth - remainder)/2; |
| } |
| } |
| |
| const CGFloat dottedLine[2] = { static_cast<CGFloat>(patWidth), static_cast<CGFloat>(patWidth) }; |
| CGContextSetLineDash(context, patternOffset, dottedLine, 2); |
| } |
| |
| CGContextBeginPath(context); |
| CGContextMoveToPoint(context, p1.x(), p1.y()); |
| CGContextAddLineToPoint(context, p2.x(), p2.y()); |
| |
| CGContextStrokePath(context); |
| |
| if (patWidth) |
| CGContextRestoreGState(context); |
| |
| if (shouldAntialias()) |
| CGContextSetShouldAntialias(context, true); |
| } |
| |
| #if PLATFORM(IOS) |
| void GraphicsContext::drawJoinedLines(CGPoint points[], unsigned count, bool antialias, CGLineCap lineCap) |
| { |
| if (paintingDisabled() || !count) |
| return; |
| |
| CGContextRef context = platformContext(); |
| float width = CGContextGetLineWidth(context); |
| |
| CGContextSaveGState(context); |
| |
| CGContextSetShouldAntialias(context, antialias); |
| |
| CGContextSetLineWidth(context, width < 1 ? 1 : width); |
| |
| CGContextBeginPath(context); |
| |
| CGContextSetLineCap(context, lineCap); |
| |
| CGContextMoveToPoint(context, points[0].x, points[0].y); |
| |
| for (unsigned i = 1; i < count; ++i) |
| CGContextAddLineToPoint(context, points[i].x, points[i].y); |
| |
| CGContextStrokePath(context); |
| |
| CGContextRestoreGState(context); |
| } |
| #endif |
| |
| void GraphicsContext::drawEllipse(const FloatRect& rect) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| Path path; |
| path.addEllipse(rect); |
| drawPath(path); |
| } |
| |
| static void addConvexPolygonToPath(Path& path, size_t numberOfPoints, const FloatPoint* points) |
| { |
| ASSERT(numberOfPoints > 0); |
| |
| path.moveTo(points[0]); |
| for (size_t i = 1; i < numberOfPoints; ++i) |
| path.addLineTo(points[i]); |
| path.closeSubpath(); |
| } |
| |
| void GraphicsContext::drawConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialiased) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| if (numberOfPoints <= 1) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| if (antialiased != shouldAntialias()) |
| CGContextSetShouldAntialias(context, antialiased); |
| |
| Path path; |
| addConvexPolygonToPath(path, numberOfPoints, points); |
| drawPath(path); |
| |
| if (antialiased != shouldAntialias()) |
| CGContextSetShouldAntialias(context, shouldAntialias()); |
| } |
| |
| void GraphicsContext::clipConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialias) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| if (numberOfPoints <= 1) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| if (antialias != shouldAntialias()) |
| CGContextSetShouldAntialias(context, antialias); |
| |
| Path path; |
| addConvexPolygonToPath(path, numberOfPoints, points); |
| clipPath(path, RULE_NONZERO); |
| |
| if (antialias != shouldAntialias()) |
| CGContextSetShouldAntialias(context, shouldAntialias()); |
| } |
| |
| void GraphicsContext::applyStrokePattern() |
| { |
| CGContextRef cgContext = platformContext(); |
| AffineTransform userToBaseCTM = AffineTransform(wkGetUserToBaseCTM(cgContext)); |
| |
| RetainPtr<CGPatternRef> platformPattern = adoptCF(m_state.strokePattern->createPlatformPattern(userToBaseCTM)); |
| if (!platformPattern) |
| return; |
| |
| RetainPtr<CGColorSpaceRef> patternSpace = adoptCF(CGColorSpaceCreatePattern(0)); |
| CGContextSetStrokeColorSpace(cgContext, patternSpace.get()); |
| |
| const CGFloat patternAlpha = 1; |
| CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha); |
| } |
| |
| void GraphicsContext::applyFillPattern() |
| { |
| CGContextRef cgContext = platformContext(); |
| AffineTransform userToBaseCTM = AffineTransform(wkGetUserToBaseCTM(cgContext)); |
| |
| RetainPtr<CGPatternRef> platformPattern = adoptCF(m_state.fillPattern->createPlatformPattern(userToBaseCTM)); |
| if (!platformPattern) |
| return; |
| |
| RetainPtr<CGColorSpaceRef> patternSpace = adoptCF(CGColorSpaceCreatePattern(0)); |
| CGContextSetFillColorSpace(cgContext, patternSpace.get()); |
| |
| const CGFloat patternAlpha = 1; |
| CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha); |
| } |
| |
| static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode) |
| { |
| bool shouldFill = state.fillPattern || state.fillColor.alpha(); |
| bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha()); |
| bool useEOFill = state.fillRule == RULE_EVENODD; |
| |
| if (shouldFill) { |
| if (shouldStroke) { |
| if (useEOFill) |
| mode = kCGPathEOFillStroke; |
| else |
| mode = kCGPathFillStroke; |
| } else { // fill, no stroke |
| if (useEOFill) |
| mode = kCGPathEOFill; |
| else |
| mode = kCGPathFill; |
| } |
| } else { |
| // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used, |
| // but the compiler will not complain about an uninitialized variable. |
| mode = kCGPathStroke; |
| } |
| |
| return shouldFill || shouldStroke; |
| } |
| |
| void GraphicsContext::drawPath(const Path& path) |
| { |
| if (paintingDisabled() || path.isEmpty()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| const GraphicsContextState& state = m_state; |
| |
| if (state.fillGradient || state.strokeGradient) { |
| // We don't have any optimized way to fill & stroke a path using gradients |
| // FIXME: Be smarter about this. |
| fillPath(path); |
| strokePath(path); |
| return; |
| } |
| |
| CGContextBeginPath(context); |
| CGContextAddPath(context, path.platformPath()); |
| |
| if (state.fillPattern) |
| applyFillPattern(); |
| if (state.strokePattern) |
| applyStrokePattern(); |
| |
| CGPathDrawingMode drawingMode; |
| if (calculateDrawingMode(state, drawingMode)) |
| CGContextDrawPath(context, drawingMode); |
| } |
| |
| static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule) |
| { |
| if (fillRule == RULE_EVENODD) |
| CGContextEOFillPath(context); |
| else |
| CGContextFillPath(context); |
| } |
| |
| void GraphicsContext::fillPath(const Path& path) |
| { |
| if (paintingDisabled() || path.isEmpty()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| if (m_state.fillGradient) { |
| if (hasShadow()) { |
| FloatRect rect = path.fastBoundingRect(); |
| FloatSize layerSize = getCTM().mapSize(rect.size()); |
| |
| CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0); |
| CGContextRef layerContext = CGLayerGetContext(layer); |
| |
| CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height()); |
| CGContextTranslateCTM(layerContext, -rect.x(), -rect.y()); |
| CGContextBeginPath(layerContext); |
| CGContextAddPath(layerContext, path.platformPath()); |
| CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform()); |
| |
| if (fillRule() == RULE_EVENODD) |
| CGContextEOClip(layerContext); |
| else |
| CGContextClip(layerContext); |
| |
| m_state.fillGradient->paint(layerContext); |
| CGContextDrawLayerInRect(context, rect, layer); |
| CGLayerRelease(layer); |
| } else { |
| CGContextBeginPath(context); |
| CGContextAddPath(context, path.platformPath()); |
| CGContextSaveGState(context); |
| CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform()); |
| |
| if (fillRule() == RULE_EVENODD) |
| CGContextEOClip(context); |
| else |
| CGContextClip(context); |
| |
| m_state.fillGradient->paint(this); |
| CGContextRestoreGState(context); |
| } |
| |
| return; |
| } |
| |
| CGContextBeginPath(context); |
| CGContextAddPath(context, path.platformPath()); |
| |
| if (m_state.fillPattern) |
| applyFillPattern(); |
| fillPathWithFillRule(context, fillRule()); |
| } |
| |
| void GraphicsContext::strokePath(const Path& path) |
| { |
| if (paintingDisabled() || path.isEmpty()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| CGContextBeginPath(context); |
| CGContextAddPath(context, path.platformPath()); |
| |
| if (m_state.strokeGradient) { |
| if (hasShadow()) { |
| FloatRect rect = path.fastBoundingRect(); |
| float lineWidth = strokeThickness(); |
| float doubleLineWidth = lineWidth * 2; |
| float adjustedWidth = ceilf(rect.width() + doubleLineWidth); |
| float adjustedHeight = ceilf(rect.height() + doubleLineWidth); |
| |
| FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight)); |
| |
| CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0); |
| CGContextRef layerContext = CGLayerGetContext(layer); |
| CGContextSetLineWidth(layerContext, lineWidth); |
| |
| // Compensate for the line width, otherwise the layer's top-left corner would be |
| // aligned with the rect's top-left corner. This would result in leaving pixels out of |
| // the layer on the left and top sides. |
| float translationX = lineWidth - rect.x(); |
| float translationY = lineWidth - rect.y(); |
| CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight); |
| CGContextTranslateCTM(layerContext, translationX, translationY); |
| |
| CGContextAddPath(layerContext, path.platformPath()); |
| CGContextReplacePathWithStrokedPath(layerContext); |
| CGContextClip(layerContext); |
| CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform()); |
| m_state.strokeGradient->paint(layerContext); |
| |
| float destinationX = roundf(rect.x() - lineWidth); |
| float destinationY = roundf(rect.y() - lineWidth); |
| CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer); |
| CGLayerRelease(layer); |
| } else { |
| CGContextSaveGState(context); |
| CGContextReplacePathWithStrokedPath(context); |
| CGContextClip(context); |
| CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform()); |
| m_state.strokeGradient->paint(this); |
| CGContextRestoreGState(context); |
| } |
| return; |
| } |
| |
| if (m_state.strokePattern) |
| applyStrokePattern(); |
| CGContextStrokePath(context); |
| } |
| |
| void GraphicsContext::fillRect(const FloatRect& rect) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| if (m_state.fillGradient) { |
| CGContextSaveGState(context); |
| if (hasShadow()) { |
| FloatSize layerSize = getCTM().mapSize(rect.size()); |
| |
| CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0); |
| CGContextRef layerContext = CGLayerGetContext(layer); |
| |
| CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height()); |
| CGContextTranslateCTM(layerContext, -rect.x(), -rect.y()); |
| CGContextAddRect(layerContext, rect); |
| CGContextClip(layerContext); |
| |
| CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform()); |
| m_state.fillGradient->paint(layerContext); |
| CGContextDrawLayerInRect(context, rect, layer); |
| CGLayerRelease(layer); |
| } else { |
| CGContextClipToRect(context, rect); |
| CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform()); |
| m_state.fillGradient->paint(this); |
| } |
| CGContextRestoreGState(context); |
| return; |
| } |
| |
| if (m_state.fillPattern) |
| applyFillPattern(); |
| |
| bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet. |
| if (drawOwnShadow) { |
| // Turn off CG shadows. |
| CGContextSaveGState(context); |
| CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); |
| |
| ShadowBlur contextShadow(m_state); |
| contextShadow.drawRectShadow(this, FloatRoundedRect(rect)); |
| } |
| |
| CGContextFillRect(context, rect); |
| |
| if (drawOwnShadow) |
| CGContextRestoreGState(context); |
| } |
| |
| void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| Color oldFillColor = fillColor(); |
| ColorSpace oldColorSpace = fillColorSpace(); |
| |
| if (oldFillColor != color || oldColorSpace != colorSpace) |
| setCGFillColor(context, color, colorSpace); |
| |
| bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet. |
| if (drawOwnShadow) { |
| // Turn off CG shadows. |
| CGContextSaveGState(context); |
| CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); |
| |
| ShadowBlur contextShadow(m_state); |
| contextShadow.drawRectShadow(this, FloatRoundedRect(rect)); |
| } |
| |
| CGContextFillRect(context, rect); |
| |
| if (drawOwnShadow) |
| CGContextRestoreGState(context); |
| |
| if (oldFillColor != color || oldColorSpace != colorSpace) |
| setCGFillColor(context, oldFillColor, oldColorSpace); |
| } |
| |
| void GraphicsContext::platformFillRoundedRect(const FloatRoundedRect& rect, const Color& color, ColorSpace colorSpace) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| Color oldFillColor = fillColor(); |
| ColorSpace oldColorSpace = fillColorSpace(); |
| |
| if (oldFillColor != color || oldColorSpace != colorSpace) |
| setCGFillColor(context, color, colorSpace); |
| |
| bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet. |
| if (drawOwnShadow) { |
| // Turn off CG shadows. |
| CGContextSaveGState(context); |
| CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); |
| |
| ShadowBlur contextShadow(m_state); |
| contextShadow.drawRectShadow(this, rect); |
| } |
| |
| const FloatRect& r = rect.rect(); |
| const FloatRoundedRect::Radii& radii = rect.radii(); |
| bool equalWidths = (radii.topLeft().width() == radii.topRight().width() && radii.topRight().width() == radii.bottomLeft().width() && radii.bottomLeft().width() == radii.bottomRight().width()); |
| bool equalHeights = (radii.topLeft().height() == radii.bottomLeft().height() && radii.bottomLeft().height() == radii.topRight().height() && radii.topRight().height() == radii.bottomRight().height()); |
| bool hasCustomFill = m_state.fillGradient || m_state.fillPattern; |
| if (!hasCustomFill && equalWidths && equalHeights && radii.topLeft().width() * 2 == r.width() && radii.topLeft().height() * 2 == r.height()) |
| CGContextFillEllipseInRect(context, r); |
| else { |
| Path path; |
| path.addRoundedRect(rect); |
| fillPath(path); |
| } |
| |
| if (drawOwnShadow) |
| CGContextRestoreGState(context); |
| |
| if (oldFillColor != color || oldColorSpace != colorSpace) |
| setCGFillColor(context, oldFillColor, oldColorSpace); |
| } |
| |
| void GraphicsContext::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color, ColorSpace colorSpace) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| Path path; |
| path.addRect(rect); |
| |
| if (!roundedHoleRect.radii().isZero()) |
| path.addRoundedRect(roundedHoleRect); |
| else |
| path.addRect(roundedHoleRect.rect()); |
| |
| WindRule oldFillRule = fillRule(); |
| Color oldFillColor = fillColor(); |
| ColorSpace oldFillColorSpace = fillColorSpace(); |
| |
| setFillRule(RULE_EVENODD); |
| setFillColor(color, colorSpace); |
| |
| // fillRectWithRoundedHole() assumes that the edges of rect are clipped out, so we only care about shadows cast around inside the hole. |
| bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; |
| if (drawOwnShadow) { |
| // Turn off CG shadows. |
| CGContextSaveGState(context); |
| CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); |
| |
| ShadowBlur contextShadow(m_state); |
| contextShadow.drawInsetShadow(this, rect, roundedHoleRect); |
| } |
| |
| fillPath(path); |
| |
| if (drawOwnShadow) |
| CGContextRestoreGState(context); |
| |
| setFillRule(oldFillRule); |
| setFillColor(oldFillColor, oldFillColorSpace); |
| } |
| |
| void GraphicsContext::clip(const FloatRect& rect) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextClipToRect(platformContext(), rect); |
| m_data->clip(rect); |
| } |
| |
| void GraphicsContext::clipOut(const FloatRect& rect) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| // FIXME: Using CGRectInfinite is much faster than getting the clip bounding box. However, due |
| // to <rdar://problem/12584492>, CGRectInfinite can't be used with an accelerated context that |
| // has certain transforms that aren't just a translation or a scale. And due to <rdar://problem/14634453> |
| // we cannot use it in for a printing context either. |
| const AffineTransform& ctm = getCTM(); |
| bool canUseCGRectInfinite = !wkCGContextIsPDFContext(platformContext()) && (!isAcceleratedContext() || (!ctm.b() && !ctm.c())); |
| CGRect rects[2] = { canUseCGRectInfinite ? CGRectInfinite : CGContextGetClipBoundingBox(platformContext()), rect }; |
| CGContextBeginPath(platformContext()); |
| CGContextAddRects(platformContext(), rects, 2); |
| CGContextEOClip(platformContext()); |
| } |
| |
| void GraphicsContext::clipPath(const Path& path, WindRule clipRule) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| // Why does clipping to an empty path do nothing? |
| // Why is this different from GraphicsContext::clip(const Path&). |
| if (path.isEmpty()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| CGContextBeginPath(platformContext()); |
| CGContextAddPath(platformContext(), path.platformPath()); |
| |
| if (clipRule == RULE_EVENODD) |
| CGContextEOClip(context); |
| else |
| CGContextClip(context); |
| } |
| |
| IntRect GraphicsContext::clipBounds() const |
| { |
| return enclosingIntRect(CGContextGetClipBoundingBox(platformContext())); |
| } |
| |
| void GraphicsContext::beginPlatformTransparencyLayer(float opacity) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| save(); |
| |
| CGContextRef context = platformContext(); |
| CGContextSetAlpha(context, opacity); |
| CGContextBeginTransparencyLayer(context, 0); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| void GraphicsContext::endPlatformTransparencyLayer() |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextRef context = platformContext(); |
| CGContextEndTransparencyLayer(context); |
| |
| restore(); |
| } |
| |
| bool GraphicsContext::supportsTransparencyLayers() |
| { |
| return true; |
| } |
| |
| static void applyShadowOffsetWorkaroundIfNeeded(const GraphicsContext& context, CGFloat& xOffset, CGFloat& yOffset) |
| { |
| #if PLATFORM(IOS) |
| UNUSED_PARAM(context); |
| UNUSED_PARAM(xOffset); |
| UNUSED_PARAM(yOffset); |
| #else |
| if (context.isAcceleratedContext()) |
| return; |
| |
| if (wkCGContextDrawsWithCorrectShadowOffsets(context.platformContext())) |
| return; |
| |
| // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated |
| // to the desired integer. Also see: <rdar://problem/10056277> |
| static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128); |
| if (xOffset > 0) |
| xOffset += extraShadowOffset; |
| else if (xOffset < 0) |
| xOffset -= extraShadowOffset; |
| |
| if (yOffset > 0) |
| yOffset += extraShadowOffset; |
| else if (yOffset < 0) |
| yOffset -= extraShadowOffset; |
| #endif |
| } |
| |
| void GraphicsContext::setPlatformShadow(const FloatSize& offset, float blur, const Color& color, ColorSpace colorSpace) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| // FIXME: we could avoid the shadow setup cost when we know we'll render the shadow ourselves. |
| |
| CGFloat xOffset = offset.width(); |
| CGFloat yOffset = offset.height(); |
| CGFloat blurRadius = blur; |
| CGContextRef context = platformContext(); |
| |
| if (!m_state.shadowsIgnoreTransforms) { |
| CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context); |
| |
| CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b; |
| CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d; |
| CGFloat C = B; |
| CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d; |
| |
| CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D))))); |
| |
| blurRadius = blur * smallEigenvalue; |
| |
| CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM); |
| |
| xOffset = offsetInBaseSpace.width; |
| yOffset = offsetInBaseSpace.height; |
| } |
| |
| // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp |
| blurRadius = std::min(blurRadius, narrowPrecisionToCGFloat(1000.0)); |
| |
| applyShadowOffsetWorkaroundIfNeeded(*this, xOffset, yOffset); |
| |
| // Check for an invalid color, as this means that the color was not set for the shadow |
| // and we should therefore just use the default shadow color. |
| if (!color.isValid()) |
| CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius); |
| else |
| CGContextSetShadowWithColor(context, CGSizeMake(xOffset, yOffset), blurRadius, cachedCGColor(color, colorSpace)); |
| } |
| |
| void GraphicsContext::clearPlatformShadow() |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); |
| } |
| |
| void GraphicsContext::setMiterLimit(float limit) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetMiterLimit(platformContext(), limit); |
| } |
| |
| void GraphicsContext::setAlpha(float alpha) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetAlpha(platformContext(), alpha); |
| } |
| |
| void GraphicsContext::clearRect(const FloatRect& r) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextClearRect(platformContext(), r); |
| } |
| |
| void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| if (m_state.strokeGradient) { |
| if (hasShadow()) { |
| const float doubleLineWidth = lineWidth * 2; |
| float adjustedWidth = ceilf(rect.width() + doubleLineWidth); |
| float adjustedHeight = ceilf(rect.height() + doubleLineWidth); |
| FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight)); |
| |
| CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0); |
| |
| CGContextRef layerContext = CGLayerGetContext(layer); |
| m_state.strokeThickness = lineWidth; |
| CGContextSetLineWidth(layerContext, lineWidth); |
| |
| // Compensate for the line width, otherwise the layer's top-left corner would be |
| // aligned with the rect's top-left corner. This would result in leaving pixels out of |
| // the layer on the left and top sides. |
| const float translationX = lineWidth - rect.x(); |
| const float translationY = lineWidth - rect.y(); |
| CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight); |
| CGContextTranslateCTM(layerContext, translationX, translationY); |
| |
| CGContextAddRect(layerContext, rect); |
| CGContextReplacePathWithStrokedPath(layerContext); |
| CGContextClip(layerContext); |
| CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform()); |
| m_state.strokeGradient->paint(layerContext); |
| |
| const float destinationX = roundf(rect.x() - lineWidth); |
| const float destinationY = roundf(rect.y() - lineWidth); |
| CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer); |
| CGLayerRelease(layer); |
| } else { |
| CGContextSaveGState(context); |
| setStrokeThickness(lineWidth); |
| CGContextAddRect(context, rect); |
| CGContextReplacePathWithStrokedPath(context); |
| CGContextClip(context); |
| CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform()); |
| m_state.strokeGradient->paint(this); |
| CGContextRestoreGState(context); |
| } |
| return; |
| } |
| |
| if (m_state.strokePattern) |
| applyStrokePattern(); |
| |
| // Using CGContextAddRect and CGContextStrokePath to stroke rect rather than |
| // convenience functions (CGContextStrokeRect/CGContextStrokeRectWithWidth). |
| // The convenience functions currently (in at least OSX 10.9.4) fail to |
| // apply some attributes of the graphics state in certain cases |
| // (as identified in https://bugs.webkit.org/show_bug.cgi?id=132948) |
| CGContextSaveGState(context); |
| setStrokeThickness(lineWidth); |
| |
| CGContextAddRect(context, rect); |
| CGContextStrokePath(context); |
| |
| CGContextRestoreGState(context); |
| } |
| |
| void GraphicsContext::setLineCap(LineCap cap) |
| { |
| if (paintingDisabled()) |
| return; |
| switch (cap) { |
| case ButtCap: |
| CGContextSetLineCap(platformContext(), kCGLineCapButt); |
| break; |
| case RoundCap: |
| CGContextSetLineCap(platformContext(), kCGLineCapRound); |
| break; |
| case SquareCap: |
| CGContextSetLineCap(platformContext(), kCGLineCapSquare); |
| break; |
| } |
| } |
| |
| void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) |
| { |
| if (dashOffset < 0) { |
| float length = 0; |
| for (size_t i = 0; i < dashes.size(); ++i) |
| length += static_cast<float>(dashes[i]); |
| if (length) |
| dashOffset = fmod(dashOffset, length) + length; |
| } |
| CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size()); |
| } |
| |
| void GraphicsContext::setLineJoin(LineJoin join) |
| { |
| if (paintingDisabled()) |
| return; |
| switch (join) { |
| case MiterJoin: |
| CGContextSetLineJoin(platformContext(), kCGLineJoinMiter); |
| break; |
| case RoundJoin: |
| CGContextSetLineJoin(platformContext(), kCGLineJoinRound); |
| break; |
| case BevelJoin: |
| CGContextSetLineJoin(platformContext(), kCGLineJoinBevel); |
| break; |
| } |
| } |
| |
| void GraphicsContext::clip(const Path& path, WindRule fillRule) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextRef context = platformContext(); |
| |
| // CGContextClip does nothing if the path is empty, so in this case, we |
| // instead clip against a zero rect to reduce the clipping region to |
| // nothing - which is the intended behavior of clip() if the path is empty. |
| if (path.isEmpty()) |
| CGContextClipToRect(context, CGRectZero); |
| else { |
| CGContextBeginPath(context); |
| CGContextAddPath(context, path.platformPath()); |
| if (fillRule == RULE_NONZERO) |
| CGContextClip(context); |
| else |
| CGContextEOClip(context); |
| } |
| m_data->clip(path); |
| } |
| |
| void GraphicsContext::canvasClip(const Path& path, WindRule fillRule) |
| { |
| clip(path, fillRule); |
| } |
| |
| void GraphicsContext::clipOut(const Path& path) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextBeginPath(platformContext()); |
| CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext())); |
| if (!path.isEmpty()) |
| CGContextAddPath(platformContext(), path.platformPath()); |
| CGContextEOClip(platformContext()); |
| } |
| |
| void GraphicsContext::scale(const FloatSize& size) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextScaleCTM(platformContext(), size.width(), size.height()); |
| m_data->scale(size); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| void GraphicsContext::rotate(float angle) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextRotateCTM(platformContext(), angle); |
| m_data->rotate(angle); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| void GraphicsContext::translate(float x, float y) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextTranslateCTM(platformContext(), x, y); |
| m_data->translate(x, y); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| void GraphicsContext::concatCTM(const AffineTransform& transform) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextConcatCTM(platformContext(), transform); |
| m_data->concatCTM(transform); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| void GraphicsContext::setCTM(const AffineTransform& transform) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetCTM(platformContext(), transform); |
| m_data->setCTM(transform); |
| m_data->m_userToDeviceTransformKnownToBeIdentity = false; |
| } |
| |
| AffineTransform GraphicsContext::getCTM(IncludeDeviceScale includeScale) const |
| { |
| if (paintingDisabled()) |
| return AffineTransform(); |
| |
| // The CTM usually includes the deviceScaleFactor except in WebKit 1 when the |
| // content is non-composited, since the scale factor is integrated at a lower |
| // level. To guarantee the deviceScale is included, we can use this CG API. |
| if (includeScale == DefinitelyIncludeDeviceScale) |
| return CGContextGetUserSpaceToDeviceSpaceTransform(platformContext()); |
| |
| return CGContextGetCTM(platformContext()); |
| } |
| |
| FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode roundingMode) |
| { |
| // It is not enough just to round to pixels in device space. The rotation part of the |
| // affine transform matrix to device space can mess with this conversion if we have a |
| // rotating image like the hands of the world clock widget. We just need the scale, so |
| // we get the affine transform matrix and extract the scale. |
| |
| if (m_data->m_userToDeviceTransformKnownToBeIdentity) |
| return roundedIntRect(rect); |
| |
| CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext()); |
| if (CGAffineTransformIsIdentity(deviceMatrix)) { |
| m_data->m_userToDeviceTransformKnownToBeIdentity = true; |
| return roundedIntRect(rect); |
| } |
| |
| float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b); |
| float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d); |
| |
| CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY); |
| CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX, |
| (rect.y() + rect.height()) * deviceScaleY); |
| |
| deviceOrigin.x = roundf(deviceOrigin.x); |
| deviceOrigin.y = roundf(deviceOrigin.y); |
| if (roundingMode == RoundAllSides) { |
| deviceLowerRight.x = roundf(deviceLowerRight.x); |
| deviceLowerRight.y = roundf(deviceLowerRight.y); |
| } else { |
| deviceLowerRight.x = deviceOrigin.x + roundf(rect.width() * deviceScaleX); |
| deviceLowerRight.y = deviceOrigin.y + roundf(rect.height() * deviceScaleY); |
| } |
| |
| // Don't let the height or width round to 0 unless either was originally 0 |
| if (deviceOrigin.y == deviceLowerRight.y && rect.height()) |
| deviceLowerRight.y += 1; |
| if (deviceOrigin.x == deviceLowerRight.x && rect.width()) |
| deviceLowerRight.x += 1; |
| |
| FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY); |
| FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY); |
| return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin); |
| } |
| |
| FloatRect GraphicsContext::computeLineBoundsForText(const FloatPoint& point, float width, bool printing) |
| { |
| bool dummyBool; |
| Color dummyColor; |
| return computeLineBoundsAndAntialiasingModeForText(point, width, printing, dummyBool, dummyColor); |
| } |
| |
| void GraphicsContext::drawLineForText(const FloatPoint& point, float width, bool printing, bool doubleLines) |
| { |
| DashArray widths; |
| widths.append(width); |
| widths.append(0); |
| drawLinesForText(point, widths, printing, doubleLines); |
| } |
| |
| void GraphicsContext::drawLinesForText(const FloatPoint& point, const DashArray& widths, bool printing, bool doubleLines) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| if (widths.size() <= 0) |
| return; |
| |
| Color localStrokeColor(strokeColor()); |
| |
| bool shouldAntialiasLine; |
| FloatRect bounds = computeLineBoundsAndAntialiasingModeForText(point, widths.last(), printing, shouldAntialiasLine, localStrokeColor); |
| bool fillColorIsNotEqualToStrokeColor = fillColor() != localStrokeColor; |
| |
| Vector<CGRect, 4> dashBounds; |
| ASSERT(!(widths.size() % 2)); |
| dashBounds.reserveInitialCapacity(dashBounds.size() / 2); |
| for (size_t i = 0; i < widths.size(); i += 2) |
| dashBounds.append(CGRectMake(bounds.x() + widths[i], bounds.y(), widths[i+1] - widths[i], bounds.height())); |
| |
| if (doubleLines) { |
| // The space between double underlines is equal to the height of the underline |
| for (size_t i = 0; i < widths.size(); i += 2) |
| dashBounds.append(CGRectMake(bounds.x() + widths[i], bounds.y() + 2 * bounds.height(), widths[i+1] - widths[i], bounds.height())); |
| } |
| |
| if (fillColorIsNotEqualToStrokeColor) |
| setCGFillColor(platformContext(), localStrokeColor, strokeColorSpace()); |
| |
| CGContextFillRects(platformContext(), dashBounds.data(), dashBounds.size()); |
| |
| if (fillColorIsNotEqualToStrokeColor) |
| setCGFillColor(platformContext(), fillColor(), fillColorSpace()); |
| } |
| |
| void GraphicsContext::setURLForRect(const URL& link, const IntRect& destRect) |
| { |
| #if !PLATFORM(IOS) |
| if (paintingDisabled()) |
| return; |
| |
| RetainPtr<CFURLRef> urlRef = link.createCFURL(); |
| if (!urlRef) |
| return; |
| |
| CGContextRef context = platformContext(); |
| |
| // Get the bounding box to handle clipping. |
| CGRect box = CGContextGetClipBoundingBox(context); |
| |
| IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height); |
| IntRect rect = destRect; |
| rect.intersect(intBox); |
| |
| CGPDFContextSetURLForRect(context, urlRef.get(), |
| CGRectApplyAffineTransform(rect, CGContextGetCTM(context))); |
| #else |
| UNUSED_PARAM(link); |
| UNUSED_PARAM(destRect); |
| #endif |
| } |
| |
| void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGInterpolationQuality quality = kCGInterpolationDefault; |
| switch (mode) { |
| case InterpolationDefault: |
| quality = kCGInterpolationDefault; |
| break; |
| case InterpolationNone: |
| quality = kCGInterpolationNone; |
| break; |
| case InterpolationLow: |
| quality = kCGInterpolationLow; |
| break; |
| case InterpolationMedium: |
| quality = kCGInterpolationMedium; |
| break; |
| case InterpolationHigh: |
| quality = kCGInterpolationHigh; |
| break; |
| } |
| CGContextSetInterpolationQuality(platformContext(), quality); |
| } |
| |
| InterpolationQuality GraphicsContext::imageInterpolationQuality() const |
| { |
| if (paintingDisabled()) |
| return InterpolationDefault; |
| |
| CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext()); |
| switch (quality) { |
| case kCGInterpolationDefault: |
| return InterpolationDefault; |
| case kCGInterpolationNone: |
| return InterpolationNone; |
| case kCGInterpolationLow: |
| return InterpolationLow; |
| case kCGInterpolationMedium: |
| return InterpolationMedium; |
| case kCGInterpolationHigh: |
| return InterpolationHigh; |
| } |
| return InterpolationDefault; |
| } |
| |
| void GraphicsContext::setAllowsFontSmoothing(bool allowsFontSmoothing) |
| { |
| UNUSED_PARAM(allowsFontSmoothing); |
| #if PLATFORM(COCOA) |
| CGContextRef context = platformContext(); |
| CGContextSetAllowsFontSmoothing(context, allowsFontSmoothing); |
| #endif |
| } |
| |
| void GraphicsContext::setIsCALayerContext(bool isLayerContext) |
| { |
| if (isLayerContext) |
| m_data->m_contextFlags |= IsLayerCGContext; |
| else |
| m_data->m_contextFlags &= ~IsLayerCGContext; |
| } |
| |
| bool GraphicsContext::isCALayerContext() const |
| { |
| return m_data->m_contextFlags & IsLayerCGContext; |
| } |
| |
| void GraphicsContext::setIsAcceleratedContext(bool isAccelerated) |
| { |
| if (isAccelerated) |
| m_data->m_contextFlags |= IsAcceleratedCGContext; |
| else |
| m_data->m_contextFlags &= ~IsAcceleratedCGContext; |
| } |
| |
| bool GraphicsContext::isAcceleratedContext() const |
| { |
| return m_data->m_contextFlags & IsAcceleratedCGContext; |
| } |
| |
| void GraphicsContext::setPlatformTextDrawingMode(TextDrawingModeFlags mode) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGContextRef context = platformContext(); |
| switch (mode) { |
| case TextModeFill: |
| CGContextSetTextDrawingMode(context, kCGTextFill); |
| break; |
| case TextModeStroke: |
| CGContextSetTextDrawingMode(context, kCGTextStroke); |
| break; |
| case TextModeFill | TextModeStroke: |
| CGContextSetTextDrawingMode(context, kCGTextFillStroke); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace) |
| { |
| if (paintingDisabled()) |
| return; |
| setCGStrokeColor(platformContext(), color, colorSpace); |
| } |
| |
| void GraphicsContext::setPlatformStrokeThickness(float thickness) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetLineWidth(platformContext(), thickness); |
| } |
| |
| void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace) |
| { |
| if (paintingDisabled()) |
| return; |
| setCGFillColor(platformContext(), color, colorSpace); |
| } |
| |
| void GraphicsContext::setPlatformShouldAntialias(bool enable) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetShouldAntialias(platformContext(), enable); |
| } |
| |
| void GraphicsContext::setPlatformShouldSmoothFonts(bool enable) |
| { |
| if (paintingDisabled()) |
| return; |
| CGContextSetShouldSmoothFonts(platformContext(), enable); |
| } |
| |
| void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode, BlendMode blendMode) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| CGBlendMode target = kCGBlendModeNormal; |
| if (blendMode != BlendModeNormal) { |
| switch (blendMode) { |
| case BlendModeMultiply: |
| target = kCGBlendModeMultiply; |
| break; |
| case BlendModeScreen: |
| target = kCGBlendModeScreen; |
| break; |
| case BlendModeOverlay: |
| target = kCGBlendModeOverlay; |
| break; |
| case BlendModeDarken: |
| target = kCGBlendModeDarken; |
| break; |
| case BlendModeLighten: |
| target = kCGBlendModeLighten; |
| break; |
| case BlendModeColorDodge: |
| target = kCGBlendModeColorDodge; |
| break; |
| case BlendModeColorBurn: |
| target = kCGBlendModeColorBurn; |
| break; |
| case BlendModeHardLight: |
| target = kCGBlendModeHardLight; |
| break; |
| case BlendModeSoftLight: |
| target = kCGBlendModeSoftLight; |
| break; |
| case BlendModeDifference: |
| target = kCGBlendModeDifference; |
| break; |
| case BlendModeExclusion: |
| target = kCGBlendModeExclusion; |
| break; |
| case BlendModeHue: |
| target = kCGBlendModeHue; |
| break; |
| case BlendModeSaturation: |
| target = kCGBlendModeSaturation; |
| break; |
| case BlendModeColor: |
| target = kCGBlendModeColor; |
| break; |
| case BlendModeLuminosity: |
| target = kCGBlendModeLuminosity; |
| break; |
| default: |
| break; |
| } |
| } else { |
| switch (mode) { |
| case CompositeClear: |
| target = kCGBlendModeClear; |
| break; |
| case CompositeCopy: |
| target = kCGBlendModeCopy; |
| break; |
| case CompositeSourceOver: |
| // kCGBlendModeNormal |
| break; |
| case CompositeSourceIn: |
| target = kCGBlendModeSourceIn; |
| break; |
| case CompositeSourceOut: |
| target = kCGBlendModeSourceOut; |
| break; |
| case CompositeSourceAtop: |
| target = kCGBlendModeSourceAtop; |
| break; |
| case CompositeDestinationOver: |
| target = kCGBlendModeDestinationOver; |
| break; |
| case CompositeDestinationIn: |
| target = kCGBlendModeDestinationIn; |
| break; |
| case CompositeDestinationOut: |
| target = kCGBlendModeDestinationOut; |
| break; |
| case CompositeDestinationAtop: |
| target = kCGBlendModeDestinationAtop; |
| break; |
| case CompositeXOR: |
| target = kCGBlendModeXOR; |
| break; |
| case CompositePlusDarker: |
| target = kCGBlendModePlusDarker; |
| break; |
| case CompositePlusLighter: |
| target = kCGBlendModePlusLighter; |
| break; |
| case CompositeDifference: |
| target = kCGBlendModeDifference; |
| break; |
| } |
| } |
| CGContextSetBlendMode(platformContext(), target); |
| } |
| |
| void GraphicsContext::platformApplyDeviceScaleFactor(float deviceScaleFactor) |
| { |
| // CoreGraphics expects the base CTM of a HiDPI context to have the scale factor applied to it. |
| // Failing to change the base level CTM will cause certain CG features, such as focus rings, |
| // to draw with a scale factor of 1 rather than the actual scale factor. |
| wkSetBaseCTM(platformContext(), CGAffineTransformScale(CGContextGetBaseCTM(platformContext()), deviceScaleFactor, deviceScaleFactor)); |
| } |
| |
| void GraphicsContext::platformFillEllipse(const FloatRect& ellipse) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| // CGContextFillEllipseInRect only supports solid colors. |
| if (m_state.fillGradient || m_state.fillPattern) { |
| fillEllipseAsPath(ellipse); |
| return; |
| } |
| |
| CGContextRef context = platformContext(); |
| CGContextFillEllipseInRect(context, ellipse); |
| } |
| |
| void GraphicsContext::platformStrokeEllipse(const FloatRect& ellipse) |
| { |
| if (paintingDisabled()) |
| return; |
| |
| // CGContextStrokeEllipseInRect only supports solid colors. |
| if (m_state.strokeGradient || m_state.strokePattern) { |
| strokeEllipseAsPath(ellipse); |
| return; |
| } |
| |
| CGContextRef context = platformContext(); |
| CGContextStrokeEllipseInRect(context, ellipse); |
| } |
| |
| } |