#include "config.h"
#include "core/html/canvas/CanvasRenderingContext2D.h"
-#include "CSSPropertyNames.h"
-#include "bindings/v8/ExceptionMessages.h"
-#include "bindings/v8/ExceptionState.h"
-#include "bindings/v8/ExceptionStatePlaceholder.h"
-#include "core/accessibility/AXObjectCache.h"
+#include "bindings/core/v8/ExceptionMessages.h"
+#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ExceptionStatePlaceholder.h"
+#include "core/CSSPropertyNames.h"
#include "core/css/CSSFontSelector.h"
-#include "core/css/parser/BisonCSSParser.h"
#include "core/css/StylePropertySet.h"
+#include "core/css/parser/CSSParser.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/ExceptionCode.h"
+#include "core/dom/StyleEngine.h"
+#include "core/events/Event.h"
#include "core/fetch/ImageResource.h"
+#include "core/frame/ImageBitmap.h"
+#include "core/frame/Settings.h"
+#include "core/frame/UseCounter.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLMediaElement.h"
#include "core/html/canvas/CanvasGradient.h"
#include "core/html/canvas/CanvasPattern.h"
#include "core/html/canvas/CanvasStyle.h"
-#include "core/html/canvas/DOMPath.h"
-#include "core/frame/ImageBitmap.h"
+#include "core/html/canvas/Path2D.h"
#include "core/rendering/RenderImage.h"
#include "core/rendering/RenderLayer.h"
#include "core/rendering/RenderTheme.h"
#include "platform/fonts/FontCache.h"
#include "platform/geometry/FloatQuad.h"
+#include "platform/graphics/DrawLooperBuilder.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
-#include "platform/graphics/DrawLooper.h"
#include "platform/text/TextRun.h"
-#include "platform/weborigin/SecurityOrigin.h"
#include "wtf/CheckedArithmetic.h"
#include "wtf/MathExtras.h"
#include "wtf/OwnPtr.h"
#include "wtf/Uint8ClampedArray.h"
#include "wtf/text/StringBuilder.h"
-using namespace std;
-
-namespace WebCore {
+namespace blink {
static const int defaultFontSize = 10;
static const char defaultFontFamily[] = "sans-serif";
static const char defaultFont[] = "10px sans-serif";
+static const char inherit[] = "inherit";
+static const char rtl[] = "rtl";
+static const char ltr[] = "ltr";
+static const double TryRestoreContextInterval = 0.5;
+static const unsigned MaxTryRestoreContextAttempts = 4;
+static const unsigned FetchedFontsCacheLimit = 50;
+
+static bool contextLostRestoredEventsEnabled()
+{
+ return RuntimeEnabledFeatures::experimentalCanvasFeaturesEnabled();
+}
-CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, const Canvas2DContextAttributes* attrs, bool usesCSSCompatibilityParseMode)
+CanvasRenderingContext2D::CanvasRenderingContext2D(HTMLCanvasElement* canvas, const Canvas2DContextAttributes* attrs, Document& document)
: CanvasRenderingContext(canvas)
- , m_stateStack(1)
- , m_usesCSSCompatibilityParseMode(usesCSSCompatibilityParseMode)
+ , m_usesCSSCompatibilityParseMode(document.inQuirksMode())
+ , m_clipAntialiasing(GraphicsContext::NotAntiAliased)
, m_hasAlpha(!attrs || attrs->alpha())
+ , m_isContextLost(false)
+ , m_contextRestorable(true)
+ , m_storageMode(!attrs ? PersistentStorage : attrs->parsedStorage())
+ , m_tryRestoreContextAttemptCount(0)
+ , m_dispatchContextLostEventTimer(this, &CanvasRenderingContext2D::dispatchContextLostEvent)
+ , m_dispatchContextRestoredEventTimer(this, &CanvasRenderingContext2D::dispatchContextRestoredEvent)
+ , m_tryRestoreContextEventTimer(this, &CanvasRenderingContext2D::tryRestoreContextEvent)
{
- ScriptWrappable::init(this);
+ if (document.settings() && document.settings()->antialiasedClips2dCanvasEnabled())
+ m_clipAntialiasing = GraphicsContext::AntiAliased;
+ m_stateStack.append(adoptPtrWillBeNoop(new State()));
}
void CanvasRenderingContext2D::unwindStateStack()
{
- // Ensure that the state stack in the ImageBuffer's context
- // is cleared before destruction, to avoid assertions in the
- // GraphicsContext dtor.
if (size_t stackSize = m_stateStack.size()) {
if (GraphicsContext* context = canvas()->existingDrawingContext()) {
while (--stackSize)
CanvasRenderingContext2D::~CanvasRenderingContext2D()
{
-#if !ASSERT_DISABLED
- unwindStateStack();
+}
+
+void CanvasRenderingContext2D::validateStateStack()
+{
+#if ENABLE(ASSERT)
+ GraphicsContext* context = canvas()->existingDrawingContext();
+ if (context && !context->contextDisabled() && !m_isContextLost)
+ ASSERT(context->saveCount() == m_stateStack.size());
#endif
}
return context && context->isAccelerated();
}
+bool CanvasRenderingContext2D::isContextLost() const
+{
+ return m_isContextLost;
+}
+
+void CanvasRenderingContext2D::loseContext()
+{
+ if (m_isContextLost)
+ return;
+ m_isContextLost = true;
+ m_dispatchContextLostEventTimer.startOneShot(0, FROM_HERE);
+}
+
+void CanvasRenderingContext2D::restoreContext()
+{
+ if (!m_contextRestorable)
+ return;
+ // This code path is for restoring from an eviction
+ // Restoring from surface failure is handled internally
+ ASSERT(m_isContextLost && !canvas()->hasImageBuffer());
+
+ if (canvas()->buffer()) {
+ if (contextLostRestoredEventsEnabled()) {
+ m_dispatchContextRestoredEventTimer.startOneShot(0, FROM_HERE);
+ } else {
+ // legacy synchronous context restoration.
+ reset();
+ m_isContextLost = false;
+ }
+ }
+}
+
+void CanvasRenderingContext2D::trace(Visitor* visitor)
+{
+#if ENABLE(OILPAN)
+ visitor->trace(m_stateStack);
+ visitor->trace(m_fetchedFonts);
+ visitor->trace(m_hitRegionManager);
+#endif
+ CanvasRenderingContext::trace(visitor);
+}
+
+void CanvasRenderingContext2D::dispatchContextLostEvent(Timer<CanvasRenderingContext2D>*)
+{
+ if (contextLostRestoredEventsEnabled()) {
+ RefPtrWillBeRawPtr<Event> event = Event::createCancelable(EventTypeNames::contextlost);
+ canvas()->dispatchEvent(event);
+ if (event->defaultPrevented()) {
+ m_contextRestorable = false;
+ }
+ }
+
+ // If an image buffer is present, it means the context was not lost due to
+ // an eviction, but rather due to a surface failure (gpu context lost?)
+ if (m_contextRestorable && canvas()->hasImageBuffer()) {
+ m_tryRestoreContextAttemptCount = 0;
+ m_tryRestoreContextEventTimer.startRepeating(TryRestoreContextInterval, FROM_HERE);
+ }
+}
+
+void CanvasRenderingContext2D::tryRestoreContextEvent(Timer<CanvasRenderingContext2D>* timer)
+{
+ if (!m_isContextLost) {
+ // Canvas was already restored (possibly thanks to a resize), so stop trying.
+ m_tryRestoreContextEventTimer.stop();
+ return;
+ }
+ if (canvas()->hasImageBuffer() && canvas()->buffer()->restoreSurface()) {
+ m_tryRestoreContextEventTimer.stop();
+ dispatchContextRestoredEvent(nullptr);
+ }
+
+ if (++m_tryRestoreContextAttemptCount > MaxTryRestoreContextAttempts)
+ canvas()->discardImageBuffer();
+
+ if (!canvas()->hasImageBuffer()) {
+ // final attempt: allocate a brand new image buffer instead of restoring
+ timer->stop();
+ if (canvas()->buffer())
+ dispatchContextRestoredEvent(nullptr);
+ }
+}
+
+void CanvasRenderingContext2D::dispatchContextRestoredEvent(Timer<CanvasRenderingContext2D>*)
+{
+ if (!m_isContextLost)
+ return;
+ reset();
+ m_isContextLost = false;
+ if (contextLostRestoredEventsEnabled()) {
+ RefPtrWillBeRawPtr<Event> event(Event::create(EventTypeNames::contextrestored));
+ canvas()->dispatchEvent(event);
+ }
+}
+
void CanvasRenderingContext2D::reset()
{
+ validateStateStack();
unwindStateStack();
m_stateStack.resize(1);
- m_stateStack.first() = State();
+ m_stateStack.first() = adoptPtrWillBeNoop(new State());
m_path.clear();
+ validateStateStack();
}
// Important: Several of these properties are also stored in GraphicsContext's
, m_shadowColor(Color::transparent)
, m_globalAlpha(1)
, m_globalComposite(CompositeSourceOver)
- , m_globalBlend(blink::WebBlendModeNormal)
+ , m_globalBlend(WebBlendModeNormal)
, m_invertibleCTM(true)
, m_lineDashOffset(0)
, m_imageSmoothingEnabled(true)
, m_textAlign(StartTextAlign)
, m_textBaseline(AlphabeticTextBaseline)
+ , m_direction(DirectionInherit)
, m_unparsedFont(defaultFont)
, m_realizedFont(false)
+ , m_hasClip(false)
{
}
, m_imageSmoothingEnabled(other.m_imageSmoothingEnabled)
, m_textAlign(other.m_textAlign)
, m_textBaseline(other.m_textBaseline)
+ , m_direction(other.m_direction)
, m_unparsedFont(other.m_unparsedFont)
, m_font(other.m_font)
, m_realizedFont(other.m_realizedFont)
+ , m_hasClip(other.m_hasClip)
{
if (m_realizedFont)
static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalidationCallbacks(this);
if (this == &other)
return *this;
+#if !ENABLE(OILPAN)
if (m_realizedFont)
static_cast<CSSFontSelector*>(m_font.fontSelector())->unregisterForInvalidationCallbacks(this);
+#endif
m_unrealizedSaveCount = other.m_unrealizedSaveCount;
m_unparsedStrokeColor = other.m_unparsedStrokeColor;
m_imageSmoothingEnabled = other.m_imageSmoothingEnabled;
m_textAlign = other.m_textAlign;
m_textBaseline = other.m_textBaseline;
+ m_direction = other.m_direction;
m_unparsedFont = other.m_unparsedFont;
m_font = other.m_font;
m_realizedFont = other.m_realizedFont;
+ m_hasClip = other.m_hasClip;
if (m_realizedFont)
static_cast<CSSFontSelector*>(m_font.fontSelector())->registerForInvalidationCallbacks(this);
CanvasRenderingContext2D::State::~State()
{
+#if !ENABLE(OILPAN)
if (m_realizedFont)
static_cast<CSSFontSelector*>(m_font.fontSelector())->unregisterForInvalidationCallbacks(this);
+#endif
}
void CanvasRenderingContext2D::State::fontsNeedUpdate(CSSFontSelector* fontSelector)
m_font.update(fontSelector);
}
-void CanvasRenderingContext2D::realizeSaves()
+void CanvasRenderingContext2D::State::trace(Visitor* visitor)
{
+ visitor->trace(m_strokeStyle);
+ visitor->trace(m_fillStyle);
+ CSSFontSelectorClient::trace(visitor);
+}
+
+void CanvasRenderingContext2D::realizeSaves(GraphicsContext* context)
+{
+ validateStateStack();
if (state().m_unrealizedSaveCount) {
ASSERT(m_stateStack.size() >= 1);
// Reduce the current state's unrealized count by one now,
// to reflect the fact we are saving one state.
- m_stateStack.last().m_unrealizedSaveCount--;
- m_stateStack.append(state());
+ m_stateStack.last()->m_unrealizedSaveCount--;
+ m_stateStack.append(adoptPtrWillBeNoop(new State(state())));
// Set the new state's unrealized count to 0, because it has no outstanding saves.
// We need to do this explicitly because the copy constructor and operator= used
// by the Vector operations copy the unrealized count from the previous state (in
// turn necessary to support correct resizing and unwinding of the stack).
- m_stateStack.last().m_unrealizedSaveCount = 0;
- GraphicsContext* context = drawingContext();
+ m_stateStack.last()->m_unrealizedSaveCount = 0;
+ if (!context)
+ context = drawingContext();
if (context)
context->save();
+ validateStateStack();
}
}
void CanvasRenderingContext2D::restore()
{
+ validateStateStack();
if (state().m_unrealizedSaveCount) {
// We never realized the save, so just record that it was unnecessary.
- --m_stateStack.last().m_unrealizedSaveCount;
+ --m_stateStack.last()->m_unrealizedSaveCount;
return;
}
ASSERT(m_stateStack.size() >= 1);
GraphicsContext* c = drawingContext();
if (c)
c->restore();
+ validateStateStack();
}
CanvasStyle* CanvasRenderingContext2D::strokeStyle() const
return state().m_strokeStyle.get();
}
-void CanvasRenderingContext2D::setStrokeStyle(PassRefPtr<CanvasStyle> prpStyle)
+void CanvasRenderingContext2D::setStrokeStyle(PassRefPtrWillBeRawPtr<CanvasStyle> prpStyle)
{
- RefPtr<CanvasStyle> style = prpStyle;
+ RefPtrWillBeRawPtr<CanvasStyle> style = prpStyle;
if (!style)
return;
style = CanvasStyle::createFromRGBA(colorWithOverrideAlpha(currentColor(canvas()), style->overrideAlpha()));
else
style = CanvasStyle::createFromRGBA(currentColor(canvas()));
- } else
- checkOrigin(style->canvasPattern());
+ } else if (canvas()->originClean() && style->canvasPattern() && !style->canvasPattern()->originClean()) {
+ canvas()->setOriginTainted();
+ }
- realizeSaves();
- modifiableState().m_strokeStyle = style.release();
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_strokeStyle = style.release();
if (!c)
return;
state().m_strokeStyle->applyStrokeColor(c);
return state().m_fillStyle.get();
}
-void CanvasRenderingContext2D::setFillStyle(PassRefPtr<CanvasStyle> prpStyle)
+void CanvasRenderingContext2D::setFillStyle(PassRefPtrWillBeRawPtr<CanvasStyle> prpStyle)
{
- RefPtr<CanvasStyle> style = prpStyle;
+ RefPtrWillBeRawPtr<CanvasStyle> style = prpStyle;
if (!style)
return;
style = CanvasStyle::createFromRGBA(colorWithOverrideAlpha(currentColor(canvas()), style->overrideAlpha()));
else
style = CanvasStyle::createFromRGBA(currentColor(canvas()));
- } else
- checkOrigin(style->canvasPattern());
+ } else if (canvas()->originClean() && style->canvasPattern() && !style->canvasPattern()->originClean()) {
+ canvas()->setOriginTainted();
+ }
- realizeSaves();
- modifiableState().m_fillStyle = style.release();
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_fillStyle = style.release();
if (!c)
return;
state().m_fillStyle->applyFillColor(c);
void CanvasRenderingContext2D::setLineWidth(float width)
{
- if (!(std::isfinite(width) && width > 0))
+ if (!std::isfinite(width) || width <= 0)
return;
if (state().m_lineWidth == width)
return;
- realizeSaves();
- modifiableState().m_lineWidth = width;
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_lineWidth = width;
if (!c)
return;
c->setStrokeThickness(width);
return;
if (state().m_lineCap == cap)
return;
- realizeSaves();
- modifiableState().m_lineCap = cap;
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_lineCap = cap;
if (!c)
return;
c->setLineCap(cap);
return;
if (state().m_lineJoin == join)
return;
- realizeSaves();
- modifiableState().m_lineJoin = join;
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_lineJoin = join;
if (!c)
return;
c->setLineJoin(join);
void CanvasRenderingContext2D::setMiterLimit(float limit)
{
- if (!(std::isfinite(limit) && limit > 0))
+ if (!std::isfinite(limit) || limit <= 0)
return;
if (state().m_miterLimit == limit)
return;
- realizeSaves();
- modifiableState().m_miterLimit = limit;
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_miterLimit = limit;
if (!c)
return;
c->setMiterLimit(limit);
return;
if (state().m_shadowOffset.width() == x)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_shadowOffset.setWidth(x);
applyShadow();
}
return;
if (state().m_shadowOffset.height() == y)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_shadowOffset.setHeight(y);
applyShadow();
}
void CanvasRenderingContext2D::setShadowBlur(float blur)
{
- if (!(std::isfinite(blur) && blur >= 0))
+ if (!std::isfinite(blur) || blur < 0)
return;
if (state().m_shadowBlur == blur)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_shadowBlur = blur;
applyShadow();
}
return;
if (state().m_shadowColor == rgba)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_shadowColor = rgba;
applyShadow();
}
if (!lineDashSequenceIsValid(dash))
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_lineDash = dash;
// Spec requires the concatenation of two copies the dash list when the
// number of elements is odd
if (dash.size() % 2)
- modifiableState().m_lineDash.append(dash);
+ modifiableState().m_lineDash.appendVector(dash);
applyLineDash();
}
if (!std::isfinite(offset) || state().m_lineDashOffset == offset)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_lineDashOffset = offset;
applyLineDash();
}
return;
if (state().m_globalAlpha == alpha)
return;
- realizeSaves();
- modifiableState().m_globalAlpha = alpha;
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_globalAlpha = alpha;
if (!c)
return;
- c->setAlpha(alpha);
+ c->setAlphaAsFloat(alpha);
}
String CanvasRenderingContext2D::globalCompositeOperation() const
void CanvasRenderingContext2D::setGlobalCompositeOperation(const String& operation)
{
CompositeOperator op = CompositeSourceOver;
- blink::WebBlendMode blendMode = blink::WebBlendModeNormal;
+ WebBlendMode blendMode = WebBlendModeNormal;
if (!parseCompositeAndBlendOperator(operation, op, blendMode))
return;
+ // crbug.com/425628: Count the use of "darker" to remove it.
+ if (op == CompositePlusDarker)
+ UseCounter::count(canvas()->document(), UseCounter::CanvasRenderingContext2DCompositeOperationDarker);
if ((state().m_globalComposite == op) && (state().m_globalBlend == blendMode))
return;
- realizeSaves();
+ GraphicsContext* c = drawingContext();
+ realizeSaves(c);
modifiableState().m_globalComposite = op;
modifiableState().m_globalBlend = blendMode;
- GraphicsContext* c = drawingContext();
if (!c)
return;
c->setCompositeOperation(op, blendMode);
}
-void CanvasRenderingContext2D::setCurrentTransform(const SVGMatrix& matrix)
+void CanvasRenderingContext2D::setCurrentTransform(PassRefPtr<SVGMatrixTearOff> passMatrixTearOff)
{
- setTransform(matrix.a(), matrix.b(), matrix.c(), matrix.d(), matrix.e(), matrix.f());
+ RefPtr<SVGMatrixTearOff> matrixTearOff = passMatrixTearOff;
+ const AffineTransform& transform = matrixTearOff->value();
+ setTransform(transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f());
}
void CanvasRenderingContext2D::scale(float sx, float sy)
if (!state().m_invertibleCTM)
return;
- if (!std::isfinite(sx) | !std::isfinite(sy))
+ if (!std::isfinite(sx) || !std::isfinite(sy))
return;
AffineTransform newTransform = state().m_transform;
if (state().m_transform == newTransform)
return;
- realizeSaves();
+ realizeSaves(c);
if (!newTransform.isInvertible()) {
modifiableState().m_invertibleCTM = false;
}
modifiableState().m_transform = newTransform;
- c->scale(FloatSize(sx, sy));
+ c->scale(sx, sy);
m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy));
}
return;
AffineTransform newTransform = state().m_transform;
- newTransform.rotate(angleInRadians / piDouble * 180.0);
+ newTransform.rotateRadians(angleInRadians);
if (state().m_transform == newTransform)
return;
- realizeSaves();
+ realizeSaves(c);
if (!newTransform.isInvertible()) {
modifiableState().m_invertibleCTM = false;
modifiableState().m_transform = newTransform;
c->rotate(angleInRadians);
- m_path.transform(AffineTransform().rotate(-angleInRadians / piDouble * 180.0));
+ m_path.transform(AffineTransform().rotateRadians(-angleInRadians));
}
void CanvasRenderingContext2D::translate(float tx, float ty)
if (!state().m_invertibleCTM)
return;
- if (!std::isfinite(tx) | !std::isfinite(ty))
+ if (!std::isfinite(tx) || !std::isfinite(ty))
return;
AffineTransform newTransform = state().m_transform;
if (state().m_transform == newTransform)
return;
- realizeSaves();
+ realizeSaves(c);
if (!newTransform.isInvertible()) {
modifiableState().m_invertibleCTM = false;
if (!state().m_invertibleCTM)
return;
- if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy))
+ if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || !std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy))
return;
AffineTransform transform(m11, m12, m21, m22, dx, dy);
if (state().m_transform == newTransform)
return;
- realizeSaves();
+ realizeSaves(c);
modifiableState().m_transform = newTransform;
if (!newTransform.isInvertible()) {
if (ctm.isIdentity() && invertibleCTM)
return;
- realizeSaves();
+ realizeSaves(c);
// resetTransform() resolves the non-invertible CTM state.
modifiableState().m_transform.makeIdentity();
modifiableState().m_invertibleCTM = true;
if (!c)
return;
- if (!std::isfinite(m11) | !std::isfinite(m21) | !std::isfinite(dx) | !std::isfinite(m12) | !std::isfinite(m22) | !std::isfinite(dy))
+ if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) || !std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy))
return;
resetTransform();
{
if (color == state().m_unparsedStrokeColor)
return;
- realizeSaves();
- setStrokeStyle(CanvasStyle::createFromString(color, &canvas()->document()));
+ realizeSaves(nullptr);
+ setStrokeStyle(CanvasStyle::createFromString(color));
modifiableState().m_unparsedStrokeColor = color;
}
{
if (color == state().m_unparsedFillColor)
return;
- realizeSaves();
- setFillStyle(CanvasStyle::createFromString(color, &canvas()->document()));
+ realizeSaves(nullptr);
+ setFillStyle(CanvasStyle::createFromString(color));
modifiableState().m_unparsedFillColor = color;
}
m_path.clear();
}
-PassRefPtr<DOMPath> CanvasRenderingContext2D::currentPath()
-{
- return DOMPath::create(m_path);
-}
-
-void CanvasRenderingContext2D::setCurrentPath(DOMPath* path)
-{
- if (!path)
- return;
- m_path = path->path();
-}
-
static bool validateRectForCanvas(float& x, float& y, float& width, float& height)
{
- if (!std::isfinite(x) | !std::isfinite(y) | !std::isfinite(width) | !std::isfinite(height))
+ if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) || !std::isfinite(height))
return false;
if (!width && !height)
return op == CompositeSourceIn || op == CompositeSourceOut || op == CompositeDestinationIn || op == CompositeDestinationAtop;
}
-static bool parseWinding(const String& windingRuleString, WindRule& windRule)
+static WindRule parseWinding(const String& windingRuleString)
{
if (windingRuleString == "nonzero")
- windRule = RULE_NONZERO;
- else if (windingRuleString == "evenodd")
- windRule = RULE_EVENODD;
- else
- return false;
+ return RULE_NONZERO;
+ if (windingRuleString == "evenodd")
+ return RULE_EVENODD;
- return true;
+ ASSERT_NOT_REACHED();
+ return RULE_EVENODD;
}
-void CanvasRenderingContext2D::fill(const String& windingRuleString)
+void CanvasRenderingContext2D::fillInternal(const Path& path, const String& windingRuleString)
{
+ if (path.isEmpty()) {
+ return;
+ }
GraphicsContext* c = drawingContext();
- if (!c)
+ if (!c) {
return;
- if (!state().m_invertibleCTM)
+ }
+ if (!state().m_invertibleCTM) {
return;
+ }
FloatRect clipBounds;
- if (!drawingContext()->getTransformedClipBounds(&clipBounds))
+ if (!c->getTransformedClipBounds(&clipBounds)) {
return;
+ }
// If gradient size is zero, then paint nothing.
Gradient* gradient = c->fillGradient();
- if (gradient && gradient->isZeroSize())
+ if (gradient && gradient->isZeroSize()) {
return;
+ }
- if (!m_path.isEmpty()) {
- WindRule windRule = c->fillRule();
- WindRule newWindRule = RULE_NONZERO;
- if (!parseWinding(windingRuleString, newWindRule))
- return;
- c->setFillRule(newWindRule);
-
- if (isFullCanvasCompositeMode(state().m_globalComposite)) {
- fullCanvasCompositedFill(m_path);
- didDraw(clipBounds);
- } else if (state().m_globalComposite == CompositeCopy) {
- clearCanvas();
- c->fillPath(m_path);
- didDraw(clipBounds);
- } else {
- FloatRect dirtyRect;
- if (computeDirtyRect(m_path.boundingRect(), clipBounds, &dirtyRect)) {
- c->fillPath(m_path);
- didDraw(dirtyRect);
- }
- }
+ WindRule windRule = c->fillRule();
+ c->setFillRule(parseWinding(windingRuleString));
- c->setFillRule(windRule);
+ if (isFullCanvasCompositeMode(state().m_globalComposite)) {
+ fullCanvasCompositedDraw(bind(&GraphicsContext::fillPath, c, path));
+ didDraw(clipBounds);
+ } else if (state().m_globalComposite == CompositeCopy) {
+ clearCanvas();
+ c->clearShadow();
+ c->fillPath(path);
+ applyShadow(DrawShadowAndForeground);
+ didDraw(clipBounds);
+ } else {
+ FloatRect dirtyRect;
+ if (computeDirtyRect(path.boundingRect(), clipBounds, &dirtyRect)) {
+ c->fillPath(path);
+ didDraw(dirtyRect);
+ }
}
+
+ c->setFillRule(windRule);
}
-void CanvasRenderingContext2D::stroke()
+void CanvasRenderingContext2D::fill(const String& windingRuleString)
+{
+ fillInternal(m_path, windingRuleString);
+}
+
+void CanvasRenderingContext2D::fill(Path2D* domPath, const String& windingRuleString)
{
+ fillInternal(domPath->path(), windingRuleString);
+}
+
+void CanvasRenderingContext2D::strokeInternal(const Path& path)
+{
+ if (path.isEmpty()) {
+ return;
+ }
GraphicsContext* c = drawingContext();
- if (!c)
+ if (!c) {
return;
- if (!state().m_invertibleCTM)
+ }
+ if (!state().m_invertibleCTM) {
+ return;
+ }
+ FloatRect clipBounds;
+ if (!c->getTransformedClipBounds(&clipBounds))
return;
// If gradient size is zero, then paint nothing.
Gradient* gradient = c->strokeGradient();
- if (gradient && gradient->isZeroSize())
+ if (gradient && gradient->isZeroSize()) {
return;
+ }
- if (!m_path.isEmpty()) {
- FloatRect bounds = m_path.boundingRect();
+ if (isFullCanvasCompositeMode(state().m_globalComposite)) {
+ fullCanvasCompositedDraw(bind(&GraphicsContext::strokePath, c, path));
+ didDraw(clipBounds);
+ } else if (state().m_globalComposite == CompositeCopy) {
+ clearCanvas();
+ c->clearShadow();
+ c->strokePath(path);
+ applyShadow(DrawShadowAndForeground);
+ didDraw(clipBounds);
+ } else {
+ FloatRect bounds = path.boundingRect();
inflateStrokeRect(bounds);
FloatRect dirtyRect;
- if (computeDirtyRect(bounds, &dirtyRect)) {
- c->strokePath(m_path);
+ if (computeDirtyRect(bounds, clipBounds, &dirtyRect)) {
+ c->strokePath(path);
didDraw(dirtyRect);
}
}
}
-void CanvasRenderingContext2D::clip(const String& windingRuleString)
+void CanvasRenderingContext2D::stroke()
+{
+ strokeInternal(m_path);
+}
+
+void CanvasRenderingContext2D::stroke(Path2D* domPath)
+{
+ strokeInternal(domPath->path());
+}
+
+void CanvasRenderingContext2D::clipInternal(const Path& path, const String& windingRuleString)
{
GraphicsContext* c = drawingContext();
- if (!c)
+ if (!c) {
return;
- if (!state().m_invertibleCTM)
+ }
+ if (!state().m_invertibleCTM) {
return;
+ }
- WindRule newWindRule = RULE_NONZERO;
- if (!parseWinding(windingRuleString, newWindRule))
- return;
+ realizeSaves(c);
+ c->canvasClip(path, parseWinding(windingRuleString), m_clipAntialiasing);
+ modifiableState().m_hasClip = true;
+}
+
+void CanvasRenderingContext2D::clip(const String& windingRuleString)
+{
+ clipInternal(m_path, windingRuleString);
+}
- realizeSaves();
- c->canvasClip(m_path, newWindRule);
+void CanvasRenderingContext2D::clip(Path2D* domPath, const String& windingRuleString)
+{
+ clipInternal(domPath->path(), windingRuleString);
}
bool CanvasRenderingContext2D::isPointInPath(const float x, const float y, const String& windingRuleString)
{
+ return isPointInPathInternal(m_path, x, y, windingRuleString);
+}
+
+bool CanvasRenderingContext2D::isPointInPath(Path2D* domPath, const float x, const float y, const String& windingRuleString)
+{
+ return isPointInPathInternal(domPath->path(), x, y, windingRuleString);
+}
+
+bool CanvasRenderingContext2D::isPointInPathInternal(const Path& path, const float x, const float y, const String& windingRuleString)
+{
GraphicsContext* c = drawingContext();
if (!c)
return false;
return false;
FloatPoint point(x, y);
+ if (!std::isfinite(point.x()) || !std::isfinite(point.y()))
+ return false;
AffineTransform ctm = state().m_transform;
FloatPoint transformedPoint = ctm.inverse().mapPoint(point);
- if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y()))
- return false;
- WindRule windRule = RULE_NONZERO;
- if (!parseWinding(windingRuleString, windRule))
- return false;
+ return path.contains(transformedPoint, parseWinding(windingRuleString));
+}
- return m_path.contains(transformedPoint, windRule);
+bool CanvasRenderingContext2D::isPointInStroke(const float x, const float y)
+{
+ return isPointInStrokeInternal(m_path, x, y);
}
+bool CanvasRenderingContext2D::isPointInStroke(Path2D* domPath, const float x, const float y)
+{
+ return isPointInStrokeInternal(domPath->path(), x, y);
+}
-bool CanvasRenderingContext2D::isPointInStroke(const float x, const float y)
+bool CanvasRenderingContext2D::isPointInStrokeInternal(const Path& path, const float x, const float y)
{
GraphicsContext* c = drawingContext();
if (!c)
return false;
FloatPoint point(x, y);
+ if (!std::isfinite(point.x()) || !std::isfinite(point.y()))
+ return false;
AffineTransform ctm = state().m_transform;
FloatPoint transformedPoint = ctm.inverse().mapPoint(point);
- if (!std::isfinite(transformedPoint.x()) || !std::isfinite(transformedPoint.y()))
- return false;
StrokeData strokeData;
strokeData.setThickness(lineWidth());
strokeData.setLineJoin(getLineJoin());
strokeData.setMiterLimit(miterLimit());
strokeData.setLineDash(getLineDash(), lineDashOffset());
- return m_path.strokeContains(transformedPoint, strokeData);
+ return path.strokeContains(transformedPoint, strokeData);
+}
+
+void CanvasRenderingContext2D::scrollPathIntoView()
+{
+ scrollPathIntoViewInternal(m_path);
+}
+
+void CanvasRenderingContext2D::scrollPathIntoView(Path2D* path2d)
+{
+ scrollPathIntoViewInternal(path2d->path());
+}
+
+void CanvasRenderingContext2D::scrollPathIntoViewInternal(const Path& path)
+{
+ RenderObject* renderer = canvas()->renderer();
+ RenderBox* renderBox = canvas()->renderBox();
+ if (!renderer || !renderBox || !state().m_invertibleCTM || path.isEmpty())
+ return;
+
+ canvas()->document().updateLayoutIgnorePendingStylesheets();
+
+ // Apply transformation and get the bounding rect
+ Path transformedPath = path;
+ transformedPath.transform(state().m_transform);
+ FloatRect boundingRect = transformedPath.boundingRect();
+
+ // Offset by the canvas rect
+ LayoutRect pathRect(boundingRect);
+ IntRect canvasRect = renderBox->absoluteContentBox();
+ pathRect.move(canvasRect.x(), canvasRect.y());
+
+ renderer->scrollRectToVisible(
+ pathRect, ScrollAlignment::alignCenterAlways, ScrollAlignment::alignTopAlways);
+
+ // TODO: should implement "inform the user" that the caret and/or
+ // selection the specified rectangle of the canvas. See http://crbug.com/357987
}
void CanvasRenderingContext2D::clearRect(float x, float y, float width, float height)
context->save();
saved = true;
}
- context->setAlpha(1);
+ context->setAlphaAsFloat(1);
}
if (state().m_globalComposite != CompositeSourceOver) {
if (!saved) {
context->setCompositeOperation(CompositeSourceOver);
}
context->clearRect(rect);
+ if (m_hitRegionManager)
+ m_hitRegionManager->removeHitRegionsInRect(rect, state().m_transform);
if (saved)
context->restore();
+ validateStateStack();
didDraw(dirtyRect);
}
+// FIXME(crbug.com/425531): Funtional.h cannot handle override function signature.
+static void fillRectOnContext(GraphicsContext* context, const FloatRect& rect)
+{
+ context->fillRect(rect);
+}
+
+static void strokeRectOnContext(GraphicsContext* context, const FloatRect& rect)
+{
+ context->strokeRect(rect);
+}
+
void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height)
{
if (!validateRectForCanvas(x, y, width, height))
if (!state().m_invertibleCTM)
return;
FloatRect clipBounds;
- if (!drawingContext()->getTransformedClipBounds(&clipBounds))
+ if (!c->getTransformedClipBounds(&clipBounds))
return;
// from the HTML5 Canvas spec:
c->fillRect(rect);
didDraw(clipBounds);
} else if (isFullCanvasCompositeMode(state().m_globalComposite)) {
- fullCanvasCompositedFill(rect);
+ fullCanvasCompositedDraw(bind(&fillRectOnContext, c, rect));
didDraw(clipBounds);
} else if (state().m_globalComposite == CompositeCopy) {
clearCanvas();
+ c->clearShadow();
c->fillRect(rect);
+ applyShadow(DrawShadowAndForeground);
didDraw(clipBounds);
} else {
FloatRect dirtyRect;
return;
if (!state().m_invertibleCTM)
return;
+ FloatRect clipBounds;
+ if (!c->getTransformedClipBounds(&clipBounds))
+ return;
// If gradient size is zero, then paint nothing.
Gradient* gradient = c->strokeGradient();
return;
FloatRect rect(x, y, width, height);
-
- FloatRect boundingRect = rect;
- boundingRect.inflate(state().m_lineWidth / 2);
- FloatRect dirtyRect;
- if (computeDirtyRect(boundingRect, &dirtyRect)) {
- c->strokeRect(rect, state().m_lineWidth);
- didDraw(dirtyRect);
+ if (isFullCanvasCompositeMode(state().m_globalComposite)) {
+ fullCanvasCompositedDraw(bind(&strokeRectOnContext, c, rect));
+ didDraw(clipBounds);
+ } else if (state().m_globalComposite == CompositeCopy) {
+ clearCanvas();
+ c->clearShadow();
+ c->strokeRect(rect);
+ applyShadow(DrawShadowAndForeground);
+ didDraw(clipBounds);
+ } else {
+ FloatRect boundingRect = rect;
+ boundingRect.inflate(state().m_lineWidth / 2);
+ FloatRect dirtyRect;
+ if (computeDirtyRect(boundingRect, clipBounds, &dirtyRect)) {
+ c->strokeRect(rect);
+ didDraw(dirtyRect);
+ }
}
}
if (state().m_shadowOffset == offset && state().m_shadowBlur == blur && state().m_shadowColor == color)
return;
bool wasDrawingShadows = shouldDrawShadows();
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_shadowOffset = offset;
modifiableState().m_shadowBlur = blur;
modifiableState().m_shadowColor = color;
applyShadow();
}
-void CanvasRenderingContext2D::applyShadow()
+void CanvasRenderingContext2D::applyShadow(ShadowMode shadowMode)
{
GraphicsContext* c = drawingContext();
if (!c)
if (shouldDrawShadows()) {
c->setShadow(state().m_shadowOffset, state().m_shadowBlur, state().m_shadowColor,
- DrawLooper::ShadowIgnoresTransforms);
+ DrawLooperBuilder::ShadowIgnoresTransforms, DrawLooperBuilder::ShadowRespectsAlpha, shadowMode);
} else {
c->clearShadow();
}
return alphaChannel(state().m_shadowColor) && (state().m_shadowBlur || !state().m_shadowOffset.isZero());
}
-enum ImageSizeType {
- ImageSizeAfterDevicePixelRatio,
- ImageSizeBeforeDevicePixelRatio
-};
-
-static LayoutSize sizeFor(HTMLImageElement* image, ImageSizeType sizeType)
-{
- LayoutSize size;
- ImageResource* cachedImage = image->cachedImage();
- if (cachedImage) {
- size = cachedImage->imageSizeForRenderer(image->renderer(), 1.0f); // FIXME: Not sure about this.
-
- if (sizeType == ImageSizeAfterDevicePixelRatio && image->renderer() && image->renderer()->isRenderImage() && cachedImage->image() && !cachedImage->image()->hasRelativeWidth())
- size.scale(toRenderImage(image->renderer())->imageDevicePixelRatio());
- }
- return size;
-}
-
-static IntSize sizeFor(HTMLVideoElement* video)
-{
- if (MediaPlayer* player = video->player())
- return player->naturalSize();
- return IntSize();
-}
-
static inline FloatRect normalizeRect(const FloatRect& rect)
{
- return FloatRect(min(rect.x(), rect.maxX()),
- min(rect.y(), rect.maxY()),
- max(rect.width(), -rect.width()),
- max(rect.height(), -rect.height()));
+ return FloatRect(std::min(rect.x(), rect.maxX()),
+ std::min(rect.y(), rect.maxY()),
+ std::max(rect.width(), -rect.width()),
+ std::max(rect.height(), -rect.height()));
}
static inline void clipRectsToImageRect(const FloatRect& imageRect, FloatRect* srcRect, FloatRect* dstRect)
dstRect->move(offset);
}
-void CanvasRenderingContext2D::drawImageInternal(Image* image, const FloatRect& srcRect, const FloatRect& dstRect, const CompositeOperator& op, const blink::WebBlendMode& blendMode)
+void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource, float x, float y, ExceptionState& exceptionState)
{
- if (!image)
- return;
+ FloatSize sourceRectSize = imageSource->sourceSize();
+ FloatSize destRectSize = imageSource->defaultDestinationSize();
+ drawImageInternal(imageSource, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, destRectSize.width(), destRectSize.height(), exceptionState);
+}
+
+void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource,
+ float x, float y, float width, float height, ExceptionState& exceptionState)
+{
+ FloatSize sourceRectSize = imageSource->sourceSize();
+ drawImageInternal(imageSource, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, width, height, exceptionState);
+}
+
+void CanvasRenderingContext2D::drawImage(CanvasImageSource* imageSource,
+ float sx, float sy, float sw, float sh,
+ float dx, float dy, float dw, float dh, ExceptionState& exceptionState)
+{
+ drawImageInternal(imageSource, sx, sy, sw, sh, dx, dy, dw, dh, exceptionState);
+}
+
+static void drawVideo(GraphicsContext* c, CanvasImageSource* imageSource, FloatRect srcRect, FloatRect dstRect)
+{
+ HTMLVideoElement* video = static_cast<HTMLVideoElement*>(imageSource);
+ GraphicsContextStateSaver stateSaver(*c);
+ c->clip(dstRect);
+ c->translate(dstRect.x(), dstRect.y());
+ c->scale(dstRect.width() / srcRect.width(), dstRect.height() / srcRect.height());
+ c->translate(-srcRect.x(), -srcRect.y());
+ video->paintCurrentFrameInContext(c, IntRect(IntPoint(), IntSize(video->videoWidth(), video->videoHeight())));
+ stateSaver.restore();
+}
+
+static void drawImageOnContext(GraphicsContext* c, CanvasImageSource* imageSource, Image* image, const FloatRect& srcRect, const FloatRect& dstRect)
+{
+ if (!imageSource->isVideoElement()) {
+ c->drawImage(image, dstRect, srcRect, c->compositeOperation(), c->blendModeOperation());
+ } else {
+ drawVideo(c, static_cast<HTMLVideoElement*>(imageSource), srcRect, dstRect);
+ }
+}
+
+void CanvasRenderingContext2D::drawImageInternal(CanvasImageSource* imageSource,
+ float sx, float sy, float sw, float sh,
+ float dx, float dy, float dw, float dh, ExceptionState& exceptionState)
+{
+ RefPtr<Image> image;
+ SourceImageStatus sourceImageStatus = InvalidSourceImageStatus;
+ if (!imageSource->isVideoElement()) {
+ SourceImageMode mode = canvas() == imageSource ? CopySourceImageIfVolatile : DontCopySourceImage; // Thunking for ==
+ image = imageSource->getSourceImageForCanvas(mode, &sourceImageStatus);
+ if (sourceImageStatus == UndecodableSourceImageStatus)
+ exceptionState.throwDOMException(InvalidStateError, "The HTMLImageElement provided is in the 'broken' state.");
+ if (!image || !image->width() || !image->height())
+ return;
+ }
GraphicsContext* c = drawingContext();
if (!c)
return;
+
if (!state().m_invertibleCTM)
return;
+
+ if (!std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dw) || !std::isfinite(dh)
+ || !std::isfinite(sx) || !std::isfinite(sy) || !std::isfinite(sw) || !std::isfinite(sh)
+ || !dw || !dh || !sw || !sh)
+ return;
+
FloatRect clipBounds;
- if (!drawingContext()->getTransformedClipBounds(&clipBounds))
+ if (!c->getTransformedClipBounds(&clipBounds))
+ return;
+
+ FloatRect srcRect = normalizeRect(FloatRect(sx, sy, sw, sh));
+ FloatRect dstRect = normalizeRect(FloatRect(dx, dy, dw, dh));
+
+ clipRectsToImageRect(FloatRect(FloatPoint(), imageSource->sourceSize()), &srcRect, &dstRect);
+
+ imageSource->adjustDrawRects(&srcRect, &dstRect);
+
+ if (srcRect.isEmpty())
return;
+ if (imageSource->isVideoElement())
+ canvas()->buffer()->willDrawVideo();
+
+ CompositeOperator op = state().m_globalComposite;
if (rectContainsTransformedRect(dstRect, clipBounds)) {
- c->drawImage(image, dstRect, srcRect, op, blendMode);
+ drawImageOnContext(c, imageSource, image.get(), srcRect, dstRect);
didDraw(clipBounds);
} else if (isFullCanvasCompositeMode(op)) {
- fullCanvasCompositedDrawImage(image, dstRect, srcRect, op);
+ fullCanvasCompositedDraw(bind(&drawImageOnContext, c, imageSource, image.get(), srcRect, dstRect));
didDraw(clipBounds);
} else if (op == CompositeCopy) {
clearCanvas();
- c->drawImage(image, dstRect, srcRect, op, blendMode);
+ drawImageOnContext(c, imageSource, image.get(), srcRect, dstRect);
didDraw(clipBounds);
} else {
FloatRect dirtyRect;
- if (computeDirtyRect(dstRect, &dirtyRect)) {
- c->drawImage(image, dstRect, srcRect, op, blendMode);
+ if (computeDirtyRect(dstRect, clipBounds, &dirtyRect)) {
+ drawImageOnContext(c, imageSource, image.get(), srcRect, dstRect);
didDraw(dirtyRect);
}
}
-}
-
-void CanvasRenderingContext2D::drawImage(ImageBitmap* bitmap, float x, float y, ExceptionState& exceptionState)
-{
- if (!bitmap) {
- exceptionState.throwDOMException(TypeMismatchError, "The element provided is invalid.");
- return;
- }
- drawImage(bitmap, x, y, bitmap->width(), bitmap->height(), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(ImageBitmap* bitmap,
- float x, float y, float width, float height, ExceptionState& exceptionState)
-{
- if (!bitmap) {
- exceptionState.throwDOMException(TypeMismatchError, "The element provided is invalid.");
- return;
- }
- if (!bitmap->bitmapRect().width() || !bitmap->bitmapRect().height())
- return;
-
- drawImage(bitmap, 0, 0, bitmap->width(), bitmap->height(), x, y, width, height, exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(ImageBitmap* bitmap,
- float sx, float sy, float sw, float sh,
- float dx, float dy, float dw, float dh, ExceptionState& exceptionState)
-{
- if (!bitmap) {
- exceptionState.throwDOMException(TypeMismatchError, "The element provided is invalid.");
- return;
- }
-
- FloatRect srcRect(sx, sy, sw, sh);
- FloatRect dstRect(dx, dy, dw, dh);
- FloatRect bitmapRect = bitmap->bitmapRect();
-
- if (!std::isfinite(dstRect.x()) || !std::isfinite(dstRect.y()) || !std::isfinite(dstRect.width()) || !std::isfinite(dstRect.height())
- || !std::isfinite(srcRect.x()) || !std::isfinite(srcRect.y()) || !std::isfinite(srcRect.width()) || !std::isfinite(srcRect.height()))
- return;
-
- if (!dstRect.width() || !dstRect.height() || !srcRect.width() || !srcRect.height())
- return;
-
- ASSERT(bitmap->height() && bitmap->width());
- FloatRect normalizedSrcRect = normalizeRect(srcRect);
- FloatRect normalizedDstRect = normalizeRect(dstRect);
-
- // Clip the rects to where the user thinks that the image is situated.
- clipRectsToImageRect(IntRect(IntPoint(), bitmap->size()), &normalizedSrcRect, &normalizedDstRect);
-
- FloatRect intersectRect = intersection(bitmapRect, normalizedSrcRect);
- FloatRect actualSrcRect(intersectRect);
-
- IntPoint bitmapOffset = bitmap->bitmapOffset();
- actualSrcRect.move(bitmapOffset - bitmapRect.location());
- FloatRect imageRect = FloatRect(bitmapOffset, bitmapRect.size());
-
- FloatRect actualDstRect(FloatPoint(intersectRect.location() - normalizedSrcRect.location()), bitmapRect.size());
- actualDstRect.scale(normalizedDstRect.width() / normalizedSrcRect.width() * intersectRect.width() / bitmapRect.width(),
- normalizedDstRect.height() / normalizedSrcRect.height() * intersectRect.height() / bitmapRect.height());
- actualDstRect.moveBy(normalizedDstRect.location());
-
- if (!imageRect.intersects(actualSrcRect))
- return;
-
- RefPtr<Image> imageForRendering = bitmap->bitmapImage();
- if (!imageForRendering)
- return;
-
- drawImageInternal(imageForRendering.get(), actualSrcRect, actualDstRect, state().m_globalComposite, state().m_globalBlend);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLImageElement* image, float x, float y, ExceptionState& exceptionState)
-{
- if (!image) {
- exceptionState.throwDOMException(TypeMismatchError, "The image element provided is invalid.");
- return;
- }
- LayoutSize destRectSize = sizeFor(image, ImageSizeAfterDevicePixelRatio);
- drawImage(image, x, y, destRectSize.width(), destRectSize.height(), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLImageElement* image,
- float x, float y, float width, float height, ExceptionState& exceptionState)
-{
- if (!image) {
- exceptionState.throwDOMException(TypeMismatchError, "The image element provided is invalid.");
- return;
- }
- LayoutSize sourceRectSize = sizeFor(image, ImageSizeBeforeDevicePixelRatio);
- drawImage(image, FloatRect(0, 0, sourceRectSize.width(), sourceRectSize.height()), FloatRect(x, y, width, height), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLImageElement* image,
- float sx, float sy, float sw, float sh,
- float dx, float dy, float dw, float dh, ExceptionState& exceptionState)
-{
- drawImage(image, FloatRect(sx, sy, sw, sh), FloatRect(dx, dy, dw, dh), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLImageElement* image, const FloatRect& srcRect, const FloatRect& dstRect, ExceptionState& exceptionState)
-{
- drawImage(image, srcRect, dstRect, state().m_globalComposite, state().m_globalBlend, exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLImageElement* image, const FloatRect& srcRect, const FloatRect& dstRect, const CompositeOperator& op, const blink::WebBlendMode& blendMode, ExceptionState& exceptionState)
-{
- if (!image) {
- exceptionState.throwDOMException(TypeMismatchError, "The image element provided is invalid.");
- return;
- }
-
- if (!std::isfinite(dstRect.x()) || !std::isfinite(dstRect.y()) || !std::isfinite(dstRect.width()) || !std::isfinite(dstRect.height())
- || !std::isfinite(srcRect.x()) || !std::isfinite(srcRect.y()) || !std::isfinite(srcRect.width()) || !std::isfinite(srcRect.height()))
- return;
-
- ImageResource* cachedImage = image->cachedImage();
- if (!cachedImage || !image->complete())
- return;
-
- LayoutSize size = sizeFor(image, ImageSizeBeforeDevicePixelRatio);
- if (!size.width() || !size.height() || !dstRect.width() || !dstRect.height() || !srcRect.width() || !srcRect.height())
- return;
-
- FloatRect normalizedSrcRect = normalizeRect(srcRect);
- FloatRect normalizedDstRect = normalizeRect(dstRect);
-
- FloatRect imageRect = FloatRect(FloatPoint(), size);
- if (!imageRect.intersects(normalizedSrcRect))
- return;
-
- clipRectsToImageRect(imageRect, &normalizedSrcRect, &normalizedDstRect);
-
- checkOrigin(image);
-
- Image* imageForRendering = cachedImage->imageForRenderer(image->renderer());
-
- // For images that depend on an unavailable container size, we need to fall back to the intrinsic
- // object size. http://www.w3.org/TR/2dcontext2/#dom-context-2d-drawimage
- // FIXME: Without a specified image size this should resolve against the canvas element's size, see: crbug.com/230163.
- if (!image->renderer() && imageForRendering->usesContainerSize())
- imageForRendering->setContainerSize(imageForRendering->size());
-
- drawImageInternal(imageForRendering, normalizedSrcRect, normalizedDstRect, op, blendMode);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLCanvasElement* sourceCanvas, float x, float y, ExceptionState& exceptionState)
-{
- drawImage(sourceCanvas, 0, 0, sourceCanvas->width(), sourceCanvas->height(), x, y, sourceCanvas->width(), sourceCanvas->height(), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLCanvasElement* sourceCanvas,
- float x, float y, float width, float height, ExceptionState& exceptionState)
-{
- drawImage(sourceCanvas, FloatRect(0, 0, sourceCanvas->width(), sourceCanvas->height()), FloatRect(x, y, width, height), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLCanvasElement* sourceCanvas,
- float sx, float sy, float sw, float sh,
- float dx, float dy, float dw, float dh, ExceptionState& exceptionState)
-{
- drawImage(sourceCanvas, FloatRect(sx, sy, sw, sh), FloatRect(dx, dy, dw, dh), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLCanvasElement* sourceCanvas, const FloatRect& srcRect,
- const FloatRect& dstRect, ExceptionState& exceptionState)
-{
- if (!sourceCanvas) {
- exceptionState.throwDOMException(TypeMismatchError, "The canvas element provided is invalid.");
- return;
- }
-
- FloatRect srcCanvasRect = FloatRect(FloatPoint(), sourceCanvas->size());
-
- if (!srcCanvasRect.width() || !srcCanvasRect.height() || !srcRect.width() || !srcRect.height())
- return;
-
- FloatRect normalizedSrcRect = normalizeRect(srcRect);
- FloatRect normalizedDstRect = normalizeRect(dstRect);
-
- if (!srcCanvasRect.intersects(normalizedSrcRect) || !normalizedDstRect.width() || !normalizedDstRect.height())
- return;
-
- clipRectsToImageRect(srcCanvasRect, &normalizedSrcRect, &normalizedDstRect);
-
- GraphicsContext* c = drawingContext();
- if (!c)
- return;
- if (!state().m_invertibleCTM)
- return;
-
- // FIXME: Do this through platform-independent GraphicsContext API.
- ImageBuffer* buffer = sourceCanvas->buffer();
- if (!buffer)
- return;
-
- FloatRect clipBounds;
- if (!drawingContext()->getTransformedClipBounds(&clipBounds))
- return;
-
- checkOrigin(sourceCanvas);
-
- // If we're drawing from one accelerated canvas 2d to another, avoid calling sourceCanvas->makeRenderingResultsAvailable()
- // as that will do a readback to software.
- CanvasRenderingContext* sourceContext = sourceCanvas->renderingContext();
- // FIXME: Implement an accelerated path for drawing from a WebGL canvas to a 2d canvas when possible.
- if (sourceContext && sourceContext->is3d())
- sourceContext->paintRenderingResultsToCanvas();
-
- if (rectContainsTransformedRect(normalizedDstRect, clipBounds)) {
- c->drawImageBuffer(buffer, normalizedDstRect, normalizedSrcRect, state().m_globalComposite, state().m_globalBlend);
- didDraw(clipBounds);
- } else if (isFullCanvasCompositeMode(state().m_globalComposite)) {
- fullCanvasCompositedDrawImage(buffer, normalizedDstRect, normalizedSrcRect, state().m_globalComposite);
- didDraw(clipBounds);
- } else if (state().m_globalComposite == CompositeCopy) {
- clearCanvas();
- c->drawImageBuffer(buffer, normalizedDstRect, normalizedSrcRect, state().m_globalComposite, state().m_globalBlend);
- didDraw(clipBounds);
- } else {
- FloatRect dirtyRect;
- if (computeDirtyRect(normalizedDstRect, clipBounds, &dirtyRect)) {
- c->drawImageBuffer(buffer, normalizedDstRect, normalizedSrcRect, state().m_globalComposite, state().m_globalBlend);
- didDraw(dirtyRect);
- }
- }
-
- // Flush canvas's ImageBuffer when drawImage from WebGL to HW accelerated 2d canvas
- if (sourceContext && sourceContext->is3d() && is2d() && isAccelerated() && canvas()->buffer())
- canvas()->buffer()->flush();
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLVideoElement* video, float x, float y, ExceptionState& exceptionState)
-{
- if (!video) {
- exceptionState.throwDOMException(TypeMismatchError, "The video element provided is invalid.");
- return;
- }
- IntSize size = sizeFor(video);
- drawImage(video, x, y, size.width(), size.height(), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLVideoElement* video,
- float x, float y, float width, float height, ExceptionState& exceptionState)
-{
- if (!video) {
- exceptionState.throwDOMException(TypeMismatchError, "The video element provided is invalid.");
- return;
- }
- IntSize size = sizeFor(video);
- drawImage(video, FloatRect(0, 0, size.width(), size.height()), FloatRect(x, y, width, height), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLVideoElement* video,
- float sx, float sy, float sw, float sh,
- float dx, float dy, float dw, float dh, ExceptionState& exceptionState)
-{
- drawImage(video, FloatRect(sx, sy, sw, sh), FloatRect(dx, dy, dw, dh), exceptionState);
-}
-
-void CanvasRenderingContext2D::drawImage(HTMLVideoElement* video, const FloatRect& srcRect, const FloatRect& dstRect, ExceptionState& exceptionState)
-{
- if (!video) {
- exceptionState.throwDOMException(TypeMismatchError, "The video element provided is invalid.");
- return;
- }
-
- if (video->readyState() == HTMLMediaElement::HAVE_NOTHING || video->readyState() == HTMLMediaElement::HAVE_METADATA)
- return;
- if (!srcRect.width() || !srcRect.height())
- return;
-
- FloatRect videoRect = FloatRect(FloatPoint(), sizeFor(video));
-
- FloatRect normalizedSrcRect = normalizeRect(srcRect);
- FloatRect normalizedDstRect = normalizeRect(dstRect);
-
- if (!videoRect.intersects(normalizedSrcRect) || !normalizedDstRect.width() || !normalizedDstRect.height())
- return;
-
- clipRectsToImageRect(videoRect, &normalizedSrcRect, &normalizedDstRect);
-
- GraphicsContext* c = drawingContext();
- if (!c)
- return;
- if (!state().m_invertibleCTM)
- return;
-
- checkOrigin(video);
-
- FloatRect dirtyRect;
- if (!computeDirtyRect(normalizedDstRect, &dirtyRect))
- return;
-
- GraphicsContextStateSaver stateSaver(*c);
- c->clip(normalizedDstRect);
- c->translate(normalizedDstRect.x(), normalizedDstRect.y());
- c->scale(FloatSize(normalizedDstRect.width() / normalizedSrcRect.width(), normalizedDstRect.height() / normalizedSrcRect.height()));
- c->translate(-normalizedSrcRect.x(), -normalizedSrcRect.y());
- video->paintCurrentFrameInContext(c, IntRect(IntPoint(), sizeFor(video)));
- stateSaver.restore();
-
- didDraw(dirtyRect);
+ validateStateStack();
+
+ if (sourceImageStatus == ExternalSourceImageStatus && isAccelerated() && canvas()->buffer())
+ canvas()->buffer()->flush();
+
+ if (canvas()->originClean() && wouldTaintOrigin(imageSource))
+ canvas()->setOriginTainted();
}
void CanvasRenderingContext2D::drawImageFromRect(HTMLImageElement* image,
float dx, float dy, float dw, float dh,
const String& compositeOperation)
{
- CompositeOperator op;
- blink::WebBlendMode blendOp = blink::WebBlendModeNormal;
- if (!parseCompositeAndBlendOperator(compositeOperation, op, blendOp) || blendOp != blink::WebBlendModeNormal)
- op = CompositeSourceOver;
-
- drawImage(image, FloatRect(sx, sy, sw, sh), FloatRect(dx, dy, dw, dh), op, blink::WebBlendModeNormal, IGNORE_EXCEPTION);
+ if (!image)
+ return;
+ save();
+ setGlobalCompositeOperation(compositeOperation);
+ drawImageInternal(image, sx, sy, sw, sh, dx, dy, dw, dh, IGNORE_EXCEPTION);
+ restore();
}
void CanvasRenderingContext2D::setAlpha(float alpha)
return state().m_transform.mapQuad(quad).containsQuad(transformedQuad);
}
-static void drawImageToContext(Image* image, GraphicsContext* context, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
-{
- context->drawImage(image, dest, src, op);
-}
-
-static void drawImageToContext(ImageBuffer* imageBuffer, GraphicsContext* context, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
-{
- context->drawImageBuffer(imageBuffer, dest, src, op);
-}
-
-template<class T> void CanvasRenderingContext2D::fullCanvasCompositedDrawImage(T* image, const FloatRect& dest, const FloatRect& src, CompositeOperator op)
-{
- ASSERT(isFullCanvasCompositeMode(op));
-
- drawingContext()->beginLayer(1, op);
- drawImageToContext(image, drawingContext(), dest, src, CompositeSourceOver);
- drawingContext()->endLayer();
-}
-
-static void fillPrimitive(const FloatRect& rect, GraphicsContext* context)
-{
- context->fillRect(rect);
-}
-
-static void fillPrimitive(const Path& path, GraphicsContext* context)
-{
- context->fillPath(path);
-}
-
-template<class T> void CanvasRenderingContext2D::fullCanvasCompositedFill(const T& area)
+void CanvasRenderingContext2D::fullCanvasCompositedDraw(const Closure& draw)
{
ASSERT(isFullCanvasCompositeMode(state().m_globalComposite));
GraphicsContext* c = drawingContext();
ASSERT(c);
- c->beginLayer(1, state().m_globalComposite);
+
CompositeOperator previousOperator = c->compositeOperation();
+ if (shouldDrawShadows()) {
+ // unroll into two independently composited passes if drawing shadows
+ c->beginLayer(1, state().m_globalComposite);
+ c->setCompositeOperation(CompositeSourceOver);
+ applyShadow(DrawShadowOnly);
+ draw();
+ c->setCompositeOperation(previousOperator);
+ c->endLayer();
+ }
+
+ c->beginLayer(1, state().m_globalComposite);
+ c->clearShadow();
c->setCompositeOperation(CompositeSourceOver);
- fillPrimitive(area, c);
+ draw();
c->setCompositeOperation(previousOperator);
c->endLayer();
+ applyShadow(DrawShadowAndForeground); // go back to normal shadows mode
}
-PassRefPtr<CanvasGradient> CanvasRenderingContext2D::createLinearGradient(float x0, float y0, float x1, float y1, ExceptionState& exceptionState)
+PassRefPtrWillBeRawPtr<CanvasGradient> CanvasRenderingContext2D::createLinearGradient(float x0, float y0, float x1, float y1)
{
- if (!std::isfinite(x0))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(x0, "x0"));
- else if (!std::isfinite(y0))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(y0, "y0"));
- else if (!std::isfinite(x1))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(x1, "x1"));
- else if (!std::isfinite(y1))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(y1, "y1"));
-
- if (exceptionState.hadException())
- return 0;
-
- RefPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0), FloatPoint(x1, y1));
+ RefPtrWillBeRawPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0), FloatPoint(x1, y1));
return gradient.release();
}
-PassRefPtr<CanvasGradient> CanvasRenderingContext2D::createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1, ExceptionState& exceptionState)
-{
- if (!std::isfinite(x0))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(x0, "x0"));
- else if (!std::isfinite(y0))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(y0, "y0"));
- else if (!std::isfinite(r0))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(r0, "r0"));
- else if (!std::isfinite(x1))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(x1, "x1"));
- else if (!std::isfinite(y1))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(y1, "y1"));
- else if (!std::isfinite(r1))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(r1, "r1"));
- else if (r0 < 0 || r1 < 0)
+PassRefPtrWillBeRawPtr<CanvasGradient> CanvasRenderingContext2D::createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1, ExceptionState& exceptionState)
+{
+ if (r0 < 0 || r1 < 0) {
exceptionState.throwDOMException(IndexSizeError, String::format("The %s provided is less than 0.", r0 < 0 ? "r0" : "r1"));
+ return nullptr;
+ }
- if (exceptionState.hadException())
- return 0;
-
- RefPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1);
+ RefPtrWillBeRawPtr<CanvasGradient> gradient = CanvasGradient::create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1);
return gradient.release();
}
-PassRefPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(HTMLImageElement* image,
+PassRefPtrWillBeRawPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(CanvasImageSource* imageSource,
const String& repetitionType, ExceptionState& exceptionState)
{
- if (!image) {
- exceptionState.throwDOMException(TypeMismatchError, "The image element provided is invalid.");
- return 0;
- }
- bool repeatX, repeatY;
- CanvasPattern::parseRepetitionType(repetitionType, repeatX, repeatY, exceptionState);
+ Pattern::RepeatMode repeatMode = CanvasPattern::parseRepetitionType(repetitionType, exceptionState);
if (exceptionState.hadException())
- return 0;
+ return nullptr;
- if (!image->complete())
- return 0;
+ SourceImageStatus status;
+ RefPtr<Image> imageForRendering = imageSource->getSourceImageForCanvas(CopySourceImageIfVolatile, &status);
- ImageResource* cachedImage = image->cachedImage();
- Image* imageForRendering = cachedImage ? cachedImage->imageForRenderer(image->renderer()) : 0;
- if (!imageForRendering)
- return CanvasPattern::create(Image::nullImage(), repeatX, repeatY, true);
-
- // We need to synthesize a container size if a renderer is not available to provide one.
- if (!image->renderer() && imageForRendering->usesContainerSize())
- imageForRendering->setContainerSize(imageForRendering->size());
-
- bool originClean = cachedImage->isAccessAllowed(canvas()->securityOrigin());
- return CanvasPattern::create(imageForRendering, repeatX, repeatY, originClean);
-}
-
-PassRefPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(HTMLCanvasElement* canvas,
- const String& repetitionType, ExceptionState& exceptionState)
-{
- if (!canvas)
- exceptionState.throwDOMException(TypeMismatchError, "The canvas element provided is invalid.");
- else if (!canvas->width() || !canvas->height())
- exceptionState.throwDOMException(InvalidStateError, String::format("The canvas %s is 0.", canvas->width() ? "height" : "width"));
+ switch (status) {
+ case NormalSourceImageStatus:
+ break;
+ case ZeroSizeCanvasSourceImageStatus:
+ exceptionState.throwDOMException(InvalidStateError, String::format("The canvas %s is 0.", imageSource->sourceSize().width() ? "height" : "width"));
+ return nullptr;
+ case UndecodableSourceImageStatus:
+ exceptionState.throwDOMException(InvalidStateError, "Source image is in the 'broken' state.");
+ return nullptr;
+ case InvalidSourceImageStatus:
+ imageForRendering = Image::nullImage();
+ break;
+ case IncompleteSourceImageStatus:
+ return nullptr;
+ default:
+ case ExternalSourceImageStatus: // should not happen when mode is CopySourceImageIfVolatile
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+ ASSERT(imageForRendering);
- if (exceptionState.hadException())
- return 0;
+ bool originClean = !wouldTaintOrigin(imageSource);
- bool repeatX, repeatY;
- CanvasPattern::parseRepetitionType(repetitionType, repeatX, repeatY, exceptionState);
- if (exceptionState.hadException())
- return 0;
- return CanvasPattern::create(canvas->copiedImage(), repeatX, repeatY, canvas->originClean());
+ return CanvasPattern::create(imageForRendering.release(), repeatMode, originClean);
}
bool CanvasRenderingContext2D::computeDirtyRect(const FloatRect& localRect, FloatRect* dirtyRect)
if (dirtyRect.isEmpty())
return;
- // If we are drawing to hardware and we have a composited layer, just call contentChanged().
- if (isAccelerated()) {
- RenderBox* renderBox = canvas()->renderBox();
- if (renderBox && renderBox->hasAcceleratedCompositing()) {
- renderBox->contentChanged(CanvasPixelsChanged);
- canvas()->clearCopiedImage();
- canvas()->notifyObserversCanvasChanged(dirtyRect);
- return;
- }
- }
-
canvas()->didDraw(dirtyRect);
}
GraphicsContext* CanvasRenderingContext2D::drawingContext() const
{
+ if (isContextLost())
+ return nullptr;
return canvas()->drawingContext();
}
-static PassRefPtr<ImageData> createEmptyImageData(const IntSize& size)
+static PassRefPtrWillBeRawPtr<ImageData> createEmptyImageData(const IntSize& size)
{
- Checked<int, RecordOverflow> dataSize = 4;
- dataSize *= size.width();
- dataSize *= size.height();
- if (dataSize.hasOverflowed())
- return 0;
+ if (RefPtrWillBeRawPtr<ImageData> data = ImageData::create(size)) {
+ data->data()->zeroFill();
+ return data.release();
+ }
- RefPtr<ImageData> data = ImageData::create(size);
- data->data()->zeroFill();
- return data.release();
+ return nullptr;
}
-PassRefPtr<ImageData> CanvasRenderingContext2D::createImageData(PassRefPtr<ImageData> imageData, ExceptionState& exceptionState) const
+PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::createImageData(PassRefPtrWillBeRawPtr<ImageData> imageData) const
{
- if (!imageData) {
- exceptionState.throwDOMException(NotSupportedError, "The ImageData provided is invalid.");
- return 0;
- }
-
return createEmptyImageData(imageData->size());
}
-PassRefPtr<ImageData> CanvasRenderingContext2D::createImageData(float sw, float sh, ExceptionState& exceptionState) const
+PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::createImageData(float sw, float sh, ExceptionState& exceptionState) const
{
- if (!sw || !sh)
+ if (!sw || !sh) {
exceptionState.throwDOMException(IndexSizeError, String::format("The source %s is 0.", sw ? "height" : "width"));
- else if (!std::isfinite(sw))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(sw, "source width"));
- else if (!std::isfinite(sh))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(sh, "source height"));
-
- if (exceptionState.hadException())
- return 0;
+ return nullptr;
+ }
FloatSize logicalSize(fabs(sw), fabs(sh));
if (!logicalSize.isExpressibleAsIntSize())
- return 0;
+ return nullptr;
IntSize size = expandedIntSize(logicalSize);
if (size.width() < 1)
return createEmptyImageData(size);
}
-PassRefPtr<ImageData> CanvasRenderingContext2D::webkitGetImageDataHD(float sx, float sy, float sw, float sh, ExceptionState& exceptionState) const
-{
- return getImageData(sx, sy, sw, sh, exceptionState);
-}
-
-PassRefPtr<ImageData> CanvasRenderingContext2D::getImageData(float sx, float sy, float sw, float sh, ExceptionState& exceptionState) const
+PassRefPtrWillBeRawPtr<ImageData> CanvasRenderingContext2D::getImageData(float sx, float sy, float sw, float sh, ExceptionState& exceptionState) const
{
if (!canvas()->originClean())
exceptionState.throwSecurityError("The canvas has been tainted by cross-origin data.");
else if (!sw || !sh)
exceptionState.throwDOMException(IndexSizeError, String::format("The source %s is 0.", sw ? "height" : "width"));
- else if (!std::isfinite(sx))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(sx, "source X"));
- else if (!std::isfinite(sy))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(sy, "source Y"));
- else if (!std::isfinite(sw))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(sw, "source width"));
- else if (!std::isfinite(sh))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(sh, "source height"));
if (exceptionState.hadException())
- return 0;
+ return nullptr;
if (sw < 0) {
sx += sw;
if (logicalRect.height() < 1)
logicalRect.setHeight(1);
if (!logicalRect.isExpressibleAsIntRect())
- return 0;
+ return nullptr;
IntRect imageDataRect = enclosingIntRect(logicalRect);
ImageBuffer* buffer = canvas()->buffer();
- if (!buffer)
+ if (!buffer || isContextLost())
return createEmptyImageData(imageDataRect.size());
- RefPtr<Uint8ClampedArray> byteArray = buffer->getUnmultipliedImageData(imageDataRect);
+ RefPtr<Uint8ClampedArray> byteArray = buffer->getImageData(Unmultiplied, imageDataRect);
if (!byteArray)
- return 0;
+ return nullptr;
return ImageData::create(imageDataRect.size(), byteArray.release());
}
-void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, ExceptionState& exceptionState)
+void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy)
{
- if (!data) {
- exceptionState.throwDOMException(TypeMismatchError, "The ImageData provided is invalid.");
- return;
- }
- putImageData(data, dx, dy, 0, 0, data->width(), data->height(), exceptionState);
-}
-
-void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, float dirtyX, float dirtyY,
- float dirtyWidth, float dirtyHeight, ExceptionState& exceptionState)
-{
- if (!data)
- exceptionState.throwDOMException(TypeMismatchError, "The ImageData provided is invalid.");
- else if (!std::isfinite(dx))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(dx, "dx"));
- else if (!std::isfinite(dy))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(dy, "dy"));
- else if (!std::isfinite(dirtyX))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(dirtyX, "dirtyX"));
- else if (!std::isfinite(dirtyY))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(dirtyY, "dirtyY"));
- else if (!std::isfinite(dirtyWidth))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(dirtyWidth, "dirtyWidth"));
- else if (!std::isfinite(dirtyHeight))
- exceptionState.throwDOMException(NotSupportedError, ExceptionMessages::notAFiniteNumber(dirtyHeight, "dirtyHeight"));
-
- if (exceptionState.hadException())
- return;
+ putImageData(data, dx, dy, 0, 0, data->width(), data->height());
+}
+void CanvasRenderingContext2D::putImageData(ImageData* data, float dx, float dy, float dirtyX, float dirtyY, float dirtyWidth, float dirtyHeight)
+{
ImageBuffer* buffer = canvas()->buffer();
if (!buffer)
return;
StringBuilder serializedFont;
const FontDescription& fontDescription = state().m_font.fontDescription();
- if (fontDescription.italic())
+ if (fontDescription.style() == FontStyleItalic)
serializedFont.appendLiteral("italic ");
if (fontDescription.weight() == FontWeightBold)
serializedFont.appendLiteral("bold ");
- if (fontDescription.smallCaps() == FontSmallCapsOn)
+ if (fontDescription.variant() == FontVariantSmallCaps)
serializedFont.appendLiteral("small-caps ");
serializedFont.appendNumber(fontDescription.computedPixelSize());
void CanvasRenderingContext2D::setFont(const String& newFont)
{
- MutableStylePropertyMap::iterator i = m_fetchedFonts.find(newFont);
- RefPtr<MutableStylePropertySet> parsedStyle = i != m_fetchedFonts.end() ? i->value : 0;
+ // The style resolution required for rendering text is not available in frame-less documents.
+ if (!canvas()->document().frame())
+ return;
- if (!parsedStyle) {
+ RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle;
+ MutableStylePropertyMap::iterator i = m_fetchedFonts.find(newFont);
+ if (i != m_fetchedFonts.end()) {
+ parsedStyle = i->value;
+ m_fetchedFontsLRUList.remove(newFont);
+ } else {
parsedStyle = MutableStylePropertySet::create();
CSSParserMode mode = m_usesCSSCompatibilityParseMode ? HTMLQuirksMode : HTMLStandardMode;
- BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, newFont, true, mode, 0);
+ CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, newFont, true, mode, 0);
+ if (m_fetchedFonts.size() >= FetchedFontsCacheLimit) {
+ m_fetchedFonts.remove(m_fetchedFontsLRUList.first());
+ m_fetchedFontsLRUList.removeFirst();
+ }
m_fetchedFonts.add(newFont, parsedStyle);
}
+ m_fetchedFontsLRUList.add(newFont);
+
if (parsedStyle->isEmpty())
return;
// The parse succeeded.
String newFontSafeCopy(newFont); // Create a string copy since newFont can be deleted inside realizeSaves.
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_unparsedFont = newFontSafeCopy;
// Map the <canvas> font into the text style. If the font uses keywords like larger/smaller, these will work
// relative to the canvas.
RefPtr<RenderStyle> newStyle = RenderStyle::create();
- if (RenderStyle* computedStyle = canvas()->computedStyle())
- newStyle->setFontDescription(computedStyle->fontDescription());
- else {
+ canvas()->document().updateRenderTreeIfNeeded();
+ if (RenderStyle* computedStyle = canvas()->computedStyle()) {
+ FontDescription elementFontDescription(computedStyle->fontDescription());
+ // Reset the computed size to avoid inheriting the zoom factor from the <canvas> element.
+ elementFontDescription.setComputedSize(elementFontDescription.specifiedSize());
+ newStyle->setFontDescription(elementFontDescription);
+ } else {
FontFamily fontFamily;
fontFamily.setFamily(defaultFontFamily);
StyleResolver& styleResolver = canvas()->document().ensureStyleResolver();
styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), newStyle.get());
+#if !ENABLE(OILPAN)
if (state().m_realizedFont)
static_cast<CSSFontSelector*>(state().m_font.fontSelector())->unregisterForInvalidationCallbacks(&modifiableState());
+#endif
modifiableState().m_font = newStyle->font();
modifiableState().m_font.update(canvas()->document().styleEngine()->fontSelector());
modifiableState().m_realizedFont = true;
return;
if (state().m_textAlign == align)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_textAlign = align;
}
return;
if (state().m_textBaseline == baseline)
return;
- realizeSaves();
+ realizeSaves(nullptr);
modifiableState().m_textBaseline = baseline;
}
+inline TextDirection CanvasRenderingContext2D::toTextDirection(Direction direction, RenderStyle** computedStyle) const
+{
+ RenderStyle* style = (computedStyle || direction == DirectionInherit) ? canvas()->computedStyle() : nullptr;
+ if (computedStyle)
+ *computedStyle = style;
+ switch (direction) {
+ case DirectionInherit:
+ return style ? style->direction() : LTR;
+ case DirectionRTL:
+ return RTL;
+ case DirectionLTR:
+ return LTR;
+ }
+ ASSERT_NOT_REACHED();
+ return LTR;
+}
+
+String CanvasRenderingContext2D::direction() const
+{
+ if (state().m_direction == DirectionInherit)
+ canvas()->document().updateRenderTreeIfNeeded();
+ return toTextDirection(state().m_direction) == RTL ? rtl : ltr;
+}
+
+void CanvasRenderingContext2D::setDirection(const String& directionString)
+{
+ Direction direction;
+ if (directionString == inherit)
+ direction = DirectionInherit;
+ else if (directionString == rtl)
+ direction = DirectionRTL;
+ else if (directionString == ltr)
+ direction = DirectionLTR;
+ else
+ return;
+
+ if (state().m_direction == direction)
+ return;
+
+ realizeSaves(nullptr);
+ modifiableState().m_direction = direction;
+}
+
void CanvasRenderingContext2D::fillText(const String& text, float x, float y)
{
drawTextInternal(text, x, y, true);
drawTextInternal(text, x, y, false, maxWidth, true);
}
-PassRefPtr<TextMetrics> CanvasRenderingContext2D::measureText(const String& text)
+PassRefPtrWillBeRawPtr<TextMetrics> CanvasRenderingContext2D::measureText(const String& text)
{
+ RefPtrWillBeRawPtr<TextMetrics> metrics = TextMetrics::create();
+
+ // The style resolution required for rendering text is not available in frame-less documents.
+ if (!canvas()->document().frame())
+ return metrics.release();
+
FontCachePurgePreventer fontCachePurgePreventer;
- RefPtr<TextMetrics> metrics = TextMetrics::create();
- canvas()->document().updateStyleIfNeeded();
- metrics->setWidth(accessFont().width(TextRun(text)));
- return metrics.release();
-}
+ canvas()->document().updateRenderTreeIfNeeded();
+ const Font& font = accessFont();
+ const TextRun textRun(text, 0, 0, TextRun::AllowTrailingExpansion | TextRun::ForbidLeadingExpansion, LTR, false, true, true);
+ FloatRect textBounds = font.selectionRectForText(textRun, FloatPoint(), font.fontDescription().computedSize(), 0, -1, true);
-static void replaceCharacterInString(String& text, WTF::CharacterMatchFunctionPtr matchFunction, const String& replacement)
-{
- const size_t replacementLength = replacement.length();
- size_t index = 0;
- while ((index = text.find(matchFunction, index)) != kNotFound) {
- text.replace(index, 1, replacement);
- index += replacementLength;
- }
+ // x direction
+ metrics->setWidth(font.width(textRun));
+ metrics->setActualBoundingBoxLeft(-textBounds.x());
+ metrics->setActualBoundingBoxRight(textBounds.maxX());
+
+ // y direction
+ const FontMetrics& fontMetrics = font.fontMetrics();
+ const float ascent = fontMetrics.floatAscent();
+ const float descent = fontMetrics.floatDescent();
+ const float baselineY = getFontBaseline(fontMetrics);
+
+ metrics->setFontBoundingBoxAscent(ascent - baselineY);
+ metrics->setFontBoundingBoxDescent(descent + baselineY);
+ metrics->setActualBoundingBoxAscent(-textBounds.y() - baselineY);
+ metrics->setActualBoundingBoxDescent(textBounds.maxY() + baselineY);
+
+ // Note : top/bottom and ascend/descend are currently the same, so there's no difference
+ // between the EM box's top and bottom and the font's ascend and descend
+ metrics->setEmHeightAscent(0);
+ metrics->setEmHeightDescent(0);
+
+ metrics->setHangingBaseline(-0.8f * ascent + baselineY);
+ metrics->setAlphabeticBaseline(baselineY);
+ metrics->setIdeographicBaseline(descent + baselineY);
+ return metrics.release();
}
void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, float y, bool fill, float maxWidth, bool useMaxWidth)
{
+ // The style resolution required for rendering text is not available in frame-less documents.
+ if (!canvas()->document().frame())
+ return;
+
// accessFont needs the style to be up to date, but updating style can cause script to run,
// (e.g. due to autofocus) which can free the GraphicsContext, so update style before grabbing
// the GraphicsContext.
- canvas()->document().updateStyleIfNeeded();
+ canvas()->document().updateRenderTreeIfNeeded();
GraphicsContext* c = drawingContext();
if (!c)
return;
if (!state().m_invertibleCTM)
return;
- if (!std::isfinite(x) | !std::isfinite(y))
+ if (!std::isfinite(x) || !std::isfinite(y))
return;
if (useMaxWidth && (!std::isfinite(maxWidth) || maxWidth <= 0))
return;
const Font& font = accessFont();
const FontMetrics& fontMetrics = font.fontMetrics();
- // According to spec, all the space characters must be replaced with U+0020 SPACE characters.
- String normalizedText = text;
- replaceCharacterInString(normalizedText, isSpaceOrNewline, " ");
// FIXME: Need to turn off font smoothing.
- RenderStyle* computedStyle = canvas()->computedStyle();
- TextDirection direction = computedStyle ? computedStyle->direction() : LTR;
+ RenderStyle* computedStyle;
+ TextDirection direction = toTextDirection(state().m_direction, &computedStyle);
bool isRTL = direction == RTL;
bool override = computedStyle ? isOverride(computedStyle->unicodeBidi()) : false;
- TextRun textRun(normalizedText, 0, 0, TextRun::AllowTrailingExpansion, direction, override, true, TextRun::NoRounding);
+ TextRun textRun(text, 0, 0, TextRun::AllowTrailingExpansion, direction, override, true, true);
// Draw the item text at the correct point.
- FloatPoint location(x, y);
- switch (state().m_textBaseline) {
- case TopTextBaseline:
- case HangingTextBaseline:
- location.setY(y + fontMetrics.ascent());
- break;
- case BottomTextBaseline:
- case IdeographicTextBaseline:
- location.setY(y - fontMetrics.descent());
- break;
- case MiddleTextBaseline:
- location.setY(y - fontMetrics.descent() + fontMetrics.height() / 2);
- break;
- case AlphabeticTextBaseline:
- default:
- // Do nothing.
- break;
- }
-
- float fontWidth = font.width(TextRun(normalizedText, 0, 0, TextRun::AllowTrailingExpansion, direction, override));
+ FloatPoint location(x, y + getFontBaseline(fontMetrics));
+ float fontWidth = font.width(textRun);
useMaxWidth = (useMaxWidth && maxWidth < fontWidth);
float width = useMaxWidth ? maxWidth : fontWidth;
if (!fill)
inflateStrokeRect(textRunPaintInfo.bounds);
- FloatRect dirtyRect;
- if (!computeDirtyRect(textRunPaintInfo.bounds, &dirtyRect))
- return;
-
c->setTextDrawingMode(fill ? TextModeFill : TextModeStroke);
+
+ GraphicsContextStateSaver stateSaver(*c);
if (useMaxWidth) {
- GraphicsContextStateSaver stateSaver(*c);
c->translate(location.x(), location.y());
// We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work.
- c->scale(FloatSize((fontWidth > 0 ? (width / fontWidth) : 0), 1));
- c->drawBidiText(font, textRunPaintInfo, FloatPoint(0, 0), Font::UseFallbackIfFontNotReady);
- } else
- c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady);
+ c->scale((fontWidth > 0 ? (width / fontWidth) : 0), 1);
+ location = FloatPoint();
+ }
- didDraw(dirtyRect);
+ FloatRect clipBounds;
+ if (!c->getTransformedClipBounds(&clipBounds)) {
+ return;
+ }
+
+ if (isFullCanvasCompositeMode(state().m_globalComposite)) {
+ fullCanvasCompositedDraw(bind(&GraphicsContext::drawBidiText, c, font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady));
+ didDraw(clipBounds);
+ } else if (state().m_globalComposite == CompositeCopy) {
+ clearCanvas();
+ c->clearShadow();
+ c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady);
+ applyShadow(DrawShadowAndForeground);
+ didDraw(clipBounds);
+ } else {
+ FloatRect dirtyRect;
+ if (computeDirtyRect(textRunPaintInfo.bounds, clipBounds, &dirtyRect)) {
+ c->drawBidiText(font, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady);
+ didDraw(dirtyRect);
+ }
+ }
}
void CanvasRenderingContext2D::inflateStrokeRect(FloatRect& rect) const
return state().m_font;
}
-blink::WebLayer* CanvasRenderingContext2D::platformLayer() const
+int CanvasRenderingContext2D::getFontBaseline(const FontMetrics& fontMetrics) const
+{
+ switch (state().m_textBaseline) {
+ case TopTextBaseline:
+ return fontMetrics.ascent();
+ case HangingTextBaseline:
+ // According to http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
+ // "FOP (Formatting Objects Processor) puts the hanging baseline at 80% of the ascender height"
+ return (fontMetrics.ascent() * 4) / 5;
+ case BottomTextBaseline:
+ case IdeographicTextBaseline:
+ return -fontMetrics.descent();
+ case MiddleTextBaseline:
+ return -fontMetrics.descent() + fontMetrics.height() / 2;
+ case AlphabeticTextBaseline:
+ default:
+ // Do nothing.
+ break;
+ }
+ return 0;
+}
+
+void CanvasRenderingContext2D::setIsHidden(bool hidden)
+{
+ ImageBuffer* buffer = canvas()->buffer();
+ if (buffer)
+ buffer->setIsHidden(hidden);
+}
+
+WebLayer* CanvasRenderingContext2D::platformLayer() const
{
return canvas()->buffer() ? canvas()->buffer()->platformLayer() : 0;
}
if (enabled == state().m_imageSmoothingEnabled)
return;
- realizeSaves();
- modifiableState().m_imageSmoothingEnabled = enabled;
GraphicsContext* c = drawingContext();
+ realizeSaves(c);
+ modifiableState().m_imageSmoothingEnabled = enabled;
if (c)
- c->setImageInterpolationQuality(enabled ? DefaultInterpolationQuality : InterpolationNone);
+ c->setImageInterpolationQuality(enabled ? CanvasDefaultInterpolationQuality : InterpolationNone);
}
-PassRefPtr<Canvas2DContextAttributes> CanvasRenderingContext2D::getContextAttributes() const
+PassRefPtrWillBeRawPtr<Canvas2DContextAttributes> CanvasRenderingContext2D::getContextAttributes() const
{
- RefPtr<Canvas2DContextAttributes> attributes = Canvas2DContextAttributes::create();
+ RefPtrWillBeRawPtr<Canvas2DContextAttributes> attributes = Canvas2DContextAttributes::create();
attributes->setAlpha(m_hasAlpha);
return attributes.release();
}
-void CanvasRenderingContext2D::drawSystemFocusRing(Element* element)
+void CanvasRenderingContext2D::drawFocusIfNeeded(Element* element)
+{
+ drawFocusIfNeededInternal(m_path, element);
+}
+
+void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D* path2d, Element* element)
+{
+ drawFocusIfNeededInternal(path2d->path(), element);
+}
+
+void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Element* element)
{
- if (!focusRingCallIsValid(m_path, element))
+ if (!focusRingCallIsValid(path, element))
return;
- updateFocusRingAccessibility(m_path, element);
// Note: we need to check document->focusedElement() rather than just calling
// element->focused(), because element->focused() isn't updated until after
// focus events fire.
if (element->document().focusedElement() == element)
- drawFocusRing(m_path);
-}
-
-bool CanvasRenderingContext2D::drawCustomFocusRing(Element* element)
-{
- if (!focusRingCallIsValid(m_path, element))
- return false;
-
- updateFocusRingAccessibility(m_path, element);
-
- // Return true if the application should draw the focus ring. The spec allows us to
- // override this for accessibility, but currently Blink doesn't take advantage of this.
- return element->focused();
+ drawFocusRing(path);
}
bool CanvasRenderingContext2D::focusRingCallIsValid(const Path& path, Element* element)
{
+ ASSERT(element);
if (!state().m_invertibleCTM)
return false;
if (path.isEmpty())
return true;
}
-void CanvasRenderingContext2D::updateFocusRingAccessibility(const Path& path, Element* element)
+void CanvasRenderingContext2D::drawFocusRing(const Path& path)
{
- if (!canvas()->renderer())
+ GraphicsContext* c = drawingContext();
+ if (!c)
return;
- // If accessibility is already enabled in this frame, associate this path's
- // bounding box with the accessible object. Do this even if the element
- // isn't focused because assistive technology might try to explore the object's
- // location before it gets focus.
- if (AXObjectCache* axObjectCache = element->document().existingAXObjectCache()) {
- if (AXObject* obj = axObjectCache->getOrCreate(element)) {
- // Get the bounding rect and apply transformations.
- FloatRect bounds = m_path.boundingRect();
- AffineTransform ctm = state().m_transform;
- FloatRect transformedBounds = ctm.mapRect(bounds);
- LayoutRect elementRect = LayoutRect(transformedBounds);
+ // These should match the style defined in html.css.
+ Color focusRingColor = RenderTheme::theme().focusRingColor();
+ const int focusRingWidth = 5;
+ const int focusRingOutline = 0;
+
+ // We need to add focusRingWidth to dirtyRect.
+ StrokeData strokeData;
+ strokeData.setThickness(focusRingWidth);
- // Offset by the canvas rect and set the bounds of the accessible element.
- IntRect canvasRect = canvas()->renderer()->absoluteBoundingBoxRect();
- elementRect.moveBy(canvasRect.location());
- obj->setElementRect(elementRect);
+ FloatRect dirtyRect;
+ if (!computeDirtyRect(path.strokeBoundingRect(strokeData), &dirtyRect))
+ return;
- // Set the bounds of any ancestor accessible elements, up to the canvas element,
- // otherwise this element will appear to not be within its parent element.
- obj = obj->parentObject();
- while (obj && obj->node() != canvas()) {
- obj->setElementRect(elementRect);
- obj = obj->parentObject();
- }
- }
- }
+ c->save();
+ c->setAlphaAsFloat(1.0);
+ c->clearShadow();
+ c->setCompositeOperation(CompositeSourceOver, WebBlendModeNormal);
+ c->drawFocusRing(path, focusRingWidth, focusRingOutline, focusRingColor);
+ c->restore();
+ validateStateStack();
+ didDraw(dirtyRect);
}
-void CanvasRenderingContext2D::drawFocusRing(const Path& path)
+void CanvasRenderingContext2D::addHitRegion(const HitRegionOptions& options, ExceptionState& exceptionState)
{
- GraphicsContext* c = drawingContext();
- if (!c)
+ if (options.id().isEmpty() && !options.control()) {
+ exceptionState.throwDOMException(NotSupportedError, "Both id and control are null.");
return;
+ }
- FloatRect dirtyRect;
- if (!computeDirtyRect(path.boundingRect(), &dirtyRect))
+ Path hitRegionPath = options.hasPath() ? options.path()->path() : m_path;
+
+ FloatRect clipBounds;
+ GraphicsContext* context = drawingContext();
+
+ if (hitRegionPath.isEmpty() || !context || !state().m_invertibleCTM
+ || !context->getTransformedClipBounds(&clipBounds)) {
+ exceptionState.throwDOMException(NotSupportedError, "The specified path has no pixels.");
return;
+ }
- c->save();
- c->setAlpha(1.0);
- c->clearShadow();
- c->setCompositeOperation(CompositeSourceOver, blink::WebBlendModeNormal);
+ hitRegionPath.transform(state().m_transform);
- // These should match the style defined in html.css.
- Color focusRingColor = RenderTheme::theme().focusRingColor();
- const int focusRingWidth = 5;
- const int focusRingOutline = 0;
- c->drawFocusRing(path, focusRingWidth, focusRingOutline, focusRingColor);
+ if (hasClip()) {
+ // FIXME: The hit regions should take clipping region into account.
+ // However, we have no way to get the region from canvas state stack by now.
+ // See http://crbug.com/387057
+ exceptionState.throwDOMException(NotSupportedError, "The specified path has no pixels.");
+ return;
+ }
- c->restore();
+ if (!m_hitRegionManager)
+ m_hitRegionManager = HitRegionManager::create();
- didDraw(dirtyRect);
+ // Remove previous region (with id or control)
+ m_hitRegionManager->removeHitRegionById(options.id());
+ m_hitRegionManager->removeHitRegionByControl(options.control().get());
+
+ RefPtrWillBeRawPtr<HitRegion> hitRegion = HitRegion::create(hitRegionPath, options);
+ hitRegion->updateAccessibility(canvas());
+ m_hitRegionManager->addHitRegion(hitRegion.release());
+}
+
+void CanvasRenderingContext2D::removeHitRegion(const String& id)
+{
+ if (m_hitRegionManager)
+ m_hitRegionManager->removeHitRegionById(id);
+}
+
+void CanvasRenderingContext2D::clearHitRegions()
+{
+ if (m_hitRegionManager)
+ m_hitRegionManager->removeAllHitRegions();
+}
+
+HitRegion* CanvasRenderingContext2D::hitRegionAtPoint(const LayoutPoint& point)
+{
+ if (m_hitRegionManager)
+ return m_hitRegionManager->getHitRegionAtPoint(point);
+
+ return nullptr;
+}
+
+unsigned CanvasRenderingContext2D::hitRegionsCount() const
+{
+ if (m_hitRegionManager)
+ return m_hitRegionManager->getHitRegionsCount();
+
+ return 0;
}
-} // namespace WebCore
+} // namespace blink