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