Reviewed by Darin Adler.
[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 "FloatConversion.h"
34 #include "GraphicsContextPlatformPrivate.h"
35 #include "KURL.h"
36 #include "Path.h"
37 #include <CoreGraphics/CGPDFContext.h>
38 #include <wtf/MathExtras.h>
39
40 using namespace std;
41
42 namespace WebCore {
43
44 static void setCGFillColor(CGContextRef context, const Color& color)
45 {
46     CGFloat red, green, blue, alpha;
47     color.getRGBA(red, green, blue, alpha);
48     CGContextSetRGBFillColor(context, red, green, blue, alpha);
49 }
50
51 static void setCGStrokeColor(CGContextRef context, const Color& color)
52 {
53     CGFloat red, green, blue, alpha;
54     color.getRGBA(red, green, blue, alpha);
55     CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
56 }
57
58 GraphicsContext::GraphicsContext(CGContextRef cgContext)
59     : m_common(createGraphicsContextPrivate())
60     , m_data(new GraphicsContextPlatformPrivate(cgContext))
61 {
62     setPaintingDisabled(!cgContext);
63     if (cgContext) {
64         // Make sure the context starts in sync with our state.
65         setPlatformFillColor(fillColor());
66         setPlatformStrokeColor(strokeColor());
67     }
68 }
69
70 GraphicsContext::~GraphicsContext()
71 {
72     destroyGraphicsContextPrivate(m_common);
73     delete m_data;
74 }
75
76 CGContextRef GraphicsContext::platformContext() const
77 {
78     ASSERT(!paintingDisabled());
79     ASSERT(m_data->m_cgContext);
80     return m_data->m_cgContext;
81 }
82
83 void GraphicsContext::savePlatformState()
84 {
85     // Note: Do not use this function within this class implementation, since we want to avoid the extra
86     // save of the secondary context (in GraphicsContextPlatformPrivate.h).
87     CGContextSaveGState(platformContext());
88     m_data->save();
89 }
90
91 void GraphicsContext::restorePlatformState()
92 {
93     // Note: Do not use this function within this class implementation, since we want to avoid the extra
94     // restore of the secondary context (in GraphicsContextPlatformPrivate.h).
95     CGContextRestoreGState(platformContext());
96     m_data->restore();
97     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
98 }
99
100 // Draws a filled rectangle with a stroked border.
101 void GraphicsContext::drawRect(const IntRect& rect)
102 {
103     if (paintingDisabled())
104         return;
105
106     CGContextRef context = platformContext();
107
108     if (fillColor().alpha())
109         CGContextFillRect(context, rect);
110
111     if (strokeStyle() != NoStroke && strokeColor().alpha()) {
112         // We do a fill of four rects to simulate the stroke of a border.
113         Color oldFillColor = fillColor();
114         if (oldFillColor != strokeColor())
115             setCGFillColor(context, strokeColor());
116         CGRect rects[4] = {
117             FloatRect(rect.x(), rect.y(), rect.width(), 1),
118             FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
119             FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
120             FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
121         };
122         CGContextFillRects(context, rects, 4);
123         if (oldFillColor != strokeColor())
124             setCGFillColor(context, oldFillColor);
125     }
126 }
127
128 // This is only used to draw borders.
129 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
130 {
131     if (paintingDisabled())
132         return;
133
134     if (strokeStyle() == NoStroke || !strokeColor().alpha())
135         return;
136
137     float width = strokeThickness();
138
139     FloatPoint p1 = point1;
140     FloatPoint p2 = point2;
141     bool isVerticalLine = (p1.x() == p2.x());
142     
143     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
144     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
145     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
146     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
147     if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) {
148         if (isVerticalLine) {
149             p1.move(0, width);
150             p2.move(0, -width);
151         } else {
152             p1.move(width, 0);
153             p2.move(-width, 0);
154         }
155     }
156     
157     if (((int)width) % 2) {
158         if (isVerticalLine) {
159             // We're a vertical line.  Adjust our x.
160             p1.move(0.5f, 0.0f);
161             p2.move(0.5f, 0.0f);
162         } else {
163             // We're a horizontal line. Adjust our y.
164             p1.move(0.0f, 0.5f);
165             p2.move(0.0f, 0.5f);
166         }
167     }
168     
169     int patWidth = 0;
170     switch (strokeStyle()) {
171         case NoStroke:
172         case SolidStroke:
173             break;
174         case DottedStroke:
175             patWidth = (int)width;
176             break;
177         case DashedStroke:
178             patWidth = 3 * (int)width;
179             break;
180     }
181
182     CGContextRef context = platformContext();
183     CGContextSaveGState(context);
184
185     CGContextSetShouldAntialias(context, false);
186
187     if (patWidth) {
188         // Do a rect fill of our endpoints.  This ensures we always have the
189         // appearance of being a border.  We then draw the actual dotted/dashed line.
190         setCGFillColor(context, strokeColor());  // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
191         if (isVerticalLine) {
192             CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
193             CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
194         } else {
195             CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
196             CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
197         }
198
199         // Example: 80 pixels with a width of 30 pixels.
200         // Remainder is 20.  The maximum pixels of line we could paint
201         // will be 50 pixels.
202         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
203         int remainder = distance % patWidth;
204         int coverage = distance - remainder;
205         int numSegments = coverage / patWidth;
206
207         float patternOffset = 0.0f;
208         // Special case 1px dotted borders for speed.
209         if (patWidth == 1)
210             patternOffset = 1.0f;
211         else {
212             bool evenNumberOfSegments = numSegments % 2 == 0;
213             if (remainder)
214                 evenNumberOfSegments = !evenNumberOfSegments;
215             if (evenNumberOfSegments) {
216                 if (remainder) {
217                     patternOffset += patWidth - remainder;
218                     patternOffset += remainder / 2;
219                 } else
220                     patternOffset = patWidth / 2;
221             } else {
222                 if (remainder)
223                     patternOffset = (patWidth - remainder)/2;
224             }
225         }
226         
227         const CGFloat dottedLine[2] = { patWidth, patWidth };
228         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
229     }
230
231     CGContextBeginPath(context);
232     CGContextMoveToPoint(context, p1.x(), p1.y());
233     CGContextAddLineToPoint(context, p2.x(), p2.y());
234
235     CGContextStrokePath(context);
236
237     CGContextRestoreGState(context);
238 }
239
240 // This method is only used to draw the little circles used in lists.
241 void GraphicsContext::drawEllipse(const IntRect& rect)
242 {
243     // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
244     // This code can only handle circles, not ellipses. But khtml only
245     // uses it for circles.
246     ASSERT(rect.width() == rect.height());
247
248     if (paintingDisabled())
249         return;
250         
251     CGContextRef context = platformContext();
252     CGContextBeginPath(context);
253     float r = (float)rect.width() / 2;
254     CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0.0f, 2.0f * piFloat, 0);
255     CGContextClosePath(context);
256
257     if (fillColor().alpha()) {
258         if (strokeStyle() != NoStroke)
259             // stroke and fill
260             CGContextDrawPath(context, kCGPathFillStroke);
261         else
262             CGContextFillPath(context);
263     } else if (strokeStyle() != NoStroke)
264         CGContextStrokePath(context);
265 }
266
267
268 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
269
270     if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f || !strokeColor().alpha())
271         return;
272     
273     CGContextRef context = platformContext();
274     CGContextSaveGState(context);
275     CGContextBeginPath(context);
276     CGContextSetShouldAntialias(context, false);
277     
278     int x = rect.x();
279     int y = rect.y();
280     float w = (float)rect.width();
281     float h = (float)rect.height();
282     float scaleFactor = h / w;
283     float reverseScaleFactor = w / h;
284     
285     if (w != h)
286         scale(FloatSize(1, scaleFactor));
287     
288     float hRadius = w / 2;
289     float vRadius = h / 2;
290     float fa = startAngle;
291     float falen =  fa + angleSpan;
292     float start = -fa * piFloat / 180.0f;
293     float end = -falen * piFloat / 180.0f;
294     CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
295
296     if (w != h)
297         scale(FloatSize(1, reverseScaleFactor));
298     
299     
300     float width = strokeThickness();
301     int patWidth = 0;
302     
303     switch (strokeStyle()) {
304         case DottedStroke:
305             patWidth = (int)(width / 2);
306             break;
307         case DashedStroke:
308             patWidth = 3 * (int)(width / 2);
309             break;
310         default:
311             break;
312     }
313     
314     if (patWidth) {
315         // Example: 80 pixels with a width of 30 pixels.
316         // Remainder is 20.  The maximum pixels of line we could paint
317         // will be 50 pixels.
318         int distance;
319         if (hRadius == vRadius)
320             distance = static_cast<int>((piFloat * hRadius) / 2.0f);
321         else // We are elliptical and will have to estimate the distance
322             distance = static_cast<int>((piFloat * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0f)) / 2.0f);
323         
324         int remainder = distance % patWidth;
325         int coverage = distance - remainder;
326         int numSegments = coverage / patWidth;
327
328         float patternOffset = 0.0f;
329         // Special case 1px dotted borders for speed.
330         if (patWidth == 1)
331             patternOffset = 1.0f;
332         else {
333             bool evenNumberOfSegments = numSegments % 2 == 0;
334             if (remainder)
335                 evenNumberOfSegments = !evenNumberOfSegments;
336             if (evenNumberOfSegments) {
337                 if (remainder) {
338                     patternOffset += patWidth - remainder;
339                     patternOffset += remainder / 2.0f;
340                 } else
341                     patternOffset = patWidth / 2.0f;
342             } else {
343                 if (remainder)
344                     patternOffset = (patWidth - remainder) / 2.0f;
345             }
346         }
347     
348         const CGFloat dottedLine[2] = { patWidth, patWidth };
349         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
350     }
351
352     CGContextStrokePath(context);
353     
354     CGContextRestoreGState(context);
355 }
356
357 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
358 {
359     if (paintingDisabled() || !fillColor().alpha() && (strokeThickness() <= 0 || strokeStyle() == NoStroke))
360         return;
361
362     if (npoints <= 1)
363         return;
364
365     CGContextRef context = platformContext();
366
367     CGContextSaveGState(context);
368
369     CGContextSetShouldAntialias(context, shouldAntialias);
370     
371     CGContextBeginPath(context);
372     CGContextMoveToPoint(context, points[0].x(), points[0].y());
373     for (size_t i = 1; i < npoints; i++)
374         CGContextAddLineToPoint(context, points[i].x(), points[i].y());
375     CGContextClosePath(context);
376
377     if (fillColor().alpha()) {
378         if (strokeStyle() != NoStroke)
379             CGContextDrawPath(context, kCGPathEOFillStroke);
380         else
381             CGContextEOFillPath(context);
382     } else
383         CGContextStrokePath(context);
384
385     CGContextRestoreGState(context);
386 }
387
388 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
389 {
390     if (paintingDisabled())
391         return;
392     if (color.alpha()) {
393         CGContextRef context = platformContext();
394         Color oldFillColor = fillColor();
395         if (oldFillColor != color)
396             setCGFillColor(context, color);
397         CGContextFillRect(context, rect);
398         if (oldFillColor != color)
399             setCGFillColor(context, oldFillColor);
400     }
401 }
402
403 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
404 {
405     if (paintingDisabled())
406         return;
407     if (color.alpha()) {
408         CGContextRef context = platformContext();
409         Color oldFillColor = fillColor();
410         if (oldFillColor != color)
411             setCGFillColor(context, color);
412         CGContextFillRect(context, rect);
413         if (oldFillColor != color)
414             setCGFillColor(context, oldFillColor);
415     }
416 }
417
418 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color)
419 {
420     if (paintingDisabled() || !color.alpha())
421         return;
422
423     CGContextRef context = platformContext();
424     Color oldFillColor = fillColor();
425     if (oldFillColor != color)
426         setCGFillColor(context, color);
427
428     addPath(Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight));
429     CGContextFillPath(context);
430
431     if (oldFillColor != color)
432         setCGFillColor(context, oldFillColor);
433 }
434
435
436 void GraphicsContext::clip(const IntRect& rect)
437 {
438     if (paintingDisabled())
439         return;
440     CGContextClipToRect(platformContext(), rect);
441     m_data->clip(rect);
442 }
443
444 void GraphicsContext::clipOut(const IntRect& rect)
445 {
446     if (paintingDisabled())
447         return;
448         
449     CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect };
450     CGContextBeginPath(platformContext());
451     CGContextAddRects(platformContext(), rects, 2);
452     CGContextEOClip(platformContext());
453 }
454
455 void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
456 {
457     if (paintingDisabled())
458         return;
459         
460     CGContextBeginPath(platformContext());
461     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
462     CGContextAddEllipseInRect(platformContext(), rect);
463     CGContextEOClip(platformContext());
464 }
465
466 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
467 {
468     if (paintingDisabled())
469         return;
470
471     clip(rect);
472     CGContextRef context = platformContext();
473     
474     // Add outer ellipse
475     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
476     // Add inner ellipse.
477     CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
478         rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
479     
480     CGContextEOClip(context);
481 }
482
483 void GraphicsContext::beginTransparencyLayer(float opacity)
484 {
485     if (paintingDisabled())
486         return;
487     CGContextRef context = platformContext();
488     CGContextSaveGState(context);
489     CGContextSetAlpha(context, opacity);
490     CGContextBeginTransparencyLayer(context, 0);
491     m_data->beginTransparencyLayer();
492     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
493 }
494
495 void GraphicsContext::endTransparencyLayer()
496 {
497     if (paintingDisabled())
498         return;
499     CGContextRef context = platformContext();
500     CGContextEndTransparencyLayer(context);
501     CGContextRestoreGState(context);
502     m_data->endTransparencyLayer();
503     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
504 }
505
506 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
507 {
508     // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
509     blur = min(blur, 1000);
510
511     if (paintingDisabled())
512         return;
513     CGContextRef context = platformContext();
514
515     CGFloat width = size.width();
516     CGFloat height = size.height();
517
518 #if PLATFORM(MAC)
519     // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated
520     // to the desired integer.
521     // FIXME: This is not needed post-Leopard.
522     static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128);
523     if (width > 0)
524         width += extraShadowOffset;
525     else if (width < 0)
526         width -= extraShadowOffset;
527
528     if (height > 0)
529         height += extraShadowOffset;
530     else if (height < 0)
531         height -= extraShadowOffset;
532 #endif
533
534     // Check for an invalid color, as this means that the color was not set for the shadow
535     // and we should therefore just use the default shadow color.
536     if (!color.isValid())
537         CGContextSetShadow(context, CGSizeMake(width, -height), blur); // y is flipped.
538     else {
539         CGColorRef colorCG = cgColor(color);
540         CGContextSetShadowWithColor(context,
541                                     CGSizeMake(width, -height), // y is flipped.
542                                     blur, 
543                                     colorCG);
544         CGColorRelease(colorCG);
545     }
546 }
547
548 void GraphicsContext::clearShadow()
549 {
550     if (paintingDisabled())
551         return;
552     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
553 }
554
555 void GraphicsContext::setMiterLimit(float limit)
556 {
557     if (paintingDisabled())
558         return;
559     CGContextSetMiterLimit(platformContext(), limit);
560 }
561
562 void GraphicsContext::setAlpha(float alpha)
563 {
564     if (paintingDisabled())
565         return;
566     CGContextSetAlpha(platformContext(), alpha);
567 }
568
569 void GraphicsContext::clearRect(const FloatRect& r)
570 {
571     if (paintingDisabled())
572         return;
573     CGContextClearRect(platformContext(), r);
574 }
575
576 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
577 {
578     if (paintingDisabled())
579         return;
580     CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
581 }
582
583 void GraphicsContext::setLineCap(LineCap cap)
584 {
585     if (paintingDisabled())
586         return;
587     switch (cap) {
588         case ButtCap:
589             CGContextSetLineCap(platformContext(), kCGLineCapButt);
590             break;
591         case RoundCap:
592             CGContextSetLineCap(platformContext(), kCGLineCapRound);
593             break;
594         case SquareCap:
595             CGContextSetLineCap(platformContext(), kCGLineCapSquare);
596             break;
597     }
598 }
599
600 void GraphicsContext::setLineJoin(LineJoin join)
601 {
602     if (paintingDisabled())
603         return;
604     switch (join) {
605         case MiterJoin:
606             CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
607             break;
608         case RoundJoin:
609             CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
610             break;
611         case BevelJoin:
612             CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
613             break;
614     }
615 }
616  
617 void GraphicsContext::beginPath()
618 {
619     CGContextBeginPath(platformContext());
620 }
621
622 void GraphicsContext::addPath(const Path& path)
623 {
624     CGContextAddPath(platformContext(), path.platformPath());
625 }
626
627 void GraphicsContext::clip(const Path& path)
628 {
629     if (paintingDisabled())
630         return;
631     CGContextRef context = platformContext();
632     CGContextBeginPath(context);
633     CGContextAddPath(context, path.platformPath());
634     CGContextClip(context);
635     m_data->clip(path);
636 }
637
638 void GraphicsContext::clipOut(const Path& path)
639 {
640     if (paintingDisabled())
641         return;
642         
643     CGContextBeginPath(platformContext());
644     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
645     CGContextAddPath(platformContext(), path.platformPath());
646     CGContextEOClip(platformContext());
647 }
648
649 void GraphicsContext::scale(const FloatSize& size)
650 {
651     if (paintingDisabled())
652         return;
653     CGContextScaleCTM(platformContext(), size.width(), size.height());
654     m_data->scale(size);
655     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
656 }
657
658 void GraphicsContext::rotate(float angle)
659 {
660     if (paintingDisabled())
661         return;
662     CGContextRotateCTM(platformContext(), angle);
663     m_data->rotate(angle);
664     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
665 }
666
667 void GraphicsContext::translate(float x, float y)
668 {
669     if (paintingDisabled())
670         return;
671     CGContextTranslateCTM(platformContext(), x, y);
672     m_data->translate(x, y);
673     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
674 }
675
676 void GraphicsContext::concatCTM(const AffineTransform& transform)
677 {
678     if (paintingDisabled())
679         return;
680     CGContextConcatCTM(platformContext(), transform);
681     m_data->concatCTM(transform);
682     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
683 }
684
685 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
686 {
687     // It is not enough just to round to pixels in device space. The rotation part of the 
688     // affine transform matrix to device space can mess with this conversion if we have a
689     // rotating image like the hands of the world clock widget. We just need the scale, so 
690     // we get the affine transform matrix and extract the scale.
691
692     if (m_data->m_userToDeviceTransformKnownToBeIdentity)
693         return rect;
694
695     CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
696     if (CGAffineTransformIsIdentity(deviceMatrix)) {
697         m_data->m_userToDeviceTransformKnownToBeIdentity = true;
698         return rect;
699     }
700
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 width, bool printing)
725 {
726     if (paintingDisabled())
727         return;
728
729     if (width <= 0)
730         return;
731
732     CGContextSaveGState(platformContext());
733
734     float x = point.x();
735     float y = point.y();
736     float lineLength = width;
737
738     // Use a minimum thickness of 0.5 in user space.
739     // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use.
740     float thickness = max(strokeThickness(), 0.5f);
741
742     if (!printing) {
743         // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
744         float adjustedThickness = max(thickness, 1.0f);
745
746         // FIXME: This should be done a better way.
747         // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space
748         // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels
749         // in device space will make the underlines too thick.
750         CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness));
751         if (lineRect.size.height < thickness * 2.0) {
752             x = lineRect.origin.x;
753             y = lineRect.origin.y;
754             lineLength = lineRect.size.width;
755             thickness = lineRect.size.height;
756             CGContextSetShouldAntialias(platformContext(), false);
757         }
758     }
759     
760     if (fillColor() != strokeColor())
761         setCGFillColor(platformContext(), strokeColor());
762     CGContextFillRect(platformContext(), CGRectMake(x, y, lineLength, thickness));
763
764     CGContextRestoreGState(platformContext());
765 }
766
767 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
768 {
769     if (paintingDisabled())
770         return;
771         
772     CFURLRef urlRef = link.createCFURL();
773     if (urlRef) {
774         CGContextRef context = platformContext();
775         
776         // Get the bounding box to handle clipping.
777         CGRect box = CGContextGetClipBoundingBox(context);
778
779         IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
780         IntRect rect = destRect;
781         rect.intersect(intBox);
782
783         CGPDFContextSetURLForRect(context, urlRef,
784             CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
785
786         CFRelease(urlRef);
787     }
788 }
789
790 void GraphicsContext::setUseLowQualityImageInterpolation(bool lowQualityMode)
791 {
792     if (paintingDisabled())
793         return;
794         
795     CGContextSetInterpolationQuality(platformContext(), lowQualityMode ? kCGInterpolationNone : kCGInterpolationDefault);
796 }
797
798 bool GraphicsContext::useLowQualityImageInterpolation() const
799 {
800     if (paintingDisabled())
801         return false;
802     
803     return CGContextGetInterpolationQuality(platformContext());
804 }
805
806 void GraphicsContext::setPlatformTextDrawingMode(int mode)
807 {
808     if (paintingDisabled())
809         return;
810
811     // Wow, wish CG had used bits here.
812     CGContextRef context = platformContext();
813     switch (mode) {
814         case cTextInvisible: // Invisible
815             CGContextSetTextDrawingMode(context, kCGTextInvisible);
816             break;
817         case cTextFill: // Fill
818             CGContextSetTextDrawingMode(context, kCGTextFill);
819             break;
820         case cTextStroke: // Stroke
821             CGContextSetTextDrawingMode(context, kCGTextStroke);
822             break;
823         case 3: // Fill | Stroke
824             CGContextSetTextDrawingMode(context, kCGTextFillStroke);
825             break;
826         case cTextClip: // Clip
827             CGContextSetTextDrawingMode(context, kCGTextClip);
828             break;
829         case 5: // Fill | Clip
830             CGContextSetTextDrawingMode(context, kCGTextFillClip);
831             break;
832         case 6: // Stroke | Clip
833             CGContextSetTextDrawingMode(context, kCGTextStrokeClip);
834             break;
835         case 7: // Fill | Stroke | Clip
836             CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip);
837             break;
838         default:
839             break;
840     }
841 }
842
843 void GraphicsContext::setPlatformStrokeColor(const Color& color)
844 {
845     if (paintingDisabled())
846         return;
847     setCGStrokeColor(platformContext(), color);
848 }
849
850 void GraphicsContext::setPlatformStrokeThickness(float thickness)
851 {
852     if (paintingDisabled())
853         return;
854     CGContextSetLineWidth(platformContext(), thickness);
855 }
856
857 void GraphicsContext::setPlatformFillColor(const Color& color)
858 {
859     if (paintingDisabled())
860         return;
861     setCGFillColor(platformContext(), color);
862 }
863
864 void GraphicsContext::setUseAntialiasing(bool enable)
865 {
866     if (paintingDisabled())
867         return;
868     CGContextSetShouldAntialias(platformContext(), enable);
869 }
870
871 }
872
873 #endif // PLATFORM(CG)