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