JavaScriptCore:
[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.5f, 0.0f);
171             p2.move(0.5f, 0.0f);
172         } else {
173             // We're a horizontal line. Adjust our y.
174             p1.move(0.0f, 0.5f);
175             p2.move(0.0f, 0.5f);
176         }
177     }
178     
179     int patWidth = 0;
180     switch (strokeStyle()) {
181         case NoStroke:
182         case SolidStroke:
183             break;
184         case DottedStroke:
185             patWidth = (int)width;
186             break;
187         case DashedStroke:
188             patWidth = 3 * (int)width;
189             break;
190     }
191
192     CGContextRef context = platformContext();
193     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.0f;
218         // Special case 1px dotted borders for speed.
219         if (patWidth == 1)
220             patternOffset = 1.0f;
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, 0);
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.0f;
303     float end = -falen * M_PI / 180.0f;
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.0f;
341         // Special case 1px dotted borders for speed.
342         if (patWidth == 1)
343             patternOffset = 1.0f;
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.0f;
352                 } else
353                     patternOffset = patWidth / 2.0f;
354             } else {
355                 if (remainder)
356                     patternOffset = (patWidth - remainder) / 2.0f;
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() || !fillColor().alpha() && (strokeThickness() <= 0 || strokeStyle() == NoStroke))
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         if (strokeStyle() != NoStroke)
392             CGContextDrawPath(context, kCGPathEOFillStroke);
393         else
394             CGContextEOFillPath(context);
395     } else
396         CGContextStrokePath(context);
397
398     CGContextRestoreGState(context);
399 }
400
401 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
402 {
403     if (paintingDisabled())
404         return;
405     if (color.alpha()) {
406         CGContextRef context = platformContext();
407         Color oldFillColor = fillColor();
408         if (oldFillColor != color)
409             setCGFillColor(context, color);
410         CGContextFillRect(context, rect);
411         if (oldFillColor != color)
412             setCGFillColor(context, oldFillColor);
413     }
414 }
415
416 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
417 {
418     if (paintingDisabled())
419         return;
420     if (color.alpha()) {
421         CGContextRef context = platformContext();
422         Color oldFillColor = fillColor();
423         if (oldFillColor != color)
424             setCGFillColor(context, color);
425         CGContextFillRect(context, rect);
426         if (oldFillColor != color)
427             setCGFillColor(context, oldFillColor);
428     }
429 }
430
431 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color)
432 {
433     if (paintingDisabled() || !color.alpha())
434         return;
435
436     CGContextRef context = platformContext();
437     Color oldFillColor = fillColor();
438     if (oldFillColor != color)
439         setCGFillColor(context, color);
440
441     addPath(Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight));
442     CGContextFillPath(context);
443
444     if (oldFillColor != color)
445         setCGFillColor(context, oldFillColor);
446 }
447
448
449 void GraphicsContext::clip(const IntRect& rect)
450 {
451     if (paintingDisabled())
452         return;
453     CGContextClipToRect(platformContext(), rect);
454     m_data->clip(rect);
455 }
456
457 void GraphicsContext::clipOut(const IntRect& rect)
458 {
459     if (paintingDisabled())
460         return;
461         
462     CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect };
463     CGContextBeginPath(platformContext());
464     CGContextAddRects(platformContext(), rects, 2);
465     CGContextEOClip(platformContext());
466 }
467
468 void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
469 {
470     if (paintingDisabled())
471         return;
472         
473     CGContextBeginPath(platformContext());
474     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
475     CGContextAddEllipseInRect(platformContext(), rect);
476     CGContextEOClip(platformContext());
477 }
478
479 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
480 {
481     if (paintingDisabled())
482         return;
483
484     clip(rect);
485     CGContextRef context = platformContext();
486     
487     // Add outer ellipse
488     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
489     // Add inner ellipse.
490     CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
491         rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
492     
493     CGContextEOClip(context);
494 }
495
496 void GraphicsContext::beginTransparencyLayer(float opacity)
497 {
498     if (paintingDisabled())
499         return;
500     CGContextRef context = platformContext();
501     CGContextSaveGState(context);
502     CGContextSetAlpha(context, opacity);
503     CGContextBeginTransparencyLayer(context, 0);
504     m_data->beginTransparencyLayer();
505 }
506
507 void GraphicsContext::endTransparencyLayer()
508 {
509     if (paintingDisabled())
510         return;
511     CGContextRef context = platformContext();
512     CGContextEndTransparencyLayer(context);
513     CGContextRestoreGState(context);
514     m_data->endTransparencyLayer();
515 }
516
517 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
518 {
519     // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
520     blur = min(blur, 1000);
521
522     if (paintingDisabled())
523         return;
524     // Check for an invalid color, as this means that the color was not set for the shadow
525     // and we should therefore just use the default shadow color.
526     CGContextRef context = platformContext();
527     if (!color.isValid())
528         CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
529     else {
530         CGColorRef colorCG = cgColor(color);
531         CGContextSetShadowWithColor(context,
532                                     CGSizeMake(size.width(), -size.height()), // y is flipped.
533                                     blur, 
534                                     colorCG);
535         CGColorRelease(colorCG);
536     }
537 }
538
539 void GraphicsContext::clearShadow()
540 {
541     if (paintingDisabled())
542         return;
543     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
544 }
545
546 void GraphicsContext::setMiterLimit(float limit)
547 {
548     if (paintingDisabled())
549         return;
550     CGContextSetMiterLimit(platformContext(), limit);
551 }
552
553 void GraphicsContext::setAlpha(float alpha)
554 {
555     if (paintingDisabled())
556         return;
557     CGContextSetAlpha(platformContext(), alpha);
558 }
559
560 void GraphicsContext::clearRect(const FloatRect& r)
561 {
562     if (paintingDisabled())
563         return;
564     CGContextClearRect(platformContext(), r);
565 }
566
567 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
568 {
569     if (paintingDisabled())
570         return;
571     CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
572 }
573
574 void GraphicsContext::setLineCap(LineCap cap)
575 {
576     if (paintingDisabled())
577         return;
578     switch (cap) {
579         case ButtCap:
580             CGContextSetLineCap(platformContext(), kCGLineCapButt);
581             break;
582         case RoundCap:
583             CGContextSetLineCap(platformContext(), kCGLineCapRound);
584             break;
585         case SquareCap:
586             CGContextSetLineCap(platformContext(), kCGLineCapSquare);
587             break;
588     }
589 }
590
591 void GraphicsContext::setLineJoin(LineJoin join)
592 {
593     if (paintingDisabled())
594         return;
595     switch (join) {
596         case MiterJoin:
597             CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
598             break;
599         case RoundJoin:
600             CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
601             break;
602         case BevelJoin:
603             CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
604             break;
605     }
606 }
607  
608 void GraphicsContext::beginPath()
609 {
610     CGContextBeginPath(platformContext());
611 }
612
613 void GraphicsContext::addPath(const Path& path)
614 {
615     CGContextAddPath(platformContext(), path.platformPath());
616 }
617
618 void GraphicsContext::clip(const Path& path)
619 {
620     if (paintingDisabled())
621         return;
622     CGContextRef context = platformContext();
623     CGContextBeginPath(context);
624     CGContextAddPath(context, path.platformPath());
625     CGContextClip(context);
626     m_data->clip(path);
627 }
628
629 void GraphicsContext::clipOut(const Path& path)
630 {
631     if (paintingDisabled())
632         return;
633         
634     CGContextBeginPath(platformContext());
635     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
636     CGContextAddPath(platformContext(), path.platformPath());
637     CGContextEOClip(platformContext());
638 }
639
640 void GraphicsContext::scale(const FloatSize& size)
641 {
642     if (paintingDisabled())
643         return;
644     CGContextScaleCTM(platformContext(), size.width(), size.height());
645     m_data->scale(size);
646 }
647
648 void GraphicsContext::rotate(float angle)
649 {
650     if (paintingDisabled())
651         return;
652     CGContextRotateCTM(platformContext(), angle);
653     m_data->rotate(angle);
654 }
655
656 void GraphicsContext::translate(float x, float y)
657 {
658     if (paintingDisabled())
659         return;
660     CGContextTranslateCTM(platformContext(), x, y);
661     m_data->translate(x, y);
662 }
663
664 void GraphicsContext::concatCTM(const AffineTransform& transform)
665 {
666     if (paintingDisabled())
667         return;
668     CGContextConcatCTM(platformContext(), transform);
669     m_data->concatCTM(transform);
670 }
671
672 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
673 {
674     // It is not enough just to round to pixels in device space. The rotation part of the 
675     // affine transform matrix to device space can mess with this conversion if we have a
676     // rotating image like the hands of the world clock widget. We just need the scale, so 
677     // we get the affine transform matrix and extract the scale.
678     CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
679     if (CGAffineTransformIsIdentity(deviceMatrix))
680         return rect;
681
682     float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
683     float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
684
685     CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
686     CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
687         (rect.y() + rect.height()) * deviceScaleY);
688
689     deviceOrigin.x = roundf(deviceOrigin.x);
690     deviceOrigin.y = roundf(deviceOrigin.y);
691     deviceLowerRight.x = roundf(deviceLowerRight.x);
692     deviceLowerRight.y = roundf(deviceLowerRight.y);
693     
694     // Don't let the height or width round to 0 unless either was originally 0
695     if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
696         deviceLowerRight.y += 1;
697     if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
698         deviceLowerRight.x += 1;
699
700     FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
701     FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
702     return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
703 }
704
705 void GraphicsContext::drawLineForText(const IntPoint& point, int width, bool printing)
706 {
707     if (paintingDisabled())
708         return;
709
710     if (width <= 0)
711         return;
712
713     CGContextSaveGState(platformContext());
714
715     float x = point.x();
716     float y = point.y();
717     float lineLength = width;
718
719     // Use a minimum thickness of 0.5 in user space.
720     // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use.
721     float thickness = max(strokeThickness(), 0.5f);
722
723     if (!printing) {
724         // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
725         float adjustedThickness = max(thickness, 1.0f);
726
727         // FIXME: This should be done a better way.
728         // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space
729         // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels
730         // in device space will make the underlines too thick.
731         CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness));
732         if (lineRect.size.height < thickness * 2.0) {
733             x = lineRect.origin.x;
734             y = lineRect.origin.y;
735             lineLength = lineRect.size.width;
736             thickness = lineRect.size.height;
737             CGContextSetShouldAntialias(platformContext(), false);
738         }
739     }
740     
741     if (fillColor() != strokeColor())
742         setCGFillColor(platformContext(), strokeColor());
743     CGContextFillRect(platformContext(), CGRectMake(x, y, lineLength, thickness));
744
745     CGContextRestoreGState(platformContext());
746 }
747
748 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
749 {
750     if (paintingDisabled())
751         return;
752         
753     CFURLRef urlRef = link.createCFURL();
754     if (urlRef) {
755         CGContextRef context = platformContext();
756         
757         // Get the bounding box to handle clipping.
758         CGRect box = CGContextGetClipBoundingBox(context);
759
760         IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
761         IntRect rect = destRect;
762         rect.intersect(intBox);
763
764         CGPDFContextSetURLForRect(context, urlRef,
765             CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
766
767         CFRelease(urlRef);
768     }
769 }
770
771 void GraphicsContext::setPlatformTextDrawingMode(int mode)
772 {
773     if (paintingDisabled())
774         return;
775
776     // Wow, wish CG had used bits here.
777     CGContextRef context = platformContext();
778     switch (mode) {
779         case cTextInvisible: // Invisible
780             CGContextSetTextDrawingMode(context, kCGTextInvisible);
781             break;
782         case cTextFill: // Fill
783             CGContextSetTextDrawingMode(context, kCGTextFill);
784             break;
785         case cTextStroke: // Stroke
786             CGContextSetTextDrawingMode(context, kCGTextStroke);
787             break;
788         case 3: // Fill | Stroke
789             CGContextSetTextDrawingMode(context, kCGTextFillStroke);
790             break;
791         case cTextClip: // Clip
792             CGContextSetTextDrawingMode(context, kCGTextClip);
793             break;
794         case 5: // Fill | Clip
795             CGContextSetTextDrawingMode(context, kCGTextFillClip);
796             break;
797         case 6: // Stroke | Clip
798             CGContextSetTextDrawingMode(context, kCGTextStrokeClip);
799             break;
800         case 7: // Fill | Stroke | Clip
801             CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip);
802             break;
803         default:
804             break;
805     }
806 }
807
808 void GraphicsContext::setPlatformStrokeColor(const Color& color)
809 {
810     if (paintingDisabled())
811         return;
812     setCGStrokeColor(platformContext(), color);
813 }
814
815 void GraphicsContext::setPlatformStrokeThickness(float thickness)
816 {
817     if (paintingDisabled())
818         return;
819     CGContextSetLineWidth(platformContext(), thickness);
820 }
821
822 void GraphicsContext::setPlatformFillColor(const Color& color)
823 {
824     if (paintingDisabled())
825         return;
826     setCGFillColor(platformContext(), color);
827 }
828
829 }
830
831 #endif // PLATFORM(CG)