1 /* 2 * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Eric Seidel <eric (at) webkit.org> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #define _USE_MATH_DEFINES 1 28 #include "config.h" 29 #include "GraphicsContext.h" 30 31 #include "AffineTransform.h" 32 #include "FloatConversion.h" 33 #include "GraphicsContextPlatformPrivateCG.h" 34 #include "GraphicsContextPrivate.h" 35 #include "ImageBuffer.h" 36 #include "KURL.h" 37 #include "Path.h" 38 #include "Pattern.h" 39 40 #include <CoreGraphics/CGBitmapContext.h> 41 #include <CoreGraphics/CGPDFContext.h> 42 #include <wtf/MathExtras.h> 43 #include <wtf/OwnArrayPtr.h> 44 #include <wtf/RetainPtr.h> 45 46 #if PLATFORM(MAC) || PLATFORM(CHROMIUM) 47 #include "WebCoreSystemInterface.h" 48 #endif 49 50 #if PLATFORM(WIN) 51 #include <WebKitSystemInterface/WebKitSystemInterface.h> 52 #endif 53 54 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN)) 55 56 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 57 // Building on 10.6 or later: kCGInterpolationMedium is defined in the CGInterpolationQuality enum. 58 #define HAVE_CG_INTERPOLATION_MEDIUM 1 59 #endif 60 61 #if !defined(TARGETING_TIGER) && !defined(TARGETING_LEOPARD) 62 // Targeting 10.6 or later: use kCGInterpolationMedium. 63 #define WTF_USE_CG_INTERPOLATION_MEDIUM 1 64 #endif 65 66 #endif 67 68 using namespace std; 69 70 namespace WebCore { 71 72 static CGColorRef createCGColorWithColorSpace(const Color& color, ColorSpace colorSpace) 73 { 74 CGFloat components[4]; 75 color.getRGBA(components[0], components[1], components[2], components[3]); 76 77 CGColorRef cgColor = 0; 78 if (colorSpace == sRGBColorSpace) 79 cgColor = CGColorCreate(sRGBColorSpaceRef(), components); 80 else 81 cgColor = CGColorCreate(deviceRGBColorSpaceRef(), components); 82 83 return cgColor; 84 } 85 86 static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace) 87 { 88 CGColorRef cgColor = createCGColorWithColorSpace(color, colorSpace); 89 CGContextSetFillColorWithColor(context, cgColor); 90 CFRelease(cgColor); 91 } 92 93 static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace) 94 { 95 CGColorRef cgColor = createCGColorWithColorSpace(color, colorSpace); 96 CGContextSetStrokeColorWithColor(context, cgColor); 97 CFRelease(cgColor); 98 } 99 100 static void setCGFillColorSpace(CGContextRef context, ColorSpace colorSpace) 101 { 102 switch (colorSpace) { 103 case DeviceColorSpace: 104 break; 105 case sRGBColorSpace: 106 CGContextSetFillColorSpace(context, sRGBColorSpaceRef()); 107 break; 108 default: 109 ASSERT_NOT_REACHED(); 110 break; 111 } 112 } 113 114 static void setCGStrokeColorSpace(CGContextRef context, ColorSpace colorSpace) 115 { 116 switch (colorSpace) { 117 case DeviceColorSpace: 118 break; 119 case sRGBColorSpace: 120 CGContextSetStrokeColorSpace(context, sRGBColorSpaceRef()); 121 break; 122 default: 123 ASSERT_NOT_REACHED(); 124 break; 125 } 126 } 127 128 CGColorSpaceRef deviceRGBColorSpaceRef() 129 { 130 static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB(); 131 return deviceSpace; 132 } 133 134 CGColorSpaceRef sRGBColorSpaceRef() 135 { 136 // FIXME: Windows should be able to use kCGColorSpaceSRGB, this is tracked by http://webkit.org/b/31363. 137 #if PLATFORM(WIN) || defined(BUILDING_ON_TIGER) 138 return deviceRGBColorSpaceRef(); 139 #else 140 static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); 141 return sRGBSpace; 142 #endif 143 } 144 145 GraphicsContext::GraphicsContext(CGContextRef cgContext) 146 : m_common(createGraphicsContextPrivate()) 147 , m_data(new GraphicsContextPlatformPrivate(cgContext)) 148 { 149 setPaintingDisabled(!cgContext); 150 if (cgContext) { 151 // Make sure the context starts in sync with our state. 152 setPlatformFillColor(fillColor(), fillColorSpace()); 153 setPlatformStrokeColor(strokeColor(), strokeColorSpace()); 154 } 155 } 156 157 GraphicsContext::~GraphicsContext() 158 { 159 destroyGraphicsContextPrivate(m_common); 160 delete m_data; 161 } 162 163 CGContextRef GraphicsContext::platformContext() const 164 { 165 ASSERT(!paintingDisabled()); 166 ASSERT(m_data->m_cgContext); 167 return m_data->m_cgContext.get(); 168 } 169 170 void GraphicsContext::savePlatformState() 171 { 172 // Note: Do not use this function within this class implementation, since we want to avoid the extra 173 // save of the secondary context (in GraphicsContextPlatformPrivateCG.h). 174 CGContextSaveGState(platformContext()); 175 m_data->save(); 176 } 177 178 void GraphicsContext::restorePlatformState() 179 { 180 // Note: Do not use this function within this class implementation, since we want to avoid the extra 181 // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h). 182 CGContextRestoreGState(platformContext()); 183 m_data->restore(); 184 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 185 } 186 187 // Draws a filled rectangle with a stroked border. 188 void GraphicsContext::drawRect(const IntRect& rect) 189 { 190 // FIXME: this function does not handle patterns and gradients 191 // like drawPath does, it probably should. 192 if (paintingDisabled()) 193 return; 194 195 CGContextRef context = platformContext(); 196 197 CGContextFillRect(context, rect); 198 199 if (strokeStyle() != NoStroke) { 200 // We do a fill of four rects to simulate the stroke of a border. 201 Color oldFillColor = fillColor(); 202 if (oldFillColor != strokeColor()) 203 setCGFillColor(context, strokeColor(), strokeColorSpace()); 204 CGRect rects[4] = { 205 FloatRect(rect.x(), rect.y(), rect.width(), 1), 206 FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1), 207 FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2), 208 FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2) 209 }; 210 CGContextFillRects(context, rects, 4); 211 if (oldFillColor != strokeColor()) 212 setCGFillColor(context, oldFillColor, fillColorSpace()); 213 } 214 } 215 216 // This is only used to draw borders. 217 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) 218 { 219 if (paintingDisabled()) 220 return; 221 222 if (strokeStyle() == NoStroke) 223 return; 224 225 float width = strokeThickness(); 226 227 FloatPoint p1 = point1; 228 FloatPoint p2 = point2; 229 bool isVerticalLine = (p1.x() == p2.x()); 230 231 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic 232 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g., 233 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave 234 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5. 235 if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) { 236 if (isVerticalLine) { 237 p1.move(0, width); 238 p2.move(0, -width); 239 } else { 240 p1.move(width, 0); 241 p2.move(-width, 0); 242 } 243 } 244 245 if (((int)width) % 2) { 246 if (isVerticalLine) { 247 // We're a vertical line. Adjust our x. 248 p1.move(0.5f, 0.0f); 249 p2.move(0.5f, 0.0f); 250 } else { 251 // We're a horizontal line. Adjust our y. 252 p1.move(0.0f, 0.5f); 253 p2.move(0.0f, 0.5f); 254 } 255 } 256 257 int patWidth = 0; 258 switch (strokeStyle()) { 259 case NoStroke: 260 case SolidStroke: 261 break; 262 case DottedStroke: 263 patWidth = (int)width; 264 break; 265 case DashedStroke: 266 patWidth = 3 * (int)width; 267 break; 268 } 269 270 CGContextRef context = platformContext(); 271 272 if (shouldAntialias()) 273 CGContextSetShouldAntialias(context, false); 274 275 if (patWidth) { 276 CGContextSaveGState(context); 277 278 // Do a rect fill of our endpoints. This ensures we always have the 279 // appearance of being a border. We then draw the actual dotted/dashed line. 280 setCGFillColor(context, strokeColor(), strokeColorSpace()); // The save/restore make it safe to mutate the fill color here without setting it back to the old color. 281 if (isVerticalLine) { 282 CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width)); 283 CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width)); 284 } else { 285 CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width)); 286 CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width)); 287 } 288 289 // Example: 80 pixels with a width of 30 pixels. 290 // Remainder is 20. The maximum pixels of line we could paint 291 // will be 50 pixels. 292 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width; 293 int remainder = distance % patWidth; 294 int coverage = distance - remainder; 295 int numSegments = coverage / patWidth; 296 297 float patternOffset = 0.0f; 298 // Special case 1px dotted borders for speed. 299 if (patWidth == 1) 300 patternOffset = 1.0f; 301 else { 302 bool evenNumberOfSegments = !(numSegments % 2); 303 if (remainder) 304 evenNumberOfSegments = !evenNumberOfSegments; 305 if (evenNumberOfSegments) { 306 if (remainder) { 307 patternOffset += patWidth - remainder; 308 patternOffset += remainder / 2; 309 } else 310 patternOffset = patWidth / 2; 311 } else { 312 if (remainder) 313 patternOffset = (patWidth - remainder)/2; 314 } 315 } 316 317 const CGFloat dottedLine[2] = { patWidth, patWidth }; 318 CGContextSetLineDash(context, patternOffset, dottedLine, 2); 319 } 320 321 CGContextBeginPath(context); 322 CGContextMoveToPoint(context, p1.x(), p1.y()); 323 CGContextAddLineToPoint(context, p2.x(), p2.y()); 324 325 CGContextStrokePath(context); 326 327 if (patWidth) 328 CGContextRestoreGState(context); 329 330 if (shouldAntialias()) 331 CGContextSetShouldAntialias(context, true); 332 } 333 334 // This method is only used to draw the little circles used in lists. 335 void GraphicsContext::drawEllipse(const IntRect& rect) 336 { 337 // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse. 338 // This code can only handle circles, not ellipses. But khtml only 339 // uses it for circles. 340 ASSERT(rect.width() == rect.height()); 341 342 if (paintingDisabled()) 343 return; 344 345 CGContextRef context = platformContext(); 346 CGContextBeginPath(context); 347 float r = (float)rect.width() / 2; 348 CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0.0f, 2.0f * piFloat, 0); 349 CGContextClosePath(context); 350 351 drawPath(); 352 } 353 354 355 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan) 356 { 357 if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f) 358 return; 359 360 CGContextRef context = platformContext(); 361 CGContextSaveGState(context); 362 CGContextBeginPath(context); 363 CGContextSetShouldAntialias(context, false); 364 365 int x = rect.x(); 366 int y = rect.y(); 367 float w = (float)rect.width(); 368 float h = (float)rect.height(); 369 float scaleFactor = h / w; 370 float reverseScaleFactor = w / h; 371 372 if (w != h) 373 scale(FloatSize(1, scaleFactor)); 374 375 float hRadius = w / 2; 376 float vRadius = h / 2; 377 float fa = startAngle; 378 float falen = fa + angleSpan; 379 float start = -fa * piFloat / 180.0f; 380 float end = -falen * piFloat / 180.0f; 381 CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true); 382 383 if (w != h) 384 scale(FloatSize(1, reverseScaleFactor)); 385 386 float width = strokeThickness(); 387 int patWidth = 0; 388 389 switch (strokeStyle()) { 390 case DottedStroke: 391 patWidth = (int)(width / 2); 392 break; 393 case DashedStroke: 394 patWidth = 3 * (int)(width / 2); 395 break; 396 default: 397 break; 398 } 399 400 if (patWidth) { 401 // Example: 80 pixels with a width of 30 pixels. 402 // Remainder is 20. The maximum pixels of line we could paint 403 // will be 50 pixels. 404 int distance; 405 if (hRadius == vRadius) 406 distance = static_cast<int>((piFloat * hRadius) / 2.0f); 407 else // We are elliptical and will have to estimate the distance 408 distance = static_cast<int>((piFloat * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0f)) / 2.0f); 409 410 int remainder = distance % patWidth; 411 int coverage = distance - remainder; 412 int numSegments = coverage / patWidth; 413 414 float patternOffset = 0.0f; 415 // Special case 1px dotted borders for speed. 416 if (patWidth == 1) 417 patternOffset = 1.0f; 418 else { 419 bool evenNumberOfSegments = !(numSegments % 2); 420 if (remainder) 421 evenNumberOfSegments = !evenNumberOfSegments; 422 if (evenNumberOfSegments) { 423 if (remainder) { 424 patternOffset += patWidth - remainder; 425 patternOffset += remainder / 2.0f; 426 } else 427 patternOffset = patWidth / 2.0f; 428 } else { 429 if (remainder) 430 patternOffset = (patWidth - remainder) / 2.0f; 431 } 432 } 433 434 const CGFloat dottedLine[2] = { patWidth, patWidth }; 435 CGContextSetLineDash(context, patternOffset, dottedLine, 2); 436 } 437 438 CGContextStrokePath(context); 439 440 CGContextRestoreGState(context); 441 } 442 443 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool antialiased) 444 { 445 if (paintingDisabled()) 446 return; 447 448 if (npoints <= 1) 449 return; 450 451 CGContextRef context = platformContext(); 452 453 if (antialiased != shouldAntialias()) 454 CGContextSetShouldAntialias(context, antialiased); 455 456 CGContextBeginPath(context); 457 CGContextMoveToPoint(context, points[0].x(), points[0].y()); 458 for (size_t i = 1; i < npoints; i++) 459 CGContextAddLineToPoint(context, points[i].x(), points[i].y()); 460 CGContextClosePath(context); 461 462 drawPath(); 463 464 if (antialiased != shouldAntialias()) 465 CGContextSetShouldAntialias(context, shouldAntialias()); 466 } 467 468 void GraphicsContext::applyStrokePattern() 469 { 470 CGContextRef cgContext = platformContext(); 471 472 RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_common->state.strokePattern->createPlatformPattern(getCTM())); 473 if (!platformPattern) 474 return; 475 476 RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0)); 477 CGContextSetStrokeColorSpace(cgContext, patternSpace.get()); 478 479 const CGFloat patternAlpha = 1; 480 CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha); 481 } 482 483 void GraphicsContext::applyFillPattern() 484 { 485 CGContextRef cgContext = platformContext(); 486 487 RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_common->state.fillPattern->createPlatformPattern(getCTM())); 488 if (!platformPattern) 489 return; 490 491 RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0)); 492 CGContextSetFillColorSpace(cgContext, patternSpace.get()); 493 494 const CGFloat patternAlpha = 1; 495 CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha); 496 } 497 498 static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode) 499 { 500 bool shouldFill = state.fillPattern || state.fillColor.alpha(); 501 bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha()); 502 bool useEOFill = state.fillRule == RULE_EVENODD; 503 504 if (shouldFill) { 505 if (shouldStroke) { 506 if (useEOFill) 507 mode = kCGPathEOFillStroke; 508 else 509 mode = kCGPathFillStroke; 510 } else { // fill, no stroke 511 if (useEOFill) 512 mode = kCGPathEOFill; 513 else 514 mode = kCGPathFill; 515 } 516 } else { 517 // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used, 518 // but the compiler will not complain about an uninitialized variable. 519 mode = kCGPathStroke; 520 } 521 522 return shouldFill || shouldStroke; 523 } 524 525 void GraphicsContext::drawPath() 526 { 527 if (paintingDisabled()) 528 return; 529 530 CGContextRef context = platformContext(); 531 const GraphicsContextState& state = m_common->state; 532 533 if (state.fillGradient || state.strokeGradient) { 534 // We don't have any optimized way to fill & stroke a path using gradients 535 fillPath(); 536 strokePath(); 537 return; 538 } 539 540 if (state.fillPattern) 541 applyFillPattern(); 542 if (state.strokePattern) 543 applyStrokePattern(); 544 545 CGPathDrawingMode drawingMode; 546 if (calculateDrawingMode(state, drawingMode)) 547 CGContextDrawPath(context, drawingMode); 548 } 549 550 static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule) 551 { 552 if (fillRule == RULE_EVENODD) 553 CGContextEOFillPath(context); 554 else 555 CGContextFillPath(context); 556 } 557 558 void GraphicsContext::fillPath() 559 { 560 if (paintingDisabled()) 561 return; 562 563 CGContextRef context = platformContext(); 564 565 // FIXME: Is this helpful and correct in the fillPattern and fillGradient cases? 566 setCGFillColorSpace(context, m_common->state.fillColorSpace); 567 568 if (m_common->state.fillGradient) { 569 CGContextSaveGState(context); 570 if (fillRule() == RULE_EVENODD) 571 CGContextEOClip(context); 572 else 573 CGContextClip(context); 574 CGContextConcatCTM(context, m_common->state.fillGradient->gradientSpaceTransform()); 575 m_common->state.fillGradient->paint(this); 576 CGContextRestoreGState(context); 577 return; 578 } 579 580 if (m_common->state.fillPattern) 581 applyFillPattern(); 582 fillPathWithFillRule(context, fillRule()); 583 } 584 585 void GraphicsContext::strokePath() 586 { 587 if (paintingDisabled()) 588 return; 589 590 CGContextRef context = platformContext(); 591 592 // FIXME: Is this helpful and correct in the strokePattern and strokeGradient cases? 593 setCGStrokeColorSpace(context, m_common->state.strokeColorSpace); 594 595 if (m_common->state.strokeGradient) { 596 CGContextSaveGState(context); 597 CGContextReplacePathWithStrokedPath(context); 598 CGContextClip(context); 599 CGContextConcatCTM(context, m_common->state.strokeGradient->gradientSpaceTransform()); 600 m_common->state.strokeGradient->paint(this); 601 CGContextRestoreGState(context); 602 return; 603 } 604 605 if (m_common->state.strokePattern) 606 applyStrokePattern(); 607 CGContextStrokePath(context); 608 } 609 610 void GraphicsContext::fillRect(const FloatRect& rect) 611 { 612 if (paintingDisabled()) 613 return; 614 615 CGContextRef context = platformContext(); 616 617 // FIXME: Is this helpful and correct in the fillPattern and fillGradient cases? 618 setCGFillColorSpace(context, m_common->state.fillColorSpace); 619 620 if (m_common->state.fillGradient) { 621 CGContextSaveGState(context); 622 CGContextClipToRect(context, rect); 623 CGContextConcatCTM(context, m_common->state.fillGradient->gradientSpaceTransform()); 624 m_common->state.fillGradient->paint(this); 625 CGContextRestoreGState(context); 626 return; 627 } 628 629 if (m_common->state.fillPattern) 630 applyFillPattern(); 631 CGContextFillRect(context, rect); 632 } 633 634 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace) 635 { 636 if (paintingDisabled()) 637 return; 638 CGContextRef context = platformContext(); 639 Color oldFillColor = fillColor(); 640 ColorSpace oldColorSpace = fillColorSpace(); 641 642 if (oldFillColor != color || oldColorSpace != colorSpace) 643 setCGFillColor(context, color, colorSpace); 644 645 CGContextFillRect(context, rect); 646 647 if (oldFillColor != color || oldColorSpace != colorSpace) 648 setCGFillColor(context, oldFillColor, oldColorSpace); 649 } 650 651 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace) 652 { 653 if (paintingDisabled()) 654 return; 655 656 CGContextRef context = platformContext(); 657 Color oldFillColor = fillColor(); 658 ColorSpace oldColorSpace = fillColorSpace(); 659 660 if (oldFillColor != color || oldColorSpace != colorSpace) 661 setCGFillColor(context, color, colorSpace); 662 663 addPath(Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight)); 664 fillPath(); 665 666 if (oldFillColor != color || oldColorSpace != colorSpace) 667 setCGFillColor(context, oldFillColor, oldColorSpace); 668 } 669 670 void GraphicsContext::clip(const FloatRect& rect) 671 { 672 if (paintingDisabled()) 673 return; 674 CGContextClipToRect(platformContext(), rect); 675 m_data->clip(rect); 676 } 677 678 void GraphicsContext::clipOut(const IntRect& rect) 679 { 680 if (paintingDisabled()) 681 return; 682 683 CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect }; 684 CGContextBeginPath(platformContext()); 685 CGContextAddRects(platformContext(), rects, 2); 686 CGContextEOClip(platformContext()); 687 } 688 689 void GraphicsContext::clipOutEllipseInRect(const IntRect& rect) 690 { 691 if (paintingDisabled()) 692 return; 693 694 CGContextBeginPath(platformContext()); 695 CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext())); 696 CGContextAddEllipseInRect(platformContext(), rect); 697 CGContextEOClip(platformContext()); 698 } 699 700 void GraphicsContext::clipPath(WindRule clipRule) 701 { 702 if (paintingDisabled()) 703 return; 704 705 CGContextRef context = platformContext(); 706 707 if (!CGContextIsPathEmpty(context)) { 708 if (clipRule == RULE_EVENODD) 709 CGContextEOClip(context); 710 else 711 CGContextClip(context); 712 } 713 } 714 715 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness) 716 { 717 if (paintingDisabled()) 718 return; 719 720 clip(rect); 721 CGContextRef context = platformContext(); 722 723 // Add outer ellipse 724 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height())); 725 // Add inner ellipse. 726 CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness, 727 rect.width() - (thickness * 2), rect.height() - (thickness * 2))); 728 729 CGContextEOClip(context); 730 } 731 732 void GraphicsContext::clipToImageBuffer(const FloatRect& rect, const ImageBuffer* imageBuffer) 733 { 734 if (paintingDisabled()) 735 return; 736 737 CGContextTranslateCTM(platformContext(), rect.x(), rect.y() + rect.height()); 738 CGContextScaleCTM(platformContext(), 1, -1); 739 CGContextClipToMask(platformContext(), FloatRect(FloatPoint(), rect.size()), imageBuffer->image()->getCGImageRef()); 740 CGContextScaleCTM(platformContext(), 1, -1); 741 CGContextTranslateCTM(platformContext(), -rect.x(), -rect.y() - rect.height()); 742 } 743 744 void GraphicsContext::beginTransparencyLayer(float opacity) 745 { 746 if (paintingDisabled()) 747 return; 748 CGContextRef context = platformContext(); 749 CGContextSaveGState(context); 750 CGContextSetAlpha(context, opacity); 751 CGContextBeginTransparencyLayer(context, 0); 752 m_data->beginTransparencyLayer(); 753 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 754 } 755 756 void GraphicsContext::endTransparencyLayer() 757 { 758 if (paintingDisabled()) 759 return; 760 CGContextRef context = platformContext(); 761 CGContextEndTransparencyLayer(context); 762 CGContextRestoreGState(context); 763 m_data->endTransparencyLayer(); 764 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 765 } 766 767 void GraphicsContext::setPlatformShadow(const IntSize& offset, int blur, const Color& color, ColorSpace colorSpace) 768 { 769 if (paintingDisabled()) 770 return; 771 CGFloat xOffset = offset.width(); 772 CGFloat yOffset = offset.height(); 773 CGFloat blurRadius = blur; 774 CGContextRef context = platformContext(); 775 776 if (!m_common->state.shadowsIgnoreTransforms) { 777 CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context); 778 779 CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b; 780 CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d; 781 CGFloat C = B; 782 CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d; 783 784 CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D))))); 785 786 // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp 787 blurRadius = min(blur * smallEigenvalue, narrowPrecisionToCGFloat(1000.0)); 788 789 CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM); 790 791 xOffset = offsetInBaseSpace.width; 792 yOffset = offsetInBaseSpace.height; 793 } 794 795 // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated 796 // to the desired integer. 797 static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128); 798 if (xOffset > 0) 799 xOffset += extraShadowOffset; 800 else if (xOffset < 0) 801 xOffset -= extraShadowOffset; 802 803 if (yOffset > 0) 804 yOffset += extraShadowOffset; 805 else if (yOffset < 0) 806 yOffset -= extraShadowOffset; 807 808 // Check for an invalid color, as this means that the color was not set for the shadow 809 // and we should therefore just use the default shadow color. 810 if (!color.isValid()) 811 CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius); 812 else { 813 RetainPtr<CGColorRef> colorCG(AdoptCF, createCGColorWithColorSpace(color, colorSpace)); 814 CGContextSetShadowWithColor(context, 815 CGSizeMake(xOffset, yOffset), 816 blurRadius, 817 colorCG.get()); 818 } 819 } 820 821 void GraphicsContext::clearPlatformShadow() 822 { 823 if (paintingDisabled()) 824 return; 825 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); 826 } 827 828 void GraphicsContext::setMiterLimit(float limit) 829 { 830 if (paintingDisabled()) 831 return; 832 CGContextSetMiterLimit(platformContext(), limit); 833 } 834 835 void GraphicsContext::setAlpha(float alpha) 836 { 837 if (paintingDisabled()) 838 return; 839 CGContextSetAlpha(platformContext(), alpha); 840 } 841 842 void GraphicsContext::clearRect(const FloatRect& r) 843 { 844 if (paintingDisabled()) 845 return; 846 CGContextClearRect(platformContext(), r); 847 } 848 849 void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth) 850 { 851 if (paintingDisabled()) 852 return; 853 854 CGContextRef context = platformContext(); 855 856 // FIXME: Is this helpful and correct in the strokePattern and strokeGradient cases? 857 setCGStrokeColorSpace(context, m_common->state.strokeColorSpace); 858 859 if (m_common->state.strokeGradient) { 860 CGContextSaveGState(context); 861 setStrokeThickness(lineWidth); 862 CGContextAddRect(context, r); 863 CGContextReplacePathWithStrokedPath(context); 864 CGContextClip(context); 865 m_common->state.strokeGradient->paint(this); 866 CGContextRestoreGState(context); 867 return; 868 } 869 870 if (m_common->state.strokePattern) 871 applyStrokePattern(); 872 CGContextStrokeRectWithWidth(context, r, lineWidth); 873 } 874 875 void GraphicsContext::setLineCap(LineCap cap) 876 { 877 if (paintingDisabled()) 878 return; 879 switch (cap) { 880 case ButtCap: 881 CGContextSetLineCap(platformContext(), kCGLineCapButt); 882 break; 883 case RoundCap: 884 CGContextSetLineCap(platformContext(), kCGLineCapRound); 885 break; 886 case SquareCap: 887 CGContextSetLineCap(platformContext(), kCGLineCapSquare); 888 break; 889 } 890 } 891 892 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) 893 { 894 CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size()); 895 } 896 897 void GraphicsContext::setLineJoin(LineJoin join) 898 { 899 if (paintingDisabled()) 900 return; 901 switch (join) { 902 case MiterJoin: 903 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter); 904 break; 905 case RoundJoin: 906 CGContextSetLineJoin(platformContext(), kCGLineJoinRound); 907 break; 908 case BevelJoin: 909 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel); 910 break; 911 } 912 } 913 914 void GraphicsContext::beginPath() 915 { 916 CGContextBeginPath(platformContext()); 917 } 918 919 void GraphicsContext::addPath(const Path& path) 920 { 921 CGContextAddPath(platformContext(), path.platformPath()); 922 } 923 924 void GraphicsContext::clip(const Path& path) 925 { 926 if (paintingDisabled()) 927 return; 928 CGContextRef context = platformContext(); 929 CGContextBeginPath(context); 930 CGContextAddPath(context, path.platformPath()); 931 CGContextClip(context); 932 m_data->clip(path); 933 } 934 935 void GraphicsContext::canvasClip(const Path& path) 936 { 937 clip(path); 938 } 939 940 void GraphicsContext::clipOut(const Path& path) 941 { 942 if (paintingDisabled()) 943 return; 944 945 CGContextBeginPath(platformContext()); 946 CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext())); 947 CGContextAddPath(platformContext(), path.platformPath()); 948 CGContextEOClip(platformContext()); 949 } 950 951 void GraphicsContext::scale(const FloatSize& size) 952 { 953 if (paintingDisabled()) 954 return; 955 CGContextScaleCTM(platformContext(), size.width(), size.height()); 956 m_data->scale(size); 957 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 958 } 959 960 void GraphicsContext::rotate(float angle) 961 { 962 if (paintingDisabled()) 963 return; 964 CGContextRotateCTM(platformContext(), angle); 965 m_data->rotate(angle); 966 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 967 } 968 969 void GraphicsContext::translate(float x, float y) 970 { 971 if (paintingDisabled()) 972 return; 973 CGContextTranslateCTM(platformContext(), x, y); 974 m_data->translate(x, y); 975 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 976 } 977 978 void GraphicsContext::concatCTM(const AffineTransform& transform) 979 { 980 if (paintingDisabled()) 981 return; 982 CGContextConcatCTM(platformContext(), transform); 983 m_data->concatCTM(transform); 984 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 985 } 986 987 AffineTransform GraphicsContext::getCTM() const 988 { 989 CGAffineTransform t = CGContextGetCTM(platformContext()); 990 return AffineTransform(t.a, t.b, t.c, t.d, t.tx, t.ty); 991 } 992 993 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect) 994 { 995 // It is not enough just to round to pixels in device space. The rotation part of the 996 // affine transform matrix to device space can mess with this conversion if we have a 997 // rotating image like the hands of the world clock widget. We just need the scale, so 998 // we get the affine transform matrix and extract the scale. 999 1000 if (m_data->m_userToDeviceTransformKnownToBeIdentity) 1001 return rect; 1002 1003 CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext()); 1004 if (CGAffineTransformIsIdentity(deviceMatrix)) { 1005 m_data->m_userToDeviceTransformKnownToBeIdentity = true; 1006 return rect; 1007 } 1008 1009 float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b); 1010 float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d); 1011 1012 CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY); 1013 CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX, 1014 (rect.y() + rect.height()) * deviceScaleY); 1015 1016 deviceOrigin.x = roundf(deviceOrigin.x); 1017 deviceOrigin.y = roundf(deviceOrigin.y); 1018 deviceLowerRight.x = roundf(deviceLowerRight.x); 1019 deviceLowerRight.y = roundf(deviceLowerRight.y); 1020 1021 // Don't let the height or width round to 0 unless either was originally 0 1022 if (deviceOrigin.y == deviceLowerRight.y && rect.height()) 1023 deviceLowerRight.y += 1; 1024 if (deviceOrigin.x == deviceLowerRight.x && rect.width()) 1025 deviceLowerRight.x += 1; 1026 1027 FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY); 1028 FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY); 1029 return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin); 1030 } 1031 1032 void GraphicsContext::drawLineForText(const IntPoint& point, int width, bool printing) 1033 { 1034 if (paintingDisabled()) 1035 return; 1036 1037 if (width <= 0) 1038 return; 1039 1040 float x = point.x(); 1041 float y = point.y(); 1042 float lineLength = width; 1043 1044 // Use a minimum thickness of 0.5 in user space. 1045 // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use. 1046 float thickness = max(strokeThickness(), 0.5f); 1047 1048 bool restoreAntialiasMode = false; 1049 1050 if (!printing) { 1051 // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space). 1052 float adjustedThickness = max(thickness, 1.0f); 1053 1054 // FIXME: This should be done a better way. 1055 // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space 1056 // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels 1057 // in device space will make the underlines too thick. 1058 CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness)); 1059 if (lineRect.size.height < thickness * 2.0) { 1060 x = lineRect.origin.x; 1061 y = lineRect.origin.y; 1062 lineLength = lineRect.size.width; 1063 thickness = lineRect.size.height; 1064 if (shouldAntialias()) { 1065 CGContextSetShouldAntialias(platformContext(), false); 1066 restoreAntialiasMode = true; 1067 } 1068 } 1069 } 1070 1071 if (fillColor() != strokeColor()) 1072 setCGFillColor(platformContext(), strokeColor(), strokeColorSpace()); 1073 CGContextFillRect(platformContext(), CGRectMake(x, y, lineLength, thickness)); 1074 if (fillColor() != strokeColor()) 1075 setCGFillColor(platformContext(), fillColor(), fillColorSpace()); 1076 1077 if (restoreAntialiasMode) 1078 CGContextSetShouldAntialias(platformContext(), true); 1079 } 1080 1081 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) 1082 { 1083 if (paintingDisabled()) 1084 return; 1085 1086 RetainPtr<CFURLRef> urlRef(AdoptCF, link.createCFURL()); 1087 if (!urlRef) 1088 return; 1089 1090 CGContextRef context = platformContext(); 1091 1092 // Get the bounding box to handle clipping. 1093 CGRect box = CGContextGetClipBoundingBox(context); 1094 1095 IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height); 1096 IntRect rect = destRect; 1097 rect.intersect(intBox); 1098 1099 CGPDFContextSetURLForRect(context, urlRef.get(), 1100 CGRectApplyAffineTransform(rect, CGContextGetCTM(context))); 1101 } 1102 1103 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode) 1104 { 1105 if (paintingDisabled()) 1106 return; 1107 1108 CGInterpolationQuality quality = kCGInterpolationDefault; 1109 switch (mode) { 1110 case InterpolationDefault: 1111 quality = kCGInterpolationDefault; 1112 break; 1113 case InterpolationNone: 1114 quality = kCGInterpolationNone; 1115 break; 1116 case InterpolationLow: 1117 quality = kCGInterpolationLow; 1118 break; 1119 1120 // Fall through to InterpolationHigh if kCGInterpolationMedium is not usable. 1121 case InterpolationMedium: 1122 #if USE(CG_INTERPOLATION_MEDIUM) 1123 quality = kCGInterpolationMedium; 1124 break; 1125 #endif 1126 case InterpolationHigh: 1127 quality = kCGInterpolationHigh; 1128 break; 1129 } 1130 CGContextSetInterpolationQuality(platformContext(), quality); 1131 } 1132 1133 InterpolationQuality GraphicsContext::imageInterpolationQuality() const 1134 { 1135 if (paintingDisabled()) 1136 return InterpolationDefault; 1137 1138 CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext()); 1139 switch (quality) { 1140 case kCGInterpolationDefault: 1141 return InterpolationDefault; 1142 case kCGInterpolationNone: 1143 return InterpolationNone; 1144 case kCGInterpolationLow: 1145 return InterpolationLow; 1146 #if HAVE(CG_INTERPOLATION_MEDIUM) 1147 // kCGInterpolationMedium is known to be present in the CGInterpolationQuality enum. 1148 case kCGInterpolationMedium: 1149 #if USE(CG_INTERPOLATION_MEDIUM) 1150 // Only map to InterpolationMedium if targeting a system that understands it. 1151 return InterpolationMedium; 1152 #else 1153 return InterpolationDefault; 1154 #endif // USE(CG_INTERPOLATION_MEDIUM) 1155 #endif // HAVE(CG_INTERPOLATION_MEDIUM) 1156 case kCGInterpolationHigh: 1157 return InterpolationHigh; 1158 } 1159 return InterpolationDefault; 1160 } 1161 1162 void GraphicsContext::setPlatformTextDrawingMode(int mode) 1163 { 1164 if (paintingDisabled()) 1165 return; 1166 1167 // Wow, wish CG had used bits here. 1168 CGContextRef context = platformContext(); 1169 switch (mode) { 1170 case cTextInvisible: // Invisible 1171 CGContextSetTextDrawingMode(context, kCGTextInvisible); 1172 break; 1173 case cTextFill: // Fill 1174 CGContextSetTextDrawingMode(context, kCGTextFill); 1175 break; 1176 case cTextStroke: // Stroke 1177 CGContextSetTextDrawingMode(context, kCGTextStroke); 1178 break; 1179 case 3: // Fill | Stroke 1180 CGContextSetTextDrawingMode(context, kCGTextFillStroke); 1181 break; 1182 case cTextClip: // Clip 1183 CGContextSetTextDrawingMode(context, kCGTextClip); 1184 break; 1185 case 5: // Fill | Clip 1186 CGContextSetTextDrawingMode(context, kCGTextFillClip); 1187 break; 1188 case 6: // Stroke | Clip 1189 CGContextSetTextDrawingMode(context, kCGTextStrokeClip); 1190 break; 1191 case 7: // Fill | Stroke | Clip 1192 CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip); 1193 break; 1194 default: 1195 break; 1196 } 1197 } 1198 1199 void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace) 1200 { 1201 if (paintingDisabled()) 1202 return; 1203 setCGStrokeColor(platformContext(), color, colorSpace); 1204 } 1205 1206 void GraphicsContext::setPlatformStrokeThickness(float thickness) 1207 { 1208 if (paintingDisabled()) 1209 return; 1210 CGContextSetLineWidth(platformContext(), thickness); 1211 } 1212 1213 void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace) 1214 { 1215 if (paintingDisabled()) 1216 return; 1217 setCGFillColor(platformContext(), color, colorSpace); 1218 } 1219 1220 void GraphicsContext::setPlatformShouldAntialias(bool enable) 1221 { 1222 if (paintingDisabled()) 1223 return; 1224 CGContextSetShouldAntialias(platformContext(), enable); 1225 } 1226 1227 #ifndef BUILDING_ON_TIGER // Tiger's setCompositeOperation() is defined in GraphicsContextMac.mm. 1228 void GraphicsContext::setCompositeOperation(CompositeOperator mode) 1229 { 1230 if (paintingDisabled()) 1231 return; 1232 1233 CGBlendMode target = kCGBlendModeNormal; 1234 switch (mode) { 1235 case CompositeClear: 1236 target = kCGBlendModeClear; 1237 break; 1238 case CompositeCopy: 1239 target = kCGBlendModeCopy; 1240 break; 1241 case CompositeSourceOver: 1242 //kCGBlendModeNormal 1243 break; 1244 case CompositeSourceIn: 1245 target = kCGBlendModeSourceIn; 1246 break; 1247 case CompositeSourceOut: 1248 target = kCGBlendModeSourceOut; 1249 break; 1250 case CompositeSourceAtop: 1251 target = kCGBlendModeSourceAtop; 1252 break; 1253 case CompositeDestinationOver: 1254 target = kCGBlendModeDestinationOver; 1255 break; 1256 case CompositeDestinationIn: 1257 target = kCGBlendModeDestinationIn; 1258 break; 1259 case CompositeDestinationOut: 1260 target = kCGBlendModeDestinationOut; 1261 break; 1262 case CompositeDestinationAtop: 1263 target = kCGBlendModeDestinationAtop; 1264 break; 1265 case CompositeXOR: 1266 target = kCGBlendModeXOR; 1267 break; 1268 case CompositePlusDarker: 1269 target = kCGBlendModePlusDarker; 1270 break; 1271 case CompositeHighlight: 1272 // currently unsupported 1273 break; 1274 case CompositePlusLighter: 1275 target = kCGBlendModePlusLighter; 1276 break; 1277 } 1278 CGContextSetBlendMode(platformContext(), target); 1279 } 1280 #endif 1281 1282 } 1283