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