michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CanvasRenderingContext2D.h" michael@0: michael@0: #include "nsXULElement.h" michael@0: michael@0: #include "nsIServiceManager.h" michael@0: #include "nsMathUtils.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: michael@0: #include "nsIDocument.h" michael@0: #include "mozilla/dom/HTMLCanvasElement.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIPresShell.h" michael@0: michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsError.h" michael@0: michael@0: #include "nsCSSParser.h" michael@0: #include "mozilla/css/StyleRule.h" michael@0: #include "mozilla/css/Declaration.h" michael@0: #include "mozilla/css/Loader.h" michael@0: #include "nsComputedDOMStyle.h" michael@0: #include "nsStyleSet.h" michael@0: michael@0: #include "nsPrintfCString.h" michael@0: michael@0: #include "nsReadableUtils.h" michael@0: michael@0: #include "nsColor.h" michael@0: #include "nsGfxCIID.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsFocusManager.h" michael@0: michael@0: #include "nsTArray.h" michael@0: michael@0: #include "ImageEncoder.h" michael@0: michael@0: #include "gfxContext.h" michael@0: #include "gfxASurface.h" michael@0: #include "gfxImageSurface.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxFont.h" michael@0: #include "gfxBlur.h" michael@0: #include "gfxUtils.h" michael@0: michael@0: #include "nsFrameManager.h" michael@0: #include "nsFrameLoader.h" michael@0: #include "nsBidi.h" michael@0: #include "nsBidiPresUtils.h" michael@0: #include "Layers.h" michael@0: #include "CanvasUtils.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "nsStyleUtil.h" michael@0: #include "CanvasImageCache.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: michael@0: #include "mozilla/Alignment.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/CheckedInt.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "mozilla/dom/ImageData.h" michael@0: #include "mozilla/dom/PBrowserParent.h" michael@0: #include "mozilla/dom/ToJSValue.h" michael@0: #include "mozilla/dom/TypedArray.h" michael@0: #include "mozilla/Endian.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/gfx/PathHelpers.h" michael@0: #include "mozilla/gfx/DataSurfaceHelpers.h" michael@0: #include "mozilla/ipc/DocumentRendererParent.h" michael@0: #include "mozilla/ipc/PDocumentRendererParent.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/unused.h" michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "mozilla/dom/CanvasRenderingContext2DBinding.h" michael@0: #include "mozilla/dom/HTMLImageElement.h" michael@0: #include "mozilla/dom/HTMLVideoElement.h" michael@0: #include "mozilla/dom/TextMetrics.h" michael@0: #include "mozilla/dom/UnionTypes.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "GLContext.h" michael@0: #include "GLContextProvider.h" michael@0: #include "SVGContentUtils.h" michael@0: #include "nsIScreenManager.h" michael@0: michael@0: #undef free // apparently defined by some windows header, clashing with a free() michael@0: // method in SkTypes.h michael@0: #ifdef USE_SKIA michael@0: #include "SkiaGLGlue.h" michael@0: #include "SurfaceStream.h" michael@0: #include "SurfaceTypes.h" michael@0: #endif michael@0: michael@0: using mozilla::gl::GLContext; michael@0: using mozilla::gl::SkiaGLGlue; michael@0: using mozilla::gl::GLContextProvider; michael@0: michael@0: #ifdef XP_WIN michael@0: #include "gfxWindowsPlatform.h" michael@0: #endif michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include "mozilla/layers/ShadowLayers.h" michael@0: #endif michael@0: michael@0: // windows.h (included by chromium code) defines this, in its infinite wisdom michael@0: #undef DrawText michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::CanvasUtils; michael@0: using namespace mozilla::css; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::ipc; michael@0: using namespace mozilla::layers; michael@0: michael@0: namespace mgfx = mozilla::gfx; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: // Cap sigma to avoid overly large temp surfaces. michael@0: const Float SIGMA_MAX = 100; michael@0: michael@0: /* Memory reporter stuff */ michael@0: static int64_t gCanvasAzureMemoryUsed = 0; michael@0: michael@0: // This is KIND_OTHER because it's not always clear where in memory the pixels michael@0: // of a canvas are stored. Furthermore, this memory will be tracked by the michael@0: // underlying surface implementations. See bug 655638 for details. michael@0: class Canvas2dPixelsReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: return MOZ_COLLECT_REPORT( michael@0: "canvas-2d-pixels", KIND_OTHER, UNITS_BYTES, michael@0: gCanvasAzureMemoryUsed, michael@0: "Memory used by 2D canvases. Each canvas requires " michael@0: "(width * height * 4) bytes."); michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter) michael@0: michael@0: class CanvasRadialGradient : public CanvasGradient michael@0: { michael@0: public: michael@0: CanvasRadialGradient(CanvasRenderingContext2D* aContext, michael@0: mozilla::css::Loader *aLoader, michael@0: const Point &aBeginOrigin, Float aBeginRadius, michael@0: const Point &aEndOrigin, Float aEndRadius) michael@0: : CanvasGradient(aContext, aLoader, Type::RADIAL) michael@0: , mCenter1(aBeginOrigin) michael@0: , mCenter2(aEndOrigin) michael@0: , mRadius1(aBeginRadius) michael@0: , mRadius2(aEndRadius) michael@0: { michael@0: } michael@0: michael@0: Point mCenter1; michael@0: Point mCenter2; michael@0: Float mRadius1; michael@0: Float mRadius2; michael@0: }; michael@0: michael@0: class CanvasLinearGradient : public CanvasGradient michael@0: { michael@0: public: michael@0: CanvasLinearGradient(CanvasRenderingContext2D* aContext, michael@0: mozilla::css::Loader *aLoader, michael@0: const Point &aBegin, const Point &aEnd) michael@0: : CanvasGradient(aContext, aLoader, Type::LINEAR) michael@0: , mBegin(aBegin) michael@0: , mEnd(aEnd) michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: friend class CanvasGeneralPattern; michael@0: michael@0: // Beginning of linear gradient. michael@0: Point mBegin; michael@0: // End of linear gradient. michael@0: Point mEnd; michael@0: }; michael@0: michael@0: // This class is named 'GeneralCanvasPattern' instead of just michael@0: // 'GeneralPattern' to keep Windows PGO builds from confusing the michael@0: // GeneralPattern class in gfxContext.cpp with this one. michael@0: michael@0: class CanvasGeneralPattern michael@0: { michael@0: public: michael@0: typedef CanvasRenderingContext2D::Style Style; michael@0: typedef CanvasRenderingContext2D::ContextState ContextState; michael@0: michael@0: CanvasGeneralPattern() : mPattern(nullptr) {} michael@0: ~CanvasGeneralPattern() michael@0: { michael@0: if (mPattern) { michael@0: mPattern->~Pattern(); michael@0: } michael@0: } michael@0: michael@0: Pattern& ForStyle(CanvasRenderingContext2D *aCtx, michael@0: Style aStyle, michael@0: DrawTarget *aRT) michael@0: { michael@0: // This should only be called once or the mPattern destructor will michael@0: // not be executed. michael@0: NS_ASSERTION(!mPattern, "ForStyle() should only be called once on CanvasGeneralPattern!"); michael@0: michael@0: const ContextState &state = aCtx->CurrentState(); michael@0: michael@0: if (state.StyleIsColor(aStyle)) { michael@0: mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle])); michael@0: } else if (state.gradientStyles[aStyle] && michael@0: state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) { michael@0: CanvasLinearGradient *gradient = michael@0: static_cast(state.gradientStyles[aStyle].get()); michael@0: michael@0: mPattern = new (mLinearGradientPattern.addr()) michael@0: LinearGradientPattern(gradient->mBegin, gradient->mEnd, michael@0: gradient->GetGradientStopsForTarget(aRT)); michael@0: } else if (state.gradientStyles[aStyle] && michael@0: state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) { michael@0: CanvasRadialGradient *gradient = michael@0: static_cast(state.gradientStyles[aStyle].get()); michael@0: michael@0: mPattern = new (mRadialGradientPattern.addr()) michael@0: RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, michael@0: gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT)); michael@0: } else if (state.patternStyles[aStyle]) { michael@0: if (aCtx->mCanvasElement) { michael@0: CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement, michael@0: state.patternStyles[aStyle]->mPrincipal, michael@0: state.patternStyles[aStyle]->mForceWriteOnly, michael@0: state.patternStyles[aStyle]->mCORSUsed); michael@0: } michael@0: michael@0: ExtendMode mode; michael@0: if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) { michael@0: mode = ExtendMode::CLAMP; michael@0: } else { michael@0: mode = ExtendMode::REPEAT; michael@0: } michael@0: mPattern = new (mSurfacePattern.addr()) michael@0: SurfacePattern(state.patternStyles[aStyle]->mSurface, mode); michael@0: } michael@0: michael@0: return *mPattern; michael@0: } michael@0: michael@0: union { michael@0: AlignedStorage2 mColorPattern; michael@0: AlignedStorage2 mLinearGradientPattern; michael@0: AlignedStorage2 mRadialGradientPattern; michael@0: AlignedStorage2 mSurfacePattern; michael@0: }; michael@0: Pattern *mPattern; michael@0: }; michael@0: michael@0: /* This is an RAII based class that can be used as a drawtarget for michael@0: * operations that need a shadow drawn. It will automatically provide a michael@0: * temporary target when needed, and if so blend it back with a shadow. michael@0: * michael@0: * aBounds specifies the bounds of the drawing operation that will be michael@0: * drawn to the target, it is given in device space! This function will michael@0: * change aBounds to incorporate shadow bounds. If this is nullptr the drawing michael@0: * operation will be assumed to cover an infinite rect. michael@0: */ michael@0: class AdjustedTarget michael@0: { michael@0: public: michael@0: typedef CanvasRenderingContext2D::ContextState ContextState; michael@0: michael@0: AdjustedTarget(CanvasRenderingContext2D *ctx, michael@0: mgfx::Rect *aBounds = nullptr) michael@0: : mCtx(nullptr) michael@0: { michael@0: if (!ctx->NeedToDrawShadow()) { michael@0: mTarget = ctx->mTarget; michael@0: return; michael@0: } michael@0: mCtx = ctx; michael@0: michael@0: const ContextState &state = mCtx->CurrentState(); michael@0: michael@0: mSigma = state.shadowBlur / 2.0f; michael@0: michael@0: if (mSigma > SIGMA_MAX) { michael@0: mSigma = SIGMA_MAX; michael@0: } michael@0: michael@0: Matrix transform = mCtx->mTarget->GetTransform(); michael@0: michael@0: mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight); michael@0: michael@0: static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; michael@0: int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5); michael@0: michael@0: // We need to enlarge and possibly offset our temporary surface michael@0: // so that things outside of the canvas may cast shadows. michael@0: mTempRect.Inflate(Margin(blurRadius + std::max(state.shadowOffset.y, 0), michael@0: blurRadius + std::max(-state.shadowOffset.x, 0), michael@0: blurRadius + std::max(-state.shadowOffset.y, 0), michael@0: blurRadius + std::max(state.shadowOffset.x, 0))); michael@0: michael@0: if (aBounds) { michael@0: // We actually include the bounds of the shadow blur, this makes it michael@0: // easier to execute the actual blur on hardware, and shouldn't affect michael@0: // the amount of pixels that need to be touched. michael@0: aBounds->Inflate(Margin(blurRadius, blurRadius, michael@0: blurRadius, blurRadius)); michael@0: mTempRect = mTempRect.Intersect(*aBounds); michael@0: } michael@0: michael@0: mTempRect.ScaleRoundOut(1.0f); michael@0: michael@0: transform._31 -= mTempRect.x; michael@0: transform._32 -= mTempRect.y; michael@0: michael@0: mTarget = michael@0: mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)), michael@0: SurfaceFormat::B8G8R8A8, mSigma); michael@0: michael@0: if (!mTarget) { michael@0: // XXX - Deal with the situation where our temp size is too big to michael@0: // fit in a texture. michael@0: mTarget = ctx->mTarget; michael@0: mCtx = nullptr; michael@0: } else { michael@0: mTarget->SetTransform(transform); michael@0: } michael@0: } michael@0: michael@0: ~AdjustedTarget() michael@0: { michael@0: if (!mCtx) { michael@0: return; michael@0: } michael@0: michael@0: RefPtr snapshot = mTarget->Snapshot(); michael@0: michael@0: mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), michael@0: Color::FromABGR(mCtx->CurrentState().shadowColor), michael@0: mCtx->CurrentState().shadowOffset, mSigma, michael@0: mCtx->CurrentState().op); michael@0: } michael@0: michael@0: operator DrawTarget*() michael@0: { michael@0: return mTarget; michael@0: } michael@0: michael@0: DrawTarget* operator->() michael@0: { michael@0: return mTarget; michael@0: } michael@0: michael@0: private: michael@0: RefPtr mTarget; michael@0: CanvasRenderingContext2D *mCtx; michael@0: Float mSigma; michael@0: mgfx::Rect mTempRect; michael@0: }; michael@0: michael@0: void michael@0: CanvasGradient::AddColorStop(float offset, const nsAString& colorstr, ErrorResult& rv) michael@0: { michael@0: if (offset < 0.0 || offset > 1.0) { michael@0: rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsCSSValue value; michael@0: nsCSSParser parser; michael@0: if (!parser.ParseColorString(colorstr, nullptr, 0, value)) { michael@0: rv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsIPresShell* presShell = nullptr; michael@0: if (mCSSLoader) { michael@0: nsIDocument *doc = mCSSLoader->GetDocument(); michael@0: if (doc) michael@0: presShell = doc->GetShell(); michael@0: } michael@0: michael@0: nscolor color; michael@0: if (!nsRuleNode::ComputeColor(value, presShell ? presShell->GetPresContext() : nullptr, michael@0: nullptr, color)) { michael@0: rv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: mStops = nullptr; michael@0: michael@0: GradientStop newStop; michael@0: michael@0: newStop.offset = offset; michael@0: newStop.color = Color::FromABGR(color); michael@0: michael@0: mRawStops.AppendElement(newStop); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasGradient, mContext) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasPattern, mContext) michael@0: michael@0: class CanvasRenderingContext2DUserData : public LayerUserData { michael@0: public: michael@0: CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext) michael@0: : mContext(aContext) michael@0: { michael@0: aContext->mUserDatas.AppendElement(this); michael@0: } michael@0: ~CanvasRenderingContext2DUserData() michael@0: { michael@0: if (mContext) { michael@0: mContext->mUserDatas.RemoveElement(this); michael@0: } michael@0: } michael@0: michael@0: static void PreTransactionCallback(void* aData) michael@0: { michael@0: CanvasRenderingContext2DUserData* self = michael@0: static_cast(aData); michael@0: CanvasRenderingContext2D* context = self->mContext; michael@0: if (!context || !context->mStream || !context->mTarget) michael@0: return; michael@0: michael@0: // Since SkiaGL default to store drawing command until flush michael@0: // We will have to flush it before present. michael@0: context->mTarget->Flush(); michael@0: } michael@0: michael@0: static void DidTransactionCallback(void* aData) michael@0: { michael@0: CanvasRenderingContext2DUserData* self = michael@0: static_cast(aData); michael@0: if (self->mContext) { michael@0: self->mContext->MarkContextClean(); michael@0: } michael@0: } michael@0: bool IsForContext(CanvasRenderingContext2D *aContext) michael@0: { michael@0: return mContext == aContext; michael@0: } michael@0: void Forget() michael@0: { michael@0: mContext = nullptr; michael@0: } michael@0: michael@0: private: michael@0: CanvasRenderingContext2D *mContext; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement) michael@0: for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { michael@0: ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]); michael@0: ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]); michael@0: ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]); michael@0: ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement) michael@0: for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { michael@0: ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern"); michael@0: ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern"); michael@0: ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient"); michael@0: ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient"); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) michael@0: if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { michael@0: dom::Element* canvasElement = tmp->mCanvasElement; michael@0: if (canvasElement) { michael@0: if (canvasElement->IsPurple()) { michael@0: canvasElement->RemovePurple(); michael@0: } michael@0: dom::Element::MarkNodeChildren(canvasElement); michael@0: } michael@0: return true; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D) michael@0: return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) michael@0: return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: /** michael@0: ** CanvasRenderingContext2D impl michael@0: **/ michael@0: michael@0: michael@0: // Initialize our static variables. michael@0: uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; michael@0: DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; michael@0: michael@0: michael@0: michael@0: CanvasRenderingContext2D::CanvasRenderingContext2D() michael@0: : mForceSoftware(false), mZero(false), mOpaque(false), mResetLayer(true) michael@0: , mIPC(false) michael@0: , mStream(nullptr) michael@0: , mIsEntireFrameInvalid(false) michael@0: , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) michael@0: , mInvalidateCount(0) michael@0: { michael@0: sNumLivingContexts++; michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: CanvasRenderingContext2D::~CanvasRenderingContext2D() michael@0: { michael@0: Reset(); michael@0: // Drop references from all CanvasRenderingContext2DUserData to this context michael@0: for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { michael@0: mUserDatas[i]->Forget(); michael@0: } michael@0: sNumLivingContexts--; michael@0: if (!sNumLivingContexts) { michael@0: NS_IF_RELEASE(sErrorTarget); michael@0: } michael@0: michael@0: RemoveDemotableContext(this); michael@0: } michael@0: michael@0: JSObject* michael@0: CanvasRenderingContext2D::WrapObject(JSContext *cx) michael@0: { michael@0: return CanvasRenderingContext2DBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: bool michael@0: CanvasRenderingContext2D::ParseColor(const nsAString& aString, michael@0: nscolor* aColor) michael@0: { michael@0: nsIDocument* document = mCanvasElement michael@0: ? mCanvasElement->OwnerDoc() michael@0: : nullptr; michael@0: michael@0: // Pass the CSS Loader object to the parser, to allow parser error michael@0: // reports to include the outer window ID. michael@0: nsCSSParser parser(document ? document->CSSLoader() : nullptr); michael@0: nsCSSValue value; michael@0: if (!parser.ParseColorString(aString, nullptr, 0, value)) { michael@0: return false; michael@0: } michael@0: michael@0: if (value.IsNumericColorUnit()) { michael@0: // if we already have a color we can just use it directly michael@0: *aColor = value.GetColorValue(); michael@0: } else { michael@0: // otherwise resolve it michael@0: nsIPresShell* presShell = GetPresShell(); michael@0: nsRefPtr parentContext; michael@0: if (mCanvasElement && mCanvasElement->IsInDoc()) { michael@0: // Inherit from the canvas element. michael@0: parentContext = nsComputedDOMStyle::GetStyleContextForElement( michael@0: mCanvasElement, nullptr, presShell); michael@0: } michael@0: michael@0: unused << nsRuleNode::ComputeColor( michael@0: value, presShell ? presShell->GetPresContext() : nullptr, parentContext, michael@0: *aColor); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: PLDHashOperator michael@0: CanvasRenderingContext2D::RemoveHitRegionProperty(RegionInfo* aEntry, void*) michael@0: { michael@0: aEntry->mElement->DeleteProperty(nsGkAtoms::hitregion); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: #endif michael@0: michael@0: nsresult michael@0: CanvasRenderingContext2D::Reset() michael@0: { michael@0: if (mCanvasElement) { michael@0: mCanvasElement->InvalidateCanvas(); michael@0: } michael@0: michael@0: // only do this for non-docshell created contexts, michael@0: // since those are the ones that we created a surface for michael@0: if (mTarget && IsTargetValid() && !mDocShell) { michael@0: gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; michael@0: } michael@0: michael@0: mTarget = nullptr; michael@0: mStream = nullptr; michael@0: michael@0: // reset hit regions michael@0: #ifdef ACCESSIBILITY michael@0: mHitRegionsOptions.EnumerateEntries(RemoveHitRegionProperty, nullptr); michael@0: #endif michael@0: mHitRegionsOptions.Clear(); michael@0: michael@0: // Since the target changes the backing texture will change, and this will michael@0: // no longer be valid. michael@0: mIsEntireFrameInvalid = false; michael@0: mPredictManyRedrawCalls = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetStyleFromString(const nsAString& str, michael@0: Style whichStyle) michael@0: { michael@0: MOZ_ASSERT(!str.IsVoid()); michael@0: michael@0: nscolor color; michael@0: if (!ParseColor(str, &color)) { michael@0: return; michael@0: } michael@0: michael@0: CurrentState().SetColorStyle(whichStyle, color); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue, michael@0: Style aWhichStyle) michael@0: { michael@0: const ContextState &state = CurrentState(); michael@0: if (state.patternStyles[aWhichStyle]) { michael@0: aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle]; michael@0: } else if (state.gradientStyles[aWhichStyle]) { michael@0: aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle]; michael@0: } else { michael@0: StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString()); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: void michael@0: CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) michael@0: { michael@0: // We can't reuse the normal CSS color stringification code, michael@0: // because the spec calls for a different algorithm for canvas. michael@0: if (NS_GET_A(aColor) == 255) { michael@0: CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", michael@0: NS_GET_R(aColor), michael@0: NS_GET_G(aColor), michael@0: NS_GET_B(aColor)), michael@0: aStr); michael@0: } else { michael@0: CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", michael@0: NS_GET_R(aColor), michael@0: NS_GET_G(aColor), michael@0: NS_GET_B(aColor)), michael@0: aStr); michael@0: aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); michael@0: aStr.Append(')'); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: CanvasRenderingContext2D::Redraw() michael@0: { michael@0: if (mIsEntireFrameInvalid) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mIsEntireFrameInvalid = true; michael@0: michael@0: if (!mCanvasElement) { michael@0: NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); michael@0: michael@0: mCanvasElement->InvalidateCanvasContent(nullptr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) michael@0: { michael@0: ++mInvalidateCount; michael@0: michael@0: if (mIsEntireFrameInvalid) { michael@0: return; michael@0: } michael@0: michael@0: if (mPredictManyRedrawCalls || michael@0: mInvalidateCount > kCanvasMaxInvalidateCount) { michael@0: Redraw(); michael@0: return; michael@0: } michael@0: michael@0: if (!mCanvasElement) { michael@0: NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); michael@0: return; michael@0: } michael@0: michael@0: nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); michael@0: michael@0: mCanvasElement->InvalidateCanvasContent(&r); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::RedrawUser(const gfxRect& r) michael@0: { michael@0: if (mIsEntireFrameInvalid) { michael@0: ++mInvalidateCount; michael@0: return; michael@0: } michael@0: michael@0: mgfx::Rect newr = michael@0: mTarget->GetTransform().TransformBounds(ToRect(r)); michael@0: Redraw(newr); michael@0: } michael@0: michael@0: void CanvasRenderingContext2D::Demote() michael@0: { michael@0: if (!IsTargetValid() || mForceSoftware || !mStream) michael@0: return; michael@0: michael@0: RemoveDemotableContext(this); michael@0: michael@0: RefPtr snapshot = mTarget->Snapshot(); michael@0: RefPtr oldTarget = mTarget; michael@0: mTarget = nullptr; michael@0: mStream = nullptr; michael@0: mResetLayer = true; michael@0: mForceSoftware = true; michael@0: michael@0: // Recreate target, now demoted to software only michael@0: EnsureTarget(); michael@0: if (!IsTargetValid()) michael@0: return; michael@0: michael@0: // Restore the content from the old DrawTarget michael@0: mgfx::Rect r(0, 0, mWidth, mHeight); michael@0: mTarget->DrawSurface(snapshot, r, r); michael@0: michael@0: // Restore the clips and transform michael@0: for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { michael@0: mTarget->PushClip(CurrentState().clipsPushed[i]); michael@0: } michael@0: michael@0: mTarget->SetTransform(oldTarget->GetTransform()); michael@0: } michael@0: michael@0: std::vector& michael@0: CanvasRenderingContext2D::DemotableContexts() michael@0: { michael@0: static std::vector contexts; michael@0: return contexts; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::DemoteOldestContextIfNecessary() michael@0: { michael@0: const size_t kMaxContexts = 64; michael@0: michael@0: std::vector& contexts = DemotableContexts(); michael@0: if (contexts.size() < kMaxContexts) michael@0: return; michael@0: michael@0: CanvasRenderingContext2D* oldest = contexts.front(); michael@0: oldest->Demote(); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* context) michael@0: { michael@0: std::vector::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context); michael@0: if (iter != DemotableContexts().end()) michael@0: return; michael@0: michael@0: DemotableContexts().push_back(context); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* context) michael@0: { michael@0: std::vector::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context); michael@0: if (iter != DemotableContexts().end()) michael@0: DemotableContexts().erase(iter); michael@0: } michael@0: michael@0: bool michael@0: CheckSizeForSkiaGL(IntSize size) { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: int minsize = Preferences::GetInt("gfx.canvas.min-size-for-skia-gl", 128); michael@0: if (size.width < minsize || size.height < minsize) { michael@0: return false; michael@0: } michael@0: michael@0: // Maximum pref allows 3 different options: michael@0: // 0 means unlimited size michael@0: // > 0 means use value as an absolute threshold michael@0: // < 0 means use the number of screen pixels as a threshold michael@0: int maxsize = Preferences::GetInt("gfx.canvas.max-size-for-skia-gl", 0); michael@0: michael@0: // unlimited max size michael@0: if (!maxsize) { michael@0: return true; michael@0: } michael@0: michael@0: // absolute max size threshold michael@0: if (maxsize > 0) { michael@0: return size.width <= maxsize && size.height <= maxsize; michael@0: } michael@0: michael@0: // Cache the number of pixels on the primary screen michael@0: static int32_t gScreenPixels = -1; michael@0: if (gScreenPixels < 0) { michael@0: nsCOMPtr screenManager = michael@0: do_GetService("@mozilla.org/gfx/screenmanager;1"); michael@0: if (screenManager) { michael@0: nsCOMPtr primaryScreen; michael@0: screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen)); michael@0: if (primaryScreen) { michael@0: int32_t x, y, width, height; michael@0: primaryScreen->GetRect(&x, &y, &width, &height); michael@0: michael@0: gScreenPixels = width * height; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // screen size acts as max threshold michael@0: return gScreenPixels < 0 || (size.width * size.height) <= gScreenPixels; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::EnsureTarget() michael@0: { michael@0: if (mTarget) { michael@0: return; michael@0: } michael@0: michael@0: // Check that the dimensions are sane michael@0: IntSize size(mWidth, mHeight); michael@0: if (size.width <= 0xFFFF && size.height <= 0xFFFF && michael@0: size.width >= 0 && size.height >= 0) { michael@0: SurfaceFormat format = GetSurfaceFormat(); michael@0: nsIDocument* ownerDoc = nullptr; michael@0: if (mCanvasElement) { michael@0: ownerDoc = mCanvasElement->OwnerDoc(); michael@0: } michael@0: michael@0: nsRefPtr layerManager = nullptr; michael@0: michael@0: if (ownerDoc) { michael@0: layerManager = michael@0: nsContentUtils::PersistentLayerManagerForDocument(ownerDoc); michael@0: } michael@0: michael@0: if (layerManager) { michael@0: if (gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas() && michael@0: !mForceSoftware && michael@0: CheckSizeForSkiaGL(size)) { michael@0: DemoteOldestContextIfNecessary(); michael@0: michael@0: SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); michael@0: michael@0: #if USE_SKIA michael@0: if (glue && glue->GetGrContext() && glue->GetGLContext()) { michael@0: mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format); michael@0: if (mTarget) { michael@0: mStream = gfx::SurfaceStream::CreateForType(SurfaceStreamType::TripleBuffer, glue->GetGLContext()); michael@0: AddDemotableContext(this); michael@0: } else { michael@0: printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n"); michael@0: } michael@0: } michael@0: #endif michael@0: if (!mTarget) { michael@0: mTarget = layerManager->CreateDrawTarget(size, format); michael@0: } michael@0: } else michael@0: mTarget = layerManager->CreateDrawTarget(size, format); michael@0: } else { michael@0: mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format); michael@0: } michael@0: } michael@0: michael@0: if (mTarget) { michael@0: static bool registered = false; michael@0: if (!registered) { michael@0: registered = true; michael@0: RegisterStrongMemoryReporter(new Canvas2dPixelsReporter()); michael@0: } michael@0: michael@0: gCanvasAzureMemoryUsed += mWidth * mHeight * 4; michael@0: JSContext* context = nsContentUtils::GetCurrentJSContext(); michael@0: if (context) { michael@0: JS_updateMallocCounter(context, mWidth * mHeight * 4); michael@0: } michael@0: michael@0: mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight))); michael@0: // Force a full layer transaction since we didn't have a layer before michael@0: // and now we might need one. michael@0: if (mCanvasElement) { michael@0: mCanvasElement->InvalidateCanvas(); michael@0: } michael@0: // Calling Redraw() tells our invalidation machinery that the entire michael@0: // canvas is already invalid, which can speed up future drawing. michael@0: Redraw(); michael@0: } else { michael@0: EnsureErrorTarget(); michael@0: mTarget = sErrorTarget; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: int32_t michael@0: CanvasRenderingContext2D::GetWidth() const michael@0: { michael@0: return mWidth; michael@0: } michael@0: michael@0: int32_t michael@0: CanvasRenderingContext2D::GetHeight() const michael@0: { michael@0: return mHeight; michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height) michael@0: { michael@0: ClearTarget(); michael@0: michael@0: // Zero sized surfaces can cause problems. michael@0: mZero = false; michael@0: if (height == 0) { michael@0: height = 1; michael@0: mZero = true; michael@0: } michael@0: if (width == 0) { michael@0: width = 1; michael@0: mZero = true; michael@0: } michael@0: mWidth = width; michael@0: mHeight = height; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::ClearTarget() michael@0: { michael@0: Reset(); michael@0: michael@0: mResetLayer = true; michael@0: michael@0: // set up the initial canvas defaults michael@0: mStyleStack.Clear(); michael@0: mPathBuilder = nullptr; michael@0: mPath = nullptr; michael@0: mDSPathBuilder = nullptr; michael@0: michael@0: ContextState *state = mStyleStack.AppendElement(); michael@0: state->globalAlpha = 1.0; michael@0: michael@0: state->colorStyles[Style::FILL] = NS_RGB(0,0,0); michael@0: state->colorStyles[Style::STROKE] = NS_RGB(0,0,0); michael@0: state->shadowColor = NS_RGBA(0,0,0,0); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell, michael@0: gfxASurface *surface, michael@0: int32_t width, michael@0: int32_t height) michael@0: { michael@0: mDocShell = shell; michael@0: michael@0: SetDimensions(width, height); michael@0: mTarget = gfxPlatform::GetPlatform()-> michael@0: CreateDrawTargetForSurface(surface, IntSize(width, height)); michael@0: michael@0: if (!mTarget) { michael@0: EnsureErrorTarget(); michael@0: mTarget = sErrorTarget; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasRenderingContext2D::SetIsOpaque(bool isOpaque) michael@0: { michael@0: if (isOpaque != mOpaque) { michael@0: mOpaque = isOpaque; michael@0: ClearTarget(); michael@0: } michael@0: michael@0: if (mOpaque) { michael@0: EnsureTarget(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasRenderingContext2D::SetIsIPC(bool isIPC) michael@0: { michael@0: if (isIPC != mIPC) { michael@0: mIPC = isIPC; michael@0: ClearTarget(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasRenderingContext2D::SetContextOptions(JSContext* aCx, JS::Handle aOptions) michael@0: { michael@0: if (aOptions.isNullOrUndefined()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: ContextAttributes2D attributes; michael@0: NS_ENSURE_TRUE(attributes.Init(aCx, aOptions), NS_ERROR_UNEXPECTED); michael@0: michael@0: if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) { michael@0: // Use software when there is going to be a lot of readback michael@0: mForceSoftware = attributes.mWillReadFrequently; michael@0: } michael@0: michael@0: if (!attributes.mAlpha) { michael@0: SetIsOpaque(true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer, michael@0: int32_t* aFormat) michael@0: { michael@0: *aImageBuffer = nullptr; michael@0: *aFormat = 0; michael@0: michael@0: EnsureTarget(); michael@0: RefPtr snapshot = mTarget->Snapshot(); michael@0: if (!snapshot) { michael@0: return; michael@0: } michael@0: michael@0: RefPtr data = snapshot->GetDataSurface(); michael@0: if (!data || data->GetSize() != IntSize(mWidth, mHeight)) { michael@0: return; michael@0: } michael@0: michael@0: *aImageBuffer = SurfaceToPackedBGRA(data); michael@0: *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CanvasRenderingContext2D::GetInputStream(const char *aMimeType, michael@0: const char16_t *aEncoderOptions, michael@0: nsIInputStream **aStream) michael@0: { michael@0: nsCString enccid("@mozilla.org/image/encoder;2?type="); michael@0: enccid += aMimeType; michael@0: nsCOMPtr encoder = do_CreateInstance(enccid.get()); michael@0: if (!encoder) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsAutoArrayPtr imageBuffer; michael@0: int32_t format = 0; michael@0: GetImageBuffer(getter_Transfers(imageBuffer), &format); michael@0: if (!imageBuffer) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, michael@0: encoder, aEncoderOptions, aStream); michael@0: } michael@0: michael@0: SurfaceFormat michael@0: CanvasRenderingContext2D::GetSurfaceFormat() const michael@0: { michael@0: return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; michael@0: } michael@0: michael@0: // michael@0: // state michael@0: // michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Save() michael@0: { michael@0: EnsureTarget(); michael@0: mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); michael@0: mStyleStack.SetCapacity(mStyleStack.Length() + 1); michael@0: mStyleStack.AppendElement(CurrentState()); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Restore() michael@0: { michael@0: if (mStyleStack.Length() - 1 == 0) michael@0: return; michael@0: michael@0: TransformWillUpdate(); michael@0: michael@0: for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { michael@0: mTarget->PopClip(); michael@0: } michael@0: michael@0: mStyleStack.RemoveElementAt(mStyleStack.Length() - 1); michael@0: michael@0: mTarget->SetTransform(CurrentState().transform); michael@0: } michael@0: michael@0: // michael@0: // transformations michael@0: // michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error) michael@0: { michael@0: TransformWillUpdate(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix newMatrix = mTarget->GetTransform(); michael@0: mTarget->SetTransform(newMatrix.Scale(x, y)); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error) michael@0: { michael@0: TransformWillUpdate(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix rotation = Matrix::Rotation(angle); michael@0: mTarget->SetTransform(rotation * mTarget->GetTransform()); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error) michael@0: { michael@0: TransformWillUpdate(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix newMatrix = mTarget->GetTransform(); michael@0: mTarget->SetTransform(newMatrix.Translate(x, y)); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Transform(double m11, double m12, double m21, michael@0: double m22, double dx, double dy, michael@0: ErrorResult& error) michael@0: { michael@0: TransformWillUpdate(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix matrix(m11, m12, m21, m22, dx, dy); michael@0: mTarget->SetTransform(matrix * mTarget->GetTransform()); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetTransform(double m11, double m12, michael@0: double m21, double m22, michael@0: double dx, double dy, michael@0: ErrorResult& error) michael@0: { michael@0: TransformWillUpdate(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix matrix(m11, m12, m21, m22, dx, dy); michael@0: mTarget->SetTransform(matrix); michael@0: } michael@0: michael@0: static void michael@0: MatrixToJSObject(JSContext* cx, const Matrix& matrix, michael@0: JS::MutableHandle result, ErrorResult& error) michael@0: { michael@0: double elts[6] = { matrix._11, matrix._12, michael@0: matrix._21, matrix._22, michael@0: matrix._31, matrix._32 }; michael@0: michael@0: // XXX Should we enter GetWrapper()'s compartment? michael@0: JS::Rooted val(cx); michael@0: if (!ToJSValue(cx, elts, &val)) { michael@0: error.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: } else { michael@0: result.set(&val.toObject()); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: ObjectToMatrix(JSContext* cx, JS::Handle obj, Matrix& matrix, michael@0: ErrorResult& error) michael@0: { michael@0: uint32_t length; michael@0: if (!JS_GetArrayLength(cx, obj, &length) || length != 6) { michael@0: // Not an array-like thing or wrong size michael@0: error.Throw(NS_ERROR_INVALID_ARG); michael@0: return false; michael@0: } michael@0: michael@0: Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22, michael@0: &matrix._31, &matrix._32 }; michael@0: for (uint32_t i = 0; i < 6; ++i) { michael@0: JS::Rooted elt(cx); michael@0: double d; michael@0: if (!JS_GetElement(cx, obj, i, &elt)) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return false; michael@0: } michael@0: if (!CoerceDouble(elt, &d)) { michael@0: error.Throw(NS_ERROR_INVALID_ARG); michael@0: return false; michael@0: } michael@0: if (!FloatValidate(d)) { michael@0: // This is weird, but it's the behavior of SetTransform() michael@0: return false; michael@0: } michael@0: *elts[i] = Float(d); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, michael@0: JS::Handle currentTransform, michael@0: ErrorResult& error) michael@0: { michael@0: EnsureTarget(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix newCTM; michael@0: if (ObjectToMatrix(cx, currentTransform, newCTM, error)) { michael@0: mTarget->SetTransform(newCTM); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, michael@0: JS::MutableHandle result, michael@0: ErrorResult& error) const michael@0: { michael@0: MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), michael@0: result, error); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, michael@0: JS::Handle currentTransform, michael@0: ErrorResult& error) michael@0: { michael@0: EnsureTarget(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: Matrix newCTMInverse; michael@0: if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) { michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: if (newCTMInverse.Invert()) { michael@0: mTarget->SetTransform(newCTMInverse); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, michael@0: JS::MutableHandle result, michael@0: ErrorResult& error) const michael@0: { michael@0: if (!mTarget) { michael@0: MatrixToJSObject(cx, Matrix(), result, error); michael@0: return; michael@0: } michael@0: michael@0: Matrix ctm = mTarget->GetTransform(); michael@0: michael@0: if (!ctm.Invert()) { michael@0: double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); michael@0: ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); michael@0: } michael@0: michael@0: MatrixToJSObject(cx, ctm, result, error); michael@0: } michael@0: michael@0: // michael@0: // colors michael@0: // michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& value, michael@0: Style whichStyle) michael@0: { michael@0: if (value.IsString()) { michael@0: SetStyleFromString(value.GetAsString(), whichStyle); michael@0: return; michael@0: } michael@0: michael@0: if (value.IsCanvasGradient()) { michael@0: SetStyleFromGradient(value.GetAsCanvasGradient(), whichStyle); michael@0: return; michael@0: } michael@0: michael@0: if (value.IsCanvasPattern()) { michael@0: SetStyleFromPattern(value.GetAsCanvasPattern(), whichStyle); michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Invalid union value"); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetFillRule(const nsAString& aString) michael@0: { michael@0: FillRule rule; michael@0: michael@0: if (aString.EqualsLiteral("evenodd")) michael@0: rule = FillRule::FILL_EVEN_ODD; michael@0: else if (aString.EqualsLiteral("nonzero")) michael@0: rule = FillRule::FILL_WINDING; michael@0: else michael@0: return; michael@0: michael@0: CurrentState().fillRule = rule; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetFillRule(nsAString& aString) michael@0: { michael@0: switch (CurrentState().fillRule) { michael@0: case FillRule::FILL_WINDING: michael@0: aString.AssignLiteral("nonzero"); break; michael@0: case FillRule::FILL_EVEN_ODD: michael@0: aString.AssignLiteral("evenodd"); break; michael@0: } michael@0: } michael@0: // michael@0: // gradients and patterns michael@0: // michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1) michael@0: { michael@0: nsIDocument *doc = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; michael@0: mozilla::css::Loader *cssLoader = doc ? doc->CSSLoader() : nullptr; michael@0: michael@0: nsRefPtr grad = michael@0: new CanvasLinearGradient(this, cssLoader, Point(x0, y0), Point(x1, y1)); michael@0: michael@0: return grad.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, michael@0: double x1, double y1, double r1, michael@0: ErrorResult& aError) michael@0: { michael@0: if (r0 < 0.0 || r1 < 0.0) { michael@0: aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIDocument *doc = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; michael@0: mozilla::css::Loader *cssLoader = doc ? doc->CSSLoader() : nullptr; michael@0: nsRefPtr grad = michael@0: new CanvasRadialGradient(this, cssLoader, Point(x0, y0), r0, Point(x1, y1), r1); michael@0: michael@0: return grad.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, michael@0: const nsAString& repeat, michael@0: ErrorResult& error) michael@0: { michael@0: CanvasPattern::RepeatMode repeatMode = michael@0: CanvasPattern::RepeatMode::NOREPEAT; michael@0: michael@0: if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) { michael@0: repeatMode = CanvasPattern::RepeatMode::REPEAT; michael@0: } else if (repeat.EqualsLiteral("repeat-x")) { michael@0: repeatMode = CanvasPattern::RepeatMode::REPEATX; michael@0: } else if (repeat.EqualsLiteral("repeat-y")) { michael@0: repeatMode = CanvasPattern::RepeatMode::REPEATY; michael@0: } else if (repeat.EqualsLiteral("no-repeat")) { michael@0: repeatMode = CanvasPattern::RepeatMode::NOREPEAT; michael@0: } else { michael@0: error.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: Element* htmlElement; michael@0: if (element.IsHTMLCanvasElement()) { michael@0: HTMLCanvasElement* canvas = &element.GetAsHTMLCanvasElement(); michael@0: htmlElement = canvas; michael@0: michael@0: nsIntSize size = canvas->GetSize(); michael@0: if (size.width == 0 || size.height == 0) { michael@0: error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Special case for Canvas, which could be an Azure canvas! michael@0: nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); michael@0: if (srcCanvas) { michael@0: // This might not be an Azure canvas! michael@0: RefPtr srcSurf = srcCanvas->GetSurfaceSnapshot(); michael@0: michael@0: nsRefPtr pat = michael@0: new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false); michael@0: michael@0: return pat.forget(); michael@0: } michael@0: } else if (element.IsHTMLImageElement()) { michael@0: htmlElement = &element.GetAsHTMLImageElement(); michael@0: } else { michael@0: htmlElement = &element.GetAsHTMLVideoElement(); michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: michael@0: // The canvas spec says that createPattern should use the first frame michael@0: // of animated images michael@0: nsLayoutUtils::SurfaceFromElementResult res = michael@0: nsLayoutUtils::SurfaceFromElement(htmlElement, michael@0: nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget); michael@0: michael@0: if (!res.mSourceSurface) { michael@0: error.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr pat = michael@0: new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal, michael@0: res.mIsWriteOnly, res.mCORSUsed); michael@0: michael@0: return pat.forget(); michael@0: } michael@0: michael@0: // michael@0: // shadows michael@0: // michael@0: void michael@0: CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor) michael@0: { michael@0: nscolor color; michael@0: if (!ParseColor(shadowColor, &color)) { michael@0: return; michael@0: } michael@0: michael@0: CurrentState().shadowColor = color; michael@0: } michael@0: michael@0: // michael@0: // rects michael@0: // michael@0: michael@0: void michael@0: CanvasRenderingContext2D::ClearRect(double x, double y, double w, michael@0: double h) michael@0: { michael@0: if (!mTarget) { michael@0: return; michael@0: } michael@0: michael@0: mTarget->ClearRect(mgfx::Rect(x, y, w, h)); michael@0: michael@0: RedrawUser(gfxRect(x, y, w, h)); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::FillRect(double x, double y, double w, michael@0: double h) michael@0: { michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: if (state.patternStyles[Style::FILL]) { michael@0: CanvasPattern::RepeatMode repeat = michael@0: state.patternStyles[Style::FILL]->mRepeat; michael@0: // In the FillRect case repeat modes are easy to deal with. michael@0: bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATY; michael@0: bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATX; michael@0: michael@0: IntSize patternSize = michael@0: state.patternStyles[Style::FILL]->mSurface->GetSize(); michael@0: michael@0: // We always need to execute painting for non-over operators, even if michael@0: // we end up with w/h = 0. michael@0: if (limitx) { michael@0: if (x < 0) { michael@0: w += x; michael@0: if (w < 0) { michael@0: w = 0; michael@0: } michael@0: michael@0: x = 0; michael@0: } michael@0: if (x + w > patternSize.width) { michael@0: w = patternSize.width - x; michael@0: if (w < 0) { michael@0: w = 0; michael@0: } michael@0: } michael@0: } michael@0: if (limity) { michael@0: if (y < 0) { michael@0: h += y; michael@0: if (h < 0) { michael@0: h = 0; michael@0: } michael@0: michael@0: y = 0; michael@0: } michael@0: if (y + h > patternSize.height) { michael@0: h = patternSize.height - y; michael@0: if (h < 0) { michael@0: h = 0; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: mgfx::Rect bounds; michael@0: michael@0: EnsureTarget(); michael@0: if (NeedToDrawShadow()) { michael@0: bounds = mgfx::Rect(x, y, w, h); michael@0: bounds = mTarget->GetTransform().TransformBounds(bounds); michael@0: } michael@0: michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: FillRect(mgfx::Rect(x, y, w, h), michael@0: CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), michael@0: DrawOptions(state.globalAlpha, UsedOperation())); michael@0: michael@0: RedrawUser(gfxRect(x, y, w, h)); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::StrokeRect(double x, double y, double w, michael@0: double h) michael@0: { michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: mgfx::Rect bounds; michael@0: michael@0: if (!w && !h) { michael@0: return; michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: if (!IsTargetValid()) { michael@0: return; michael@0: } michael@0: michael@0: if (NeedToDrawShadow()) { michael@0: bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f, michael@0: w + state.lineWidth, h + state.lineWidth); michael@0: bounds = mTarget->GetTransform().TransformBounds(bounds); michael@0: } michael@0: michael@0: if (!h) { michael@0: CapStyle cap = CapStyle::BUTT; michael@0: if (state.lineJoin == JoinStyle::ROUND) { michael@0: cap = CapStyle::ROUND; michael@0: } michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: StrokeLine(Point(x, y), Point(x + w, y), michael@0: CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), michael@0: StrokeOptions(state.lineWidth, state.lineJoin, michael@0: cap, state.miterLimit, michael@0: state.dash.Length(), michael@0: state.dash.Elements(), michael@0: state.dashOffset), michael@0: DrawOptions(state.globalAlpha, UsedOperation())); michael@0: return; michael@0: } michael@0: michael@0: if (!w) { michael@0: CapStyle cap = CapStyle::BUTT; michael@0: if (state.lineJoin == JoinStyle::ROUND) { michael@0: cap = CapStyle::ROUND; michael@0: } michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: StrokeLine(Point(x, y), Point(x, y + h), michael@0: CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), michael@0: StrokeOptions(state.lineWidth, state.lineJoin, michael@0: cap, state.miterLimit, michael@0: state.dash.Length(), michael@0: state.dash.Elements(), michael@0: state.dashOffset), michael@0: DrawOptions(state.globalAlpha, UsedOperation())); michael@0: return; michael@0: } michael@0: michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: StrokeRect(mgfx::Rect(x, y, w, h), michael@0: CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), michael@0: StrokeOptions(state.lineWidth, state.lineJoin, michael@0: state.lineCap, state.miterLimit, michael@0: state.dash.Length(), michael@0: state.dash.Elements(), michael@0: state.dashOffset), michael@0: DrawOptions(state.globalAlpha, UsedOperation())); michael@0: michael@0: Redraw(); michael@0: } michael@0: michael@0: // michael@0: // path bits michael@0: // michael@0: michael@0: void michael@0: CanvasRenderingContext2D::BeginPath() michael@0: { michael@0: mPath = nullptr; michael@0: mPathBuilder = nullptr; michael@0: mDSPathBuilder = nullptr; michael@0: mPathTransformWillUpdate = false; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Fill(const CanvasWindingRule& winding) michael@0: { michael@0: EnsureUserSpacePath(winding); michael@0: michael@0: if (!mPath) { michael@0: return; michael@0: } michael@0: michael@0: mgfx::Rect bounds; michael@0: michael@0: if (NeedToDrawShadow()) { michael@0: bounds = mPath->GetBounds(mTarget->GetTransform()); michael@0: } michael@0: michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: Fill(mPath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), michael@0: DrawOptions(CurrentState().globalAlpha, UsedOperation())); michael@0: michael@0: Redraw(); michael@0: } michael@0: michael@0: void CanvasRenderingContext2D::Fill(const CanvasPath& path, const CanvasWindingRule& winding) michael@0: { michael@0: EnsureTarget(); michael@0: michael@0: RefPtr gfxpath = path.GetPath(winding, mTarget); michael@0: michael@0: if (!gfxpath) { michael@0: return; michael@0: } michael@0: michael@0: mgfx::Rect bounds; michael@0: michael@0: if (NeedToDrawShadow()) { michael@0: bounds = gfxpath->GetBounds(mTarget->GetTransform()); michael@0: } michael@0: michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: Fill(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), michael@0: DrawOptions(CurrentState().globalAlpha, UsedOperation())); michael@0: michael@0: Redraw(); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Stroke() michael@0: { michael@0: EnsureUserSpacePath(); michael@0: michael@0: if (!mPath) { michael@0: return; michael@0: } michael@0: michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, michael@0: state.lineCap, state.miterLimit, michael@0: state.dash.Length(), state.dash.Elements(), michael@0: state.dashOffset); michael@0: michael@0: mgfx::Rect bounds; michael@0: if (NeedToDrawShadow()) { michael@0: bounds = michael@0: mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); michael@0: } michael@0: michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: Stroke(mPath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), michael@0: strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); michael@0: michael@0: Redraw(); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Stroke(const CanvasPath& path) michael@0: { michael@0: EnsureTarget(); michael@0: michael@0: RefPtr gfxpath = path.GetPath(CanvasWindingRule::Nonzero, mTarget); michael@0: michael@0: if (!gfxpath) { michael@0: return; michael@0: } michael@0: michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, michael@0: state.lineCap, state.miterLimit, michael@0: state.dash.Length(), state.dash.Elements(), michael@0: state.dashOffset); michael@0: michael@0: mgfx::Rect bounds; michael@0: if (NeedToDrawShadow()) { michael@0: bounds = michael@0: gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); michael@0: } michael@0: michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: Stroke(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), michael@0: strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); michael@0: michael@0: Redraw(); michael@0: } michael@0: michael@0: void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement) michael@0: { michael@0: EnsureUserSpacePath(); michael@0: michael@0: if (!mPath) { michael@0: return; michael@0: } michael@0: michael@0: if(DrawCustomFocusRing(aElement)) { michael@0: Save(); michael@0: michael@0: // set state to conforming focus state michael@0: ContextState& state = CurrentState(); michael@0: state.globalAlpha = 1.0; michael@0: state.shadowBlur = 0; michael@0: state.shadowOffset.x = 0; michael@0: state.shadowOffset.y = 0; michael@0: state.op = mozilla::gfx::CompositionOp::OP_OVER; michael@0: michael@0: state.lineCap = CapStyle::BUTT; michael@0: state.lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL; michael@0: state.lineWidth = 1; michael@0: CurrentState().dash.Clear(); michael@0: michael@0: // color and style of the rings is the same as for image maps michael@0: // set the background focus color michael@0: CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255)); michael@0: // draw the focus ring michael@0: Stroke(); michael@0: michael@0: // set dashing for foreground michael@0: FallibleTArray& dash = CurrentState().dash; michael@0: dash.AppendElement(1); michael@0: dash.AppendElement(1); michael@0: michael@0: // set the foreground focus color michael@0: CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255)); michael@0: // draw the focus ring michael@0: Stroke(); michael@0: michael@0: Restore(); michael@0: } michael@0: } michael@0: michael@0: bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement) michael@0: { michael@0: EnsureUserSpacePath(); michael@0: michael@0: HTMLCanvasElement* canvas = GetCanvas(); michael@0: michael@0: if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) { michael@0: return false; michael@0: } michael@0: michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (fm) { michael@0: // check that the element i focused michael@0: nsCOMPtr focusedElement; michael@0: fm->GetFocusedElement(getter_AddRefs(focusedElement)); michael@0: if (SameCOMIdentity(aElement.AsDOMNode(), focusedElement)) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding) michael@0: { michael@0: EnsureUserSpacePath(winding); michael@0: michael@0: if (!mPath) { michael@0: return; michael@0: } michael@0: michael@0: mTarget->PushClip(mPath); michael@0: CurrentState().clipsPushed.push_back(mPath); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Clip(const CanvasPath& path, const CanvasWindingRule& winding) michael@0: { michael@0: EnsureTarget(); michael@0: michael@0: RefPtr gfxpath = path.GetPath(winding, mTarget); michael@0: michael@0: if (!gfxpath) { michael@0: return; michael@0: } michael@0: michael@0: mTarget->PushClip(gfxpath); michael@0: CurrentState().clipsPushed.push_back(gfxpath); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, michael@0: double y2, double radius, michael@0: ErrorResult& error) michael@0: { michael@0: if (radius < 0) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: EnsureWritablePath(); michael@0: michael@0: // Current point in user space! michael@0: Point p0; michael@0: if (mPathBuilder) { michael@0: p0 = mPathBuilder->CurrentPoint(); michael@0: } else { michael@0: Matrix invTransform = mTarget->GetTransform(); michael@0: if (!invTransform.Invert()) { michael@0: return; michael@0: } michael@0: michael@0: p0 = invTransform * mDSPathBuilder->CurrentPoint(); michael@0: } michael@0: michael@0: Point p1(x1, y1); michael@0: Point p2(x2, y2); michael@0: michael@0: // Execute these calculations in double precision to avoid cumulative michael@0: // rounding errors. michael@0: double dir, a2, b2, c2, cosx, sinx, d, anx, any, michael@0: bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; michael@0: bool anticlockwise; michael@0: michael@0: if (p0 == p1 || p1 == p2 || radius == 0) { michael@0: LineTo(p1.x, p1.y); michael@0: return; michael@0: } michael@0: michael@0: // Check for colinearity michael@0: dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); michael@0: if (dir == 0) { michael@0: LineTo(p1.x, p1.y); michael@0: return; michael@0: } michael@0: michael@0: michael@0: // XXX - Math for this code was already available from the non-azure code michael@0: // and would be well tested. Perhaps converting to bezier directly might michael@0: // be more efficient longer run. michael@0: a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); michael@0: b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); michael@0: c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); michael@0: cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); michael@0: michael@0: sinx = sqrt(1 - cosx*cosx); michael@0: d = radius / ((1 - cosx) / sinx); michael@0: michael@0: anx = (x1-p0.x) / sqrt(a2); michael@0: any = (y1-p0.y) / sqrt(a2); michael@0: bnx = (x1-x2) / sqrt(b2); michael@0: bny = (y1-y2) / sqrt(b2); michael@0: x3 = x1 - anx*d; michael@0: y3 = y1 - any*d; michael@0: x4 = x1 - bnx*d; michael@0: y4 = y1 - bny*d; michael@0: anticlockwise = (dir < 0); michael@0: cx = x3 + any*radius*(anticlockwise ? 1 : -1); michael@0: cy = y3 - anx*radius*(anticlockwise ? 1 : -1); michael@0: angle0 = atan2((y3-cy), (x3-cx)); michael@0: angle1 = atan2((y4-cy), (x4-cx)); michael@0: michael@0: michael@0: LineTo(x3, y3); michael@0: michael@0: Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Arc(double x, double y, double r, michael@0: double startAngle, double endAngle, michael@0: bool anticlockwise, ErrorResult& error) michael@0: { michael@0: if (r < 0.0) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: EnsureWritablePath(); michael@0: michael@0: ArcToBezier(this, Point(x, y), Size(r, r), startAngle, endAngle, anticlockwise); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::Rect(double x, double y, double w, double h) michael@0: { michael@0: EnsureWritablePath(); michael@0: michael@0: if (mPathBuilder) { michael@0: mPathBuilder->MoveTo(Point(x, y)); michael@0: mPathBuilder->LineTo(Point(x + w, y)); michael@0: mPathBuilder->LineTo(Point(x + w, y + h)); michael@0: mPathBuilder->LineTo(Point(x, y + h)); michael@0: mPathBuilder->Close(); michael@0: } else { michael@0: mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y)); michael@0: mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y)); michael@0: mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h)); michael@0: mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h)); michael@0: mDSPathBuilder->Close(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::EnsureWritablePath() michael@0: { michael@0: if (mDSPathBuilder) { michael@0: return; michael@0: } michael@0: michael@0: FillRule fillRule = CurrentState().fillRule; michael@0: michael@0: if (mPathBuilder) { michael@0: if (mPathTransformWillUpdate) { michael@0: mPath = mPathBuilder->Finish(); michael@0: mDSPathBuilder = michael@0: mPath->TransformedCopyToBuilder(mPathToDS, fillRule); michael@0: mPath = nullptr; michael@0: mPathBuilder = nullptr; michael@0: mPathTransformWillUpdate = false; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: if (!mPath) { michael@0: NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); michael@0: mPathBuilder = mTarget->CreatePathBuilder(fillRule); michael@0: } else if (!mPathTransformWillUpdate) { michael@0: mPathBuilder = mPath->CopyToBuilder(fillRule); michael@0: } else { michael@0: mDSPathBuilder = michael@0: mPath->TransformedCopyToBuilder(mPathToDS, fillRule); michael@0: mPathTransformWillUpdate = false; michael@0: mPath = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& winding) michael@0: { michael@0: FillRule fillRule = CurrentState().fillRule; michael@0: if(winding == CanvasWindingRule::Evenodd) michael@0: fillRule = FillRule::FILL_EVEN_ODD; michael@0: michael@0: if (!mPath && !mPathBuilder && !mDSPathBuilder) { michael@0: EnsureTarget(); michael@0: mPathBuilder = mTarget->CreatePathBuilder(fillRule); michael@0: } michael@0: michael@0: if (mPathBuilder) { michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: } michael@0: michael@0: if (mPath && michael@0: mPathTransformWillUpdate) { michael@0: mDSPathBuilder = michael@0: mPath->TransformedCopyToBuilder(mPathToDS, fillRule); michael@0: mPath = nullptr; michael@0: mPathTransformWillUpdate = false; michael@0: } michael@0: michael@0: if (mDSPathBuilder) { michael@0: RefPtr dsPath; michael@0: dsPath = mDSPathBuilder->Finish(); michael@0: mDSPathBuilder = nullptr; michael@0: michael@0: Matrix inverse = mTarget->GetTransform(); michael@0: if (!inverse.Invert()) { michael@0: NS_WARNING("Could not invert transform"); michael@0: return; michael@0: } michael@0: michael@0: mPathBuilder = michael@0: dsPath->TransformedCopyToBuilder(inverse, fillRule); michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: } michael@0: michael@0: if (mPath && mPath->GetFillRule() != fillRule) { michael@0: mPathBuilder = mPath->CopyToBuilder(fillRule); michael@0: mPath = mPathBuilder->Finish(); michael@0: mPathBuilder = nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(mPath, "mPath should exist"); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::TransformWillUpdate() michael@0: { michael@0: EnsureTarget(); michael@0: michael@0: // Store the matrix that would transform the current path to device michael@0: // space. michael@0: if (mPath || mPathBuilder) { michael@0: if (!mPathTransformWillUpdate) { michael@0: // If the transform has already been updated, but a device space builder michael@0: // has not been created yet mPathToDS contains the right transform to michael@0: // transform the current mPath into device space. michael@0: // We should leave it alone. michael@0: mPathToDS = mTarget->GetTransform(); michael@0: } michael@0: mPathTransformWillUpdate = true; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // text michael@0: // michael@0: michael@0: /** michael@0: * Helper function for SetFont that creates a style rule for the given font. michael@0: * @param aFont The CSS font string michael@0: * @param aNode The canvas element michael@0: * @param aResult Pointer in which to place the new style rule. michael@0: * @remark Assumes all pointer arguments are non-null. michael@0: */ michael@0: static nsresult michael@0: CreateFontStyleRule(const nsAString& aFont, michael@0: nsINode* aNode, michael@0: StyleRule** aResult) michael@0: { michael@0: nsRefPtr rule; michael@0: bool changed; michael@0: michael@0: nsIPrincipal* principal = aNode->NodePrincipal(); michael@0: nsIDocument* document = aNode->OwnerDoc(); michael@0: michael@0: nsIURI* docURL = document->GetDocumentURI(); michael@0: nsIURI* baseURL = document->GetDocBaseURI(); michael@0: michael@0: // Pass the CSS Loader object to the parser, to allow parser error reports michael@0: // to include the outer window ID. michael@0: nsCSSParser parser(document->CSSLoader()); michael@0: michael@0: nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, michael@0: principal, getter_AddRefs(rule)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL, michael@0: principal, rule->GetDeclaration(), &changed, michael@0: false); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = parser.ParseProperty(eCSSProperty_line_height, michael@0: NS_LITERAL_STRING("normal"), docURL, baseURL, michael@0: principal, rule->GetDeclaration(), &changed, michael@0: false); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: rule->RuleMatched(); michael@0: michael@0: rule.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetFont(const nsAString& font, michael@0: ErrorResult& error) michael@0: { michael@0: /* michael@0: * If font is defined with relative units (e.g. ems) and the parent michael@0: * style context changes in between calls, setting the font to the michael@0: * same value as previous could result in a different computed value, michael@0: * so we cannot have the optimization where we check if the new font michael@0: * string is equal to the old one. michael@0: */ michael@0: michael@0: if (!mCanvasElement && !mDocShell) { michael@0: NS_WARNING("Canvas element must be non-null or a docshell must be provided"); michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsIPresShell* presShell = GetPresShell(); michael@0: if (!presShell) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: nsIDocument* document = presShell->GetDocument(); michael@0: michael@0: nsRefPtr rule; michael@0: error = CreateFontStyleRule(font, document, getter_AddRefs(rule)); michael@0: michael@0: if (error.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: css::Declaration *declaration = rule->GetDeclaration(); michael@0: // The easiest way to see whether we got a syntax error or whether michael@0: // we got 'inherit' or 'initial' is to look at font-size-adjust, michael@0: // which the shorthand resets to either 'none' or michael@0: // '-moz-system-font'. michael@0: // We know the declaration is not !important, so we can use michael@0: // GetNormalBlock(). michael@0: const nsCSSValue *fsaVal = michael@0: declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust); michael@0: if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None && michael@0: fsaVal->GetUnit() != eCSSUnit_System_Font)) { michael@0: // We got an all-property value or a syntax error. The spec says michael@0: // this value must be ignored. michael@0: return; michael@0: } michael@0: michael@0: nsTArray< nsCOMPtr > rules; michael@0: rules.AppendElement(rule); michael@0: michael@0: nsStyleSet* styleSet = presShell->StyleSet(); michael@0: michael@0: // have to get a parent style context for inherit-like relative michael@0: // values (2em, bolder, etc.) michael@0: nsRefPtr parentContext; michael@0: michael@0: if (mCanvasElement && mCanvasElement->IsInDoc()) { michael@0: // inherit from the canvas element michael@0: parentContext = nsComputedDOMStyle::GetStyleContextForElement( michael@0: mCanvasElement, michael@0: nullptr, michael@0: presShell); michael@0: } else { michael@0: // otherwise inherit from default (10px sans-serif) michael@0: nsRefPtr parentRule; michael@0: error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"), michael@0: document, michael@0: getter_AddRefs(parentRule)); michael@0: michael@0: if (error.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: nsTArray< nsCOMPtr > parentRules; michael@0: parentRules.AppendElement(parentRule); michael@0: parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules); michael@0: } michael@0: michael@0: if (!parentContext) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: // add a rule to prevent text zoom from affecting the style michael@0: rules.AppendElement(new nsDisableTextZoomStyleRule); michael@0: michael@0: nsRefPtr sc = michael@0: styleSet->ResolveStyleForRules(parentContext, rules); michael@0: if (!sc) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: const nsStyleFont* fontStyle = sc->StyleFont(); michael@0: michael@0: NS_ASSERTION(fontStyle, "Could not obtain font style"); michael@0: michael@0: nsIAtom* language = sc->StyleFont()->mLanguage; michael@0: if (!language) { michael@0: language = presShell->GetPresContext()->GetLanguageFromCharset(); michael@0: } michael@0: michael@0: // use CSS pixels instead of dev pixels to avoid being affected by page zoom michael@0: const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel(); michael@0: michael@0: bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview || michael@0: presShell->GetPresContext()->Type() == nsPresContext::eContext_Print); michael@0: michael@0: // Purposely ignore the font size that respects the user's minimum michael@0: // font preference (fontStyle->mFont.size) in favor of the computed michael@0: // size (fontStyle->mSize). See michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. michael@0: MOZ_ASSERT(!fontStyle->mAllowZoom, michael@0: "expected text zoom to be disabled on this nsStyleFont"); michael@0: gfxFontStyle style(fontStyle->mFont.style, michael@0: fontStyle->mFont.weight, michael@0: fontStyle->mFont.stretch, michael@0: NSAppUnitsToFloatPixels(fontStyle->mSize, float(aupcp)), michael@0: language, michael@0: fontStyle->mFont.sizeAdjust, michael@0: fontStyle->mFont.systemFont, michael@0: printerFont, michael@0: fontStyle->mFont.languageOverride); michael@0: michael@0: fontStyle->mFont.AddFontFeaturesToStyle(&style); michael@0: michael@0: nsPresContext *c = presShell->GetPresContext(); michael@0: CurrentState().fontGroup = michael@0: gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, michael@0: &style, michael@0: c->GetUserFontSet()); michael@0: NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); michael@0: CurrentState().fontGroup->SetTextPerfMetrics(c->GetTextPerfMetrics()); michael@0: michael@0: // The font getter is required to be reserialized based on what we michael@0: // parsed (including having line-height removed). (Older drafts of michael@0: // the spec required font sizes be converted to pixels, but that no michael@0: // longer seems to be required.) michael@0: declaration->GetValue(eCSSProperty_font, CurrentState().font); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetTextAlign(const nsAString& ta) michael@0: { michael@0: if (ta.EqualsLiteral("start")) michael@0: CurrentState().textAlign = TextAlign::START; michael@0: else if (ta.EqualsLiteral("end")) michael@0: CurrentState().textAlign = TextAlign::END; michael@0: else if (ta.EqualsLiteral("left")) michael@0: CurrentState().textAlign = TextAlign::LEFT; michael@0: else if (ta.EqualsLiteral("right")) michael@0: CurrentState().textAlign = TextAlign::RIGHT; michael@0: else if (ta.EqualsLiteral("center")) michael@0: CurrentState().textAlign = TextAlign::CENTER; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetTextAlign(nsAString& ta) michael@0: { michael@0: switch (CurrentState().textAlign) michael@0: { michael@0: case TextAlign::START: michael@0: ta.AssignLiteral("start"); michael@0: break; michael@0: case TextAlign::END: michael@0: ta.AssignLiteral("end"); michael@0: break; michael@0: case TextAlign::LEFT: michael@0: ta.AssignLiteral("left"); michael@0: break; michael@0: case TextAlign::RIGHT: michael@0: ta.AssignLiteral("right"); michael@0: break; michael@0: case TextAlign::CENTER: michael@0: ta.AssignLiteral("center"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb) michael@0: { michael@0: if (tb.EqualsLiteral("top")) michael@0: CurrentState().textBaseline = TextBaseline::TOP; michael@0: else if (tb.EqualsLiteral("hanging")) michael@0: CurrentState().textBaseline = TextBaseline::HANGING; michael@0: else if (tb.EqualsLiteral("middle")) michael@0: CurrentState().textBaseline = TextBaseline::MIDDLE; michael@0: else if (tb.EqualsLiteral("alphabetic")) michael@0: CurrentState().textBaseline = TextBaseline::ALPHABETIC; michael@0: else if (tb.EqualsLiteral("ideographic")) michael@0: CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC; michael@0: else if (tb.EqualsLiteral("bottom")) michael@0: CurrentState().textBaseline = TextBaseline::BOTTOM; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetTextBaseline(nsAString& tb) michael@0: { michael@0: switch (CurrentState().textBaseline) michael@0: { michael@0: case TextBaseline::TOP: michael@0: tb.AssignLiteral("top"); michael@0: break; michael@0: case TextBaseline::HANGING: michael@0: tb.AssignLiteral("hanging"); michael@0: break; michael@0: case TextBaseline::MIDDLE: michael@0: tb.AssignLiteral("middle"); michael@0: break; michael@0: case TextBaseline::ALPHABETIC: michael@0: tb.AssignLiteral("alphabetic"); michael@0: break; michael@0: case TextBaseline::IDEOGRAPHIC: michael@0: tb.AssignLiteral("ideographic"); michael@0: break; michael@0: case TextBaseline::BOTTOM: michael@0: tb.AssignLiteral("bottom"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Helper function that replaces the whitespace characters in a string michael@0: * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, michael@0: * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE michael@0: * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). michael@0: * @param str The string whose whitespace characters to replace. michael@0: */ michael@0: static inline void michael@0: TextReplaceWhitespaceCharacters(nsAutoString& str) michael@0: { michael@0: str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' ')); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::FillText(const nsAString& text, double x, michael@0: double y, michael@0: const Optional& maxWidth, michael@0: ErrorResult& error) michael@0: { michael@0: error = DrawOrMeasureText(text, x, y, maxWidth, TextDrawOperation::FILL, nullptr); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::StrokeText(const nsAString& text, double x, michael@0: double y, michael@0: const Optional& maxWidth, michael@0: ErrorResult& error) michael@0: { michael@0: error = DrawOrMeasureText(text, x, y, maxWidth, TextDrawOperation::STROKE, nullptr); michael@0: } michael@0: michael@0: TextMetrics* michael@0: CanvasRenderingContext2D::MeasureText(const nsAString& rawText, michael@0: ErrorResult& error) michael@0: { michael@0: float width; michael@0: Optional maxWidth; michael@0: error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, &width); michael@0: if (error.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return new TextMetrics(width); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& options, ErrorResult& error) michael@0: { michael@0: // remove old hit region first michael@0: RemoveHitRegion(options.mId); michael@0: michael@0: // for now, we require a fallback element michael@0: if (options.mControl == NULL) { michael@0: error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return; michael@0: } michael@0: michael@0: // check if the control is a descendant of our canvas michael@0: HTMLCanvasElement* canvas = GetCanvas(); michael@0: bool isDescendant = true; michael@0: if (!canvas || !nsContentUtils::ContentIsDescendantOf(options.mControl, canvas)) { michael@0: isDescendant = false; michael@0: } michael@0: michael@0: // check if the path is valid michael@0: EnsureUserSpacePath(CanvasWindingRule::Nonzero); michael@0: if(!mPath) { michael@0: error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return; michael@0: } michael@0: michael@0: // get the bounds of the current path. They are relative to the canvas michael@0: mgfx::Rect bounds(mPath->GetBounds(mTarget->GetTransform())); michael@0: if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) { michael@0: // The specified region has no pixels. michael@0: error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return; michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (isDescendant) { michael@0: nsRect* nsBounds = new nsRect(); michael@0: gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height); michael@0: *nsBounds = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel()); michael@0: options.mControl->DeleteProperty(nsGkAtoms::hitregion); michael@0: options.mControl->SetProperty(nsGkAtoms::hitregion, nsBounds, michael@0: nsINode::DeleteProperty); michael@0: } michael@0: #endif michael@0: michael@0: // finally, add the region to the list if it has an ID michael@0: if (options.mId.Length() != 0) { michael@0: mHitRegionsOptions.PutEntry(options.mId)->mElement = options.mControl; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::RemoveHitRegion(const nsAString& id) michael@0: { michael@0: RegionInfo* info = mHitRegionsOptions.GetEntry(id); michael@0: if (!info) { michael@0: return; michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: info->mElement->DeleteProperty(nsGkAtoms::hitregion); michael@0: #endif michael@0: mHitRegionsOptions.RemoveEntry(id); michael@0: } michael@0: michael@0: /** michael@0: * Used for nsBidiPresUtils::ProcessText michael@0: */ michael@0: struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor michael@0: { michael@0: typedef CanvasRenderingContext2D::ContextState ContextState; michael@0: michael@0: virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction) michael@0: { michael@0: mFontgrp->UpdateFontList(); // ensure user font generation is current michael@0: mTextRun = mFontgrp->MakeTextRun(text, michael@0: length, michael@0: mThebes, michael@0: mAppUnitsPerDevPixel, michael@0: direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); michael@0: } michael@0: michael@0: virtual nscoord GetWidth() michael@0: { michael@0: gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, michael@0: mTextRun->GetLength(), michael@0: mDoMeasureBoundingBox ? michael@0: gfxFont::TIGHT_INK_EXTENTS : michael@0: gfxFont::LOOSE_INK_EXTENTS, michael@0: mThebes, michael@0: nullptr); michael@0: michael@0: // this only measures the height; the total width is gotten from the michael@0: // the return value of ProcessText. michael@0: if (mDoMeasureBoundingBox) { michael@0: textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); michael@0: mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); michael@0: } michael@0: michael@0: return NSToCoordRound(textRunMetrics.mAdvanceWidth); michael@0: } michael@0: michael@0: virtual void DrawText(nscoord xOffset, nscoord width) michael@0: { michael@0: gfxPoint point = mPt; michael@0: point.x += xOffset; michael@0: michael@0: // offset is given in terms of left side of string michael@0: if (mTextRun->IsRightToLeft()) { michael@0: // Bug 581092 - don't use rounded pixel width to advance to michael@0: // right-hand end of run, because this will cause different michael@0: // glyph positioning for LTR vs RTL drawing of the same michael@0: // glyph string on OS X and DWrite where textrun widths may michael@0: // involve fractional pixels. michael@0: gfxTextRun::Metrics textRunMetrics = michael@0: mTextRun->MeasureText(0, michael@0: mTextRun->GetLength(), michael@0: mDoMeasureBoundingBox ? michael@0: gfxFont::TIGHT_INK_EXTENTS : michael@0: gfxFont::LOOSE_INK_EXTENTS, michael@0: mThebes, michael@0: nullptr); michael@0: point.x += textRunMetrics.mAdvanceWidth; michael@0: // old code was: michael@0: // point.x += width * mAppUnitsPerDevPixel; michael@0: // TODO: restore this if/when we move to fractional coords michael@0: // throughout the text layout process michael@0: } michael@0: michael@0: uint32_t numRuns; michael@0: const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); michael@0: const int32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel; michael@0: const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); michael@0: Point baselineOrigin = michael@0: Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); michael@0: michael@0: float advanceSum = 0; michael@0: michael@0: mCtx->EnsureTarget(); michael@0: for (uint32_t c = 0; c < numRuns; c++) { michael@0: gfxFont *font = runs[c].mFont; michael@0: uint32_t endRun = 0; michael@0: if (c + 1 < numRuns) { michael@0: endRun = runs[c + 1].mCharacterOffset; michael@0: } else { michael@0: endRun = mTextRun->GetLength(); michael@0: } michael@0: michael@0: const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); michael@0: michael@0: RefPtr scaledFont = michael@0: gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); michael@0: michael@0: if (!scaledFont) { michael@0: // This can occur when something switched DirectWrite off. michael@0: return; michael@0: } michael@0: michael@0: RefPtr renderingOptions = font->GetGlyphRenderingOptions(); michael@0: michael@0: GlyphBuffer buffer; michael@0: michael@0: std::vector glyphBuf; michael@0: michael@0: for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { michael@0: Glyph newGlyph; michael@0: if (glyphs[i].IsSimpleGlyph()) { michael@0: newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); michael@0: if (mTextRun->IsRightToLeft()) { michael@0: newGlyph.mPosition.x = baselineOrigin.x - advanceSum - michael@0: glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; michael@0: } else { michael@0: newGlyph.mPosition.x = baselineOrigin.x + advanceSum; michael@0: } michael@0: newGlyph.mPosition.y = baselineOrigin.y; michael@0: advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; michael@0: glyphBuf.push_back(newGlyph); michael@0: continue; michael@0: } michael@0: michael@0: if (!glyphs[i].GetGlyphCount()) { michael@0: continue; michael@0: } michael@0: michael@0: gfxTextRun::DetailedGlyph *detailedGlyphs = michael@0: mTextRun->GetDetailedGlyphs(i); michael@0: michael@0: if (glyphs[i].IsMissing()) { michael@0: newGlyph.mIndex = 0; michael@0: if (mTextRun->IsRightToLeft()) { michael@0: newGlyph.mPosition.x = baselineOrigin.x - advanceSum - michael@0: detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; michael@0: } else { michael@0: newGlyph.mPosition.x = baselineOrigin.x + advanceSum; michael@0: } michael@0: newGlyph.mPosition.y = baselineOrigin.y; michael@0: advanceSum += detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; michael@0: glyphBuf.push_back(newGlyph); michael@0: continue; michael@0: } michael@0: michael@0: for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { michael@0: newGlyph.mIndex = detailedGlyphs[c].mGlyphID; michael@0: if (mTextRun->IsRightToLeft()) { michael@0: newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - michael@0: advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; michael@0: } else { michael@0: newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; michael@0: } michael@0: newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; michael@0: glyphBuf.push_back(newGlyph); michael@0: advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; michael@0: } michael@0: } michael@0: michael@0: if (!glyphBuf.size()) { michael@0: // This may happen for glyph runs for a 0 size font. michael@0: continue; michael@0: } michael@0: michael@0: buffer.mGlyphs = &glyphBuf.front(); michael@0: buffer.mNumGlyphs = glyphBuf.size(); michael@0: michael@0: Rect bounds = mCtx->mTarget->GetTransform(). michael@0: TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y, michael@0: mBoundingBox.width, mBoundingBox.height)); michael@0: if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) { michael@0: AdjustedTarget(mCtx, &bounds)-> michael@0: FillGlyphs(scaledFont, buffer, michael@0: CanvasGeneralPattern(). michael@0: ForStyle(mCtx, CanvasRenderingContext2D::Style::FILL, mCtx->mTarget), michael@0: DrawOptions(mState->globalAlpha, mCtx->UsedOperation()), michael@0: renderingOptions); michael@0: } else if (mOp == CanvasRenderingContext2D::TextDrawOperation::STROKE) { michael@0: // stroke glyphs one at a time to avoid poor CoreGraphics performance michael@0: // when stroking a path with a very large number of points michael@0: buffer.mGlyphs = &glyphBuf.front(); michael@0: buffer.mNumGlyphs = 1; michael@0: const ContextState& state = *mState; michael@0: AdjustedTarget target(mCtx, &bounds); michael@0: const StrokeOptions strokeOpts(state.lineWidth, state.lineJoin, michael@0: state.lineCap, state.miterLimit, michael@0: state.dash.Length(), michael@0: state.dash.Elements(), michael@0: state.dashOffset); michael@0: CanvasGeneralPattern cgp; michael@0: const Pattern& patForStyle michael@0: (cgp.ForStyle(mCtx, CanvasRenderingContext2D::Style::STROKE, mCtx->mTarget)); michael@0: const DrawOptions drawOpts(state.globalAlpha, mCtx->UsedOperation()); michael@0: michael@0: for (unsigned i = glyphBuf.size(); i > 0; --i) { michael@0: RefPtr path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget); michael@0: target->Stroke(path, patForStyle, strokeOpts, drawOpts); michael@0: buffer.mGlyphs++; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // current text run michael@0: nsAutoPtr mTextRun; michael@0: michael@0: // pointer to a screen reference context used to measure text and such michael@0: nsRefPtr mThebes; michael@0: michael@0: // Pointer to the draw target we should fill our text to michael@0: CanvasRenderingContext2D *mCtx; michael@0: michael@0: // position of the left side of the string, alphabetic baseline michael@0: gfxPoint mPt; michael@0: michael@0: // current font michael@0: gfxFontGroup* mFontgrp; michael@0: michael@0: // dev pixel conversion factor michael@0: int32_t mAppUnitsPerDevPixel; michael@0: michael@0: // operation (fill or stroke) michael@0: CanvasRenderingContext2D::TextDrawOperation mOp; michael@0: michael@0: // context state michael@0: ContextState *mState; michael@0: michael@0: // union of bounding boxes of all runs, needed for shadows michael@0: gfxRect mBoundingBox; michael@0: michael@0: // true iff the bounding box should be measured michael@0: bool mDoMeasureBoundingBox; michael@0: }; michael@0: michael@0: nsresult michael@0: CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, michael@0: float aX, michael@0: float aY, michael@0: const Optional& aMaxWidth, michael@0: TextDrawOperation aOp, michael@0: float* aWidth) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // spec isn't clear on what should happen if aMaxWidth <= 0, so michael@0: // treat it as an invalid argument michael@0: // technically, 0 should be an invalid value as well, but 0 is the default michael@0: // arg, and there is no way to tell if the default was used michael@0: if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (!mCanvasElement && !mDocShell) { michael@0: NS_WARNING("Canvas element must be non-null or a docshell must be provided"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: if (!presShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIDocument* document = presShell->GetDocument(); michael@0: michael@0: // replace all the whitespace characters with U+0020 SPACE michael@0: nsAutoString textToDraw(aRawText); michael@0: TextReplaceWhitespaceCharacters(textToDraw); michael@0: michael@0: // for now, default to ltr if not in doc michael@0: bool isRTL = false; michael@0: michael@0: if (mCanvasElement && mCanvasElement->IsInDoc()) { michael@0: // try to find the closest context michael@0: nsRefPtr canvasStyle = michael@0: nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, michael@0: nullptr, michael@0: presShell); michael@0: if (!canvasStyle) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: isRTL = canvasStyle->StyleVisibility()->mDirection == michael@0: NS_STYLE_DIRECTION_RTL; michael@0: } else { michael@0: isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; michael@0: } michael@0: michael@0: gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); michael@0: NS_ASSERTION(currentFontStyle, "font group is null"); michael@0: michael@0: // ensure user font set is up to date michael@0: currentFontStyle-> michael@0: SetUserFontSet(presShell->GetPresContext()->GetUserFontSet()); michael@0: michael@0: if (currentFontStyle->GetStyle()->size == 0.0F) { michael@0: if (aWidth) { michael@0: *aWidth = 0; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: // This is only needed to know if we can know the drawing bounding box easily. michael@0: bool doDrawShadow = NeedToDrawShadow(); michael@0: michael@0: CanvasBidiProcessor processor; michael@0: michael@0: GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); michael@0: processor.mPt = gfxPoint(aX, aY); michael@0: processor.mThebes = michael@0: new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()); michael@0: michael@0: // If we don't have a target then we don't have a transform. A target won't michael@0: // be needed in the case where we're measuring the text size. This allows michael@0: // to avoid creating a target if it's only being used to measure text sizes. michael@0: if (mTarget) { michael@0: Matrix matrix = mTarget->GetTransform(); michael@0: processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); michael@0: } michael@0: processor.mCtx = this; michael@0: processor.mOp = aOp; michael@0: processor.mBoundingBox = gfxRect(0, 0, 0, 0); michael@0: processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid; michael@0: processor.mState = &CurrentState(); michael@0: processor.mFontgrp = currentFontStyle; michael@0: michael@0: nscoord totalWidthCoord; michael@0: michael@0: // calls bidi algo twice since it needs the full text width and the michael@0: // bounding boxes before rendering anything michael@0: nsBidi bidiEngine; michael@0: rv = nsBidiPresUtils::ProcessText(textToDraw.get(), michael@0: textToDraw.Length(), michael@0: isRTL ? NSBIDI_RTL : NSBIDI_LTR, michael@0: presShell->GetPresContext(), michael@0: processor, michael@0: nsBidiPresUtils::MODE_MEASURE, michael@0: nullptr, michael@0: 0, michael@0: &totalWidthCoord, michael@0: &bidiEngine); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; michael@0: if (aWidth) { michael@0: *aWidth = totalWidth; michael@0: } michael@0: michael@0: // if only measuring, don't need to do any more work michael@0: if (aOp==TextDrawOperation::MEASURE) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // offset pt.x based on text align michael@0: gfxFloat anchorX; michael@0: michael@0: if (state.textAlign == TextAlign::CENTER) { michael@0: anchorX = .5; michael@0: } else if (state.textAlign == TextAlign::LEFT || michael@0: (!isRTL && state.textAlign == TextAlign::START) || michael@0: (isRTL && state.textAlign == TextAlign::END)) { michael@0: anchorX = 0; michael@0: } else { michael@0: anchorX = 1; michael@0: } michael@0: michael@0: processor.mPt.x -= anchorX * totalWidth; michael@0: michael@0: // offset pt.y based on text baseline michael@0: processor.mFontgrp->UpdateFontList(); // ensure user font generation is current michael@0: NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts"); michael@0: const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics(); michael@0: michael@0: gfxFloat anchorY; michael@0: michael@0: switch (state.textBaseline) michael@0: { michael@0: case TextBaseline::HANGING: michael@0: // fall through; best we can do with the information available michael@0: case TextBaseline::TOP: michael@0: anchorY = fontMetrics.emAscent; michael@0: break; michael@0: case TextBaseline::MIDDLE: michael@0: anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; michael@0: break; michael@0: case TextBaseline::IDEOGRAPHIC: michael@0: // fall through; best we can do with the information available michael@0: case TextBaseline::ALPHABETIC: michael@0: anchorY = 0; michael@0: break; michael@0: case TextBaseline::BOTTOM: michael@0: anchorY = -fontMetrics.emDescent; michael@0: break; michael@0: default: michael@0: MOZ_CRASH("unexpected TextBaseline"); michael@0: } michael@0: michael@0: processor.mPt.y += anchorY; michael@0: michael@0: // correct bounding box to get it to be the correct size/position michael@0: processor.mBoundingBox.width = totalWidth; michael@0: processor.mBoundingBox.MoveBy(processor.mPt); michael@0: michael@0: processor.mPt.x *= processor.mAppUnitsPerDevPixel; michael@0: processor.mPt.y *= processor.mAppUnitsPerDevPixel; michael@0: michael@0: EnsureTarget(); michael@0: Matrix oldTransform = mTarget->GetTransform(); michael@0: // if text is over aMaxWidth, then scale the text horizontally such that its michael@0: // width is precisely aMaxWidth michael@0: if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && michael@0: totalWidth > aMaxWidth.Value()) { michael@0: Matrix newTransform = oldTransform; michael@0: michael@0: // Translate so that the anchor point is at 0,0, then scale and then michael@0: // translate back. michael@0: newTransform.Translate(aX, 0); michael@0: newTransform.Scale(aMaxWidth.Value() / totalWidth, 1); michael@0: newTransform.Translate(-aX, 0); michael@0: /* we do this to avoid an ICE in the android compiler */ michael@0: Matrix androidCompilerBug = newTransform; michael@0: mTarget->SetTransform(androidCompilerBug); michael@0: } michael@0: michael@0: // save the previous bounding box michael@0: gfxRect boundingBox = processor.mBoundingBox; michael@0: michael@0: // don't ever need to measure the bounding box twice michael@0: processor.mDoMeasureBoundingBox = false; michael@0: michael@0: rv = nsBidiPresUtils::ProcessText(textToDraw.get(), michael@0: textToDraw.Length(), michael@0: isRTL ? NSBIDI_RTL : NSBIDI_LTR, michael@0: presShell->GetPresContext(), michael@0: processor, michael@0: nsBidiPresUtils::MODE_DRAW, michael@0: nullptr, michael@0: 0, michael@0: nullptr, michael@0: &bidiEngine); michael@0: michael@0: michael@0: mTarget->SetTransform(oldTransform); michael@0: michael@0: if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL && michael@0: !doDrawShadow) { michael@0: RedrawUser(boundingBox); michael@0: return NS_OK; michael@0: } michael@0: michael@0: Redraw(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle() michael@0: { michael@0: // use lazy initilization for the font group since it's rather expensive michael@0: if (!CurrentState().fontGroup) { michael@0: ErrorResult err; michael@0: NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); michael@0: static float kDefaultFontSize = 10.0; michael@0: SetFont(kDefaultFontStyle, err); michael@0: if (err.Failed()) { michael@0: gfxFontStyle style; michael@0: style.size = kDefaultFontSize; michael@0: CurrentState().fontGroup = michael@0: gfxPlatform::GetPlatform()->CreateFontGroup(NS_LITERAL_STRING("sans-serif"), michael@0: &style, michael@0: nullptr); michael@0: if (CurrentState().fontGroup) { michael@0: CurrentState().font = kDefaultFontStyle; michael@0: michael@0: nsIPresShell* presShell = GetPresShell(); michael@0: if (presShell) { michael@0: CurrentState().fontGroup->SetTextPerfMetrics( michael@0: presShell->GetPresContext()->GetTextPerfMetrics()); michael@0: } michael@0: } else { michael@0: NS_ERROR("Default canvas font is invalid"); michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: return CurrentState().fontGroup; michael@0: } michael@0: michael@0: // michael@0: // line caps/joins michael@0: // michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle) michael@0: { michael@0: CapStyle cap; michael@0: michael@0: if (capstyle.EqualsLiteral("butt")) { michael@0: cap = CapStyle::BUTT; michael@0: } else if (capstyle.EqualsLiteral("round")) { michael@0: cap = CapStyle::ROUND; michael@0: } else if (capstyle.EqualsLiteral("square")) { michael@0: cap = CapStyle::SQUARE; michael@0: } else { michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: return; michael@0: } michael@0: michael@0: CurrentState().lineCap = cap; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetLineCap(nsAString& capstyle) michael@0: { michael@0: switch (CurrentState().lineCap) { michael@0: case CapStyle::BUTT: michael@0: capstyle.AssignLiteral("butt"); michael@0: break; michael@0: case CapStyle::ROUND: michael@0: capstyle.AssignLiteral("round"); michael@0: break; michael@0: case CapStyle::SQUARE: michael@0: capstyle.AssignLiteral("square"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle) michael@0: { michael@0: JoinStyle j; michael@0: michael@0: if (joinstyle.EqualsLiteral("round")) { michael@0: j = JoinStyle::ROUND; michael@0: } else if (joinstyle.EqualsLiteral("bevel")) { michael@0: j = JoinStyle::BEVEL; michael@0: } else if (joinstyle.EqualsLiteral("miter")) { michael@0: j = JoinStyle::MITER_OR_BEVEL; michael@0: } else { michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: return; michael@0: } michael@0: michael@0: CurrentState().lineJoin = j; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error) michael@0: { michael@0: switch (CurrentState().lineJoin) { michael@0: case JoinStyle::ROUND: michael@0: joinstyle.AssignLiteral("round"); michael@0: break; michael@0: case JoinStyle::BEVEL: michael@0: joinstyle.AssignLiteral("bevel"); michael@0: break; michael@0: case JoinStyle::MITER_OR_BEVEL: michael@0: joinstyle.AssignLiteral("miter"); michael@0: break; michael@0: default: michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetMozDash(JSContext* cx, michael@0: const JS::Value& mozDash, michael@0: ErrorResult& error) michael@0: { michael@0: FallibleTArray dash; michael@0: error = JSValToDashArray(cx, mozDash, dash); michael@0: if (!error.Failed()) { michael@0: ContextState& state = CurrentState(); michael@0: state.dash = dash; michael@0: if (state.dash.IsEmpty()) { michael@0: state.dashOffset = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetMozDash(JSContext* cx, michael@0: JS::MutableHandle retval, michael@0: ErrorResult& error) michael@0: { michael@0: DashArrayToJSVal(CurrentState().dash, cx, retval, error); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) michael@0: { michael@0: ContextState& state = CurrentState(); michael@0: if (!state.dash.IsEmpty()) { michael@0: state.dashOffset = mozDashOffset; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments) michael@0: { michael@0: FallibleTArray& dash = CurrentState().dash; michael@0: dash.Clear(); michael@0: michael@0: for (uint32_t x = 0; x < aSegments.Length(); x++) { michael@0: dash.AppendElement(aSegments[x]); michael@0: } michael@0: if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again michael@0: for (uint32_t x = 0; x < aSegments.Length(); x++) { michael@0: dash.AppendElement(aSegments[x]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetLineDash(nsTArray& aSegments) const { michael@0: const FallibleTArray& dash = CurrentState().dash; michael@0: aSegments.Clear(); michael@0: michael@0: for (uint32_t x = 0; x < dash.Length(); x++) { michael@0: aSegments.AppendElement(dash[x]); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetLineDashOffset(double mOffset) { michael@0: CurrentState().dashOffset = mOffset; michael@0: } michael@0: michael@0: double michael@0: CanvasRenderingContext2D::LineDashOffset() const { michael@0: return CurrentState().dashOffset; michael@0: } michael@0: michael@0: bool michael@0: CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double x, double y, const CanvasWindingRule& winding) michael@0: { michael@0: if (!FloatValidate(x,y)) { michael@0: return false; michael@0: } michael@0: michael@0: // Check for site-specific permission and return false if no permission. michael@0: if (mCanvasElement) { michael@0: nsCOMPtr ownerDoc = mCanvasElement->OwnerDoc(); michael@0: if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) michael@0: return false; michael@0: } michael@0: michael@0: EnsureUserSpacePath(winding); michael@0: if (!mPath) { michael@0: return false; michael@0: } michael@0: michael@0: if (mPathTransformWillUpdate) { michael@0: return mPath->ContainsPoint(Point(x, y), mPathToDS); michael@0: } michael@0: michael@0: return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); michael@0: } michael@0: michael@0: bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& mPath, double x, double y, const CanvasWindingRule& mWinding) michael@0: { michael@0: if (!FloatValidate(x,y)) { michael@0: return false; michael@0: } michael@0: michael@0: // Check for site-specific permission and return false if no permission. michael@0: if (mCanvasElement) { michael@0: nsCOMPtr ownerDoc = mCanvasElement->OwnerDoc(); michael@0: if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) michael@0: return false; michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: RefPtr tempPath = mPath.GetPath(mWinding, mTarget); michael@0: michael@0: return tempPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); michael@0: } michael@0: michael@0: bool michael@0: CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double x, double y) michael@0: { michael@0: if (!FloatValidate(x,y)) { michael@0: return false; michael@0: } michael@0: michael@0: // Check for site-specific permission and return false if no permission. michael@0: if (mCanvasElement) { michael@0: nsCOMPtr ownerDoc = mCanvasElement->OwnerDoc(); michael@0: if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) michael@0: return false; michael@0: } michael@0: michael@0: EnsureUserSpacePath(); michael@0: if (!mPath) { michael@0: return false; michael@0: } michael@0: michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: StrokeOptions strokeOptions(state.lineWidth, michael@0: state.lineJoin, michael@0: state.lineCap, michael@0: state.miterLimit, michael@0: state.dash.Length(), michael@0: state.dash.Elements(), michael@0: state.dashOffset); michael@0: michael@0: if (mPathTransformWillUpdate) { michael@0: return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS); michael@0: } michael@0: return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); michael@0: } michael@0: michael@0: bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& mPath, double x, double y) michael@0: { michael@0: if (!FloatValidate(x,y)) { michael@0: return false; michael@0: } michael@0: michael@0: // Check for site-specific permission and return false if no permission. michael@0: if (mCanvasElement) { michael@0: nsCOMPtr ownerDoc = mCanvasElement->OwnerDoc(); michael@0: if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) michael@0: return false; michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: RefPtr tempPath = mPath.GetPath(CanvasWindingRule::Nonzero, mTarget); michael@0: michael@0: const ContextState &state = CurrentState(); michael@0: michael@0: StrokeOptions strokeOptions(state.lineWidth, michael@0: state.lineJoin, michael@0: state.lineCap, michael@0: state.miterLimit, michael@0: state.dash.Length(), michael@0: state.dash.Elements(), michael@0: state.dashOffset); michael@0: michael@0: return tempPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); michael@0: } michael@0: michael@0: // michael@0: // image michael@0: // michael@0: michael@0: // drawImage(in HTMLImageElement image, in float dx, in float dy); michael@0: // -- render image from 0,0 at dx,dy top-left coords michael@0: // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh); michael@0: // -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh michael@0: // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); michael@0: // -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas michael@0: michael@0: // If only dx and dy are passed in then optional_argc should be 0. If only michael@0: // dx, dy, dw and dh are passed in then optional_argc should be 2. The only michael@0: // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh michael@0: // are all passed in. michael@0: michael@0: void michael@0: CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, michael@0: double sx, double sy, double sw, michael@0: double sh, double dx, double dy, michael@0: double dw, double dh, michael@0: uint8_t optional_argc, michael@0: ErrorResult& error) michael@0: { michael@0: MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6); michael@0: michael@0: RefPtr srcSurf; michael@0: gfxIntSize imgSize; michael@0: michael@0: Element* element; michael@0: michael@0: EnsureTarget(); michael@0: if (image.IsHTMLCanvasElement()) { michael@0: HTMLCanvasElement* canvas = &image.GetAsHTMLCanvasElement(); michael@0: element = canvas; michael@0: nsIntSize size = canvas->GetSize(); michael@0: if (size.width == 0 || size.height == 0) { michael@0: error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: } else { michael@0: if (image.IsHTMLImageElement()) { michael@0: HTMLImageElement* img = &image.GetAsHTMLImageElement(); michael@0: element = img; michael@0: } else { michael@0: HTMLVideoElement* video = &image.GetAsHTMLVideoElement(); michael@0: element = video; michael@0: } michael@0: michael@0: srcSurf = michael@0: CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); michael@0: } michael@0: michael@0: nsLayoutUtils::DirectDrawInfo drawInfo; michael@0: michael@0: if (!srcSurf) { michael@0: // The canvas spec says that drawImage should draw the first frame michael@0: // of animated images. We also don't want to rasterize vector images. michael@0: uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME | michael@0: nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS; michael@0: nsLayoutUtils::SurfaceFromElementResult res = michael@0: nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget); michael@0: michael@0: if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) { michael@0: // Spec says to silently do nothing if the element is still loading. michael@0: if (!res.mIsStillLoading) { michael@0: error.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: imgSize = res.mSize; michael@0: michael@0: // Scale sw/sh based on aspect ratio michael@0: if (image.IsHTMLVideoElement()) { michael@0: HTMLVideoElement* video = &image.GetAsHTMLVideoElement(); michael@0: int32_t displayWidth = video->VideoWidth(); michael@0: int32_t displayHeight = video->VideoHeight(); michael@0: sw *= (double)imgSize.width / (double)displayWidth; michael@0: sh *= (double)imgSize.height / (double)displayHeight; michael@0: } michael@0: michael@0: if (mCanvasElement) { michael@0: CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, michael@0: res.mPrincipal, res.mIsWriteOnly, michael@0: res.mCORSUsed); michael@0: } michael@0: michael@0: if (res.mSourceSurface) { michael@0: if (res.mImageRequest) { michael@0: CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest, michael@0: res.mSourceSurface, imgSize); michael@0: } michael@0: michael@0: srcSurf = res.mSourceSurface; michael@0: } else { michael@0: drawInfo = res.mDrawInfo; michael@0: } michael@0: } michael@0: michael@0: if (optional_argc == 0) { michael@0: sx = sy = 0.0; michael@0: dw = sw = (double) imgSize.width; michael@0: dh = sh = (double) imgSize.height; michael@0: } else if (optional_argc == 2) { michael@0: sx = sy = 0.0; michael@0: sw = (double) imgSize.width; michael@0: sh = (double) imgSize.height; michael@0: } michael@0: michael@0: if (sw == 0.0 || sh == 0.0) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (dw == 0.0 || dh == 0.0) { michael@0: // not really failure, but nothing to do -- michael@0: // and noone likes a divide-by-zero michael@0: return; michael@0: } michael@0: michael@0: if (sx < 0.0 || sy < 0.0 || michael@0: sw < 0.0 || sw > (double) imgSize.width || michael@0: sh < 0.0 || sh > (double) imgSize.height || michael@0: dw < 0.0 || dh < 0.0) { michael@0: // XXX - Unresolved spec issues here, for now return error. michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: Filter filter; michael@0: michael@0: if (CurrentState().imageSmoothingEnabled) michael@0: filter = mgfx::Filter::LINEAR; michael@0: else michael@0: filter = mgfx::Filter::POINT; michael@0: michael@0: mgfx::Rect bounds; michael@0: michael@0: if (NeedToDrawShadow()) { michael@0: bounds = mgfx::Rect(dx, dy, dw, dh); michael@0: bounds = mTarget->GetTransform().TransformBounds(bounds); michael@0: } michael@0: michael@0: if (srcSurf) { michael@0: AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> michael@0: DrawSurface(srcSurf, michael@0: mgfx::Rect(dx, dy, dw, dh), michael@0: mgfx::Rect(sx, sy, sw, sh), michael@0: DrawSurfaceOptions(filter), michael@0: DrawOptions(CurrentState().globalAlpha, UsedOperation())); michael@0: } else { michael@0: DrawDirectlyToCanvas(drawInfo, &bounds, dx, dy, dw, dh, michael@0: sx, sy, sw, sh, imgSize); michael@0: } michael@0: michael@0: RedrawUser(gfxRect(dx, dy, dw, dh)); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::DrawDirectlyToCanvas( michael@0: const nsLayoutUtils::DirectDrawInfo& image, michael@0: mgfx::Rect* bounds, double dx, double dy, michael@0: double dw, double dh, double sx, double sy, michael@0: double sw, double sh, gfxIntSize imgSize) michael@0: { michael@0: gfxMatrix contextMatrix; michael@0: michael@0: AdjustedTarget tempTarget(this, bounds->IsEmpty() ? nullptr: bounds); michael@0: michael@0: // get any already existing transforms on the context. Include transformations used for context shadow michael@0: if (tempTarget) { michael@0: Matrix matrix = tempTarget->GetTransform(); michael@0: contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21, michael@0: matrix._22, matrix._31, matrix._32); michael@0: } michael@0: michael@0: gfxMatrix transformMatrix; michael@0: transformMatrix.Translate(gfxPoint(sx, sy)); michael@0: if (dw > 0 && dh > 0) { michael@0: transformMatrix.Scale(sw/dw, sh/dh); michael@0: } michael@0: transformMatrix.Translate(gfxPoint(-dx, -dy)); michael@0: michael@0: nsRefPtr context = new gfxContext(tempTarget); michael@0: context->SetMatrix(contextMatrix); michael@0: michael@0: // FLAG_CLAMP is added for increased performance michael@0: uint32_t modifiedFlags = image.mDrawingFlags | imgIContainer::FLAG_CLAMP; michael@0: michael@0: nsresult rv = image.mImgContainer-> michael@0: Draw(context, GraphicsFilter::FILTER_GOOD, transformMatrix, michael@0: gfxRect(gfxPoint(dx, dy), gfxIntSize(dw, dh)), michael@0: nsIntRect(nsIntPoint(0, 0), gfxIntSize(imgSize.width, imgSize.height)), michael@0: gfxIntSize(imgSize.width, imgSize.height), nullptr, image.mWhichFrame, michael@0: modifiedFlags); michael@0: michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: michael@0: static bool michael@0: IsStandardCompositeOp(CompositionOp op) michael@0: { michael@0: return (op == CompositionOp::OP_SOURCE || michael@0: op == CompositionOp::OP_ATOP || michael@0: op == CompositionOp::OP_IN || michael@0: op == CompositionOp::OP_OUT || michael@0: op == CompositionOp::OP_OVER || michael@0: op == CompositionOp::OP_DEST_IN || michael@0: op == CompositionOp::OP_DEST_OUT || michael@0: op == CompositionOp::OP_DEST_OVER || michael@0: op == CompositionOp::OP_DEST_ATOP || michael@0: op == CompositionOp::OP_ADD || michael@0: op == CompositionOp::OP_XOR); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op, michael@0: ErrorResult& error) michael@0: { michael@0: CompositionOp comp_op; michael@0: michael@0: #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ michael@0: if (op.EqualsLiteral(cvsop)) \ michael@0: comp_op = CompositionOp::OP_##op2d; michael@0: michael@0: CANVAS_OP_TO_GFX_OP("copy", SOURCE) michael@0: else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) michael@0: else CANVAS_OP_TO_GFX_OP("source-in", IN) michael@0: else CANVAS_OP_TO_GFX_OP("source-out", OUT) michael@0: else CANVAS_OP_TO_GFX_OP("source-over", OVER) michael@0: else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) michael@0: else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) michael@0: else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) michael@0: else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) michael@0: else CANVAS_OP_TO_GFX_OP("lighter", ADD) michael@0: else CANVAS_OP_TO_GFX_OP("xor", XOR) michael@0: else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) michael@0: else CANVAS_OP_TO_GFX_OP("screen", SCREEN) michael@0: else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) michael@0: else CANVAS_OP_TO_GFX_OP("darken", DARKEN) michael@0: else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) michael@0: else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) michael@0: else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) michael@0: else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) michael@0: else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) michael@0: else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) michael@0: else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) michael@0: else CANVAS_OP_TO_GFX_OP("hue", HUE) michael@0: else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) michael@0: else CANVAS_OP_TO_GFX_OP("color", COLOR) michael@0: else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: else return; michael@0: michael@0: if (!IsStandardCompositeOp(comp_op)) { michael@0: Demote(); michael@0: } michael@0: michael@0: #undef CANVAS_OP_TO_GFX_OP michael@0: CurrentState().op = comp_op; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op, michael@0: ErrorResult& error) michael@0: { michael@0: CompositionOp comp_op = CurrentState().op; michael@0: michael@0: #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ michael@0: if (comp_op == CompositionOp::OP_##op2d) \ michael@0: op.AssignLiteral(cvsop); michael@0: michael@0: CANVAS_OP_TO_GFX_OP("copy", SOURCE) michael@0: else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) michael@0: else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) michael@0: else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) michael@0: else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) michael@0: else CANVAS_OP_TO_GFX_OP("lighter", ADD) michael@0: else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) michael@0: else CANVAS_OP_TO_GFX_OP("source-in", IN) michael@0: else CANVAS_OP_TO_GFX_OP("source-out", OUT) michael@0: else CANVAS_OP_TO_GFX_OP("source-over", OVER) michael@0: else CANVAS_OP_TO_GFX_OP("xor", XOR) michael@0: else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) michael@0: else CANVAS_OP_TO_GFX_OP("screen", SCREEN) michael@0: else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) michael@0: else CANVAS_OP_TO_GFX_OP("darken", DARKEN) michael@0: else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) michael@0: else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) michael@0: else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) michael@0: else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) michael@0: else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) michael@0: else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) michael@0: else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) michael@0: else CANVAS_OP_TO_GFX_OP("hue", HUE) michael@0: else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) michael@0: else CANVAS_OP_TO_GFX_OP("color", COLOR) michael@0: else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) michael@0: else { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: if (!IsStandardCompositeOp(comp_op)) { michael@0: Demote(); michael@0: } michael@0: michael@0: #undef CANVAS_OP_TO_GFX_OP michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& window, double x, michael@0: double y, double w, double h, michael@0: const nsAString& bgColor, michael@0: uint32_t flags, ErrorResult& error) michael@0: { michael@0: // protect against too-large surfaces that will cause allocation michael@0: // or overflow issues michael@0: if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)), michael@0: 0xffff)) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: // We can't allow web apps to call this until we fix at least the michael@0: // following potential security issues: michael@0: // -- rendering cross-domain IFRAMEs and then extracting the results michael@0: // -- rendering the user's theme and then extracting the results michael@0: // -- rendering native anonymous content (e.g., file input paths; michael@0: // scrollbars should be allowed) michael@0: if (!nsContentUtils::IsCallerChrome()) { michael@0: // not permitted to use DrawWindow michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: error.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Flush layout updates michael@0: if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) { michael@0: nsContentUtils::FlushLayoutForTree(&window); michael@0: } michael@0: michael@0: nsRefPtr presContext; michael@0: nsIDocShell* docshell = window.GetDocShell(); michael@0: if (docshell) { michael@0: docshell->GetPresContext(getter_AddRefs(presContext)); michael@0: } michael@0: if (!presContext) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nscolor backgroundColor; michael@0: if (!ParseColor(bgColor, &backgroundColor)) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x), michael@0: nsPresContext::CSSPixelsToAppUnits((float)y), michael@0: nsPresContext::CSSPixelsToAppUnits((float)w), michael@0: nsPresContext::CSSPixelsToAppUnits((float)h)); michael@0: uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | michael@0: nsIPresShell::RENDER_DOCUMENT_RELATIVE); michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { michael@0: renderDocFlags |= nsIPresShell::RENDER_CARET; michael@0: } michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { michael@0: renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | michael@0: nsIPresShell::RENDER_DOCUMENT_RELATIVE); michael@0: } michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) { michael@0: renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; michael@0: } michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) { michael@0: renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; michael@0: } michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) { michael@0: renderDocFlags |= nsIPresShell::RENDER_DRAWWINDOW_NOT_FLUSHING; michael@0: } michael@0: michael@0: // gfxContext-over-Azure may modify the DrawTarget's transform, so michael@0: // save and restore it michael@0: Matrix matrix = mTarget->GetTransform(); michael@0: double sw = matrix._11 * w; michael@0: double sh = matrix._22 * h; michael@0: if (!sw || !sh) { michael@0: return; michael@0: } michael@0: nsRefPtr thebes; michael@0: RefPtr drawDT; michael@0: if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget)) { michael@0: thebes = new gfxContext(mTarget); michael@0: thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, michael@0: matrix._22, matrix._31, matrix._32)); michael@0: } else { michael@0: drawDT = michael@0: gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)), michael@0: SurfaceFormat::B8G8R8A8); michael@0: if (!drawDT) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: thebes = new gfxContext(drawDT); michael@0: thebes->Scale(matrix._11, matrix._22); michael@0: } michael@0: michael@0: nsCOMPtr shell = presContext->PresShell(); michael@0: unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); michael@0: if (drawDT) { michael@0: RefPtr snapshot = drawDT->Snapshot(); michael@0: RefPtr data = snapshot->GetDataSurface(); michael@0: michael@0: RefPtr source = michael@0: mTarget->CreateSourceSurfaceFromData(data->GetData(), michael@0: data->GetSize(), michael@0: data->Stride(), michael@0: data->GetFormat()); michael@0: michael@0: if (!source) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: mgfx::Rect destRect(0, 0, w, h); michael@0: mgfx::Rect sourceRect(0, 0, sw, sh); michael@0: mTarget->DrawSurface(source, destRect, sourceRect, michael@0: DrawSurfaceOptions(mgfx::Filter::POINT), michael@0: DrawOptions(1.0f, CompositionOp::OP_OVER, michael@0: AntialiasMode::NONE)); michael@0: mTarget->Flush(); michael@0: } else { michael@0: mTarget->SetTransform(matrix); michael@0: } michael@0: michael@0: // note that x and y are coordinates in the document that michael@0: // we're drawing; x and y are drawn to 0,0 in current user michael@0: // space. michael@0: RedrawUser(gfxRect(0, 0, w, h)); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::AsyncDrawXULElement(nsXULElement& elem, michael@0: double x, double y, michael@0: double w, double h, michael@0: const nsAString& bgColor, michael@0: uint32_t flags, michael@0: ErrorResult& error) michael@0: { michael@0: // We can't allow web apps to call this until we fix at least the michael@0: // following potential security issues: michael@0: // -- rendering cross-domain IFRAMEs and then extracting the results michael@0: // -- rendering the user's theme and then extracting the results michael@0: // -- rendering native anonymous content (e.g., file input paths; michael@0: // scrollbars should be allowed) michael@0: if (!nsContentUtils::IsCallerChrome()) { michael@0: // not permitted to use DrawWindow michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: error.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: #if 0 michael@0: nsCOMPtr loaderOwner = do_QueryInterface(&elem); michael@0: if (!loaderOwner) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr frameloader = loaderOwner->GetFrameLoader(); michael@0: if (!frameloader) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: PBrowserParent *child = frameloader->GetRemoteBrowser(); michael@0: if (!child) { michael@0: nsCOMPtr window = michael@0: do_GetInterface(frameloader->GetExistingDocShell()); michael@0: if (!window) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: return DrawWindow(window, x, y, w, h, bgColor, flags); michael@0: } michael@0: michael@0: // protect against too-large surfaces that will cause allocation michael@0: // or overflow issues michael@0: if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: michael@0: bool flush = michael@0: (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0; michael@0: michael@0: uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { michael@0: renderDocFlags |= nsIPresShell::RENDER_CARET; michael@0: } michael@0: if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { michael@0: renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; michael@0: } michael@0: michael@0: nsRect rect(nsPresContext::CSSPixelsToAppUnits(x), michael@0: nsPresContext::CSSPixelsToAppUnits(y), michael@0: nsPresContext::CSSPixelsToAppUnits(w), michael@0: nsPresContext::CSSPixelsToAppUnits(h)); michael@0: if (mIPC) { michael@0: PDocumentRendererParent *pdocrender = michael@0: child->SendPDocumentRendererConstructor(rect, michael@0: mThebes->CurrentMatrix(), michael@0: nsString(aBGColor), michael@0: renderDocFlags, flush, michael@0: nsIntSize(mWidth, mHeight)); michael@0: if (!pdocrender) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: DocumentRendererParent *docrender = michael@0: static_cast(pdocrender); michael@0: michael@0: docrender->SetCanvasContext(this, mThebes); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // michael@0: // device pixel getting/setting michael@0: // michael@0: michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, michael@0: double aSy, double aSw, michael@0: double aSh, ErrorResult& error) michael@0: { michael@0: EnsureTarget(); michael@0: if (!IsTargetValid()) { michael@0: error.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!mCanvasElement && !mDocShell) { michael@0: NS_ERROR("No canvas element and no docshell in GetImageData!!!"); michael@0: error.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Check only if we have a canvas element; if we were created with a docshell, michael@0: // then it's special internal use. michael@0: if (mCanvasElement && mCanvasElement->IsWriteOnly() && michael@0: !nsContentUtils::IsCallerChrome()) michael@0: { michael@0: // XXX ERRMSG we need to report an error to developers here! (bug 329026) michael@0: error.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!NS_finite(aSx) || !NS_finite(aSy) || michael@0: !NS_finite(aSw) || !NS_finite(aSh)) { michael@0: error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!aSw || !aSh) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t x = JS_DoubleToInt32(aSx); michael@0: int32_t y = JS_DoubleToInt32(aSy); michael@0: int32_t wi = JS_DoubleToInt32(aSw); michael@0: int32_t hi = JS_DoubleToInt32(aSh); michael@0: michael@0: // Handle negative width and height by flipping the rectangle over in the michael@0: // relevant direction. michael@0: uint32_t w, h; michael@0: if (aSw < 0) { michael@0: w = -wi; michael@0: x -= w; michael@0: } else { michael@0: w = wi; michael@0: } michael@0: if (aSh < 0) { michael@0: h = -hi; michael@0: y -= h; michael@0: } else { michael@0: h = hi; michael@0: } michael@0: michael@0: if (w == 0) { michael@0: w = 1; michael@0: } michael@0: if (h == 0) { michael@0: h = 1; michael@0: } michael@0: michael@0: JS::Rooted array(aCx); michael@0: error = GetImageDataArray(aCx, x, y, w, h, array.address()); michael@0: if (error.Failed()) { michael@0: return nullptr; michael@0: } michael@0: MOZ_ASSERT(array); michael@0: michael@0: nsRefPtr imageData = new ImageData(w, h, *array); michael@0: return imageData.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, michael@0: int32_t aX, michael@0: int32_t aY, michael@0: uint32_t aWidth, michael@0: uint32_t aHeight, michael@0: JSObject** aRetval) michael@0: { michael@0: MOZ_ASSERT(aWidth && aHeight); michael@0: michael@0: CheckedInt len = CheckedInt(aWidth) * aHeight * 4; michael@0: if (!len.isValid()) { michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: } michael@0: michael@0: CheckedInt rightMost = CheckedInt(aX) + aWidth; michael@0: CheckedInt bottomMost = CheckedInt(aY) + aHeight; michael@0: michael@0: if (!rightMost.isValid() || !bottomMost.isValid()) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: IntRect srcRect(0, 0, mWidth, mHeight); michael@0: IntRect destRect(aX, aY, aWidth, aHeight); michael@0: IntRect srcReadRect = srcRect.Intersect(destRect); michael@0: RefPtr readback; michael@0: if (!srcReadRect.IsEmpty() && !mZero) { michael@0: RefPtr snapshot = mTarget->Snapshot(); michael@0: if (snapshot) { michael@0: readback = snapshot->GetDataSurface(); michael@0: } michael@0: if (!readback || !readback->GetData()) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: JS::Rooted darray(aCx, JS_NewUint8ClampedArray(aCx, len.value())); michael@0: if (!darray) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (mZero) { michael@0: *aRetval = darray; michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint8_t* data = JS_GetUint8ClampedArrayData(darray); michael@0: michael@0: // Check for site-specific permission and return all-white, opaque pixel michael@0: // data if no permission. This check is not needed if the canvas was michael@0: // created with a docshell (that is only done for special internal uses). michael@0: bool usePlaceholder = false; michael@0: if (mCanvasElement) { michael@0: nsCOMPtr ownerDoc = mCanvasElement->OwnerDoc(); michael@0: usePlaceholder = !ownerDoc || michael@0: !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx); michael@0: } michael@0: michael@0: if (usePlaceholder) { michael@0: memset(data, 0xFF, len.value()); michael@0: *aRetval = darray; michael@0: return NS_OK; michael@0: } michael@0: michael@0: IntRect dstWriteRect = srcReadRect; michael@0: dstWriteRect.MoveBy(-aX, -aY); michael@0: michael@0: uint8_t* src = data; michael@0: uint32_t srcStride = aWidth * 4; michael@0: if (readback) { michael@0: srcStride = readback->Stride(); michael@0: src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4; michael@0: } michael@0: michael@0: // NOTE! dst is the same as src, and this relies on reading michael@0: // from src and advancing that ptr before writing to dst. michael@0: // NOTE! I'm not sure that it is, I think this comment might have been michael@0: // inherited from Thebes canvas and is no longer true michael@0: uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; michael@0: michael@0: if (mOpaque) { michael@0: for (int32_t j = 0; j < dstWriteRect.height; ++j) { michael@0: for (int32_t i = 0; i < dstWriteRect.width; ++i) { michael@0: // XXX Is there some useful swizzle MMX we can use here? michael@0: #if MOZ_LITTLE_ENDIAN michael@0: uint8_t b = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t r = *src++; michael@0: src++; michael@0: #else michael@0: src++; michael@0: uint8_t r = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t b = *src++; michael@0: #endif michael@0: *dst++ = r; michael@0: *dst++ = g; michael@0: *dst++ = b; michael@0: *dst++ = 255; michael@0: } michael@0: src += srcStride - (dstWriteRect.width * 4); michael@0: dst += (aWidth * 4) - (dstWriteRect.width * 4); michael@0: } michael@0: } else michael@0: for (int32_t j = 0; j < dstWriteRect.height; ++j) { michael@0: for (int32_t i = 0; i < dstWriteRect.width; ++i) { michael@0: // XXX Is there some useful swizzle MMX we can use here? michael@0: #if MOZ_LITTLE_ENDIAN michael@0: uint8_t b = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t r = *src++; michael@0: uint8_t a = *src++; michael@0: #else michael@0: uint8_t a = *src++; michael@0: uint8_t r = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t b = *src++; michael@0: #endif michael@0: // Convert to non-premultiplied color michael@0: *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + r]; michael@0: *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + g]; michael@0: *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + b]; michael@0: *dst++ = a; michael@0: } michael@0: src += srcStride - (dstWriteRect.width * 4); michael@0: dst += (aWidth * 4) - (dstWriteRect.width * 4); michael@0: } michael@0: michael@0: *aRetval = darray; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::EnsureErrorTarget() michael@0: { michael@0: if (sErrorTarget) { michael@0: return; michael@0: } michael@0: michael@0: RefPtr errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8); michael@0: MOZ_ASSERT(errorTarget, "Failed to allocate the error target!"); michael@0: michael@0: sErrorTarget = errorTarget; michael@0: NS_ADDREF(sErrorTarget); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::FillRuleChanged() michael@0: { michael@0: if (mPath) { michael@0: mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); michael@0: mPath = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::PutImageData(ImageData& imageData, double dx, michael@0: double dy, ErrorResult& error) michael@0: { michael@0: dom::Uint8ClampedArray arr(imageData.GetDataObject()); michael@0: michael@0: error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), michael@0: imageData.Width(), imageData.Height(), michael@0: &arr, false, 0, 0, 0, 0); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::PutImageData(ImageData& imageData, double dx, michael@0: double dy, double dirtyX, michael@0: double dirtyY, double dirtyWidth, michael@0: double dirtyHeight, michael@0: ErrorResult& error) michael@0: { michael@0: dom::Uint8ClampedArray arr(imageData.GetDataObject()); michael@0: michael@0: error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), michael@0: imageData.Width(), imageData.Height(), michael@0: &arr, true, michael@0: JS_DoubleToInt32(dirtyX), michael@0: JS_DoubleToInt32(dirtyY), michael@0: JS_DoubleToInt32(dirtyWidth), michael@0: JS_DoubleToInt32(dirtyHeight)); michael@0: } michael@0: michael@0: // void putImageData (in ImageData d, in float x, in float y); michael@0: // void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight); michael@0: michael@0: nsresult michael@0: CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h, michael@0: dom::Uint8ClampedArray* aArray, michael@0: bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY, michael@0: int32_t dirtyWidth, int32_t dirtyHeight) michael@0: { michael@0: if (w == 0 || h == 0) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: IntRect dirtyRect; michael@0: IntRect imageDataRect(0, 0, w, h); michael@0: michael@0: if (hasDirtyRect) { michael@0: // fix up negative dimensions michael@0: if (dirtyWidth < 0) { michael@0: NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: michael@0: CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth; michael@0: michael@0: if (!checkedDirtyX.isValid()) michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: michael@0: dirtyX = checkedDirtyX.value(); michael@0: dirtyWidth = -dirtyWidth; michael@0: } michael@0: michael@0: if (dirtyHeight < 0) { michael@0: NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: michael@0: CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight; michael@0: michael@0: if (!checkedDirtyY.isValid()) michael@0: return NS_ERROR_DOM_INDEX_SIZE_ERR; michael@0: michael@0: dirtyY = checkedDirtyY.value(); michael@0: dirtyHeight = -dirtyHeight; michael@0: } michael@0: michael@0: // bound the dirty rect within the imageData rectangle michael@0: dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); michael@0: michael@0: if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) michael@0: return NS_OK; michael@0: } else { michael@0: dirtyRect = imageDataRect; michael@0: } michael@0: michael@0: dirtyRect.MoveBy(IntPoint(x, y)); michael@0: dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); michael@0: michael@0: if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: aArray->ComputeLengthAndData(); michael@0: michael@0: uint32_t dataLen = aArray->Length(); michael@0: michael@0: uint32_t len = w * h * 4; michael@0: if (dataLen != len) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: nsRefPtr imgsurf = new gfxImageSurface(gfxIntSize(w, h), michael@0: gfxImageFormat::ARGB32, michael@0: false); michael@0: if (!imgsurf || imgsurf->CairoStatus()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint8_t *src = aArray->Data(); michael@0: uint8_t *dst = imgsurf->Data(); michael@0: michael@0: for (uint32_t j = 0; j < h; j++) { michael@0: for (uint32_t i = 0; i < w; i++) { michael@0: uint8_t r = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t b = *src++; michael@0: uint8_t a = *src++; michael@0: michael@0: // Convert to premultiplied color (losslessly if the input came from getImageData) michael@0: #if MOZ_LITTLE_ENDIAN michael@0: *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b]; michael@0: *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g]; michael@0: *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r]; michael@0: *dst++ = a; michael@0: #else michael@0: *dst++ = a; michael@0: *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r]; michael@0: *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g]; michael@0: *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b]; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: EnsureTarget(); michael@0: if (!IsTargetValid()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: RefPtr sourceSurface = michael@0: mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), SurfaceFormat::B8G8R8A8); michael@0: michael@0: // In certain scenarios, requesting larger than 8k image fails. Bug 803568 michael@0: // covers the details of how to run into it, but the full detailed michael@0: // investigation hasn't been done to determine the underlying cause. We michael@0: // will just handle the failure to allocate the surface to avoid a crash. michael@0: if (!sourceSurface) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mTarget->CopySurface(sourceSurface, michael@0: IntRect(dirtyRect.x - x, dirtyRect.y - y, michael@0: dirtyRect.width, dirtyRect.height), michael@0: IntPoint(dirtyRect.x, dirtyRect.y)); michael@0: michael@0: Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static already_AddRefed michael@0: CreateImageData(JSContext* cx, CanvasRenderingContext2D* context, michael@0: uint32_t w, uint32_t h, ErrorResult& error) michael@0: { michael@0: if (w == 0) michael@0: w = 1; michael@0: if (h == 0) michael@0: h = 1; michael@0: michael@0: CheckedInt len = CheckedInt(w) * h * 4; michael@0: if (!len.isValid()) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Create the fast typed array; it's initialized to 0 by default. michael@0: JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value()); michael@0: if (!darray) { michael@0: error.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr imageData = michael@0: new mozilla::dom::ImageData(w, h, *darray); michael@0: return imageData.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw, michael@0: double sh, ErrorResult& error) michael@0: { michael@0: if (!sw || !sh) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t wi = JS_DoubleToInt32(sw); michael@0: int32_t hi = JS_DoubleToInt32(sh); michael@0: michael@0: uint32_t w = Abs(wi); michael@0: uint32_t h = Abs(hi); michael@0: return mozilla::dom::CreateImageData(cx, this, w, h, error); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::CreateImageData(JSContext* cx, michael@0: ImageData& imagedata, michael@0: ErrorResult& error) michael@0: { michael@0: return mozilla::dom::CreateImageData(cx, this, imagedata.Width(), michael@0: imagedata.Height(), error); michael@0: } michael@0: michael@0: static uint8_t g2DContextLayerUserData; michael@0: michael@0: already_AddRefed michael@0: CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, michael@0: CanvasLayer *aOldLayer, michael@0: LayerManager *aManager) michael@0: { michael@0: // Don't call EnsureTarget() ... if there isn't already a surface, then michael@0: // we have nothing to paint and there is no need to create a surface just michael@0: // to paint nothing. Also, EnsureTarget() can cause creation of a persistent michael@0: // layer manager which must NOT happen during a paint. michael@0: if (!mTarget || !IsTargetValid()) { michael@0: // No DidTransactionCallback will be received, so mark the context clean michael@0: // now so future invalidations will be dispatched. michael@0: MarkContextClean(); michael@0: return nullptr; michael@0: } michael@0: michael@0: mTarget->Flush(); michael@0: michael@0: if (!mResetLayer && aOldLayer) { michael@0: CanvasRenderingContext2DUserData* userData = michael@0: static_cast( michael@0: aOldLayer->GetUserData(&g2DContextLayerUserData)); michael@0: michael@0: CanvasLayer::Data data; michael@0: if (mStream) { michael@0: #ifdef USE_SKIA michael@0: SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); michael@0: michael@0: if (glue) { michael@0: data.mGLContext = glue->GetGLContext(); michael@0: data.mStream = mStream.get(); michael@0: } michael@0: #endif michael@0: } else { michael@0: data.mDrawTarget = mTarget; michael@0: } michael@0: michael@0: if (userData && userData->IsForContext(this) && aOldLayer->IsDataValid(data)) { michael@0: nsRefPtr ret = aOldLayer; michael@0: return ret.forget(); michael@0: } michael@0: } michael@0: michael@0: nsRefPtr canvasLayer = aManager->CreateCanvasLayer(); michael@0: if (!canvasLayer) { michael@0: NS_WARNING("CreateCanvasLayer returned null!"); michael@0: // No DidTransactionCallback will be received, so mark the context clean michael@0: // now so future invalidations will be dispatched. michael@0: MarkContextClean(); michael@0: return nullptr; michael@0: } michael@0: CanvasRenderingContext2DUserData *userData = nullptr; michael@0: // Make the layer tell us whenever a transaction finishes (including michael@0: // the current transaction), so we can clear our invalidation state and michael@0: // start invalidating again. We need to do this for all layers since michael@0: // callers of DrawWindow may be expecting to receive normal invalidation michael@0: // notifications after this paint. michael@0: michael@0: // The layer will be destroyed when we tear down the presentation michael@0: // (at the latest), at which time this userData will be destroyed, michael@0: // releasing the reference to the element. michael@0: // The userData will receive DidTransactionCallbacks, which flush the michael@0: // the invalidation state to indicate that the canvas is up to date. michael@0: userData = new CanvasRenderingContext2DUserData(this); michael@0: canvasLayer->SetDidTransactionCallback( michael@0: CanvasRenderingContext2DUserData::DidTransactionCallback, userData); michael@0: canvasLayer->SetUserData(&g2DContextLayerUserData, userData); michael@0: michael@0: CanvasLayer::Data data; michael@0: if (mStream) { michael@0: SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); michael@0: michael@0: if (glue) { michael@0: canvasLayer->SetPreTransactionCallback( michael@0: CanvasRenderingContext2DUserData::PreTransactionCallback, userData); michael@0: #if USE_SKIA michael@0: data.mGLContext = glue->GetGLContext(); michael@0: #endif michael@0: data.mStream = mStream.get(); michael@0: data.mTexID = (uint32_t)((uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE)); michael@0: } michael@0: } else { michael@0: data.mDrawTarget = mTarget; michael@0: } michael@0: michael@0: data.mSize = nsIntSize(mWidth, mHeight); michael@0: michael@0: canvasLayer->Initialize(data); michael@0: uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; michael@0: canvasLayer->SetContentFlags(flags); michael@0: canvasLayer->Updated(); michael@0: michael@0: mResetLayer = false; michael@0: michael@0: return canvasLayer.forget(); michael@0: } michael@0: michael@0: void michael@0: CanvasRenderingContext2D::MarkContextClean() michael@0: { michael@0: if (mInvalidateCount > 0) { michael@0: mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; michael@0: } michael@0: mIsEntireFrameInvalid = false; michael@0: mInvalidateCount = 0; michael@0: } michael@0: michael@0: michael@0: bool michael@0: CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) michael@0: { michael@0: return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight)); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasPath, mParent) michael@0: michael@0: CanvasPath::CanvasPath(nsISupports* aParent) michael@0: : mParent(aParent) michael@0: { michael@0: SetIsDOMBinding(); michael@0: michael@0: mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); michael@0: } michael@0: michael@0: CanvasPath::CanvasPath(nsISupports* aParent, RefPtr aPathBuilder) michael@0: : mParent(aParent), mPathBuilder(aPathBuilder) michael@0: { michael@0: SetIsDOMBinding(); michael@0: michael@0: if (!mPathBuilder) { michael@0: mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); michael@0: } michael@0: } michael@0: michael@0: JSObject* michael@0: CanvasPath::WrapObject(JSContext* aCx) michael@0: { michael@0: return Path2DBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) michael@0: { michael@0: nsRefPtr path = new CanvasPath(aGlobal.GetAsSupports()); michael@0: return path.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv) michael@0: { michael@0: RefPtr tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero, michael@0: gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()); michael@0: michael@0: nsRefPtr path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); michael@0: return path.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: CanvasPath::Constructor(const GlobalObject& aGlobal, const nsAString& aPathString, ErrorResult& aRv) michael@0: { michael@0: RefPtr tempPath = SVGContentUtils::GetPath(aPathString); michael@0: if (!tempPath) { michael@0: return Constructor(aGlobal, aRv); michael@0: } michael@0: michael@0: nsRefPtr path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); michael@0: return path.forget(); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::ClosePath() michael@0: { michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->Close(); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::MoveTo(double x, double y) michael@0: { michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->MoveTo(Point(ToFloat(x), ToFloat(y))); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::LineTo(double x, double y) michael@0: { michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->LineTo(Point(ToFloat(x), ToFloat(y))); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::QuadraticCurveTo(double cpx, double cpy, double x, double y) michael@0: { michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(cpx), ToFloat(cpy)), michael@0: gfx::Point(ToFloat(x), ToFloat(y))); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::BezierCurveTo(double cp1x, double cp1y, michael@0: double cp2x, double cp2y, michael@0: double x, double y) michael@0: { michael@0: BezierTo(gfx::Point(ToFloat(cp1x), ToFloat(cp1y)), michael@0: gfx::Point(ToFloat(cp2x), ToFloat(cp2y)), michael@0: gfx::Point(ToFloat(x), ToFloat(y))); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::ArcTo(double x1, double y1, double x2, double y2, double radius, michael@0: ErrorResult& error) michael@0: { michael@0: if (radius < 0) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Current point in user space! michael@0: Point p0 = mPathBuilder->CurrentPoint(); michael@0: Point p1(x1, y1); michael@0: Point p2(x2, y2); michael@0: michael@0: // Execute these calculations in double precision to avoid cumulative michael@0: // rounding errors. michael@0: double dir, a2, b2, c2, cosx, sinx, d, anx, any, michael@0: bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; michael@0: bool anticlockwise; michael@0: michael@0: if (p0 == p1 || p1 == p2 || radius == 0) { michael@0: LineTo(p1.x, p1.y); michael@0: return; michael@0: } michael@0: michael@0: // Check for colinearity michael@0: dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); michael@0: if (dir == 0) { michael@0: LineTo(p1.x, p1.y); michael@0: return; michael@0: } michael@0: michael@0: michael@0: // XXX - Math for this code was already available from the non-azure code michael@0: // and would be well tested. Perhaps converting to bezier directly might michael@0: // be more efficient longer run. michael@0: a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); michael@0: b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); michael@0: c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); michael@0: cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); michael@0: michael@0: sinx = sqrt(1 - cosx*cosx); michael@0: d = radius / ((1 - cosx) / sinx); michael@0: michael@0: anx = (x1-p0.x) / sqrt(a2); michael@0: any = (y1-p0.y) / sqrt(a2); michael@0: bnx = (x1-x2) / sqrt(b2); michael@0: bny = (y1-y2) / sqrt(b2); michael@0: x3 = x1 - anx*d; michael@0: y3 = y1 - any*d; michael@0: x4 = x1 - bnx*d; michael@0: y4 = y1 - bny*d; michael@0: anticlockwise = (dir < 0); michael@0: cx = x3 + any*radius*(anticlockwise ? 1 : -1); michael@0: cy = y3 - anx*radius*(anticlockwise ? 1 : -1); michael@0: angle0 = atan2((y3-cy), (x3-cx)); michael@0: angle1 = atan2((y4-cy), (x4-cx)); michael@0: michael@0: michael@0: LineTo(x3, y3); michael@0: michael@0: Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::Rect(double x, double y, double w, double h) michael@0: { michael@0: MoveTo(x, y); michael@0: LineTo(x + w, y); michael@0: LineTo(x + w, y + h); michael@0: LineTo(x, y + h); michael@0: ClosePath(); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::Arc(double x, double y, double radius, michael@0: double startAngle, double endAngle, bool anticlockwise, michael@0: ErrorResult& error) michael@0: { michael@0: if (radius < 0.0) { michael@0: error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: ArcToBezier(this, Point(x, y), Size(radius, radius), startAngle, endAngle, anticlockwise); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::LineTo(const gfx::Point& aPoint) michael@0: { michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->LineTo(aPoint); michael@0: } michael@0: michael@0: void michael@0: CanvasPath::BezierTo(const gfx::Point& aCP1, michael@0: const gfx::Point& aCP2, michael@0: const gfx::Point& aCP3) michael@0: { michael@0: EnsurePathBuilder(); michael@0: michael@0: mPathBuilder->BezierTo(aCP1, aCP2, aCP3); michael@0: } michael@0: michael@0: RefPtr michael@0: CanvasPath::GetPath(const CanvasWindingRule& winding, const mozilla::RefPtr& mTarget) const michael@0: { michael@0: FillRule fillRule = FillRule::FILL_WINDING; michael@0: if (winding == CanvasWindingRule::Evenodd) { michael@0: fillRule = FillRule::FILL_EVEN_ODD; michael@0: } michael@0: michael@0: if (mPath && michael@0: (mPath->GetBackendType() == mTarget->GetType()) && michael@0: (mPath->GetFillRule() == fillRule)) { michael@0: return mPath; michael@0: } michael@0: michael@0: if (!mPath) { michael@0: // if there is no path, there must be a pathbuilder michael@0: MOZ_ASSERT(mPathBuilder); michael@0: mPath = mPathBuilder->Finish(); michael@0: if (!mPath) michael@0: return mPath; michael@0: michael@0: mPathBuilder = nullptr; michael@0: } michael@0: michael@0: // retarget our backend if we're used with a different backend michael@0: if (mPath->GetBackendType() != mTarget->GetType()) { michael@0: RefPtr tmpPathBuilder = mTarget->CreatePathBuilder(fillRule); michael@0: mPath->StreamToSink(tmpPathBuilder); michael@0: mPath = tmpPathBuilder->Finish(); michael@0: } else if (mPath->GetFillRule() != fillRule) { michael@0: RefPtr tmpPathBuilder = mPath->CopyToBuilder(fillRule); michael@0: mPath = tmpPathBuilder->Finish(); michael@0: } michael@0: michael@0: return mPath; michael@0: } michael@0: michael@0: void michael@0: CanvasPath::EnsurePathBuilder() const michael@0: { michael@0: if (mPathBuilder) { michael@0: return; michael@0: } michael@0: michael@0: // if there is not pathbuilder, there must be a path michael@0: MOZ_ASSERT(mPath); michael@0: mPathBuilder = mPath->CopyToBuilder(); michael@0: mPath = nullptr; michael@0: } michael@0: michael@0: } michael@0: }