Patch written mostly by Ken Kraisler, but also by me.
[WebKit-https.git] / WebCore / platform / cg / GraphicsContextCG.cpp
1 /*
2  * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, 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 #ifdef SVG_SUPPORT
38 #include "KRenderingDeviceQuartz.h"
39 #endif
40
41 #include <GraphicsContextPlatformPrivate.h> // FIXME: Temporary.
42
43 using namespace std;
44
45 namespace WebCore {
46
47 GraphicsContext::GraphicsContext(CGContextRef cgContext)
48     : m_common(createGraphicsContextPrivate())
49     , m_data(new GraphicsContextPlatformPrivate(cgContext))
50 {
51     setPaintingDisabled(!cgContext);
52 }
53
54 GraphicsContext::~GraphicsContext()
55 {
56     destroyGraphicsContextPrivate(m_common);
57     delete m_data;
58 }
59
60 void GraphicsContext::setFocusRingClip(const IntRect& r)
61 {
62     // This method only exists to work around bugs in Mac focus ring clipping.
63     m_data->m_focusRingClip = r;
64 }
65
66 void GraphicsContext::clearFocusRingClip()
67 {
68     // This method only exists to work around bugs in Mac focus ring clipping.
69     m_data->m_focusRingClip = IntRect();
70 }
71
72 CGContextRef GraphicsContext::platformContext() const
73 {
74     ASSERT(!paintingDisabled());
75     ASSERT(m_data->m_cgContext);
76     return m_data->m_cgContext;
77 }
78
79 static void setCGFillColor(CGContextRef context, const Color& color)
80 {
81     CGFloat red, green, blue, alpha;
82     color.getRGBA(red, green, blue, alpha);
83     CGContextSetRGBFillColor(context, red, green, blue, alpha);
84 }
85
86 static void setCGStrokeColor(CGContextRef context, const Color& color)
87 {
88     CGFloat red, green, blue, alpha;
89     color.getRGBA(red, green, blue, alpha);
90     CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
91 }
92
93 void GraphicsContext::savePlatformState()
94 {
95     // Note: Do not use this function within this class implementation, since we want to avoid the extra
96     // save of the secondary context (in GraphicsContextPlatformPrivate.h).
97     CGContextSaveGState(platformContext());
98     m_data->save();
99 }
100
101 void GraphicsContext::restorePlatformState()
102 {
103     // Note: Do not use this function within this class implementation, since we want to avoid the extra
104     // restore of the secondary context (in GraphicsContextPlatformPrivate.h).
105     CGContextRestoreGState(platformContext());
106     m_data->restore();
107 }
108
109 // Draws a filled rectangle with a stroked border.
110 void GraphicsContext::drawRect(const IntRect& rect)
111 {
112     if (paintingDisabled())
113         return;
114
115     CGContextRef context = platformContext();
116
117     if (fillColor().alpha()) {
118         setCGFillColor(context, fillColor());
119         CGContextFillRect(context, rect);
120     }
121
122     if (pen().style() != Pen::NoPen) {
123         setCGFillColor(context, pen().color());
124         CGRect rects[4] = {
125             FloatRect(rect.x(), rect.y(), rect.width(), 1),
126             FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
127             FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
128             FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
129         };
130         CGContextFillRects(context, rects, 4);
131     }
132 }
133
134 // This is only used to draw borders.
135 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
136 {
137     if (paintingDisabled())
138         return;
139
140     Pen::PenStyle penStyle = pen().style();
141     if (penStyle == Pen::NoPen)
142         return;
143     float width = pen().width();
144     if (width < 1)
145         width = 1;
146
147     FloatPoint p1 = point1;
148     FloatPoint p2 = point2;
149     bool isVerticalLine = (p1.x() == p2.x());
150     
151     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
152     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
153     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
154     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
155     if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
156         if (isVerticalLine) {
157             p1.move(0, width);
158             p2.move(0, -width);
159         } else {
160             p1.move(width, 0);
161             p2.move(-width, 0);
162         }
163     }
164     
165     if (((int)width) % 2) {
166         if (isVerticalLine) {
167             // We're a vertical line.  Adjust our x.
168             p1.move(0.5, 0);
169             p2.move(0.5, 0);
170         } else {
171             // We're a horizontal line. Adjust our y.
172             p1.move(0, 0.5);
173             p2.move(0, 0.5);
174         }
175     }
176     
177     int patWidth = 0;
178     switch (penStyle) {
179         case Pen::NoPen:
180         case Pen::SolidLine:
181             break;
182         case Pen::DotLine:
183             patWidth = (int)width;
184             break;
185         case Pen::DashLine:
186             patWidth = 3 * (int)width;
187             break;
188     }
189
190     CGContextRef context = platformContext();
191
192     CGContextSaveGState(context);
193
194     setCGStrokeColor(context, pen().color());
195
196     CGContextSetShouldAntialias(context, false);
197
198     if (patWidth) {
199         // Do a rect fill of our endpoints.  This ensures we always have the
200         // appearance of being a border.  We then draw the actual dotted/dashed line.
201         setCGFillColor(context, pen().color());
202         if (isVerticalLine) {
203             CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
204             CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
205         } else {
206             CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
207             CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
208         }
209
210         // Example: 80 pixels with a width of 30 pixels.
211         // Remainder is 20.  The maximum pixels of line we could paint
212         // will be 50 pixels.
213         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
214         int remainder = distance % patWidth;
215         int coverage = distance - remainder;
216         int numSegments = coverage / patWidth;
217
218         float patternOffset = 0;
219         // Special case 1px dotted borders for speed.
220         if (patWidth == 1)
221             patternOffset = 1.0;
222         else {
223             bool evenNumberOfSegments = numSegments % 2 == 0;
224             if (remainder)
225                 evenNumberOfSegments = !evenNumberOfSegments;
226             if (evenNumberOfSegments) {
227                 if (remainder) {
228                     patternOffset += patWidth - remainder;
229                     patternOffset += remainder / 2;
230                 } else
231                     patternOffset = patWidth / 2;
232             } else {
233                 if (remainder)
234                     patternOffset = (patWidth - remainder)/2;
235             }
236         }
237         
238         const CGFloat dottedLine[2] = { patWidth, patWidth };
239         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
240     }
241     
242     CGContextSetLineWidth(context, width);
243
244     CGContextBeginPath(context);
245     CGContextMoveToPoint(context, p1.x(), p1.y());
246     CGContextAddLineToPoint(context, p2.x(), p2.y());
247
248     CGContextStrokePath(context);
249
250     CGContextRestoreGState(context);
251 }
252
253 // This method is only used to draw the little circles used in lists.
254 void GraphicsContext::drawEllipse(const IntRect& rect)
255 {
256     // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
257     // This code can only handle circles, not ellipses. But khtml only
258     // uses it for circles.
259     ASSERT(rect.width() == rect.height());
260
261     if (paintingDisabled())
262         return;
263         
264     CGContextRef context = platformContext();
265     CGContextBeginPath(context);
266     float r = (float)rect.width() / 2;
267     CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, true);
268     CGContextClosePath(context);
269
270     if (fillColor().alpha()) {
271         setCGFillColor(context, fillColor());
272         if (pen().style() != Pen::NoPen) {
273             // stroke and fill
274             setCGStrokeColor(context, pen().color());
275             unsigned penWidth = pen().width();
276             if (penWidth == 0) 
277                 penWidth++;
278             CGContextSetLineWidth(context, penWidth);
279             CGContextDrawPath(context, kCGPathFillStroke);
280         } else
281             CGContextFillPath(context);
282     } else if (pen().style() != Pen::NoPen) {
283         setCGStrokeColor(context, pen().color());
284         unsigned penWidth = pen().width();
285         if (penWidth == 0) 
286             penWidth++;
287         CGContextSetLineWidth(context, penWidth);
288         CGContextStrokePath(context);
289     }
290 }
291
292
293 void GraphicsContext::drawArc(const IntRect& rect, float thickness, int startAngle, int angleSpan)
294
295     if (paintingDisabled())
296         return;
297     
298     CGContextRef context = platformContext();
299     CGContextSaveGState(context);
300     CGContextBeginPath(context);
301     CGContextSetShouldAntialias(context, false);
302     
303     int x = rect.x();
304     int y = rect.y();
305     float w = (float)rect.width();
306     float h = (float)rect.height();
307     float scaleFactor = h / w;
308     float reverseScaleFactor = w / h;
309     
310     if (w != h)
311         scale(FloatSize(1, scaleFactor));
312     
313     float hRadius = w / 2;
314     float vRadius = h / 2;
315     float fa = startAngle;
316     float falen =  fa + angleSpan;
317     float start = -fa * M_PI/180;
318     float end = -falen * M_PI/180;
319     CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
320
321     if (w != h)
322         scale(FloatSize(1, reverseScaleFactor));
323     
324     if (pen().style() == Pen::NoPen) {
325         setCGStrokeColor(context, fillColor());
326         CGContextSetLineWidth(context, thickness);
327         CGContextStrokePath(context);
328     } else {
329         Pen::PenStyle penStyle = pen().style();
330         float width = pen().width();
331         if (width < 1)
332             width = 1;
333         int patWidth = 0;
334         
335         switch (penStyle) {
336             case Pen::NoPen:
337             case Pen::SolidLine:
338                 break;
339             case Pen::DotLine:
340                 patWidth = (int)(width / 2);
341                 break;
342             case Pen::DashLine:
343                 patWidth = 3 * (int)(width / 2);
344                 break;
345         }
346
347         CGContextSaveGState(context);
348         setCGStrokeColor(context, pen().color());
349
350         if (patWidth) {
351             // Example: 80 pixels with a width of 30 pixels.
352             // Remainder is 20.  The maximum pixels of line we could paint
353             // will be 50 pixels.
354             int distance;
355             if (hRadius == vRadius)
356                 distance = (int)(M_PI * hRadius) / 2;
357             else // We are elliptical and will have to estimate the distance
358                 distance = (int)(M_PI * sqrt((hRadius * hRadius + vRadius * vRadius) / 2)) / 2;
359             
360             int remainder = distance % patWidth;
361             int coverage = distance - remainder;
362             int numSegments = coverage / patWidth;
363
364             float patternOffset = 0;
365             // Special case 1px dotted borders for speed.
366             if (patWidth == 1)
367                 patternOffset = 1.0;
368             else {
369                 bool evenNumberOfSegments = numSegments % 2 == 0;
370                 if (remainder)
371                     evenNumberOfSegments = !evenNumberOfSegments;
372                 if (evenNumberOfSegments) {
373                     if (remainder) {
374                         patternOffset += patWidth - remainder;
375                         patternOffset += remainder / 2;
376                     } else
377                         patternOffset = patWidth / 2;
378                 } else {
379                     if (remainder)
380                         patternOffset = (patWidth - remainder) / 2;
381                 }
382             }
383         
384             const CGFloat dottedLine[2] = { patWidth, patWidth };
385             CGContextSetLineDash(context, patternOffset, dottedLine, 2);
386         }
387     
388         CGContextSetLineWidth(context, width);
389         CGContextStrokePath(context);
390         CGContextRestoreGState(context);
391     }
392     
393     CGContextRestoreGState(context);
394 }
395
396 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
397 {
398     if (paintingDisabled())
399         return;
400
401     if (npoints <= 1)
402         return;
403
404     CGContextRef context = platformContext();
405
406     CGContextSaveGState(context);
407
408     CGContextSetShouldAntialias(context, shouldAntialias);
409     
410     CGContextBeginPath(context);
411     CGContextMoveToPoint(context, points[0].x(), points[0].y());
412     for (size_t i = 1; i < npoints; i++)
413         CGContextAddLineToPoint(context, points[i].x(), points[i].y());
414     CGContextClosePath(context);
415
416     if (fillColor().alpha()) {
417         setCGFillColor(context, fillColor());
418         CGContextEOFillPath(context);
419     }
420
421     if (pen().style() != Pen::NoPen) {
422         setCGStrokeColor(context, pen().color());
423         CGContextSetLineWidth(context, pen().width());
424         CGContextStrokePath(context);
425     }
426
427     CGContextRestoreGState(context);
428 }
429
430 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
431 {
432     if (paintingDisabled())
433         return;
434     if (color.alpha()) {
435         CGContextRef context = platformContext();
436         setCGFillColor(context, color);
437         CGContextFillRect(context, rect);
438     }
439 }
440
441 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
442 {
443     if (paintingDisabled())
444         return;
445     if (color.alpha()) {
446         CGContextRef context = platformContext();
447         setCGFillColor(context, color);
448         CGContextFillRect(context, rect);
449     }
450 }
451
452 void GraphicsContext::clip(const IntRect& rect)
453 {
454     if (paintingDisabled())
455         return;
456     CGContextClipToRect(platformContext(), rect);
457     m_data->clip(rect);
458 }
459
460 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
461     const IntSize& bottomLeft, const IntSize& bottomRight)
462 {
463     if (paintingDisabled())
464         return;
465
466     // Need sufficient width and height to contain these curves.  Sanity check our top/bottom
467     // values and our width/height values to make sure the curves can all fit.
468     int requiredWidth = max(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
469     if (requiredWidth > rect.width())
470         return;
471     int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
472     if (requiredHeight > rect.height())
473         return;
474  
475     // Clip to our rect.
476     clip(rect);
477
478     // OK, the curves can fit.
479     CGContextRef context = platformContext();
480     
481     // Add the four ellipses to the path.  Technically this really isn't good enough, since we could end up
482     // not clipping the other 3/4 of the ellipse we don't care about.  We're relying on the fact that for
483     // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
484     // be subsumed by the other).
485     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
486     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
487                                                   topRight.width() * 2, topRight.height() * 2));
488     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
489                                                   bottomLeft.width() * 2, bottomLeft.height() * 2));
490     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
491                                                   rect.bottom() - bottomRight.height() * 2,
492                                                   bottomRight.width() * 2, bottomRight.height() * 2));
493     
494     // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
495     CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
496                                          rect.width() - topLeft.width() - topRight.width(),
497                                          max(topLeft.height(), topRight.height())));
498     CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(), 
499                                          rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
500                                          rect.width() - bottomLeft.width() - bottomRight.width(),
501                                          max(bottomLeft.height(), bottomRight.height())));
502     CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
503                                          max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
504     CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
505                                          rect.y() + topRight.height(),
506                                          max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
507     CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
508                                          rect.y() + max(topLeft.height(), topRight.height()),
509                                          rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
510                                          rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
511     CGContextClip(context);
512 }
513
514 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
515 {
516     if (paintingDisabled())
517         return;
518
519     clip(rect);
520     CGContextRef context = platformContext();
521     
522     // Add outer ellipse
523     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
524     // Add inner ellipse.
525     CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
526         rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
527     
528     CGContextEOClip(context);
529 }
530
531 #ifdef SVG_SUPPORT
532 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
533 {
534     return new KRenderingDeviceContextQuartz(platformContext());
535 }
536 #endif
537
538 void GraphicsContext::beginTransparencyLayer(float opacity)
539 {
540     if (paintingDisabled())
541         return;
542     CGContextRef context = platformContext();
543     CGContextSaveGState(context);
544     CGContextSetAlpha(context, opacity);
545     CGContextBeginTransparencyLayer(context, 0);
546     m_data->beginTransparencyLayer();
547 }
548
549 void GraphicsContext::endTransparencyLayer()
550 {
551     if (paintingDisabled())
552         return;
553     CGContextRef context = platformContext();
554     CGContextEndTransparencyLayer(context);
555     CGContextRestoreGState(context);
556     m_data->endTransparencyLayer();
557 }
558
559 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
560 {
561     if (paintingDisabled())
562         return;
563     // Check for an invalid color, as this means that the color was not set for the shadow
564     // and we should therefore just use the default shadow color.
565     CGContextRef context = platformContext();
566     if (!color.isValid())
567         CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
568     else {
569         CGColorRef colorCG = cgColor(color);
570         CGContextSetShadowWithColor(context,
571                                     CGSizeMake(size.width(), -size.height()), // y is flipped.
572                                     blur, 
573                                     colorCG);
574         CGColorRelease(colorCG);
575     }
576 }
577
578 void GraphicsContext::clearShadow()
579 {
580     if (paintingDisabled())
581         return;
582     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
583 }
584
585 void GraphicsContext::setLineWidth(float width)
586 {
587     if (paintingDisabled())
588         return;
589     CGContextSetLineWidth(platformContext(), width);
590 }
591
592 void GraphicsContext::setMiterLimit(float limit)
593 {
594     if (paintingDisabled())
595         return;
596     CGContextSetMiterLimit(platformContext(), limit);
597 }
598
599 void GraphicsContext::setAlpha(float alpha)
600 {
601     if (paintingDisabled())
602         return;
603     CGContextSetAlpha(platformContext(), alpha);
604 }
605
606 void GraphicsContext::clearRect(const FloatRect& r)
607 {
608     if (paintingDisabled())
609         return;
610     CGContextClearRect(platformContext(), r);
611 }
612
613 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
614 {
615     if (paintingDisabled())
616         return;
617     CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
618 }
619
620 void GraphicsContext::setLineCap(LineCap cap)
621 {
622     if (paintingDisabled())
623         return;
624     switch (cap) {
625         case ButtCap:
626             CGContextSetLineCap(platformContext(), kCGLineCapButt);
627             break;
628         case RoundCap:
629             CGContextSetLineCap(platformContext(), kCGLineCapRound);
630             break;
631         case SquareCap:
632             CGContextSetLineCap(platformContext(), kCGLineCapSquare);
633             break;
634     }
635 }
636
637 void GraphicsContext::setLineJoin(LineJoin join)
638 {
639     if (paintingDisabled())
640         return;
641     switch (join) {
642         case MiterJoin:
643             CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
644             break;
645         case RoundJoin:
646             CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
647             break;
648         case BevelJoin:
649             CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
650             break;
651     }
652 }
653
654 void GraphicsContext::clip(const Path& path)
655 {
656     if (paintingDisabled())
657         return;
658     CGContextRef context = platformContext();
659     CGContextBeginPath(context);
660     CGContextAddPath(context, path.platformPath());
661     CGContextClip(context);
662     m_data->clip(path);
663 }
664
665 void GraphicsContext::scale(const FloatSize& size)
666 {
667     if (paintingDisabled())
668         return;
669     CGContextScaleCTM(platformContext(), size.width(), size.height());
670     m_data->scale(size);
671 }
672
673 void GraphicsContext::rotate(float angle)
674 {
675     if (paintingDisabled())
676         return;
677     CGContextRotateCTM(platformContext(), angle);
678     m_data->rotate(angle);
679 }
680
681 void GraphicsContext::translate(float x, float y)
682 {
683     if (paintingDisabled())
684         return;
685     CGContextTranslateCTM(platformContext(), x, y);
686     m_data->translate(x, y);
687 }
688
689 void GraphicsContext::concatCTM(const AffineTransform& transform)
690 {
691     if (paintingDisabled())
692         return;
693     CGContextConcatCTM(platformContext(), transform);
694     m_data->concatCTM(transform);
695 }
696
697 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
698 {
699     // It is not enough just to round to pixels in device space. The rotation part of the 
700     // affine transform matrix to device space can mess with this conversion if we have a
701     // rotating image like the hands of the world clock widget. We just need the scale, so 
702     // we get the affine transform matrix and extract the scale.
703     CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
704     float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
705     float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
706
707     CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
708     CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
709         (rect.y() + rect.height()) * deviceScaleY);
710
711     deviceOrigin.x = roundf(deviceOrigin.x);
712     deviceOrigin.y = roundf(deviceOrigin.y);
713     deviceLowerRight.x = roundf(deviceLowerRight.x);
714     deviceLowerRight.y = roundf(deviceLowerRight.y);
715     
716     // Don't let the height or width round to 0 unless either was originally 0
717     if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
718         deviceLowerRight.y += 1;
719     if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
720         deviceLowerRight.x += 1;
721
722     FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
723     FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
724     return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
725 }
726
727 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset, int width, bool printing)
728 {
729     if (paintingDisabled())
730         return;
731     
732     // Note: This function assumes that point.x and point.y are integers (and that's currently always the case).
733     float x = point.x();
734     float y = point.y() + yOffset;
735
736     // Leave 1.0 in user space between the baseline of the text and the top of the underline.
737     // FIXME: Is this the right distance for space above the underline? Even for thick underlines on large sized text?
738     y += 1;
739
740     float thickness = pen().width();
741     if (printing) {
742         // When printing, use a minimum thickness of 0.5 in user space.
743         // See bugzilla bug 4255 for details of why 0.5 is the right minimum thickness to use while printing.
744         if (thickness < 0.5)
745             thickness = 0.5;
746
747         // When printing, use antialiasing instead of putting things on integral pixel boundaries.
748     } else {
749         // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
750         if (thickness < 1)
751             thickness = 1;
752
753         // On screen, round all parameters to integer boundaries in device space.
754         CGRect lineRect = roundToDevicePixels(FloatRect(x, y, width, thickness));
755         x = lineRect.origin.x;
756         y = lineRect.origin.y;
757         width = (int)(lineRect.size.width);
758         thickness = lineRect.size.height;
759     }
760
761     // FIXME: How about using a rectangle fill instead of drawing a line?
762     CGContextSaveGState(platformContext());
763
764     setCGStrokeColor(platformContext(), pen().color());
765     
766     CGContextSetLineWidth(platformContext(), thickness);
767     CGContextSetShouldAntialias(platformContext(), printing);
768
769     float halfThickness = thickness / 2;
770
771     CGPoint linePoints[2];
772     linePoints[0].x = x + halfThickness;
773     linePoints[0].y = y + halfThickness;
774     linePoints[1].x = x + width - halfThickness;
775     linePoints[1].y = y + halfThickness;
776     CGContextStrokeLineSegments(platformContext(), linePoints, 2);
777
778     CGContextRestoreGState(platformContext());
779 }
780
781 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
782 {
783     if (paintingDisabled())
784         return;
785         
786     CFURLRef urlRef = link.createCFURL();
787     if (urlRef) {
788         CGContextRef context = platformContext();
789         
790         // Get the bounding box to handle clipping.
791         CGRect box = CGContextGetClipBoundingBox(context);
792
793         IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
794         IntRect rect = destRect;
795         rect.intersect(intBox);
796
797         CGPDFContextSetURLForRect(context, urlRef,
798             CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
799
800         CFRelease(urlRef);
801     }
802 }
803
804 }
805
806 #endif // PLATFORM(CG)