1/*
2 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#define _USE_MATH_DEFINES 1
28#include "config.h"
29#include "GraphicsContextCG.h"
30
31#include "AffineTransform.h"
32#include "FloatConversion.h"
33#include "GraphicsContextPlatformPrivateCG.h"
34#include "ImageBuffer.h"
35#include "ImageOrientation.h"
36#include "URL.h"
37#include "Path.h"
38#include "Pattern.h"
39#include "ShadowBlur.h"
40#include "SubimageCacheWithTimer.h"
41#include "Timer.h"
42#include <CoreGraphics/CoreGraphics.h>
43#include <wtf/MathExtras.h>
44#include <wtf/RetainPtr.h>
45
46#if PLATFORM(COCOA)
47#include "WebCoreSystemInterface.h"
48#endif
49
50#if PLATFORM(WIN)
51#include <WebKitSystemInterface/WebKitSystemInterface.h>
52#endif
53
54#if PLATFORM(IOS)
55#include <CoreGraphics/CGContextGState.h>
56#include <wtf/HashMap.h>
57#endif
58
59#if !PLATFORM(IOS)
60extern "C" {
61    CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
62    CG_EXTERN CGAffineTransform CGContextGetBaseCTM(CGContextRef);
63};
64#endif // !PLATFORM(IOS)
65
66// FIXME: The following using declaration should be in <wtf/HashFunctions.h>.
67using WTF::pairIntHash;
68
69// FIXME: The following using declaration should be in <wtf/HashTraits.h>.
70using WTF::GenericHashTraits;
71
72namespace WebCore {
73
74static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
75{
76    CGContextSetFillColorWithColor(context, cachedCGColor(color, colorSpace));
77}
78
79static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
80{
81    CGContextSetStrokeColorWithColor(context, cachedCGColor(color, colorSpace));
82}
83
84CGColorSpaceRef deviceRGBColorSpaceRef()
85{
86    static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB();
87    return deviceSpace;
88}
89
90CGColorSpaceRef sRGBColorSpaceRef()
91{
92#if PLATFORM(IOS)
93    return deviceRGBColorSpaceRef();
94#else
95    static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
96#if PLATFORM(WIN)
97    // Out-of-date CG installations will not honor kCGColorSpaceSRGB. This logic avoids
98    // causing a crash under those conditions. Since the default color space in Windows
99    // is sRGB, this all works out nicely.
100    if (!sRGBSpace)
101        sRGBSpace = deviceRGBColorSpaceRef();
102#endif // PLATFORM(WIN)
103    return sRGBSpace;
104#endif // PLATFORM(IOS)
105}
106
107#if PLATFORM(IOS)
108void setStrokeAndFillColor(CGContextRef context, CGColorRef color)
109{
110    CGContextSetStrokeColorWithColor(context, color);
111    CGContextSetFillColorWithColor(context, color);
112}
113#endif // PLATFORM(IOS)
114
115#if PLATFORM(WIN) || PLATFORM(IOS)
116CGColorSpaceRef linearRGBColorSpaceRef()
117{
118    // FIXME: Windows should be able to use linear sRGB, this is tracked by http://webkit.org/b/80000.
119    return deviceRGBColorSpaceRef();
120}
121#endif
122
123void GraphicsContext::platformInit(CGContextRef cgContext, bool shouldUseContextColors)
124{
125    m_data = new GraphicsContextPlatformPrivate(cgContext);
126    setPaintingDisabled(!cgContext);
127    if (cgContext) {
128#if PLATFORM(IOS)
129        m_state.shouldUseContextColors = shouldUseContextColors;
130        if (shouldUseContextColors) {
131#else
132        UNUSED_PARAM(shouldUseContextColors);
133#endif
134        // Make sure the context starts in sync with our state.
135        setPlatformFillColor(fillColor(), fillColorSpace());
136        setPlatformStrokeColor(strokeColor(), strokeColorSpace());
137#if PLATFORM(IOS)
138        }
139#endif
140    }
141}
142
143void GraphicsContext::platformDestroy()
144{
145    delete m_data;
146}
147
148CGContextRef GraphicsContext::platformContext() const
149{
150    ASSERT(!paintingDisabled());
151    ASSERT(m_data->m_cgContext);
152    return m_data->m_cgContext.get();
153}
154
155void GraphicsContext::savePlatformState()
156{
157    // Note: Do not use this function within this class implementation, since we want to avoid the extra
158    // save of the secondary context (in GraphicsContextPlatformPrivateCG.h).
159    CGContextSaveGState(platformContext());
160    m_data->save();
161}
162
163void GraphicsContext::restorePlatformState()
164{
165    // Note: Do not use this function within this class implementation, since we want to avoid the extra
166    // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h).
167    CGContextRestoreGState(platformContext());
168    m_data->restore();
169    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
170}
171
172void GraphicsContext::drawNativeImage(PassNativeImagePtr imagePtr, const FloatSize& imageSize, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, float scale, CompositeOperator op, BlendMode blendMode, ImageOrientation orientation)
173{
174    RetainPtr<CGImageRef> image(imagePtr);
175
176    float currHeight = orientation.usesWidthAsHeight() ? CGImageGetWidth(image.get()) : CGImageGetHeight(image.get());
177#if PLATFORM(IOS)
178    // Unapply the scaling since we are getting this from a scaled bitmap.
179    currHeight /= scale;
180#else
181    UNUSED_PARAM(scale);
182#endif
183
184    if (currHeight <= srcRect.y())
185        return;
186
187    CGContextRef context = platformContext();
188    CGContextSaveGState(context);
189
190#if PLATFORM(IOS)
191    // Anti-aliasing is on by default on the iPhone. Need to turn it off when drawing images.
192    CGContextSetShouldAntialias(context, false);
193#endif
194
195    bool shouldUseSubimage = false;
196
197    // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image
198    // and then set a clip to the portion that we want to display.
199    FloatRect adjustedDestRect = destRect;
200
201    if (srcRect.size() != imageSize) {
202        CGInterpolationQuality interpolationQuality = CGContextGetInterpolationQuality(context);
203        // When the image is scaled using high-quality interpolation, we create a temporary CGImage
204        // containing only the portion we want to display. We need to do this because high-quality
205        // interpolation smoothes sharp edges, causing pixels from outside the source rect to bleed
206        // into the destination rect. See <rdar://problem/6112909>.
207        shouldUseSubimage = (interpolationQuality != kCGInterpolationNone) && (srcRect.size() != destRect.size() || !getCTM().isIdentityOrTranslationOrFlipped());
208        float xScale = srcRect.width() / destRect.width();
209        float yScale = srcRect.height() / destRect.height();
210        if (shouldUseSubimage) {
211            FloatRect subimageRect = srcRect;
212            float leftPadding = srcRect.x() - floorf(srcRect.x());
213            float topPadding = srcRect.y() - floorf(srcRect.y());
214
215            subimageRect.move(-leftPadding, -topPadding);
216            adjustedDestRect.move(-leftPadding / xScale, -topPadding / yScale);
217
218            subimageRect.setWidth(ceilf(subimageRect.width() + leftPadding));
219            adjustedDestRect.setWidth(subimageRect.width() / xScale);
220
221            subimageRect.setHeight(ceilf(subimageRect.height() + topPadding));
222            adjustedDestRect.setHeight(subimageRect.height() / yScale);
223
224#if PLATFORM(IOS)
225            subimageRect.scale(scale, scale);
226#endif
227#if CACHE_SUBIMAGES
228            image = subimageCache().getSubimage(image.get(), subimageRect);
229#else
230            image = adoptCF(CGImageCreateWithImageInRect(image.get(), subimageRect));
231#endif
232            if (currHeight < srcRect.maxY()) {
233                ASSERT(CGImageGetHeight(image.get()) == currHeight - CGRectIntegral(srcRect).origin.y);
234                adjustedDestRect.setHeight(CGImageGetHeight(image.get()) / yScale);
235            }
236        } else {
237            adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale));
238            adjustedDestRect.setSize(FloatSize(imageSize.width() / xScale, imageSize.height() / yScale));
239        }
240
241        if (!destRect.contains(adjustedDestRect))
242            CGContextClipToRect(context, destRect);
243    }
244
245    // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly.
246    if (!shouldUseSubimage && currHeight < imageSize.height())
247        adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / imageSize.height());
248
249#if PLATFORM(IOS)
250    // Align to pixel boundaries
251    adjustedDestRect = roundToDevicePixels(adjustedDestRect);
252#endif
253
254    setPlatformCompositeOperation(op, blendMode);
255
256    // ImageOrientation expects the origin to be at (0, 0)
257    CGContextTranslateCTM(context, adjustedDestRect.x(), adjustedDestRect.y());
258    adjustedDestRect.setLocation(FloatPoint());
259
260    if (orientation != DefaultImageOrientation) {
261        CGContextConcatCTM(context, orientation.transformFromDefault(adjustedDestRect.size()));
262        if (orientation.usesWidthAsHeight()) {
263            // The destination rect will have it's width and height already reversed for the orientation of
264            // the image, as it was needed for page layout, so we need to reverse it back here.
265            adjustedDestRect = FloatRect(adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.height(), adjustedDestRect.width());
266        }
267    }
268
269    // Flip the coords.
270    CGContextTranslateCTM(context, 0, adjustedDestRect.height());
271    CGContextScaleCTM(context, 1, -1);
272
273    // Adjust the color space.
274    image = Image::imageWithColorSpace(image.get(), styleColorSpace);
275
276    // Draw the image.
277    CGContextDrawImage(context, adjustedDestRect, image.get());
278
279    CGContextRestoreGState(context);
280}
281
282// Draws a filled rectangle with a stroked border.
283void GraphicsContext::drawRect(const FloatRect& rect, float borderThickness)
284{
285    // FIXME: this function does not handle patterns and gradients
286    // like drawPath does, it probably should.
287    if (paintingDisabled())
288        return;
289
290    ASSERT(!rect.isEmpty());
291
292    CGContextRef context = platformContext();
293
294    CGContextFillRect(context, rect);
295
296    if (strokeStyle() != NoStroke) {
297        // We do a fill of four rects to simulate the stroke of a border.
298        Color oldFillColor = fillColor();
299        if (oldFillColor != strokeColor())
300            setCGFillColor(context, strokeColor(), strokeColorSpace());
301        CGRect rects[4] = {
302            FloatRect(rect.x(), rect.y(), rect.width(), borderThickness),
303            FloatRect(rect.x(), rect.maxY() - borderThickness, rect.width(), borderThickness),
304            FloatRect(rect.x(), rect.y() + borderThickness, borderThickness, rect.height() - 2 * borderThickness),
305            FloatRect(rect.maxX() - borderThickness, rect.y() + borderThickness, borderThickness, rect.height() - 2 * borderThickness)
306        };
307        CGContextFillRects(context, rects, 4);
308        if (oldFillColor != strokeColor())
309            setCGFillColor(context, oldFillColor, fillColorSpace());
310    }
311}
312
313// This is only used to draw borders.
314void GraphicsContext::drawLine(const FloatPoint& point1, const FloatPoint& point2)
315{
316    if (paintingDisabled())
317        return;
318
319    if (strokeStyle() == NoStroke)
320        return;
321
322    float width = strokeThickness();
323
324    FloatPoint p1 = point1;
325    FloatPoint p2 = point2;
326    bool isVerticalLine = (p1.x() == p2.x());
327
328    // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
329    // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
330    // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
331    // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
332    if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) {
333        if (isVerticalLine) {
334            p1.move(0, width);
335            p2.move(0, -width);
336        } else {
337            p1.move(width, 0);
338            p2.move(-width, 0);
339        }
340    }
341
342    if (((int)width) % 2) {
343        if (isVerticalLine) {
344            // We're a vertical line.  Adjust our x.
345            p1.move(0.5f, 0.0f);
346            p2.move(0.5f, 0.0f);
347        } else {
348            // We're a horizontal line. Adjust our y.
349            p1.move(0.0f, 0.5f);
350            p2.move(0.0f, 0.5f);
351        }
352    }
353
354    int patWidth = 0;
355    switch (strokeStyle()) {
356    case NoStroke:
357    case SolidStroke:
358    case DoubleStroke:
359    case WavyStroke: // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94112 - Needs platform support.
360        break;
361    case DottedStroke:
362        patWidth = (int)width;
363        break;
364    case DashedStroke:
365        patWidth = 3 * (int)width;
366        break;
367    }
368
369    CGContextRef context = platformContext();
370
371    if (shouldAntialias()) {
372        bool willAntialias = false;
373#if PLATFORM(IOS)
374        // Force antialiasing on for line patterns as they don't look good with it turned off (<rdar://problem/5459772>).
375        willAntialias = patWidth;
376#endif
377        CGContextSetShouldAntialias(context, willAntialias);
378    }
379
380    if (patWidth) {
381        CGContextSaveGState(context);
382
383        // Do a rect fill of our endpoints.  This ensures we always have the
384        // appearance of being a border.  We then draw the actual dotted/dashed line.
385        setCGFillColor(context, strokeColor(), strokeColorSpace());  // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
386        if (isVerticalLine) {
387            CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
388            CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
389        } else {
390            CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
391            CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
392        }
393
394        // Example: 80 pixels with a width of 30 pixels.
395        // Remainder is 20.  The maximum pixels of line we could paint
396        // will be 50 pixels.
397        int distance = (isVerticalLine ? (int)(point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
398        int remainder = distance % patWidth;
399        int coverage = distance - remainder;
400        int numSegments = coverage / patWidth;
401
402        float patternOffset = 0.0f;
403        // Special case 1px dotted borders for speed.
404        if (patWidth == 1)
405            patternOffset = 1.0f;
406        else {
407            bool evenNumberOfSegments = !(numSegments % 2);
408            if (remainder)
409                evenNumberOfSegments = !evenNumberOfSegments;
410            if (evenNumberOfSegments) {
411                if (remainder) {
412                    patternOffset += patWidth - remainder;
413                    patternOffset += remainder / 2;
414                } else
415                    patternOffset = patWidth / 2;
416            } else {
417                if (remainder)
418                    patternOffset = (patWidth - remainder)/2;
419            }
420        }
421
422        const CGFloat dottedLine[2] = { static_cast<CGFloat>(patWidth), static_cast<CGFloat>(patWidth) };
423        CGContextSetLineDash(context, patternOffset, dottedLine, 2);
424    }
425
426    CGContextBeginPath(context);
427    CGContextMoveToPoint(context, p1.x(), p1.y());
428    CGContextAddLineToPoint(context, p2.x(), p2.y());
429
430    CGContextStrokePath(context);
431
432    if (patWidth)
433        CGContextRestoreGState(context);
434
435    if (shouldAntialias())
436        CGContextSetShouldAntialias(context, true);
437}
438
439#if PLATFORM(IOS)
440void GraphicsContext::drawJoinedLines(CGPoint points[], unsigned count, bool antialias, CGLineCap lineCap)
441{
442    if (paintingDisabled() || !count)
443        return;
444
445    CGContextRef context = platformContext();
446    float width = CGContextGetLineWidth(context);
447
448    CGContextSaveGState(context);
449
450    CGContextSetShouldAntialias(context, antialias);
451
452    CGContextSetLineWidth(context, width < 1 ? 1 : width);
453
454    CGContextBeginPath(context);
455
456    CGContextSetLineCap(context, lineCap);
457
458    CGContextMoveToPoint(context, points[0].x, points[0].y);
459
460    for (unsigned i = 1; i < count; ++i)
461        CGContextAddLineToPoint(context, points[i].x, points[i].y);
462
463    CGContextStrokePath(context);
464
465    CGContextRestoreGState(context);
466}
467#endif
468
469// This method is only used to draw the little circles used in lists.
470void GraphicsContext::drawEllipse(const IntRect& rect)
471{
472    if (paintingDisabled())
473        return;
474
475    Path path;
476    path.addEllipse(rect);
477    drawPath(path);
478}
479
480#if PLATFORM(IOS)
481void GraphicsContext::drawEllipse(const FloatRect& rect)
482{
483    if (paintingDisabled())
484        return;
485
486    CGContextRef context(platformContext());
487
488    CGContextSaveGState(context);
489
490    setCGFillColor(context, fillColor(), fillColorSpace());
491    setCGStrokeColor(context, strokeColor(), strokeColorSpace());
492
493    CGContextSetLineWidth(context, strokeThickness());
494
495    CGContextBeginPath(context);
496    CGContextAddEllipseInRect(context, rect);
497
498    CGContextFillPath(context);
499    CGContextStrokePath(context);
500
501    CGContextRestoreGState(context);
502}
503#endif
504
505static void addConvexPolygonToPath(Path& path, size_t numberOfPoints, const FloatPoint* points)
506{
507    ASSERT(numberOfPoints > 0);
508
509    path.moveTo(points[0]);
510    for (size_t i = 1; i < numberOfPoints; ++i)
511        path.addLineTo(points[i]);
512    path.closeSubpath();
513}
514
515void GraphicsContext::drawConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialiased)
516{
517    if (paintingDisabled())
518        return;
519
520    if (numberOfPoints <= 1)
521        return;
522
523    CGContextRef context = platformContext();
524
525    if (antialiased != shouldAntialias())
526        CGContextSetShouldAntialias(context, antialiased);
527
528    Path path;
529    addConvexPolygonToPath(path, numberOfPoints, points);
530    drawPath(path);
531
532    if (antialiased != shouldAntialias())
533        CGContextSetShouldAntialias(context, shouldAntialias());
534}
535
536void GraphicsContext::clipConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialias)
537{
538    if (paintingDisabled())
539        return;
540
541    if (numberOfPoints <= 1)
542        return;
543
544    CGContextRef context = platformContext();
545
546    if (antialias != shouldAntialias())
547        CGContextSetShouldAntialias(context, antialias);
548
549    Path path;
550    addConvexPolygonToPath(path, numberOfPoints, points);
551    clipPath(path, RULE_NONZERO);
552
553    if (antialias != shouldAntialias())
554        CGContextSetShouldAntialias(context, shouldAntialias());
555}
556
557void GraphicsContext::applyStrokePattern()
558{
559    CGContextRef cgContext = platformContext();
560    AffineTransform userToBaseCTM = AffineTransform(wkGetUserToBaseCTM(cgContext));
561
562    RetainPtr<CGPatternRef> platformPattern = adoptCF(m_state.strokePattern->createPlatformPattern(userToBaseCTM));
563    if (!platformPattern)
564        return;
565
566    RetainPtr<CGColorSpaceRef> patternSpace = adoptCF(CGColorSpaceCreatePattern(0));
567    CGContextSetStrokeColorSpace(cgContext, patternSpace.get());
568
569    const CGFloat patternAlpha = 1;
570    CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha);
571}
572
573void GraphicsContext::applyFillPattern()
574{
575    CGContextRef cgContext = platformContext();
576    AffineTransform userToBaseCTM = AffineTransform(wkGetUserToBaseCTM(cgContext));
577
578    RetainPtr<CGPatternRef> platformPattern = adoptCF(m_state.fillPattern->createPlatformPattern(userToBaseCTM));
579    if (!platformPattern)
580        return;
581
582    RetainPtr<CGColorSpaceRef> patternSpace = adoptCF(CGColorSpaceCreatePattern(0));
583    CGContextSetFillColorSpace(cgContext, patternSpace.get());
584
585    const CGFloat patternAlpha = 1;
586    CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha);
587}
588
589static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode)
590{
591    bool shouldFill = state.fillPattern || state.fillColor.alpha();
592    bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha());
593    bool useEOFill = state.fillRule == RULE_EVENODD;
594
595    if (shouldFill) {
596        if (shouldStroke) {
597            if (useEOFill)
598                mode = kCGPathEOFillStroke;
599            else
600                mode = kCGPathFillStroke;
601        } else { // fill, no stroke
602            if (useEOFill)
603                mode = kCGPathEOFill;
604            else
605                mode = kCGPathFill;
606        }
607    } else {
608        // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used,
609        // but the compiler will not complain about an uninitialized variable.
610        mode = kCGPathStroke;
611    }
612
613    return shouldFill || shouldStroke;
614}
615
616void GraphicsContext::drawPath(const Path& path)
617{
618    if (paintingDisabled() || path.isEmpty())
619        return;
620
621    CGContextRef context = platformContext();
622    const GraphicsContextState& state = m_state;
623
624    if (state.fillGradient || state.strokeGradient) {
625        // We don't have any optimized way to fill & stroke a path using gradients
626        // FIXME: Be smarter about this.
627        fillPath(path);
628        strokePath(path);
629        return;
630    }
631
632    CGContextBeginPath(context);
633    CGContextAddPath(context, path.platformPath());
634
635    if (state.fillPattern)
636        applyFillPattern();
637    if (state.strokePattern)
638        applyStrokePattern();
639
640    CGPathDrawingMode drawingMode;
641    if (calculateDrawingMode(state, drawingMode))
642        CGContextDrawPath(context, drawingMode);
643}
644
645static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule)
646{
647    if (fillRule == RULE_EVENODD)
648        CGContextEOFillPath(context);
649    else
650        CGContextFillPath(context);
651}
652
653void GraphicsContext::fillPath(const Path& path)
654{
655    if (paintingDisabled() || path.isEmpty())
656        return;
657
658    CGContextRef context = platformContext();
659
660    if (m_state.fillGradient) {
661        if (hasShadow()) {
662            FloatRect rect = path.fastBoundingRect();
663            FloatSize layerSize = getCTM().mapSize(rect.size());
664
665            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
666            CGContextRef layerContext = CGLayerGetContext(layer);
667
668            CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height());
669            CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
670            CGContextBeginPath(layerContext);
671            CGContextAddPath(layerContext, path.platformPath());
672            CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
673
674            if (fillRule() == RULE_EVENODD)
675                CGContextEOClip(layerContext);
676            else
677                CGContextClip(layerContext);
678
679            m_state.fillGradient->paint(layerContext);
680            CGContextDrawLayerInRect(context, rect, layer);
681            CGLayerRelease(layer);
682        } else {
683            CGContextBeginPath(context);
684            CGContextAddPath(context, path.platformPath());
685            CGContextSaveGState(context);
686            CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
687
688            if (fillRule() == RULE_EVENODD)
689                CGContextEOClip(context);
690            else
691                CGContextClip(context);
692
693            m_state.fillGradient->paint(this);
694            CGContextRestoreGState(context);
695        }
696
697        return;
698    }
699
700    CGContextBeginPath(context);
701    CGContextAddPath(context, path.platformPath());
702
703    if (m_state.fillPattern)
704        applyFillPattern();
705    fillPathWithFillRule(context, fillRule());
706}
707
708void GraphicsContext::strokePath(const Path& path)
709{
710    if (paintingDisabled() || path.isEmpty())
711        return;
712
713    CGContextRef context = platformContext();
714
715    CGContextBeginPath(context);
716    CGContextAddPath(context, path.platformPath());
717
718    if (m_state.strokeGradient) {
719        if (hasShadow()) {
720            FloatRect rect = path.fastBoundingRect();
721            float lineWidth = strokeThickness();
722            float doubleLineWidth = lineWidth * 2;
723            float adjustedWidth = ceilf(rect.width() + doubleLineWidth);
724            float adjustedHeight = ceilf(rect.height() + doubleLineWidth);
725
726            FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight));
727
728            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
729            CGContextRef layerContext = CGLayerGetContext(layer);
730            CGContextSetLineWidth(layerContext, lineWidth);
731
732            // Compensate for the line width, otherwise the layer's top-left corner would be
733            // aligned with the rect's top-left corner. This would result in leaving pixels out of
734            // the layer on the left and top sides.
735            float translationX = lineWidth - rect.x();
736            float translationY = lineWidth - rect.y();
737            CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight);
738            CGContextTranslateCTM(layerContext, translationX, translationY);
739
740            CGContextAddPath(layerContext, path.platformPath());
741            CGContextReplacePathWithStrokedPath(layerContext);
742            CGContextClip(layerContext);
743            CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform());
744            m_state.strokeGradient->paint(layerContext);
745
746            float destinationX = roundf(rect.x() - lineWidth);
747            float destinationY = roundf(rect.y() - lineWidth);
748            CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer);
749            CGLayerRelease(layer);
750        } else {
751            CGContextSaveGState(context);
752            CGContextReplacePathWithStrokedPath(context);
753            CGContextClip(context);
754            CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
755            m_state.strokeGradient->paint(this);
756            CGContextRestoreGState(context);
757        }
758        return;
759    }
760
761    if (m_state.strokePattern)
762        applyStrokePattern();
763    CGContextStrokePath(context);
764}
765
766void GraphicsContext::fillRect(const FloatRect& rect)
767{
768    if (paintingDisabled())
769        return;
770
771    CGContextRef context = platformContext();
772
773    if (m_state.fillGradient) {
774        CGContextSaveGState(context);
775        if (hasShadow()) {
776            FloatSize layerSize = getCTM().mapSize(rect.size());
777
778            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
779            CGContextRef layerContext = CGLayerGetContext(layer);
780
781            CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height());
782            CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
783            CGContextAddRect(layerContext, rect);
784            CGContextClip(layerContext);
785
786            CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
787            m_state.fillGradient->paint(layerContext);
788            CGContextDrawLayerInRect(context, rect, layer);
789            CGLayerRelease(layer);
790        } else {
791            CGContextClipToRect(context, rect);
792            CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
793            m_state.fillGradient->paint(this);
794        }
795        CGContextRestoreGState(context);
796        return;
797    }
798
799    if (m_state.fillPattern)
800        applyFillPattern();
801
802    bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
803    if (drawOwnShadow) {
804        // Turn off CG shadows.
805        CGContextSaveGState(context);
806        CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
807
808        ShadowBlur contextShadow(m_state);
809        contextShadow.drawRectShadow(this, FloatRoundedRect(rect));
810    }
811
812    CGContextFillRect(context, rect);
813
814    if (drawOwnShadow)
815        CGContextRestoreGState(context);
816}
817
818void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
819{
820    if (paintingDisabled())
821        return;
822
823    CGContextRef context = platformContext();
824    Color oldFillColor = fillColor();
825    ColorSpace oldColorSpace = fillColorSpace();
826
827    if (oldFillColor != color || oldColorSpace != colorSpace)
828        setCGFillColor(context, color, colorSpace);
829
830    bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
831    if (drawOwnShadow) {
832        // Turn off CG shadows.
833        CGContextSaveGState(context);
834        CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
835
836        ShadowBlur contextShadow(m_state);
837        contextShadow.drawRectShadow(this, FloatRoundedRect(rect));
838    }
839
840    CGContextFillRect(context, rect);
841
842    if (drawOwnShadow)
843        CGContextRestoreGState(context);
844
845    if (oldFillColor != color || oldColorSpace != colorSpace)
846        setCGFillColor(context, oldFillColor, oldColorSpace);
847}
848
849void GraphicsContext::platformFillRoundedRect(const FloatRoundedRect& rect, const Color& color, ColorSpace colorSpace)
850{
851    if (paintingDisabled())
852        return;
853
854    CGContextRef context = platformContext();
855    Color oldFillColor = fillColor();
856    ColorSpace oldColorSpace = fillColorSpace();
857
858    if (oldFillColor != color || oldColorSpace != colorSpace)
859        setCGFillColor(context, color, colorSpace);
860
861    bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
862    if (drawOwnShadow) {
863        // Turn off CG shadows.
864        CGContextSaveGState(context);
865        CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
866
867        ShadowBlur contextShadow(m_state);
868        contextShadow.drawRectShadow(this, rect);
869    }
870
871    const FloatRect& r = rect.rect();
872    const FloatRoundedRect::Radii& radii = rect.radii();
873    bool equalWidths = (radii.topLeft().width() == radii.topRight().width() && radii.topRight().width() == radii.bottomLeft().width() && radii.bottomLeft().width() == radii.bottomRight().width());
874    bool equalHeights = (radii.topLeft().height() == radii.bottomLeft().height() && radii.bottomLeft().height() == radii.topRight().height() && radii.topRight().height() == radii.bottomRight().height());
875    bool hasCustomFill = m_state.fillGradient || m_state.fillPattern;
876    if (!hasCustomFill && equalWidths && equalHeights && radii.topLeft().width() * 2 == r.width() && radii.topLeft().height() * 2 == r.height())
877        CGContextFillEllipseInRect(context, r);
878    else {
879        Path path;
880        path.addRoundedRect(rect);
881        fillPath(path);
882    }
883
884    if (drawOwnShadow)
885        CGContextRestoreGState(context);
886
887    if (oldFillColor != color || oldColorSpace != colorSpace)
888        setCGFillColor(context, oldFillColor, oldColorSpace);
889}
890
891void GraphicsContext::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color, ColorSpace colorSpace)
892{
893    if (paintingDisabled())
894        return;
895
896    CGContextRef context = platformContext();
897
898    Path path;
899    path.addRect(rect);
900
901    if (!roundedHoleRect.radii().isZero())
902        path.addRoundedRect(roundedHoleRect);
903    else
904        path.addRect(roundedHoleRect.rect());
905
906    WindRule oldFillRule = fillRule();
907    Color oldFillColor = fillColor();
908    ColorSpace oldFillColorSpace = fillColorSpace();
909
910    setFillRule(RULE_EVENODD);
911    setFillColor(color, colorSpace);
912
913    // fillRectWithRoundedHole() assumes that the edges of rect are clipped out, so we only care about shadows cast around inside the hole.
914    bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms;
915    if (drawOwnShadow) {
916        // Turn off CG shadows.
917        CGContextSaveGState(context);
918        CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
919
920        ShadowBlur contextShadow(m_state);
921        contextShadow.drawInsetShadow(this, rect, roundedHoleRect);
922    }
923
924    fillPath(path);
925
926    if (drawOwnShadow)
927        CGContextRestoreGState(context);
928
929    setFillRule(oldFillRule);
930    setFillColor(oldFillColor, oldFillColorSpace);
931}
932
933void GraphicsContext::clip(const FloatRect& rect)
934{
935    if (paintingDisabled())
936        return;
937    CGContextClipToRect(platformContext(), rect);
938    m_data->clip(rect);
939}
940
941void GraphicsContext::clipOut(const FloatRect& rect)
942{
943    if (paintingDisabled())
944        return;
945
946    // FIXME: Using CGRectInfinite is much faster than getting the clip bounding box. However, due
947    // to <rdar://problem/12584492>, CGRectInfinite can't be used with an accelerated context that
948    // has certain transforms that aren't just a translation or a scale. And due to <rdar://problem/14634453>
949    // we cannot use it in for a printing context either.
950    const AffineTransform& ctm = getCTM();
951    bool canUseCGRectInfinite = !wkCGContextIsPDFContext(platformContext()) && (!isAcceleratedContext() || (!ctm.b() && !ctm.c()));
952    CGRect rects[2] = { canUseCGRectInfinite ? CGRectInfinite : CGContextGetClipBoundingBox(platformContext()), rect };
953    CGContextBeginPath(platformContext());
954    CGContextAddRects(platformContext(), rects, 2);
955    CGContextEOClip(platformContext());
956}
957
958void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
959{
960    if (paintingDisabled())
961        return;
962
963    // Why does clipping to an empty path do nothing?
964    // Why is this different from GraphicsContext::clip(const Path&).
965    if (path.isEmpty())
966        return;
967
968    CGContextRef context = platformContext();
969
970    CGContextBeginPath(platformContext());
971    CGContextAddPath(platformContext(), path.platformPath());
972
973    if (clipRule == RULE_EVENODD)
974        CGContextEOClip(context);
975    else
976        CGContextClip(context);
977}
978
979IntRect GraphicsContext::clipBounds() const
980{
981    return enclosingIntRect(CGContextGetClipBoundingBox(platformContext()));
982}
983
984void GraphicsContext::beginPlatformTransparencyLayer(float opacity)
985{
986    if (paintingDisabled())
987        return;
988
989    save();
990
991    CGContextRef context = platformContext();
992    CGContextSetAlpha(context, opacity);
993    CGContextBeginTransparencyLayer(context, 0);
994    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
995}
996
997void GraphicsContext::endPlatformTransparencyLayer()
998{
999    if (paintingDisabled())
1000        return;
1001    CGContextRef context = platformContext();
1002    CGContextEndTransparencyLayer(context);
1003
1004    restore();
1005}
1006
1007bool GraphicsContext::supportsTransparencyLayers()
1008{
1009    return true;
1010}
1011
1012static void applyShadowOffsetWorkaroundIfNeeded(const GraphicsContext& context, CGFloat& xOffset, CGFloat& yOffset)
1013{
1014#if PLATFORM(IOS)
1015    UNUSED_PARAM(context);
1016    UNUSED_PARAM(xOffset);
1017    UNUSED_PARAM(yOffset);
1018#else
1019    if (context.isAcceleratedContext())
1020        return;
1021
1022    if (wkCGContextDrawsWithCorrectShadowOffsets(context.platformContext()))
1023        return;
1024
1025    // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated
1026    // to the desired integer. Also see: <rdar://problem/10056277>
1027    static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128);
1028    if (xOffset > 0)
1029        xOffset += extraShadowOffset;
1030    else if (xOffset < 0)
1031        xOffset -= extraShadowOffset;
1032
1033    if (yOffset > 0)
1034        yOffset += extraShadowOffset;
1035    else if (yOffset < 0)
1036        yOffset -= extraShadowOffset;
1037#endif
1038}
1039
1040void GraphicsContext::setPlatformShadow(const FloatSize& offset, float blur, const Color& color, ColorSpace colorSpace)
1041{
1042    if (paintingDisabled())
1043        return;
1044
1045    // FIXME: we could avoid the shadow setup cost when we know we'll render the shadow ourselves.
1046
1047    CGFloat xOffset = offset.width();
1048    CGFloat yOffset = offset.height();
1049    CGFloat blurRadius = blur;
1050    CGContextRef context = platformContext();
1051
1052    if (!m_state.shadowsIgnoreTransforms) {
1053        CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context);
1054
1055        CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b;
1056        CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d;
1057        CGFloat C = B;
1058        CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d;
1059
1060        CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D)))));
1061
1062        blurRadius = blur * smallEigenvalue;
1063
1064        CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM);
1065
1066        xOffset = offsetInBaseSpace.width;
1067        yOffset = offsetInBaseSpace.height;
1068    }
1069
1070    // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
1071    blurRadius = std::min(blurRadius, narrowPrecisionToCGFloat(1000.0));
1072
1073    applyShadowOffsetWorkaroundIfNeeded(*this, xOffset, yOffset);
1074
1075    // Check for an invalid color, as this means that the color was not set for the shadow
1076    // and we should therefore just use the default shadow color.
1077    if (!color.isValid())
1078        CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius);
1079    else
1080        CGContextSetShadowWithColor(context, CGSizeMake(xOffset, yOffset), blurRadius, cachedCGColor(color, colorSpace));
1081}
1082
1083void GraphicsContext::clearPlatformShadow()
1084{
1085    if (paintingDisabled())
1086        return;
1087    CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
1088}
1089
1090void GraphicsContext::setMiterLimit(float limit)
1091{
1092    if (paintingDisabled())
1093        return;
1094    CGContextSetMiterLimit(platformContext(), limit);
1095}
1096
1097void GraphicsContext::setAlpha(float alpha)
1098{
1099    if (paintingDisabled())
1100        return;
1101    CGContextSetAlpha(platformContext(), alpha);
1102}
1103
1104void GraphicsContext::clearRect(const FloatRect& r)
1105{
1106    if (paintingDisabled())
1107        return;
1108    CGContextClearRect(platformContext(), r);
1109}
1110
1111void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
1112{
1113    if (paintingDisabled())
1114        return;
1115
1116    CGContextRef context = platformContext();
1117
1118    if (m_state.strokeGradient) {
1119        if (hasShadow()) {
1120            const float doubleLineWidth = lineWidth * 2;
1121            float adjustedWidth = ceilf(rect.width() + doubleLineWidth);
1122            float adjustedHeight = ceilf(rect.height() + doubleLineWidth);
1123            FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight));
1124
1125            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
1126
1127            CGContextRef layerContext = CGLayerGetContext(layer);
1128            m_state.strokeThickness = lineWidth;
1129            CGContextSetLineWidth(layerContext, lineWidth);
1130
1131            // Compensate for the line width, otherwise the layer's top-left corner would be
1132            // aligned with the rect's top-left corner. This would result in leaving pixels out of
1133            // the layer on the left and top sides.
1134            const float translationX = lineWidth - rect.x();
1135            const float translationY = lineWidth - rect.y();
1136            CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight);
1137            CGContextTranslateCTM(layerContext, translationX, translationY);
1138
1139            CGContextAddRect(layerContext, rect);
1140            CGContextReplacePathWithStrokedPath(layerContext);
1141            CGContextClip(layerContext);
1142            CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform());
1143            m_state.strokeGradient->paint(layerContext);
1144
1145            const float destinationX = roundf(rect.x() - lineWidth);
1146            const float destinationY = roundf(rect.y() - lineWidth);
1147            CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer);
1148            CGLayerRelease(layer);
1149        } else {
1150            CGContextSaveGState(context);
1151            setStrokeThickness(lineWidth);
1152            CGContextAddRect(context, rect);
1153            CGContextReplacePathWithStrokedPath(context);
1154            CGContextClip(context);
1155            CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
1156            m_state.strokeGradient->paint(this);
1157            CGContextRestoreGState(context);
1158        }
1159        return;
1160    }
1161
1162    if (m_state.strokePattern)
1163        applyStrokePattern();
1164    CGContextStrokeRectWithWidth(context, rect, lineWidth);
1165}
1166
1167void GraphicsContext::setLineCap(LineCap cap)
1168{
1169    if (paintingDisabled())
1170        return;
1171    switch (cap) {
1172    case ButtCap:
1173        CGContextSetLineCap(platformContext(), kCGLineCapButt);
1174        break;
1175    case RoundCap:
1176        CGContextSetLineCap(platformContext(), kCGLineCapRound);
1177        break;
1178    case SquareCap:
1179        CGContextSetLineCap(platformContext(), kCGLineCapSquare);
1180        break;
1181    }
1182}
1183
1184void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
1185{
1186    if (dashOffset < 0) {
1187        float length = 0;
1188        for (size_t i = 0; i < dashes.size(); ++i)
1189            length += static_cast<float>(dashes[i]);
1190        if (length)
1191            dashOffset = fmod(dashOffset, length) + length;
1192    }
1193    CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size());
1194}
1195
1196void GraphicsContext::setLineJoin(LineJoin join)
1197{
1198    if (paintingDisabled())
1199        return;
1200    switch (join) {
1201    case MiterJoin:
1202        CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
1203        break;
1204    case RoundJoin:
1205        CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
1206        break;
1207    case BevelJoin:
1208        CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
1209        break;
1210    }
1211}
1212
1213void GraphicsContext::clip(const Path& path, WindRule fillRule)
1214{
1215    if (paintingDisabled())
1216        return;
1217    CGContextRef context = platformContext();
1218
1219    // CGContextClip does nothing if the path is empty, so in this case, we
1220    // instead clip against a zero rect to reduce the clipping region to
1221    // nothing - which is the intended behavior of clip() if the path is empty.
1222    if (path.isEmpty())
1223        CGContextClipToRect(context, CGRectZero);
1224    else {
1225        CGContextBeginPath(context);
1226        CGContextAddPath(context, path.platformPath());
1227        if (fillRule == RULE_NONZERO)
1228            CGContextClip(context);
1229        else
1230            CGContextEOClip(context);
1231    }
1232    m_data->clip(path);
1233}
1234
1235void GraphicsContext::canvasClip(const Path& path, WindRule fillRule)
1236{
1237    clip(path, fillRule);
1238}
1239
1240void GraphicsContext::clipOut(const Path& path)
1241{
1242    if (paintingDisabled())
1243        return;
1244
1245    CGContextBeginPath(platformContext());
1246    CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
1247    if (!path.isEmpty())
1248        CGContextAddPath(platformContext(), path.platformPath());
1249    CGContextEOClip(platformContext());
1250}
1251
1252void GraphicsContext::scale(const FloatSize& size)
1253{
1254    if (paintingDisabled())
1255        return;
1256    CGContextScaleCTM(platformContext(), size.width(), size.height());
1257    m_data->scale(size);
1258    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1259}
1260
1261void GraphicsContext::rotate(float angle)
1262{
1263    if (paintingDisabled())
1264        return;
1265    CGContextRotateCTM(platformContext(), angle);
1266    m_data->rotate(angle);
1267    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1268}
1269
1270void GraphicsContext::translate(float x, float y)
1271{
1272    if (paintingDisabled())
1273        return;
1274    CGContextTranslateCTM(platformContext(), x, y);
1275    m_data->translate(x, y);
1276    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1277}
1278
1279void GraphicsContext::concatCTM(const AffineTransform& transform)
1280{
1281    if (paintingDisabled())
1282        return;
1283    CGContextConcatCTM(platformContext(), transform);
1284    m_data->concatCTM(transform);
1285    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1286}
1287
1288void GraphicsContext::setCTM(const AffineTransform& transform)
1289{
1290    if (paintingDisabled())
1291        return;
1292    CGContextSetCTM(platformContext(), transform);
1293    m_data->setCTM(transform);
1294    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1295}
1296
1297AffineTransform GraphicsContext::getCTM(IncludeDeviceScale includeScale) const
1298{
1299    if (paintingDisabled())
1300        return AffineTransform();
1301
1302    // The CTM usually includes the deviceScaleFactor except in WebKit 1 when the
1303    // content is non-composited, since the scale factor is integrated at a lower
1304    // level. To guarantee the deviceScale is included, we can use this CG API.
1305    if (includeScale == DefinitelyIncludeDeviceScale)
1306        return CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
1307
1308    return CGContextGetCTM(platformContext());
1309}
1310
1311FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode roundingMode)
1312{
1313    // It is not enough just to round to pixels in device space. The rotation part of the
1314    // affine transform matrix to device space can mess with this conversion if we have a
1315    // rotating image like the hands of the world clock widget. We just need the scale, so
1316    // we get the affine transform matrix and extract the scale.
1317
1318    if (m_data->m_userToDeviceTransformKnownToBeIdentity)
1319        return roundedIntRect(rect);
1320
1321    CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
1322    if (CGAffineTransformIsIdentity(deviceMatrix)) {
1323        m_data->m_userToDeviceTransformKnownToBeIdentity = true;
1324        return roundedIntRect(rect);
1325    }
1326
1327    float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
1328    float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
1329
1330    CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
1331    CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
1332        (rect.y() + rect.height()) * deviceScaleY);
1333
1334    deviceOrigin.x = roundf(deviceOrigin.x);
1335    deviceOrigin.y = roundf(deviceOrigin.y);
1336    if (roundingMode == RoundAllSides) {
1337        deviceLowerRight.x = roundf(deviceLowerRight.x);
1338        deviceLowerRight.y = roundf(deviceLowerRight.y);
1339    } else {
1340        deviceLowerRight.x = deviceOrigin.x + roundf(rect.width() * deviceScaleX);
1341        deviceLowerRight.y = deviceOrigin.y + roundf(rect.height() * deviceScaleY);
1342    }
1343
1344    // Don't let the height or width round to 0 unless either was originally 0
1345    if (deviceOrigin.y == deviceLowerRight.y && rect.height())
1346        deviceLowerRight.y += 1;
1347    if (deviceOrigin.x == deviceLowerRight.x && rect.width())
1348        deviceLowerRight.x += 1;
1349
1350    FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
1351    FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
1352    return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
1353}
1354
1355FloatRect GraphicsContext::computeLineBoundsForText(const FloatPoint& point, float width, bool printing)
1356{
1357    bool dummyBool;
1358    Color dummyColor;
1359    return computeLineBoundsAndAntialiasingModeForText(point, width, printing, dummyBool, dummyColor);
1360}
1361
1362void GraphicsContext::drawLineForText(const FloatPoint& point, float width, bool printing, bool doubleLines)
1363{
1364    DashArray widths;
1365    widths.append(width);
1366    widths.append(0);
1367    drawLinesForText(point, widths, printing, doubleLines);
1368}
1369
1370void GraphicsContext::drawLinesForText(const FloatPoint& point, const DashArray& widths, bool printing, bool doubleLines)
1371{
1372    if (paintingDisabled())
1373        return;
1374
1375    if (widths.size() <= 0)
1376        return;
1377
1378    Color localStrokeColor(strokeColor());
1379
1380    bool shouldAntialiasLine;
1381    FloatRect bounds = computeLineBoundsAndAntialiasingModeForText(point, widths.last(), printing, shouldAntialiasLine, localStrokeColor);
1382    bool fillColorIsNotEqualToStrokeColor = fillColor() != localStrokeColor;
1383
1384    Vector<CGRect, 4> dashBounds;
1385    ASSERT(!(widths.size() % 2));
1386    dashBounds.reserveInitialCapacity(dashBounds.size() / 2);
1387    for (size_t i = 0; i < widths.size(); i += 2)
1388        dashBounds.append(CGRectMake(bounds.x() + widths[i], bounds.y(), widths[i+1] - widths[i], bounds.height()));
1389
1390    if (doubleLines) {
1391        // The space between double underlines is equal to the height of the underline
1392        for (size_t i = 0; i < widths.size(); i += 2)
1393            dashBounds.append(CGRectMake(bounds.x() + widths[i], bounds.y() + 2 * bounds.height(), widths[i+1] - widths[i], bounds.height()));
1394    }
1395
1396#if PLATFORM(IOS)
1397    if (m_state.shouldUseContextColors)
1398#endif
1399        if (fillColorIsNotEqualToStrokeColor)
1400            setCGFillColor(platformContext(), localStrokeColor, strokeColorSpace());
1401
1402    CGContextFillRects(platformContext(), dashBounds.data(), dashBounds.size());
1403
1404#if PLATFORM(IOS)
1405    if (m_state.shouldUseContextColors)
1406#endif
1407        if (fillColorIsNotEqualToStrokeColor)
1408            setCGFillColor(platformContext(), fillColor(), fillColorSpace());
1409}
1410
1411void GraphicsContext::setURLForRect(const URL& link, const IntRect& destRect)
1412{
1413#if !PLATFORM(IOS)
1414    if (paintingDisabled())
1415        return;
1416
1417    RetainPtr<CFURLRef> urlRef = link.createCFURL();
1418    if (!urlRef)
1419        return;
1420
1421    CGContextRef context = platformContext();
1422
1423    // Get the bounding box to handle clipping.
1424    CGRect box = CGContextGetClipBoundingBox(context);
1425
1426    IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
1427    IntRect rect = destRect;
1428    rect.intersect(intBox);
1429
1430    CGPDFContextSetURLForRect(context, urlRef.get(),
1431        CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
1432#else
1433    UNUSED_PARAM(link);
1434    UNUSED_PARAM(destRect);
1435#endif
1436}
1437
1438void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode)
1439{
1440    if (paintingDisabled())
1441        return;
1442
1443    CGInterpolationQuality quality = kCGInterpolationDefault;
1444    switch (mode) {
1445    case InterpolationDefault:
1446        quality = kCGInterpolationDefault;
1447        break;
1448    case InterpolationNone:
1449        quality = kCGInterpolationNone;
1450        break;
1451    case InterpolationLow:
1452        quality = kCGInterpolationLow;
1453        break;
1454    case InterpolationMedium:
1455        quality = kCGInterpolationMedium;
1456        break;
1457    case InterpolationHigh:
1458        quality = kCGInterpolationHigh;
1459        break;
1460    }
1461    CGContextSetInterpolationQuality(platformContext(), quality);
1462}
1463
1464InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1465{
1466    if (paintingDisabled())
1467        return InterpolationDefault;
1468
1469    CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext());
1470    switch (quality) {
1471    case kCGInterpolationDefault:
1472        return InterpolationDefault;
1473    case kCGInterpolationNone:
1474        return InterpolationNone;
1475    case kCGInterpolationLow:
1476        return InterpolationLow;
1477    case kCGInterpolationMedium:
1478        return InterpolationMedium;
1479    case kCGInterpolationHigh:
1480        return InterpolationHigh;
1481    }
1482    return InterpolationDefault;
1483}
1484
1485void GraphicsContext::setAllowsFontSmoothing(bool allowsFontSmoothing)
1486{
1487    UNUSED_PARAM(allowsFontSmoothing);
1488#if PLATFORM(COCOA)
1489    CGContextRef context = platformContext();
1490    CGContextSetAllowsFontSmoothing(context, allowsFontSmoothing);
1491#endif
1492}
1493
1494void GraphicsContext::setIsCALayerContext(bool isLayerContext)
1495{
1496    if (isLayerContext)
1497        m_data->m_contextFlags |= IsLayerCGContext;
1498    else
1499        m_data->m_contextFlags &= ~IsLayerCGContext;
1500}
1501
1502bool GraphicsContext::isCALayerContext() const
1503{
1504    return m_data->m_contextFlags & IsLayerCGContext;
1505}
1506
1507void GraphicsContext::setIsAcceleratedContext(bool isAccelerated)
1508{
1509    if (isAccelerated)
1510        m_data->m_contextFlags |= IsAcceleratedCGContext;
1511    else
1512        m_data->m_contextFlags &= ~IsAcceleratedCGContext;
1513}
1514
1515bool GraphicsContext::isAcceleratedContext() const
1516{
1517    return m_data->m_contextFlags & IsAcceleratedCGContext;
1518}
1519
1520void GraphicsContext::setPlatformTextDrawingMode(TextDrawingModeFlags mode)
1521{
1522    if (paintingDisabled())
1523        return;
1524
1525    CGContextRef context = platformContext();
1526    switch (mode) {
1527    case TextModeFill:
1528        CGContextSetTextDrawingMode(context, kCGTextFill);
1529        break;
1530    case TextModeStroke:
1531        CGContextSetTextDrawingMode(context, kCGTextStroke);
1532        break;
1533    case TextModeFill | TextModeStroke:
1534        CGContextSetTextDrawingMode(context, kCGTextFillStroke);
1535        break;
1536    default:
1537        break;
1538    }
1539}
1540
1541void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace)
1542{
1543    if (paintingDisabled())
1544        return;
1545    setCGStrokeColor(platformContext(), color, colorSpace);
1546}
1547
1548void GraphicsContext::setPlatformStrokeThickness(float thickness)
1549{
1550    if (paintingDisabled())
1551        return;
1552    CGContextSetLineWidth(platformContext(), thickness);
1553}
1554
1555void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
1556{
1557    if (paintingDisabled())
1558        return;
1559    setCGFillColor(platformContext(), color, colorSpace);
1560}
1561
1562void GraphicsContext::setPlatformShouldAntialias(bool enable)
1563{
1564    if (paintingDisabled())
1565        return;
1566    CGContextSetShouldAntialias(platformContext(), enable);
1567}
1568
1569void GraphicsContext::setPlatformShouldSmoothFonts(bool enable)
1570{
1571    if (paintingDisabled())
1572        return;
1573    CGContextSetShouldSmoothFonts(platformContext(), enable);
1574}
1575
1576void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode, BlendMode blendMode)
1577{
1578    if (paintingDisabled())
1579        return;
1580
1581    CGBlendMode target = kCGBlendModeNormal;
1582    if (blendMode != BlendModeNormal) {
1583        switch (blendMode) {
1584        case BlendModeMultiply:
1585            target = kCGBlendModeMultiply;
1586            break;
1587        case BlendModeScreen:
1588            target = kCGBlendModeScreen;
1589            break;
1590        case BlendModeOverlay:
1591            target = kCGBlendModeOverlay;
1592            break;
1593        case BlendModeDarken:
1594            target = kCGBlendModeDarken;
1595            break;
1596        case BlendModeLighten:
1597            target = kCGBlendModeLighten;
1598            break;
1599        case BlendModeColorDodge:
1600            target = kCGBlendModeColorDodge;
1601            break;
1602        case BlendModeColorBurn:
1603            target = kCGBlendModeColorBurn;
1604            break;
1605        case BlendModeHardLight:
1606            target = kCGBlendModeHardLight;
1607            break;
1608        case BlendModeSoftLight:
1609            target = kCGBlendModeSoftLight;
1610            break;
1611        case BlendModeDifference:
1612            target = kCGBlendModeDifference;
1613            break;
1614        case BlendModeExclusion:
1615            target = kCGBlendModeExclusion;
1616            break;
1617        case BlendModeHue:
1618            target = kCGBlendModeHue;
1619            break;
1620        case BlendModeSaturation:
1621            target = kCGBlendModeSaturation;
1622            break;
1623        case BlendModeColor:
1624            target = kCGBlendModeColor;
1625            break;
1626        case BlendModeLuminosity:
1627            target = kCGBlendModeLuminosity;
1628            break;
1629        default:
1630            break;
1631        }
1632    } else {
1633        switch (mode) {
1634        case CompositeClear:
1635            target = kCGBlendModeClear;
1636            break;
1637        case CompositeCopy:
1638            target = kCGBlendModeCopy;
1639            break;
1640        case CompositeSourceOver:
1641            // kCGBlendModeNormal
1642            break;
1643        case CompositeSourceIn:
1644            target = kCGBlendModeSourceIn;
1645            break;
1646        case CompositeSourceOut:
1647            target = kCGBlendModeSourceOut;
1648            break;
1649        case CompositeSourceAtop:
1650            target = kCGBlendModeSourceAtop;
1651            break;
1652        case CompositeDestinationOver:
1653            target = kCGBlendModeDestinationOver;
1654            break;
1655        case CompositeDestinationIn:
1656            target = kCGBlendModeDestinationIn;
1657            break;
1658        case CompositeDestinationOut:
1659            target = kCGBlendModeDestinationOut;
1660            break;
1661        case CompositeDestinationAtop:
1662            target = kCGBlendModeDestinationAtop;
1663            break;
1664        case CompositeXOR:
1665            target = kCGBlendModeXOR;
1666            break;
1667        case CompositePlusDarker:
1668            target = kCGBlendModePlusDarker;
1669            break;
1670        case CompositePlusLighter:
1671            target = kCGBlendModePlusLighter;
1672            break;
1673        case CompositeDifference:
1674            target = kCGBlendModeDifference;
1675            break;
1676        }
1677    }
1678    CGContextSetBlendMode(platformContext(), target);
1679}
1680
1681void GraphicsContext::platformApplyDeviceScaleFactor(float deviceScaleFactor)
1682{
1683    // CoreGraphics expects the base CTM of a HiDPI context to have the scale factor applied to it.
1684    // Failing to change the base level CTM will cause certain CG features, such as focus rings,
1685    // to draw with a scale factor of 1 rather than the actual scale factor.
1686    wkSetBaseCTM(platformContext(), CGAffineTransformScale(CGContextGetBaseCTM(platformContext()), deviceScaleFactor, deviceScaleFactor));
1687}
1688
1689void GraphicsContext::platformFillEllipse(const FloatRect& ellipse)
1690{
1691    if (paintingDisabled())
1692        return;
1693
1694    // CGContextFillEllipseInRect only supports solid colors.
1695    if (m_state.fillGradient || m_state.fillPattern) {
1696        fillEllipseAsPath(ellipse);
1697        return;
1698    }
1699
1700    CGContextRef context = platformContext();
1701    CGContextFillEllipseInRect(context, ellipse);
1702}
1703
1704void GraphicsContext::platformStrokeEllipse(const FloatRect& ellipse)
1705{
1706    if (paintingDisabled())
1707        return;
1708
1709    // CGContextStrokeEllipseInRect only supports solid colors.
1710    if (m_state.strokeGradient || m_state.strokePattern) {
1711        strokeEllipseAsPath(ellipse);
1712        return;
1713    }
1714
1715    CGContextRef context = platformContext();
1716    CGContextStrokeEllipseInRect(context, ellipse);
1717}
1718
1719}
1720