Fix 1% regression on the PLT. Make sure roundToDevicePixels does no
[WebKit-https.git] / WebCore / platform / graphics / cg / GraphicsContextCG.cpp
1 /*
2  * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #define _USE_MATH_DEFINES 1
27 #include "config.h"
28 #include "GraphicsContext.h"
29
30 #if PLATFORM(CG)
31
32 #include "AffineTransform.h"
33 #include "KURL.h"
34 #include "Path.h"
35 #include <wtf/MathExtras.h>
36
37 #include <GraphicsContextPlatformPrivate.h> // FIXME: Temporary.
38
39 using namespace std;
40
41 namespace WebCore {
42
43 static void setCGFillColor(CGContextRef context, const Color& color)
44 {
45     CGFloat red, green, blue, alpha;
46     color.getRGBA(red, green, blue, alpha);
47     CGContextSetRGBFillColor(context, red, green, blue, alpha);
48 }
49
50 static void setCGStrokeColor(CGContextRef context, const Color& color)
51 {
52     CGFloat red, green, blue, alpha;
53     color.getRGBA(red, green, blue, alpha);
54     CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
55 }
56
57 GraphicsContext::GraphicsContext(CGContextRef cgContext)
58     : m_common(createGraphicsContextPrivate())
59     , m_data(new GraphicsContextPlatformPrivate(cgContext))
60 {
61     setPaintingDisabled(!cgContext);
62     if (cgContext) {
63         // Make sure the context starts in sync with our state.
64         setPlatformFillColor(fillColor());
65         setPlatformStrokeColor(strokeColor());
66     }
67 }
68
69 GraphicsContext::~GraphicsContext()
70 {
71     destroyGraphicsContextPrivate(m_common);
72     delete m_data;
73 }
74
75 void GraphicsContext::setFocusRingClip(const IntRect& r)
76 {
77     // This method only exists to work around bugs in Mac focus ring clipping.
78     m_data->m_focusRingClip = r;
79 }
80
81 void GraphicsContext::clearFocusRingClip()
82 {
83     // This method only exists to work around bugs in Mac focus ring clipping.
84     m_data->m_focusRingClip = IntRect();
85 }
86
87 CGContextRef GraphicsContext::platformContext() const
88 {
89     ASSERT(!paintingDisabled());
90     ASSERT(m_data->m_cgContext);
91     return m_data->m_cgContext;
92 }
93
94 void GraphicsContext::savePlatformState()
95 {
96     // Note: Do not use this function within this class implementation, since we want to avoid the extra
97     // save of the secondary context (in GraphicsContextPlatformPrivate.h).
98     CGContextSaveGState(platformContext());
99     m_data->save();
100 }
101
102 void GraphicsContext::restorePlatformState()
103 {
104     // Note: Do not use this function within this class implementation, since we want to avoid the extra
105     // restore of the secondary context (in GraphicsContextPlatformPrivate.h).
106     CGContextRestoreGState(platformContext());
107     m_data->restore();
108 }
109
110 // Draws a filled rectangle with a stroked border.
111 void GraphicsContext::drawRect(const IntRect& rect)
112 {
113     if (paintingDisabled())
114         return;
115
116     CGContextRef context = platformContext();
117
118     if (fillColor().alpha())
119         CGContextFillRect(context, rect);
120
121     if (strokeStyle() != NoStroke && strokeColor().alpha()) {
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.
139 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
140 {
141     if (paintingDisabled())
142         return;
143
144     if (strokeStyle() == NoStroke || !strokeColor().alpha())
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.5, 0);
171             p2.move(0.5, 0);
172         } else {
173             // We're a horizontal line. Adjust our y.
174             p1.move(0, 0.5);
175             p2.move(0, 0.5);
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     CGContextSaveGState(context);
194
195     CGContextSetShouldAntialias(context, false);
196
197     if (patWidth) {
198         // Do a rect fill of our endpoints.  This ensures we always have the
199         // appearance of being a border.  We then draw the actual dotted/dashed line.
200         setCGFillColor(context, strokeColor());  // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
201         if (isVerticalLine) {
202             CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
203             CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
204         } else {
205             CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
206             CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
207         }
208
209         // Example: 80 pixels with a width of 30 pixels.
210         // Remainder is 20.  The maximum pixels of line we could paint
211         // will be 50 pixels.
212         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
213         int remainder = distance % patWidth;
214         int coverage = distance - remainder;
215         int numSegments = coverage / patWidth;
216
217         float patternOffset = 0;
218         // Special case 1px dotted borders for speed.
219         if (patWidth == 1)
220             patternOffset = 1.0;
221         else {
222             bool evenNumberOfSegments = numSegments % 2 == 0;
223             if (remainder)
224                 evenNumberOfSegments = !evenNumberOfSegments;
225             if (evenNumberOfSegments) {
226                 if (remainder) {
227                     patternOffset += patWidth - remainder;
228                     patternOffset += remainder / 2;
229                 } else
230                     patternOffset = patWidth / 2;
231             } else {
232                 if (remainder)
233                     patternOffset = (patWidth - remainder)/2;
234             }
235         }
236         
237         const CGFloat dottedLine[2] = { patWidth, patWidth };
238         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
239     }
240
241     CGContextBeginPath(context);
242     CGContextMoveToPoint(context, p1.x(), p1.y());
243     CGContextAddLineToPoint(context, p2.x(), p2.y());
244
245     CGContextStrokePath(context);
246
247     CGContextRestoreGState(context);
248 }
249
250 // This method is only used to draw the little circles used in lists.
251 void GraphicsContext::drawEllipse(const IntRect& rect)
252 {
253     // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
254     // This code can only handle circles, not ellipses. But khtml only
255     // uses it for circles.
256     ASSERT(rect.width() == rect.height());
257
258     if (paintingDisabled())
259         return;
260         
261     CGContextRef context = platformContext();
262     CGContextBeginPath(context);
263     float r = (float)rect.width() / 2;
264     CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, true);
265     CGContextClosePath(context);
266
267     if (fillColor().alpha()) {
268         if (strokeStyle() != NoStroke)
269             // stroke and fill
270             CGContextDrawPath(context, kCGPathFillStroke);
271         else
272             CGContextFillPath(context);
273     } else if (strokeStyle() != NoStroke)
274         CGContextStrokePath(context);
275 }
276
277
278 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
279
280     if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f || !strokeColor().alpha())
281         return;
282     
283     CGContextRef context = platformContext();
284     CGContextSaveGState(context);
285     CGContextBeginPath(context);
286     CGContextSetShouldAntialias(context, false);
287     
288     int x = rect.x();
289     int y = rect.y();
290     float w = (float)rect.width();
291     float h = (float)rect.height();
292     float scaleFactor = h / w;
293     float reverseScaleFactor = w / h;
294     
295     if (w != h)
296         scale(FloatSize(1, scaleFactor));
297     
298     float hRadius = w / 2;
299     float vRadius = h / 2;
300     float fa = startAngle;
301     float falen =  fa + angleSpan;
302     float start = -fa * M_PI/180;
303     float end = -falen * M_PI/180;
304     CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
305
306     if (w != h)
307         scale(FloatSize(1, reverseScaleFactor));
308     
309     
310     float width = strokeThickness();
311     int patWidth = 0;
312     
313     switch (strokeStyle()) {
314         case DottedStroke:
315             patWidth = (int)(width / 2);
316             break;
317         case DashedStroke:
318             patWidth = 3 * (int)(width / 2);
319             break;
320         default:
321             break;
322     }
323
324     CGContextSaveGState(context);
325     
326     if (patWidth) {
327         // Example: 80 pixels with a width of 30 pixels.
328         // Remainder is 20.  The maximum pixels of line we could paint
329         // will be 50 pixels.
330         int distance;
331         if (hRadius == vRadius)
332             distance = (int)(M_PI * hRadius) / 2;
333         else // We are elliptical and will have to estimate the distance
334             distance = (int)(M_PI * sqrt((hRadius * hRadius + vRadius * vRadius) / 2)) / 2;
335         
336         int remainder = distance % patWidth;
337         int coverage = distance - remainder;
338         int numSegments = coverage / patWidth;
339
340         float patternOffset = 0;
341         // Special case 1px dotted borders for speed.
342         if (patWidth == 1)
343             patternOffset = 1.0;
344         else {
345             bool evenNumberOfSegments = numSegments % 2 == 0;
346             if (remainder)
347                 evenNumberOfSegments = !evenNumberOfSegments;
348             if (evenNumberOfSegments) {
349                 if (remainder) {
350                     patternOffset += patWidth - remainder;
351                     patternOffset += remainder / 2;
352                 } else
353                     patternOffset = patWidth / 2;
354             } else {
355                 if (remainder)
356                     patternOffset = (patWidth - remainder) / 2;
357             }
358         }
359     
360         const CGFloat dottedLine[2] = { patWidth, patWidth };
361         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
362     }
363
364     CGContextStrokePath(context);
365     CGContextRestoreGState(context);
366     
367     CGContextRestoreGState(context);
368 }
369
370 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
371 {
372     if (paintingDisabled())
373         return;
374
375     if (npoints <= 1)
376         return;
377
378     CGContextRef context = platformContext();
379
380     CGContextSaveGState(context);
381
382     CGContextSetShouldAntialias(context, shouldAntialias);
383     
384     CGContextBeginPath(context);
385     CGContextMoveToPoint(context, points[0].x(), points[0].y());
386     for (size_t i = 1; i < npoints; i++)
387         CGContextAddLineToPoint(context, points[i].x(), points[i].y());
388     CGContextClosePath(context);
389
390     if (fillColor().alpha())
391         CGContextEOFillPath(context);
392
393     if (strokeStyle() != NoStroke)
394         CGContextStrokePath(context);
395
396     CGContextRestoreGState(context);
397 }
398
399 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
400 {
401     if (paintingDisabled())
402         return;
403     if (color.alpha()) {
404         CGContextRef context = platformContext();
405         Color oldFillColor = fillColor();
406         if (oldFillColor != color)
407             setCGFillColor(context, color);
408         CGContextFillRect(context, rect);
409         if (oldFillColor != color)
410             setCGFillColor(context, oldFillColor);
411     }
412 }
413
414 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
415 {
416     if (paintingDisabled())
417         return;
418     if (color.alpha()) {
419         CGContextRef context = platformContext();
420         Color oldFillColor = fillColor();
421         if (oldFillColor != color)
422             setCGFillColor(context, color);
423         CGContextFillRect(context, rect);
424         if (oldFillColor != color)
425             setCGFillColor(context, oldFillColor);
426     }
427 }
428
429 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color)
430 {
431     if (paintingDisabled() || !color.alpha())
432         return;
433
434     CGContextRef context = platformContext();
435     Color oldFillColor = fillColor();
436     if (oldFillColor != color)
437         setCGFillColor(context, color);
438
439     // Add the four ellipses to the path.  Technically this really isn't good enough, since we could end up
440     // not clipping the other 3/4 of the ellipse we don't care about.  We're relying on the fact that for
441     // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
442     // be subsumed by the other).
443     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
444     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
445                                                   topRight.width() * 2, topRight.height() * 2));
446     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
447                                                   bottomLeft.width() * 2, bottomLeft.height() * 2));
448     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
449                                                   rect.bottom() - bottomRight.height() * 2,
450                                                   bottomRight.width() * 2, bottomRight.height() * 2));
451     
452     // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
453     CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
454                                          rect.width() - topLeft.width() - topRight.width(),
455                                          max(topLeft.height(), topRight.height())));
456     CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(), 
457                                          rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
458                                          rect.width() - bottomLeft.width() - bottomRight.width(),
459                                          max(bottomLeft.height(), bottomRight.height())));
460     CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
461                                          max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
462     CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
463                                          rect.y() + topRight.height(),
464                                          max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
465     CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
466                                          rect.y() + max(topLeft.height(), topRight.height()),
467                                          rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
468                                          rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
469     CGContextFillPath(context);
470     if (oldFillColor != color)
471         setCGFillColor(context, oldFillColor);
472 }
473
474
475 void GraphicsContext::clip(const IntRect& rect)
476 {
477     if (paintingDisabled())
478         return;
479     CGContextClipToRect(platformContext(), rect);
480     m_data->clip(rect);
481 }
482
483 void GraphicsContext::clipOut(const IntRect& rect)
484 {
485     if (paintingDisabled())
486         return;
487         
488     CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect };
489     CGContextBeginPath(platformContext());
490     CGContextAddRects(platformContext(), rects, 2);
491     CGContextEOClip(platformContext());
492 }
493
494 void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
495 {
496     if (paintingDisabled())
497         return;
498         
499     CGContextBeginPath(platformContext());
500     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
501     CGContextAddEllipseInRect(platformContext(), rect);
502     CGContextEOClip(platformContext());
503 }
504
505 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
506     const IntSize& bottomLeft, const IntSize& bottomRight)
507 {
508     if (paintingDisabled())
509         return;
510
511     // Need sufficient width and height to contain these curves.  Sanity check our
512     // corner radii and our width/height values to make sure the curves can all fit.
513     if (static_cast<unsigned>(rect.width()) < static_cast<unsigned>(topLeft.width()) + static_cast<unsigned>(topRight.width()) ||
514         static_cast<unsigned>(rect.width()) < static_cast<unsigned>(bottomLeft.width()) + static_cast<unsigned>(bottomRight.width()) ||
515         static_cast<unsigned>(rect.height()) < static_cast<unsigned>(topLeft.height()) + static_cast<unsigned>(bottomLeft.height()) ||
516         static_cast<unsigned>(rect.height()) < static_cast<unsigned>(topRight.height()) + static_cast<unsigned>(bottomRight.height()))
517         return;
518  
519     // Clip to our rect.
520     clip(rect);
521
522     // OK, the curves can fit.
523     CGContextRef context = platformContext();
524     
525     // Add the four ellipses to the path.  Technically this really isn't good enough, since we could end up
526     // not clipping the other 3/4 of the ellipse we don't care about.  We're relying on the fact that for
527     // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
528     // be subsumed by the other).
529     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
530     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
531                                                   topRight.width() * 2, topRight.height() * 2));
532     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
533                                                   bottomLeft.width() * 2, bottomLeft.height() * 2));
534     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
535                                                   rect.bottom() - bottomRight.height() * 2,
536                                                   bottomRight.width() * 2, bottomRight.height() * 2));
537     
538     // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
539     CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
540                                          rect.width() - topLeft.width() - topRight.width(),
541                                          max(topLeft.height(), topRight.height())));
542     CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(), 
543                                          rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
544                                          rect.width() - bottomLeft.width() - bottomRight.width(),
545                                          max(bottomLeft.height(), bottomRight.height())));
546     CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
547                                          max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
548     CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
549                                          rect.y() + topRight.height(),
550                                          max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
551     CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
552                                          rect.y() + max(topLeft.height(), topRight.height()),
553                                          rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
554                                          rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
555     CGContextClip(context);
556 }
557
558 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
559 {
560     if (paintingDisabled())
561         return;
562
563     clip(rect);
564     CGContextRef context = platformContext();
565     
566     // Add outer ellipse
567     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
568     // Add inner ellipse.
569     CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
570         rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
571     
572     CGContextEOClip(context);
573 }
574
575 void GraphicsContext::beginTransparencyLayer(float opacity)
576 {
577     if (paintingDisabled())
578         return;
579     CGContextRef context = platformContext();
580     CGContextSaveGState(context);
581     CGContextSetAlpha(context, opacity);
582     CGContextBeginTransparencyLayer(context, 0);
583     m_data->beginTransparencyLayer();
584 }
585
586 void GraphicsContext::endTransparencyLayer()
587 {
588     if (paintingDisabled())
589         return;
590     CGContextRef context = platformContext();
591     CGContextEndTransparencyLayer(context);
592     CGContextRestoreGState(context);
593     m_data->endTransparencyLayer();
594 }
595
596 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
597 {
598     // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
599     blur = min(blur, 1000);
600
601     if (paintingDisabled())
602         return;
603     // Check for an invalid color, as this means that the color was not set for the shadow
604     // and we should therefore just use the default shadow color.
605     CGContextRef context = platformContext();
606     if (!color.isValid())
607         CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
608     else {
609         CGColorRef colorCG = cgColor(color);
610         CGContextSetShadowWithColor(context,
611                                     CGSizeMake(size.width(), -size.height()), // y is flipped.
612                                     blur, 
613                                     colorCG);
614         CGColorRelease(colorCG);
615     }
616 }
617
618 void GraphicsContext::clearShadow()
619 {
620     if (paintingDisabled())
621         return;
622     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
623 }
624
625 void GraphicsContext::setMiterLimit(float limit)
626 {
627     if (paintingDisabled())
628         return;
629     CGContextSetMiterLimit(platformContext(), limit);
630 }
631
632 void GraphicsContext::setAlpha(float alpha)
633 {
634     if (paintingDisabled())
635         return;
636     CGContextSetAlpha(platformContext(), alpha);
637 }
638
639 void GraphicsContext::clearRect(const FloatRect& r)
640 {
641     if (paintingDisabled())
642         return;
643     CGContextClearRect(platformContext(), r);
644 }
645
646 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
647 {
648     if (paintingDisabled())
649         return;
650     CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
651 }
652
653 void GraphicsContext::setLineCap(LineCap cap)
654 {
655     if (paintingDisabled())
656         return;
657     switch (cap) {
658         case ButtCap:
659             CGContextSetLineCap(platformContext(), kCGLineCapButt);
660             break;
661         case RoundCap:
662             CGContextSetLineCap(platformContext(), kCGLineCapRound);
663             break;
664         case SquareCap:
665             CGContextSetLineCap(platformContext(), kCGLineCapSquare);
666             break;
667     }
668 }
669
670 void GraphicsContext::setLineJoin(LineJoin join)
671 {
672     if (paintingDisabled())
673         return;
674     switch (join) {
675         case MiterJoin:
676             CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
677             break;
678         case RoundJoin:
679             CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
680             break;
681         case BevelJoin:
682             CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
683             break;
684     }
685 }
686  
687 void GraphicsContext::beginPath()
688 {
689     CGContextBeginPath(platformContext());
690 }
691
692 void GraphicsContext::addPath(const Path& path)
693 {
694     CGContextAddPath(platformContext(), path.platformPath());
695 }
696
697 void GraphicsContext::clip(const Path& path)
698 {
699     if (paintingDisabled())
700         return;
701     CGContextRef context = platformContext();
702     CGContextBeginPath(context);
703     CGContextAddPath(context, path.platformPath());
704     CGContextClip(context);
705     m_data->clip(path);
706 }
707
708 void GraphicsContext::scale(const FloatSize& size)
709 {
710     if (paintingDisabled())
711         return;
712     CGContextScaleCTM(platformContext(), size.width(), size.height());
713     m_data->scale(size);
714 }
715
716 void GraphicsContext::rotate(float angle)
717 {
718     if (paintingDisabled())
719         return;
720     CGContextRotateCTM(platformContext(), angle);
721     m_data->rotate(angle);
722 }
723
724 void GraphicsContext::translate(float x, float y)
725 {
726     if (paintingDisabled())
727         return;
728     CGContextTranslateCTM(platformContext(), x, y);
729     m_data->translate(x, y);
730 }
731
732 void GraphicsContext::concatCTM(const AffineTransform& transform)
733 {
734     if (paintingDisabled())
735         return;
736     CGContextConcatCTM(platformContext(), transform);
737     m_data->concatCTM(transform);
738 }
739
740 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
741 {
742     // It is not enough just to round to pixels in device space. The rotation part of the 
743     // affine transform matrix to device space can mess with this conversion if we have a
744     // rotating image like the hands of the world clock widget. We just need the scale, so 
745     // we get the affine transform matrix and extract the scale.
746     CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
747     if (CGAffineTransformIsIdentity(deviceMatrix))
748         return rect;
749
750     float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
751     float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
752
753     CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
754     CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
755         (rect.y() + rect.height()) * deviceScaleY);
756
757     deviceOrigin.x = roundf(deviceOrigin.x);
758     deviceOrigin.y = roundf(deviceOrigin.y);
759     deviceLowerRight.x = roundf(deviceLowerRight.x);
760     deviceLowerRight.y = roundf(deviceLowerRight.y);
761     
762     // Don't let the height or width round to 0 unless either was originally 0
763     if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
764         deviceLowerRight.y += 1;
765     if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
766         deviceLowerRight.x += 1;
767
768     FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
769     FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
770     return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
771 }
772
773 void GraphicsContext::drawLineForText(const IntPoint& point, int width, bool printing)
774 {
775     if (paintingDisabled())
776         return;
777
778     CGContextSaveGState(platformContext());
779
780     float x = point.x();
781     float y = point.y();
782     float lineLength = width;
783
784     // Use a minimum thickness of 0.5 in user space.
785     // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use.
786     float thickness = max(strokeThickness(), 0.5f);
787
788     if (!printing) {
789         // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
790         float adjustedThickness = max(thickness, 1.0f);
791
792         // FIXME: This should be done a better way.
793         // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space
794         // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels
795         // in device space will make the underlines too thick.
796         CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness));
797         if (lineRect.size.height < thickness * 2.0) {
798             x = lineRect.origin.x;
799             y = lineRect.origin.y;
800             lineLength = lineRect.size.width;
801             thickness = lineRect.size.height;
802             CGContextSetShouldAntialias(platformContext(), false);
803         }
804     }
805     
806     CGContextSetLineWidth(platformContext(), thickness);
807
808     float halfThickness = thickness / 2;
809
810     // FIXME: How about using a rectangle fill instead of drawing a line?
811     CGPoint linePoints[2];
812     linePoints[0].x = x + halfThickness;
813     linePoints[0].y = y + halfThickness;
814     linePoints[1].x = x + lineLength - halfThickness;
815     linePoints[1].y = y + halfThickness;
816     CGContextStrokeLineSegments(platformContext(), linePoints, 2);
817
818     CGContextRestoreGState(platformContext());
819 }
820
821 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
822 {
823     if (paintingDisabled())
824         return;
825         
826     CFURLRef urlRef = link.createCFURL();
827     if (urlRef) {
828         CGContextRef context = platformContext();
829         
830         // Get the bounding box to handle clipping.
831         CGRect box = CGContextGetClipBoundingBox(context);
832
833         IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
834         IntRect rect = destRect;
835         rect.intersect(intBox);
836
837         CGPDFContextSetURLForRect(context, urlRef,
838             CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
839
840         CFRelease(urlRef);
841     }
842 }
843
844 void GraphicsContext::setPlatformTextDrawingMode(int mode)
845 {
846     if (paintingDisabled())
847         return;
848
849     // Wow, wish CG had used bits here.
850     CGContextRef context = platformContext();
851     switch (mode) {
852         case cTextInvisible: // Invisible
853             CGContextSetTextDrawingMode(context, kCGTextInvisible);
854             break;
855         case cTextFill: // Fill
856             CGContextSetTextDrawingMode(context, kCGTextFill);
857             break;
858         case cTextStroke: // Stroke
859             CGContextSetTextDrawingMode(context, kCGTextStroke);
860             break;
861         case 3: // Fill | Stroke
862             CGContextSetTextDrawingMode(context, kCGTextFillStroke);
863             break;
864         case cTextClip: // Clip
865             CGContextSetTextDrawingMode(context, kCGTextClip);
866             break;
867         case 5: // Fill | Clip
868             CGContextSetTextDrawingMode(context, kCGTextFillClip);
869             break;
870         case 6: // Stroke | Clip
871             CGContextSetTextDrawingMode(context, kCGTextStrokeClip);
872             break;
873         case 7: // Fill | Stroke | Clip
874             CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip);
875             break;
876         default:
877             break;
878     }
879 }
880
881 void GraphicsContext::setPlatformStrokeColor(const Color& color)
882 {
883     if (paintingDisabled())
884         return;
885     setCGStrokeColor(platformContext(), color);
886 }
887
888 void GraphicsContext::setPlatformStrokeThickness(float thickness)
889 {
890     if (paintingDisabled())
891         return;
892     CGContextSetLineWidth(platformContext(), thickness);
893 }
894
895 void GraphicsContext::setPlatformFillColor(const Color& color)
896 {
897     if (paintingDisabled())
898         return;
899     setCGFillColor(platformContext(), color);
900 }
901
902 }
903
904 #endif // PLATFORM(CG)