2 * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "GraphicsContext.h"
29 #include "IntPointArray.h"
30 #include "KRenderingDeviceQuartz.h"
37 // NSColor, NSBezierPath, and NSGraphicsContext
38 // calls in this file are all exception-safe, so we don't block
39 // exceptions for those.
41 static void setCGFillColor(CGContextRef context, const Color& color)
43 CGFloat red, green, blue, alpha;
44 color.getRGBA(red, green, blue, alpha);
45 CGContextSetRGBFillColor(context, red, green, blue, alpha);
48 static void setCGStrokeColor(CGContextRef context, const Color& color)
50 CGFloat red, green, blue, alpha;
51 color.getRGBA(red, green, blue, alpha);
52 CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
55 void GraphicsContext::savePlatformState()
57 CGContextSaveGState(platformContext());
60 void GraphicsContext::restorePlatformState()
62 CGContextRestoreGState(platformContext());
65 // Draws a filled rectangle with a stroked border.
66 void GraphicsContext::drawRect(const IntRect& rect)
68 if (paintingDisabled())
71 CGContextRef context = platformContext();
73 if (fillColor().alpha()) {
74 setCGFillColor(context, fillColor());
75 CGContextFillRect(context, rect);
78 if (pen().style() != Pen::Pen::NoPen) {
79 setCGFillColor(context, pen().color());
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)
86 CGContextFillRects(context, rects, 4);
90 // This is only used to draw borders.
91 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
93 if (paintingDisabled())
96 Pen::PenStyle penStyle = pen().style();
97 if (penStyle == Pen::Pen::NoPen)
99 float width = pen().width();
103 FloatPoint p1 = point1;
104 FloatPoint p2 = point2;
105 bool isVerticalLine = (p1.x() == p2.x());
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) {
121 if (((int)width) % 2) {
122 if (isVerticalLine) {
123 // We're a vertical line. Adjust our x.
127 // We're a horizontal line. Adjust our y.
139 patWidth = (int)width;
142 patWidth = 3 * (int)width;
146 CGContextRef context = platformContext();
148 CGContextSaveGState(context);
150 setCGStrokeColor(context, pen().color());
152 CGContextSetShouldAntialias(context, false);
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));
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));
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;
174 float patternOffset = 0;
175 // Special case 1px dotted borders for speed.
179 bool evenNumberOfSegments = numSegments % 2 == 0;
181 evenNumberOfSegments = !evenNumberOfSegments;
182 if (evenNumberOfSegments) {
184 patternOffset += patWidth - remainder;
185 patternOffset += remainder / 2;
187 patternOffset = patWidth / 2;
190 patternOffset = (patWidth - remainder)/2;
194 const CGFloat dottedLine[2] = { patWidth, patWidth };
195 CGContextSetLineDash(context, patternOffset, dottedLine, 2);
198 CGContextSetLineWidth(context, width);
200 CGContextBeginPath(context);
201 CGContextMoveToPoint(context, p1.x(), p1.y());
202 CGContextAddLineToPoint(context, p2.x(), p2.y());
204 CGContextStrokePath(context);
206 CGContextRestoreGState(context);
209 // This method is only used to draw the little circles used in lists.
210 void GraphicsContext::drawEllipse(const IntRect& rect)
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());
217 if (paintingDisabled())
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);
226 if (fillColor().alpha()) {
227 setCGFillColor(context, fillColor());
228 if (pen().style() != Pen::NoPen) {
230 setCGStrokeColor(context, pen().color());
231 unsigned penWidth = pen().width();
234 CGContextSetLineWidth(context, penWidth);
235 CGContextDrawPath(context, kCGPathFillStroke);
237 CGContextFillPath(context);
238 } else if (pen().style() != Pen::NoPen) {
239 setCGStrokeColor(context, pen().color());
240 unsigned penWidth = pen().width();
243 CGContextSetLineWidth(context, penWidth);
244 CGContextStrokePath(context);
249 void GraphicsContext::drawArc(int x, int y, int w, int h, int a, int alen)
251 // Only supports arc on circles. That's all khtml needs.
254 if (paintingDisabled())
257 if (pen().style() != Pen::NoPen) {
258 CGContextRef context = platformContext();
259 CGContextBeginPath(context);
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);
266 setCGStrokeColor(context, pen().color());
267 CGContextSetLineWidth(context, pen().width());
268 CGContextStrokePath(context);
272 void GraphicsContext::drawConvexPolygon(const IntPointArray& points)
274 if (paintingDisabled())
277 int npoints = points.size();
281 CGContextRef context = platformContext();
283 CGContextSaveGState(context);
285 CGContextSetShouldAntialias(context, false);
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);
293 if (fillColor().alpha()) {
294 setCGFillColor(context, fillColor());
295 CGContextEOFillPath(context);
298 if (pen().style() != Pen::NoPen) {
299 setCGStrokeColor(context, pen().color());
300 CGContextSetLineWidth(context, pen().width());
301 CGContextStrokePath(context);
304 CGContextRestoreGState(context);
307 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
309 if (paintingDisabled())
312 CGContextRef context = platformContext();
313 setCGFillColor(context, color);
314 CGContextFillRect(context, rect);
318 void GraphicsContext::addClip(const IntRect& rect)
320 if (paintingDisabled())
322 CGContextClipToRect(platformContext(), rect);
325 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
326 const IntSize& bottomLeft, const IntSize& bottomRight)
328 if (paintingDisabled())
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())
336 int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
337 if (requiredHeight > rect.height())
343 // OK, the curves can fit.
344 CGContextRef context = platformContext();
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));
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);
380 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
382 return new KRenderingDeviceContextQuartz(platformContext());
386 void GraphicsContext::beginTransparencyLayer(float opacity)
388 if (paintingDisabled())
390 CGContextRef context = platformContext();
391 CGContextSaveGState(context);
392 CGContextSetAlpha(context, opacity);
393 CGContextBeginTransparencyLayer(context, 0);
396 void GraphicsContext::endTransparencyLayer()
398 if (paintingDisabled())
400 CGContextRef context = platformContext();
401 CGContextEndTransparencyLayer(context);
402 CGContextRestoreGState(context);
405 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
407 if (paintingDisabled())
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.
415 CGColorRef colorCG = cgColor(color);
416 CGContextSetShadowWithColor(context,
417 CGSizeMake(size.width(), -size.height()), // y is flipped.
420 CGColorRelease(colorCG);
424 void GraphicsContext::clearShadow()
426 if (paintingDisabled())
428 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
431 void GraphicsContext::setLineWidth(float width)
433 if (paintingDisabled())
435 CGContextSetLineWidth(platformContext(), width);
438 void GraphicsContext::setMiterLimit(float limit)
440 if (paintingDisabled())
442 CGContextSetMiterLimit(platformContext(), limit);
445 void GraphicsContext::setAlpha(float alpha)
447 if (paintingDisabled())
449 CGContextSetAlpha(platformContext(), alpha);
452 void GraphicsContext::clearRect(const FloatRect& r)
454 if (paintingDisabled())
456 CGContextClearRect(platformContext(), r);
459 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
461 if (paintingDisabled())
463 CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
466 void GraphicsContext::setLineCap(LineCap cap)
468 if (paintingDisabled())
472 CGContextSetLineCap(platformContext(), kCGLineCapButt);
475 CGContextSetLineCap(platformContext(), kCGLineCapRound);
478 CGContextSetLineCap(platformContext(), kCGLineCapSquare);
483 void GraphicsContext::setLineJoin(LineJoin join)
485 if (paintingDisabled())
489 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
492 CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
495 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
500 void GraphicsContext::clip(const Path& path)
502 if (paintingDisabled())
504 CGContextRef context = platformContext();
505 CGContextBeginPath(context);
506 CGContextAddPath(context, path.platformPath());
507 CGContextClip(context);
510 void GraphicsContext::scale(const FloatSize& size)
512 if (paintingDisabled())
514 CGContextScaleCTM(platformContext(), size.width(), size.height());
517 void GraphicsContext::rotate(float angle)
519 if (paintingDisabled())
521 CGContextRotateCTM(platformContext(), angle);
524 void GraphicsContext::translate(const FloatSize& size)
526 if (paintingDisabled())
528 CGContextTranslateCTM(platformContext(), size.width(), size.height());