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