fa290f10a06d32ea1963fb0c89a1f94940bd495e
[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::addClip(const IntRect& rect)
319 {
320     if (paintingDisabled())
321         return;
322     CGContextClipToRect(platformContext(), rect);
323 }
324
325 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
326     const IntSize& bottomLeft, const IntSize& bottomRight)
327 {
328     if (paintingDisabled())
329         return;
330
331     // Need sufficient width and height to contain these curves.  Sanity check our top/bottom
332     // values and our width/height values to make sure the curves can all fit.
333     int requiredWidth = max(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
334     if (requiredWidth > rect.width())
335         return;
336     int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
337     if (requiredHeight > rect.height())
338         return;
339  
340     // Clip to our rect.
341     addClip(rect);
342
343     // OK, the curves can fit.
344     CGContextRef context = platformContext();
345     
346     // Add the four ellipses to the path.  Technically this really isn't good enough, since we could end up
347     // not clipping the other 3/4 of the ellipse we don't care about.  We're relying on the fact that for
348     // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
349     // be subsumed by the other).
350     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
351     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
352                                                   topRight.width() * 2, topRight.height() * 2));
353     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
354                                                   bottomLeft.width() * 2, bottomLeft.height() * 2));
355     CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
356                                                   rect.bottom() - bottomRight.height() * 2,
357                                                   bottomRight.width() * 2, bottomRight.height() * 2));
358     
359     // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
360     CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
361                                          rect.width() - topLeft.width() - topRight.width(),
362                                          max(topLeft.height(), topRight.height())));
363     CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(), 
364                                          rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
365                                          rect.width() - bottomLeft.width() - bottomRight.width(),
366                                          max(bottomLeft.height(), bottomRight.height())));
367     CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
368                                          max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
369     CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
370                                          rect.y() + topRight.height(),
371                                          max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
372     CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
373                                          rect.y() + max(topLeft.height(), topRight.height()),
374                                          rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
375                                          rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
376     CGContextClip(context);
377 }
378
379 #if SVG_SUPPORT
380 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
381 {
382     return new KRenderingDeviceContextQuartz(platformContext());
383 }
384 #endif
385
386 void GraphicsContext::beginTransparencyLayer(float opacity)
387 {
388     if (paintingDisabled())
389         return;
390     CGContextRef context = platformContext();
391     CGContextSaveGState(context);
392     CGContextSetAlpha(context, opacity);
393     CGContextBeginTransparencyLayer(context, 0);
394 }
395
396 void GraphicsContext::endTransparencyLayer()
397 {
398     if (paintingDisabled())
399         return;
400     CGContextRef context = platformContext();
401     CGContextEndTransparencyLayer(context);
402     CGContextRestoreGState(context);
403 }
404
405 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
406 {
407     if (paintingDisabled())
408         return;
409     // Check for an invalid color, as this means that the color was not set for the shadow
410     // and we should therefore just use the default shadow color.
411     CGContextRef context = platformContext();
412     if (!color.isValid())
413         CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
414     else {
415         CGColorRef colorCG = cgColor(color);
416         CGContextSetShadowWithColor(context,
417                                     CGSizeMake(size.width(), -size.height()), // y is flipped.
418                                     blur, 
419                                     colorCG);
420         CGColorRelease(colorCG);
421     }
422 }
423
424 void GraphicsContext::clearShadow()
425 {
426     if (paintingDisabled())
427         return;
428     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
429 }
430
431 void GraphicsContext::setLineWidth(float width)
432 {
433     if (paintingDisabled())
434         return;
435     CGContextSetLineWidth(platformContext(), width);
436 }
437
438 void GraphicsContext::setMiterLimit(float limit)
439 {
440     if (paintingDisabled())
441         return;
442     CGContextSetMiterLimit(platformContext(), limit);
443 }
444
445 void GraphicsContext::setAlpha(float alpha)
446 {
447     if (paintingDisabled())
448         return;
449     CGContextSetAlpha(platformContext(), alpha);
450 }
451
452 void GraphicsContext::clearRect(const FloatRect& r)
453 {
454     if (paintingDisabled())
455         return;
456     CGContextClearRect(platformContext(), r);
457 }
458
459 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
460 {
461     if (paintingDisabled())
462         return;
463     CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
464 }
465
466 void GraphicsContext::setLineCap(LineCap cap)
467 {
468     if (paintingDisabled())
469         return;
470     switch (cap) {
471         case ButtCap:
472             CGContextSetLineCap(platformContext(), kCGLineCapButt);
473             break;
474         case RoundCap:
475             CGContextSetLineCap(platformContext(), kCGLineCapRound);
476             break;
477         case SquareCap:
478             CGContextSetLineCap(platformContext(), kCGLineCapSquare);
479             break;
480     }
481 }
482
483 void GraphicsContext::setLineJoin(LineJoin join)
484 {
485     if (paintingDisabled())
486         return;
487     switch (join) {
488         case MiterJoin:
489             CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
490             break;
491         case RoundJoin:
492             CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
493             break;
494         case BevelJoin:
495             CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
496             break;
497     }
498 }
499
500 void GraphicsContext::clip(const Path& path)
501 {
502     if (paintingDisabled())
503         return;
504     CGContextRef context = platformContext();
505     CGContextBeginPath(context);
506     CGContextAddPath(context, path.platformPath());
507     CGContextClip(context);
508 }
509
510 void GraphicsContext::scale(const FloatSize& size)
511 {
512     if (paintingDisabled())
513         return;
514     CGContextScaleCTM(platformContext(), size.width(), size.height());
515 }
516
517 void GraphicsContext::rotate(float angle)
518 {
519     if (paintingDisabled())
520         return;
521     CGContextRotateCTM(platformContext(), angle);
522 }
523
524 void GraphicsContext::translate(const FloatSize& size)
525 {
526     if (paintingDisabled())
527         return;
528     CGContextTranslateCTM(platformContext(), size.width(), size.height());
529 }
530
531 }