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