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 "GraphicsContextCG.h" 30 31 #include "AffineTransform.h" 32 #include "FloatConversion.h" 33 #include "GraphicsContextPlatformPrivateCG.h" 34 #include "ImageBuffer.h" 35 #include "KURL.h" 36 #include "Path.h" 37 #include "Pattern.h" 38 #include "ShadowBlur.h" 39 40 #include <CoreGraphics/CoreGraphics.h> 41 #include <wtf/MathExtras.h> 42 #include <wtf/OwnArrayPtr.h> 43 #include <wtf/RetainPtr.h> 44 #include <wtf/UnusedParam.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 // Undocumented CGContextSetCTM function, available at least since 10.4. 69 extern "C" { 70 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); 71 }; 72 73 using namespace std; 74 75 namespace WebCore { 76 77 static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace) 78 { 79 CGContextSetFillColorWithColor(context, cachedCGColor(color, colorSpace)); 80 } 81 82 static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace) 83 { 84 CGContextSetStrokeColorWithColor(context, cachedCGColor(color, colorSpace)); 85 } 86 87 CGColorSpaceRef deviceRGBColorSpaceRef() 88 { 89 static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB(); 90 return deviceSpace; 91 } 92 93 CGColorSpaceRef sRGBColorSpaceRef() 94 { 95 // FIXME: Windows should be able to use kCGColorSpaceSRGB, this is tracked by http://webkit.org/b/31363. 96 #if PLATFORM(WIN) || defined(BUILDING_ON_TIGER) 97 return deviceRGBColorSpaceRef(); 98 #else 99 static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); 100 return sRGBSpace; 101 #endif 102 } 103 104 CGColorSpaceRef linearRGBColorSpaceRef() 105 { 106 // FIXME: Windows should be able to use kCGColorSpaceGenericRGBLinear, this is tracked by http://webkit.org/b/31363. 107 #if PLATFORM(WIN) || defined(BUILDING_ON_TIGER) 108 return deviceRGBColorSpaceRef(); 109 #else 110 static CGColorSpaceRef linearRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGBLinear); 111 return linearRGBSpace; 112 #endif 113 } 114 115 void GraphicsContext::platformInit(CGContextRef cgContext) 116 { 117 m_data = new GraphicsContextPlatformPrivate(cgContext); 118 setPaintingDisabled(!cgContext); 119 if (cgContext) { 120 // Make sure the context starts in sync with our state. 121 setPlatformFillColor(fillColor(), fillColorSpace()); 122 setPlatformStrokeColor(strokeColor(), strokeColorSpace()); 123 } 124 } 125 126 void GraphicsContext::platformDestroy() 127 { 128 delete m_data; 129 } 130 131 CGContextRef GraphicsContext::platformContext() const 132 { 133 ASSERT(!paintingDisabled()); 134 ASSERT(m_data->m_cgContext); 135 return m_data->m_cgContext.get(); 136 } 137 138 void GraphicsContext::savePlatformState() 139 { 140 // Note: Do not use this function within this class implementation, since we want to avoid the extra 141 // save of the secondary context (in GraphicsContextPlatformPrivateCG.h). 142 CGContextSaveGState(platformContext()); 143 m_data->save(); 144 } 145 146 void GraphicsContext::restorePlatformState() 147 { 148 // Note: Do not use this function within this class implementation, since we want to avoid the extra 149 // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h). 150 CGContextRestoreGState(platformContext()); 151 m_data->restore(); 152 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 153 } 154 155 // Draws a filled rectangle with a stroked border. 156 void GraphicsContext::drawRect(const IntRect& rect) 157 { 158 // FIXME: this function does not handle patterns and gradients 159 // like drawPath does, it probably should. 160 if (paintingDisabled()) 161 return; 162 163 CGContextRef context = platformContext(); 164 165 CGContextFillRect(context, rect); 166 167 if (strokeStyle() != NoStroke) { 168 // We do a fill of four rects to simulate the stroke of a border. 169 Color oldFillColor = fillColor(); 170 if (oldFillColor != strokeColor()) 171 setCGFillColor(context, strokeColor(), strokeColorSpace()); 172 CGRect rects[4] = { 173 FloatRect(rect.x(), rect.y(), rect.width(), 1), 174 FloatRect(rect.x(), rect.maxY() - 1, rect.width(), 1), 175 FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2), 176 FloatRect(rect.maxX() - 1, rect.y() + 1, 1, rect.height() - 2) 177 }; 178 CGContextFillRects(context, rects, 4); 179 if (oldFillColor != strokeColor()) 180 setCGFillColor(context, oldFillColor, fillColorSpace()); 181 } 182 } 183 184 // This is only used to draw borders. 185 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) 186 { 187 if (paintingDisabled()) 188 return; 189 190 if (strokeStyle() == NoStroke) 191 return; 192 193 float width = strokeThickness(); 194 195 FloatPoint p1 = point1; 196 FloatPoint p2 = point2; 197 bool isVerticalLine = (p1.x() == p2.x()); 198 199 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic 200 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g., 201 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave 202 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5. 203 if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) { 204 if (isVerticalLine) { 205 p1.move(0, width); 206 p2.move(0, -width); 207 } else { 208 p1.move(width, 0); 209 p2.move(-width, 0); 210 } 211 } 212 213 if (((int)width) % 2) { 214 if (isVerticalLine) { 215 // We're a vertical line. Adjust our x. 216 p1.move(0.5f, 0.0f); 217 p2.move(0.5f, 0.0f); 218 } else { 219 // We're a horizontal line. Adjust our y. 220 p1.move(0.0f, 0.5f); 221 p2.move(0.0f, 0.5f); 222 } 223 } 224 225 int patWidth = 0; 226 switch (strokeStyle()) { 227 case NoStroke: 228 case SolidStroke: 229 break; 230 case DottedStroke: 231 patWidth = (int)width; 232 break; 233 case DashedStroke: 234 patWidth = 3 * (int)width; 235 break; 236 } 237 238 CGContextRef context = platformContext(); 239 240 if (shouldAntialias()) 241 CGContextSetShouldAntialias(context, false); 242 243 if (patWidth) { 244 CGContextSaveGState(context); 245 246 // Do a rect fill of our endpoints. This ensures we always have the 247 // appearance of being a border. We then draw the actual dotted/dashed line. 248 setCGFillColor(context, strokeColor(), strokeColorSpace()); // The save/restore make it safe to mutate the fill color here without setting it back to the old color. 249 if (isVerticalLine) { 250 CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width)); 251 CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width)); 252 } else { 253 CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width)); 254 CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width)); 255 } 256 257 // Example: 80 pixels with a width of 30 pixels. 258 // Remainder is 20. The maximum pixels of line we could paint 259 // will be 50 pixels. 260 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width; 261 int remainder = distance % patWidth; 262 int coverage = distance - remainder; 263 int numSegments = coverage / patWidth; 264 265 float patternOffset = 0.0f; 266 // Special case 1px dotted borders for speed. 267 if (patWidth == 1) 268 patternOffset = 1.0f; 269 else { 270 bool evenNumberOfSegments = !(numSegments % 2); 271 if (remainder) 272 evenNumberOfSegments = !evenNumberOfSegments; 273 if (evenNumberOfSegments) { 274 if (remainder) { 275 patternOffset += patWidth - remainder; 276 patternOffset += remainder / 2; 277 } else 278 patternOffset = patWidth / 2; 279 } else { 280 if (remainder) 281 patternOffset = (patWidth - remainder)/2; 282 } 283 } 284 285 const CGFloat dottedLine[2] = { patWidth, patWidth }; 286 CGContextSetLineDash(context, patternOffset, dottedLine, 2); 287 } 288 289 CGContextBeginPath(context); 290 CGContextMoveToPoint(context, p1.x(), p1.y()); 291 CGContextAddLineToPoint(context, p2.x(), p2.y()); 292 293 CGContextStrokePath(context); 294 295 if (patWidth) 296 CGContextRestoreGState(context); 297 298 if (shouldAntialias()) 299 CGContextSetShouldAntialias(context, true); 300 } 301 302 // This method is only used to draw the little circles used in lists. 303 void GraphicsContext::drawEllipse(const IntRect& rect) 304 { 305 if (paintingDisabled()) 306 return; 307 308 Path path; 309 path.addEllipse(rect); 310 drawPath(path); 311 } 312 313 314 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan) 315 { 316 if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f) 317 return; 318 319 CGContextRef context = platformContext(); 320 CGContextSaveGState(context); 321 CGContextBeginPath(context); 322 CGContextSetShouldAntialias(context, false); 323 324 int x = rect.x(); 325 int y = rect.y(); 326 float w = (float)rect.width(); 327 float h = (float)rect.height(); 328 float scaleFactor = h / w; 329 float reverseScaleFactor = w / h; 330 331 if (w != h) 332 scale(FloatSize(1, scaleFactor)); 333 334 float hRadius = w / 2; 335 float vRadius = h / 2; 336 float fa = startAngle; 337 float falen = fa + angleSpan; 338 float start = -fa * piFloat / 180.0f; 339 float end = -falen * piFloat / 180.0f; 340 CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true); 341 342 if (w != h) 343 scale(FloatSize(1, reverseScaleFactor)); 344 345 float width = strokeThickness(); 346 int patWidth = 0; 347 348 switch (strokeStyle()) { 349 case DottedStroke: 350 patWidth = (int)(width / 2); 351 break; 352 case DashedStroke: 353 patWidth = 3 * (int)(width / 2); 354 break; 355 default: 356 break; 357 } 358 359 if (patWidth) { 360 // Example: 80 pixels with a width of 30 pixels. 361 // Remainder is 20. The maximum pixels of line we could paint 362 // will be 50 pixels. 363 int distance; 364 if (hRadius == vRadius) 365 distance = static_cast<int>((piFloat * hRadius) / 2.0f); 366 else // We are elliptical and will have to estimate the distance 367 distance = static_cast<int>((piFloat * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0f)) / 2.0f); 368 369 int remainder = distance % patWidth; 370 int coverage = distance - remainder; 371 int numSegments = coverage / patWidth; 372 373 float patternOffset = 0.0f; 374 // Special case 1px dotted borders for speed. 375 if (patWidth == 1) 376 patternOffset = 1.0f; 377 else { 378 bool evenNumberOfSegments = !(numSegments % 2); 379 if (remainder) 380 evenNumberOfSegments = !evenNumberOfSegments; 381 if (evenNumberOfSegments) { 382 if (remainder) { 383 patternOffset += patWidth - remainder; 384 patternOffset += remainder / 2.0f; 385 } else 386 patternOffset = patWidth / 2.0f; 387 } else { 388 if (remainder) 389 patternOffset = (patWidth - remainder) / 2.0f; 390 } 391 } 392 393 const CGFloat dottedLine[2] = { patWidth, patWidth }; 394 CGContextSetLineDash(context, patternOffset, dottedLine, 2); 395 } 396 397 CGContextStrokePath(context); 398 399 CGContextRestoreGState(context); 400 } 401 402 static void addConvexPolygonToPath(Path& path, size_t numberOfPoints, const FloatPoint* points) 403 { 404 ASSERT(numberOfPoints > 0); 405 406 path.moveTo(points[0]); 407 for (size_t i = 1; i < numberOfPoints; ++i) 408 path.addLineTo(points[i]); 409 path.closeSubpath(); 410 } 411 412 void GraphicsContext::drawConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialiased) 413 { 414 if (paintingDisabled()) 415 return; 416 417 if (numberOfPoints <= 1) 418 return; 419 420 CGContextRef context = platformContext(); 421 422 if (antialiased != shouldAntialias()) 423 CGContextSetShouldAntialias(context, antialiased); 424 425 Path path; 426 addConvexPolygonToPath(path, numberOfPoints, points); 427 drawPath(path); 428 429 if (antialiased != shouldAntialias()) 430 CGContextSetShouldAntialias(context, shouldAntialias()); 431 } 432 433 void GraphicsContext::clipConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialias) 434 { 435 if (paintingDisabled()) 436 return; 437 438 if (numberOfPoints <= 1) 439 return; 440 441 CGContextRef context = platformContext(); 442 443 if (antialias != shouldAntialias()) 444 CGContextSetShouldAntialias(context, antialias); 445 446 Path path; 447 addConvexPolygonToPath(path, numberOfPoints, points); 448 clipPath(path, RULE_NONZERO); 449 450 if (antialias != shouldAntialias()) 451 CGContextSetShouldAntialias(context, shouldAntialias()); 452 } 453 454 void GraphicsContext::applyStrokePattern() 455 { 456 CGContextRef cgContext = platformContext(); 457 458 RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_state.strokePattern->createPlatformPattern(getCTM())); 459 if (!platformPattern) 460 return; 461 462 RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0)); 463 CGContextSetStrokeColorSpace(cgContext, patternSpace.get()); 464 465 const CGFloat patternAlpha = 1; 466 CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha); 467 } 468 469 void GraphicsContext::applyFillPattern() 470 { 471 CGContextRef cgContext = platformContext(); 472 473 RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_state.fillPattern->createPlatformPattern(getCTM())); 474 if (!platformPattern) 475 return; 476 477 RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0)); 478 CGContextSetFillColorSpace(cgContext, patternSpace.get()); 479 480 const CGFloat patternAlpha = 1; 481 CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha); 482 } 483 484 static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode) 485 { 486 bool shouldFill = state.fillPattern || state.fillColor.alpha(); 487 bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha()); 488 bool useEOFill = state.fillRule == RULE_EVENODD; 489 490 if (shouldFill) { 491 if (shouldStroke) { 492 if (useEOFill) 493 mode = kCGPathEOFillStroke; 494 else 495 mode = kCGPathFillStroke; 496 } else { // fill, no stroke 497 if (useEOFill) 498 mode = kCGPathEOFill; 499 else 500 mode = kCGPathFill; 501 } 502 } else { 503 // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used, 504 // but the compiler will not complain about an uninitialized variable. 505 mode = kCGPathStroke; 506 } 507 508 return shouldFill || shouldStroke; 509 } 510 511 void GraphicsContext::drawPath(const Path& path) 512 { 513 if (paintingDisabled()) 514 return; 515 516 CGContextRef context = platformContext(); 517 const GraphicsContextState& state = m_state; 518 519 if (state.fillGradient || state.strokeGradient) { 520 // We don't have any optimized way to fill & stroke a path using gradients 521 // FIXME: Be smarter about this. 522 fillPath(path); 523 strokePath(path); 524 return; 525 } 526 527 CGContextBeginPath(context); 528 CGContextAddPath(context, path.platformPath()); 529 530 if (state.fillPattern) 531 applyFillPattern(); 532 if (state.strokePattern) 533 applyStrokePattern(); 534 535 CGPathDrawingMode drawingMode; 536 if (calculateDrawingMode(state, drawingMode)) 537 CGContextDrawPath(context, drawingMode); 538 } 539 540 static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule) 541 { 542 if (fillRule == RULE_EVENODD) 543 CGContextEOFillPath(context); 544 else 545 CGContextFillPath(context); 546 } 547 548 void GraphicsContext::fillPath(const Path& path) 549 { 550 if (paintingDisabled()) 551 return; 552 553 CGContextRef context = platformContext(); 554 555 if (m_state.fillGradient) { 556 if (hasShadow()) { 557 FloatRect rect = path.boundingRect(); 558 CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0); 559 CGContextRef layerContext = CGLayerGetContext(layer); 560 561 CGContextTranslateCTM(layerContext, -rect.x(), -rect.y()); 562 CGContextBeginPath(layerContext); 563 CGContextAddPath(layerContext, path.platformPath()); 564 CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform()); 565 566 if (fillRule() == RULE_EVENODD) 567 CGContextEOClip(layerContext); 568 else 569 CGContextClip(layerContext); 570 571 m_state.fillGradient->paint(layerContext); 572 CGContextDrawLayerAtPoint(context, CGPointMake(rect.x(), rect.y()), layer); 573 CGLayerRelease(layer); 574 } else { 575 CGContextBeginPath(context); 576 CGContextAddPath(context, path.platformPath()); 577 CGContextSaveGState(context); 578 CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform()); 579 580 if (fillRule() == RULE_EVENODD) 581 CGContextEOClip(context); 582 else 583 CGContextClip(context); 584 585 m_state.fillGradient->paint(this); 586 CGContextRestoreGState(context); 587 } 588 589 return; 590 } 591 592 CGContextBeginPath(context); 593 CGContextAddPath(context, path.platformPath()); 594 595 if (m_state.fillPattern) 596 applyFillPattern(); 597 fillPathWithFillRule(context, fillRule()); 598 } 599 600 void GraphicsContext::strokePath(const Path& path) 601 { 602 if (paintingDisabled()) 603 return; 604 605 CGContextRef context = platformContext(); 606 607 CGContextBeginPath(context); 608 CGContextAddPath(context, path.platformPath()); 609 610 if (m_state.strokeGradient) { 611 if (hasShadow()) { 612 FloatRect rect = path.boundingRect(); 613 float lineWidth = strokeThickness(); 614 float doubleLineWidth = lineWidth * 2; 615 float layerWidth = ceilf(rect.width() + doubleLineWidth); 616 float layerHeight = ceilf(rect.height() + doubleLineWidth); 617 618 CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(layerWidth, layerHeight), 0); 619 CGContextRef layerContext = CGLayerGetContext(layer); 620 CGContextSetLineWidth(layerContext, lineWidth); 621 622 // Compensate for the line width, otherwise the layer's top-left corner would be 623 // aligned with the rect's top-left corner. This would result in leaving pixels out of 624 // the layer on the left and top sides. 625 float translationX = lineWidth - rect.x(); 626 float translationY = lineWidth - rect.y(); 627 CGContextTranslateCTM(layerContext, translationX, translationY); 628 629 CGContextAddPath(layerContext, path.platformPath()); 630 CGContextReplacePathWithStrokedPath(layerContext); 631 CGContextClip(layerContext); 632 CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform()); 633 m_state.strokeGradient->paint(layerContext); 634 635 float destinationX = roundf(rect.x() - lineWidth); 636 float destinationY = roundf(rect.y() - lineWidth); 637 CGContextDrawLayerAtPoint(context, CGPointMake(destinationX, destinationY), layer); 638 CGLayerRelease(layer); 639 } else { 640 CGContextSaveGState(context); 641 CGContextReplacePathWithStrokedPath(context); 642 CGContextClip(context); 643 CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform()); 644 m_state.strokeGradient->paint(this); 645 CGContextRestoreGState(context); 646 } 647 return; 648 } 649 650 if (m_state.strokePattern) 651 applyStrokePattern(); 652 CGContextStrokePath(context); 653 } 654 655 static float radiusToLegacyRadius(float radius) 656 { 657 return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius; 658 } 659 660 static bool hasBlurredShadow(const GraphicsContextState& state) 661 { 662 return state.shadowColor.isValid() && state.shadowColor.alpha() && state.shadowBlur; 663 } 664 665 void GraphicsContext::fillRect(const FloatRect& rect) 666 { 667 if (paintingDisabled()) 668 return; 669 670 CGContextRef context = platformContext(); 671 672 if (m_state.fillGradient) { 673 CGContextSaveGState(context); 674 if (hasShadow()) { 675 CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0); 676 CGContextRef layerContext = CGLayerGetContext(layer); 677 678 CGContextTranslateCTM(layerContext, -rect.x(), -rect.y()); 679 CGContextAddRect(layerContext, rect); 680 CGContextClip(layerContext); 681 682 CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform()); 683 m_state.fillGradient->paint(layerContext); 684 CGContextDrawLayerAtPoint(context, CGPointMake(rect.x(), rect.y()), layer); 685 CGLayerRelease(layer); 686 } else { 687 CGContextClipToRect(context, rect); 688 CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform()); 689 m_state.fillGradient->paint(this); 690 } 691 CGContextRestoreGState(context); 692 return; 693 } 694 695 if (m_state.fillPattern) 696 applyFillPattern(); 697 698 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet. 699 if (drawOwnShadow) { 700 float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur; 701 // Turn off CG shadows. 702 CGContextSaveGState(context); 703 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); 704 705 ShadowBlur contextShadow(shadowBlur, m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace); 706 contextShadow.drawRectShadow(this, rect, RoundedIntRect::Radii()); 707 } 708 709 CGContextFillRect(context, rect); 710 711 if (drawOwnShadow) 712 CGContextRestoreGState(context); 713 } 714 715 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace) 716 { 717 if (paintingDisabled()) 718 return; 719 720 CGContextRef context = platformContext(); 721 Color oldFillColor = fillColor(); 722 ColorSpace oldColorSpace = fillColorSpace(); 723 724 if (oldFillColor != color || oldColorSpace != colorSpace) 725 setCGFillColor(context, color, colorSpace); 726 727 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet. 728 if (drawOwnShadow) { 729 float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur; 730 // Turn off CG shadows. 731 CGContextSaveGState(context); 732 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); 733 734 ShadowBlur contextShadow(shadowBlur, m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace); 735 contextShadow.drawRectShadow(this, rect, RoundedIntRect::Radii()); 736 } 737 738 CGContextFillRect(context, rect); 739 740 if (drawOwnShadow) 741 CGContextRestoreGState(context); 742 743 if (oldFillColor != color || oldColorSpace != colorSpace) 744 setCGFillColor(context, oldFillColor, oldColorSpace); 745 } 746 747 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace) 748 { 749 if (paintingDisabled()) 750 return; 751 752 CGContextRef context = platformContext(); 753 Color oldFillColor = fillColor(); 754 ColorSpace oldColorSpace = fillColorSpace(); 755 756 if (oldFillColor != color || oldColorSpace != colorSpace) 757 setCGFillColor(context, color, colorSpace); 758 759 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet. 760 if (drawOwnShadow) { 761 float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur; 762 763 // Turn off CG shadows. 764 CGContextSaveGState(context); 765 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); 766 767 ShadowBlur contextShadow(shadowBlur, m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace); 768 contextShadow.drawRectShadow(this, rect, RoundedIntRect::Radii(topLeft, topRight, bottomLeft, bottomRight)); 769 } 770 771 bool equalWidths = (topLeft.width() == topRight.width() && topRight.width() == bottomLeft.width() && bottomLeft.width() == bottomRight.width()); 772 bool equalHeights = (topLeft.height() == bottomLeft.height() && bottomLeft.height() == topRight.height() && topRight.height() == bottomRight.height()); 773 if (equalWidths && equalHeights && topLeft.width() * 2 == rect.width() && topLeft.height() * 2 == rect.height()) 774 CGContextFillEllipseInRect(context, rect); 775 else { 776 Path path; 777 path.addRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight); 778 fillPath(path); 779 } 780 781 if (drawOwnShadow) 782 CGContextRestoreGState(context); 783 784 if (oldFillColor != color || oldColorSpace != colorSpace) 785 setCGFillColor(context, oldFillColor, oldColorSpace); 786 } 787 788 void GraphicsContext::fillRectWithRoundedHole(const IntRect& rect, const RoundedIntRect& roundedHoleRect, const Color& color, ColorSpace colorSpace) 789 { 790 if (paintingDisabled()) 791 return; 792 793 CGContextRef context = platformContext(); 794 795 Path path; 796 path.addRect(rect); 797 798 if (!roundedHoleRect.radii().isZero()) 799 path.addRoundedRect(roundedHoleRect); 800 else 801 path.addRect(roundedHoleRect.rect()); 802 803 WindRule oldFillRule = fillRule(); 804 Color oldFillColor = fillColor(); 805 ColorSpace oldFillColorSpace = fillColorSpace(); 806 807 setFillRule(RULE_EVENODD); 808 setFillColor(color, colorSpace); 809 810 // fillRectWithRoundedHole() assumes that the edges of rect are clipped out, so we only care about shadows cast around inside the hole. 811 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; 812 if (drawOwnShadow) { 813 float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur; 814 815 // Turn off CG shadows. 816 CGContextSaveGState(context); 817 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); 818 819 ShadowBlur contextShadow(shadowBlur, m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace); 820 contextShadow.drawInsetShadow(this, rect, roundedHoleRect.rect(), roundedHoleRect.radii()); 821 } 822 823 fillPath(path); 824 825 if (drawOwnShadow) 826 CGContextRestoreGState(context); 827 828 setFillRule(oldFillRule); 829 setFillColor(oldFillColor, oldFillColorSpace); 830 } 831 832 void GraphicsContext::clip(const FloatRect& rect) 833 { 834 if (paintingDisabled()) 835 return; 836 CGContextClipToRect(platformContext(), rect); 837 m_data->clip(rect); 838 } 839 840 void GraphicsContext::clipOut(const IntRect& rect) 841 { 842 if (paintingDisabled()) 843 return; 844 845 CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect }; 846 CGContextBeginPath(platformContext()); 847 CGContextAddRects(platformContext(), rects, 2); 848 CGContextEOClip(platformContext()); 849 } 850 851 void GraphicsContext::clipPath(const Path& path, WindRule clipRule) 852 { 853 if (paintingDisabled()) 854 return; 855 856 if (path.isEmpty()) 857 return; 858 859 CGContextRef context = platformContext(); 860 861 CGContextBeginPath(platformContext()); 862 CGContextAddPath(platformContext(), path.platformPath()); 863 864 if (clipRule == RULE_EVENODD) 865 CGContextEOClip(context); 866 else 867 CGContextClip(context); 868 } 869 870 IntRect GraphicsContext::clipBounds() const 871 { 872 return enclosingIntRect(CGContextGetClipBoundingBox(platformContext())); 873 } 874 875 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness) 876 { 877 if (paintingDisabled()) 878 return; 879 880 clip(rect); 881 CGContextRef context = platformContext(); 882 883 // Add outer ellipse 884 CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height())); 885 // Add inner ellipse. 886 CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness, 887 rect.width() - (thickness * 2), rect.height() - (thickness * 2))); 888 889 CGContextEOClip(context); 890 } 891 892 void GraphicsContext::beginTransparencyLayer(float opacity) 893 { 894 if (paintingDisabled()) 895 return; 896 CGContextRef context = platformContext(); 897 CGContextSaveGState(context); 898 CGContextSetAlpha(context, opacity); 899 CGContextBeginTransparencyLayer(context, 0); 900 m_data->beginTransparencyLayer(); 901 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 902 } 903 904 void GraphicsContext::endTransparencyLayer() 905 { 906 if (paintingDisabled()) 907 return; 908 CGContextRef context = platformContext(); 909 CGContextEndTransparencyLayer(context); 910 CGContextRestoreGState(context); 911 m_data->endTransparencyLayer(); 912 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 913 } 914 915 void GraphicsContext::setPlatformShadow(const FloatSize& offset, float blur, const Color& color, ColorSpace colorSpace) 916 { 917 if (paintingDisabled()) 918 return; 919 920 // FIXME: we could avoid the shadow setup cost when we know we'll render the shadow ourselves. 921 922 CGFloat xOffset = offset.width(); 923 CGFloat yOffset = offset.height(); 924 CGFloat blurRadius = blur; 925 CGContextRef context = platformContext(); 926 927 if (!m_state.shadowsIgnoreTransforms) { 928 CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context); 929 930 CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b; 931 CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d; 932 CGFloat C = B; 933 CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d; 934 935 CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D))))); 936 937 blurRadius = blur * smallEigenvalue; 938 939 CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM); 940 941 xOffset = offsetInBaseSpace.width; 942 yOffset = offsetInBaseSpace.height; 943 } 944 945 // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp 946 blurRadius = min(blurRadius, narrowPrecisionToCGFloat(1000.0)); 947 948 // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated 949 // to the desired integer. 950 static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128); 951 if (xOffset > 0) 952 xOffset += extraShadowOffset; 953 else if (xOffset < 0) 954 xOffset -= extraShadowOffset; 955 956 if (yOffset > 0) 957 yOffset += extraShadowOffset; 958 else if (yOffset < 0) 959 yOffset -= extraShadowOffset; 960 961 // Check for an invalid color, as this means that the color was not set for the shadow 962 // and we should therefore just use the default shadow color. 963 if (!color.isValid()) 964 CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius); 965 else 966 CGContextSetShadowWithColor(context, CGSizeMake(xOffset, yOffset), blurRadius, cachedCGColor(color, colorSpace)); 967 } 968 969 void GraphicsContext::clearPlatformShadow() 970 { 971 if (paintingDisabled()) 972 return; 973 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0); 974 } 975 976 void GraphicsContext::setMiterLimit(float limit) 977 { 978 if (paintingDisabled()) 979 return; 980 CGContextSetMiterLimit(platformContext(), limit); 981 } 982 983 void GraphicsContext::setAlpha(float alpha) 984 { 985 if (paintingDisabled()) 986 return; 987 CGContextSetAlpha(platformContext(), alpha); 988 } 989 990 void GraphicsContext::clearRect(const FloatRect& r) 991 { 992 if (paintingDisabled()) 993 return; 994 CGContextClearRect(platformContext(), r); 995 } 996 997 void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth) 998 { 999 if (paintingDisabled()) 1000 return; 1001 1002 CGContextRef context = platformContext(); 1003 1004 if (m_state.strokeGradient) { 1005 if (hasShadow()) { 1006 const float doubleLineWidth = lineWidth * 2; 1007 const float layerWidth = ceilf(rect.width() + doubleLineWidth); 1008 const float layerHeight = ceilf(rect.height() + doubleLineWidth); 1009 CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(layerWidth, layerHeight), 0); 1010 1011 CGContextRef layerContext = CGLayerGetContext(layer); 1012 m_state.strokeThickness = lineWidth; 1013 CGContextSetLineWidth(layerContext, lineWidth); 1014 1015 // Compensate for the line width, otherwise the layer's top-left corner would be 1016 // aligned with the rect's top-left corner. This would result in leaving pixels out of 1017 // the layer on the left and top sides. 1018 const float translationX = lineWidth - rect.x(); 1019 const float translationY = lineWidth - rect.y(); 1020 CGContextTranslateCTM(layerContext, translationX, translationY); 1021 1022 CGContextAddRect(layerContext, rect); 1023 CGContextReplacePathWithStrokedPath(layerContext); 1024 CGContextClip(layerContext); 1025 CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform()); 1026 m_state.strokeGradient->paint(layerContext); 1027 1028 const float destinationX = roundf(rect.x() - lineWidth); 1029 const float destinationY = roundf(rect.y() - lineWidth); 1030 CGContextDrawLayerAtPoint(context, CGPointMake(destinationX, destinationY), layer); 1031 CGLayerRelease(layer); 1032 } else { 1033 CGContextSaveGState(context); 1034 setStrokeThickness(lineWidth); 1035 CGContextAddRect(context, rect); 1036 CGContextReplacePathWithStrokedPath(context); 1037 CGContextClip(context); 1038 CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform()); 1039 m_state.strokeGradient->paint(this); 1040 CGContextRestoreGState(context); 1041 } 1042 return; 1043 } 1044 1045 if (m_state.strokePattern) 1046 applyStrokePattern(); 1047 CGContextStrokeRectWithWidth(context, rect, lineWidth); 1048 } 1049 1050 void GraphicsContext::setLineCap(LineCap cap) 1051 { 1052 if (paintingDisabled()) 1053 return; 1054 switch (cap) { 1055 case ButtCap: 1056 CGContextSetLineCap(platformContext(), kCGLineCapButt); 1057 break; 1058 case RoundCap: 1059 CGContextSetLineCap(platformContext(), kCGLineCapRound); 1060 break; 1061 case SquareCap: 1062 CGContextSetLineCap(platformContext(), kCGLineCapSquare); 1063 break; 1064 } 1065 } 1066 1067 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) 1068 { 1069 CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size()); 1070 } 1071 1072 void GraphicsContext::setLineJoin(LineJoin join) 1073 { 1074 if (paintingDisabled()) 1075 return; 1076 switch (join) { 1077 case MiterJoin: 1078 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter); 1079 break; 1080 case RoundJoin: 1081 CGContextSetLineJoin(platformContext(), kCGLineJoinRound); 1082 break; 1083 case BevelJoin: 1084 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel); 1085 break; 1086 } 1087 } 1088 1089 void GraphicsContext::clip(const Path& path) 1090 { 1091 if (paintingDisabled()) 1092 return; 1093 CGContextRef context = platformContext(); 1094 1095 // CGContextClip does nothing if the path is empty, so in this case, we 1096 // instead clip against a zero rect to reduce the clipping region to 1097 // nothing - which is the intended behavior of clip() if the path is empty. 1098 if (path.isEmpty()) 1099 CGContextClipToRect(context, CGRectZero); 1100 else { 1101 CGContextBeginPath(context); 1102 CGContextAddPath(context, path.platformPath()); 1103 CGContextClip(context); 1104 } 1105 m_data->clip(path); 1106 } 1107 1108 void GraphicsContext::canvasClip(const Path& path) 1109 { 1110 clip(path); 1111 } 1112 1113 void GraphicsContext::clipOut(const Path& path) 1114 { 1115 if (paintingDisabled()) 1116 return; 1117 1118 CGContextBeginPath(platformContext()); 1119 CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext())); 1120 CGContextAddPath(platformContext(), path.platformPath()); 1121 CGContextEOClip(platformContext()); 1122 } 1123 1124 void GraphicsContext::scale(const FloatSize& size) 1125 { 1126 if (paintingDisabled()) 1127 return; 1128 CGContextScaleCTM(platformContext(), size.width(), size.height()); 1129 m_data->scale(size); 1130 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 1131 } 1132 1133 void GraphicsContext::rotate(float angle) 1134 { 1135 if (paintingDisabled()) 1136 return; 1137 CGContextRotateCTM(platformContext(), angle); 1138 m_data->rotate(angle); 1139 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 1140 } 1141 1142 void GraphicsContext::translate(float x, float y) 1143 { 1144 if (paintingDisabled()) 1145 return; 1146 CGContextTranslateCTM(platformContext(), x, y); 1147 m_data->translate(x, y); 1148 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 1149 } 1150 1151 void GraphicsContext::concatCTM(const AffineTransform& transform) 1152 { 1153 if (paintingDisabled()) 1154 return; 1155 CGContextConcatCTM(platformContext(), transform); 1156 m_data->concatCTM(transform); 1157 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 1158 } 1159 1160 void GraphicsContext::setCTM(const AffineTransform& transform) 1161 { 1162 if (paintingDisabled()) 1163 return; 1164 CGContextSetCTM(platformContext(), transform); 1165 m_data->setCTM(transform); 1166 m_data->m_userToDeviceTransformKnownToBeIdentity = false; 1167 } 1168 1169 AffineTransform GraphicsContext::getCTM() const 1170 { 1171 CGAffineTransform t = CGContextGetCTM(platformContext()); 1172 return AffineTransform(t.a, t.b, t.c, t.d, t.tx, t.ty); 1173 } 1174 1175 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode roundingMode) 1176 { 1177 #if PLATFORM(CHROMIUM) 1178 return rect; 1179 #else 1180 // It is not enough just to round to pixels in device space. The rotation part of the 1181 // affine transform matrix to device space can mess with this conversion if we have a 1182 // rotating image like the hands of the world clock widget. We just need the scale, so 1183 // we get the affine transform matrix and extract the scale. 1184 1185 if (m_data->m_userToDeviceTransformKnownToBeIdentity) 1186 return rect; 1187 1188 CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext()); 1189 if (CGAffineTransformIsIdentity(deviceMatrix)) { 1190 m_data->m_userToDeviceTransformKnownToBeIdentity = true; 1191 return rect; 1192 } 1193 1194 float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b); 1195 float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d); 1196 1197 CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY); 1198 CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX, 1199 (rect.y() + rect.height()) * deviceScaleY); 1200 1201 deviceOrigin.x = roundf(deviceOrigin.x); 1202 deviceOrigin.y = roundf(deviceOrigin.y); 1203 if (roundingMode == RoundAllSides) { 1204 deviceLowerRight.x = roundf(deviceLowerRight.x); 1205 deviceLowerRight.y = roundf(deviceLowerRight.y); 1206 } else { 1207 deviceLowerRight.x = deviceOrigin.x + roundf(rect.width() * deviceScaleX); 1208 deviceLowerRight.y = deviceOrigin.y + roundf(rect.height() * deviceScaleY); 1209 } 1210 1211 // Don't let the height or width round to 0 unless either was originally 0 1212 if (deviceOrigin.y == deviceLowerRight.y && rect.height()) 1213 deviceLowerRight.y += 1; 1214 if (deviceOrigin.x == deviceLowerRight.x && rect.width()) 1215 deviceLowerRight.x += 1; 1216 1217 FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY); 1218 FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY); 1219 return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin); 1220 #endif 1221 } 1222 1223 void GraphicsContext::drawLineForText(const FloatPoint& point, float width, bool printing) 1224 { 1225 if (paintingDisabled()) 1226 return; 1227 1228 if (width <= 0) 1229 return; 1230 1231 float x = point.x(); 1232 float y = point.y(); 1233 float lineLength = width; 1234 1235 // Use a minimum thickness of 0.5 in user space. 1236 // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use. 1237 float thickness = max(strokeThickness(), 0.5f); 1238 1239 bool restoreAntialiasMode = false; 1240 1241 if (!printing) { 1242 // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space). 1243 float adjustedThickness = max(thickness, 1.0f); 1244 1245 // FIXME: This should be done a better way. 1246 // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space 1247 // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels 1248 // in device space will make the underlines too thick. 1249 CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness), RoundOriginAndDimensions); 1250 if (lineRect.size.height < thickness * 2.0) { 1251 x = lineRect.origin.x; 1252 y = lineRect.origin.y; 1253 lineLength = lineRect.size.width; 1254 thickness = lineRect.size.height; 1255 if (shouldAntialias()) { 1256 CGContextSetShouldAntialias(platformContext(), false); 1257 restoreAntialiasMode = true; 1258 } 1259 } 1260 } 1261 1262 if (fillColor() != strokeColor()) 1263 setCGFillColor(platformContext(), strokeColor(), strokeColorSpace()); 1264 CGContextFillRect(platformContext(), CGRectMake(x, y, lineLength, thickness)); 1265 if (fillColor() != strokeColor()) 1266 setCGFillColor(platformContext(), fillColor(), fillColorSpace()); 1267 1268 if (restoreAntialiasMode) 1269 CGContextSetShouldAntialias(platformContext(), true); 1270 } 1271 1272 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) 1273 { 1274 if (paintingDisabled()) 1275 return; 1276 1277 RetainPtr<CFURLRef> urlRef(AdoptCF, link.createCFURL()); 1278 if (!urlRef) 1279 return; 1280 1281 CGContextRef context = platformContext(); 1282 1283 // Get the bounding box to handle clipping. 1284 CGRect box = CGContextGetClipBoundingBox(context); 1285 1286 IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height); 1287 IntRect rect = destRect; 1288 rect.intersect(intBox); 1289 1290 CGPDFContextSetURLForRect(context, urlRef.get(), 1291 CGRectApplyAffineTransform(rect, CGContextGetCTM(context))); 1292 } 1293 1294 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode) 1295 { 1296 if (paintingDisabled()) 1297 return; 1298 1299 CGInterpolationQuality quality = kCGInterpolationDefault; 1300 switch (mode) { 1301 case InterpolationDefault: 1302 quality = kCGInterpolationDefault; 1303 break; 1304 case InterpolationNone: 1305 quality = kCGInterpolationNone; 1306 break; 1307 case InterpolationLow: 1308 quality = kCGInterpolationLow; 1309 break; 1310 1311 // Fall through to InterpolationHigh if kCGInterpolationMedium is not usable. 1312 case InterpolationMedium: 1313 #if USE(CG_INTERPOLATION_MEDIUM) 1314 quality = kCGInterpolationMedium; 1315 break; 1316 #endif 1317 case InterpolationHigh: 1318 quality = kCGInterpolationHigh; 1319 break; 1320 } 1321 CGContextSetInterpolationQuality(platformContext(), quality); 1322 } 1323 1324 InterpolationQuality GraphicsContext::imageInterpolationQuality() const 1325 { 1326 if (paintingDisabled()) 1327 return InterpolationDefault; 1328 1329 CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext()); 1330 switch (quality) { 1331 case kCGInterpolationDefault: 1332 return InterpolationDefault; 1333 case kCGInterpolationNone: 1334 return InterpolationNone; 1335 case kCGInterpolationLow: 1336 return InterpolationLow; 1337 #if HAVE(CG_INTERPOLATION_MEDIUM) 1338 // kCGInterpolationMedium is known to be present in the CGInterpolationQuality enum. 1339 case kCGInterpolationMedium: 1340 #if USE(CG_INTERPOLATION_MEDIUM) 1341 // Only map to InterpolationMedium if targeting a system that understands it. 1342 return InterpolationMedium; 1343 #else 1344 return InterpolationDefault; 1345 #endif // USE(CG_INTERPOLATION_MEDIUM) 1346 #endif // HAVE(CG_INTERPOLATION_MEDIUM) 1347 case kCGInterpolationHigh: 1348 return InterpolationHigh; 1349 } 1350 return InterpolationDefault; 1351 } 1352 1353 void GraphicsContext::setAllowsFontSmoothing(bool allowsFontSmoothing) 1354 { 1355 UNUSED_PARAM(allowsFontSmoothing); 1356 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 1357 CGContextRef context = platformContext(); 1358 CGContextSetAllowsFontSmoothing(context, allowsFontSmoothing); 1359 #endif 1360 } 1361 1362 void GraphicsContext::setIsCALayerContext(bool isLayerContext) 1363 { 1364 if (isLayerContext) 1365 m_data->m_contextFlags |= IsLayerCGContext; 1366 else 1367 m_data->m_contextFlags &= ~IsLayerCGContext; 1368 } 1369 1370 bool GraphicsContext::isCALayerContext() const 1371 { 1372 return m_data->m_contextFlags & IsLayerCGContext; 1373 } 1374 1375 void GraphicsContext::setIsAcceleratedContext(bool isAccelerated) 1376 { 1377 if (isAccelerated) 1378 m_data->m_contextFlags |= IsAcceleratedCGContext; 1379 else 1380 m_data->m_contextFlags &= ~IsAcceleratedCGContext; 1381 } 1382 1383 bool GraphicsContext::isAcceleratedContext() const 1384 { 1385 return m_data->m_contextFlags & IsAcceleratedCGContext; 1386 } 1387 1388 void GraphicsContext::setPlatformTextDrawingMode(TextDrawingModeFlags mode) 1389 { 1390 if (paintingDisabled()) 1391 return; 1392 1393 // Wow, wish CG had used bits here. 1394 CGContextRef context = platformContext(); 1395 switch (mode) { 1396 case TextModeInvisible: 1397 CGContextSetTextDrawingMode(context, kCGTextInvisible); 1398 break; 1399 case TextModeFill: 1400 CGContextSetTextDrawingMode(context, kCGTextFill); 1401 break; 1402 case TextModeStroke: 1403 CGContextSetTextDrawingMode(context, kCGTextStroke); 1404 break; 1405 case TextModeFill | TextModeStroke: 1406 CGContextSetTextDrawingMode(context, kCGTextFillStroke); 1407 break; 1408 case TextModeClip: 1409 CGContextSetTextDrawingMode(context, kCGTextClip); 1410 break; 1411 case TextModeFill | TextModeClip: 1412 CGContextSetTextDrawingMode(context, kCGTextFillClip); 1413 break; 1414 case TextModeStroke | TextModeClip: 1415 CGContextSetTextDrawingMode(context, kCGTextStrokeClip); 1416 break; 1417 case TextModeFill | TextModeStroke | TextModeClip: 1418 CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip); 1419 break; 1420 default: 1421 break; 1422 } 1423 } 1424 1425 void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace) 1426 { 1427 if (paintingDisabled()) 1428 return; 1429 setCGStrokeColor(platformContext(), color, colorSpace); 1430 } 1431 1432 void GraphicsContext::setPlatformStrokeThickness(float thickness) 1433 { 1434 if (paintingDisabled()) 1435 return; 1436 CGContextSetLineWidth(platformContext(), thickness); 1437 } 1438 1439 void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace) 1440 { 1441 if (paintingDisabled()) 1442 return; 1443 setCGFillColor(platformContext(), color, colorSpace); 1444 } 1445 1446 void GraphicsContext::setPlatformShouldAntialias(bool enable) 1447 { 1448 if (paintingDisabled()) 1449 return; 1450 CGContextSetShouldAntialias(platformContext(), enable); 1451 } 1452 1453 void GraphicsContext::setPlatformShouldSmoothFonts(bool enable) 1454 { 1455 if (paintingDisabled()) 1456 return; 1457 CGContextSetShouldSmoothFonts(platformContext(), enable); 1458 } 1459 1460 #ifndef BUILDING_ON_TIGER // Tiger's setPlatformCompositeOperation() is defined in GraphicsContextMac.mm. 1461 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode) 1462 { 1463 if (paintingDisabled()) 1464 return; 1465 1466 CGBlendMode target = kCGBlendModeNormal; 1467 switch (mode) { 1468 case CompositeClear: 1469 target = kCGBlendModeClear; 1470 break; 1471 case CompositeCopy: 1472 target = kCGBlendModeCopy; 1473 break; 1474 case CompositeSourceOver: 1475 //kCGBlendModeNormal 1476 break; 1477 case CompositeSourceIn: 1478 target = kCGBlendModeSourceIn; 1479 break; 1480 case CompositeSourceOut: 1481 target = kCGBlendModeSourceOut; 1482 break; 1483 case CompositeSourceAtop: 1484 target = kCGBlendModeSourceAtop; 1485 break; 1486 case CompositeDestinationOver: 1487 target = kCGBlendModeDestinationOver; 1488 break; 1489 case CompositeDestinationIn: 1490 target = kCGBlendModeDestinationIn; 1491 break; 1492 case CompositeDestinationOut: 1493 target = kCGBlendModeDestinationOut; 1494 break; 1495 case CompositeDestinationAtop: 1496 target = kCGBlendModeDestinationAtop; 1497 break; 1498 case CompositeXOR: 1499 target = kCGBlendModeXOR; 1500 break; 1501 case CompositePlusDarker: 1502 target = kCGBlendModePlusDarker; 1503 break; 1504 case CompositeHighlight: 1505 // currently unsupported 1506 break; 1507 case CompositePlusLighter: 1508 target = kCGBlendModePlusLighter; 1509 break; 1510 } 1511 CGContextSetBlendMode(platformContext(), target); 1512 } 1513 #endif 1514 1515 } 1516