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