1 /* 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies) 4 * Copyright (C) 2007 Alp Toker <alp (at) atoker.com> 5 * Copyright (C) 2008 Eric Seidel <eric (at) webkit.org> 6 * Copyright (C) 2008 Dirk Schulze <krit (at) webkit.org> 7 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved. 8 * Copyright (C) 2012, 2013 Intel Corporation. All rights reserved. 9 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include "config.h" 34 #include "core/html/canvas/CanvasRenderingContext2D.h" 35 36 #include "bindings/core/v8/ExceptionMessages.h" 37 #include "bindings/core/v8/ExceptionState.h" 38 #include "bindings/core/v8/ExceptionStatePlaceholder.h" 39 #include "core/CSSPropertyNames.h" 40 #include "core/css/CSSFontSelector.h" 41 #include "core/css/StylePropertySet.h" 42 #include "core/css/parser/CSSParser.h" 43 #include "core/css/resolver/StyleResolver.h" 44 #include "core/dom/ExceptionCode.h" 45 #include "core/dom/StyleEngine.h" 46 #include "core/events/Event.h" 47 #include "core/fetch/ImageResource.h" 48 #include "core/frame/ImageBitmap.h" 49 #include "core/html/HTMLCanvasElement.h" 50 #include "core/html/HTMLImageElement.h" 51 #include "core/html/HTMLMediaElement.h" 52 #include "core/html/HTMLVideoElement.h" 53 #include "core/html/ImageData.h" 54 #include "core/html/TextMetrics.h" 55 #include "core/html/canvas/CanvasGradient.h" 56 #include "core/html/canvas/CanvasPattern.h" 57 #include "core/html/canvas/CanvasStyle.h" 58 #include "core/html/canvas/HitRegionOptions.h" 59 #include "core/html/canvas/Path2D.h" 60 #include "core/rendering/RenderImage.h" 61 #include "core/rendering/RenderLayer.h" 62 #include "core/rendering/RenderTheme.h" 63 #include "platform/fonts/FontCache.h" 64 #include "platform/geometry/FloatQuad.h" 65 #include "platform/graphics/DrawLooperBuilder.h" 66 #include "platform/graphics/GraphicsContextStateSaver.h" 67 #include "platform/text/TextRun.h" 68 #include "wtf/CheckedArithmetic.h" 69 #include "wtf/MathExtras.h" 70 #include "wtf/OwnPtr.h" 71 #include "wtf/Uint8ClampedArray.h" 72 #include "wtf/text/StringBuilder.h" 73 74 namespace blink { 75 76 static const int defaultFontSize = 10; 77 static const char defaultFontFamily[] = "sans-serif"; 78 static const char defaultFont[] = "10px sans-serif"; 79 static const char inherit[] = "inherit"; 80 static const char rtl[] = "rtl"; 81 static const char ltr[] = "ltr"; 82 static const double TryRestoreContextInterval = 0.5; 83 static const unsigned MaxTryRestoreContextAttempts = 4; 84 85 static bool contextLostRestoredEventsEnabled() 86 { 87 return RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled(); 88 } 89 90 CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, const Canvas2DContextAttributes* attrs, bool usesCSSCompatibilityParseMode) 91 : CanvasRenderingContext(canvas) 92 , m_usesCSSCompatibilityParseMode(usesCSSCompatibilityParseMode) 93 , m_hasAlpha(!attrs || attrs->alpha()) 94 , m_isContextLost(false) 95 , m_contextRestorable(true) 96 , m_storageMode(!attrs ? PersistentStorage : attrs->parsedStorage()) 97 , m_tryRestoreContextAttemptCount(0) 98 , m_dispatchContextLostEventTimer(this, &CanvasRenderingContext2D::dispatchContextLostEvent) 99 , m_dispatchContextRestoredEventTimer(this, &CanvasRenderingContext2D::dispatchContextRestoredEvent) 100 , m_tryRestoreContextEventTimer(this, &CanvasRenderingContext2D::tryRestoreContextEvent) 101 { 102 m_stateStack.append(adoptPtrWillBeNoop(new State())); 103 } 104 105 void CanvasRenderingContext2D::unwindStateStack() 106 { 107 if (size_t stackSize = m_stateStack.size()) { 108 if (GraphicsContext* context = canvas()->existingDrawingContext()) { 109 while (--stackSize) 110 context->restore(); 111 } 112 } 113 } 114 115 CanvasRenderingContext2D::~CanvasRenderingContext2D() 116 { 117 } 118 119 void CanvasRenderingContext2D::validateStateStack() 120 { 121 #if ENABLE(ASSERT) 122 GraphicsContext* context = canvas()->existingDrawingContext(); 123 if (context && !context->contextDisabled()) 124 ASSERT(context->saveCount() == m_stateStack.size()); 125 #endif 126 } 127 128 bool CanvasRenderingContext2D::isAccelerated() const 129 { 130 if (!canvas()->hasImageBuffer()) 131 return false; 132 GraphicsContext* context = drawingContext(); 133 return context && context->isAccelerated(); 134 } 135 136 bool CanvasRenderingContext2D::isContextLost() const 137 { 138 return m_isContextLost; 139 } 140 141 void CanvasRenderingContext2D::loseContext() 142 { 143 if (m_isContextLost) 144 return; 145 m_isContextLost = true; 146 m_dispatchContextLostEventTimer.startOneShot(0, FROM_HERE); 147 } 148 149 void CanvasRenderingContext2D::restoreContext() 150 { 151 if (!m_contextRestorable) 152 return; 153 // This code path is for restoring from an eviction 154 // Restoring from surface failure is handled internally 155 ASSERT(m_isContextLost && !canvas()->hasImageBuffer()); 156 157 if (canvas()->buffer()) { 158 if (contextLostRestoredEventsEnabled()) { 159 m_dispatchContextRestoredEventTimer.startOneShot(0, FROM_HERE); 160 } else { 161 // legacy synchronous context restoration. 162 reset(); 163 m_isContextLost = false; 164 } 165 } 166 } 167 168 void CanvasRenderingContext2D::trace(Visitor* visitor) 169 { 170 #if ENABLE(OILPAN) 171 visitor->trace(m_stateStack); 172 visitor->trace(m_fetchedFonts); 173 visitor->trace(m_hitRegionManager); 174 #endif 175 CanvasRenderingContext::trace(visitor); 176 } 177 178 void CanvasRenderingContext2D::dispatchContextLostEvent(Timer<CanvasRenderingContext2D>*) 179 { 180 if (contextLostRestoredEventsEnabled()) { 181 RefPtrWillBeRawPtr<Event> event = Event::createCancelable(EventTypeNames::contextlost); 182 canvas()->dispatchEvent(event); 183 if (event->defaultPrevented()) { 184 m_contextRestorable = false; 185 } 186 } 187 188 // If an image buffer is present, it means the context was not lost due to 189 // an eviction, but rather due to a surface failure (gpu context lost?) 190 if (m_contextRestorable && canvas()->hasImageBuffer()) { 191 m_tryRestoreContextAttemptCount = 0; 192 m_tryRestoreContextEventTimer.startRepeating(TryRestoreContextInterval, FROM_HERE); 193 } 194 } 195 196 void CanvasRenderingContext2D::tryRestoreContextEvent(Timer<CanvasRenderingContext2D>* timer) 197 { 198 if (!m_isContextLost) { 199 // Canvas was already restored (possibly thanks to a resize), so stop trying. 200 m_tryRestoreContextEventTimer.stop(); 201 return; 202 } 203 if (canvas()->hasImageBuffer() && canvas()->buffer()->restoreSurface()) { 204 m_tryRestoreContextEventTimer.stop(); 205 dispatchContextRestoredEvent(0); 206 } 207 208 if (++m_tryRestoreContextAttemptCount > MaxTryRestoreContextAttempts) 209 canvas()->discardImageBuffer(); 210 211 if (!canvas()->hasImageBuffer()) { 212 // final attempt: allocate a brand new image buffer instead of restoring 213 timer->stop(); 214 if (canvas()->buffer()) 215 dispatchContextRestoredEvent(0); 216 } 217 } 218 219 void CanvasRenderingContext2D::dispatchContextRestoredEvent(Timer<CanvasRenderingContext2D>*) 220 { 221 if (!m_isContextLost) 222 return; 223 reset(); 224 m_isContextLost = false; 225 if (contextLostRestoredEventsEnabled()) { 226 RefPtrWillBeRawPtr<Event> event(Event::create(EventTypeNames::contextrestored)); 227 canvas()->dispatchEvent(event); 228 } 229 } 230 231 void CanvasRenderingContext2D::reset() 232 { 233 validateStateStack(); 234 unwindStateStack(); 235 m_stateStack.resize(1); 236 m_stateStack.first() = adoptPtrWillBeNoop(new State()); 237 m_path.clear(); 238 validateStateStack(); 239 } 240 241 // Important: Several of these properties are also stored in GraphicsContext's 242 // StrokeData. The default values that StrokeData uses may not the same values 243 // that the canvas 2d spec specifies. Make sure to sync the initial state of the 244 // GraphicsContext in HTMLCanvasElement::createImageBuffer()! 245 CanvasRenderingContext2D::State::State() 246 : m_unrealizedSaveCount(0) 247 , m_strokeStyle(CanvasStyle::createFromRGBA(Color::black)) 248 , m_fillStyle(CanvasStyle::createFromRGBA(Color::black)) 249 , m_lineWidth(1) 250 , m_lineCap(ButtCap) 251 , m_lineJoin(MiterJoin) 252 , m_miterLimit(10) 253 , m_shadowBlur(0) 254 , m_shadowColor(Color::transparent) 255 , m_globalAlpha(1) 256 , m_globalComposite(CompositeSourceOver) 257 , m_globalBlend(blink::WebBlendModeNormal) 258 , m_invertibleCTM(true) 259 , m_lineDashOffset(0) 260 , m_imageSmoothingEnabled(true) 261 , m_textAlign(StartTextAlign) 262 , m_textBaseline(AlphabeticTextBaseline) 263 , m_direction(DirectionInherit) 264 , m_unparsedFont(defaultFont) 265 , m_realizedFont(false) 266 , m_hasClip(false) 267 { 268 } 269 270 CanvasRenderingContext2D::State::State(const State& other) 271 : CSSFontSelectorClient() 272 , m_unrealizedSaveCount(other.m_unrealizedSaveCount) 273 , m_unparsedStrokeColor(other.m_unparsedStrokeColor) 274 , m_unparsedFillColor(other.m_unparsedFillColor) 275 , m_strokeStyle(other.m_strokeStyle) 276 , m_fillStyle(other.m_fillStyle) 277 , m_lineWidth(other.m_lineWidth) 278 , m_lineCap(other.m_lineCap) 279 , m_lineJoin(other.m_lineJoin) 280 , m_miterLimit(other.m_miterLimit) 281 , m_shadowOffset(other.m_shadowOffset) 282 , m_shadowBlur(other.m_shadowBlur) 283 , m_shadowColor(other.m_shadowColor) 284 , m_globalAlpha(other.m_globalAlpha) 285 , m_globalComposite(other.m_globalComposite) 286 , m_globalBlend(other.m_globalBlend) 287 , m_transform(other.m_transform) 288 , m_invertibleCTM(other.m_invertibleCTM) 289 , m_lineDashOffset(other.m_lineDashOffset) 290 , m_imageSmoothingEnabled(other.m_imageSmoothingEnabled) 291 , m_textAlign(other.m_textAlign) 292 , m_textBaseline(other.m_textBaseline) 293 , m_direction(other.m_direction) 294 , m_unparsedFont(other.m_unparsedFont) 295 , m_font(other.m_font) 296 , m_realizedFont(other.m_realizedFont) 297 , m_hasClip(other.m_hasClip) 298 { 299 if (m_realizedFont) 300 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalidationCallbacks(this); 301 } 302 303 CanvasRenderingContext2D::State& CanvasRenderingContext2D::State::operator=(const State& other) 304 { 305 if (this == &other) 306 return *this; 307 308 #if !ENABLE(OILPAN) 309 if (m_realizedFont) 310 static_cast<CSSFontSelector*>(m_font.fontSelector())->unregisterForInvalidationCallbacks(this); 311 #endif 312 313 m_unrealizedSaveCount = other.m_unrealizedSaveCount; 314 m_unparsedStrokeColor = other.m_unparsedStrokeColor; 315 m_unparsedFillColor = other.m_unparsedFillColor; 316 m_strokeStyle = other.m_strokeStyle; 317 m_fillStyle = other.m_fillStyle; 318 m_lineWidth = other.m_lineWidth; 319 m_lineCap = other.m_lineCap; 320 m_lineJoin = other.m_lineJoin; 321 m_miterLimit = other.m_miterLimit; 322 m_shadowOffset = other.m_shadowOffset; 323 m_shadowBlur = other.m_shadowBlur; 324 m_shadowColor = other.m_shadowColor; 325 m_globalAlpha = other.m_globalAlpha; 326 m_globalComposite = other.m_globalComposite; 327 m_globalBlend = other.m_globalBlend; 328 m_transform = other.m_transform; 329 m_invertibleCTM = other.m_invertibleCTM; 330 m_imageSmoothingEnabled = other.m_imageSmoothingEnabled; 331 m_textAlign = other.m_textAlign; 332 m_textBaseline = other.m_textBaseline; 333 m_direction = other.m_direction; 334 m_unparsedFont = other.m_unparsedFont; 335 m_font = other.m_font; 336 m_realizedFont = other.m_realizedFont; 337 m_hasClip = other.m_hasClip; 338 339 if (m_realizedFont) 340 static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalidationCallbacks(this); 341 342 return *this; 343 } 344 345 CanvasRenderingContext2D::State::~State() 346 { 347 #if !ENABLE(OILPAN) 348 if (m_realizedFont) 349 static_cast<CSSFontSelector*>(m_font.fontSelector())->unregisterForInvalidationCallbacks(this); 350 #endif 351 } 352 353 void CanvasRenderingContext2D::State::fontsNeedUpdate(CSSFontSelector* fontSelector) 354 { 355 ASSERT_ARG(fontSelector, fontSelector == m_font.fontSelector()); 356 ASSERT(m_realizedFont); 357 358 m_font.update(fontSelector); 359 } 360 361 void CanvasRenderingContext2D::State::trace(Visitor* visitor) 362 { 363 visitor->trace(m_strokeStyle); 364 visitor->trace(m_fillStyle); 365 CSSFontSelectorClient::trace(visitor); 366 } 367 368 void CanvasRenderingContext2D::realizeSaves(GraphicsContext* context) 369 { 370 validateStateStack(); 371 if (state().m_unrealizedSaveCount) { 372 ASSERT(m_stateStack.size() >= 1); 373 // Reduce the current state's unrealized count by one now, 374 // to reflect the fact we are saving one state. 375 m_stateStack.last()->m_unrealizedSaveCount--; 376 m_stateStack.append(adoptPtrWillBeNoop(new State(state()))); 377 // Set the new state's unrealized count to 0, because it has no outstanding saves. 378 // We need to do this explicitly because the copy constructor and operator= used 379 // by the Vector operations copy the unrealized count from the previous state (in 380 // turn necessary to support correct resizing and unwinding of the stack). 381 m_stateStack.last()->m_unrealizedSaveCount = 0; 382 if (!context) 383 context = drawingContext(); 384 if (context) 385 context->save(); 386 validateStateStack(); 387 } 388 } 389 390 void CanvasRenderingContext2D::restore() 391 { 392 validateStateStack(); 393 if (state().m_unrealizedSaveCount) { 394 // We never realized the save, so just record that it was unnecessary. 395 --m_stateStack.last()->m_unrealizedSaveCount; 396 return; 397 } 398 ASSERT(m_stateStack.size() >= 1); 399 if (m_stateStack.size() <= 1) 400 return; 401 m_path.transform(state().m_transform); 402 m_stateStack.removeLast(); 403 m_path.transform(state().m_transform.inverse()); 404 GraphicsContext* c = drawingContext(); 405 if (c) 406 c->restore(); 407 validateStateStack(); 408 } 409 410 CanvasStyle* CanvasRenderingContext2D::strokeStyle() const 411 { 412 return state().m_strokeStyle.get(); 413 } 414 415 void CanvasRenderingContext2D::setStrokeStyle(PassRefPtrWillBeRawPtr<CanvasStyle> prpStyle) 416 { 417 RefPtrWillBeRawPtr<CanvasStyle> style = prpStyle; 418 419 if (!style) 420 return; 421 422 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentColor(*style)) 423 return; 424 425 if (style->isCurrentColor()) { 426 if (style->hasOverrideAlpha()) 427 style = CanvasStyle::createFromRGBA(colorWithOverrideAlpha(currentColor(canvas()), style->overrideAlpha())); 428 else 429 style = CanvasStyle::createFromRGBA(currentColor(canvas())); 430 } else if (canvas()->originClean() && style->canvasPattern() && !style->canvasPattern()->originClean()) { 431 canvas()->setOriginTainted(); 432 } 433 434 GraphicsContext* c = drawingContext(); 435 realizeSaves(c); 436 modifiableState().m_strokeStyle = style.release(); 437 if (!c) 438 return; 439 state().m_strokeStyle->applyStrokeColor(c); 440 modifiableState().m_unparsedStrokeColor = String(); 441 } 442 443 CanvasStyle* CanvasRenderingContext2D::fillStyle() const 444 { 445 return state().m_fillStyle.get(); 446 } 447 448 void CanvasRenderingContext2D::setFillStyle(PassRefPtrWillBeRawPtr<CanvasStyle> prpStyle) 449 { 450 RefPtrWillBeRawPtr<CanvasStyle> style = prpStyle; 451 452 if (!style) 453 return; 454 455 if (state().m_fillStyle && state().m_fillStyle->isEquivalentColor(*style)) 456 return; 457 458 if (style->isCurrentColor()) { 459 if (style->hasOverrideAlpha()) 460 style = CanvasStyle::createFromRGBA(colorWithOverrideAlpha(currentColor(canvas()), style->overrideAlpha())); 461 else 462 style = CanvasStyle::createFromRGBA(currentColor(canvas())); 463 } else if (canvas()->originClean() && style->canvasPattern() && !style->canvasPattern()->originClean()) { 464 canvas()->setOriginTainted(); 465 } 466 467 GraphicsContext* c = drawingContext(); 468 realizeSaves(c); 469 modifiableState().m_fillStyle = style.release(); 470 if (!c) 471 return; 472 state().m_fillStyle->applyFillColor(c); 473 modifiableState().m_unparsedFillColor = String(); 474 } 475 476 float CanvasRenderingContext2D::lineWidth() const 477 { 478 return state().m_lineWidth; 479 } 480 481 void CanvasRenderingContext2D::setLineWidth(float width) 482 { 483 if (!(std::isfinite(width) && width > 0)) 484 return; 485 if (state().m_lineWidth == width) 486 return; 487 GraphicsContext* c = drawingContext(); 488 realizeSaves(c); 489 modifiableState().m_lineWidth = width; 490 if (!c) 491 return; 492 c->setStrokeThickness(width); 493 } 494 495 String CanvasRenderingContext2D::lineCap() const 496 { 497 return lineCapName(state().m_lineCap); 498 } 499 500 void CanvasRenderingContext2D::setLineCap(const String& s) 501 { 502 LineCap cap; 503 if (!parseLineCap(s, cap)) 504 return; 505 if (state().m_lineCap == cap) 506 return; 507 GraphicsContext* c = drawingContext(); 508 realizeSaves(c); 509 modifiableState().m_lineCap = cap; 510 if (!c) 511 return; 512 c->setLineCap(cap); 513 } 514 515 String CanvasRenderingContext2D::lineJoin() const 516 { 517 return lineJoinName(state().m_lineJoin); 518 } 519 520 void CanvasRenderingContext2D::setLineJoin(const String& s) 521 { 522 LineJoin join; 523 if (!parseLineJoin(s, join)) 524 return; 525 if (state().m_lineJoin == join) 526 return; 527 GraphicsContext* c = drawingContext(); 528 realizeSaves(c); 529 modifiableState().m_lineJoin = join; 530 if (!c) 531 return; 532 c->setLineJoin(join); 533 } 534 535 float CanvasRenderingContext2D::miterLimit() const 536 { 537 return state().m_miterLimit; 538 } 539 540 void CanvasRenderingContext2D::setMiterLimit(float limit) 541 { 542 if (!(std::isfinite(limit) && limit > 0)) 543 return; 544 if (state().m_miterLimit == limit) 545 return; 546 GraphicsContext* c = drawingContext(); 547 realizeSaves(c); 548 modifiableState().m_miterLimit = limit; 549 if (!c) 550 return; 551 c->setMiterLimit(limit); 552 } 553 554 float CanvasRenderingContext2D::shadowOffsetX() const 555 { 556 return state().m_shadowOffset.width(); 557 } 558 559 void CanvasRenderingContext2D::setShadowOffsetX(float x) 560 { 561 if (!std::isfinite(x)) 562 return; 563 if (state().m_shadowOffset.width() == x) 564 return; 565 realizeSaves(0); 566 modifiableState().m_shadowOffset.setWidth(x); 567 applyShadow(); 568 } 569 570 float CanvasRenderingContext2D::shadowOffsetY() const 571 { 572 return state().m_shadowOffset.height(); 573 } 574 575 void CanvasRenderingContext2D::setShadowOffsetY(float y) 576 { 577 if (!std::isfinite(y)) 578 return; 579 if (state().m_shadowOffset.height() == y) 580 return; 581 realizeSaves(0); 582 modifiableState().m_shadowOffset.setHeight(y); 583 applyShadow(); 584 } 585 586 float CanvasRenderingContext2D::shadowBlur() const 587 { 588 return state().m_shadowBlur; 589 } 590 591 void CanvasRenderingContext2D::setShadowBlur(float blur) 592 { 593 if (!(std::isfinite(blur) && blur >= 0)) 594 return; 595 if (state().m_shadowBlur == blur) 596 return; 597 realizeSaves(0); 598 modifiableState().m_shadowBlur = blur; 599 applyShadow(); 600 } 601 602 String CanvasRenderingContext2D::shadowColor() const 603 { 604 return Color(state().m_shadowColor).serialized(); 605 } 606 607 void CanvasRenderingContext2D::setShadowColor(const String& color) 608 { 609 RGBA32 rgba; 610 if (!parseColorOrCurrentColor(rgba, color, canvas())) 611 return; 612 if (state().m_shadowColor == rgba) 613 return; 614 realizeSaves(0); 615 modifiableState().m_shadowColor = rgba; 616 applyShadow(); 617 } 618 619 const Vector<float>& CanvasRenderingContext2D::getLineDash() const 620 { 621 return state().m_lineDash; 622 } 623 624 static bool lineDashSequenceIsValid(const Vector<float>& dash) 625 { 626 for (size_t i = 0; i < dash.size(); i++) { 627 if (!std::isfinite(dash[i]) || dash[i] < 0) 628 return false; 629 } 630 return true; 631 } 632 633 void CanvasRenderingContext2D::setLineDash(const Vector<float>& dash) 634 { 635 if (!lineDashSequenceIsValid(dash)) 636 return; 637 638 realizeSaves(0); 639 modifiableState().m_lineDash = dash; 640 // Spec requires the concatenation of two copies the dash list when the 641 // number of elements is odd 642 if (dash.size() % 2) 643 modifiableState().m_lineDash.appendVector(dash); 644 645 applyLineDash(); 646 } 647 648 float CanvasRenderingContext2D::lineDashOffset() const 649 { 650 return state().m_lineDashOffset; 651 } 652 653 void CanvasRenderingContext2D::setLineDashOffset(float offset) 654 { 655 if (!std::isfinite(offset) || state().m_lineDashOffset == offset) 656 return; 657 658 realizeSaves(0); 659 modifiableState().m_lineDashOffset = offset; 660 applyLineDash(); 661 } 662 663 void CanvasRenderingContext2D::applyLineDash() const 664 { 665 GraphicsContext* c = drawingContext(); 666 if (!c) 667 return; 668 DashArray convertedLineDash(state().m_lineDash.size()); 669 for (size_t i = 0; i < state().m_lineDash.size(); ++i) 670 convertedLineDash[i] = static_cast<DashArrayElement>(state().m_lineDash[i]); 671 c->setLineDash(convertedLineDash, state().m_lineDashOffset); 672 } 673 674 float CanvasRenderingContext2D::globalAlpha() const 675 { 676 return state().m_globalAlpha; 677 } 678 679 void CanvasRenderingContext2D::setGlobalAlpha(float alpha) 680 { 681 if (!(alpha >= 0 && alpha <= 1)) 682 return; 683 if (state().m_globalAlpha == alpha) 684 return; 685 GraphicsContext* c = drawingContext(); 686 realizeSaves(c); 687 modifiableState().m_globalAlpha = alpha; 688 if (!c) 689 return; 690 c->setAlphaAsFloat(alpha); 691 } 692 693 String CanvasRenderingContext2D::globalCompositeOperation() const 694 { 695 return compositeOperatorName(state().m_globalComposite, state().m_globalBlend); 696 } 697 698 void CanvasRenderingContext2D::setGlobalCompositeOperation(const String& operation) 699 { 700 CompositeOperator op = CompositeSourceOver; 701 blink::WebBlendMode blendMode = blink::WebBlendModeNormal; 702 if (!parseCompositeAndBlendOperator(operation, op, blendMode)) 703 return; 704 if ((state().m_globalComposite == op) && (state().m_globalBlend == blendMode)) 705 return; 706 GraphicsContext* c = drawingContext(); 707 realizeSaves(c); 708 modifiableState().m_globalComposite = op; 709 modifiableState().m_globalBlend = blendMode; 710 if (!c) 711 return; 712 c->setCompositeOperation(op, blendMode); 713 } 714 715 void CanvasRenderingContext2D::setCurrentTransform(PassRefPtr<SVGMatrixTearOff> passMatrixTearOff) 716 { 717 RefPtr<SVGMatrixTearOff> matrixTearOff = passMatrixTearOff; 718 const AffineTransform& transform = matrixTearOff->value(); 719 setTransform(transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f()); 720 } 721 722 void CanvasRenderingContext2D::scale(float sx, float sy) 723 { 724 GraphicsContext* c = drawingContext(); 725 if (!c) 726 return; 727 if (!state().m_invertibleCTM) 728 return; 729 730 if (!std::isfinite(sx) | !std::isfinite(sy)) 731 return; 732 733 AffineTransform newTransform = state().m_transform; 734 newTransform.scaleNonUniform(sx, sy); 735 if (state().m_transform == newTransform) 736 return; 737 738 realizeSaves(c); 739 740 if (!newTransform.isInvertible()) { 741 modifiableState().m_invertibleCTM = false; 742 return; 743 } 744 745 modifiableState().m_transform = newTransform; 746 c->scale(sx, sy); 747 m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy)); 748 } 749 750 void CanvasRenderingContext2D::rotate(float angleInRadians) 751 { 752 GraphicsContext* c = drawingContext(); 753 if (!c) 754 return; 755 if (!state().m_invertibleCTM) 756 return; 757 758 if (!std::isfinite(angleInRadians)) 759 return; 760 761 AffineTransform newTransform = state().m_transform; 762 newTransform.rotateRadians(angleInRadians); 763 if (state().m_transform == newTransform) 764 return; 765 766 realizeSaves(c); 767 768 if (!newTransform.isInvertible()) { 769 modifiableState().m_invertibleCTM = false; 770 return; 771 } 772 773 modifiableState().m_transform = newTransform; 774 c->rotate(angleInRadians); 775 m_path.transform(AffineTransform().rotateRadians(-angleInRadians)); 776 } 777 778 void CanvasRenderingContext2D::translate(float tx, float ty) 779 { 780 GraphicsContext* c = drawingContext(); 781 if (!c) 782 return; 783 if (!state().m_invertibleCTM) 784 return; 785 786 if (!std::isfinite(tx) | !std::isfinite(ty)) 787 return; 788 789 AffineTransform newTransform = state().m_transform; 790 newTransform.translate(tx, ty); 791 if (state().m_transform == newTransform) 792 return; 793 794 realizeSaves(c); 795 796 if (!newTransform.isInvertible()) { 797 modifiableState().m_invertibleCTM = false; 798 return; 799 } 800 801 modifiableState().m_transform = newTransform; 802 c->translate(tx, ty); 803 m_path.transform(AffineTransform().translate(-tx, -ty)); 804 } 805 806 void CanvasRenderingContext2D::transform(float m11, float m12, float m21, float m22, float dx, float dy) 807 { 808 GraphicsContext* c = drawingContext(); 809 if (!c) 810 return; 811 if (!state().m_invertibleCTM) 812 return; 813 814 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy)) 815 return; 816 817 AffineTransform transform(m11, m12, m21, m22, dx, dy); 818 AffineTransform newTransform = state().m_transform * transform; 819 if (state().m_transform == newTransform) 820 return; 821 822 realizeSaves(c); 823 824 modifiableState().m_transform = newTransform; 825 if (!newTransform.isInvertible()) { 826 modifiableState().m_invertibleCTM = false; 827 return; 828 } 829 830 c->concatCTM(transform); 831 m_path.transform(transform.inverse()); 832 } 833 834 void CanvasRenderingContext2D::resetTransform() 835 { 836 GraphicsContext* c = drawingContext(); 837 if (!c) 838 return; 839 840 AffineTransform ctm = state().m_transform; 841 bool invertibleCTM = state().m_invertibleCTM; 842 // It is possible that CTM is identity while CTM is not invertible. 843 // When CTM becomes non-invertible, realizeSaves() can make CTM identity. 844 if (ctm.isIdentity() && invertibleCTM) 845 return; 846 847 realizeSaves(c); 848 // resetTransform() resolves the non-invertible CTM state. 849 modifiableState().m_transform.makeIdentity(); 850 modifiableState().m_invertibleCTM = true; 851 c->setCTM(canvas()->baseTransform()); 852 853 if (invertibleCTM) 854 m_path.transform(ctm); 855 // When else, do nothing because all transform methods didn't update m_path when CTM became non-invertible. 856 // It means that resetTransform() restores m_path just before CTM became non-invertible. 857 } 858 859 void CanvasRenderingContext2D::setTransform(float m11, float m12, float m21, float m22, float dx, float dy) 860 { 861 GraphicsContext* c = drawingContext(); 862 if (!c) 863 return; 864 865 if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy)) 866 return; 867 868 resetTransform(); 869 transform(m11, m12, m21, m22, dx, dy); 870 } 871 872 void CanvasRenderingContext2D::setStrokeColor(const String& color) 873 { 874 if (color == state().m_unparsedStrokeColor) 875 return; 876 realizeSaves(0); 877 setStrokeStyle(CanvasStyle::createFromString(color)); 878 modifiableState().m_unparsedStrokeColor = color; 879 } 880 881 void CanvasRenderingContext2D::setStrokeColor(float grayLevel) 882 { 883 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentRGBA(grayLevel, grayLevel, grayLevel, 1.0f)) 884 return; 885 setStrokeStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, 1.0f)); 886 } 887 888 void CanvasRenderingContext2D::setStrokeColor(const String& color, float alpha) 889 { 890 setStrokeStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha)); 891 } 892 893 void CanvasRenderingContext2D::setStrokeColor(float grayLevel, float alpha) 894 { 895 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentRGBA(grayLevel, grayLevel, grayLevel, alpha)) 896 return; 897 setStrokeStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, alpha)); 898 } 899 900 void CanvasRenderingContext2D::setStrokeColor(float r, float g, float b, float a) 901 { 902 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentRGBA(r, g, b, a)) 903 return; 904 setStrokeStyle(CanvasStyle::createFromRGBAChannels(r, g, b, a)); 905 } 906 907 void CanvasRenderingContext2D::setStrokeColor(float c, float m, float y, float k, float a) 908 { 909 if (state().m_strokeStyle && state().m_strokeStyle->isEquivalentCMYKA(c, m, y, k, a)) 910 return; 911 setStrokeStyle(CanvasStyle::createFromCMYKAChannels(c, m, y, k, a)); 912 } 913 914 void CanvasRenderingContext2D::setFillColor(const String& color) 915 { 916 if (color == state().m_unparsedFillColor) 917 return; 918 realizeSaves(0); 919 setFillStyle(CanvasStyle::createFromString(color)); 920 modifiableState().m_unparsedFillColor = color; 921 } 922 923 void CanvasRenderingContext2D::setFillColor(float grayLevel) 924 { 925 if (state().m_fillStyle && state().m_fillStyle->isEquivalentRGBA(grayLevel, grayLevel, grayLevel, 1.0f)) 926 return; 927 setFillStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, 1.0f)); 928 } 929 930 void CanvasRenderingContext2D::setFillColor(const String& color, float alpha) 931 { 932 setFillStyle(CanvasStyle::createFromStringWithOverrideAlpha(color, alpha)); 933 } 934 935 void CanvasRenderingContext2D::setFillColor(float grayLevel, float alpha) 936 { 937 if (state().m_fillStyle && state().m_fillStyle->isEquivalentRGBA(grayLevel, grayLevel, grayLevel, alpha)) 938 return; 939 setFillStyle(CanvasStyle::createFromGrayLevelWithAlpha(grayLevel, alpha)); 940 } 941 942 void CanvasRenderingContext2D::setFillColor(float r, float g, float b, float a) 943 { 944 if (state().m_fillStyle && state().m_fillStyle->isEquivalentRGBA(r, g, b, a)) 945 return; 946 setFillStyle(CanvasStyle::createFromRGBAChannels(r, g, b, a)); 947 } 948 949 void CanvasRenderingContext2D::setFillColor(float c, float m, float y, float k, float a) 950 { 951 if (state().m_fillStyle && state().m_fillStyle->isEquivalentCMYKA(c, m, y, k, a)) 952 return; 953 setFillStyle(CanvasStyle::createFromCMYKAChannels(c, m, y, k, a)); 954 } 955 956 void CanvasRenderingContext2D::beginPath() 957 { 958 m_path.clear(); 959 } 960 961 static bool validateRectForCanvas(float& x, float& y, float& width, float& height) 962 { 963 if (!std::isfinite(x) | !std::isfinite(y) | !std::isfinite(width) | !std::isfinite(height)) 964 return false; 965 966 if (!width && !height) 967 return false; 968 969 if (width < 0) { 970 width = -width; 971 x -= width; 972 } 973 974 if (height < 0) { 975 height = -height; 976 y -= height; 977 } 978 979 return true; 980 } 981 982 static bool isFullCanvasCompositeMode(CompositeOperator op) 983 { 984 // See 4.8.11.1.3 Compositing 985 // CompositeSourceAtop and CompositeDestinationOut are not listed here as the platforms already 986 // implement the specification's behavior. 987 return op == CompositeSourceIn || op == CompositeSourceOut || op == CompositeDestinationIn || op == CompositeDestinationAtop; 988 } 989 990 static WindRule parseWinding(const String& windingRuleString) 991 { 992 if (windingRuleString == "nonzero") 993 return RULE_NONZERO; 994 if (windingRuleString == "evenodd") 995 return RULE_EVENODD; 996 997 ASSERT_NOT_REACHED(); 998 return RULE_EVENODD; 999 } 1000 1001 void CanvasRenderingContext2D::fillInternal(const Path& path, const String& windingRuleString) 1002 { 1003 if (path.isEmpty()) { 1004 return; 1005 } 1006 GraphicsContext* c = drawingContext(); 1007 if (!c) { 1008 return; 1009 } 1010 if (!state().m_invertibleCTM) { 1011 return; 1012 } 1013 FloatRect clipBounds; 1014 if (!c->getTransformedClipBounds(&clipBounds)) { 1015 return; 1016 } 1017 1018 // If gradient size is zero, then paint nothing. 1019 Gradient* gradient = c->fillGradient(); 1020 if (gradient && gradient->isZeroSize()) { 1021 return; 1022 } 1023 1024 WindRule windRule = c->fillRule(); 1025 c->setFillRule(parseWinding(windingRuleString)); 1026 1027 if (isFullCanvasCompositeMode(state().m_globalComposite)) { 1028 fullCanvasCompositedFill(path); 1029 didDraw(clipBounds); 1030 } else if (state().m_globalComposite == CompositeCopy) { 1031 clearCanvas(); 1032 c->fillPath(path); 1033 didDraw(clipBounds); 1034 } else { 1035 FloatRect dirtyRect; 1036 if (computeDirtyRect(path.boundingRect(), clipBounds, &dirtyRect)) { 1037 c->fillPath(path); 1038 didDraw(dirtyRect); 1039 } 1040 } 1041 1042 c->setFillRule(windRule); 1043 } 1044 1045 void CanvasRenderingContext2D::fill(const String& windingRuleString) 1046 { 1047 fillInternal(m_path, windingRuleString); 1048 } 1049 1050 void CanvasRenderingContext2D::fill(Path2D* domPath, const String& windingRuleString) 1051 { 1052 fillInternal(domPath->path(), windingRuleString); 1053 } 1054 1055 void CanvasRenderingContext2D::strokeInternal(const Path& path) 1056 { 1057 if (path.isEmpty()) { 1058 return; 1059 } 1060 GraphicsContext* c = drawingContext(); 1061 if (!c) { 1062 return; 1063 } 1064 if (!state().m_invertibleCTM) { 1065 return; 1066 } 1067 FloatRect clipBounds; 1068 if (!c->getTransformedClipBounds(&clipBounds)) 1069 return; 1070 1071 // If gradient size is zero, then paint nothing. 1072 Gradient* gradient = c->strokeGradient(); 1073 if (gradient && gradient->isZeroSize()) { 1074 return; 1075 } 1076 1077 if (isFullCanvasCompositeMode(state().m_globalComposite)) { 1078 fullCanvasCompositedStroke(path); 1079 didDraw(clipBounds); 1080 } else if (state().m_globalComposite == CompositeCopy) { 1081 clearCanvas(); 1082 c->strokePath(path); 1083 didDraw(clipBounds); 1084 } else { 1085 FloatRect bounds = path.boundingRect(); 1086 inflateStrokeRect(bounds); 1087 FloatRect dirtyRect; 1088 if (computeDirtyRect(bounds, clipBounds, &dirtyRect)) { 1089 c->strokePath(path); 1090 didDraw(dirtyRect); 1091 } 1092 } 1093 } 1094 1095 void CanvasRenderingContext2D::stroke() 1096 { 1097 strokeInternal(m_path); 1098 } 1099 1100 void CanvasRenderingContext2D::stroke(Path2D* domPath) 1101 { 1102 strokeInternal(domPath->path()); 1103 } 1104 1105 void CanvasRenderingContext2D::clipInternal(const Path& path, const String& windingRuleString) 1106 { 1107 GraphicsContext* c = drawingContext(); 1108 if (!c) { 1109 return; 1110 } 1111 if (!state().m_invertibleCTM) { 1112 return; 1113 } 1114 1115 realizeSaves(c); 1116 c->canvasClip(path, parseWinding(windingRuleString)); 1117 modifiableState().m_hasClip = true; 1118 } 1119 1120 void CanvasRenderingContext2D::clip(const String& windingRuleString) 1121 { 1122 clipInternal(m_path, windingRuleString); 1123 } 1124 1125 void CanvasRenderingContext2D::clip(Path2D* domPath, const String& windingRuleString) 1126 { 1127 clipInternal(domPath->path(), windingRuleString); 1128 } 1129 1130 bool CanvasRenderingContext2D::isPointInPath(const float x, const float y, const String& windingRuleString) 1131 { 1132 return isPointInPathInternal(m_path, x, y, windingRuleString); 1133 } 1134 1135 bool CanvasRenderingContext2D::isPointInPath(Path2D* domPath, const float x, const float y, const String& windingRuleString) 1136 { 1137 return isPointInPathInternal(domPath->path(), x, y, windingRuleString); 1138 } 1139 1140 bool CanvasRenderingContext2D::isPointInPathInternal(const Path& path, const float x, const float y, const String& windingRuleString) 1141 { 1142 GraphicsContext* c = drawingContext(); 1143 if (!c) 1144 return false; 1145 if (!state().m_invertibleCTM) 1146 return false; 1147 1148 FloatPoint point(x, y); 1149 AffineTransform ctm = state().m_transform; 1150 FloatPoint transformedPoint = ctm.inverse().mapPoint(point); 1151 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y())) 1152 return false; 1153 1154 return path.contains(transformedPoint, parseWinding(windingRuleString)); 1155 } 1156 1157 bool CanvasRenderingContext2D::isPointInStroke(const float x, const float y) 1158 { 1159 return isPointInStrokeInternal(m_path, x, y); 1160 } 1161 1162 bool CanvasRenderingContext2D::isPointInStroke(Path2D* domPath, const float x, const float y) 1163 { 1164 return isPointInStrokeInternal(domPath->path(), x, y); 1165 } 1166 1167 bool CanvasRenderingContext2D::isPointInStrokeInternal(const Path& path, const float x, const float y) 1168 { 1169 GraphicsContext* c = drawingContext(); 1170 if (!c) 1171 return false; 1172 if (!state().m_invertibleCTM) 1173 return false; 1174 1175 FloatPoint point(x, y); 1176 AffineTransform ctm = state().m_transform; 1177 FloatPoint transformedPoint = ctm.inverse().mapPoint(point); 1178 if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y())) 1179 return false; 1180 1181 StrokeData strokeData; 1182 strokeData.setThickness(lineWidth()); 1183 strokeData.setLineCap(getLineCap()); 1184 strokeData.setLineJoin(getLineJoin()); 1185 strokeData.setMiterLimit(miterLimit()); 1186 strokeData.setLineDash(getLineDash(), lineDashOffset()); 1187 return path.strokeContains(transformedPoint, strokeData); 1188 } 1189 1190 void CanvasRenderingContext2D::scrollPathIntoView() 1191 { 1192 scrollPathIntoViewInternal(m_path); 1193 } 1194 1195 void CanvasRenderingContext2D::scrollPathIntoView(Path2D* path2d) 1196 { 1197 scrollPathIntoViewInternal(path2d->path()); 1198 } 1199 1200 void CanvasRenderingContext2D::scrollPathIntoViewInternal(const Path& path) 1201 { 1202 RenderObject* renderer = canvas()->renderer(); 1203 RenderBox* renderBox = canvas()->renderBox(); 1204 if (!renderer || !renderBox || !state().m_invertibleCTM || path.isEmpty()) 1205 return; 1206 1207 canvas()->document().updateLayoutIgnorePendingStylesheets(); 1208 1209 // Apply transformation and get the bounding rect 1210 Path transformedPath = path; 1211 transformedPath.transform(state().m_transform); 1212 FloatRect boundingRect = transformedPath.boundingRect(); 1213 1214 // Offset by the canvas rect 1215 LayoutRect pathRect(boundingRect); 1216 IntRect canvasRect = renderBox->absoluteContentBox(); 1217 pathRect.move(canvasRect.x(), canvasRect.y()); 1218 1219 renderer->scrollRectToVisible( 1220 pathRect, ScrollAlignment::alignCenterAlways, ScrollAlignment::alignTopAlways); 1221 1222 // TODO: should implement "inform the user" that the caret and/or 1223 // selection the specified rectangle of the canvas. See http://crbug.com/357987 1224 } 1225 1226 void CanvasRenderingContext2D::clearRect(float x, float y, float width, float height) 1227 { 1228 if (!validateRectForCanvas(x, y, width, height)) 1229 return; 1230 GraphicsContext* context = drawingContext(); 1231 if (!context) 1232 return; 1233 if (!state().m_invertibleCTM) 1234 return; 1235 FloatRect rect(x, y, width, height); 1236 1237 FloatRect dirtyRect; 1238 if (!computeDirtyRect(rect, &dirtyRect)) 1239 return; 1240 1241 bool saved = false; 1242 if (shouldDrawShadows()) { 1243 context->save(); 1244 saved = true; 1245 context->clearShadow(); 1246 } 1247 if (state().m_globalAlpha != 1) { 1248 if (!saved) { 1249 context->save(); 1250 saved = true; 1251 } 1252 context->setAlphaAsFloat(1); 1253 } 1254 if (state().m_globalComposite != CompositeSourceOver) { 1255 if (!saved) { 1256 context->save(); 1257 saved = true; 1258 } 1259 context->setCompositeOperation(CompositeSourceOver); 1260 } 1261 context->clearRect(rect); 1262 if (m_hitRegionManager) 1263 m_hitRegionManager->removeHitRegionsInRect(rect, state().m_transform); 1264 if (saved) 1265 context->restore(); 1266 1267 validateStateStack(); 1268 didDraw(dirtyRect); 1269 } 1270 1271 void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height) 1272 { 1273 if (!validateRectForCanvas(x, y, width, height)) 1274 return; 1275 1276 GraphicsContext* c = drawingContext(); 1277 if (!c) 1278 return; 1279 if (!state().m_invertibleCTM) 1280 return; 1281 FloatRect clipBounds; 1282 if (!c->getTransformedClipBounds(&clipBounds)) 1283 return; 1284 1285 // from the HTML5 Canvas spec: 1286 // If x0 = x1 and y0 = y1, then the linear gradient must paint nothing 1287 // If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint nothing 1288 Gradient* gradient = c->fillGradient(); 1289 if (gradient && gradient->isZeroSize()) 1290 return; 1291 1292 FloatRect rect(x, y, width, height); 1293 if (rectContainsTransformedRect(rect, clipBounds)) { 1294 c->fillRect(rect); 1295 didDraw(clipBounds); 1296 } else if (isFullCanvasCompositeMode(state().m_globalComposite)) { 1297 fullCanvasCompositedFill(rect); 1298 didDraw(clipBounds); 1299 } else if (state().m_globalComposite == CompositeCopy) { 1300 clearCanvas(); 1301 c->fillRect(rect); 1302 didDraw(clipBounds); 1303 } else { 1304 FloatRect dirtyRect; 1305 if (computeDirtyRect(rect, clipBounds, &dirtyRect)) { 1306 c->fillRect(rect); 1307 didDraw(dirtyRect); 1308 } 1309 } 1310 } 1311 1312 void CanvasRenderingContext2D::strokeRect(float x, float y, float width, float height) 1313 { 1314 if (!validateRectForCanvas(x, y, width, height)) 1315 return; 1316 1317 if (!(state().m_lineWidth >= 0)) 1318 return; 1319 1320 GraphicsContext* c = drawingContext(); 1321 if (!c) 1322 return; 1323 if (!state().m_invertibleCTM) 1324 return; 1325 FloatRect clipBounds; 1326 if (!c->getTransformedClipBounds(&clipBounds)) 1327 return; 1328 1329 // If gradient size is zero, then paint nothing. 1330 Gradient* gradient = c->strokeGradient(); 1331 if (gradient && gradient->isZeroSize()) 1332 return; 1333 1334 FloatRect rect(x, y, width, height); 1335 1336 if (isFullCanvasCompositeMode(state().m_globalComposite)) { 1337 fullCanvasCompositedStroke(rect); 1338 didDraw(clipBounds); 1339 } else if (state().m_globalComposite == CompositeCopy) { 1340 clearCanvas(); 1341 c->strokeRect(rect); 1342 didDraw(clipBounds); 1343 } else { 1344 FloatRect boundingRect = rect; 1345 boundingRect.inflate(state().m_lineWidth / 2); 1346 FloatRect dirtyRect; 1347 if (computeDirtyRect(boundingRect, clipBounds, &dirtyRect)) { 1348 c->strokeRect(rect); 1349 didDraw(dirtyRect); 1350 } 1351 } 1352 } 1353 1354 void CanvasRenderingContext2D::setShadow(float width, float height, float blur) 1355 { 1356 setShadow(FloatSize(width, height), blur, Color::transparent); 1357 } 1358 1359 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, const String& color) 1360 { 1361 RGBA32 rgba; 1362 if (!parseColorOrCurrentColor(rgba, color, canvas())) 1363 return; 1364 setShadow(FloatSize(width, height), blur, rgba); 1365 } 1366 1367 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float grayLevel) 1368 { 1369 setShadow(FloatSize(width, height), blur, makeRGBA32FromFloats(grayLevel, grayLevel, grayLevel, 1)); 1370 } 1371 1372 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, const String& color, float alpha) 1373 { 1374 RGBA32 rgba; 1375 if (!parseColorOrCurrentColor(rgba, color, canvas())) 1376 return; 1377 setShadow(FloatSize(width, height), blur, colorWithOverrideAlpha(rgba, alpha)); 1378 } 1379 1380 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float grayLevel, float alpha) 1381 { 1382 setShadow(FloatSize(width, height), blur, makeRGBA32FromFloats(grayLevel, grayLevel, grayLevel, alpha)); 1383 } 1384 1385 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float r, float g, float b, float a) 1386 { 1387 setShadow(FloatSize(width, height), blur, makeRGBA32FromFloats(r, g, b, a)); 1388 } 1389 1390 void CanvasRenderingContext2D::setShadow(float width, float height, float blur, float c, float m, float y, float k, float a) 1391 { 1392 setShadow(FloatSize(width, height), blur, makeRGBAFromCMYKA(c, m, y, k, a)); 1393 } 1394 1395 void CanvasRenderingContext2D::clearShadow() 1396 { 1397 setShadow(FloatSize(), 0, Color::transparent); 1398 } 1399 1400 void CanvasRenderingContext2D::setShadow(const FloatSize& offset, float blur, RGBA32 color) 1401 { 1402 if (state().m_shadowOffset == offset && state().m_shadowBlur == blur && state().m_shadowColor == color) 1403 return; 1404 bool wasDrawingShadows = shouldDrawShadows(); 1405 realizeSaves(0); 1406 modifiableState().m_shadowOffset = offset; 1407 modifiableState().m_shadowBlur = blur; 1408 modifiableState().m_shadowColor = color; 1409 if (!wasDrawingShadows && !shouldDrawShadows()) 1410 return; 1411 applyShadow(); 1412 } 1413 1414 void CanvasRenderingContext2D::applyShadow() 1415 { 1416 GraphicsContext* c = drawingContext(); 1417 if (!c) 1418 return; 1419 1420 if (shouldDrawShadows()) { 1421 c->setShadow(state().m_shadowOffset, state().m_shadowBlur, state().m_shadowColor, 1422 DrawLooperBuilder::ShadowIgnoresTransforms); 1423 } else { 1424 c->clearShadow(); 1425 } 1426 } 1427 1428 bool CanvasRenderingContext2D::shouldDrawShadows() const 1429 { 1430 return alphaChannel(state().m_shadowColor) && (state().m_shadowBlur || !state().m_shadowOffset.isZero()); 1431 } 1432 1433 static inline FloatRect normalizeRect(const FloatRect& rect) 1434 { 1435 return FloatRect(std::min(rect.x(), rect.maxX()), 1436 std::min(rect.y(), rect.maxY()), 1437 std::max(rect.width(), -rect.width()), 1438 std::max(rect.height(), -rect.height())); 1439 } 1440 1441 static inline void clipRectsToImageRect(const FloatRect& imageRect, FloatRect* srcRect, FloatRect* dstRect) 1442 { 1443 if (imageRect.contains(*srcRect)) 1444 return; 1445 1446 // Compute the src to dst transform 1447 FloatSize scale(dstRect->size().width() / srcRect->size().width(), dstRect->size().height() / srcRect->size().height()); 1448 FloatPoint scaledSrcLocation = srcRect->location(); 1449 scaledSrcLocation.scale(scale.width(), scale.height()); 1450 FloatSize offset = dstRect->location() - scaledSrcLocation; 1451 1452 srcRect->intersect(imageRect); 1453 1454 // To clip the destination rectangle in the same proportion, transform the clipped src rect 1455 *dstRect = *srcRect; 1456 dstRect->scale(scale.width(), scale.height()); 1457 dstRect->move(offset); 1458 } 1459 1460 void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, float x, float y, ExceptionState& exceptionState) 1461 { 1462 FloatSize destRectSize = imageSource->defaultDestinationSize(); 1463 drawImage(imageSource, x, y, destRectSize.width(), destRectSize.height(), exceptionState); 1464 } 1465 1466 void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, 1467 float x, float y, float width, float height, ExceptionState& exceptionState) 1468 { 1469 FloatSize sourceRectSize = imageSource->sourceSize(); 1470 drawImage(imageSource, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, width, height, exceptionState); 1471 } 1472 1473 void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, 1474 float sx, float sy, float sw, float sh, 1475 float dx, float dy, float dw, float dh, ExceptionState& exceptionState) 1476 { 1477 GraphicsContext* c = drawingContext(); // Do not exit yet if !c because we may need to throw exceptions first 1478 CompositeOperator op = c ? c->compositeOperation() : CompositeSourceOver; 1479 blink::WebBlendMode blendMode = c ? c->blendModeOperation() : blink::WebBlendModeNormal; 1480 drawImageInternal(imageSource, sx, sy, sw, sh, dx, dy, dw, dh, exceptionState, op, blendMode, c); 1481 } 1482 1483 void CanvasRenderingContext2D::drawImageInternal(CanvasImageSource* imageSource, 1484 float sx, float sy, float sw, float sh, 1485 float dx, float dy, float dw, float dh, ExceptionState& exceptionState, 1486 CompositeOperator op, blink::WebBlendMode blendMode, GraphicsContext* c) 1487 { 1488 RefPtr<Image> image; 1489 SourceImageStatus sourceImageStatus = InvalidSourceImageStatus; 1490 if (!imageSource->isVideoElement()) { 1491 SourceImageMode mode = canvas() == imageSource ? CopySourceImageIfVolatile : DontCopySourceImage; // Thunking for == 1492 image = imageSource->getSourceImageForCanvas(mode, &sourceImageStatus); 1493 if (sourceImageStatus == UndecodableSourceImageStatus) 1494 exceptionState.throwDOMException(InvalidStateError, "The HTMLImageElement provided is in the 'broken' state."); 1495 if (!image || !image->width() || !image->height()) 1496 return; 1497 } 1498 1499 if (!c) 1500 c = drawingContext(); 1501 if (!c) 1502 return; 1503 1504 if (!state().m_invertibleCTM) 1505 return; 1506 1507 if (!std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dw) || !std::isfinite(dh) 1508 || !std::isfinite(sx) || !std::isfinite(sy) || !std::isfinite(sw) || !std::isfinite(sh) 1509 || !dw || !dh || !sw || !sh) 1510 return; 1511 1512 FloatRect clipBounds; 1513 if (!c->getTransformedClipBounds(&clipBounds)) 1514 return; 1515 1516 FloatRect srcRect = normalizeRect(FloatRect(sx, sy, sw, sh)); 1517 FloatRect dstRect = normalizeRect(FloatRect(dx, dy, dw, dh)); 1518 1519 clipRectsToImageRect(FloatRect(FloatPoint(), imageSource->sourceSize()), &srcRect, &dstRect); 1520 1521 imageSource->adjustDrawRects(&srcRect, &dstRect); 1522 1523 if (srcRect.isEmpty()) 1524 return; 1525 1526 FloatRect dirtyRect = clipBounds; 1527 if (imageSource->isVideoElement()) { 1528 // TODO(dshwang): unify video code into below code to composite correctly; crbug.com/407079 1529 drawVideo(static_cast<HTMLVideoElement*>(imageSource), srcRect, dstRect); 1530 computeDirtyRect(dstRect, clipBounds, &dirtyRect); 1531 } else { 1532 if (rectContainsTransformedRect(dstRect, clipBounds)) { 1533 c->drawImage(image.get(), dstRect, srcRect, op, blendMode); 1534 } else if (isFullCanvasCompositeMode(op)) { 1535 fullCanvasCompositedDrawImage(image.get(), dstRect, srcRect, op); 1536 } else if (op == CompositeCopy) { 1537 clearCanvas(); 1538 c->drawImage(image.get(), dstRect, srcRect, op, blendMode); 1539 } else { 1540 FloatRect dirtyRect; 1541 computeDirtyRect(dstRect, clipBounds, &dirtyRect); 1542 c->drawImage(image.get(), dstRect, srcRect, op, blendMode); 1543 } 1544 1545 if (sourceImageStatus == ExternalSourceImageStatus && isAccelerated() && canvas()->buffer()) 1546 canvas()->buffer()->flush(); 1547 } 1548 1549 if (canvas()->originClean() && wouldTaintOrigin(imageSource)) 1550 canvas()->setOriginTainted(); 1551 1552 didDraw(dirtyRect); 1553 } 1554 1555 void CanvasRenderingContext2D::drawVideo(HTMLVideoElement* video, FloatRect srcRect, FloatRect dstRect) 1556 { 1557 GraphicsContext* c = drawingContext(); 1558 GraphicsContextStateSaver stateSaver(*c); 1559 c->clip(dstRect); 1560 c->translate(dstRect.x(), dstRect.y()); 1561 c->scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height()); 1562 c->translate(-srcRect.x(), -srcRect.y()); 1563 video->paintCurrentFrameInContext(c, IntRect(IntPoint(), IntSize(video->videoWidth(), video->videoHeight()))); 1564 stateSaver.restore(); 1565 validateStateStack(); 1566 } 1567 1568 void CanvasRenderingContext2D::drawImageFromRect(HTMLImageElement* image, 1569 float sx, float sy, float sw, float sh, 1570 float dx, float dy, float dw, float dh, 1571 const String& compositeOperation) 1572 { 1573 if (!image) 1574 return; 1575 CompositeOperator op; 1576 blink::WebBlendMode blendOp = blink::WebBlendModeNormal; 1577 if (!parseCompositeAndBlendOperator(compositeOperation, op, blendOp) || blendOp != blink::WebBlendModeNormal) 1578 op = CompositeSourceOver; 1579 1580 drawImageInternal(image, sx, sy, sw, sh, dx, dy, dw, dh, IGNORE_EXCEPTION, op, blendOp); 1581 } 1582 1583 void CanvasRenderingContext2D::setAlpha(float alpha) 1584 { 1585 setGlobalAlpha(alpha); 1586 } 1587 1588 void CanvasRenderingContext2D::setCompositeOperation(const String& operation) 1589 { 1590 setGlobalCompositeOperation(operation); 1591 } 1592 1593 void CanvasRenderingContext2D::clearCanvas() 1594 { 1595 FloatRect canvasRect(0, 0, canvas()->width(), canvas()->height()); 1596 GraphicsContext* c = drawingContext(); 1597 if (!c) 1598 return; 1599 1600 c->save(); 1601 c->setCTM(canvas()->baseTransform()); 1602 c->clearRect(canvasRect); 1603 c->restore(); 1604 } 1605 1606 bool CanvasRenderingContext2D::rectContainsTransformedRect(const FloatRect& rect, const FloatRect& transformedRect) const 1607 { 1608 FloatQuad quad(rect); 1609 FloatQuad transformedQuad(transformedRect); 1610 return state().m_transform.mapQuad(quad).containsQuad(transformedQuad); 1611 } 1612 1613 static void drawImageToContext(Image* image, GraphicsContext* context, const FloatRect& dest, const FloatRect& src, CompositeOperator op) 1614 { 1615 context->drawImage(image, dest, src, op); 1616 } 1617 1618 template<class T> void CanvasRenderingContext2D::fullCanvasCompositedDrawImage(T* image, const FloatRect& dest, const FloatRect& src, CompositeOperator op) 1619 { 1620 ASSERT(isFullCanvasCompositeMode(op)); 1621 1622 GraphicsContext* c = drawingContext(); 1623 c->beginLayer(1, op); 1624 drawImageToContext(image, c, dest, src, CompositeSourceOver); 1625 c->endLayer(); 1626 } 1627 1628 static void fillPrimitive(const FloatRect& rect, GraphicsContext* context) 1629 { 1630 context->fillRect(rect); 1631 } 1632 1633 static void fillPrimitive(const Path& path, GraphicsContext* context) 1634 { 1635 context->fillPath(path); 1636 } 1637 1638 template<class T> void CanvasRenderingContext2D::fullCanvasCompositedFill(const T& area) 1639 { 1640 ASSERT(isFullCanvasCompositeMode(state().m_globalComposite)); 1641 1642 GraphicsContext* c = drawingContext(); 1643 ASSERT(c); 1644 c->beginLayer(1, state().m_globalComposite); 1645 CompositeOperator previousOperator = c->compositeOperation(); 1646 c->setCompositeOperation(CompositeSourceOver); 1647 fillPrimitive(area, c); 1648 c->setCompositeOperation(previousOperator); 1649 c->endLayer(); 1650 } 1651 1652 static void strokePrimitive(const FloatRect& rect, GraphicsContext* context) 1653 { 1654 context->strokeRect(rect); 1655 } 1656 1657 static void strokePrimitive(const Path& path, GraphicsContext* context) 1658 { 1659 context->strokePath(path); 1660 } 1661 1662 template<class T> void CanvasRenderingContext2D::fullCanvasCompositedStroke(const T& area) 1663 { 1664 ASSERT(isFullCanvasCompositeMode(state().m_globalComposite)); 1665 1666 GraphicsContext* c = drawingContext(); 1667 ASSERT(c); 1668 c->beginLayer(1, state().m_globalComposite); 1669 CompositeOperator previousOperator = c->compositeOperation(); 1670 c->setCompositeOperation(CompositeSourceOver); 1671 strokePrimitive(area, c); 1672 c->setCompositeOperation(previousOperator); 1673 c->endLayer(); 1674 } 1675 1676 PassRefPtrWillBeRawPtr<CanvasGradient> CanvasRenderingContext2D::createLinearGradient(float x0, float y0, float x1, float y1) 1677 { 1678 RefPtrWillBeRawPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0), FloatPoint(x1, y1)); 1679 return gradient.release(); 1680 } 1681 1682 PassRefPtrWillBeRawPtr<CanvasGradient> CanvasRenderingContext2D::createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1, ExceptionState& exceptionState) 1683 { 1684 if (r0 < 0 || r1 < 0) { 1685 exceptionState.throwDOMException(IndexSizeError, String::format("The %s provided is less than 0.", r0 < 0 ? "r0" : "r1")); 1686 return nullptr; 1687 } 1688 1689 RefPtrWillBeRawPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1); 1690 return gradient.release(); 1691 } 1692 1693 PassRefPtrWillBeRawPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(CanvasImageSource* imageSource, 1694 const String& repetitionType, ExceptionState& exceptionState) 1695 { 1696 Pattern::RepeatMode repeatMode = CanvasPattern::parseRepetitionType(repetitionType, exceptionState); 1697 if (exceptionState.hadException()) 1698 return nullptr; 1699 1700 SourceImageStatus status; 1701 RefPtr<Image> imageForRendering = imageSource->getSourceImageForCanvas(CopySourceImageIfVolatile, &status); 1702 1703 switch (status) { 1704 case NormalSourceImageStatus: 1705 break; 1706 case ZeroSizeCanvasSourceImageStatus: 1707 exceptionState.throwDOMException(InvalidStateError, String::format("The canvas %s is 0.", imageSource->sourceSize().width() ? "height" : "width")); 1708 return nullptr; 1709 case UndecodableSourceImageStatus: 1710 exceptionState.throwDOMException(InvalidStateError, "Source image is in the 'broken' state."); 1711 return nullptr; 1712 case InvalidSourceImageStatus: 1713 imageForRendering = Image::nullImage(); 1714 break; 1715 case IncompleteSourceImageStatus: 1716 return nullptr; 1717 default: 1718 case ExternalSourceImageStatus: // should not happen when mode is CopySourceImageIfVolatile 1719 ASSERT_NOT_REACHED(); 1720 return nullptr; 1721 } 1722 ASSERT(imageForRendering); 1723 1724 bool originClean = !wouldTaintOrigin(imageSource); 1725 1726 return CanvasPattern::create(imageForRendering.release(), repeatMode, originClean); 1727 } 1728 1729 bool CanvasRenderingContext2D::computeDirtyRect(const FloatRect& localRect, FloatRect* dirtyRect) 1730 { 1731 FloatRect clipBounds; 1732 if (!drawingContext()->getTransformedClipBounds(&clipBounds)) 1733 return false; 1734 return computeDirtyRect(localRect, clipBounds, dirtyRect); 1735 } 1736 1737 bool CanvasRenderingContext2D::computeDirtyRect(const FloatRect& localRect, const FloatRect& transformedClipBounds, FloatRect* dirtyRect) 1738 { 1739 FloatRect canvasRect = state().m_transform.mapRect(localRect); 1740 1741 if (alphaChannel(state().m_shadowColor)) { 1742 FloatRect shadowRect(canvasRect); 1743 shadowRect.move(state().m_shadowOffset); 1744 shadowRect.inflate(state().m_shadowBlur); 1745 canvasRect.unite(shadowRect); 1746 } 1747 1748 canvasRect.intersect(transformedClipBounds); 1749 if (canvasRect.isEmpty()) 1750 return false; 1751 1752 if (dirtyRect) 1753 *dirtyRect = canvasRect; 1754 1755 return true; 1756 } 1757 1758 void CanvasRenderingContext2D::didDraw(const FloatRect& dirtyRect) 1759 { 1760 if (dirtyRect.isEmpty()) 1761 return; 1762 1763 canvas()->didDraw(dirtyRect); 1764 } 1765 1766 GraphicsContext* CanvasRenderingContext2D::drawingContext() const 1767 { 1768 if (isContextLost()) 1769 return 0; 1770 return canvas()->drawingContext(); 1771 } 1772 1773 static PassRefPtrWillBeRawPtr<ImageData> createEmptyImageData(const IntSize& size) 1774 { 1775 if (RefPtrWillBeRawPtr<ImageData> data = ImageData::create(size)) { 1776 data->data()->zeroFill(); 1777 return data.release(); 1778 } 1779 1780 return nullptr; 1781 } 1782 1783 PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::createImageData(PassRefPtrWillBeRawPtr<ImageData> imageData) const 1784 { 1785 return createEmptyImageData(imageData->size()); 1786 } 1787 1788 PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::createImageData(float sw, float sh, ExceptionState& exceptionState) const 1789 { 1790 if (!sw || !sh) { 1791 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s is 0.", sw ? "height" : "width")); 1792 return nullptr; 1793 } 1794 1795 FloatSize logicalSize(fabs(sw), fabs(sh)); 1796 if (!logicalSize.isExpressibleAsIntSize()) 1797 return nullptr; 1798 1799 IntSize size = expandedIntSize(logicalSize); 1800 if (size.width() < 1) 1801 size.setWidth(1); 1802 if (size.height() < 1) 1803 size.setHeight(1); 1804 1805 return createEmptyImageData(size); 1806 } 1807 1808 PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::getImageData(float sx, float sy, float sw, float sh, ExceptionState& exceptionState) const 1809 { 1810 if (!canvas()->originClean()) 1811 exceptionState.throwSecurityError("The canvas has been tainted by cross-origin data."); 1812 else if (!sw || !sh) 1813 exceptionState.throwDOMException(IndexSizeError, String::format("The source %s is 0.", sw ? "height" : "width")); 1814 1815 if (exceptionState.hadException()) 1816 return nullptr; 1817 1818 if (sw < 0) { 1819 sx += sw; 1820 sw = -sw; 1821 } 1822 if (sh < 0) { 1823 sy += sh; 1824 sh = -sh; 1825 } 1826 1827 FloatRect logicalRect(sx, sy, sw, sh); 1828 if (logicalRect.width() < 1) 1829 logicalRect.setWidth(1); 1830 if (logicalRect.height() < 1) 1831 logicalRect.setHeight(1); 1832 if (!logicalRect.isExpressibleAsIntRect()) 1833 return nullptr; 1834 1835 IntRect imageDataRect = enclosingIntRect(logicalRect); 1836 ImageBuffer* buffer = canvas()->buffer(); 1837 if (!buffer || isContextLost()) 1838 return createEmptyImageData(imageDataRect.size()); 1839 1840 RefPtr<Uint8ClampedArray> byteArray = buffer->getImageData(Unmultiplied, imageDataRect); 1841 if (!byteArray) 1842 return nullptr; 1843 1844 return ImageData::create(imageDataRect.size(), byteArray.release()); 1845 } 1846 1847 void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy) 1848 { 1849 putImageData(data, dx, dy, 0, 0, data->width(), data->height()); 1850 } 1851 1852 void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight) 1853 { 1854 ImageBuffer* buffer = canvas()->buffer(); 1855 if (!buffer) 1856 return; 1857 1858 if (dirtyWidth < 0) { 1859 dirtyX += dirtyWidth; 1860 dirtyWidth = -dirtyWidth; 1861 } 1862 1863 if (dirtyHeight < 0) { 1864 dirtyY += dirtyHeight; 1865 dirtyHeight = -dirtyHeight; 1866 } 1867 1868 FloatRect clipRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 1869 clipRect.intersect(IntRect(0, 0, data->width(), data->height())); 1870 IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy)); 1871 IntRect destRect = enclosingIntRect(clipRect); 1872 destRect.move(destOffset); 1873 destRect.intersect(IntRect(IntPoint(), buffer->size())); 1874 if (destRect.isEmpty()) 1875 return; 1876 IntRect sourceRect(destRect); 1877 sourceRect.move(-destOffset); 1878 1879 buffer->putByteArray(Unmultiplied, data->data(), IntSize(data->width(), data->height()), sourceRect, IntPoint(destOffset)); 1880 1881 didDraw(destRect); 1882 } 1883 1884 String CanvasRenderingContext2D::font() const 1885 { 1886 if (!state().m_realizedFont) 1887 return defaultFont; 1888 1889 StringBuilder serializedFont; 1890 const FontDescription& fontDescription = state().m_font.fontDescription(); 1891 1892 if (fontDescription.style() == FontStyleItalic) 1893 serializedFont.appendLiteral("italic "); 1894 if (fontDescription.weight() == FontWeightBold) 1895 serializedFont.appendLiteral("bold "); 1896 if (fontDescription.variant() == FontVariantSmallCaps) 1897 serializedFont.appendLiteral("small-caps "); 1898 1899 serializedFont.appendNumber(fontDescription.computedPixelSize()); 1900 serializedFont.appendLiteral("px"); 1901 1902 const FontFamily& firstFontFamily = fontDescription.family(); 1903 for (const FontFamily* fontFamily = &firstFontFamily; fontFamily; fontFamily = fontFamily->next()) { 1904 if (fontFamily != &firstFontFamily) 1905 serializedFont.append(','); 1906 1907 // FIXME: We should append family directly to serializedFont rather than building a temporary string. 1908 String family = fontFamily->family(); 1909 if (family.startsWith("-webkit-")) 1910 family = family.substring(8); 1911 if (family.contains(' ')) 1912 family = "\"" + family + "\""; 1913 1914 serializedFont.append(' '); 1915 serializedFont.append(family); 1916 } 1917 1918 return serializedFont.toString(); 1919 } 1920 1921 void CanvasRenderingContext2D::setFont(const String& newFont) 1922 { 1923 // The style resolution required for rendering text is not available in frame-less documents. 1924 if (!canvas()->document().frame()) 1925 return; 1926 1927 MutableStylePropertyMap::iterator i = m_fetchedFonts.find(newFont); 1928 RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = i != m_fetchedFonts.end() ? i->value : nullptr; 1929 1930 if (!parsedStyle) { 1931 parsedStyle = MutableStylePropertySet::create(); 1932 CSSParserMode mode = m_usesCSSCompatibilityParseMode ? HTMLQuirksMode : HTMLStandardMode; 1933 CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, newFont, true, mode, 0); 1934 m_fetchedFonts.add(newFont, parsedStyle); 1935 } 1936 if (parsedStyle->isEmpty()) 1937 return; 1938 1939 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont); 1940 1941 // According to http://lists.w3.org/Archives/Public/public-html/2009Jul/0947.html, 1942 // the "inherit" and "initial" values must be ignored. 1943 if (fontValue == "inherit" || fontValue == "initial") 1944 return; 1945 1946 // The parse succeeded. 1947 String newFontSafeCopy(newFont); // Create a string copy since newFont can be deleted inside realizeSaves. 1948 realizeSaves(0); 1949 modifiableState().m_unparsedFont = newFontSafeCopy; 1950 1951 // Map the <canvas> font into the text style. If the font uses keywords like larger/smaller, these will work 1952 // relative to the canvas. 1953 RefPtr<RenderStyle> newStyle = RenderStyle::create(); 1954 canvas()->document().updateRenderTreeIfNeeded(); 1955 if (RenderStyle* computedStyle = canvas()->computedStyle()) { 1956 FontDescription elementFontDescription(computedStyle->fontDescription()); 1957 // Reset the computed size to avoid inheriting the zoom factor from the <canvas> element. 1958 elementFontDescription.setComputedSize(elementFontDescription.specifiedSize()); 1959 newStyle->setFontDescription(elementFontDescription); 1960 } else { 1961 FontFamily fontFamily; 1962 fontFamily.setFamily(defaultFontFamily); 1963 1964 FontDescription defaultFontDescription; 1965 defaultFontDescription.setFamily(fontFamily); 1966 defaultFontDescription.setSpecifiedSize(defaultFontSize); 1967 defaultFontDescription.setComputedSize(defaultFontSize); 1968 1969 newStyle->setFontDescription(defaultFontDescription); 1970 } 1971 1972 newStyle->font().update(newStyle->font().fontSelector()); 1973 1974 // Now map the font property longhands into the style. 1975 CSSPropertyValue properties[] = { 1976 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle), 1977 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle), 1978 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle), 1979 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle), 1980 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle), 1981 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle), 1982 }; 1983 1984 StyleResolver& styleResolver = canvas()->document().ensureStyleResolver(); 1985 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), newStyle.get()); 1986 1987 #if !ENABLE(OILPAN) 1988 if (state().m_realizedFont) 1989 static_cast<CSSFontSelector*>(state().m_font.fontSelector())->unregisterForInvalidationCallbacks(&modifiableState()); 1990 #endif 1991 modifiableState().m_font = newStyle->font(); 1992 modifiableState().m_font.update(canvas()->document().styleEngine()->fontSelector()); 1993 modifiableState().m_realizedFont = true; 1994 canvas()->document().styleEngine()->fontSelector()->registerForInvalidationCallbacks(&modifiableState()); 1995 } 1996 1997 String CanvasRenderingContext2D::textAlign() const 1998 { 1999 return textAlignName(state().m_textAlign); 2000 } 2001 2002 void CanvasRenderingContext2D::setTextAlign(const String& s) 2003 { 2004 TextAlign align; 2005 if (!parseTextAlign(s, align)) 2006 return; 2007 if (state().m_textAlign == align) 2008 return; 2009 realizeSaves(0); 2010 modifiableState().m_textAlign = align; 2011 } 2012 2013 String CanvasRenderingContext2D::textBaseline() const 2014 { 2015 return textBaselineName(state().m_textBaseline); 2016 } 2017 2018 void CanvasRenderingContext2D::setTextBaseline(const String& s) 2019 { 2020 TextBaseline baseline; 2021 if (!parseTextBaseline(s, baseline)) 2022 return; 2023 if (state().m_textBaseline == baseline) 2024 return; 2025 realizeSaves(0); 2026 modifiableState().m_textBaseline = baseline; 2027 } 2028 2029 inline TextDirection CanvasRenderingContext2D::toTextDirection(Direction direction, RenderStyle** computedStyle) const 2030 { 2031 RenderStyle* style = (computedStyle || direction == DirectionInherit) ? canvas()->computedStyle() : nullptr; 2032 if (computedStyle) 2033 *computedStyle = style; 2034 switch (direction) { 2035 case DirectionInherit: 2036 return style ? style->direction() : LTR; 2037 case DirectionRTL: 2038 return RTL; 2039 case DirectionLTR: 2040 return LTR; 2041 } 2042 ASSERT_NOT_REACHED(); 2043 return LTR; 2044 } 2045 2046 String CanvasRenderingContext2D::direction() const 2047 { 2048 if (state().m_direction == DirectionInherit) 2049 canvas()->document().updateRenderTreeIfNeeded(); 2050 return toTextDirection(state().m_direction) == RTL ? rtl : ltr; 2051 } 2052 2053 void CanvasRenderingContext2D::setDirection(const String& directionString) 2054 { 2055 Direction direction; 2056 if (directionString == inherit) 2057 direction = DirectionInherit; 2058 else if (directionString == rtl) 2059 direction = DirectionRTL; 2060 else if (directionString == ltr) 2061 direction = DirectionLTR; 2062 else 2063 return; 2064 2065 if (state().m_direction == direction) 2066 return; 2067 2068 realizeSaves(0); 2069 modifiableState().m_direction = direction; 2070 } 2071 2072 void CanvasRenderingContext2D::fillText(const String& text, float x, float y) 2073 { 2074 drawTextInternal(text, x, y, true); 2075 } 2076 2077 void CanvasRenderingContext2D::fillText(const String& text, float x, float y, float maxWidth) 2078 { 2079 drawTextInternal(text, x, y, true, maxWidth, true); 2080 } 2081 2082 void CanvasRenderingContext2D::strokeText(const String& text, float x, float y) 2083 { 2084 drawTextInternal(text, x, y, false); 2085 } 2086 2087 void CanvasRenderingContext2D::strokeText(const String& text, float x, float y, float maxWidth) 2088 { 2089 drawTextInternal(text, x, y, false, maxWidth, true); 2090 } 2091 2092 PassRefPtrWillBeRawPtr<TextMetrics> CanvasRenderingContext2D::measureText(const String& text) 2093 { 2094 RefPtrWillBeRawPtr<TextMetrics> metrics = TextMetrics::create(); 2095 2096 // The style resolution required for rendering text is not available in frame-less documents. 2097 if (!canvas()->document().frame()) 2098 return metrics.release(); 2099 2100 FontCachePurgePreventer fontCachePurgePreventer; 2101 canvas()->document().updateRenderTreeIfNeeded(); 2102 const Font& font = accessFont(); 2103 const TextRun textRun(text, 0, 0, TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, LTR, false, true, true); 2104 FloatRect textBounds = font.selectionRectForText(textRun, FloatPoint(), font.fontDescription().computedSize(), 0, -1, true); 2105 2106 // x direction 2107 metrics->setWidth(font.width(textRun)); 2108 metrics->setActualBoundingBoxLeft(-textBounds.x()); 2109 metrics->setActualBoundingBoxRight(textBounds.maxX()); 2110 2111 // y direction 2112 const FontMetrics& fontMetrics = font.fontMetrics(); 2113 const float ascent = fontMetrics.floatAscent(); 2114 const float descent = fontMetrics.floatDescent(); 2115 const float baselineY = getFontBaseline(fontMetrics); 2116 2117 metrics->setFontBoundingBoxAscent(ascent - baselineY); 2118 metrics->setFontBoundingBoxDescent(descent + baselineY); 2119 metrics->setActualBoundingBoxAscent(-textBounds.y() - baselineY); 2120 metrics->setActualBoundingBoxDescent(textBounds.maxY() + baselineY); 2121 2122 // Note : top/bottom and ascend/descend are currently the same, so there's no difference 2123 // between the EM box's top and bottom and the font's ascend and descend 2124 metrics->setEmHeightAscent(0); 2125 metrics->setEmHeightDescent(0); 2126 2127 metrics->setHangingBaseline(-0.8f * ascent + baselineY); 2128 metrics->setAlphabeticBaseline(baselineY); 2129 metrics->setIdeographicBaseline(descent + baselineY); 2130 return metrics.release(); 2131 } 2132 2133 void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, float y, bool fill, float maxWidth, bool useMaxWidth) 2134 { 2135 // The style resolution required for rendering text is not available in frame-less documents. 2136 if (!canvas()->document().frame()) 2137 return; 2138 2139 // accessFont needs the style to be up to date, but updating style can cause script to run, 2140 // (e.g. due to autofocus) which can free the GraphicsContext, so update style before grabbing 2141 // the GraphicsContext. 2142 canvas()->document().updateRenderTreeIfNeeded(); 2143 2144 GraphicsContext* c = drawingContext(); 2145 if (!c) 2146 return; 2147 if (!state().m_invertibleCTM) 2148 return; 2149 if (!std::isfinite(x) | !std::isfinite(y)) 2150 return; 2151 if (useMaxWidth && (!std::isfinite(maxWidth) || maxWidth <= 0)) 2152 return; 2153 2154 // If gradient size is zero, then paint nothing. 2155 Gradient* gradient = c->strokeGradient(); 2156 if (!fill && gradient && gradient->isZeroSize()) 2157 return; 2158 2159 gradient = c->fillGradient(); 2160 if (fill && gradient && gradient->isZeroSize()) 2161 return; 2162 2163 FontCachePurgePreventer fontCachePurgePreventer; 2164 2165 const Font& font = accessFont(); 2166 const FontMetrics& fontMetrics = font.fontMetrics(); 2167 2168 // FIXME: Need to turn off font smoothing. 2169 2170 RenderStyle* computedStyle; 2171 TextDirection direction = toTextDirection(state().m_direction, &computedStyle); 2172 bool isRTL = direction == RTL; 2173 bool override = computedStyle ? isOverride(computedStyle->unicodeBidi()) : false; 2174 2175 TextRun textRun(text, 0, 0, TextRun::AllowTrailingExpansion, direction, override, true, true); 2176 // Draw the item text at the correct point. 2177 FloatPoint location(x, y + getFontBaseline(fontMetrics)); 2178 float fontWidth = font.width(textRun); 2179 2180 useMaxWidth = (useMaxWidth && maxWidth < fontWidth); 2181 float width = useMaxWidth ? maxWidth : fontWidth; 2182 2183 TextAlign align = state().m_textAlign; 2184 if (align == StartTextAlign) 2185 align = isRTL ? RightTextAlign : LeftTextAlign; 2186 else if (align == EndTextAlign) 2187 align = isRTL ? LeftTextAlign : RightTextAlign; 2188 2189 switch (align) { 2190 case CenterTextAlign: 2191 location.setX(location.x() - width / 2); 2192 break; 2193 case RightTextAlign: 2194 location.setX(location.x() - width); 2195 break; 2196 default: 2197 break; 2198 } 2199 2200 // The slop built in to this mask rect matches the heuristic used in FontCGWin.cpp for GDI text. 2201 TextRunPaintInfo textRunPaintInfo(textRun); 2202 textRunPaintInfo.bounds = FloatRect(location.x() - fontMetrics.height() / 2, 2203 location.y() - fontMetrics.ascent() - fontMetrics.lineGap(), 2204 width + fontMetrics.height(), 2205 fontMetrics.lineSpacing()); 2206 if (!fill) 2207 inflateStrokeRect(textRunPaintInfo.bounds); 2208 2209 c->setTextDrawingMode(fill ? TextModeFill : TextModeStroke); 2210 2211 GraphicsContextStateSaver stateSaver(*c); 2212 if (useMaxWidth) { 2213 c->translate(location.x(), location.y()); 2214 // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work. 2215 c->scale((fontWidth > 0 ? (width / fontWidth) : 0), 1); 2216 location = FloatPoint(); 2217 } 2218 2219 FloatRect clipBounds; 2220 if (!c->getTransformedClipBounds(&clipBounds)) { 2221 return; 2222 } 2223 2224 if (isFullCanvasCompositeMode(state().m_globalComposite)) { 2225 c->beginLayer(1, state().m_globalComposite); 2226 CompositeOperator previousOperator = c->compositeOperation(); 2227 c->setCompositeOperation(CompositeSourceOver); 2228 c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady); 2229 c->setCompositeOperation(previousOperator); 2230 c->endLayer(); 2231 didDraw(clipBounds); 2232 } else if (state().m_globalComposite == CompositeCopy) { 2233 clearCanvas(); 2234 c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady); 2235 didDraw(clipBounds); 2236 } else { 2237 FloatRect dirtyRect; 2238 if (computeDirtyRect(textRunPaintInfo.bounds, clipBounds, &dirtyRect)) { 2239 c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady); 2240 didDraw(dirtyRect); 2241 } 2242 } 2243 } 2244 2245 void CanvasRenderingContext2D::inflateStrokeRect(FloatRect& rect) const 2246 { 2247 // Fast approximation of the stroke's bounding rect. 2248 // This yields a slightly oversized rect but is very fast 2249 // compared to Path::strokeBoundingRect(). 2250 static const float root2 = sqrtf(2); 2251 float delta = state().m_lineWidth / 2; 2252 if (state().m_lineJoin == MiterJoin) 2253 delta *= state().m_miterLimit; 2254 else if (state().m_lineCap == SquareCap) 2255 delta *= root2; 2256 2257 rect.inflate(delta); 2258 } 2259 2260 const Font& CanvasRenderingContext2D::accessFont() 2261 { 2262 // This needs style to be up to date, but can't assert so because drawTextInternal 2263 // can invalidate style before this is called (e.g. drawingContext invalidates style). 2264 if (!state().m_realizedFont) 2265 setFont(state().m_unparsedFont); 2266 return state().m_font; 2267 } 2268 2269 int CanvasRenderingContext2D::getFontBaseline(const FontMetrics& fontMetrics) const 2270 { 2271 switch (state().m_textBaseline) { 2272 case TopTextBaseline: 2273 return fontMetrics.ascent(); 2274 case HangingTextBaseline: 2275 // According to http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling 2276 // "FOP (Formatting Objects Processor) puts the hanging baseline at 80% of the ascender height" 2277 return (fontMetrics.ascent() * 4) / 5; 2278 case BottomTextBaseline: 2279 case IdeographicTextBaseline: 2280 return -fontMetrics.descent(); 2281 case MiddleTextBaseline: 2282 return -fontMetrics.descent() + fontMetrics.height() / 2; 2283 case AlphabeticTextBaseline: 2284 default: 2285 // Do nothing. 2286 break; 2287 } 2288 return 0; 2289 } 2290 2291 void CanvasRenderingContext2D::setIsHidden(bool hidden) 2292 { 2293 ImageBuffer* buffer = canvas()->buffer(); 2294 if (buffer) 2295 buffer->setIsHidden(hidden); 2296 } 2297 2298 blink::WebLayer* CanvasRenderingContext2D::platformLayer() const 2299 { 2300 return canvas()->buffer() ? canvas()->buffer()->platformLayer() : 0; 2301 } 2302 2303 bool CanvasRenderingContext2D::imageSmoothingEnabled() const 2304 { 2305 return state().m_imageSmoothingEnabled; 2306 } 2307 2308 void CanvasRenderingContext2D::setImageSmoothingEnabled(bool enabled) 2309 { 2310 if (enabled == state().m_imageSmoothingEnabled) 2311 return; 2312 2313 GraphicsContext* c = drawingContext(); 2314 realizeSaves(c); 2315 modifiableState().m_imageSmoothingEnabled = enabled; 2316 if (c) 2317 c->setImageInterpolationQuality(enabled ? CanvasDefaultInterpolationQuality : InterpolationNone); 2318 } 2319 2320 PassRefPtrWillBeRawPtr<Canvas2DContextAttributes> CanvasRenderingContext2D::getContextAttributes() const 2321 { 2322 RefPtrWillBeRawPtr<Canvas2DContextAttributes> attributes = Canvas2DContextAttributes::create(); 2323 attributes->setAlpha(m_hasAlpha); 2324 return attributes.release(); 2325 } 2326 2327 void CanvasRenderingContext2D::drawFocusIfNeeded(Element* element) 2328 { 2329 drawFocusIfNeededInternal(m_path, element); 2330 } 2331 2332 void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D* path2d, Element* element) 2333 { 2334 drawFocusIfNeededInternal(path2d->path(), element); 2335 } 2336 2337 void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Element* element) 2338 { 2339 if (!focusRingCallIsValid(path, element)) 2340 return; 2341 2342 // Note: we need to check document->focusedElement() rather than just calling 2343 // element->focused(), because element->focused() isn't updated until after 2344 // focus events fire. 2345 if (element->document().focusedElement() == element) 2346 drawFocusRing(path); 2347 } 2348 2349 bool CanvasRenderingContext2D::focusRingCallIsValid(const Path& path, Element* element) 2350 { 2351 ASSERT(element); 2352 if (!state().m_invertibleCTM) 2353 return false; 2354 if (path.isEmpty()) 2355 return false; 2356 if (!element->isDescendantOf(canvas())) 2357 return false; 2358 2359 return true; 2360 } 2361 2362 void CanvasRenderingContext2D::drawFocusRing(const Path& path) 2363 { 2364 GraphicsContext* c = drawingContext(); 2365 if (!c) 2366 return; 2367 2368 // These should match the style defined in html.css. 2369 Color focusRingColor = RenderTheme::theme().focusRingColor(); 2370 const int focusRingWidth = 5; 2371 const int focusRingOutline = 0; 2372 2373 // We need to add focusRingWidth to dirtyRect. 2374 StrokeData strokeData; 2375 strokeData.setThickness(focusRingWidth); 2376 2377 FloatRect dirtyRect; 2378 if (!computeDirtyRect(path.strokeBoundingRect(strokeData), &dirtyRect)) 2379 return; 2380 2381 c->save(); 2382 c->setAlphaAsFloat(1.0); 2383 c->clearShadow(); 2384 c->setCompositeOperation(CompositeSourceOver, blink::WebBlendModeNormal); 2385 c->drawFocusRing(path, focusRingWidth, focusRingOutline, focusRingColor); 2386 c->restore(); 2387 validateStateStack(); 2388 didDraw(dirtyRect); 2389 } 2390 2391 void CanvasRenderingContext2D::addHitRegion(const HitRegionOptions& options, ExceptionState& exceptionState) 2392 { 2393 HitRegionOptionsInternal passOptions; 2394 passOptions.id = options.id(); 2395 passOptions.control = options.control(); 2396 if (passOptions.id.isEmpty() && !passOptions.control) { 2397 exceptionState.throwDOMException(NotSupportedError, "Both id and control are null."); 2398 return; 2399 } 2400 2401 Path hitRegionPath = options.hasPath() ? options.path()->path() : m_path; 2402 2403 FloatRect clipBounds; 2404 GraphicsContext* context = drawingContext(); 2405 2406 if (hitRegionPath.isEmpty() || !context || !state().m_invertibleCTM 2407 || !context->getTransformedClipBounds(&clipBounds)) { 2408 exceptionState.throwDOMException(NotSupportedError, "The specified path has no pixels."); 2409 return; 2410 } 2411 2412 hitRegionPath.transform(state().m_transform); 2413 2414 if (hasClip()) { 2415 // FIXME: The hit regions should take clipping region into account. 2416 // However, we have no way to get the region from canvas state stack by now. 2417 // See http://crbug.com/387057 2418 exceptionState.throwDOMException(NotSupportedError, "The specified path has no pixels."); 2419 return; 2420 } 2421 2422 passOptions.path = hitRegionPath; 2423 2424 if (options.fillRule() != "evenodd") 2425 passOptions.fillRule = RULE_NONZERO; 2426 else 2427 passOptions.fillRule = RULE_EVENODD; 2428 2429 addHitRegionInternal(passOptions, exceptionState); 2430 } 2431 2432 void CanvasRenderingContext2D::addHitRegionInternal(const HitRegionOptionsInternal& options, ExceptionState& exceptionState) 2433 { 2434 if (!m_hitRegionManager) 2435 m_hitRegionManager = HitRegionManager::create(); 2436 2437 // Remove previous region (with id or control) 2438 m_hitRegionManager->removeHitRegionById(options.id); 2439 m_hitRegionManager->removeHitRegionByControl(options.control.get()); 2440 2441 RefPtrWillBeRawPtr<HitRegion> hitRegion = HitRegion::create(options); 2442 hitRegion->updateAccessibility(canvas()); 2443 m_hitRegionManager->addHitRegion(hitRegion.release()); 2444 } 2445 2446 void CanvasRenderingContext2D::removeHitRegion(const String& id) 2447 { 2448 if (m_hitRegionManager) 2449 m_hitRegionManager->removeHitRegionById(id); 2450 } 2451 2452 void CanvasRenderingContext2D::clearHitRegions() 2453 { 2454 if (m_hitRegionManager) 2455 m_hitRegionManager->removeAllHitRegions(); 2456 } 2457 2458 HitRegion* CanvasRenderingContext2D::hitRegionAtPoint(const LayoutPoint& point) 2459 { 2460 if (m_hitRegionManager) 2461 return m_hitRegionManager->getHitRegionAtPoint(point); 2462 2463 return 0; 2464 } 2465 2466 unsigned CanvasRenderingContext2D::hitRegionsCount() const 2467 { 2468 if (m_hitRegionManager) 2469 return m_hitRegionManager->getHitRegionsCount(); 2470 2471 return 0; 2472 } 2473 2474 } // namespace blink 2475