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