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