2011-01-30 Simon Fraser <simon.fraser@apple.com>
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / GraphicsContextCG.cpp
1 /*
2  * Copyright (C) 2003, 2004, 2005, 2006, 2007 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 "KURL.h"
36 #include "Path.h"
37 #include "Pattern.h"
38
39 #include <CoreGraphics/CoreGraphics.h>
40 #include <wtf/MathExtras.h>
41 #include <wtf/OwnArrayPtr.h>
42 #include <wtf/RetainPtr.h>
43 #include <wtf/UnusedParam.h>
44
45 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
46 #include "WebCoreSystemInterface.h"
47 #endif
48
49 #if PLATFORM(WIN)
50 #include <WebKitSystemInterface/WebKitSystemInterface.h>
51 #endif
52
53 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
54
55 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
56 // Building on 10.6 or later: kCGInterpolationMedium is defined in the CGInterpolationQuality enum.
57 #define HAVE_CG_INTERPOLATION_MEDIUM 1
58 #endif
59
60 #if !defined(TARGETING_TIGER) && !defined(TARGETING_LEOPARD)
61 // Targeting 10.6 or later: use kCGInterpolationMedium.
62 #define WTF_USE_CG_INTERPOLATION_MEDIUM 1
63 #endif
64
65 #endif
66
67 using namespace std;
68
69 namespace WebCore {
70
71 static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
72 {
73     CGContextSetFillColorWithColor(context, cachedCGColor(color, colorSpace));
74 }
75
76 static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
77 {
78     CGContextSetStrokeColorWithColor(context, cachedCGColor(color, colorSpace));
79 }
80
81 CGColorSpaceRef deviceRGBColorSpaceRef()
82 {
83     static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB();
84     return deviceSpace;
85 }
86
87 CGColorSpaceRef sRGBColorSpaceRef()
88 {
89     // FIXME: Windows should be able to use kCGColorSpaceSRGB, this is tracked by http://webkit.org/b/31363.
90 #if PLATFORM(WIN) || defined(BUILDING_ON_TIGER)
91     return deviceRGBColorSpaceRef();
92 #else
93     static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
94     return sRGBSpace;
95 #endif
96 }
97
98 CGColorSpaceRef linearRGBColorSpaceRef()
99 {
100     // FIXME: Windows should be able to use kCGColorSpaceGenericRGBLinear, this is tracked by http://webkit.org/b/31363.
101 #if PLATFORM(WIN) || defined(BUILDING_ON_TIGER)
102     return deviceRGBColorSpaceRef();
103 #else
104     static CGColorSpaceRef linearRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGBLinear);
105     return linearRGBSpace;
106 #endif
107 }
108
109 void GraphicsContext::platformInit(CGContextRef cgContext)
110 {
111     m_data = new GraphicsContextPlatformPrivate(cgContext);
112     setPaintingDisabled(!cgContext);
113     if (cgContext) {
114         // Make sure the context starts in sync with our state.
115         setPlatformFillColor(fillColor(), fillColorSpace());
116         setPlatformStrokeColor(strokeColor(), strokeColorSpace());
117     }
118 }
119
120 void GraphicsContext::platformDestroy()
121 {
122     delete m_data;
123 }
124
125 CGContextRef GraphicsContext::platformContext() const
126 {
127     ASSERT(!paintingDisabled());
128     ASSERT(m_data->m_cgContext);
129     return m_data->m_cgContext.get();
130 }
131
132 void GraphicsContext::savePlatformState()
133 {
134     // Note: Do not use this function within this class implementation, since we want to avoid the extra
135     // save of the secondary context (in GraphicsContextPlatformPrivateCG.h).
136     CGContextSaveGState(platformContext());
137     m_data->save();
138 }
139
140 void GraphicsContext::restorePlatformState()
141 {
142     // Note: Do not use this function within this class implementation, since we want to avoid the extra
143     // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h).
144     CGContextRestoreGState(platformContext());
145     m_data->restore();
146     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
147 }
148
149 // Draws a filled rectangle with a stroked border.
150 void GraphicsContext::drawRect(const IntRect& rect)
151 {
152     // FIXME: this function does not handle patterns and gradients
153     // like drawPath does, it probably should.
154     if (paintingDisabled())
155         return;
156
157     CGContextRef context = platformContext();
158
159     CGContextFillRect(context, rect);
160
161     if (strokeStyle() != NoStroke) {
162         // We do a fill of four rects to simulate the stroke of a border.
163         Color oldFillColor = fillColor();
164         if (oldFillColor != strokeColor())
165             setCGFillColor(context, strokeColor(), strokeColorSpace());
166         CGRect rects[4] = {
167             FloatRect(rect.x(), rect.y(), rect.width(), 1),
168             FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
169             FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
170             FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
171         };
172         CGContextFillRects(context, rects, 4);
173         if (oldFillColor != strokeColor())
174             setCGFillColor(context, oldFillColor, fillColorSpace());
175     }
176 }
177
178 // This is only used to draw borders.
179 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
180 {
181     if (paintingDisabled())
182         return;
183
184     if (strokeStyle() == NoStroke)
185         return;
186
187     float width = strokeThickness();
188
189     FloatPoint p1 = point1;
190     FloatPoint p2 = point2;
191     bool isVerticalLine = (p1.x() == p2.x());
192     
193     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
194     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
195     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
196     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
197     if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) {
198         if (isVerticalLine) {
199             p1.move(0, width);
200             p2.move(0, -width);
201         } else {
202             p1.move(width, 0);
203             p2.move(-width, 0);
204         }
205     }
206     
207     if (((int)width) % 2) {
208         if (isVerticalLine) {
209             // We're a vertical line.  Adjust our x.
210             p1.move(0.5f, 0.0f);
211             p2.move(0.5f, 0.0f);
212         } else {
213             // We're a horizontal line. Adjust our y.
214             p1.move(0.0f, 0.5f);
215             p2.move(0.0f, 0.5f);
216         }
217     }
218     
219     int patWidth = 0;
220     switch (strokeStyle()) {
221     case NoStroke:
222     case SolidStroke:
223         break;
224     case DottedStroke:
225         patWidth = (int)width;
226         break;
227     case DashedStroke:
228         patWidth = 3 * (int)width;
229         break;
230     }
231
232     CGContextRef context = platformContext();
233
234     if (shouldAntialias())
235         CGContextSetShouldAntialias(context, false);
236
237     if (patWidth) {
238         CGContextSaveGState(context);
239
240         // Do a rect fill of our endpoints.  This ensures we always have the
241         // appearance of being a border.  We then draw the actual dotted/dashed line.
242         setCGFillColor(context, strokeColor(), strokeColorSpace());  // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
243         if (isVerticalLine) {
244             CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
245             CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
246         } else {
247             CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
248             CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
249         }
250
251         // Example: 80 pixels with a width of 30 pixels.
252         // Remainder is 20.  The maximum pixels of line we could paint
253         // will be 50 pixels.
254         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
255         int remainder = distance % patWidth;
256         int coverage = distance - remainder;
257         int numSegments = coverage / patWidth;
258
259         float patternOffset = 0.0f;
260         // Special case 1px dotted borders for speed.
261         if (patWidth == 1)
262             patternOffset = 1.0f;
263         else {
264             bool evenNumberOfSegments = !(numSegments % 2);
265             if (remainder)
266                 evenNumberOfSegments = !evenNumberOfSegments;
267             if (evenNumberOfSegments) {
268                 if (remainder) {
269                     patternOffset += patWidth - remainder;
270                     patternOffset += remainder / 2;
271                 } else
272                     patternOffset = patWidth / 2;
273             } else {
274                 if (remainder)
275                     patternOffset = (patWidth - remainder)/2;
276             }
277         }
278
279         const CGFloat dottedLine[2] = { patWidth, patWidth };
280         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
281     }
282
283     CGContextBeginPath(context);
284     CGContextMoveToPoint(context, p1.x(), p1.y());
285     CGContextAddLineToPoint(context, p2.x(), p2.y());
286
287     CGContextStrokePath(context);
288
289     if (patWidth)
290         CGContextRestoreGState(context);
291
292     if (shouldAntialias())
293         CGContextSetShouldAntialias(context, true);
294 }
295
296 // This method is only used to draw the little circles used in lists.
297 void GraphicsContext::drawEllipse(const IntRect& rect)
298 {
299     if (paintingDisabled())
300         return;
301
302     Path path;
303     path.addEllipse(rect);
304     drawPath(path);
305 }
306
307
308 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
309 {
310     if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f)
311         return;
312
313     CGContextRef context = platformContext();
314     CGContextSaveGState(context);
315     CGContextBeginPath(context);
316     CGContextSetShouldAntialias(context, false);
317
318     int x = rect.x();
319     int y = rect.y();
320     float w = (float)rect.width();
321     float h = (float)rect.height();
322     float scaleFactor = h / w;
323     float reverseScaleFactor = w / h;
324
325     if (w != h)
326         scale(FloatSize(1, scaleFactor));
327
328     float hRadius = w / 2;
329     float vRadius = h / 2;
330     float fa = startAngle;
331     float falen =  fa + angleSpan;
332     float start = -fa * piFloat / 180.0f;
333     float end = -falen * piFloat / 180.0f;
334     CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
335
336     if (w != h)
337         scale(FloatSize(1, reverseScaleFactor));
338
339     float width = strokeThickness();
340     int patWidth = 0;
341
342     switch (strokeStyle()) {
343     case DottedStroke:
344         patWidth = (int)(width / 2);
345         break;
346     case DashedStroke:
347         patWidth = 3 * (int)(width / 2);
348         break;
349     default:
350         break;
351     }
352
353     if (patWidth) {
354         // Example: 80 pixels with a width of 30 pixels.
355         // Remainder is 20.  The maximum pixels of line we could paint
356         // will be 50 pixels.
357         int distance;
358         if (hRadius == vRadius)
359             distance = static_cast<int>((piFloat * hRadius) / 2.0f);
360         else // We are elliptical and will have to estimate the distance
361             distance = static_cast<int>((piFloat * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0f)) / 2.0f);
362
363         int remainder = distance % patWidth;
364         int coverage = distance - remainder;
365         int numSegments = coverage / patWidth;
366
367         float patternOffset = 0.0f;
368         // Special case 1px dotted borders for speed.
369         if (patWidth == 1)
370             patternOffset = 1.0f;
371         else {
372             bool evenNumberOfSegments = !(numSegments % 2);
373             if (remainder)
374                 evenNumberOfSegments = !evenNumberOfSegments;
375             if (evenNumberOfSegments) {
376                 if (remainder) {
377                     patternOffset += patWidth - remainder;
378                     patternOffset += remainder / 2.0f;
379                 } else
380                     patternOffset = patWidth / 2.0f;
381             } else {
382                 if (remainder)
383                     patternOffset = (patWidth - remainder) / 2.0f;
384             }
385         }
386
387         const CGFloat dottedLine[2] = { patWidth, patWidth };
388         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
389     }
390
391     CGContextStrokePath(context);
392
393     CGContextRestoreGState(context);
394 }
395
396 static void addConvexPolygonToPath(Path& path, size_t numberOfPoints, const FloatPoint* points)
397 {
398     ASSERT(numberOfPoints > 0);
399
400     path.moveTo(points[0]);
401     for (size_t i = 1; i < numberOfPoints; ++i)
402         path.addLineTo(points[i]);
403     path.closeSubpath();
404 }
405
406 void GraphicsContext::drawConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialiased)
407 {
408     if (paintingDisabled())
409         return;
410
411     if (numberOfPoints <= 1)
412         return;
413
414     CGContextRef context = platformContext();
415
416     if (antialiased != shouldAntialias())
417         CGContextSetShouldAntialias(context, antialiased);
418
419     Path path;
420     addConvexPolygonToPath(path, numberOfPoints, points);
421     drawPath(path);
422
423     if (antialiased != shouldAntialias())
424         CGContextSetShouldAntialias(context, shouldAntialias());
425 }
426
427 void GraphicsContext::clipConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialias)
428 {
429     if (paintingDisabled())
430         return;
431
432     if (numberOfPoints <= 1)
433         return;
434
435     CGContextRef context = platformContext();
436
437     if (antialias != shouldAntialias())
438         CGContextSetShouldAntialias(context, antialias);
439
440     Path path;
441     addConvexPolygonToPath(path, numberOfPoints, points);
442     clipPath(path, RULE_NONZERO);
443
444     if (antialias != shouldAntialias())
445         CGContextSetShouldAntialias(context, shouldAntialias());
446 }
447
448 void GraphicsContext::applyStrokePattern()
449 {
450     CGContextRef cgContext = platformContext();
451
452     RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_state.strokePattern->createPlatformPattern(getCTM()));
453     if (!platformPattern)
454         return;
455
456     RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0));
457     CGContextSetStrokeColorSpace(cgContext, patternSpace.get());
458
459     const CGFloat patternAlpha = 1;
460     CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha);
461 }
462
463 void GraphicsContext::applyFillPattern()
464 {
465     CGContextRef cgContext = platformContext();
466
467     RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_state.fillPattern->createPlatformPattern(getCTM()));
468     if (!platformPattern)
469         return;
470
471     RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0));
472     CGContextSetFillColorSpace(cgContext, patternSpace.get());
473
474     const CGFloat patternAlpha = 1;
475     CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha);
476 }
477
478 static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode)
479 {
480     bool shouldFill = state.fillPattern || state.fillColor.alpha();
481     bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha());
482     bool useEOFill = state.fillRule == RULE_EVENODD;
483
484     if (shouldFill) {
485         if (shouldStroke) {
486             if (useEOFill)
487                 mode = kCGPathEOFillStroke;
488             else
489                 mode = kCGPathFillStroke;
490         } else { // fill, no stroke
491             if (useEOFill)
492                 mode = kCGPathEOFill;
493             else
494                 mode = kCGPathFill;
495         }
496     } else {
497         // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used,
498         // but the compiler will not complain about an uninitialized variable.
499         mode = kCGPathStroke;
500     }
501
502     return shouldFill || shouldStroke;
503 }
504
505 void GraphicsContext::drawPath(const Path& path)
506 {
507     if (paintingDisabled())
508         return;
509
510     CGContextRef context = platformContext();
511     const GraphicsContextState& state = m_state;
512
513     if (state.fillGradient || state.strokeGradient) {
514         // We don't have any optimized way to fill & stroke a path using gradients
515         // FIXME: Be smarter about this.
516         fillPath(path);
517         strokePath(path);
518         return;
519     }
520
521     CGContextBeginPath(context);
522     CGContextAddPath(context, path.platformPath());
523
524     if (state.fillPattern)
525         applyFillPattern();
526     if (state.strokePattern)
527         applyStrokePattern();
528
529     CGPathDrawingMode drawingMode;
530     if (calculateDrawingMode(state, drawingMode))
531         CGContextDrawPath(context, drawingMode);
532 }
533
534 static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule)
535 {
536     if (fillRule == RULE_EVENODD)
537         CGContextEOFillPath(context);
538     else
539         CGContextFillPath(context);
540 }
541
542 void GraphicsContext::fillPath(const Path& path)
543 {
544     if (paintingDisabled())
545         return;
546
547     CGContextRef context = platformContext();
548
549     if (m_state.fillGradient) {
550         if (hasShadow()) {
551             FloatRect rect = path.boundingRect();
552             CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0);
553             CGContextRef layerContext = CGLayerGetContext(layer);
554
555             CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
556             CGContextBeginPath(layerContext);
557             CGContextAddPath(layerContext, path.platformPath());
558             CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
559
560             if (fillRule() == RULE_EVENODD)
561                 CGContextEOClip(layerContext);
562             else
563                 CGContextClip(layerContext);
564
565             m_state.fillGradient->paint(layerContext);
566             CGContextDrawLayerAtPoint(context, CGPointMake(rect.left(), rect.top()), layer);
567             CGLayerRelease(layer);
568         } else {
569             CGContextBeginPath(context);
570             CGContextAddPath(context, path.platformPath());
571             CGContextSaveGState(context);
572             CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
573
574             if (fillRule() == RULE_EVENODD)
575                 CGContextEOClip(context);
576             else
577                 CGContextClip(context);
578
579             m_state.fillGradient->paint(this);
580             CGContextRestoreGState(context);
581         }
582
583         return;
584     }
585
586     CGContextBeginPath(context);
587     CGContextAddPath(context, path.platformPath());
588
589     if (m_state.fillPattern)
590         applyFillPattern();
591     fillPathWithFillRule(context, fillRule());
592 }
593
594 void GraphicsContext::strokePath(const Path& path)
595 {
596     if (paintingDisabled())
597         return;
598
599     CGContextRef context = platformContext();
600
601     CGContextBeginPath(context);
602     CGContextAddPath(context, path.platformPath());
603
604     if (m_state.strokeGradient) {
605         CGContextSaveGState(context);
606         CGContextReplacePathWithStrokedPath(context);
607         CGContextClip(context);
608         CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
609         m_state.strokeGradient->paint(this);
610         CGContextRestoreGState(context);
611         return;
612     }
613
614     if (m_state.strokePattern)
615         applyStrokePattern();
616     CGContextStrokePath(context);
617 }
618
619 void GraphicsContext::fillRect(const FloatRect& rect)
620 {
621     if (paintingDisabled())
622         return;
623
624     CGContextRef context = platformContext();
625
626     if (m_state.fillGradient) {
627         CGContextSaveGState(context);
628         if (hasShadow()) {
629             CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0);
630             CGContextRef layerContext = CGLayerGetContext(layer);
631
632             CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
633             CGContextAddRect(layerContext, rect);
634             CGContextClip(layerContext);
635
636             CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
637             m_state.fillGradient->paint(layerContext);
638             CGContextDrawLayerAtPoint(context, CGPointMake(rect.left(), rect.top()), layer);
639             CGLayerRelease(layer);
640         } else {
641             CGContextClipToRect(context, rect);
642             CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
643             m_state.fillGradient->paint(this);
644         }
645         CGContextRestoreGState(context);
646         return;
647     }
648
649     if (m_state.fillPattern)
650         applyFillPattern();
651     CGContextFillRect(context, rect);
652 }
653
654 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
655 {
656     if (paintingDisabled())
657         return;
658
659     CGContextRef context = platformContext();
660     Color oldFillColor = fillColor();
661     ColorSpace oldColorSpace = fillColorSpace();
662
663     if (oldFillColor != color || oldColorSpace != colorSpace)
664         setCGFillColor(context, color, colorSpace);
665
666     CGContextFillRect(context, rect);
667
668     if (oldFillColor != color || oldColorSpace != colorSpace)
669         setCGFillColor(context, oldFillColor, oldColorSpace);
670 }
671
672 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace)
673 {
674     if (paintingDisabled())
675         return;
676
677     CGContextRef context = platformContext();
678     Color oldFillColor = fillColor();
679     ColorSpace oldColorSpace = fillColorSpace();
680
681     if (oldFillColor != color || oldColorSpace != colorSpace)
682         setCGFillColor(context, color, colorSpace);
683
684     Path path;
685     path.addRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight);
686     fillPath(path);
687
688     if (oldFillColor != color || oldColorSpace != colorSpace)
689         setCGFillColor(context, oldFillColor, oldColorSpace);
690 }
691
692 void GraphicsContext::clip(const FloatRect& rect)
693 {
694     if (paintingDisabled())
695         return;
696     CGContextClipToRect(platformContext(), rect);
697     m_data->clip(rect);
698 }
699
700 void GraphicsContext::clipOut(const IntRect& rect)
701 {
702     if (paintingDisabled())
703         return;
704
705     CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect };
706     CGContextBeginPath(platformContext());
707     CGContextAddRects(platformContext(), rects, 2);
708     CGContextEOClip(platformContext());
709 }
710
711 void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
712 {
713     if (paintingDisabled())
714         return;
715
716     if (path.isEmpty())
717         return;
718
719     CGContextRef context = platformContext();
720
721     CGContextBeginPath(platformContext());
722     CGContextAddPath(platformContext(), path.platformPath());
723
724     if (clipRule == RULE_EVENODD)
725         CGContextEOClip(context);
726     else
727         CGContextClip(context);
728 }
729
730 IntRect GraphicsContext::clipBounds() const
731 {
732     return enclosingIntRect(CGContextGetClipBoundingBox(platformContext()));
733 }
734
735 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
736 {
737     if (paintingDisabled())
738         return;
739
740     clip(rect);
741     CGContextRef context = platformContext();
742
743     // Add outer ellipse
744     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
745     // Add inner ellipse.
746     CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
747         rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
748
749     CGContextEOClip(context);
750 }
751
752 void GraphicsContext::beginTransparencyLayer(float opacity)
753 {
754     if (paintingDisabled())
755         return;
756     CGContextRef context = platformContext();
757     CGContextSaveGState(context);
758     CGContextSetAlpha(context, opacity);
759     CGContextBeginTransparencyLayer(context, 0);
760     m_data->beginTransparencyLayer();
761     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
762 }
763
764 void GraphicsContext::endTransparencyLayer()
765 {
766     if (paintingDisabled())
767         return;
768     CGContextRef context = platformContext();
769     CGContextEndTransparencyLayer(context);
770     CGContextRestoreGState(context);
771     m_data->endTransparencyLayer();
772     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
773 }
774
775 void GraphicsContext::setPlatformShadow(const FloatSize& offset, float blur, const Color& color, ColorSpace colorSpace)
776 {
777     if (paintingDisabled())
778         return;
779     CGFloat xOffset = offset.width();
780     CGFloat yOffset = offset.height();
781     CGFloat blurRadius = blur;
782     CGContextRef context = platformContext();
783
784     if (!m_state.shadowsIgnoreTransforms) {
785         CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context);
786
787         CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b;
788         CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d;
789         CGFloat C = B;
790         CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d;
791
792         CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D)))));
793
794         // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
795         blurRadius = min(blur * smallEigenvalue, narrowPrecisionToCGFloat(1000.0));
796
797         CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM);
798
799         xOffset = offsetInBaseSpace.width;
800         yOffset = offsetInBaseSpace.height;
801     }
802
803     // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated
804     // to the desired integer.
805     static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128);
806     if (xOffset > 0)
807         xOffset += extraShadowOffset;
808     else if (xOffset < 0)
809         xOffset -= extraShadowOffset;
810
811     if (yOffset > 0)
812         yOffset += extraShadowOffset;
813     else if (yOffset < 0)
814         yOffset -= extraShadowOffset;
815
816     // Check for an invalid color, as this means that the color was not set for the shadow
817     // and we should therefore just use the default shadow color.
818     if (!color.isValid())
819         CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius);
820     else
821         CGContextSetShadowWithColor(context, CGSizeMake(xOffset, yOffset), blurRadius, cachedCGColor(color, colorSpace));
822 }
823
824 void GraphicsContext::clearPlatformShadow()
825 {
826     if (paintingDisabled())
827         return;
828     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
829 }
830
831 void GraphicsContext::setMiterLimit(float limit)
832 {
833     if (paintingDisabled())
834         return;
835     CGContextSetMiterLimit(platformContext(), limit);
836 }
837
838 void GraphicsContext::setAlpha(float alpha)
839 {
840     if (paintingDisabled())
841         return;
842     CGContextSetAlpha(platformContext(), alpha);
843 }
844
845 void GraphicsContext::clearRect(const FloatRect& r)
846 {
847     if (paintingDisabled())
848         return;
849     CGContextClearRect(platformContext(), r);
850 }
851
852 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
853 {
854     if (paintingDisabled())
855         return;
856
857     CGContextRef context = platformContext();
858
859     if (m_state.strokeGradient) {
860         CGContextSaveGState(context);
861         setStrokeThickness(lineWidth);
862         CGContextAddRect(context, r);
863         CGContextReplacePathWithStrokedPath(context);
864         CGContextClip(context);
865         m_state.strokeGradient->paint(this);
866         CGContextRestoreGState(context);
867         return;
868     }
869
870     if (m_state.strokePattern)
871         applyStrokePattern();
872     CGContextStrokeRectWithWidth(context, r, lineWidth);
873 }
874
875 void GraphicsContext::setLineCap(LineCap cap)
876 {
877     if (paintingDisabled())
878         return;
879     switch (cap) {
880     case ButtCap:
881         CGContextSetLineCap(platformContext(), kCGLineCapButt);
882         break;
883     case RoundCap:
884         CGContextSetLineCap(platformContext(), kCGLineCapRound);
885         break;
886     case SquareCap:
887         CGContextSetLineCap(platformContext(), kCGLineCapSquare);
888         break;
889     }
890 }
891
892 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
893 {
894     CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size());
895 }
896
897 void GraphicsContext::setLineJoin(LineJoin join)
898 {
899     if (paintingDisabled())
900         return;
901     switch (join) {
902     case MiterJoin:
903         CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
904         break;
905     case RoundJoin:
906         CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
907         break;
908     case BevelJoin:
909         CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
910         break;
911     }
912 }
913
914 void GraphicsContext::clip(const Path& path)
915 {
916     if (paintingDisabled())
917         return;
918     CGContextRef context = platformContext();
919
920     // CGContextClip does nothing if the path is empty, so in this case, we
921     // instead clip against a zero rect to reduce the clipping region to
922     // nothing - which is the intended behavior of clip() if the path is empty.    
923     if (path.isEmpty())
924         CGContextClipToRect(context, CGRectZero);
925     else {
926         CGContextBeginPath(context);
927         CGContextAddPath(context, path.platformPath());
928         CGContextClip(context);
929     }
930     m_data->clip(path);
931 }
932
933 void GraphicsContext::canvasClip(const Path& path)
934 {
935     clip(path);
936 }
937
938 void GraphicsContext::clipOut(const Path& path)
939 {
940     if (paintingDisabled())
941         return;
942
943     CGContextBeginPath(platformContext());
944     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
945     CGContextAddPath(platformContext(), path.platformPath());
946     CGContextEOClip(platformContext());
947 }
948
949 void GraphicsContext::scale(const FloatSize& size)
950 {
951     if (paintingDisabled())
952         return;
953     CGContextScaleCTM(platformContext(), size.width(), size.height());
954     m_data->scale(size);
955     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
956 }
957
958 void GraphicsContext::rotate(float angle)
959 {
960     if (paintingDisabled())
961         return;
962     CGContextRotateCTM(platformContext(), angle);
963     m_data->rotate(angle);
964     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
965 }
966
967 void GraphicsContext::translate(float x, float y)
968 {
969     if (paintingDisabled())
970         return;
971     CGContextTranslateCTM(platformContext(), x, y);
972     m_data->translate(x, y);
973     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
974 }
975
976 void GraphicsContext::concatCTM(const AffineTransform& transform)
977 {
978     if (paintingDisabled())
979         return;
980     CGContextConcatCTM(platformContext(), transform);
981     m_data->concatCTM(transform);
982     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
983 }
984
985 AffineTransform GraphicsContext::getCTM() const
986 {
987     CGAffineTransform t = CGContextGetCTM(platformContext());
988     return AffineTransform(t.a, t.b, t.c, t.d, t.tx, t.ty);
989 }
990
991 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
992 {
993     // It is not enough just to round to pixels in device space. The rotation part of the
994     // affine transform matrix to device space can mess with this conversion if we have a
995     // rotating image like the hands of the world clock widget. We just need the scale, so
996     // we get the affine transform matrix and extract the scale.
997
998     if (m_data->m_userToDeviceTransformKnownToBeIdentity)
999         return rect;
1000
1001     CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
1002     if (CGAffineTransformIsIdentity(deviceMatrix)) {
1003         m_data->m_userToDeviceTransformKnownToBeIdentity = true;
1004         return rect;
1005     }
1006
1007     float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
1008     float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
1009
1010     CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
1011     CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
1012         (rect.y() + rect.height()) * deviceScaleY);
1013
1014     deviceOrigin.x = roundf(deviceOrigin.x);
1015     deviceOrigin.y = roundf(deviceOrigin.y);
1016     deviceLowerRight.x = roundf(deviceLowerRight.x);
1017     deviceLowerRight.y = roundf(deviceLowerRight.y);
1018
1019     // Don't let the height or width round to 0 unless either was originally 0
1020     if (deviceOrigin.y == deviceLowerRight.y && rect.height())
1021         deviceLowerRight.y += 1;
1022     if (deviceOrigin.x == deviceLowerRight.x && rect.width())
1023         deviceLowerRight.x += 1;
1024
1025     FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
1026     FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
1027     return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
1028 }
1029
1030 void GraphicsContext::drawLineForText(const IntPoint& point, int width, bool printing)
1031 {
1032     if (paintingDisabled())
1033         return;
1034
1035     if (width <= 0)
1036         return;
1037
1038     float x = point.x();
1039     float y = point.y();
1040     float lineLength = width;
1041
1042     // Use a minimum thickness of 0.5 in user space.
1043     // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use.
1044     float thickness = max(strokeThickness(), 0.5f);
1045
1046     bool restoreAntialiasMode = false;
1047
1048     if (!printing) {
1049         // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
1050         float adjustedThickness = max(thickness, 1.0f);
1051
1052         // FIXME: This should be done a better way.
1053         // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space
1054         // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels
1055         // in device space will make the underlines too thick.
1056         CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness));
1057         if (lineRect.size.height < thickness * 2.0) {
1058             x = lineRect.origin.x;
1059             y = lineRect.origin.y;
1060             lineLength = lineRect.size.width;
1061             thickness = lineRect.size.height;
1062             if (shouldAntialias()) {
1063                 CGContextSetShouldAntialias(platformContext(), false);
1064                 restoreAntialiasMode = true;
1065             }
1066         }
1067     }
1068
1069     if (fillColor() != strokeColor())
1070         setCGFillColor(platformContext(), strokeColor(), strokeColorSpace());
1071     CGContextFillRect(platformContext(), CGRectMake(x, y, lineLength, thickness));
1072     if (fillColor() != strokeColor())
1073         setCGFillColor(platformContext(), fillColor(), fillColorSpace());
1074
1075     if (restoreAntialiasMode)
1076         CGContextSetShouldAntialias(platformContext(), true);
1077 }
1078
1079 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
1080 {
1081     if (paintingDisabled())
1082         return;
1083
1084     RetainPtr<CFURLRef> urlRef(AdoptCF, link.createCFURL());
1085     if (!urlRef)
1086         return;
1087
1088     CGContextRef context = platformContext();
1089
1090     // Get the bounding box to handle clipping.
1091     CGRect box = CGContextGetClipBoundingBox(context);
1092
1093     IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
1094     IntRect rect = destRect;
1095     rect.intersect(intBox);
1096
1097     CGPDFContextSetURLForRect(context, urlRef.get(),
1098         CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
1099 }
1100
1101 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode)
1102 {
1103     if (paintingDisabled())
1104         return;
1105
1106     CGInterpolationQuality quality = kCGInterpolationDefault;
1107     switch (mode) {
1108     case InterpolationDefault:
1109         quality = kCGInterpolationDefault;
1110         break;
1111     case InterpolationNone:
1112         quality = kCGInterpolationNone;
1113         break;
1114     case InterpolationLow:
1115         quality = kCGInterpolationLow;
1116         break;
1117
1118     // Fall through to InterpolationHigh if kCGInterpolationMedium is not usable.
1119     case InterpolationMedium:
1120 #if USE(CG_INTERPOLATION_MEDIUM)
1121         quality = kCGInterpolationMedium;
1122         break;
1123 #endif
1124     case InterpolationHigh:
1125         quality = kCGInterpolationHigh;
1126         break;
1127     }
1128     CGContextSetInterpolationQuality(platformContext(), quality);
1129 }
1130
1131 InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1132 {
1133     if (paintingDisabled())
1134         return InterpolationDefault;
1135
1136     CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext());
1137     switch (quality) {
1138     case kCGInterpolationDefault:
1139         return InterpolationDefault;
1140     case kCGInterpolationNone:
1141         return InterpolationNone;
1142     case kCGInterpolationLow:
1143         return InterpolationLow;
1144 #if HAVE(CG_INTERPOLATION_MEDIUM)
1145     // kCGInterpolationMedium is known to be present in the CGInterpolationQuality enum.
1146     case kCGInterpolationMedium:
1147 #if USE(CG_INTERPOLATION_MEDIUM)
1148         // Only map to InterpolationMedium if targeting a system that understands it.
1149         return InterpolationMedium;
1150 #else
1151         return InterpolationDefault;
1152 #endif  // USE(CG_INTERPOLATION_MEDIUM)
1153 #endif  // HAVE(CG_INTERPOLATION_MEDIUM)
1154     case kCGInterpolationHigh:
1155         return InterpolationHigh;
1156     }
1157     return InterpolationDefault;
1158 }
1159
1160 void GraphicsContext::setAllowsFontSmoothing(bool allowsFontSmoothing)
1161 {
1162     UNUSED_PARAM(allowsFontSmoothing);
1163 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
1164     CGContextRef context = platformContext();
1165     CGContextSetAllowsFontSmoothing(context, allowsFontSmoothing);
1166 #endif
1167 }
1168
1169 void GraphicsContext::setPlatformTextDrawingMode(TextDrawingModeFlags mode)
1170 {
1171     if (paintingDisabled())
1172         return;
1173
1174     // Wow, wish CG had used bits here.
1175     CGContextRef context = platformContext();
1176     switch (mode) {
1177     case TextModeInvisible:
1178         CGContextSetTextDrawingMode(context, kCGTextInvisible);
1179         break;
1180     case TextModeFill:
1181         CGContextSetTextDrawingMode(context, kCGTextFill);
1182         break;
1183     case TextModeStroke:
1184         CGContextSetTextDrawingMode(context, kCGTextStroke);
1185         break;
1186     case TextModeFill | TextModeStroke:
1187         CGContextSetTextDrawingMode(context, kCGTextFillStroke);
1188         break;
1189     case TextModeClip:
1190         CGContextSetTextDrawingMode(context, kCGTextClip);
1191         break;
1192     case TextModeFill | TextModeClip:
1193         CGContextSetTextDrawingMode(context, kCGTextFillClip);
1194         break;
1195     case TextModeStroke | TextModeClip:
1196         CGContextSetTextDrawingMode(context, kCGTextStrokeClip);
1197         break;
1198     case TextModeFill | TextModeStroke | TextModeClip:
1199         CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip);
1200         break;
1201     default:
1202         break;
1203     }
1204 }
1205
1206 void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace)
1207 {
1208     if (paintingDisabled())
1209         return;
1210     setCGStrokeColor(platformContext(), color, colorSpace);
1211 }
1212
1213 void GraphicsContext::setPlatformStrokeThickness(float thickness)
1214 {
1215     if (paintingDisabled())
1216         return;
1217     CGContextSetLineWidth(platformContext(), thickness);
1218 }
1219
1220 void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
1221 {
1222     if (paintingDisabled())
1223         return;
1224     setCGFillColor(platformContext(), color, colorSpace);
1225 }
1226
1227 void GraphicsContext::setPlatformShouldAntialias(bool enable)
1228 {
1229     if (paintingDisabled())
1230         return;
1231     CGContextSetShouldAntialias(platformContext(), enable);
1232 }
1233
1234 void GraphicsContext::setPlatformShouldSmoothFonts(bool enable)
1235 {
1236     if (paintingDisabled())
1237         return;
1238     CGContextSetShouldSmoothFonts(platformContext(), enable);
1239 }
1240
1241 #ifndef BUILDING_ON_TIGER // Tiger's setPlatformCompositeOperation() is defined in GraphicsContextMac.mm.
1242 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode)
1243 {
1244     if (paintingDisabled())
1245         return;
1246
1247     CGBlendMode target = kCGBlendModeNormal;
1248     switch (mode) {
1249     case CompositeClear:
1250         target = kCGBlendModeClear;
1251         break;
1252     case CompositeCopy:
1253         target = kCGBlendModeCopy;
1254         break;
1255     case CompositeSourceOver:
1256         //kCGBlendModeNormal
1257         break;
1258     case CompositeSourceIn:
1259         target = kCGBlendModeSourceIn;
1260         break;
1261     case CompositeSourceOut:
1262         target = kCGBlendModeSourceOut;
1263         break;
1264     case CompositeSourceAtop:
1265         target = kCGBlendModeSourceAtop;
1266         break;
1267     case CompositeDestinationOver:
1268         target = kCGBlendModeDestinationOver;
1269         break;
1270     case CompositeDestinationIn:
1271         target = kCGBlendModeDestinationIn;
1272         break;
1273     case CompositeDestinationOut:
1274         target = kCGBlendModeDestinationOut;
1275         break;
1276     case CompositeDestinationAtop:
1277         target = kCGBlendModeDestinationAtop;
1278         break;
1279     case CompositeXOR:
1280         target = kCGBlendModeXOR;
1281         break;
1282     case CompositePlusDarker:
1283         target = kCGBlendModePlusDarker;
1284         break;
1285     case CompositeHighlight:
1286         // currently unsupported
1287         break;
1288     case CompositePlusLighter:
1289         target = kCGBlendModePlusLighter;
1290         break;
1291     }
1292     CGContextSetBlendMode(platformContext(), target);
1293 }
1294 #endif
1295
1296 }