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::fillRect(const FloatRect& rect, const Color& color)
320 if (paintingDisabled())
323 CGContextRef context = platformContext();
324 setCGFillColor(context, color);
325 CGContextFillRect(context, rect);
329 void GraphicsContext::addClip(const IntRect& rect)
331 if (paintingDisabled())
333 CGContextClipToRect(platformContext(), rect);
336 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
337 const IntSize& bottomLeft, const IntSize& bottomRight)
339 if (paintingDisabled())
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())
347 int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
348 if (requiredHeight > rect.height())
354 // OK, the curves can fit.
355 CGContextRef context = platformContext();
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));
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);
391 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
393 return new KRenderingDeviceContextQuartz(platformContext());
397 void GraphicsContext::beginTransparencyLayer(float opacity)
399 if (paintingDisabled())
401 CGContextRef context = platformContext();
402 CGContextSaveGState(context);
403 CGContextSetAlpha(context, opacity);
404 CGContextBeginTransparencyLayer(context, 0);
407 void GraphicsContext::endTransparencyLayer()
409 if (paintingDisabled())
411 CGContextRef context = platformContext();
412 CGContextEndTransparencyLayer(context);
413 CGContextRestoreGState(context);
416 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
418 if (paintingDisabled())
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.
426 CGColorRef colorCG = cgColor(color);
427 CGContextSetShadowWithColor(context,
428 CGSizeMake(size.width(), -size.height()), // y is flipped.
431 CGColorRelease(colorCG);
435 void GraphicsContext::clearShadow()
437 if (paintingDisabled())
439 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
442 void GraphicsContext::setLineWidth(float width)
444 if (paintingDisabled())
446 CGContextSetLineWidth(platformContext(), width);
449 void GraphicsContext::setMiterLimit(float limit)
451 if (paintingDisabled())
453 CGContextSetMiterLimit(platformContext(), limit);
456 void GraphicsContext::setAlpha(float alpha)
458 if (paintingDisabled())
460 CGContextSetAlpha(platformContext(), alpha);
463 void GraphicsContext::clearRect(const FloatRect& r)
465 if (paintingDisabled())
467 CGContextClearRect(platformContext(), r);
470 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
472 if (paintingDisabled())
474 CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
477 void GraphicsContext::setLineCap(LineCap cap)
479 if (paintingDisabled())
483 CGContextSetLineCap(platformContext(), kCGLineCapButt);
486 CGContextSetLineCap(platformContext(), kCGLineCapRound);
489 CGContextSetLineCap(platformContext(), kCGLineCapSquare);
494 void GraphicsContext::setLineJoin(LineJoin join)
496 if (paintingDisabled())
500 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
503 CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
506 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
511 void GraphicsContext::clip(const Path& path)
513 if (paintingDisabled())
515 CGContextRef context = platformContext();
516 CGContextBeginPath(context);
517 CGContextAddPath(context, path.platformPath());
518 CGContextClip(context);
521 void GraphicsContext::scale(const FloatSize& size)
523 if (paintingDisabled())
525 CGContextScaleCTM(platformContext(), size.width(), size.height());
528 void GraphicsContext::rotate(float angle)
530 if (paintingDisabled())
532 CGContextRotateCTM(platformContext(), angle);
535 void GraphicsContext::translate(const FloatSize& size)
537 if (paintingDisabled())
539 CGContextTranslateCTM(platformContext(), size.width(), size.height());