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.
26 #define _USE_MATH_DEFINES 1
28 #include "GraphicsContext.h"
32 #include "AffineTransform.h"
34 #include <wtf/MathExtras.h>
37 #include "KRenderingDeviceQuartz.h"
40 #include "GraphicsContextPlatformPrivate.h"
46 GraphicsContext::GraphicsContext(CGContextRef cgContext)
47 : m_common(createGraphicsContextPrivate())
48 , m_data(new GraphicsContextPlatformPrivate(cgContext))
50 setPaintingDisabled(!cgContext);
53 GraphicsContext::~GraphicsContext()
55 destroyGraphicsContextPrivate(m_common);
59 void GraphicsContext::setFocusRingClip(const IntRect& r)
61 // This method only exists to work around bugs in Mac focus ring clipping.
62 m_data->m_focusRingClip = r;
65 void GraphicsContext::clearFocusRingClip()
67 // This method only exists to work around bugs in Mac focus ring clipping.
68 m_data->m_focusRingClip = IntRect();
71 CGContextRef GraphicsContext::platformContext() const
73 ASSERT(!paintingDisabled());
74 ASSERT(m_data->m_cgContext);
75 return m_data->m_cgContext;
78 static void setCGFillColor(CGContextRef context, const Color& color)
80 CGFloat red, green, blue, alpha;
81 color.getRGBA(red, green, blue, alpha);
82 CGContextSetRGBFillColor(context, red, green, blue, alpha);
85 static void setCGStrokeColor(CGContextRef context, const Color& color)
87 CGFloat red, green, blue, alpha;
88 color.getRGBA(red, green, blue, alpha);
89 CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
92 void GraphicsContext::savePlatformState()
94 CGContextSaveGState(platformContext());
97 void GraphicsContext::restorePlatformState()
99 CGContextRestoreGState(platformContext());
102 // Draws a filled rectangle with a stroked border.
103 void GraphicsContext::drawRect(const IntRect& rect)
105 if (paintingDisabled())
108 CGContextRef context = platformContext();
110 if (fillColor().alpha()) {
111 setCGFillColor(context, fillColor());
112 CGContextFillRect(context, rect);
115 if (pen().style() != Pen::NoPen) {
116 setCGFillColor(context, pen().color());
118 FloatRect(rect.x(), rect.y(), rect.width(), 1),
119 FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
120 FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
121 FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
123 CGContextFillRects(context, rects, 4);
127 // This is only used to draw borders.
128 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
130 if (paintingDisabled())
133 Pen::PenStyle penStyle = pen().style();
134 if (penStyle == Pen::NoPen)
136 float width = pen().width();
140 FloatPoint p1 = point1;
141 FloatPoint p2 = point2;
142 bool isVerticalLine = (p1.x() == p2.x());
144 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
145 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
146 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
147 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
148 if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
149 if (isVerticalLine) {
158 if (((int)width) % 2) {
159 if (isVerticalLine) {
160 // We're a vertical line. Adjust our x.
164 // We're a horizontal line. Adjust our y.
176 patWidth = (int)width;
179 patWidth = 3 * (int)width;
183 CGContextRef context = platformContext();
185 CGContextSaveGState(context);
187 setCGStrokeColor(context, pen().color());
189 CGContextSetShouldAntialias(context, false);
192 // Do a rect fill of our endpoints. This ensures we always have the
193 // appearance of being a border. We then draw the actual dotted/dashed line.
194 setCGFillColor(context, pen().color());
195 if (isVerticalLine) {
196 CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
197 CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
199 CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
200 CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
203 // Example: 80 pixels with a width of 30 pixels.
204 // Remainder is 20. The maximum pixels of line we could paint
205 // will be 50 pixels.
206 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
207 int remainder = distance % patWidth;
208 int coverage = distance - remainder;
209 int numSegments = coverage / patWidth;
211 float patternOffset = 0;
212 // Special case 1px dotted borders for speed.
216 bool evenNumberOfSegments = numSegments % 2 == 0;
218 evenNumberOfSegments = !evenNumberOfSegments;
219 if (evenNumberOfSegments) {
221 patternOffset += patWidth - remainder;
222 patternOffset += remainder / 2;
224 patternOffset = patWidth / 2;
227 patternOffset = (patWidth - remainder)/2;
231 const CGFloat dottedLine[2] = { patWidth, patWidth };
232 CGContextSetLineDash(context, patternOffset, dottedLine, 2);
235 CGContextSetLineWidth(context, width);
237 CGContextBeginPath(context);
238 CGContextMoveToPoint(context, p1.x(), p1.y());
239 CGContextAddLineToPoint(context, p2.x(), p2.y());
241 CGContextStrokePath(context);
243 CGContextRestoreGState(context);
246 // This method is only used to draw the little circles used in lists.
247 void GraphicsContext::drawEllipse(const IntRect& rect)
249 // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
250 // This code can only handle circles, not ellipses. But khtml only
251 // uses it for circles.
252 ASSERT(rect.width() == rect.height());
254 if (paintingDisabled())
257 CGContextRef context = platformContext();
258 CGContextBeginPath(context);
259 float r = (float)rect.width() / 2;
260 CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, true);
261 CGContextClosePath(context);
263 if (fillColor().alpha()) {
264 setCGFillColor(context, fillColor());
265 if (pen().style() != Pen::NoPen) {
267 setCGStrokeColor(context, pen().color());
268 unsigned penWidth = pen().width();
271 CGContextSetLineWidth(context, penWidth);
272 CGContextDrawPath(context, kCGPathFillStroke);
274 CGContextFillPath(context);
275 } else if (pen().style() != Pen::NoPen) {
276 setCGStrokeColor(context, pen().color());
277 unsigned penWidth = pen().width();
280 CGContextSetLineWidth(context, penWidth);
281 CGContextStrokePath(context);
286 void GraphicsContext::drawArc(const IntRect& rect, float thickness, int startAngle, int angleSpan)
288 if (paintingDisabled())
291 CGContextRef context = platformContext();
292 CGContextSaveGState(context);
293 CGContextBeginPath(context);
294 CGContextSetShouldAntialias(context, false);
298 float w = (float)rect.width();
299 float h = (float)rect.height();
300 float scaleFactor = h / w;
301 float reverseScaleFactor = w / h;
304 scale(FloatSize(1, scaleFactor));
306 float hRadius = w / 2;
307 float vRadius = h / 2;
308 float fa = startAngle;
309 float falen = fa + angleSpan;
310 float start = -fa * M_PI/180;
311 float end = -falen * M_PI/180;
312 CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
315 scale(FloatSize(1, reverseScaleFactor));
317 if (pen().style() == Pen::NoPen) {
318 setCGStrokeColor(context, fillColor());
319 CGContextSetLineWidth(context, thickness);
320 CGContextStrokePath(context);
322 Pen::PenStyle penStyle = pen().style();
323 float width = pen().width();
333 patWidth = (int)(width / 2);
336 patWidth = 3 * (int)(width / 2);
340 CGContextSaveGState(context);
341 setCGStrokeColor(context, pen().color());
344 // Example: 80 pixels with a width of 30 pixels.
345 // Remainder is 20. The maximum pixels of line we could paint
346 // will be 50 pixels.
348 if (hRadius == vRadius)
349 distance = (int)(M_PI * hRadius) / 2;
350 else // We are elliptical and will have to estimate the distance
351 distance = (int)(M_PI * sqrt((hRadius * hRadius + vRadius * vRadius) / 2)) / 2;
353 int remainder = distance % patWidth;
354 int coverage = distance - remainder;
355 int numSegments = coverage / patWidth;
357 float patternOffset = 0;
358 // Special case 1px dotted borders for speed.
362 bool evenNumberOfSegments = numSegments % 2 == 0;
364 evenNumberOfSegments = !evenNumberOfSegments;
365 if (evenNumberOfSegments) {
367 patternOffset += patWidth - remainder;
368 patternOffset += remainder / 2;
370 patternOffset = patWidth / 2;
373 patternOffset = (patWidth - remainder) / 2;
377 const CGFloat dottedLine[2] = { patWidth, patWidth };
378 CGContextSetLineDash(context, patternOffset, dottedLine, 2);
381 CGContextSetLineWidth(context, width);
382 CGContextStrokePath(context);
383 CGContextRestoreGState(context);
386 CGContextRestoreGState(context);
389 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
391 if (paintingDisabled())
397 CGContextRef context = platformContext();
399 CGContextSaveGState(context);
401 CGContextSetShouldAntialias(context, shouldAntialias);
403 CGContextBeginPath(context);
404 CGContextMoveToPoint(context, points[0].x(), points[0].y());
405 for (size_t i = 1; i < npoints; i++)
406 CGContextAddLineToPoint(context, points[i].x(), points[i].y());
407 CGContextClosePath(context);
409 if (fillColor().alpha()) {
410 setCGFillColor(context, fillColor());
411 CGContextEOFillPath(context);
414 if (pen().style() != Pen::NoPen) {
415 setCGStrokeColor(context, pen().color());
416 CGContextSetLineWidth(context, pen().width());
417 CGContextStrokePath(context);
420 CGContextRestoreGState(context);
423 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
425 if (paintingDisabled())
428 CGContextRef context = platformContext();
429 setCGFillColor(context, color);
430 CGContextFillRect(context, rect);
434 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
436 if (paintingDisabled())
439 CGContextRef context = platformContext();
440 setCGFillColor(context, color);
441 CGContextFillRect(context, rect);
445 void GraphicsContext::addClip(const IntRect& rect)
447 if (paintingDisabled())
449 CGContextClipToRect(platformContext(), rect);
452 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
453 const IntSize& bottomLeft, const IntSize& bottomRight)
455 if (paintingDisabled())
458 // Need sufficient width and height to contain these curves. Sanity check our top/bottom
459 // values and our width/height values to make sure the curves can all fit.
460 int requiredWidth = max(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
461 if (requiredWidth > rect.width())
463 int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
464 if (requiredHeight > rect.height())
470 // OK, the curves can fit.
471 CGContextRef context = platformContext();
473 // Add the four ellipses to the path. Technically this really isn't good enough, since we could end up
474 // not clipping the other 3/4 of the ellipse we don't care about. We're relying on the fact that for
475 // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
476 // be subsumed by the other).
477 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
478 CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
479 topRight.width() * 2, topRight.height() * 2));
480 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
481 bottomLeft.width() * 2, bottomLeft.height() * 2));
482 CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
483 rect.bottom() - bottomRight.height() * 2,
484 bottomRight.width() * 2, bottomRight.height() * 2));
486 // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
487 CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
488 rect.width() - topLeft.width() - topRight.width(),
489 max(topLeft.height(), topRight.height())));
490 CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(),
491 rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
492 rect.width() - bottomLeft.width() - bottomRight.width(),
493 max(bottomLeft.height(), bottomRight.height())));
494 CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
495 max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
496 CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
497 rect.y() + topRight.height(),
498 max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
499 CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
500 rect.y() + max(topLeft.height(), topRight.height()),
501 rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
502 rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
503 CGContextClip(context);
506 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
508 if (paintingDisabled())
512 CGContextRef context = platformContext();
515 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
516 // Add inner ellipse.
517 CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
518 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
520 CGContextEOClip(context);
524 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
526 return new KRenderingDeviceContextQuartz(platformContext());
530 void GraphicsContext::beginTransparencyLayer(float opacity)
532 if (paintingDisabled())
534 CGContextRef context = platformContext();
535 CGContextSaveGState(context);
536 CGContextSetAlpha(context, opacity);
537 CGContextBeginTransparencyLayer(context, 0);
540 void GraphicsContext::endTransparencyLayer()
542 if (paintingDisabled())
544 CGContextRef context = platformContext();
545 CGContextEndTransparencyLayer(context);
546 CGContextRestoreGState(context);
549 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
551 if (paintingDisabled())
553 // Check for an invalid color, as this means that the color was not set for the shadow
554 // and we should therefore just use the default shadow color.
555 CGContextRef context = platformContext();
556 if (!color.isValid())
557 CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
559 CGColorRef colorCG = cgColor(color);
560 CGContextSetShadowWithColor(context,
561 CGSizeMake(size.width(), -size.height()), // y is flipped.
564 CGColorRelease(colorCG);
568 void GraphicsContext::clearShadow()
570 if (paintingDisabled())
572 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
575 void GraphicsContext::setLineWidth(float width)
577 if (paintingDisabled())
579 CGContextSetLineWidth(platformContext(), width);
582 void GraphicsContext::setMiterLimit(float limit)
584 if (paintingDisabled())
586 CGContextSetMiterLimit(platformContext(), limit);
589 void GraphicsContext::setAlpha(float alpha)
591 if (paintingDisabled())
593 CGContextSetAlpha(platformContext(), alpha);
596 void GraphicsContext::clearRect(const FloatRect& r)
598 if (paintingDisabled())
600 CGContextClearRect(platformContext(), r);
603 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
605 if (paintingDisabled())
607 CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
610 void GraphicsContext::setLineCap(LineCap cap)
612 if (paintingDisabled())
616 CGContextSetLineCap(platformContext(), kCGLineCapButt);
619 CGContextSetLineCap(platformContext(), kCGLineCapRound);
622 CGContextSetLineCap(platformContext(), kCGLineCapSquare);
627 void GraphicsContext::setLineJoin(LineJoin join)
629 if (paintingDisabled())
633 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
636 CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
639 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
644 void GraphicsContext::clip(const Path& path)
646 if (paintingDisabled())
648 CGContextRef context = platformContext();
649 CGContextBeginPath(context);
650 CGContextAddPath(context, path.platformPath());
651 CGContextClip(context);
654 void GraphicsContext::scale(const FloatSize& size)
656 if (paintingDisabled())
658 CGContextScaleCTM(platformContext(), size.width(), size.height());
661 void GraphicsContext::rotate(float angle)
663 if (paintingDisabled())
665 CGContextRotateCTM(platformContext(), angle);
668 void GraphicsContext::translate(float x, float y)
670 if (paintingDisabled())
672 CGContextTranslateCTM(platformContext(), x, y);
675 void GraphicsContext::concatCTM(const AffineTransform& transform)
677 if (paintingDisabled())
679 CGContextConcatCTM(platformContext(), transform);
682 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
684 // It is not enough just to round to pixels in device space. The rotation part of the
685 // affine transform matrix to device space can mess with this conversion if we have a
686 // rotating image like the hands of the world clock widget. We just need the scale, so
687 // we get the affine transform matrix and extract the scale.
688 CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
689 float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
690 float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
692 CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
693 CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
694 (rect.y() + rect.height()) * deviceScaleY);
696 deviceOrigin.x = roundf(deviceOrigin.x);
697 deviceOrigin.y = roundf(deviceOrigin.y);
698 deviceLowerRight.x = roundf(deviceLowerRight.x);
699 deviceLowerRight.y = roundf(deviceLowerRight.y);
701 // Don't let the height or width round to 0 unless either was originally 0
702 if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
703 deviceLowerRight.y += 1;
704 if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
705 deviceLowerRight.x += 1;
707 FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
708 FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
709 return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
712 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset, int width, bool printing)
714 if (paintingDisabled())
717 // Note: This function assumes that point.x and point.y are integers (and that's currently always the case).
719 float y = point.y() + yOffset;
721 // Leave 1.0 in user space between the baseline of the text and the top of the underline.
722 // FIXME: Is this the right distance for space above the underline? Even for thick underlines on large sized text?
725 float thickness = pen().width();
727 // When printing, use a minimum thickness of 0.5 in user space.
728 // See bugzilla bug 4255 for details of why 0.5 is the right minimum thickness to use while printing.
732 // When printing, use antialiasing instead of putting things on integral pixel boundaries.
734 // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
738 // On screen, round all parameters to integer boundaries in device space.
739 CGRect lineRect = roundToDevicePixels(FloatRect(x, y, width, thickness));
740 x = lineRect.origin.x;
741 y = lineRect.origin.y;
742 width = (int)(lineRect.size.width);
743 thickness = lineRect.size.height;
746 // FIXME: How about using a rectangle fill instead of drawing a line?
747 CGContextSaveGState(platformContext());
749 setCGStrokeColor(platformContext(), pen().color());
751 CGContextSetLineWidth(platformContext(), thickness);
752 CGContextSetShouldAntialias(platformContext(), printing);
754 float halfThickness = thickness / 2;
756 CGPoint linePoints[2];
757 linePoints[0].x = x + halfThickness;
758 linePoints[0].y = y + halfThickness;
759 linePoints[1].x = x + width - halfThickness;
760 linePoints[1].y = y + halfThickness;
761 CGContextStrokeLineSegments(platformContext(), linePoints, 2);
763 CGContextRestoreGState(platformContext());
768 #endif // PLATFORM(CG)