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