This patch makes drawHighlightForText a completely cross-platform method
[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 #include "config.h"
27 #include "GraphicsContext.h"
28
29 #include "IntPointArray.h"
30 #include "KRenderingDeviceQuartz.h"
31 #include "Path.h"
32
33 using namespace std;
34
35 namespace WebCore {
36
37 // NSColor, NSBezierPath, and NSGraphicsContext
38 // calls in this file are all exception-safe, so we don't block
39 // exceptions for those.
40
41 static void setCGFillColor(CGContextRef context, const Color& color)
42 {
43     CGFloat red, green, blue, alpha;
44     color.getRGBA(red, green, blue, alpha);
45     CGContextSetRGBFillColor(context, red, green, blue, alpha);
46 }
47
48 static void setCGStrokeColor(CGContextRef context, const Color& color)
49 {
50     CGFloat red, green, blue, alpha;
51     color.getRGBA(red, green, blue, alpha);
52     CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
53 }
54
55 void GraphicsContext::savePlatformState()
56 {
57     CGContextSaveGState(platformContext());
58 }
59
60 void GraphicsContext::restorePlatformState()
61 {
62     CGContextRestoreGState(platformContext());
63 }
64
65 // Draws a filled rectangle with a stroked border.
66 void GraphicsContext::drawRect(const IntRect& rect)
67 {
68     if (paintingDisabled())
69         return;
70
71     CGContextRef context = platformContext();
72
73     if (fillColor().alpha()) {
74         setCGFillColor(context, fillColor());
75         CGContextFillRect(context, rect);
76     }
77
78     if (pen().style() != Pen::Pen::NoPen) {
79         setCGFillColor(context, pen().color());
80         CGRect rects[4] = {
81             FloatRect(rect.x(), rect.y(), rect.width(), 1),
82             FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
83             FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
84             FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
85         };
86         CGContextFillRects(context, rects, 4);
87     }
88 }
89
90 // This is only used to draw borders.
91 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
92 {
93     if (paintingDisabled())
94         return;
95
96     Pen::PenStyle penStyle = pen().style();
97     if (penStyle == Pen::Pen::NoPen)
98         return;
99     float width = pen().width();
100     if (width < 1)
101         width = 1;
102
103     FloatPoint p1 = point1;
104     FloatPoint p2 = point2;
105     bool isVerticalLine = (p1.x() == p2.x());
106     
107     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
108     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
109     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
110     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
111     if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
112         if (isVerticalLine) {
113             p1.move(0, width);
114             p2.move(0, -width);
115         } else {
116             p1.move(width, 0);
117             p2.move(-width, 0);
118         }
119     }
120     
121     if (((int)width) % 2) {
122         if (isVerticalLine) {
123             // We're a vertical line.  Adjust our x.
124             p1.move(0.5, 0);
125             p2.move(0.5, 0);
126         } else {
127             // We're a horizontal line. Adjust our y.
128             p1.move(0, 0.5);
129             p2.move(0, 0.5);
130         }
131     }
132     
133     int patWidth = 0;
134     switch (penStyle) {
135         case Pen::NoPen:
136         case Pen::SolidLine:
137             break;
138         case Pen::DotLine:
139             patWidth = (int)width;
140             break;
141         case Pen::DashLine:
142             patWidth = 3 * (int)width;
143             break;
144     }
145
146     CGContextRef context = platformContext();
147
148     CGContextSaveGState(context);
149
150     setCGStrokeColor(context, pen().color());
151
152     CGContextSetShouldAntialias(context, false);
153
154     if (patWidth) {
155         // Do a rect fill of our endpoints.  This ensures we always have the
156         // appearance of being a border.  We then draw the actual dotted/dashed line.
157         setCGFillColor(context, pen().color());
158         if (isVerticalLine) {
159             CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
160             CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
161         } else {
162             CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
163             CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
164         }
165
166         // Example: 80 pixels with a width of 30 pixels.
167         // Remainder is 20.  The maximum pixels of line we could paint
168         // will be 50 pixels.
169         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
170         int remainder = distance % patWidth;
171         int coverage = distance - remainder;
172         int numSegments = coverage / patWidth;
173
174         float patternOffset = 0;
175         // Special case 1px dotted borders for speed.
176         if (patWidth == 1)
177             patternOffset = 1.0;
178         else {
179             bool evenNumberOfSegments = numSegments % 2 == 0;
180             if (remainder)
181                 evenNumberOfSegments = !evenNumberOfSegments;
182             if (evenNumberOfSegments) {
183                 if (remainder) {
184                     patternOffset += patWidth - remainder;
185                     patternOffset += remainder / 2;
186                 } else
187                     patternOffset = patWidth / 2;
188             } else {
189                 if (remainder)
190                     patternOffset = (patWidth - remainder)/2;
191             }
192         }
193         
194         const CGFloat dottedLine[2] = { patWidth, patWidth };
195         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
196     }
197     
198     CGContextSetLineWidth(context, width);
199
200     CGContextBeginPath(context);
201     CGContextMoveToPoint(context, p1.x(), p1.y());
202     CGContextAddLineToPoint(context, p2.x(), p2.y());
203
204     CGContextStrokePath(context);
205
206     CGContextRestoreGState(context);
207 }
208
209 // This method is only used to draw the little circles used in lists.
210 void GraphicsContext::drawEllipse(const IntRect& rect)
211 {
212     // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
213     // This code can only handle circles, not ellipses. But khtml only
214     // uses it for circles.
215     ASSERT(rect.width() == rect.height());
216
217     if (paintingDisabled())
218         return;
219         
220     CGContextRef context = platformContext();
221     CGContextBeginPath(context);
222     float r = (float)rect.width() / 2;
223     CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, true);
224     CGContextClosePath(context);
225
226     if (fillColor().alpha()) {
227         setCGFillColor(context, fillColor());
228         if (pen().style() != Pen::NoPen) {
229             // stroke and fill
230             setCGStrokeColor(context, pen().color());
231             unsigned penWidth = pen().width();
232             if (penWidth == 0) 
233                 penWidth++;
234             CGContextSetLineWidth(context, penWidth);
235             CGContextDrawPath(context, kCGPathFillStroke);
236         } else
237             CGContextFillPath(context);
238     } else if (pen().style() != Pen::NoPen) {
239         setCGStrokeColor(context, pen().color());
240         unsigned penWidth = pen().width();
241         if (penWidth == 0) 
242             penWidth++;
243         CGContextSetLineWidth(context, penWidth);
244         CGContextStrokePath(context);
245     }
246 }
247
248
249 void GraphicsContext::drawArc(int x, int y, int w, int h, int a, int alen)
250
251     // Only supports arc on circles.  That's all khtml needs.
252     ASSERT(w == h);
253
254     if (paintingDisabled())
255         return;
256     
257     if (pen().style() != Pen::NoPen) {
258         CGContextRef context = platformContext();
259         CGContextBeginPath(context);
260         
261         float r = (float)w / 2;
262         float fa = (float)a / 16;
263         float falen =  fa + (float)alen / 16;
264         CGContextAddArc(context, x + r, y + r, r, -fa * M_PI/180, -falen * M_PI/180, true);
265         
266         setCGStrokeColor(context, pen().color());
267         CGContextSetLineWidth(context, pen().width());
268         CGContextStrokePath(context);
269     }
270 }
271
272 void GraphicsContext::drawConvexPolygon(const IntPointArray& points)
273 {
274     if (paintingDisabled())
275         return;
276
277     int npoints = points.size();
278     if (npoints <= 1)
279         return;
280
281     CGContextRef context = platformContext();
282
283     CGContextSaveGState(context);
284
285     CGContextSetShouldAntialias(context, false);
286     
287     CGContextBeginPath(context);
288     CGContextMoveToPoint(context, points[0].x(), points[0].y());
289     for (int i = 1; i < npoints; i++)
290         CGContextAddLineToPoint(context, points[i].x(), points[i].y());
291     CGContextClosePath(context);
292
293     if (fillColor().alpha()) {
294         setCGFillColor(context, fillColor());
295         CGContextEOFillPath(context);
296     }
297
298     if (pen().style() != Pen::NoPen) {
299         setCGStrokeColor(context, pen().color());
300         CGContextSetLineWidth(context, pen().width());
301         CGContextStrokePath(context);
302     }
303
304     CGContextRestoreGState(context);
305 }
306
307 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
308 {
309     if (paintingDisabled())
310         return;
311     if (color.alpha()) {
312         CGContextRef context = platformContext();
313         setCGFillColor(context, color);
314         CGContextFillRect(context, rect);
315     }
316 }
317
318 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
319 {
320     if (paintingDisabled())
321         return;
322     if (color.alpha()) {
323         CGContextRef context = platformContext();
324         setCGFillColor(context, color);
325         CGContextFillRect(context, rect);
326     }
327 }
328
329 void GraphicsContext::addClip(const IntRect& rect)
330 {
331     if (paintingDisabled())
332         return;
333     CGContextClipToRect(platformContext(), rect);
334 }
335
336 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
337     const IntSize& bottomLeft, const IntSize& bottomRight)
338 {
339     if (paintingDisabled())
340         return;
341
342     // Need sufficient width and height to contain these curves.  Sanity check our top/bottom
343     // values and our width/height values to make sure the curves can all fit.
344     int requiredWidth = max(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
345     if (requiredWidth > rect.width())
346         return;
347     int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
348     if (requiredHeight > rect.height())
349         return;
350  
351     // Clip to our rect.
352     addClip(rect);
353
354     // OK, the curves can fit.
355     CGContextRef context = platformContext();
356     
357     // Add the four ellipses to the path.  Technically this really isn't good enough, since we could end up
358     // not clipping the other 3/4 of the ellipse we don't care about.  We're relying on the fact that for
359     // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
360     // be subsumed by the other).
361     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
362     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
363                                                   topRight.width() * 2, topRight.height() * 2));
364     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
365                                                   bottomLeft.width() * 2, bottomLeft.height() * 2));
366     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
367                                                   rect.bottom() - bottomRight.height() * 2,
368                                                   bottomRight.width() * 2, bottomRight.height() * 2));
369     
370     // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
371     CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
372                                          rect.width() - topLeft.width() - topRight.width(),
373                                          max(topLeft.height(), topRight.height())));
374     CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(), 
375                                          rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
376                                          rect.width() - bottomLeft.width() - bottomRight.width(),
377                                          max(bottomLeft.height(), bottomRight.height())));
378     CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
379                                          max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
380     CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
381                                          rect.y() + topRight.height(),
382                                          max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
383     CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
384                                          rect.y() + max(topLeft.height(), topRight.height()),
385                                          rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
386                                          rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
387     CGContextClip(context);
388 }
389
390 #if SVG_SUPPORT
391 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
392 {
393     return new KRenderingDeviceContextQuartz(platformContext());
394 }
395 #endif
396
397 void GraphicsContext::beginTransparencyLayer(float opacity)
398 {
399     if (paintingDisabled())
400         return;
401     CGContextRef context = platformContext();
402     CGContextSaveGState(context);
403     CGContextSetAlpha(context, opacity);
404     CGContextBeginTransparencyLayer(context, 0);
405 }
406
407 void GraphicsContext::endTransparencyLayer()
408 {
409     if (paintingDisabled())
410         return;
411     CGContextRef context = platformContext();
412     CGContextEndTransparencyLayer(context);
413     CGContextRestoreGState(context);
414 }
415
416 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
417 {
418     if (paintingDisabled())
419         return;
420     // Check for an invalid color, as this means that the color was not set for the shadow
421     // and we should therefore just use the default shadow color.
422     CGContextRef context = platformContext();
423     if (!color.isValid())
424         CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
425     else {
426         CGColorRef colorCG = cgColor(color);
427         CGContextSetShadowWithColor(context,
428                                     CGSizeMake(size.width(), -size.height()), // y is flipped.
429                                     blur, 
430                                     colorCG);
431         CGColorRelease(colorCG);
432     }
433 }
434
435 void GraphicsContext::clearShadow()
436 {
437     if (paintingDisabled())
438         return;
439     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
440 }
441
442 void GraphicsContext::setLineWidth(float width)
443 {
444     if (paintingDisabled())
445         return;
446     CGContextSetLineWidth(platformContext(), width);
447 }
448
449 void GraphicsContext::setMiterLimit(float limit)
450 {
451     if (paintingDisabled())
452         return;
453     CGContextSetMiterLimit(platformContext(), limit);
454 }
455
456 void GraphicsContext::setAlpha(float alpha)
457 {
458     if (paintingDisabled())
459         return;
460     CGContextSetAlpha(platformContext(), alpha);
461 }
462
463 void GraphicsContext::clearRect(const FloatRect& r)
464 {
465     if (paintingDisabled())
466         return;
467     CGContextClearRect(platformContext(), r);
468 }
469
470 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
471 {
472     if (paintingDisabled())
473         return;
474     CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
475 }
476
477 void GraphicsContext::setLineCap(LineCap cap)
478 {
479     if (paintingDisabled())
480         return;
481     switch (cap) {
482         case ButtCap:
483             CGContextSetLineCap(platformContext(), kCGLineCapButt);
484             break;
485         case RoundCap:
486             CGContextSetLineCap(platformContext(), kCGLineCapRound);
487             break;
488         case SquareCap:
489             CGContextSetLineCap(platformContext(), kCGLineCapSquare);
490             break;
491     }
492 }
493
494 void GraphicsContext::setLineJoin(LineJoin join)
495 {
496     if (paintingDisabled())
497         return;
498     switch (join) {
499         case MiterJoin:
500             CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
501             break;
502         case RoundJoin:
503             CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
504             break;
505         case BevelJoin:
506             CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
507             break;
508     }
509 }
510
511 void GraphicsContext::clip(const Path& path)
512 {
513     if (paintingDisabled())
514         return;
515     CGContextRef context = platformContext();
516     CGContextBeginPath(context);
517     CGContextAddPath(context, path.platformPath());
518     CGContextClip(context);
519 }
520
521 void GraphicsContext::scale(const FloatSize& size)
522 {
523     if (paintingDisabled())
524         return;
525     CGContextScaleCTM(platformContext(), size.width(), size.height());
526 }
527
528 void GraphicsContext::rotate(float angle)
529 {
530     if (paintingDisabled())
531         return;
532     CGContextRotateCTM(platformContext(), angle);
533 }
534
535 void GraphicsContext::translate(const FloatSize& size)
536 {
537     if (paintingDisabled())
538         return;
539     CGContextTranslateCTM(platformContext(), size.width(), size.height());
540 }
541
542 }