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 // Note: Do not use this function within this class implementation, since we want to avoid the extra
95 // save of the secondary context (in GraphicsContextPlatformPrivate.h).
96 CGContextSaveGState(platformContext());
100 void GraphicsContext::restorePlatformState()
102 // Note: Do not use this function within this class implementation, since we want to avoid the extra
103 // restore of the secondary context (in GraphicsContextPlatformPrivate.h).
104 CGContextRestoreGState(platformContext());
108 // Draws a filled rectangle with a stroked border.
109 void GraphicsContext::drawRect(const IntRect& rect)
111 if (paintingDisabled())
114 CGContextRef context = platformContext();
116 if (fillColor().alpha()) {
117 setCGFillColor(context, fillColor());
118 CGContextFillRect(context, rect);
121 if (pen().style() != Pen::NoPen) {
122 setCGFillColor(context, pen().color());
124 FloatRect(rect.x(), rect.y(), rect.width(), 1),
125 FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
126 FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
127 FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
129 CGContextFillRects(context, rects, 4);
133 // This is only used to draw borders.
134 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
136 if (paintingDisabled())
139 Pen::PenStyle penStyle = pen().style();
140 if (penStyle == Pen::NoPen)
142 float width = pen().width();
146 FloatPoint p1 = point1;
147 FloatPoint p2 = point2;
148 bool isVerticalLine = (p1.x() == p2.x());
150 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
151 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
152 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
153 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
154 if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
155 if (isVerticalLine) {
164 if (((int)width) % 2) {
165 if (isVerticalLine) {
166 // We're a vertical line. Adjust our x.
170 // We're a horizontal line. Adjust our y.
182 patWidth = (int)width;
185 patWidth = 3 * (int)width;
189 CGContextRef context = platformContext();
191 CGContextSaveGState(context);
193 setCGStrokeColor(context, pen().color());
195 CGContextSetShouldAntialias(context, false);
198 // Do a rect fill of our endpoints. This ensures we always have the
199 // appearance of being a border. We then draw the actual dotted/dashed line.
200 setCGFillColor(context, pen().color());
201 if (isVerticalLine) {
202 CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
203 CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
205 CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
206 CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
209 // Example: 80 pixels with a width of 30 pixels.
210 // Remainder is 20. The maximum pixels of line we could paint
211 // will be 50 pixels.
212 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
213 int remainder = distance % patWidth;
214 int coverage = distance - remainder;
215 int numSegments = coverage / patWidth;
217 float patternOffset = 0;
218 // Special case 1px dotted borders for speed.
222 bool evenNumberOfSegments = numSegments % 2 == 0;
224 evenNumberOfSegments = !evenNumberOfSegments;
225 if (evenNumberOfSegments) {
227 patternOffset += patWidth - remainder;
228 patternOffset += remainder / 2;
230 patternOffset = patWidth / 2;
233 patternOffset = (patWidth - remainder)/2;
237 const CGFloat dottedLine[2] = { patWidth, patWidth };
238 CGContextSetLineDash(context, patternOffset, dottedLine, 2);
241 CGContextSetLineWidth(context, width);
243 CGContextBeginPath(context);
244 CGContextMoveToPoint(context, p1.x(), p1.y());
245 CGContextAddLineToPoint(context, p2.x(), p2.y());
247 CGContextStrokePath(context);
249 CGContextRestoreGState(context);
252 // This method is only used to draw the little circles used in lists.
253 void GraphicsContext::drawEllipse(const IntRect& rect)
255 // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
256 // This code can only handle circles, not ellipses. But khtml only
257 // uses it for circles.
258 ASSERT(rect.width() == rect.height());
260 if (paintingDisabled())
263 CGContextRef context = platformContext();
264 CGContextBeginPath(context);
265 float r = (float)rect.width() / 2;
266 CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, true);
267 CGContextClosePath(context);
269 if (fillColor().alpha()) {
270 setCGFillColor(context, fillColor());
271 if (pen().style() != Pen::NoPen) {
273 setCGStrokeColor(context, pen().color());
274 unsigned penWidth = pen().width();
277 CGContextSetLineWidth(context, penWidth);
278 CGContextDrawPath(context, kCGPathFillStroke);
280 CGContextFillPath(context);
281 } else if (pen().style() != Pen::NoPen) {
282 setCGStrokeColor(context, pen().color());
283 unsigned penWidth = pen().width();
286 CGContextSetLineWidth(context, penWidth);
287 CGContextStrokePath(context);
292 void GraphicsContext::drawArc(const IntRect& rect, float thickness, int startAngle, int angleSpan)
294 if (paintingDisabled())
297 CGContextRef context = platformContext();
298 CGContextSaveGState(context);
299 CGContextBeginPath(context);
300 CGContextSetShouldAntialias(context, false);
304 float w = (float)rect.width();
305 float h = (float)rect.height();
306 float scaleFactor = h / w;
307 float reverseScaleFactor = w / h;
310 scale(FloatSize(1, scaleFactor));
312 float hRadius = w / 2;
313 float vRadius = h / 2;
314 float fa = startAngle;
315 float falen = fa + angleSpan;
316 float start = -fa * M_PI/180;
317 float end = -falen * M_PI/180;
318 CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
321 scale(FloatSize(1, reverseScaleFactor));
323 if (pen().style() == Pen::NoPen) {
324 setCGStrokeColor(context, fillColor());
325 CGContextSetLineWidth(context, thickness);
326 CGContextStrokePath(context);
328 Pen::PenStyle penStyle = pen().style();
329 float width = pen().width();
339 patWidth = (int)(width / 2);
342 patWidth = 3 * (int)(width / 2);
346 CGContextSaveGState(context);
347 setCGStrokeColor(context, pen().color());
350 // Example: 80 pixels with a width of 30 pixels.
351 // Remainder is 20. The maximum pixels of line we could paint
352 // will be 50 pixels.
354 if (hRadius == vRadius)
355 distance = (int)(M_PI * hRadius) / 2;
356 else // We are elliptical and will have to estimate the distance
357 distance = (int)(M_PI * sqrt((hRadius * hRadius + vRadius * vRadius) / 2)) / 2;
359 int remainder = distance % patWidth;
360 int coverage = distance - remainder;
361 int numSegments = coverage / patWidth;
363 float patternOffset = 0;
364 // Special case 1px dotted borders for speed.
368 bool evenNumberOfSegments = numSegments % 2 == 0;
370 evenNumberOfSegments = !evenNumberOfSegments;
371 if (evenNumberOfSegments) {
373 patternOffset += patWidth - remainder;
374 patternOffset += remainder / 2;
376 patternOffset = patWidth / 2;
379 patternOffset = (patWidth - remainder) / 2;
383 const CGFloat dottedLine[2] = { patWidth, patWidth };
384 CGContextSetLineDash(context, patternOffset, dottedLine, 2);
387 CGContextSetLineWidth(context, width);
388 CGContextStrokePath(context);
389 CGContextRestoreGState(context);
392 CGContextRestoreGState(context);
395 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
397 if (paintingDisabled())
403 CGContextRef context = platformContext();
405 CGContextSaveGState(context);
407 CGContextSetShouldAntialias(context, shouldAntialias);
409 CGContextBeginPath(context);
410 CGContextMoveToPoint(context, points[0].x(), points[0].y());
411 for (size_t i = 1; i < npoints; i++)
412 CGContextAddLineToPoint(context, points[i].x(), points[i].y());
413 CGContextClosePath(context);
415 if (fillColor().alpha()) {
416 setCGFillColor(context, fillColor());
417 CGContextEOFillPath(context);
420 if (pen().style() != Pen::NoPen) {
421 setCGStrokeColor(context, pen().color());
422 CGContextSetLineWidth(context, pen().width());
423 CGContextStrokePath(context);
426 CGContextRestoreGState(context);
429 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
431 if (paintingDisabled())
434 CGContextRef context = platformContext();
435 setCGFillColor(context, color);
436 CGContextFillRect(context, rect);
440 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
442 if (paintingDisabled())
445 CGContextRef context = platformContext();
446 setCGFillColor(context, color);
447 CGContextFillRect(context, rect);
451 void GraphicsContext::clip(const IntRect& rect)
453 if (paintingDisabled())
455 CGContextClipToRect(platformContext(), rect);
459 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
460 const IntSize& bottomLeft, const IntSize& bottomRight)
462 if (paintingDisabled())
465 // Need sufficient width and height to contain these curves. Sanity check our top/bottom
466 // values and our width/height values to make sure the curves can all fit.
467 int requiredWidth = max(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
468 if (requiredWidth > rect.width())
470 int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
471 if (requiredHeight > rect.height())
477 // OK, the curves can fit.
478 CGContextRef context = platformContext();
480 // Add the four ellipses to the path. Technically this really isn't good enough, since we could end up
481 // not clipping the other 3/4 of the ellipse we don't care about. We're relying on the fact that for
482 // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
483 // be subsumed by the other).
484 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
485 CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
486 topRight.width() * 2, topRight.height() * 2));
487 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
488 bottomLeft.width() * 2, bottomLeft.height() * 2));
489 CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
490 rect.bottom() - bottomRight.height() * 2,
491 bottomRight.width() * 2, bottomRight.height() * 2));
493 // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
494 CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
495 rect.width() - topLeft.width() - topRight.width(),
496 max(topLeft.height(), topRight.height())));
497 CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(),
498 rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
499 rect.width() - bottomLeft.width() - bottomRight.width(),
500 max(bottomLeft.height(), bottomRight.height())));
501 CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
502 max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
503 CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
504 rect.y() + topRight.height(),
505 max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
506 CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
507 rect.y() + max(topLeft.height(), topRight.height()),
508 rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
509 rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
510 CGContextClip(context);
513 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
515 if (paintingDisabled())
519 CGContextRef context = platformContext();
522 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
523 // Add inner ellipse.
524 CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
525 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
527 CGContextEOClip(context);
531 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
533 return new KRenderingDeviceContextQuartz(platformContext());
537 void GraphicsContext::beginTransparencyLayer(float opacity)
539 if (paintingDisabled())
541 CGContextRef context = platformContext();
542 CGContextSaveGState(context);
543 CGContextSetAlpha(context, opacity);
544 CGContextBeginTransparencyLayer(context, 0);
547 void GraphicsContext::endTransparencyLayer()
549 if (paintingDisabled())
551 CGContextRef context = platformContext();
552 CGContextEndTransparencyLayer(context);
553 CGContextRestoreGState(context);
556 void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
558 if (paintingDisabled())
560 // Check for an invalid color, as this means that the color was not set for the shadow
561 // and we should therefore just use the default shadow color.
562 CGContextRef context = platformContext();
563 if (!color.isValid())
564 CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
566 CGColorRef colorCG = cgColor(color);
567 CGContextSetShadowWithColor(context,
568 CGSizeMake(size.width(), -size.height()), // y is flipped.
571 CGColorRelease(colorCG);
575 void GraphicsContext::clearShadow()
577 if (paintingDisabled())
579 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
582 void GraphicsContext::setLineWidth(float width)
584 if (paintingDisabled())
586 CGContextSetLineWidth(platformContext(), width);
589 void GraphicsContext::setMiterLimit(float limit)
591 if (paintingDisabled())
593 CGContextSetMiterLimit(platformContext(), limit);
596 void GraphicsContext::setAlpha(float alpha)
598 if (paintingDisabled())
600 CGContextSetAlpha(platformContext(), alpha);
603 void GraphicsContext::clearRect(const FloatRect& r)
605 if (paintingDisabled())
607 CGContextClearRect(platformContext(), r);
610 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
612 if (paintingDisabled())
614 CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
617 void GraphicsContext::setLineCap(LineCap cap)
619 if (paintingDisabled())
623 CGContextSetLineCap(platformContext(), kCGLineCapButt);
626 CGContextSetLineCap(platformContext(), kCGLineCapRound);
629 CGContextSetLineCap(platformContext(), kCGLineCapSquare);
634 void GraphicsContext::setLineJoin(LineJoin join)
636 if (paintingDisabled())
640 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
643 CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
646 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
651 void GraphicsContext::clip(const Path& path)
653 if (paintingDisabled())
655 CGContextRef context = platformContext();
656 CGContextBeginPath(context);
657 CGContextAddPath(context, path.platformPath());
658 CGContextClip(context);
662 void GraphicsContext::scale(const FloatSize& size)
664 if (paintingDisabled())
666 CGContextScaleCTM(platformContext(), size.width(), size.height());
670 void GraphicsContext::rotate(float angle)
672 if (paintingDisabled())
674 CGContextRotateCTM(platformContext(), angle);
675 m_data->rotate(angle);
678 void GraphicsContext::translate(float x, float y)
680 if (paintingDisabled())
682 CGContextTranslateCTM(platformContext(), x, y);
683 m_data->translate(x, y);
686 void GraphicsContext::concatCTM(const AffineTransform& transform)
688 if (paintingDisabled())
690 CGContextConcatCTM(platformContext(), transform);
691 m_data->concatCTM(transform);
694 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
696 // It is not enough just to round to pixels in device space. The rotation part of the
697 // affine transform matrix to device space can mess with this conversion if we have a
698 // rotating image like the hands of the world clock widget. We just need the scale, so
699 // we get the affine transform matrix and extract the scale.
700 CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
701 float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
702 float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
704 CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
705 CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
706 (rect.y() + rect.height()) * deviceScaleY);
708 deviceOrigin.x = roundf(deviceOrigin.x);
709 deviceOrigin.y = roundf(deviceOrigin.y);
710 deviceLowerRight.x = roundf(deviceLowerRight.x);
711 deviceLowerRight.y = roundf(deviceLowerRight.y);
713 // Don't let the height or width round to 0 unless either was originally 0
714 if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
715 deviceLowerRight.y += 1;
716 if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
717 deviceLowerRight.x += 1;
719 FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
720 FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
721 return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
724 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset, int width, bool printing)
726 if (paintingDisabled())
729 // Note: This function assumes that point.x and point.y are integers (and that's currently always the case).
731 float y = point.y() + yOffset;
733 // Leave 1.0 in user space between the baseline of the text and the top of the underline.
734 // FIXME: Is this the right distance for space above the underline? Even for thick underlines on large sized text?
737 float thickness = pen().width();
739 // When printing, use a minimum thickness of 0.5 in user space.
740 // See bugzilla bug 4255 for details of why 0.5 is the right minimum thickness to use while printing.
744 // When printing, use antialiasing instead of putting things on integral pixel boundaries.
746 // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
750 // On screen, round all parameters to integer boundaries in device space.
751 CGRect lineRect = roundToDevicePixels(FloatRect(x, y, width, thickness));
752 x = lineRect.origin.x;
753 y = lineRect.origin.y;
754 width = (int)(lineRect.size.width);
755 thickness = lineRect.size.height;
758 // FIXME: How about using a rectangle fill instead of drawing a line?
759 CGContextSaveGState(platformContext());
761 setCGStrokeColor(platformContext(), pen().color());
763 CGContextSetLineWidth(platformContext(), thickness);
764 CGContextSetShouldAntialias(platformContext(), printing);
766 float halfThickness = thickness / 2;
768 CGPoint linePoints[2];
769 linePoints[0].x = x + halfThickness;
770 linePoints[0].y = y + halfThickness;
771 linePoints[1].x = x + width - halfThickness;
772 linePoints[1].y = y + halfThickness;
773 CGContextStrokeLineSegments(platformContext(), linePoints, 2);
775 CGContextRestoreGState(platformContext());
780 #endif // PLATFORM(CG)