Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "CanvasRenderingContext2D.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "nsXULElement.h" |
michael@0 | 9 | |
michael@0 | 10 | #include "nsIServiceManager.h" |
michael@0 | 11 | #include "nsMathUtils.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "nsContentUtils.h" |
michael@0 | 14 | |
michael@0 | 15 | #include "nsIDocument.h" |
michael@0 | 16 | #include "mozilla/dom/HTMLCanvasElement.h" |
michael@0 | 17 | #include "nsSVGEffects.h" |
michael@0 | 18 | #include "nsPresContext.h" |
michael@0 | 19 | #include "nsIPresShell.h" |
michael@0 | 20 | |
michael@0 | 21 | #include "nsIInterfaceRequestorUtils.h" |
michael@0 | 22 | #include "nsIFrame.h" |
michael@0 | 23 | #include "nsError.h" |
michael@0 | 24 | |
michael@0 | 25 | #include "nsCSSParser.h" |
michael@0 | 26 | #include "mozilla/css/StyleRule.h" |
michael@0 | 27 | #include "mozilla/css/Declaration.h" |
michael@0 | 28 | #include "mozilla/css/Loader.h" |
michael@0 | 29 | #include "nsComputedDOMStyle.h" |
michael@0 | 30 | #include "nsStyleSet.h" |
michael@0 | 31 | |
michael@0 | 32 | #include "nsPrintfCString.h" |
michael@0 | 33 | |
michael@0 | 34 | #include "nsReadableUtils.h" |
michael@0 | 35 | |
michael@0 | 36 | #include "nsColor.h" |
michael@0 | 37 | #include "nsGfxCIID.h" |
michael@0 | 38 | #include "nsIDocShell.h" |
michael@0 | 39 | #include "nsIDOMWindow.h" |
michael@0 | 40 | #include "nsPIDOMWindow.h" |
michael@0 | 41 | #include "nsDisplayList.h" |
michael@0 | 42 | #include "nsFocusManager.h" |
michael@0 | 43 | |
michael@0 | 44 | #include "nsTArray.h" |
michael@0 | 45 | |
michael@0 | 46 | #include "ImageEncoder.h" |
michael@0 | 47 | |
michael@0 | 48 | #include "gfxContext.h" |
michael@0 | 49 | #include "gfxASurface.h" |
michael@0 | 50 | #include "gfxImageSurface.h" |
michael@0 | 51 | #include "gfxPlatform.h" |
michael@0 | 52 | #include "gfxFont.h" |
michael@0 | 53 | #include "gfxBlur.h" |
michael@0 | 54 | #include "gfxUtils.h" |
michael@0 | 55 | |
michael@0 | 56 | #include "nsFrameManager.h" |
michael@0 | 57 | #include "nsFrameLoader.h" |
michael@0 | 58 | #include "nsBidi.h" |
michael@0 | 59 | #include "nsBidiPresUtils.h" |
michael@0 | 60 | #include "Layers.h" |
michael@0 | 61 | #include "CanvasUtils.h" |
michael@0 | 62 | #include "nsIMemoryReporter.h" |
michael@0 | 63 | #include "nsStyleUtil.h" |
michael@0 | 64 | #include "CanvasImageCache.h" |
michael@0 | 65 | |
michael@0 | 66 | #include <algorithm> |
michael@0 | 67 | |
michael@0 | 68 | #include "jsapi.h" |
michael@0 | 69 | #include "jsfriendapi.h" |
michael@0 | 70 | |
michael@0 | 71 | #include "mozilla/Alignment.h" |
michael@0 | 72 | #include "mozilla/Assertions.h" |
michael@0 | 73 | #include "mozilla/CheckedInt.h" |
michael@0 | 74 | #include "mozilla/dom/ContentParent.h" |
michael@0 | 75 | #include "mozilla/dom/ImageData.h" |
michael@0 | 76 | #include "mozilla/dom/PBrowserParent.h" |
michael@0 | 77 | #include "mozilla/dom/ToJSValue.h" |
michael@0 | 78 | #include "mozilla/dom/TypedArray.h" |
michael@0 | 79 | #include "mozilla/Endian.h" |
michael@0 | 80 | #include "mozilla/gfx/2D.h" |
michael@0 | 81 | #include "mozilla/gfx/PathHelpers.h" |
michael@0 | 82 | #include "mozilla/gfx/DataSurfaceHelpers.h" |
michael@0 | 83 | #include "mozilla/ipc/DocumentRendererParent.h" |
michael@0 | 84 | #include "mozilla/ipc/PDocumentRendererParent.h" |
michael@0 | 85 | #include "mozilla/MathAlgorithms.h" |
michael@0 | 86 | #include "mozilla/Preferences.h" |
michael@0 | 87 | #include "mozilla/Telemetry.h" |
michael@0 | 88 | #include "mozilla/unused.h" |
michael@0 | 89 | #include "nsCCUncollectableMarker.h" |
michael@0 | 90 | #include "nsWrapperCacheInlines.h" |
michael@0 | 91 | #include "mozilla/dom/CanvasRenderingContext2DBinding.h" |
michael@0 | 92 | #include "mozilla/dom/HTMLImageElement.h" |
michael@0 | 93 | #include "mozilla/dom/HTMLVideoElement.h" |
michael@0 | 94 | #include "mozilla/dom/TextMetrics.h" |
michael@0 | 95 | #include "mozilla/dom/UnionTypes.h" |
michael@0 | 96 | #include "nsGlobalWindow.h" |
michael@0 | 97 | #include "GLContext.h" |
michael@0 | 98 | #include "GLContextProvider.h" |
michael@0 | 99 | #include "SVGContentUtils.h" |
michael@0 | 100 | #include "nsIScreenManager.h" |
michael@0 | 101 | |
michael@0 | 102 | #undef free // apparently defined by some windows header, clashing with a free() |
michael@0 | 103 | // method in SkTypes.h |
michael@0 | 104 | #ifdef USE_SKIA |
michael@0 | 105 | #include "SkiaGLGlue.h" |
michael@0 | 106 | #include "SurfaceStream.h" |
michael@0 | 107 | #include "SurfaceTypes.h" |
michael@0 | 108 | #endif |
michael@0 | 109 | |
michael@0 | 110 | using mozilla::gl::GLContext; |
michael@0 | 111 | using mozilla::gl::SkiaGLGlue; |
michael@0 | 112 | using mozilla::gl::GLContextProvider; |
michael@0 | 113 | |
michael@0 | 114 | #ifdef XP_WIN |
michael@0 | 115 | #include "gfxWindowsPlatform.h" |
michael@0 | 116 | #endif |
michael@0 | 117 | |
michael@0 | 118 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 119 | #include "mozilla/layers/ShadowLayers.h" |
michael@0 | 120 | #endif |
michael@0 | 121 | |
michael@0 | 122 | // windows.h (included by chromium code) defines this, in its infinite wisdom |
michael@0 | 123 | #undef DrawText |
michael@0 | 124 | |
michael@0 | 125 | using namespace mozilla; |
michael@0 | 126 | using namespace mozilla::CanvasUtils; |
michael@0 | 127 | using namespace mozilla::css; |
michael@0 | 128 | using namespace mozilla::gfx; |
michael@0 | 129 | using namespace mozilla::ipc; |
michael@0 | 130 | using namespace mozilla::layers; |
michael@0 | 131 | |
michael@0 | 132 | namespace mgfx = mozilla::gfx; |
michael@0 | 133 | |
michael@0 | 134 | namespace mozilla { |
michael@0 | 135 | namespace dom { |
michael@0 | 136 | |
michael@0 | 137 | // Cap sigma to avoid overly large temp surfaces. |
michael@0 | 138 | const Float SIGMA_MAX = 100; |
michael@0 | 139 | |
michael@0 | 140 | /* Memory reporter stuff */ |
michael@0 | 141 | static int64_t gCanvasAzureMemoryUsed = 0; |
michael@0 | 142 | |
michael@0 | 143 | // This is KIND_OTHER because it's not always clear where in memory the pixels |
michael@0 | 144 | // of a canvas are stored. Furthermore, this memory will be tracked by the |
michael@0 | 145 | // underlying surface implementations. See bug 655638 for details. |
michael@0 | 146 | class Canvas2dPixelsReporter MOZ_FINAL : public nsIMemoryReporter |
michael@0 | 147 | { |
michael@0 | 148 | public: |
michael@0 | 149 | NS_DECL_ISUPPORTS |
michael@0 | 150 | |
michael@0 | 151 | NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, |
michael@0 | 152 | nsISupports* aData) |
michael@0 | 153 | { |
michael@0 | 154 | return MOZ_COLLECT_REPORT( |
michael@0 | 155 | "canvas-2d-pixels", KIND_OTHER, UNITS_BYTES, |
michael@0 | 156 | gCanvasAzureMemoryUsed, |
michael@0 | 157 | "Memory used by 2D canvases. Each canvas requires " |
michael@0 | 158 | "(width * height * 4) bytes."); |
michael@0 | 159 | } |
michael@0 | 160 | }; |
michael@0 | 161 | |
michael@0 | 162 | NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter) |
michael@0 | 163 | |
michael@0 | 164 | class CanvasRadialGradient : public CanvasGradient |
michael@0 | 165 | { |
michael@0 | 166 | public: |
michael@0 | 167 | CanvasRadialGradient(CanvasRenderingContext2D* aContext, |
michael@0 | 168 | mozilla::css::Loader *aLoader, |
michael@0 | 169 | const Point &aBeginOrigin, Float aBeginRadius, |
michael@0 | 170 | const Point &aEndOrigin, Float aEndRadius) |
michael@0 | 171 | : CanvasGradient(aContext, aLoader, Type::RADIAL) |
michael@0 | 172 | , mCenter1(aBeginOrigin) |
michael@0 | 173 | , mCenter2(aEndOrigin) |
michael@0 | 174 | , mRadius1(aBeginRadius) |
michael@0 | 175 | , mRadius2(aEndRadius) |
michael@0 | 176 | { |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | Point mCenter1; |
michael@0 | 180 | Point mCenter2; |
michael@0 | 181 | Float mRadius1; |
michael@0 | 182 | Float mRadius2; |
michael@0 | 183 | }; |
michael@0 | 184 | |
michael@0 | 185 | class CanvasLinearGradient : public CanvasGradient |
michael@0 | 186 | { |
michael@0 | 187 | public: |
michael@0 | 188 | CanvasLinearGradient(CanvasRenderingContext2D* aContext, |
michael@0 | 189 | mozilla::css::Loader *aLoader, |
michael@0 | 190 | const Point &aBegin, const Point &aEnd) |
michael@0 | 191 | : CanvasGradient(aContext, aLoader, Type::LINEAR) |
michael@0 | 192 | , mBegin(aBegin) |
michael@0 | 193 | , mEnd(aEnd) |
michael@0 | 194 | { |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | protected: |
michael@0 | 198 | friend class CanvasGeneralPattern; |
michael@0 | 199 | |
michael@0 | 200 | // Beginning of linear gradient. |
michael@0 | 201 | Point mBegin; |
michael@0 | 202 | // End of linear gradient. |
michael@0 | 203 | Point mEnd; |
michael@0 | 204 | }; |
michael@0 | 205 | |
michael@0 | 206 | // This class is named 'GeneralCanvasPattern' instead of just |
michael@0 | 207 | // 'GeneralPattern' to keep Windows PGO builds from confusing the |
michael@0 | 208 | // GeneralPattern class in gfxContext.cpp with this one. |
michael@0 | 209 | |
michael@0 | 210 | class CanvasGeneralPattern |
michael@0 | 211 | { |
michael@0 | 212 | public: |
michael@0 | 213 | typedef CanvasRenderingContext2D::Style Style; |
michael@0 | 214 | typedef CanvasRenderingContext2D::ContextState ContextState; |
michael@0 | 215 | |
michael@0 | 216 | CanvasGeneralPattern() : mPattern(nullptr) {} |
michael@0 | 217 | ~CanvasGeneralPattern() |
michael@0 | 218 | { |
michael@0 | 219 | if (mPattern) { |
michael@0 | 220 | mPattern->~Pattern(); |
michael@0 | 221 | } |
michael@0 | 222 | } |
michael@0 | 223 | |
michael@0 | 224 | Pattern& ForStyle(CanvasRenderingContext2D *aCtx, |
michael@0 | 225 | Style aStyle, |
michael@0 | 226 | DrawTarget *aRT) |
michael@0 | 227 | { |
michael@0 | 228 | // This should only be called once or the mPattern destructor will |
michael@0 | 229 | // not be executed. |
michael@0 | 230 | NS_ASSERTION(!mPattern, "ForStyle() should only be called once on CanvasGeneralPattern!"); |
michael@0 | 231 | |
michael@0 | 232 | const ContextState &state = aCtx->CurrentState(); |
michael@0 | 233 | |
michael@0 | 234 | if (state.StyleIsColor(aStyle)) { |
michael@0 | 235 | mPattern = new (mColorPattern.addr()) ColorPattern(Color::FromABGR(state.colorStyles[aStyle])); |
michael@0 | 236 | } else if (state.gradientStyles[aStyle] && |
michael@0 | 237 | state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) { |
michael@0 | 238 | CanvasLinearGradient *gradient = |
michael@0 | 239 | static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get()); |
michael@0 | 240 | |
michael@0 | 241 | mPattern = new (mLinearGradientPattern.addr()) |
michael@0 | 242 | LinearGradientPattern(gradient->mBegin, gradient->mEnd, |
michael@0 | 243 | gradient->GetGradientStopsForTarget(aRT)); |
michael@0 | 244 | } else if (state.gradientStyles[aStyle] && |
michael@0 | 245 | state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) { |
michael@0 | 246 | CanvasRadialGradient *gradient = |
michael@0 | 247 | static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get()); |
michael@0 | 248 | |
michael@0 | 249 | mPattern = new (mRadialGradientPattern.addr()) |
michael@0 | 250 | RadialGradientPattern(gradient->mCenter1, gradient->mCenter2, gradient->mRadius1, |
michael@0 | 251 | gradient->mRadius2, gradient->GetGradientStopsForTarget(aRT)); |
michael@0 | 252 | } else if (state.patternStyles[aStyle]) { |
michael@0 | 253 | if (aCtx->mCanvasElement) { |
michael@0 | 254 | CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement, |
michael@0 | 255 | state.patternStyles[aStyle]->mPrincipal, |
michael@0 | 256 | state.patternStyles[aStyle]->mForceWriteOnly, |
michael@0 | 257 | state.patternStyles[aStyle]->mCORSUsed); |
michael@0 | 258 | } |
michael@0 | 259 | |
michael@0 | 260 | ExtendMode mode; |
michael@0 | 261 | if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) { |
michael@0 | 262 | mode = ExtendMode::CLAMP; |
michael@0 | 263 | } else { |
michael@0 | 264 | mode = ExtendMode::REPEAT; |
michael@0 | 265 | } |
michael@0 | 266 | mPattern = new (mSurfacePattern.addr()) |
michael@0 | 267 | SurfacePattern(state.patternStyles[aStyle]->mSurface, mode); |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | return *mPattern; |
michael@0 | 271 | } |
michael@0 | 272 | |
michael@0 | 273 | union { |
michael@0 | 274 | AlignedStorage2<ColorPattern> mColorPattern; |
michael@0 | 275 | AlignedStorage2<LinearGradientPattern> mLinearGradientPattern; |
michael@0 | 276 | AlignedStorage2<RadialGradientPattern> mRadialGradientPattern; |
michael@0 | 277 | AlignedStorage2<SurfacePattern> mSurfacePattern; |
michael@0 | 278 | }; |
michael@0 | 279 | Pattern *mPattern; |
michael@0 | 280 | }; |
michael@0 | 281 | |
michael@0 | 282 | /* This is an RAII based class that can be used as a drawtarget for |
michael@0 | 283 | * operations that need a shadow drawn. It will automatically provide a |
michael@0 | 284 | * temporary target when needed, and if so blend it back with a shadow. |
michael@0 | 285 | * |
michael@0 | 286 | * aBounds specifies the bounds of the drawing operation that will be |
michael@0 | 287 | * drawn to the target, it is given in device space! This function will |
michael@0 | 288 | * change aBounds to incorporate shadow bounds. If this is nullptr the drawing |
michael@0 | 289 | * operation will be assumed to cover an infinite rect. |
michael@0 | 290 | */ |
michael@0 | 291 | class AdjustedTarget |
michael@0 | 292 | { |
michael@0 | 293 | public: |
michael@0 | 294 | typedef CanvasRenderingContext2D::ContextState ContextState; |
michael@0 | 295 | |
michael@0 | 296 | AdjustedTarget(CanvasRenderingContext2D *ctx, |
michael@0 | 297 | mgfx::Rect *aBounds = nullptr) |
michael@0 | 298 | : mCtx(nullptr) |
michael@0 | 299 | { |
michael@0 | 300 | if (!ctx->NeedToDrawShadow()) { |
michael@0 | 301 | mTarget = ctx->mTarget; |
michael@0 | 302 | return; |
michael@0 | 303 | } |
michael@0 | 304 | mCtx = ctx; |
michael@0 | 305 | |
michael@0 | 306 | const ContextState &state = mCtx->CurrentState(); |
michael@0 | 307 | |
michael@0 | 308 | mSigma = state.shadowBlur / 2.0f; |
michael@0 | 309 | |
michael@0 | 310 | if (mSigma > SIGMA_MAX) { |
michael@0 | 311 | mSigma = SIGMA_MAX; |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | Matrix transform = mCtx->mTarget->GetTransform(); |
michael@0 | 315 | |
michael@0 | 316 | mTempRect = mgfx::Rect(0, 0, ctx->mWidth, ctx->mHeight); |
michael@0 | 317 | |
michael@0 | 318 | static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5; |
michael@0 | 319 | int32_t blurRadius = (int32_t) floor(mSigma * GAUSSIAN_SCALE_FACTOR + 0.5); |
michael@0 | 320 | |
michael@0 | 321 | // We need to enlarge and possibly offset our temporary surface |
michael@0 | 322 | // so that things outside of the canvas may cast shadows. |
michael@0 | 323 | mTempRect.Inflate(Margin(blurRadius + std::max<Float>(state.shadowOffset.y, 0), |
michael@0 | 324 | blurRadius + std::max<Float>(-state.shadowOffset.x, 0), |
michael@0 | 325 | blurRadius + std::max<Float>(-state.shadowOffset.y, 0), |
michael@0 | 326 | blurRadius + std::max<Float>(state.shadowOffset.x, 0))); |
michael@0 | 327 | |
michael@0 | 328 | if (aBounds) { |
michael@0 | 329 | // We actually include the bounds of the shadow blur, this makes it |
michael@0 | 330 | // easier to execute the actual blur on hardware, and shouldn't affect |
michael@0 | 331 | // the amount of pixels that need to be touched. |
michael@0 | 332 | aBounds->Inflate(Margin(blurRadius, blurRadius, |
michael@0 | 333 | blurRadius, blurRadius)); |
michael@0 | 334 | mTempRect = mTempRect.Intersect(*aBounds); |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | mTempRect.ScaleRoundOut(1.0f); |
michael@0 | 338 | |
michael@0 | 339 | transform._31 -= mTempRect.x; |
michael@0 | 340 | transform._32 -= mTempRect.y; |
michael@0 | 341 | |
michael@0 | 342 | mTarget = |
michael@0 | 343 | mCtx->mTarget->CreateShadowDrawTarget(IntSize(int32_t(mTempRect.width), int32_t(mTempRect.height)), |
michael@0 | 344 | SurfaceFormat::B8G8R8A8, mSigma); |
michael@0 | 345 | |
michael@0 | 346 | if (!mTarget) { |
michael@0 | 347 | // XXX - Deal with the situation where our temp size is too big to |
michael@0 | 348 | // fit in a texture. |
michael@0 | 349 | mTarget = ctx->mTarget; |
michael@0 | 350 | mCtx = nullptr; |
michael@0 | 351 | } else { |
michael@0 | 352 | mTarget->SetTransform(transform); |
michael@0 | 353 | } |
michael@0 | 354 | } |
michael@0 | 355 | |
michael@0 | 356 | ~AdjustedTarget() |
michael@0 | 357 | { |
michael@0 | 358 | if (!mCtx) { |
michael@0 | 359 | return; |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); |
michael@0 | 363 | |
michael@0 | 364 | mCtx->mTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), |
michael@0 | 365 | Color::FromABGR(mCtx->CurrentState().shadowColor), |
michael@0 | 366 | mCtx->CurrentState().shadowOffset, mSigma, |
michael@0 | 367 | mCtx->CurrentState().op); |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | operator DrawTarget*() |
michael@0 | 371 | { |
michael@0 | 372 | return mTarget; |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | DrawTarget* operator->() |
michael@0 | 376 | { |
michael@0 | 377 | return mTarget; |
michael@0 | 378 | } |
michael@0 | 379 | |
michael@0 | 380 | private: |
michael@0 | 381 | RefPtr<DrawTarget> mTarget; |
michael@0 | 382 | CanvasRenderingContext2D *mCtx; |
michael@0 | 383 | Float mSigma; |
michael@0 | 384 | mgfx::Rect mTempRect; |
michael@0 | 385 | }; |
michael@0 | 386 | |
michael@0 | 387 | void |
michael@0 | 388 | CanvasGradient::AddColorStop(float offset, const nsAString& colorstr, ErrorResult& rv) |
michael@0 | 389 | { |
michael@0 | 390 | if (offset < 0.0 || offset > 1.0) { |
michael@0 | 391 | rv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 392 | return; |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | nsCSSValue value; |
michael@0 | 396 | nsCSSParser parser; |
michael@0 | 397 | if (!parser.ParseColorString(colorstr, nullptr, 0, value)) { |
michael@0 | 398 | rv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
michael@0 | 399 | return; |
michael@0 | 400 | } |
michael@0 | 401 | |
michael@0 | 402 | nsIPresShell* presShell = nullptr; |
michael@0 | 403 | if (mCSSLoader) { |
michael@0 | 404 | nsIDocument *doc = mCSSLoader->GetDocument(); |
michael@0 | 405 | if (doc) |
michael@0 | 406 | presShell = doc->GetShell(); |
michael@0 | 407 | } |
michael@0 | 408 | |
michael@0 | 409 | nscolor color; |
michael@0 | 410 | if (!nsRuleNode::ComputeColor(value, presShell ? presShell->GetPresContext() : nullptr, |
michael@0 | 411 | nullptr, color)) { |
michael@0 | 412 | rv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
michael@0 | 413 | return; |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | mStops = nullptr; |
michael@0 | 417 | |
michael@0 | 418 | GradientStop newStop; |
michael@0 | 419 | |
michael@0 | 420 | newStop.offset = offset; |
michael@0 | 421 | newStop.color = Color::FromABGR(color); |
michael@0 | 422 | |
michael@0 | 423 | mRawStops.AppendElement(newStop); |
michael@0 | 424 | } |
michael@0 | 425 | |
michael@0 | 426 | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef) |
michael@0 | 427 | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release) |
michael@0 | 428 | |
michael@0 | 429 | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasGradient, mContext) |
michael@0 | 430 | |
michael@0 | 431 | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef) |
michael@0 | 432 | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release) |
michael@0 | 433 | |
michael@0 | 434 | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasPattern, mContext) |
michael@0 | 435 | |
michael@0 | 436 | class CanvasRenderingContext2DUserData : public LayerUserData { |
michael@0 | 437 | public: |
michael@0 | 438 | CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext) |
michael@0 | 439 | : mContext(aContext) |
michael@0 | 440 | { |
michael@0 | 441 | aContext->mUserDatas.AppendElement(this); |
michael@0 | 442 | } |
michael@0 | 443 | ~CanvasRenderingContext2DUserData() |
michael@0 | 444 | { |
michael@0 | 445 | if (mContext) { |
michael@0 | 446 | mContext->mUserDatas.RemoveElement(this); |
michael@0 | 447 | } |
michael@0 | 448 | } |
michael@0 | 449 | |
michael@0 | 450 | static void PreTransactionCallback(void* aData) |
michael@0 | 451 | { |
michael@0 | 452 | CanvasRenderingContext2DUserData* self = |
michael@0 | 453 | static_cast<CanvasRenderingContext2DUserData*>(aData); |
michael@0 | 454 | CanvasRenderingContext2D* context = self->mContext; |
michael@0 | 455 | if (!context || !context->mStream || !context->mTarget) |
michael@0 | 456 | return; |
michael@0 | 457 | |
michael@0 | 458 | // Since SkiaGL default to store drawing command until flush |
michael@0 | 459 | // We will have to flush it before present. |
michael@0 | 460 | context->mTarget->Flush(); |
michael@0 | 461 | } |
michael@0 | 462 | |
michael@0 | 463 | static void DidTransactionCallback(void* aData) |
michael@0 | 464 | { |
michael@0 | 465 | CanvasRenderingContext2DUserData* self = |
michael@0 | 466 | static_cast<CanvasRenderingContext2DUserData*>(aData); |
michael@0 | 467 | if (self->mContext) { |
michael@0 | 468 | self->mContext->MarkContextClean(); |
michael@0 | 469 | } |
michael@0 | 470 | } |
michael@0 | 471 | bool IsForContext(CanvasRenderingContext2D *aContext) |
michael@0 | 472 | { |
michael@0 | 473 | return mContext == aContext; |
michael@0 | 474 | } |
michael@0 | 475 | void Forget() |
michael@0 | 476 | { |
michael@0 | 477 | mContext = nullptr; |
michael@0 | 478 | } |
michael@0 | 479 | |
michael@0 | 480 | private: |
michael@0 | 481 | CanvasRenderingContext2D *mContext; |
michael@0 | 482 | }; |
michael@0 | 483 | |
michael@0 | 484 | NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) |
michael@0 | 485 | NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) |
michael@0 | 486 | |
michael@0 | 487 | NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D) |
michael@0 | 488 | |
michael@0 | 489 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) |
michael@0 | 490 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement) |
michael@0 | 491 | for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { |
michael@0 | 492 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]); |
michael@0 | 493 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]); |
michael@0 | 494 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]); |
michael@0 | 495 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]); |
michael@0 | 496 | } |
michael@0 | 497 | NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
michael@0 | 498 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
michael@0 | 499 | |
michael@0 | 500 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) |
michael@0 | 501 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement) |
michael@0 | 502 | for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { |
michael@0 | 503 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern"); |
michael@0 | 504 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern"); |
michael@0 | 505 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient"); |
michael@0 | 506 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient"); |
michael@0 | 507 | } |
michael@0 | 508 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
michael@0 | 509 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
michael@0 | 510 | |
michael@0 | 511 | NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D) |
michael@0 | 512 | |
michael@0 | 513 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) |
michael@0 | 514 | if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) { |
michael@0 | 515 | dom::Element* canvasElement = tmp->mCanvasElement; |
michael@0 | 516 | if (canvasElement) { |
michael@0 | 517 | if (canvasElement->IsPurple()) { |
michael@0 | 518 | canvasElement->RemovePurple(); |
michael@0 | 519 | } |
michael@0 | 520 | dom::Element::MarkNodeChildren(canvasElement); |
michael@0 | 521 | } |
michael@0 | 522 | return true; |
michael@0 | 523 | } |
michael@0 | 524 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END |
michael@0 | 525 | |
michael@0 | 526 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D) |
michael@0 | 527 | return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); |
michael@0 | 528 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END |
michael@0 | 529 | |
michael@0 | 530 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) |
michael@0 | 531 | return nsCCUncollectableMarker::sGeneration && tmp->IsBlack(); |
michael@0 | 532 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END |
michael@0 | 533 | |
michael@0 | 534 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D) |
michael@0 | 535 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
michael@0 | 536 | NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) |
michael@0 | 537 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
michael@0 | 538 | NS_INTERFACE_MAP_END |
michael@0 | 539 | |
michael@0 | 540 | /** |
michael@0 | 541 | ** CanvasRenderingContext2D impl |
michael@0 | 542 | **/ |
michael@0 | 543 | |
michael@0 | 544 | |
michael@0 | 545 | // Initialize our static variables. |
michael@0 | 546 | uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0; |
michael@0 | 547 | DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; |
michael@0 | 548 | |
michael@0 | 549 | |
michael@0 | 550 | |
michael@0 | 551 | CanvasRenderingContext2D::CanvasRenderingContext2D() |
michael@0 | 552 | : mForceSoftware(false), mZero(false), mOpaque(false), mResetLayer(true) |
michael@0 | 553 | , mIPC(false) |
michael@0 | 554 | , mStream(nullptr) |
michael@0 | 555 | , mIsEntireFrameInvalid(false) |
michael@0 | 556 | , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false) |
michael@0 | 557 | , mInvalidateCount(0) |
michael@0 | 558 | { |
michael@0 | 559 | sNumLivingContexts++; |
michael@0 | 560 | SetIsDOMBinding(); |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | CanvasRenderingContext2D::~CanvasRenderingContext2D() |
michael@0 | 564 | { |
michael@0 | 565 | Reset(); |
michael@0 | 566 | // Drop references from all CanvasRenderingContext2DUserData to this context |
michael@0 | 567 | for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { |
michael@0 | 568 | mUserDatas[i]->Forget(); |
michael@0 | 569 | } |
michael@0 | 570 | sNumLivingContexts--; |
michael@0 | 571 | if (!sNumLivingContexts) { |
michael@0 | 572 | NS_IF_RELEASE(sErrorTarget); |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | RemoveDemotableContext(this); |
michael@0 | 576 | } |
michael@0 | 577 | |
michael@0 | 578 | JSObject* |
michael@0 | 579 | CanvasRenderingContext2D::WrapObject(JSContext *cx) |
michael@0 | 580 | { |
michael@0 | 581 | return CanvasRenderingContext2DBinding::Wrap(cx, this); |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | bool |
michael@0 | 585 | CanvasRenderingContext2D::ParseColor(const nsAString& aString, |
michael@0 | 586 | nscolor* aColor) |
michael@0 | 587 | { |
michael@0 | 588 | nsIDocument* document = mCanvasElement |
michael@0 | 589 | ? mCanvasElement->OwnerDoc() |
michael@0 | 590 | : nullptr; |
michael@0 | 591 | |
michael@0 | 592 | // Pass the CSS Loader object to the parser, to allow parser error |
michael@0 | 593 | // reports to include the outer window ID. |
michael@0 | 594 | nsCSSParser parser(document ? document->CSSLoader() : nullptr); |
michael@0 | 595 | nsCSSValue value; |
michael@0 | 596 | if (!parser.ParseColorString(aString, nullptr, 0, value)) { |
michael@0 | 597 | return false; |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | if (value.IsNumericColorUnit()) { |
michael@0 | 601 | // if we already have a color we can just use it directly |
michael@0 | 602 | *aColor = value.GetColorValue(); |
michael@0 | 603 | } else { |
michael@0 | 604 | // otherwise resolve it |
michael@0 | 605 | nsIPresShell* presShell = GetPresShell(); |
michael@0 | 606 | nsRefPtr<nsStyleContext> parentContext; |
michael@0 | 607 | if (mCanvasElement && mCanvasElement->IsInDoc()) { |
michael@0 | 608 | // Inherit from the canvas element. |
michael@0 | 609 | parentContext = nsComputedDOMStyle::GetStyleContextForElement( |
michael@0 | 610 | mCanvasElement, nullptr, presShell); |
michael@0 | 611 | } |
michael@0 | 612 | |
michael@0 | 613 | unused << nsRuleNode::ComputeColor( |
michael@0 | 614 | value, presShell ? presShell->GetPresContext() : nullptr, parentContext, |
michael@0 | 615 | *aColor); |
michael@0 | 616 | } |
michael@0 | 617 | return true; |
michael@0 | 618 | } |
michael@0 | 619 | |
michael@0 | 620 | #ifdef ACCESSIBILITY |
michael@0 | 621 | PLDHashOperator |
michael@0 | 622 | CanvasRenderingContext2D::RemoveHitRegionProperty(RegionInfo* aEntry, void*) |
michael@0 | 623 | { |
michael@0 | 624 | aEntry->mElement->DeleteProperty(nsGkAtoms::hitregion); |
michael@0 | 625 | return PL_DHASH_NEXT; |
michael@0 | 626 | } |
michael@0 | 627 | #endif |
michael@0 | 628 | |
michael@0 | 629 | nsresult |
michael@0 | 630 | CanvasRenderingContext2D::Reset() |
michael@0 | 631 | { |
michael@0 | 632 | if (mCanvasElement) { |
michael@0 | 633 | mCanvasElement->InvalidateCanvas(); |
michael@0 | 634 | } |
michael@0 | 635 | |
michael@0 | 636 | // only do this for non-docshell created contexts, |
michael@0 | 637 | // since those are the ones that we created a surface for |
michael@0 | 638 | if (mTarget && IsTargetValid() && !mDocShell) { |
michael@0 | 639 | gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; |
michael@0 | 640 | } |
michael@0 | 641 | |
michael@0 | 642 | mTarget = nullptr; |
michael@0 | 643 | mStream = nullptr; |
michael@0 | 644 | |
michael@0 | 645 | // reset hit regions |
michael@0 | 646 | #ifdef ACCESSIBILITY |
michael@0 | 647 | mHitRegionsOptions.EnumerateEntries(RemoveHitRegionProperty, nullptr); |
michael@0 | 648 | #endif |
michael@0 | 649 | mHitRegionsOptions.Clear(); |
michael@0 | 650 | |
michael@0 | 651 | // Since the target changes the backing texture will change, and this will |
michael@0 | 652 | // no longer be valid. |
michael@0 | 653 | mIsEntireFrameInvalid = false; |
michael@0 | 654 | mPredictManyRedrawCalls = false; |
michael@0 | 655 | |
michael@0 | 656 | return NS_OK; |
michael@0 | 657 | } |
michael@0 | 658 | |
michael@0 | 659 | void |
michael@0 | 660 | CanvasRenderingContext2D::SetStyleFromString(const nsAString& str, |
michael@0 | 661 | Style whichStyle) |
michael@0 | 662 | { |
michael@0 | 663 | MOZ_ASSERT(!str.IsVoid()); |
michael@0 | 664 | |
michael@0 | 665 | nscolor color; |
michael@0 | 666 | if (!ParseColor(str, &color)) { |
michael@0 | 667 | return; |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | CurrentState().SetColorStyle(whichStyle, color); |
michael@0 | 671 | } |
michael@0 | 672 | |
michael@0 | 673 | void |
michael@0 | 674 | CanvasRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue, |
michael@0 | 675 | Style aWhichStyle) |
michael@0 | 676 | { |
michael@0 | 677 | const ContextState &state = CurrentState(); |
michael@0 | 678 | if (state.patternStyles[aWhichStyle]) { |
michael@0 | 679 | aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle]; |
michael@0 | 680 | } else if (state.gradientStyles[aWhichStyle]) { |
michael@0 | 681 | aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle]; |
michael@0 | 682 | } else { |
michael@0 | 683 | StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString()); |
michael@0 | 684 | } |
michael@0 | 685 | } |
michael@0 | 686 | |
michael@0 | 687 | // static |
michael@0 | 688 | void |
michael@0 | 689 | CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) |
michael@0 | 690 | { |
michael@0 | 691 | // We can't reuse the normal CSS color stringification code, |
michael@0 | 692 | // because the spec calls for a different algorithm for canvas. |
michael@0 | 693 | if (NS_GET_A(aColor) == 255) { |
michael@0 | 694 | CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", |
michael@0 | 695 | NS_GET_R(aColor), |
michael@0 | 696 | NS_GET_G(aColor), |
michael@0 | 697 | NS_GET_B(aColor)), |
michael@0 | 698 | aStr); |
michael@0 | 699 | } else { |
michael@0 | 700 | CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", |
michael@0 | 701 | NS_GET_R(aColor), |
michael@0 | 702 | NS_GET_G(aColor), |
michael@0 | 703 | NS_GET_B(aColor)), |
michael@0 | 704 | aStr); |
michael@0 | 705 | aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); |
michael@0 | 706 | aStr.Append(')'); |
michael@0 | 707 | } |
michael@0 | 708 | } |
michael@0 | 709 | |
michael@0 | 710 | nsresult |
michael@0 | 711 | CanvasRenderingContext2D::Redraw() |
michael@0 | 712 | { |
michael@0 | 713 | if (mIsEntireFrameInvalid) { |
michael@0 | 714 | return NS_OK; |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | mIsEntireFrameInvalid = true; |
michael@0 | 718 | |
michael@0 | 719 | if (!mCanvasElement) { |
michael@0 | 720 | NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); |
michael@0 | 721 | return NS_OK; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); |
michael@0 | 725 | |
michael@0 | 726 | mCanvasElement->InvalidateCanvasContent(nullptr); |
michael@0 | 727 | |
michael@0 | 728 | return NS_OK; |
michael@0 | 729 | } |
michael@0 | 730 | |
michael@0 | 731 | void |
michael@0 | 732 | CanvasRenderingContext2D::Redraw(const mgfx::Rect &r) |
michael@0 | 733 | { |
michael@0 | 734 | ++mInvalidateCount; |
michael@0 | 735 | |
michael@0 | 736 | if (mIsEntireFrameInvalid) { |
michael@0 | 737 | return; |
michael@0 | 738 | } |
michael@0 | 739 | |
michael@0 | 740 | if (mPredictManyRedrawCalls || |
michael@0 | 741 | mInvalidateCount > kCanvasMaxInvalidateCount) { |
michael@0 | 742 | Redraw(); |
michael@0 | 743 | return; |
michael@0 | 744 | } |
michael@0 | 745 | |
michael@0 | 746 | if (!mCanvasElement) { |
michael@0 | 747 | NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); |
michael@0 | 748 | return; |
michael@0 | 749 | } |
michael@0 | 750 | |
michael@0 | 751 | nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement); |
michael@0 | 752 | |
michael@0 | 753 | mCanvasElement->InvalidateCanvasContent(&r); |
michael@0 | 754 | } |
michael@0 | 755 | |
michael@0 | 756 | void |
michael@0 | 757 | CanvasRenderingContext2D::RedrawUser(const gfxRect& r) |
michael@0 | 758 | { |
michael@0 | 759 | if (mIsEntireFrameInvalid) { |
michael@0 | 760 | ++mInvalidateCount; |
michael@0 | 761 | return; |
michael@0 | 762 | } |
michael@0 | 763 | |
michael@0 | 764 | mgfx::Rect newr = |
michael@0 | 765 | mTarget->GetTransform().TransformBounds(ToRect(r)); |
michael@0 | 766 | Redraw(newr); |
michael@0 | 767 | } |
michael@0 | 768 | |
michael@0 | 769 | void CanvasRenderingContext2D::Demote() |
michael@0 | 770 | { |
michael@0 | 771 | if (!IsTargetValid() || mForceSoftware || !mStream) |
michael@0 | 772 | return; |
michael@0 | 773 | |
michael@0 | 774 | RemoveDemotableContext(this); |
michael@0 | 775 | |
michael@0 | 776 | RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); |
michael@0 | 777 | RefPtr<DrawTarget> oldTarget = mTarget; |
michael@0 | 778 | mTarget = nullptr; |
michael@0 | 779 | mStream = nullptr; |
michael@0 | 780 | mResetLayer = true; |
michael@0 | 781 | mForceSoftware = true; |
michael@0 | 782 | |
michael@0 | 783 | // Recreate target, now demoted to software only |
michael@0 | 784 | EnsureTarget(); |
michael@0 | 785 | if (!IsTargetValid()) |
michael@0 | 786 | return; |
michael@0 | 787 | |
michael@0 | 788 | // Restore the content from the old DrawTarget |
michael@0 | 789 | mgfx::Rect r(0, 0, mWidth, mHeight); |
michael@0 | 790 | mTarget->DrawSurface(snapshot, r, r); |
michael@0 | 791 | |
michael@0 | 792 | // Restore the clips and transform |
michael@0 | 793 | for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { |
michael@0 | 794 | mTarget->PushClip(CurrentState().clipsPushed[i]); |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | mTarget->SetTransform(oldTarget->GetTransform()); |
michael@0 | 798 | } |
michael@0 | 799 | |
michael@0 | 800 | std::vector<CanvasRenderingContext2D*>& |
michael@0 | 801 | CanvasRenderingContext2D::DemotableContexts() |
michael@0 | 802 | { |
michael@0 | 803 | static std::vector<CanvasRenderingContext2D*> contexts; |
michael@0 | 804 | return contexts; |
michael@0 | 805 | } |
michael@0 | 806 | |
michael@0 | 807 | void |
michael@0 | 808 | CanvasRenderingContext2D::DemoteOldestContextIfNecessary() |
michael@0 | 809 | { |
michael@0 | 810 | const size_t kMaxContexts = 64; |
michael@0 | 811 | |
michael@0 | 812 | std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts(); |
michael@0 | 813 | if (contexts.size() < kMaxContexts) |
michael@0 | 814 | return; |
michael@0 | 815 | |
michael@0 | 816 | CanvasRenderingContext2D* oldest = contexts.front(); |
michael@0 | 817 | oldest->Demote(); |
michael@0 | 818 | } |
michael@0 | 819 | |
michael@0 | 820 | void |
michael@0 | 821 | CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* context) |
michael@0 | 822 | { |
michael@0 | 823 | std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context); |
michael@0 | 824 | if (iter != DemotableContexts().end()) |
michael@0 | 825 | return; |
michael@0 | 826 | |
michael@0 | 827 | DemotableContexts().push_back(context); |
michael@0 | 828 | } |
michael@0 | 829 | |
michael@0 | 830 | void |
michael@0 | 831 | CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* context) |
michael@0 | 832 | { |
michael@0 | 833 | std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context); |
michael@0 | 834 | if (iter != DemotableContexts().end()) |
michael@0 | 835 | DemotableContexts().erase(iter); |
michael@0 | 836 | } |
michael@0 | 837 | |
michael@0 | 838 | bool |
michael@0 | 839 | CheckSizeForSkiaGL(IntSize size) { |
michael@0 | 840 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 841 | |
michael@0 | 842 | int minsize = Preferences::GetInt("gfx.canvas.min-size-for-skia-gl", 128); |
michael@0 | 843 | if (size.width < minsize || size.height < minsize) { |
michael@0 | 844 | return false; |
michael@0 | 845 | } |
michael@0 | 846 | |
michael@0 | 847 | // Maximum pref allows 3 different options: |
michael@0 | 848 | // 0 means unlimited size |
michael@0 | 849 | // > 0 means use value as an absolute threshold |
michael@0 | 850 | // < 0 means use the number of screen pixels as a threshold |
michael@0 | 851 | int maxsize = Preferences::GetInt("gfx.canvas.max-size-for-skia-gl", 0); |
michael@0 | 852 | |
michael@0 | 853 | // unlimited max size |
michael@0 | 854 | if (!maxsize) { |
michael@0 | 855 | return true; |
michael@0 | 856 | } |
michael@0 | 857 | |
michael@0 | 858 | // absolute max size threshold |
michael@0 | 859 | if (maxsize > 0) { |
michael@0 | 860 | return size.width <= maxsize && size.height <= maxsize; |
michael@0 | 861 | } |
michael@0 | 862 | |
michael@0 | 863 | // Cache the number of pixels on the primary screen |
michael@0 | 864 | static int32_t gScreenPixels = -1; |
michael@0 | 865 | if (gScreenPixels < 0) { |
michael@0 | 866 | nsCOMPtr<nsIScreenManager> screenManager = |
michael@0 | 867 | do_GetService("@mozilla.org/gfx/screenmanager;1"); |
michael@0 | 868 | if (screenManager) { |
michael@0 | 869 | nsCOMPtr<nsIScreen> primaryScreen; |
michael@0 | 870 | screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen)); |
michael@0 | 871 | if (primaryScreen) { |
michael@0 | 872 | int32_t x, y, width, height; |
michael@0 | 873 | primaryScreen->GetRect(&x, &y, &width, &height); |
michael@0 | 874 | |
michael@0 | 875 | gScreenPixels = width * height; |
michael@0 | 876 | } |
michael@0 | 877 | } |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | // screen size acts as max threshold |
michael@0 | 881 | return gScreenPixels < 0 || (size.width * size.height) <= gScreenPixels; |
michael@0 | 882 | } |
michael@0 | 883 | |
michael@0 | 884 | void |
michael@0 | 885 | CanvasRenderingContext2D::EnsureTarget() |
michael@0 | 886 | { |
michael@0 | 887 | if (mTarget) { |
michael@0 | 888 | return; |
michael@0 | 889 | } |
michael@0 | 890 | |
michael@0 | 891 | // Check that the dimensions are sane |
michael@0 | 892 | IntSize size(mWidth, mHeight); |
michael@0 | 893 | if (size.width <= 0xFFFF && size.height <= 0xFFFF && |
michael@0 | 894 | size.width >= 0 && size.height >= 0) { |
michael@0 | 895 | SurfaceFormat format = GetSurfaceFormat(); |
michael@0 | 896 | nsIDocument* ownerDoc = nullptr; |
michael@0 | 897 | if (mCanvasElement) { |
michael@0 | 898 | ownerDoc = mCanvasElement->OwnerDoc(); |
michael@0 | 899 | } |
michael@0 | 900 | |
michael@0 | 901 | nsRefPtr<LayerManager> layerManager = nullptr; |
michael@0 | 902 | |
michael@0 | 903 | if (ownerDoc) { |
michael@0 | 904 | layerManager = |
michael@0 | 905 | nsContentUtils::PersistentLayerManagerForDocument(ownerDoc); |
michael@0 | 906 | } |
michael@0 | 907 | |
michael@0 | 908 | if (layerManager) { |
michael@0 | 909 | if (gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas() && |
michael@0 | 910 | !mForceSoftware && |
michael@0 | 911 | CheckSizeForSkiaGL(size)) { |
michael@0 | 912 | DemoteOldestContextIfNecessary(); |
michael@0 | 913 | |
michael@0 | 914 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
michael@0 | 915 | |
michael@0 | 916 | #if USE_SKIA |
michael@0 | 917 | if (glue && glue->GetGrContext() && glue->GetGLContext()) { |
michael@0 | 918 | mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format); |
michael@0 | 919 | if (mTarget) { |
michael@0 | 920 | mStream = gfx::SurfaceStream::CreateForType(SurfaceStreamType::TripleBuffer, glue->GetGLContext()); |
michael@0 | 921 | AddDemotableContext(this); |
michael@0 | 922 | } else { |
michael@0 | 923 | printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n"); |
michael@0 | 924 | } |
michael@0 | 925 | } |
michael@0 | 926 | #endif |
michael@0 | 927 | if (!mTarget) { |
michael@0 | 928 | mTarget = layerManager->CreateDrawTarget(size, format); |
michael@0 | 929 | } |
michael@0 | 930 | } else |
michael@0 | 931 | mTarget = layerManager->CreateDrawTarget(size, format); |
michael@0 | 932 | } else { |
michael@0 | 933 | mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format); |
michael@0 | 934 | } |
michael@0 | 935 | } |
michael@0 | 936 | |
michael@0 | 937 | if (mTarget) { |
michael@0 | 938 | static bool registered = false; |
michael@0 | 939 | if (!registered) { |
michael@0 | 940 | registered = true; |
michael@0 | 941 | RegisterStrongMemoryReporter(new Canvas2dPixelsReporter()); |
michael@0 | 942 | } |
michael@0 | 943 | |
michael@0 | 944 | gCanvasAzureMemoryUsed += mWidth * mHeight * 4; |
michael@0 | 945 | JSContext* context = nsContentUtils::GetCurrentJSContext(); |
michael@0 | 946 | if (context) { |
michael@0 | 947 | JS_updateMallocCounter(context, mWidth * mHeight * 4); |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | mTarget->ClearRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight))); |
michael@0 | 951 | // Force a full layer transaction since we didn't have a layer before |
michael@0 | 952 | // and now we might need one. |
michael@0 | 953 | if (mCanvasElement) { |
michael@0 | 954 | mCanvasElement->InvalidateCanvas(); |
michael@0 | 955 | } |
michael@0 | 956 | // Calling Redraw() tells our invalidation machinery that the entire |
michael@0 | 957 | // canvas is already invalid, which can speed up future drawing. |
michael@0 | 958 | Redraw(); |
michael@0 | 959 | } else { |
michael@0 | 960 | EnsureErrorTarget(); |
michael@0 | 961 | mTarget = sErrorTarget; |
michael@0 | 962 | } |
michael@0 | 963 | } |
michael@0 | 964 | |
michael@0 | 965 | #ifdef DEBUG |
michael@0 | 966 | int32_t |
michael@0 | 967 | CanvasRenderingContext2D::GetWidth() const |
michael@0 | 968 | { |
michael@0 | 969 | return mWidth; |
michael@0 | 970 | } |
michael@0 | 971 | |
michael@0 | 972 | int32_t |
michael@0 | 973 | CanvasRenderingContext2D::GetHeight() const |
michael@0 | 974 | { |
michael@0 | 975 | return mHeight; |
michael@0 | 976 | } |
michael@0 | 977 | #endif |
michael@0 | 978 | |
michael@0 | 979 | NS_IMETHODIMP |
michael@0 | 980 | CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height) |
michael@0 | 981 | { |
michael@0 | 982 | ClearTarget(); |
michael@0 | 983 | |
michael@0 | 984 | // Zero sized surfaces can cause problems. |
michael@0 | 985 | mZero = false; |
michael@0 | 986 | if (height == 0) { |
michael@0 | 987 | height = 1; |
michael@0 | 988 | mZero = true; |
michael@0 | 989 | } |
michael@0 | 990 | if (width == 0) { |
michael@0 | 991 | width = 1; |
michael@0 | 992 | mZero = true; |
michael@0 | 993 | } |
michael@0 | 994 | mWidth = width; |
michael@0 | 995 | mHeight = height; |
michael@0 | 996 | |
michael@0 | 997 | return NS_OK; |
michael@0 | 998 | } |
michael@0 | 999 | |
michael@0 | 1000 | void |
michael@0 | 1001 | CanvasRenderingContext2D::ClearTarget() |
michael@0 | 1002 | { |
michael@0 | 1003 | Reset(); |
michael@0 | 1004 | |
michael@0 | 1005 | mResetLayer = true; |
michael@0 | 1006 | |
michael@0 | 1007 | // set up the initial canvas defaults |
michael@0 | 1008 | mStyleStack.Clear(); |
michael@0 | 1009 | mPathBuilder = nullptr; |
michael@0 | 1010 | mPath = nullptr; |
michael@0 | 1011 | mDSPathBuilder = nullptr; |
michael@0 | 1012 | |
michael@0 | 1013 | ContextState *state = mStyleStack.AppendElement(); |
michael@0 | 1014 | state->globalAlpha = 1.0; |
michael@0 | 1015 | |
michael@0 | 1016 | state->colorStyles[Style::FILL] = NS_RGB(0,0,0); |
michael@0 | 1017 | state->colorStyles[Style::STROKE] = NS_RGB(0,0,0); |
michael@0 | 1018 | state->shadowColor = NS_RGBA(0,0,0,0); |
michael@0 | 1019 | } |
michael@0 | 1020 | |
michael@0 | 1021 | NS_IMETHODIMP |
michael@0 | 1022 | CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell, |
michael@0 | 1023 | gfxASurface *surface, |
michael@0 | 1024 | int32_t width, |
michael@0 | 1025 | int32_t height) |
michael@0 | 1026 | { |
michael@0 | 1027 | mDocShell = shell; |
michael@0 | 1028 | |
michael@0 | 1029 | SetDimensions(width, height); |
michael@0 | 1030 | mTarget = gfxPlatform::GetPlatform()-> |
michael@0 | 1031 | CreateDrawTargetForSurface(surface, IntSize(width, height)); |
michael@0 | 1032 | |
michael@0 | 1033 | if (!mTarget) { |
michael@0 | 1034 | EnsureErrorTarget(); |
michael@0 | 1035 | mTarget = sErrorTarget; |
michael@0 | 1036 | } |
michael@0 | 1037 | |
michael@0 | 1038 | return NS_OK; |
michael@0 | 1039 | } |
michael@0 | 1040 | |
michael@0 | 1041 | NS_IMETHODIMP |
michael@0 | 1042 | CanvasRenderingContext2D::SetIsOpaque(bool isOpaque) |
michael@0 | 1043 | { |
michael@0 | 1044 | if (isOpaque != mOpaque) { |
michael@0 | 1045 | mOpaque = isOpaque; |
michael@0 | 1046 | ClearTarget(); |
michael@0 | 1047 | } |
michael@0 | 1048 | |
michael@0 | 1049 | if (mOpaque) { |
michael@0 | 1050 | EnsureTarget(); |
michael@0 | 1051 | } |
michael@0 | 1052 | |
michael@0 | 1053 | return NS_OK; |
michael@0 | 1054 | } |
michael@0 | 1055 | |
michael@0 | 1056 | NS_IMETHODIMP |
michael@0 | 1057 | CanvasRenderingContext2D::SetIsIPC(bool isIPC) |
michael@0 | 1058 | { |
michael@0 | 1059 | if (isIPC != mIPC) { |
michael@0 | 1060 | mIPC = isIPC; |
michael@0 | 1061 | ClearTarget(); |
michael@0 | 1062 | } |
michael@0 | 1063 | |
michael@0 | 1064 | return NS_OK; |
michael@0 | 1065 | } |
michael@0 | 1066 | |
michael@0 | 1067 | NS_IMETHODIMP |
michael@0 | 1068 | CanvasRenderingContext2D::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value> aOptions) |
michael@0 | 1069 | { |
michael@0 | 1070 | if (aOptions.isNullOrUndefined()) { |
michael@0 | 1071 | return NS_OK; |
michael@0 | 1072 | } |
michael@0 | 1073 | |
michael@0 | 1074 | ContextAttributes2D attributes; |
michael@0 | 1075 | NS_ENSURE_TRUE(attributes.Init(aCx, aOptions), NS_ERROR_UNEXPECTED); |
michael@0 | 1076 | |
michael@0 | 1077 | if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) { |
michael@0 | 1078 | // Use software when there is going to be a lot of readback |
michael@0 | 1079 | mForceSoftware = attributes.mWillReadFrequently; |
michael@0 | 1080 | } |
michael@0 | 1081 | |
michael@0 | 1082 | if (!attributes.mAlpha) { |
michael@0 | 1083 | SetIsOpaque(true); |
michael@0 | 1084 | } |
michael@0 | 1085 | |
michael@0 | 1086 | return NS_OK; |
michael@0 | 1087 | } |
michael@0 | 1088 | |
michael@0 | 1089 | void |
michael@0 | 1090 | CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer, |
michael@0 | 1091 | int32_t* aFormat) |
michael@0 | 1092 | { |
michael@0 | 1093 | *aImageBuffer = nullptr; |
michael@0 | 1094 | *aFormat = 0; |
michael@0 | 1095 | |
michael@0 | 1096 | EnsureTarget(); |
michael@0 | 1097 | RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); |
michael@0 | 1098 | if (!snapshot) { |
michael@0 | 1099 | return; |
michael@0 | 1100 | } |
michael@0 | 1101 | |
michael@0 | 1102 | RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); |
michael@0 | 1103 | if (!data || data->GetSize() != IntSize(mWidth, mHeight)) { |
michael@0 | 1104 | return; |
michael@0 | 1105 | } |
michael@0 | 1106 | |
michael@0 | 1107 | *aImageBuffer = SurfaceToPackedBGRA(data); |
michael@0 | 1108 | *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB; |
michael@0 | 1109 | } |
michael@0 | 1110 | |
michael@0 | 1111 | NS_IMETHODIMP |
michael@0 | 1112 | CanvasRenderingContext2D::GetInputStream(const char *aMimeType, |
michael@0 | 1113 | const char16_t *aEncoderOptions, |
michael@0 | 1114 | nsIInputStream **aStream) |
michael@0 | 1115 | { |
michael@0 | 1116 | nsCString enccid("@mozilla.org/image/encoder;2?type="); |
michael@0 | 1117 | enccid += aMimeType; |
michael@0 | 1118 | nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); |
michael@0 | 1119 | if (!encoder) { |
michael@0 | 1120 | return NS_ERROR_FAILURE; |
michael@0 | 1121 | } |
michael@0 | 1122 | |
michael@0 | 1123 | nsAutoArrayPtr<uint8_t> imageBuffer; |
michael@0 | 1124 | int32_t format = 0; |
michael@0 | 1125 | GetImageBuffer(getter_Transfers(imageBuffer), &format); |
michael@0 | 1126 | if (!imageBuffer) { |
michael@0 | 1127 | return NS_ERROR_FAILURE; |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, |
michael@0 | 1131 | encoder, aEncoderOptions, aStream); |
michael@0 | 1132 | } |
michael@0 | 1133 | |
michael@0 | 1134 | SurfaceFormat |
michael@0 | 1135 | CanvasRenderingContext2D::GetSurfaceFormat() const |
michael@0 | 1136 | { |
michael@0 | 1137 | return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; |
michael@0 | 1138 | } |
michael@0 | 1139 | |
michael@0 | 1140 | // |
michael@0 | 1141 | // state |
michael@0 | 1142 | // |
michael@0 | 1143 | |
michael@0 | 1144 | void |
michael@0 | 1145 | CanvasRenderingContext2D::Save() |
michael@0 | 1146 | { |
michael@0 | 1147 | EnsureTarget(); |
michael@0 | 1148 | mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); |
michael@0 | 1149 | mStyleStack.SetCapacity(mStyleStack.Length() + 1); |
michael@0 | 1150 | mStyleStack.AppendElement(CurrentState()); |
michael@0 | 1151 | } |
michael@0 | 1152 | |
michael@0 | 1153 | void |
michael@0 | 1154 | CanvasRenderingContext2D::Restore() |
michael@0 | 1155 | { |
michael@0 | 1156 | if (mStyleStack.Length() - 1 == 0) |
michael@0 | 1157 | return; |
michael@0 | 1158 | |
michael@0 | 1159 | TransformWillUpdate(); |
michael@0 | 1160 | |
michael@0 | 1161 | for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) { |
michael@0 | 1162 | mTarget->PopClip(); |
michael@0 | 1163 | } |
michael@0 | 1164 | |
michael@0 | 1165 | mStyleStack.RemoveElementAt(mStyleStack.Length() - 1); |
michael@0 | 1166 | |
michael@0 | 1167 | mTarget->SetTransform(CurrentState().transform); |
michael@0 | 1168 | } |
michael@0 | 1169 | |
michael@0 | 1170 | // |
michael@0 | 1171 | // transformations |
michael@0 | 1172 | // |
michael@0 | 1173 | |
michael@0 | 1174 | void |
michael@0 | 1175 | CanvasRenderingContext2D::Scale(double x, double y, ErrorResult& error) |
michael@0 | 1176 | { |
michael@0 | 1177 | TransformWillUpdate(); |
michael@0 | 1178 | if (!IsTargetValid()) { |
michael@0 | 1179 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1180 | return; |
michael@0 | 1181 | } |
michael@0 | 1182 | |
michael@0 | 1183 | Matrix newMatrix = mTarget->GetTransform(); |
michael@0 | 1184 | mTarget->SetTransform(newMatrix.Scale(x, y)); |
michael@0 | 1185 | } |
michael@0 | 1186 | |
michael@0 | 1187 | void |
michael@0 | 1188 | CanvasRenderingContext2D::Rotate(double angle, ErrorResult& error) |
michael@0 | 1189 | { |
michael@0 | 1190 | TransformWillUpdate(); |
michael@0 | 1191 | if (!IsTargetValid()) { |
michael@0 | 1192 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1193 | return; |
michael@0 | 1194 | } |
michael@0 | 1195 | |
michael@0 | 1196 | Matrix rotation = Matrix::Rotation(angle); |
michael@0 | 1197 | mTarget->SetTransform(rotation * mTarget->GetTransform()); |
michael@0 | 1198 | } |
michael@0 | 1199 | |
michael@0 | 1200 | void |
michael@0 | 1201 | CanvasRenderingContext2D::Translate(double x, double y, ErrorResult& error) |
michael@0 | 1202 | { |
michael@0 | 1203 | TransformWillUpdate(); |
michael@0 | 1204 | if (!IsTargetValid()) { |
michael@0 | 1205 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1206 | return; |
michael@0 | 1207 | } |
michael@0 | 1208 | |
michael@0 | 1209 | Matrix newMatrix = mTarget->GetTransform(); |
michael@0 | 1210 | mTarget->SetTransform(newMatrix.Translate(x, y)); |
michael@0 | 1211 | } |
michael@0 | 1212 | |
michael@0 | 1213 | void |
michael@0 | 1214 | CanvasRenderingContext2D::Transform(double m11, double m12, double m21, |
michael@0 | 1215 | double m22, double dx, double dy, |
michael@0 | 1216 | ErrorResult& error) |
michael@0 | 1217 | { |
michael@0 | 1218 | TransformWillUpdate(); |
michael@0 | 1219 | if (!IsTargetValid()) { |
michael@0 | 1220 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1221 | return; |
michael@0 | 1222 | } |
michael@0 | 1223 | |
michael@0 | 1224 | Matrix matrix(m11, m12, m21, m22, dx, dy); |
michael@0 | 1225 | mTarget->SetTransform(matrix * mTarget->GetTransform()); |
michael@0 | 1226 | } |
michael@0 | 1227 | |
michael@0 | 1228 | void |
michael@0 | 1229 | CanvasRenderingContext2D::SetTransform(double m11, double m12, |
michael@0 | 1230 | double m21, double m22, |
michael@0 | 1231 | double dx, double dy, |
michael@0 | 1232 | ErrorResult& error) |
michael@0 | 1233 | { |
michael@0 | 1234 | TransformWillUpdate(); |
michael@0 | 1235 | if (!IsTargetValid()) { |
michael@0 | 1236 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1237 | return; |
michael@0 | 1238 | } |
michael@0 | 1239 | |
michael@0 | 1240 | Matrix matrix(m11, m12, m21, m22, dx, dy); |
michael@0 | 1241 | mTarget->SetTransform(matrix); |
michael@0 | 1242 | } |
michael@0 | 1243 | |
michael@0 | 1244 | static void |
michael@0 | 1245 | MatrixToJSObject(JSContext* cx, const Matrix& matrix, |
michael@0 | 1246 | JS::MutableHandle<JSObject*> result, ErrorResult& error) |
michael@0 | 1247 | { |
michael@0 | 1248 | double elts[6] = { matrix._11, matrix._12, |
michael@0 | 1249 | matrix._21, matrix._22, |
michael@0 | 1250 | matrix._31, matrix._32 }; |
michael@0 | 1251 | |
michael@0 | 1252 | // XXX Should we enter GetWrapper()'s compartment? |
michael@0 | 1253 | JS::Rooted<JS::Value> val(cx); |
michael@0 | 1254 | if (!ToJSValue(cx, elts, &val)) { |
michael@0 | 1255 | error.Throw(NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 1256 | } else { |
michael@0 | 1257 | result.set(&val.toObject()); |
michael@0 | 1258 | } |
michael@0 | 1259 | } |
michael@0 | 1260 | |
michael@0 | 1261 | static bool |
michael@0 | 1262 | ObjectToMatrix(JSContext* cx, JS::Handle<JSObject*> obj, Matrix& matrix, |
michael@0 | 1263 | ErrorResult& error) |
michael@0 | 1264 | { |
michael@0 | 1265 | uint32_t length; |
michael@0 | 1266 | if (!JS_GetArrayLength(cx, obj, &length) || length != 6) { |
michael@0 | 1267 | // Not an array-like thing or wrong size |
michael@0 | 1268 | error.Throw(NS_ERROR_INVALID_ARG); |
michael@0 | 1269 | return false; |
michael@0 | 1270 | } |
michael@0 | 1271 | |
michael@0 | 1272 | Float* elts[] = { &matrix._11, &matrix._12, &matrix._21, &matrix._22, |
michael@0 | 1273 | &matrix._31, &matrix._32 }; |
michael@0 | 1274 | for (uint32_t i = 0; i < 6; ++i) { |
michael@0 | 1275 | JS::Rooted<JS::Value> elt(cx); |
michael@0 | 1276 | double d; |
michael@0 | 1277 | if (!JS_GetElement(cx, obj, i, &elt)) { |
michael@0 | 1278 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1279 | return false; |
michael@0 | 1280 | } |
michael@0 | 1281 | if (!CoerceDouble(elt, &d)) { |
michael@0 | 1282 | error.Throw(NS_ERROR_INVALID_ARG); |
michael@0 | 1283 | return false; |
michael@0 | 1284 | } |
michael@0 | 1285 | if (!FloatValidate(d)) { |
michael@0 | 1286 | // This is weird, but it's the behavior of SetTransform() |
michael@0 | 1287 | return false; |
michael@0 | 1288 | } |
michael@0 | 1289 | *elts[i] = Float(d); |
michael@0 | 1290 | } |
michael@0 | 1291 | return true; |
michael@0 | 1292 | } |
michael@0 | 1293 | |
michael@0 | 1294 | void |
michael@0 | 1295 | CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* cx, |
michael@0 | 1296 | JS::Handle<JSObject*> currentTransform, |
michael@0 | 1297 | ErrorResult& error) |
michael@0 | 1298 | { |
michael@0 | 1299 | EnsureTarget(); |
michael@0 | 1300 | if (!IsTargetValid()) { |
michael@0 | 1301 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1302 | return; |
michael@0 | 1303 | } |
michael@0 | 1304 | |
michael@0 | 1305 | Matrix newCTM; |
michael@0 | 1306 | if (ObjectToMatrix(cx, currentTransform, newCTM, error)) { |
michael@0 | 1307 | mTarget->SetTransform(newCTM); |
michael@0 | 1308 | } |
michael@0 | 1309 | } |
michael@0 | 1310 | |
michael@0 | 1311 | void |
michael@0 | 1312 | CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* cx, |
michael@0 | 1313 | JS::MutableHandle<JSObject*> result, |
michael@0 | 1314 | ErrorResult& error) const |
michael@0 | 1315 | { |
michael@0 | 1316 | MatrixToJSObject(cx, mTarget ? mTarget->GetTransform() : Matrix(), |
michael@0 | 1317 | result, error); |
michael@0 | 1318 | } |
michael@0 | 1319 | |
michael@0 | 1320 | void |
michael@0 | 1321 | CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* cx, |
michael@0 | 1322 | JS::Handle<JSObject*> currentTransform, |
michael@0 | 1323 | ErrorResult& error) |
michael@0 | 1324 | { |
michael@0 | 1325 | EnsureTarget(); |
michael@0 | 1326 | if (!IsTargetValid()) { |
michael@0 | 1327 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 1328 | return; |
michael@0 | 1329 | } |
michael@0 | 1330 | |
michael@0 | 1331 | Matrix newCTMInverse; |
michael@0 | 1332 | if (ObjectToMatrix(cx, currentTransform, newCTMInverse, error)) { |
michael@0 | 1333 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 1334 | if (newCTMInverse.Invert()) { |
michael@0 | 1335 | mTarget->SetTransform(newCTMInverse); |
michael@0 | 1336 | } |
michael@0 | 1337 | } |
michael@0 | 1338 | } |
michael@0 | 1339 | |
michael@0 | 1340 | void |
michael@0 | 1341 | CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* cx, |
michael@0 | 1342 | JS::MutableHandle<JSObject*> result, |
michael@0 | 1343 | ErrorResult& error) const |
michael@0 | 1344 | { |
michael@0 | 1345 | if (!mTarget) { |
michael@0 | 1346 | MatrixToJSObject(cx, Matrix(), result, error); |
michael@0 | 1347 | return; |
michael@0 | 1348 | } |
michael@0 | 1349 | |
michael@0 | 1350 | Matrix ctm = mTarget->GetTransform(); |
michael@0 | 1351 | |
michael@0 | 1352 | if (!ctm.Invert()) { |
michael@0 | 1353 | double NaN = JSVAL_TO_DOUBLE(JS_GetNaNValue(cx)); |
michael@0 | 1354 | ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); |
michael@0 | 1355 | } |
michael@0 | 1356 | |
michael@0 | 1357 | MatrixToJSObject(cx, ctm, result, error); |
michael@0 | 1358 | } |
michael@0 | 1359 | |
michael@0 | 1360 | // |
michael@0 | 1361 | // colors |
michael@0 | 1362 | // |
michael@0 | 1363 | |
michael@0 | 1364 | void |
michael@0 | 1365 | CanvasRenderingContext2D::SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& value, |
michael@0 | 1366 | Style whichStyle) |
michael@0 | 1367 | { |
michael@0 | 1368 | if (value.IsString()) { |
michael@0 | 1369 | SetStyleFromString(value.GetAsString(), whichStyle); |
michael@0 | 1370 | return; |
michael@0 | 1371 | } |
michael@0 | 1372 | |
michael@0 | 1373 | if (value.IsCanvasGradient()) { |
michael@0 | 1374 | SetStyleFromGradient(value.GetAsCanvasGradient(), whichStyle); |
michael@0 | 1375 | return; |
michael@0 | 1376 | } |
michael@0 | 1377 | |
michael@0 | 1378 | if (value.IsCanvasPattern()) { |
michael@0 | 1379 | SetStyleFromPattern(value.GetAsCanvasPattern(), whichStyle); |
michael@0 | 1380 | return; |
michael@0 | 1381 | } |
michael@0 | 1382 | |
michael@0 | 1383 | MOZ_ASSUME_UNREACHABLE("Invalid union value"); |
michael@0 | 1384 | } |
michael@0 | 1385 | |
michael@0 | 1386 | void |
michael@0 | 1387 | CanvasRenderingContext2D::SetFillRule(const nsAString& aString) |
michael@0 | 1388 | { |
michael@0 | 1389 | FillRule rule; |
michael@0 | 1390 | |
michael@0 | 1391 | if (aString.EqualsLiteral("evenodd")) |
michael@0 | 1392 | rule = FillRule::FILL_EVEN_ODD; |
michael@0 | 1393 | else if (aString.EqualsLiteral("nonzero")) |
michael@0 | 1394 | rule = FillRule::FILL_WINDING; |
michael@0 | 1395 | else |
michael@0 | 1396 | return; |
michael@0 | 1397 | |
michael@0 | 1398 | CurrentState().fillRule = rule; |
michael@0 | 1399 | } |
michael@0 | 1400 | |
michael@0 | 1401 | void |
michael@0 | 1402 | CanvasRenderingContext2D::GetFillRule(nsAString& aString) |
michael@0 | 1403 | { |
michael@0 | 1404 | switch (CurrentState().fillRule) { |
michael@0 | 1405 | case FillRule::FILL_WINDING: |
michael@0 | 1406 | aString.AssignLiteral("nonzero"); break; |
michael@0 | 1407 | case FillRule::FILL_EVEN_ODD: |
michael@0 | 1408 | aString.AssignLiteral("evenodd"); break; |
michael@0 | 1409 | } |
michael@0 | 1410 | } |
michael@0 | 1411 | // |
michael@0 | 1412 | // gradients and patterns |
michael@0 | 1413 | // |
michael@0 | 1414 | already_AddRefed<CanvasGradient> |
michael@0 | 1415 | CanvasRenderingContext2D::CreateLinearGradient(double x0, double y0, double x1, double y1) |
michael@0 | 1416 | { |
michael@0 | 1417 | nsIDocument *doc = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; |
michael@0 | 1418 | mozilla::css::Loader *cssLoader = doc ? doc->CSSLoader() : nullptr; |
michael@0 | 1419 | |
michael@0 | 1420 | nsRefPtr<CanvasGradient> grad = |
michael@0 | 1421 | new CanvasLinearGradient(this, cssLoader, Point(x0, y0), Point(x1, y1)); |
michael@0 | 1422 | |
michael@0 | 1423 | return grad.forget(); |
michael@0 | 1424 | } |
michael@0 | 1425 | |
michael@0 | 1426 | already_AddRefed<CanvasGradient> |
michael@0 | 1427 | CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, |
michael@0 | 1428 | double x1, double y1, double r1, |
michael@0 | 1429 | ErrorResult& aError) |
michael@0 | 1430 | { |
michael@0 | 1431 | if (r0 < 0.0 || r1 < 0.0) { |
michael@0 | 1432 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 1433 | return nullptr; |
michael@0 | 1434 | } |
michael@0 | 1435 | |
michael@0 | 1436 | nsIDocument *doc = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; |
michael@0 | 1437 | mozilla::css::Loader *cssLoader = doc ? doc->CSSLoader() : nullptr; |
michael@0 | 1438 | nsRefPtr<CanvasGradient> grad = |
michael@0 | 1439 | new CanvasRadialGradient(this, cssLoader, Point(x0, y0), r0, Point(x1, y1), r1); |
michael@0 | 1440 | |
michael@0 | 1441 | return grad.forget(); |
michael@0 | 1442 | } |
michael@0 | 1443 | |
michael@0 | 1444 | already_AddRefed<CanvasPattern> |
michael@0 | 1445 | CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, |
michael@0 | 1446 | const nsAString& repeat, |
michael@0 | 1447 | ErrorResult& error) |
michael@0 | 1448 | { |
michael@0 | 1449 | CanvasPattern::RepeatMode repeatMode = |
michael@0 | 1450 | CanvasPattern::RepeatMode::NOREPEAT; |
michael@0 | 1451 | |
michael@0 | 1452 | if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) { |
michael@0 | 1453 | repeatMode = CanvasPattern::RepeatMode::REPEAT; |
michael@0 | 1454 | } else if (repeat.EqualsLiteral("repeat-x")) { |
michael@0 | 1455 | repeatMode = CanvasPattern::RepeatMode::REPEATX; |
michael@0 | 1456 | } else if (repeat.EqualsLiteral("repeat-y")) { |
michael@0 | 1457 | repeatMode = CanvasPattern::RepeatMode::REPEATY; |
michael@0 | 1458 | } else if (repeat.EqualsLiteral("no-repeat")) { |
michael@0 | 1459 | repeatMode = CanvasPattern::RepeatMode::NOREPEAT; |
michael@0 | 1460 | } else { |
michael@0 | 1461 | error.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
michael@0 | 1462 | return nullptr; |
michael@0 | 1463 | } |
michael@0 | 1464 | |
michael@0 | 1465 | Element* htmlElement; |
michael@0 | 1466 | if (element.IsHTMLCanvasElement()) { |
michael@0 | 1467 | HTMLCanvasElement* canvas = &element.GetAsHTMLCanvasElement(); |
michael@0 | 1468 | htmlElement = canvas; |
michael@0 | 1469 | |
michael@0 | 1470 | nsIntSize size = canvas->GetSize(); |
michael@0 | 1471 | if (size.width == 0 || size.height == 0) { |
michael@0 | 1472 | error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
michael@0 | 1473 | return nullptr; |
michael@0 | 1474 | } |
michael@0 | 1475 | |
michael@0 | 1476 | // Special case for Canvas, which could be an Azure canvas! |
michael@0 | 1477 | nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0); |
michael@0 | 1478 | if (srcCanvas) { |
michael@0 | 1479 | // This might not be an Azure canvas! |
michael@0 | 1480 | RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); |
michael@0 | 1481 | |
michael@0 | 1482 | nsRefPtr<CanvasPattern> pat = |
michael@0 | 1483 | new CanvasPattern(this, srcSurf, repeatMode, htmlElement->NodePrincipal(), canvas->IsWriteOnly(), false); |
michael@0 | 1484 | |
michael@0 | 1485 | return pat.forget(); |
michael@0 | 1486 | } |
michael@0 | 1487 | } else if (element.IsHTMLImageElement()) { |
michael@0 | 1488 | htmlElement = &element.GetAsHTMLImageElement(); |
michael@0 | 1489 | } else { |
michael@0 | 1490 | htmlElement = &element.GetAsHTMLVideoElement(); |
michael@0 | 1491 | } |
michael@0 | 1492 | |
michael@0 | 1493 | EnsureTarget(); |
michael@0 | 1494 | |
michael@0 | 1495 | // The canvas spec says that createPattern should use the first frame |
michael@0 | 1496 | // of animated images |
michael@0 | 1497 | nsLayoutUtils::SurfaceFromElementResult res = |
michael@0 | 1498 | nsLayoutUtils::SurfaceFromElement(htmlElement, |
michael@0 | 1499 | nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget); |
michael@0 | 1500 | |
michael@0 | 1501 | if (!res.mSourceSurface) { |
michael@0 | 1502 | error.Throw(NS_ERROR_NOT_AVAILABLE); |
michael@0 | 1503 | return nullptr; |
michael@0 | 1504 | } |
michael@0 | 1505 | |
michael@0 | 1506 | nsRefPtr<CanvasPattern> pat = |
michael@0 | 1507 | new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal, |
michael@0 | 1508 | res.mIsWriteOnly, res.mCORSUsed); |
michael@0 | 1509 | |
michael@0 | 1510 | return pat.forget(); |
michael@0 | 1511 | } |
michael@0 | 1512 | |
michael@0 | 1513 | // |
michael@0 | 1514 | // shadows |
michael@0 | 1515 | // |
michael@0 | 1516 | void |
michael@0 | 1517 | CanvasRenderingContext2D::SetShadowColor(const nsAString& shadowColor) |
michael@0 | 1518 | { |
michael@0 | 1519 | nscolor color; |
michael@0 | 1520 | if (!ParseColor(shadowColor, &color)) { |
michael@0 | 1521 | return; |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | CurrentState().shadowColor = color; |
michael@0 | 1525 | } |
michael@0 | 1526 | |
michael@0 | 1527 | // |
michael@0 | 1528 | // rects |
michael@0 | 1529 | // |
michael@0 | 1530 | |
michael@0 | 1531 | void |
michael@0 | 1532 | CanvasRenderingContext2D::ClearRect(double x, double y, double w, |
michael@0 | 1533 | double h) |
michael@0 | 1534 | { |
michael@0 | 1535 | if (!mTarget) { |
michael@0 | 1536 | return; |
michael@0 | 1537 | } |
michael@0 | 1538 | |
michael@0 | 1539 | mTarget->ClearRect(mgfx::Rect(x, y, w, h)); |
michael@0 | 1540 | |
michael@0 | 1541 | RedrawUser(gfxRect(x, y, w, h)); |
michael@0 | 1542 | } |
michael@0 | 1543 | |
michael@0 | 1544 | void |
michael@0 | 1545 | CanvasRenderingContext2D::FillRect(double x, double y, double w, |
michael@0 | 1546 | double h) |
michael@0 | 1547 | { |
michael@0 | 1548 | const ContextState &state = CurrentState(); |
michael@0 | 1549 | |
michael@0 | 1550 | if (state.patternStyles[Style::FILL]) { |
michael@0 | 1551 | CanvasPattern::RepeatMode repeat = |
michael@0 | 1552 | state.patternStyles[Style::FILL]->mRepeat; |
michael@0 | 1553 | // In the FillRect case repeat modes are easy to deal with. |
michael@0 | 1554 | bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATY; |
michael@0 | 1555 | bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT || repeat == CanvasPattern::RepeatMode::REPEATX; |
michael@0 | 1556 | |
michael@0 | 1557 | IntSize patternSize = |
michael@0 | 1558 | state.patternStyles[Style::FILL]->mSurface->GetSize(); |
michael@0 | 1559 | |
michael@0 | 1560 | // We always need to execute painting for non-over operators, even if |
michael@0 | 1561 | // we end up with w/h = 0. |
michael@0 | 1562 | if (limitx) { |
michael@0 | 1563 | if (x < 0) { |
michael@0 | 1564 | w += x; |
michael@0 | 1565 | if (w < 0) { |
michael@0 | 1566 | w = 0; |
michael@0 | 1567 | } |
michael@0 | 1568 | |
michael@0 | 1569 | x = 0; |
michael@0 | 1570 | } |
michael@0 | 1571 | if (x + w > patternSize.width) { |
michael@0 | 1572 | w = patternSize.width - x; |
michael@0 | 1573 | if (w < 0) { |
michael@0 | 1574 | w = 0; |
michael@0 | 1575 | } |
michael@0 | 1576 | } |
michael@0 | 1577 | } |
michael@0 | 1578 | if (limity) { |
michael@0 | 1579 | if (y < 0) { |
michael@0 | 1580 | h += y; |
michael@0 | 1581 | if (h < 0) { |
michael@0 | 1582 | h = 0; |
michael@0 | 1583 | } |
michael@0 | 1584 | |
michael@0 | 1585 | y = 0; |
michael@0 | 1586 | } |
michael@0 | 1587 | if (y + h > patternSize.height) { |
michael@0 | 1588 | h = patternSize.height - y; |
michael@0 | 1589 | if (h < 0) { |
michael@0 | 1590 | h = 0; |
michael@0 | 1591 | } |
michael@0 | 1592 | } |
michael@0 | 1593 | } |
michael@0 | 1594 | } |
michael@0 | 1595 | |
michael@0 | 1596 | mgfx::Rect bounds; |
michael@0 | 1597 | |
michael@0 | 1598 | EnsureTarget(); |
michael@0 | 1599 | if (NeedToDrawShadow()) { |
michael@0 | 1600 | bounds = mgfx::Rect(x, y, w, h); |
michael@0 | 1601 | bounds = mTarget->GetTransform().TransformBounds(bounds); |
michael@0 | 1602 | } |
michael@0 | 1603 | |
michael@0 | 1604 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1605 | FillRect(mgfx::Rect(x, y, w, h), |
michael@0 | 1606 | CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), |
michael@0 | 1607 | DrawOptions(state.globalAlpha, UsedOperation())); |
michael@0 | 1608 | |
michael@0 | 1609 | RedrawUser(gfxRect(x, y, w, h)); |
michael@0 | 1610 | } |
michael@0 | 1611 | |
michael@0 | 1612 | void |
michael@0 | 1613 | CanvasRenderingContext2D::StrokeRect(double x, double y, double w, |
michael@0 | 1614 | double h) |
michael@0 | 1615 | { |
michael@0 | 1616 | const ContextState &state = CurrentState(); |
michael@0 | 1617 | |
michael@0 | 1618 | mgfx::Rect bounds; |
michael@0 | 1619 | |
michael@0 | 1620 | if (!w && !h) { |
michael@0 | 1621 | return; |
michael@0 | 1622 | } |
michael@0 | 1623 | |
michael@0 | 1624 | EnsureTarget(); |
michael@0 | 1625 | if (!IsTargetValid()) { |
michael@0 | 1626 | return; |
michael@0 | 1627 | } |
michael@0 | 1628 | |
michael@0 | 1629 | if (NeedToDrawShadow()) { |
michael@0 | 1630 | bounds = mgfx::Rect(x - state.lineWidth / 2.0f, y - state.lineWidth / 2.0f, |
michael@0 | 1631 | w + state.lineWidth, h + state.lineWidth); |
michael@0 | 1632 | bounds = mTarget->GetTransform().TransformBounds(bounds); |
michael@0 | 1633 | } |
michael@0 | 1634 | |
michael@0 | 1635 | if (!h) { |
michael@0 | 1636 | CapStyle cap = CapStyle::BUTT; |
michael@0 | 1637 | if (state.lineJoin == JoinStyle::ROUND) { |
michael@0 | 1638 | cap = CapStyle::ROUND; |
michael@0 | 1639 | } |
michael@0 | 1640 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1641 | StrokeLine(Point(x, y), Point(x + w, y), |
michael@0 | 1642 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
michael@0 | 1643 | StrokeOptions(state.lineWidth, state.lineJoin, |
michael@0 | 1644 | cap, state.miterLimit, |
michael@0 | 1645 | state.dash.Length(), |
michael@0 | 1646 | state.dash.Elements(), |
michael@0 | 1647 | state.dashOffset), |
michael@0 | 1648 | DrawOptions(state.globalAlpha, UsedOperation())); |
michael@0 | 1649 | return; |
michael@0 | 1650 | } |
michael@0 | 1651 | |
michael@0 | 1652 | if (!w) { |
michael@0 | 1653 | CapStyle cap = CapStyle::BUTT; |
michael@0 | 1654 | if (state.lineJoin == JoinStyle::ROUND) { |
michael@0 | 1655 | cap = CapStyle::ROUND; |
michael@0 | 1656 | } |
michael@0 | 1657 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1658 | StrokeLine(Point(x, y), Point(x, y + h), |
michael@0 | 1659 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
michael@0 | 1660 | StrokeOptions(state.lineWidth, state.lineJoin, |
michael@0 | 1661 | cap, state.miterLimit, |
michael@0 | 1662 | state.dash.Length(), |
michael@0 | 1663 | state.dash.Elements(), |
michael@0 | 1664 | state.dashOffset), |
michael@0 | 1665 | DrawOptions(state.globalAlpha, UsedOperation())); |
michael@0 | 1666 | return; |
michael@0 | 1667 | } |
michael@0 | 1668 | |
michael@0 | 1669 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1670 | StrokeRect(mgfx::Rect(x, y, w, h), |
michael@0 | 1671 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
michael@0 | 1672 | StrokeOptions(state.lineWidth, state.lineJoin, |
michael@0 | 1673 | state.lineCap, state.miterLimit, |
michael@0 | 1674 | state.dash.Length(), |
michael@0 | 1675 | state.dash.Elements(), |
michael@0 | 1676 | state.dashOffset), |
michael@0 | 1677 | DrawOptions(state.globalAlpha, UsedOperation())); |
michael@0 | 1678 | |
michael@0 | 1679 | Redraw(); |
michael@0 | 1680 | } |
michael@0 | 1681 | |
michael@0 | 1682 | // |
michael@0 | 1683 | // path bits |
michael@0 | 1684 | // |
michael@0 | 1685 | |
michael@0 | 1686 | void |
michael@0 | 1687 | CanvasRenderingContext2D::BeginPath() |
michael@0 | 1688 | { |
michael@0 | 1689 | mPath = nullptr; |
michael@0 | 1690 | mPathBuilder = nullptr; |
michael@0 | 1691 | mDSPathBuilder = nullptr; |
michael@0 | 1692 | mPathTransformWillUpdate = false; |
michael@0 | 1693 | } |
michael@0 | 1694 | |
michael@0 | 1695 | void |
michael@0 | 1696 | CanvasRenderingContext2D::Fill(const CanvasWindingRule& winding) |
michael@0 | 1697 | { |
michael@0 | 1698 | EnsureUserSpacePath(winding); |
michael@0 | 1699 | |
michael@0 | 1700 | if (!mPath) { |
michael@0 | 1701 | return; |
michael@0 | 1702 | } |
michael@0 | 1703 | |
michael@0 | 1704 | mgfx::Rect bounds; |
michael@0 | 1705 | |
michael@0 | 1706 | if (NeedToDrawShadow()) { |
michael@0 | 1707 | bounds = mPath->GetBounds(mTarget->GetTransform()); |
michael@0 | 1708 | } |
michael@0 | 1709 | |
michael@0 | 1710 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1711 | Fill(mPath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), |
michael@0 | 1712 | DrawOptions(CurrentState().globalAlpha, UsedOperation())); |
michael@0 | 1713 | |
michael@0 | 1714 | Redraw(); |
michael@0 | 1715 | } |
michael@0 | 1716 | |
michael@0 | 1717 | void CanvasRenderingContext2D::Fill(const CanvasPath& path, const CanvasWindingRule& winding) |
michael@0 | 1718 | { |
michael@0 | 1719 | EnsureTarget(); |
michael@0 | 1720 | |
michael@0 | 1721 | RefPtr<gfx::Path> gfxpath = path.GetPath(winding, mTarget); |
michael@0 | 1722 | |
michael@0 | 1723 | if (!gfxpath) { |
michael@0 | 1724 | return; |
michael@0 | 1725 | } |
michael@0 | 1726 | |
michael@0 | 1727 | mgfx::Rect bounds; |
michael@0 | 1728 | |
michael@0 | 1729 | if (NeedToDrawShadow()) { |
michael@0 | 1730 | bounds = gfxpath->GetBounds(mTarget->GetTransform()); |
michael@0 | 1731 | } |
michael@0 | 1732 | |
michael@0 | 1733 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1734 | Fill(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), |
michael@0 | 1735 | DrawOptions(CurrentState().globalAlpha, UsedOperation())); |
michael@0 | 1736 | |
michael@0 | 1737 | Redraw(); |
michael@0 | 1738 | } |
michael@0 | 1739 | |
michael@0 | 1740 | void |
michael@0 | 1741 | CanvasRenderingContext2D::Stroke() |
michael@0 | 1742 | { |
michael@0 | 1743 | EnsureUserSpacePath(); |
michael@0 | 1744 | |
michael@0 | 1745 | if (!mPath) { |
michael@0 | 1746 | return; |
michael@0 | 1747 | } |
michael@0 | 1748 | |
michael@0 | 1749 | const ContextState &state = CurrentState(); |
michael@0 | 1750 | |
michael@0 | 1751 | StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, |
michael@0 | 1752 | state.lineCap, state.miterLimit, |
michael@0 | 1753 | state.dash.Length(), state.dash.Elements(), |
michael@0 | 1754 | state.dashOffset); |
michael@0 | 1755 | |
michael@0 | 1756 | mgfx::Rect bounds; |
michael@0 | 1757 | if (NeedToDrawShadow()) { |
michael@0 | 1758 | bounds = |
michael@0 | 1759 | mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); |
michael@0 | 1760 | } |
michael@0 | 1761 | |
michael@0 | 1762 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1763 | Stroke(mPath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
michael@0 | 1764 | strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); |
michael@0 | 1765 | |
michael@0 | 1766 | Redraw(); |
michael@0 | 1767 | } |
michael@0 | 1768 | |
michael@0 | 1769 | void |
michael@0 | 1770 | CanvasRenderingContext2D::Stroke(const CanvasPath& path) |
michael@0 | 1771 | { |
michael@0 | 1772 | EnsureTarget(); |
michael@0 | 1773 | |
michael@0 | 1774 | RefPtr<gfx::Path> gfxpath = path.GetPath(CanvasWindingRule::Nonzero, mTarget); |
michael@0 | 1775 | |
michael@0 | 1776 | if (!gfxpath) { |
michael@0 | 1777 | return; |
michael@0 | 1778 | } |
michael@0 | 1779 | |
michael@0 | 1780 | const ContextState &state = CurrentState(); |
michael@0 | 1781 | |
michael@0 | 1782 | StrokeOptions strokeOptions(state.lineWidth, state.lineJoin, |
michael@0 | 1783 | state.lineCap, state.miterLimit, |
michael@0 | 1784 | state.dash.Length(), state.dash.Elements(), |
michael@0 | 1785 | state.dashOffset); |
michael@0 | 1786 | |
michael@0 | 1787 | mgfx::Rect bounds; |
michael@0 | 1788 | if (NeedToDrawShadow()) { |
michael@0 | 1789 | bounds = |
michael@0 | 1790 | gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); |
michael@0 | 1791 | } |
michael@0 | 1792 | |
michael@0 | 1793 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 1794 | Stroke(gfxpath, CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
michael@0 | 1795 | strokeOptions, DrawOptions(state.globalAlpha, UsedOperation())); |
michael@0 | 1796 | |
michael@0 | 1797 | Redraw(); |
michael@0 | 1798 | } |
michael@0 | 1799 | |
michael@0 | 1800 | void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement) |
michael@0 | 1801 | { |
michael@0 | 1802 | EnsureUserSpacePath(); |
michael@0 | 1803 | |
michael@0 | 1804 | if (!mPath) { |
michael@0 | 1805 | return; |
michael@0 | 1806 | } |
michael@0 | 1807 | |
michael@0 | 1808 | if(DrawCustomFocusRing(aElement)) { |
michael@0 | 1809 | Save(); |
michael@0 | 1810 | |
michael@0 | 1811 | // set state to conforming focus state |
michael@0 | 1812 | ContextState& state = CurrentState(); |
michael@0 | 1813 | state.globalAlpha = 1.0; |
michael@0 | 1814 | state.shadowBlur = 0; |
michael@0 | 1815 | state.shadowOffset.x = 0; |
michael@0 | 1816 | state.shadowOffset.y = 0; |
michael@0 | 1817 | state.op = mozilla::gfx::CompositionOp::OP_OVER; |
michael@0 | 1818 | |
michael@0 | 1819 | state.lineCap = CapStyle::BUTT; |
michael@0 | 1820 | state.lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL; |
michael@0 | 1821 | state.lineWidth = 1; |
michael@0 | 1822 | CurrentState().dash.Clear(); |
michael@0 | 1823 | |
michael@0 | 1824 | // color and style of the rings is the same as for image maps |
michael@0 | 1825 | // set the background focus color |
michael@0 | 1826 | CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255)); |
michael@0 | 1827 | // draw the focus ring |
michael@0 | 1828 | Stroke(); |
michael@0 | 1829 | |
michael@0 | 1830 | // set dashing for foreground |
michael@0 | 1831 | FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash; |
michael@0 | 1832 | dash.AppendElement(1); |
michael@0 | 1833 | dash.AppendElement(1); |
michael@0 | 1834 | |
michael@0 | 1835 | // set the foreground focus color |
michael@0 | 1836 | CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255)); |
michael@0 | 1837 | // draw the focus ring |
michael@0 | 1838 | Stroke(); |
michael@0 | 1839 | |
michael@0 | 1840 | Restore(); |
michael@0 | 1841 | } |
michael@0 | 1842 | } |
michael@0 | 1843 | |
michael@0 | 1844 | bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement) |
michael@0 | 1845 | { |
michael@0 | 1846 | EnsureUserSpacePath(); |
michael@0 | 1847 | |
michael@0 | 1848 | HTMLCanvasElement* canvas = GetCanvas(); |
michael@0 | 1849 | |
michael@0 | 1850 | if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) { |
michael@0 | 1851 | return false; |
michael@0 | 1852 | } |
michael@0 | 1853 | |
michael@0 | 1854 | nsIFocusManager* fm = nsFocusManager::GetFocusManager(); |
michael@0 | 1855 | if (fm) { |
michael@0 | 1856 | // check that the element i focused |
michael@0 | 1857 | nsCOMPtr<nsIDOMElement> focusedElement; |
michael@0 | 1858 | fm->GetFocusedElement(getter_AddRefs(focusedElement)); |
michael@0 | 1859 | if (SameCOMIdentity(aElement.AsDOMNode(), focusedElement)) { |
michael@0 | 1860 | return true; |
michael@0 | 1861 | } |
michael@0 | 1862 | } |
michael@0 | 1863 | |
michael@0 | 1864 | return false; |
michael@0 | 1865 | } |
michael@0 | 1866 | |
michael@0 | 1867 | void |
michael@0 | 1868 | CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding) |
michael@0 | 1869 | { |
michael@0 | 1870 | EnsureUserSpacePath(winding); |
michael@0 | 1871 | |
michael@0 | 1872 | if (!mPath) { |
michael@0 | 1873 | return; |
michael@0 | 1874 | } |
michael@0 | 1875 | |
michael@0 | 1876 | mTarget->PushClip(mPath); |
michael@0 | 1877 | CurrentState().clipsPushed.push_back(mPath); |
michael@0 | 1878 | } |
michael@0 | 1879 | |
michael@0 | 1880 | void |
michael@0 | 1881 | CanvasRenderingContext2D::Clip(const CanvasPath& path, const CanvasWindingRule& winding) |
michael@0 | 1882 | { |
michael@0 | 1883 | EnsureTarget(); |
michael@0 | 1884 | |
michael@0 | 1885 | RefPtr<gfx::Path> gfxpath = path.GetPath(winding, mTarget); |
michael@0 | 1886 | |
michael@0 | 1887 | if (!gfxpath) { |
michael@0 | 1888 | return; |
michael@0 | 1889 | } |
michael@0 | 1890 | |
michael@0 | 1891 | mTarget->PushClip(gfxpath); |
michael@0 | 1892 | CurrentState().clipsPushed.push_back(gfxpath); |
michael@0 | 1893 | } |
michael@0 | 1894 | |
michael@0 | 1895 | void |
michael@0 | 1896 | CanvasRenderingContext2D::ArcTo(double x1, double y1, double x2, |
michael@0 | 1897 | double y2, double radius, |
michael@0 | 1898 | ErrorResult& error) |
michael@0 | 1899 | { |
michael@0 | 1900 | if (radius < 0) { |
michael@0 | 1901 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 1902 | return; |
michael@0 | 1903 | } |
michael@0 | 1904 | |
michael@0 | 1905 | EnsureWritablePath(); |
michael@0 | 1906 | |
michael@0 | 1907 | // Current point in user space! |
michael@0 | 1908 | Point p0; |
michael@0 | 1909 | if (mPathBuilder) { |
michael@0 | 1910 | p0 = mPathBuilder->CurrentPoint(); |
michael@0 | 1911 | } else { |
michael@0 | 1912 | Matrix invTransform = mTarget->GetTransform(); |
michael@0 | 1913 | if (!invTransform.Invert()) { |
michael@0 | 1914 | return; |
michael@0 | 1915 | } |
michael@0 | 1916 | |
michael@0 | 1917 | p0 = invTransform * mDSPathBuilder->CurrentPoint(); |
michael@0 | 1918 | } |
michael@0 | 1919 | |
michael@0 | 1920 | Point p1(x1, y1); |
michael@0 | 1921 | Point p2(x2, y2); |
michael@0 | 1922 | |
michael@0 | 1923 | // Execute these calculations in double precision to avoid cumulative |
michael@0 | 1924 | // rounding errors. |
michael@0 | 1925 | double dir, a2, b2, c2, cosx, sinx, d, anx, any, |
michael@0 | 1926 | bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; |
michael@0 | 1927 | bool anticlockwise; |
michael@0 | 1928 | |
michael@0 | 1929 | if (p0 == p1 || p1 == p2 || radius == 0) { |
michael@0 | 1930 | LineTo(p1.x, p1.y); |
michael@0 | 1931 | return; |
michael@0 | 1932 | } |
michael@0 | 1933 | |
michael@0 | 1934 | // Check for colinearity |
michael@0 | 1935 | dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); |
michael@0 | 1936 | if (dir == 0) { |
michael@0 | 1937 | LineTo(p1.x, p1.y); |
michael@0 | 1938 | return; |
michael@0 | 1939 | } |
michael@0 | 1940 | |
michael@0 | 1941 | |
michael@0 | 1942 | // XXX - Math for this code was already available from the non-azure code |
michael@0 | 1943 | // and would be well tested. Perhaps converting to bezier directly might |
michael@0 | 1944 | // be more efficient longer run. |
michael@0 | 1945 | a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); |
michael@0 | 1946 | b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); |
michael@0 | 1947 | c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); |
michael@0 | 1948 | cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); |
michael@0 | 1949 | |
michael@0 | 1950 | sinx = sqrt(1 - cosx*cosx); |
michael@0 | 1951 | d = radius / ((1 - cosx) / sinx); |
michael@0 | 1952 | |
michael@0 | 1953 | anx = (x1-p0.x) / sqrt(a2); |
michael@0 | 1954 | any = (y1-p0.y) / sqrt(a2); |
michael@0 | 1955 | bnx = (x1-x2) / sqrt(b2); |
michael@0 | 1956 | bny = (y1-y2) / sqrt(b2); |
michael@0 | 1957 | x3 = x1 - anx*d; |
michael@0 | 1958 | y3 = y1 - any*d; |
michael@0 | 1959 | x4 = x1 - bnx*d; |
michael@0 | 1960 | y4 = y1 - bny*d; |
michael@0 | 1961 | anticlockwise = (dir < 0); |
michael@0 | 1962 | cx = x3 + any*radius*(anticlockwise ? 1 : -1); |
michael@0 | 1963 | cy = y3 - anx*radius*(anticlockwise ? 1 : -1); |
michael@0 | 1964 | angle0 = atan2((y3-cy), (x3-cx)); |
michael@0 | 1965 | angle1 = atan2((y4-cy), (x4-cx)); |
michael@0 | 1966 | |
michael@0 | 1967 | |
michael@0 | 1968 | LineTo(x3, y3); |
michael@0 | 1969 | |
michael@0 | 1970 | Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); |
michael@0 | 1971 | } |
michael@0 | 1972 | |
michael@0 | 1973 | void |
michael@0 | 1974 | CanvasRenderingContext2D::Arc(double x, double y, double r, |
michael@0 | 1975 | double startAngle, double endAngle, |
michael@0 | 1976 | bool anticlockwise, ErrorResult& error) |
michael@0 | 1977 | { |
michael@0 | 1978 | if (r < 0.0) { |
michael@0 | 1979 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 1980 | return; |
michael@0 | 1981 | } |
michael@0 | 1982 | |
michael@0 | 1983 | EnsureWritablePath(); |
michael@0 | 1984 | |
michael@0 | 1985 | ArcToBezier(this, Point(x, y), Size(r, r), startAngle, endAngle, anticlockwise); |
michael@0 | 1986 | } |
michael@0 | 1987 | |
michael@0 | 1988 | void |
michael@0 | 1989 | CanvasRenderingContext2D::Rect(double x, double y, double w, double h) |
michael@0 | 1990 | { |
michael@0 | 1991 | EnsureWritablePath(); |
michael@0 | 1992 | |
michael@0 | 1993 | if (mPathBuilder) { |
michael@0 | 1994 | mPathBuilder->MoveTo(Point(x, y)); |
michael@0 | 1995 | mPathBuilder->LineTo(Point(x + w, y)); |
michael@0 | 1996 | mPathBuilder->LineTo(Point(x + w, y + h)); |
michael@0 | 1997 | mPathBuilder->LineTo(Point(x, y + h)); |
michael@0 | 1998 | mPathBuilder->Close(); |
michael@0 | 1999 | } else { |
michael@0 | 2000 | mDSPathBuilder->MoveTo(mTarget->GetTransform() * Point(x, y)); |
michael@0 | 2001 | mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y)); |
michael@0 | 2002 | mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x + w, y + h)); |
michael@0 | 2003 | mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(x, y + h)); |
michael@0 | 2004 | mDSPathBuilder->Close(); |
michael@0 | 2005 | } |
michael@0 | 2006 | } |
michael@0 | 2007 | |
michael@0 | 2008 | void |
michael@0 | 2009 | CanvasRenderingContext2D::EnsureWritablePath() |
michael@0 | 2010 | { |
michael@0 | 2011 | if (mDSPathBuilder) { |
michael@0 | 2012 | return; |
michael@0 | 2013 | } |
michael@0 | 2014 | |
michael@0 | 2015 | FillRule fillRule = CurrentState().fillRule; |
michael@0 | 2016 | |
michael@0 | 2017 | if (mPathBuilder) { |
michael@0 | 2018 | if (mPathTransformWillUpdate) { |
michael@0 | 2019 | mPath = mPathBuilder->Finish(); |
michael@0 | 2020 | mDSPathBuilder = |
michael@0 | 2021 | mPath->TransformedCopyToBuilder(mPathToDS, fillRule); |
michael@0 | 2022 | mPath = nullptr; |
michael@0 | 2023 | mPathBuilder = nullptr; |
michael@0 | 2024 | mPathTransformWillUpdate = false; |
michael@0 | 2025 | } |
michael@0 | 2026 | return; |
michael@0 | 2027 | } |
michael@0 | 2028 | |
michael@0 | 2029 | EnsureTarget(); |
michael@0 | 2030 | if (!mPath) { |
michael@0 | 2031 | NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); |
michael@0 | 2032 | mPathBuilder = mTarget->CreatePathBuilder(fillRule); |
michael@0 | 2033 | } else if (!mPathTransformWillUpdate) { |
michael@0 | 2034 | mPathBuilder = mPath->CopyToBuilder(fillRule); |
michael@0 | 2035 | } else { |
michael@0 | 2036 | mDSPathBuilder = |
michael@0 | 2037 | mPath->TransformedCopyToBuilder(mPathToDS, fillRule); |
michael@0 | 2038 | mPathTransformWillUpdate = false; |
michael@0 | 2039 | mPath = nullptr; |
michael@0 | 2040 | } |
michael@0 | 2041 | } |
michael@0 | 2042 | |
michael@0 | 2043 | void |
michael@0 | 2044 | CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& winding) |
michael@0 | 2045 | { |
michael@0 | 2046 | FillRule fillRule = CurrentState().fillRule; |
michael@0 | 2047 | if(winding == CanvasWindingRule::Evenodd) |
michael@0 | 2048 | fillRule = FillRule::FILL_EVEN_ODD; |
michael@0 | 2049 | |
michael@0 | 2050 | if (!mPath && !mPathBuilder && !mDSPathBuilder) { |
michael@0 | 2051 | EnsureTarget(); |
michael@0 | 2052 | mPathBuilder = mTarget->CreatePathBuilder(fillRule); |
michael@0 | 2053 | } |
michael@0 | 2054 | |
michael@0 | 2055 | if (mPathBuilder) { |
michael@0 | 2056 | mPath = mPathBuilder->Finish(); |
michael@0 | 2057 | mPathBuilder = nullptr; |
michael@0 | 2058 | } |
michael@0 | 2059 | |
michael@0 | 2060 | if (mPath && |
michael@0 | 2061 | mPathTransformWillUpdate) { |
michael@0 | 2062 | mDSPathBuilder = |
michael@0 | 2063 | mPath->TransformedCopyToBuilder(mPathToDS, fillRule); |
michael@0 | 2064 | mPath = nullptr; |
michael@0 | 2065 | mPathTransformWillUpdate = false; |
michael@0 | 2066 | } |
michael@0 | 2067 | |
michael@0 | 2068 | if (mDSPathBuilder) { |
michael@0 | 2069 | RefPtr<Path> dsPath; |
michael@0 | 2070 | dsPath = mDSPathBuilder->Finish(); |
michael@0 | 2071 | mDSPathBuilder = nullptr; |
michael@0 | 2072 | |
michael@0 | 2073 | Matrix inverse = mTarget->GetTransform(); |
michael@0 | 2074 | if (!inverse.Invert()) { |
michael@0 | 2075 | NS_WARNING("Could not invert transform"); |
michael@0 | 2076 | return; |
michael@0 | 2077 | } |
michael@0 | 2078 | |
michael@0 | 2079 | mPathBuilder = |
michael@0 | 2080 | dsPath->TransformedCopyToBuilder(inverse, fillRule); |
michael@0 | 2081 | mPath = mPathBuilder->Finish(); |
michael@0 | 2082 | mPathBuilder = nullptr; |
michael@0 | 2083 | } |
michael@0 | 2084 | |
michael@0 | 2085 | if (mPath && mPath->GetFillRule() != fillRule) { |
michael@0 | 2086 | mPathBuilder = mPath->CopyToBuilder(fillRule); |
michael@0 | 2087 | mPath = mPathBuilder->Finish(); |
michael@0 | 2088 | mPathBuilder = nullptr; |
michael@0 | 2089 | } |
michael@0 | 2090 | |
michael@0 | 2091 | NS_ASSERTION(mPath, "mPath should exist"); |
michael@0 | 2092 | } |
michael@0 | 2093 | |
michael@0 | 2094 | void |
michael@0 | 2095 | CanvasRenderingContext2D::TransformWillUpdate() |
michael@0 | 2096 | { |
michael@0 | 2097 | EnsureTarget(); |
michael@0 | 2098 | |
michael@0 | 2099 | // Store the matrix that would transform the current path to device |
michael@0 | 2100 | // space. |
michael@0 | 2101 | if (mPath || mPathBuilder) { |
michael@0 | 2102 | if (!mPathTransformWillUpdate) { |
michael@0 | 2103 | // If the transform has already been updated, but a device space builder |
michael@0 | 2104 | // has not been created yet mPathToDS contains the right transform to |
michael@0 | 2105 | // transform the current mPath into device space. |
michael@0 | 2106 | // We should leave it alone. |
michael@0 | 2107 | mPathToDS = mTarget->GetTransform(); |
michael@0 | 2108 | } |
michael@0 | 2109 | mPathTransformWillUpdate = true; |
michael@0 | 2110 | } |
michael@0 | 2111 | } |
michael@0 | 2112 | |
michael@0 | 2113 | // |
michael@0 | 2114 | // text |
michael@0 | 2115 | // |
michael@0 | 2116 | |
michael@0 | 2117 | /** |
michael@0 | 2118 | * Helper function for SetFont that creates a style rule for the given font. |
michael@0 | 2119 | * @param aFont The CSS font string |
michael@0 | 2120 | * @param aNode The canvas element |
michael@0 | 2121 | * @param aResult Pointer in which to place the new style rule. |
michael@0 | 2122 | * @remark Assumes all pointer arguments are non-null. |
michael@0 | 2123 | */ |
michael@0 | 2124 | static nsresult |
michael@0 | 2125 | CreateFontStyleRule(const nsAString& aFont, |
michael@0 | 2126 | nsINode* aNode, |
michael@0 | 2127 | StyleRule** aResult) |
michael@0 | 2128 | { |
michael@0 | 2129 | nsRefPtr<StyleRule> rule; |
michael@0 | 2130 | bool changed; |
michael@0 | 2131 | |
michael@0 | 2132 | nsIPrincipal* principal = aNode->NodePrincipal(); |
michael@0 | 2133 | nsIDocument* document = aNode->OwnerDoc(); |
michael@0 | 2134 | |
michael@0 | 2135 | nsIURI* docURL = document->GetDocumentURI(); |
michael@0 | 2136 | nsIURI* baseURL = document->GetDocBaseURI(); |
michael@0 | 2137 | |
michael@0 | 2138 | // Pass the CSS Loader object to the parser, to allow parser error reports |
michael@0 | 2139 | // to include the outer window ID. |
michael@0 | 2140 | nsCSSParser parser(document->CSSLoader()); |
michael@0 | 2141 | |
michael@0 | 2142 | nsresult rv = parser.ParseStyleAttribute(EmptyString(), docURL, baseURL, |
michael@0 | 2143 | principal, getter_AddRefs(rule)); |
michael@0 | 2144 | if (NS_FAILED(rv)) { |
michael@0 | 2145 | return rv; |
michael@0 | 2146 | } |
michael@0 | 2147 | |
michael@0 | 2148 | rv = parser.ParseProperty(eCSSProperty_font, aFont, docURL, baseURL, |
michael@0 | 2149 | principal, rule->GetDeclaration(), &changed, |
michael@0 | 2150 | false); |
michael@0 | 2151 | if (NS_FAILED(rv)) |
michael@0 | 2152 | return rv; |
michael@0 | 2153 | |
michael@0 | 2154 | rv = parser.ParseProperty(eCSSProperty_line_height, |
michael@0 | 2155 | NS_LITERAL_STRING("normal"), docURL, baseURL, |
michael@0 | 2156 | principal, rule->GetDeclaration(), &changed, |
michael@0 | 2157 | false); |
michael@0 | 2158 | if (NS_FAILED(rv)) { |
michael@0 | 2159 | return rv; |
michael@0 | 2160 | } |
michael@0 | 2161 | |
michael@0 | 2162 | rule->RuleMatched(); |
michael@0 | 2163 | |
michael@0 | 2164 | rule.forget(aResult); |
michael@0 | 2165 | return NS_OK; |
michael@0 | 2166 | } |
michael@0 | 2167 | |
michael@0 | 2168 | void |
michael@0 | 2169 | CanvasRenderingContext2D::SetFont(const nsAString& font, |
michael@0 | 2170 | ErrorResult& error) |
michael@0 | 2171 | { |
michael@0 | 2172 | /* |
michael@0 | 2173 | * If font is defined with relative units (e.g. ems) and the parent |
michael@0 | 2174 | * style context changes in between calls, setting the font to the |
michael@0 | 2175 | * same value as previous could result in a different computed value, |
michael@0 | 2176 | * so we cannot have the optimization where we check if the new font |
michael@0 | 2177 | * string is equal to the old one. |
michael@0 | 2178 | */ |
michael@0 | 2179 | |
michael@0 | 2180 | if (!mCanvasElement && !mDocShell) { |
michael@0 | 2181 | NS_WARNING("Canvas element must be non-null or a docshell must be provided"); |
michael@0 | 2182 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 2183 | return; |
michael@0 | 2184 | } |
michael@0 | 2185 | |
michael@0 | 2186 | nsIPresShell* presShell = GetPresShell(); |
michael@0 | 2187 | if (!presShell) { |
michael@0 | 2188 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 2189 | return; |
michael@0 | 2190 | } |
michael@0 | 2191 | nsIDocument* document = presShell->GetDocument(); |
michael@0 | 2192 | |
michael@0 | 2193 | nsRefPtr<css::StyleRule> rule; |
michael@0 | 2194 | error = CreateFontStyleRule(font, document, getter_AddRefs(rule)); |
michael@0 | 2195 | |
michael@0 | 2196 | if (error.Failed()) { |
michael@0 | 2197 | return; |
michael@0 | 2198 | } |
michael@0 | 2199 | |
michael@0 | 2200 | css::Declaration *declaration = rule->GetDeclaration(); |
michael@0 | 2201 | // The easiest way to see whether we got a syntax error or whether |
michael@0 | 2202 | // we got 'inherit' or 'initial' is to look at font-size-adjust, |
michael@0 | 2203 | // which the shorthand resets to either 'none' or |
michael@0 | 2204 | // '-moz-system-font'. |
michael@0 | 2205 | // We know the declaration is not !important, so we can use |
michael@0 | 2206 | // GetNormalBlock(). |
michael@0 | 2207 | const nsCSSValue *fsaVal = |
michael@0 | 2208 | declaration->GetNormalBlock()->ValueFor(eCSSProperty_font_size_adjust); |
michael@0 | 2209 | if (!fsaVal || (fsaVal->GetUnit() != eCSSUnit_None && |
michael@0 | 2210 | fsaVal->GetUnit() != eCSSUnit_System_Font)) { |
michael@0 | 2211 | // We got an all-property value or a syntax error. The spec says |
michael@0 | 2212 | // this value must be ignored. |
michael@0 | 2213 | return; |
michael@0 | 2214 | } |
michael@0 | 2215 | |
michael@0 | 2216 | nsTArray< nsCOMPtr<nsIStyleRule> > rules; |
michael@0 | 2217 | rules.AppendElement(rule); |
michael@0 | 2218 | |
michael@0 | 2219 | nsStyleSet* styleSet = presShell->StyleSet(); |
michael@0 | 2220 | |
michael@0 | 2221 | // have to get a parent style context for inherit-like relative |
michael@0 | 2222 | // values (2em, bolder, etc.) |
michael@0 | 2223 | nsRefPtr<nsStyleContext> parentContext; |
michael@0 | 2224 | |
michael@0 | 2225 | if (mCanvasElement && mCanvasElement->IsInDoc()) { |
michael@0 | 2226 | // inherit from the canvas element |
michael@0 | 2227 | parentContext = nsComputedDOMStyle::GetStyleContextForElement( |
michael@0 | 2228 | mCanvasElement, |
michael@0 | 2229 | nullptr, |
michael@0 | 2230 | presShell); |
michael@0 | 2231 | } else { |
michael@0 | 2232 | // otherwise inherit from default (10px sans-serif) |
michael@0 | 2233 | nsRefPtr<css::StyleRule> parentRule; |
michael@0 | 2234 | error = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"), |
michael@0 | 2235 | document, |
michael@0 | 2236 | getter_AddRefs(parentRule)); |
michael@0 | 2237 | |
michael@0 | 2238 | if (error.Failed()) { |
michael@0 | 2239 | return; |
michael@0 | 2240 | } |
michael@0 | 2241 | |
michael@0 | 2242 | nsTArray< nsCOMPtr<nsIStyleRule> > parentRules; |
michael@0 | 2243 | parentRules.AppendElement(parentRule); |
michael@0 | 2244 | parentContext = styleSet->ResolveStyleForRules(nullptr, parentRules); |
michael@0 | 2245 | } |
michael@0 | 2246 | |
michael@0 | 2247 | if (!parentContext) { |
michael@0 | 2248 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 2249 | return; |
michael@0 | 2250 | } |
michael@0 | 2251 | |
michael@0 | 2252 | // add a rule to prevent text zoom from affecting the style |
michael@0 | 2253 | rules.AppendElement(new nsDisableTextZoomStyleRule); |
michael@0 | 2254 | |
michael@0 | 2255 | nsRefPtr<nsStyleContext> sc = |
michael@0 | 2256 | styleSet->ResolveStyleForRules(parentContext, rules); |
michael@0 | 2257 | if (!sc) { |
michael@0 | 2258 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 2259 | return; |
michael@0 | 2260 | } |
michael@0 | 2261 | |
michael@0 | 2262 | const nsStyleFont* fontStyle = sc->StyleFont(); |
michael@0 | 2263 | |
michael@0 | 2264 | NS_ASSERTION(fontStyle, "Could not obtain font style"); |
michael@0 | 2265 | |
michael@0 | 2266 | nsIAtom* language = sc->StyleFont()->mLanguage; |
michael@0 | 2267 | if (!language) { |
michael@0 | 2268 | language = presShell->GetPresContext()->GetLanguageFromCharset(); |
michael@0 | 2269 | } |
michael@0 | 2270 | |
michael@0 | 2271 | // use CSS pixels instead of dev pixels to avoid being affected by page zoom |
michael@0 | 2272 | const uint32_t aupcp = nsPresContext::AppUnitsPerCSSPixel(); |
michael@0 | 2273 | |
michael@0 | 2274 | bool printerFont = (presShell->GetPresContext()->Type() == nsPresContext::eContext_PrintPreview || |
michael@0 | 2275 | presShell->GetPresContext()->Type() == nsPresContext::eContext_Print); |
michael@0 | 2276 | |
michael@0 | 2277 | // Purposely ignore the font size that respects the user's minimum |
michael@0 | 2278 | // font preference (fontStyle->mFont.size) in favor of the computed |
michael@0 | 2279 | // size (fontStyle->mSize). See |
michael@0 | 2280 | // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. |
michael@0 | 2281 | MOZ_ASSERT(!fontStyle->mAllowZoom, |
michael@0 | 2282 | "expected text zoom to be disabled on this nsStyleFont"); |
michael@0 | 2283 | gfxFontStyle style(fontStyle->mFont.style, |
michael@0 | 2284 | fontStyle->mFont.weight, |
michael@0 | 2285 | fontStyle->mFont.stretch, |
michael@0 | 2286 | NSAppUnitsToFloatPixels(fontStyle->mSize, float(aupcp)), |
michael@0 | 2287 | language, |
michael@0 | 2288 | fontStyle->mFont.sizeAdjust, |
michael@0 | 2289 | fontStyle->mFont.systemFont, |
michael@0 | 2290 | printerFont, |
michael@0 | 2291 | fontStyle->mFont.languageOverride); |
michael@0 | 2292 | |
michael@0 | 2293 | fontStyle->mFont.AddFontFeaturesToStyle(&style); |
michael@0 | 2294 | |
michael@0 | 2295 | nsPresContext *c = presShell->GetPresContext(); |
michael@0 | 2296 | CurrentState().fontGroup = |
michael@0 | 2297 | gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, |
michael@0 | 2298 | &style, |
michael@0 | 2299 | c->GetUserFontSet()); |
michael@0 | 2300 | NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); |
michael@0 | 2301 | CurrentState().fontGroup->SetTextPerfMetrics(c->GetTextPerfMetrics()); |
michael@0 | 2302 | |
michael@0 | 2303 | // The font getter is required to be reserialized based on what we |
michael@0 | 2304 | // parsed (including having line-height removed). (Older drafts of |
michael@0 | 2305 | // the spec required font sizes be converted to pixels, but that no |
michael@0 | 2306 | // longer seems to be required.) |
michael@0 | 2307 | declaration->GetValue(eCSSProperty_font, CurrentState().font); |
michael@0 | 2308 | } |
michael@0 | 2309 | |
michael@0 | 2310 | void |
michael@0 | 2311 | CanvasRenderingContext2D::SetTextAlign(const nsAString& ta) |
michael@0 | 2312 | { |
michael@0 | 2313 | if (ta.EqualsLiteral("start")) |
michael@0 | 2314 | CurrentState().textAlign = TextAlign::START; |
michael@0 | 2315 | else if (ta.EqualsLiteral("end")) |
michael@0 | 2316 | CurrentState().textAlign = TextAlign::END; |
michael@0 | 2317 | else if (ta.EqualsLiteral("left")) |
michael@0 | 2318 | CurrentState().textAlign = TextAlign::LEFT; |
michael@0 | 2319 | else if (ta.EqualsLiteral("right")) |
michael@0 | 2320 | CurrentState().textAlign = TextAlign::RIGHT; |
michael@0 | 2321 | else if (ta.EqualsLiteral("center")) |
michael@0 | 2322 | CurrentState().textAlign = TextAlign::CENTER; |
michael@0 | 2323 | } |
michael@0 | 2324 | |
michael@0 | 2325 | void |
michael@0 | 2326 | CanvasRenderingContext2D::GetTextAlign(nsAString& ta) |
michael@0 | 2327 | { |
michael@0 | 2328 | switch (CurrentState().textAlign) |
michael@0 | 2329 | { |
michael@0 | 2330 | case TextAlign::START: |
michael@0 | 2331 | ta.AssignLiteral("start"); |
michael@0 | 2332 | break; |
michael@0 | 2333 | case TextAlign::END: |
michael@0 | 2334 | ta.AssignLiteral("end"); |
michael@0 | 2335 | break; |
michael@0 | 2336 | case TextAlign::LEFT: |
michael@0 | 2337 | ta.AssignLiteral("left"); |
michael@0 | 2338 | break; |
michael@0 | 2339 | case TextAlign::RIGHT: |
michael@0 | 2340 | ta.AssignLiteral("right"); |
michael@0 | 2341 | break; |
michael@0 | 2342 | case TextAlign::CENTER: |
michael@0 | 2343 | ta.AssignLiteral("center"); |
michael@0 | 2344 | break; |
michael@0 | 2345 | } |
michael@0 | 2346 | } |
michael@0 | 2347 | |
michael@0 | 2348 | void |
michael@0 | 2349 | CanvasRenderingContext2D::SetTextBaseline(const nsAString& tb) |
michael@0 | 2350 | { |
michael@0 | 2351 | if (tb.EqualsLiteral("top")) |
michael@0 | 2352 | CurrentState().textBaseline = TextBaseline::TOP; |
michael@0 | 2353 | else if (tb.EqualsLiteral("hanging")) |
michael@0 | 2354 | CurrentState().textBaseline = TextBaseline::HANGING; |
michael@0 | 2355 | else if (tb.EqualsLiteral("middle")) |
michael@0 | 2356 | CurrentState().textBaseline = TextBaseline::MIDDLE; |
michael@0 | 2357 | else if (tb.EqualsLiteral("alphabetic")) |
michael@0 | 2358 | CurrentState().textBaseline = TextBaseline::ALPHABETIC; |
michael@0 | 2359 | else if (tb.EqualsLiteral("ideographic")) |
michael@0 | 2360 | CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC; |
michael@0 | 2361 | else if (tb.EqualsLiteral("bottom")) |
michael@0 | 2362 | CurrentState().textBaseline = TextBaseline::BOTTOM; |
michael@0 | 2363 | } |
michael@0 | 2364 | |
michael@0 | 2365 | void |
michael@0 | 2366 | CanvasRenderingContext2D::GetTextBaseline(nsAString& tb) |
michael@0 | 2367 | { |
michael@0 | 2368 | switch (CurrentState().textBaseline) |
michael@0 | 2369 | { |
michael@0 | 2370 | case TextBaseline::TOP: |
michael@0 | 2371 | tb.AssignLiteral("top"); |
michael@0 | 2372 | break; |
michael@0 | 2373 | case TextBaseline::HANGING: |
michael@0 | 2374 | tb.AssignLiteral("hanging"); |
michael@0 | 2375 | break; |
michael@0 | 2376 | case TextBaseline::MIDDLE: |
michael@0 | 2377 | tb.AssignLiteral("middle"); |
michael@0 | 2378 | break; |
michael@0 | 2379 | case TextBaseline::ALPHABETIC: |
michael@0 | 2380 | tb.AssignLiteral("alphabetic"); |
michael@0 | 2381 | break; |
michael@0 | 2382 | case TextBaseline::IDEOGRAPHIC: |
michael@0 | 2383 | tb.AssignLiteral("ideographic"); |
michael@0 | 2384 | break; |
michael@0 | 2385 | case TextBaseline::BOTTOM: |
michael@0 | 2386 | tb.AssignLiteral("bottom"); |
michael@0 | 2387 | break; |
michael@0 | 2388 | } |
michael@0 | 2389 | } |
michael@0 | 2390 | |
michael@0 | 2391 | /* |
michael@0 | 2392 | * Helper function that replaces the whitespace characters in a string |
michael@0 | 2393 | * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, |
michael@0 | 2394 | * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE |
michael@0 | 2395 | * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). |
michael@0 | 2396 | * @param str The string whose whitespace characters to replace. |
michael@0 | 2397 | */ |
michael@0 | 2398 | static inline void |
michael@0 | 2399 | TextReplaceWhitespaceCharacters(nsAutoString& str) |
michael@0 | 2400 | { |
michael@0 | 2401 | str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' ')); |
michael@0 | 2402 | } |
michael@0 | 2403 | |
michael@0 | 2404 | void |
michael@0 | 2405 | CanvasRenderingContext2D::FillText(const nsAString& text, double x, |
michael@0 | 2406 | double y, |
michael@0 | 2407 | const Optional<double>& maxWidth, |
michael@0 | 2408 | ErrorResult& error) |
michael@0 | 2409 | { |
michael@0 | 2410 | error = DrawOrMeasureText(text, x, y, maxWidth, TextDrawOperation::FILL, nullptr); |
michael@0 | 2411 | } |
michael@0 | 2412 | |
michael@0 | 2413 | void |
michael@0 | 2414 | CanvasRenderingContext2D::StrokeText(const nsAString& text, double x, |
michael@0 | 2415 | double y, |
michael@0 | 2416 | const Optional<double>& maxWidth, |
michael@0 | 2417 | ErrorResult& error) |
michael@0 | 2418 | { |
michael@0 | 2419 | error = DrawOrMeasureText(text, x, y, maxWidth, TextDrawOperation::STROKE, nullptr); |
michael@0 | 2420 | } |
michael@0 | 2421 | |
michael@0 | 2422 | TextMetrics* |
michael@0 | 2423 | CanvasRenderingContext2D::MeasureText(const nsAString& rawText, |
michael@0 | 2424 | ErrorResult& error) |
michael@0 | 2425 | { |
michael@0 | 2426 | float width; |
michael@0 | 2427 | Optional<double> maxWidth; |
michael@0 | 2428 | error = DrawOrMeasureText(rawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, &width); |
michael@0 | 2429 | if (error.Failed()) { |
michael@0 | 2430 | return nullptr; |
michael@0 | 2431 | } |
michael@0 | 2432 | |
michael@0 | 2433 | return new TextMetrics(width); |
michael@0 | 2434 | } |
michael@0 | 2435 | |
michael@0 | 2436 | void |
michael@0 | 2437 | CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& options, ErrorResult& error) |
michael@0 | 2438 | { |
michael@0 | 2439 | // remove old hit region first |
michael@0 | 2440 | RemoveHitRegion(options.mId); |
michael@0 | 2441 | |
michael@0 | 2442 | // for now, we require a fallback element |
michael@0 | 2443 | if (options.mControl == NULL) { |
michael@0 | 2444 | error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
michael@0 | 2445 | return; |
michael@0 | 2446 | } |
michael@0 | 2447 | |
michael@0 | 2448 | // check if the control is a descendant of our canvas |
michael@0 | 2449 | HTMLCanvasElement* canvas = GetCanvas(); |
michael@0 | 2450 | bool isDescendant = true; |
michael@0 | 2451 | if (!canvas || !nsContentUtils::ContentIsDescendantOf(options.mControl, canvas)) { |
michael@0 | 2452 | isDescendant = false; |
michael@0 | 2453 | } |
michael@0 | 2454 | |
michael@0 | 2455 | // check if the path is valid |
michael@0 | 2456 | EnsureUserSpacePath(CanvasWindingRule::Nonzero); |
michael@0 | 2457 | if(!mPath) { |
michael@0 | 2458 | error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
michael@0 | 2459 | return; |
michael@0 | 2460 | } |
michael@0 | 2461 | |
michael@0 | 2462 | // get the bounds of the current path. They are relative to the canvas |
michael@0 | 2463 | mgfx::Rect bounds(mPath->GetBounds(mTarget->GetTransform())); |
michael@0 | 2464 | if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) { |
michael@0 | 2465 | // The specified region has no pixels. |
michael@0 | 2466 | error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
michael@0 | 2467 | return; |
michael@0 | 2468 | } |
michael@0 | 2469 | |
michael@0 | 2470 | #ifdef ACCESSIBILITY |
michael@0 | 2471 | if (isDescendant) { |
michael@0 | 2472 | nsRect* nsBounds = new nsRect(); |
michael@0 | 2473 | gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height); |
michael@0 | 2474 | *nsBounds = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel()); |
michael@0 | 2475 | options.mControl->DeleteProperty(nsGkAtoms::hitregion); |
michael@0 | 2476 | options.mControl->SetProperty(nsGkAtoms::hitregion, nsBounds, |
michael@0 | 2477 | nsINode::DeleteProperty<nsRect>); |
michael@0 | 2478 | } |
michael@0 | 2479 | #endif |
michael@0 | 2480 | |
michael@0 | 2481 | // finally, add the region to the list if it has an ID |
michael@0 | 2482 | if (options.mId.Length() != 0) { |
michael@0 | 2483 | mHitRegionsOptions.PutEntry(options.mId)->mElement = options.mControl; |
michael@0 | 2484 | } |
michael@0 | 2485 | } |
michael@0 | 2486 | |
michael@0 | 2487 | void |
michael@0 | 2488 | CanvasRenderingContext2D::RemoveHitRegion(const nsAString& id) |
michael@0 | 2489 | { |
michael@0 | 2490 | RegionInfo* info = mHitRegionsOptions.GetEntry(id); |
michael@0 | 2491 | if (!info) { |
michael@0 | 2492 | return; |
michael@0 | 2493 | } |
michael@0 | 2494 | |
michael@0 | 2495 | #ifdef ACCESSIBILITY |
michael@0 | 2496 | info->mElement->DeleteProperty(nsGkAtoms::hitregion); |
michael@0 | 2497 | #endif |
michael@0 | 2498 | mHitRegionsOptions.RemoveEntry(id); |
michael@0 | 2499 | } |
michael@0 | 2500 | |
michael@0 | 2501 | /** |
michael@0 | 2502 | * Used for nsBidiPresUtils::ProcessText |
michael@0 | 2503 | */ |
michael@0 | 2504 | struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor |
michael@0 | 2505 | { |
michael@0 | 2506 | typedef CanvasRenderingContext2D::ContextState ContextState; |
michael@0 | 2507 | |
michael@0 | 2508 | virtual void SetText(const char16_t* text, int32_t length, nsBidiDirection direction) |
michael@0 | 2509 | { |
michael@0 | 2510 | mFontgrp->UpdateFontList(); // ensure user font generation is current |
michael@0 | 2511 | mTextRun = mFontgrp->MakeTextRun(text, |
michael@0 | 2512 | length, |
michael@0 | 2513 | mThebes, |
michael@0 | 2514 | mAppUnitsPerDevPixel, |
michael@0 | 2515 | direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0); |
michael@0 | 2516 | } |
michael@0 | 2517 | |
michael@0 | 2518 | virtual nscoord GetWidth() |
michael@0 | 2519 | { |
michael@0 | 2520 | gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0, |
michael@0 | 2521 | mTextRun->GetLength(), |
michael@0 | 2522 | mDoMeasureBoundingBox ? |
michael@0 | 2523 | gfxFont::TIGHT_INK_EXTENTS : |
michael@0 | 2524 | gfxFont::LOOSE_INK_EXTENTS, |
michael@0 | 2525 | mThebes, |
michael@0 | 2526 | nullptr); |
michael@0 | 2527 | |
michael@0 | 2528 | // this only measures the height; the total width is gotten from the |
michael@0 | 2529 | // the return value of ProcessText. |
michael@0 | 2530 | if (mDoMeasureBoundingBox) { |
michael@0 | 2531 | textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); |
michael@0 | 2532 | mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); |
michael@0 | 2533 | } |
michael@0 | 2534 | |
michael@0 | 2535 | return NSToCoordRound(textRunMetrics.mAdvanceWidth); |
michael@0 | 2536 | } |
michael@0 | 2537 | |
michael@0 | 2538 | virtual void DrawText(nscoord xOffset, nscoord width) |
michael@0 | 2539 | { |
michael@0 | 2540 | gfxPoint point = mPt; |
michael@0 | 2541 | point.x += xOffset; |
michael@0 | 2542 | |
michael@0 | 2543 | // offset is given in terms of left side of string |
michael@0 | 2544 | if (mTextRun->IsRightToLeft()) { |
michael@0 | 2545 | // Bug 581092 - don't use rounded pixel width to advance to |
michael@0 | 2546 | // right-hand end of run, because this will cause different |
michael@0 | 2547 | // glyph positioning for LTR vs RTL drawing of the same |
michael@0 | 2548 | // glyph string on OS X and DWrite where textrun widths may |
michael@0 | 2549 | // involve fractional pixels. |
michael@0 | 2550 | gfxTextRun::Metrics textRunMetrics = |
michael@0 | 2551 | mTextRun->MeasureText(0, |
michael@0 | 2552 | mTextRun->GetLength(), |
michael@0 | 2553 | mDoMeasureBoundingBox ? |
michael@0 | 2554 | gfxFont::TIGHT_INK_EXTENTS : |
michael@0 | 2555 | gfxFont::LOOSE_INK_EXTENTS, |
michael@0 | 2556 | mThebes, |
michael@0 | 2557 | nullptr); |
michael@0 | 2558 | point.x += textRunMetrics.mAdvanceWidth; |
michael@0 | 2559 | // old code was: |
michael@0 | 2560 | // point.x += width * mAppUnitsPerDevPixel; |
michael@0 | 2561 | // TODO: restore this if/when we move to fractional coords |
michael@0 | 2562 | // throughout the text layout process |
michael@0 | 2563 | } |
michael@0 | 2564 | |
michael@0 | 2565 | uint32_t numRuns; |
michael@0 | 2566 | const gfxTextRun::GlyphRun *runs = mTextRun->GetGlyphRuns(&numRuns); |
michael@0 | 2567 | const int32_t appUnitsPerDevUnit = mAppUnitsPerDevPixel; |
michael@0 | 2568 | const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); |
michael@0 | 2569 | Point baselineOrigin = |
michael@0 | 2570 | Point(point.x * devUnitsPerAppUnit, point.y * devUnitsPerAppUnit); |
michael@0 | 2571 | |
michael@0 | 2572 | float advanceSum = 0; |
michael@0 | 2573 | |
michael@0 | 2574 | mCtx->EnsureTarget(); |
michael@0 | 2575 | for (uint32_t c = 0; c < numRuns; c++) { |
michael@0 | 2576 | gfxFont *font = runs[c].mFont; |
michael@0 | 2577 | uint32_t endRun = 0; |
michael@0 | 2578 | if (c + 1 < numRuns) { |
michael@0 | 2579 | endRun = runs[c + 1].mCharacterOffset; |
michael@0 | 2580 | } else { |
michael@0 | 2581 | endRun = mTextRun->GetLength(); |
michael@0 | 2582 | } |
michael@0 | 2583 | |
michael@0 | 2584 | const gfxTextRun::CompressedGlyph *glyphs = mTextRun->GetCharacterGlyphs(); |
michael@0 | 2585 | |
michael@0 | 2586 | RefPtr<ScaledFont> scaledFont = |
michael@0 | 2587 | gfxPlatform::GetPlatform()->GetScaledFontForFont(mCtx->mTarget, font); |
michael@0 | 2588 | |
michael@0 | 2589 | if (!scaledFont) { |
michael@0 | 2590 | // This can occur when something switched DirectWrite off. |
michael@0 | 2591 | return; |
michael@0 | 2592 | } |
michael@0 | 2593 | |
michael@0 | 2594 | RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions(); |
michael@0 | 2595 | |
michael@0 | 2596 | GlyphBuffer buffer; |
michael@0 | 2597 | |
michael@0 | 2598 | std::vector<Glyph> glyphBuf; |
michael@0 | 2599 | |
michael@0 | 2600 | for (uint32_t i = runs[c].mCharacterOffset; i < endRun; i++) { |
michael@0 | 2601 | Glyph newGlyph; |
michael@0 | 2602 | if (glyphs[i].IsSimpleGlyph()) { |
michael@0 | 2603 | newGlyph.mIndex = glyphs[i].GetSimpleGlyph(); |
michael@0 | 2604 | if (mTextRun->IsRightToLeft()) { |
michael@0 | 2605 | newGlyph.mPosition.x = baselineOrigin.x - advanceSum - |
michael@0 | 2606 | glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; |
michael@0 | 2607 | } else { |
michael@0 | 2608 | newGlyph.mPosition.x = baselineOrigin.x + advanceSum; |
michael@0 | 2609 | } |
michael@0 | 2610 | newGlyph.mPosition.y = baselineOrigin.y; |
michael@0 | 2611 | advanceSum += glyphs[i].GetSimpleAdvance() * devUnitsPerAppUnit; |
michael@0 | 2612 | glyphBuf.push_back(newGlyph); |
michael@0 | 2613 | continue; |
michael@0 | 2614 | } |
michael@0 | 2615 | |
michael@0 | 2616 | if (!glyphs[i].GetGlyphCount()) { |
michael@0 | 2617 | continue; |
michael@0 | 2618 | } |
michael@0 | 2619 | |
michael@0 | 2620 | gfxTextRun::DetailedGlyph *detailedGlyphs = |
michael@0 | 2621 | mTextRun->GetDetailedGlyphs(i); |
michael@0 | 2622 | |
michael@0 | 2623 | if (glyphs[i].IsMissing()) { |
michael@0 | 2624 | newGlyph.mIndex = 0; |
michael@0 | 2625 | if (mTextRun->IsRightToLeft()) { |
michael@0 | 2626 | newGlyph.mPosition.x = baselineOrigin.x - advanceSum - |
michael@0 | 2627 | detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; |
michael@0 | 2628 | } else { |
michael@0 | 2629 | newGlyph.mPosition.x = baselineOrigin.x + advanceSum; |
michael@0 | 2630 | } |
michael@0 | 2631 | newGlyph.mPosition.y = baselineOrigin.y; |
michael@0 | 2632 | advanceSum += detailedGlyphs[0].mAdvance * devUnitsPerAppUnit; |
michael@0 | 2633 | glyphBuf.push_back(newGlyph); |
michael@0 | 2634 | continue; |
michael@0 | 2635 | } |
michael@0 | 2636 | |
michael@0 | 2637 | for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++) { |
michael@0 | 2638 | newGlyph.mIndex = detailedGlyphs[c].mGlyphID; |
michael@0 | 2639 | if (mTextRun->IsRightToLeft()) { |
michael@0 | 2640 | newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit - |
michael@0 | 2641 | advanceSum - detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; |
michael@0 | 2642 | } else { |
michael@0 | 2643 | newGlyph.mPosition.x = baselineOrigin.x + detailedGlyphs[c].mXOffset * devUnitsPerAppUnit + advanceSum; |
michael@0 | 2644 | } |
michael@0 | 2645 | newGlyph.mPosition.y = baselineOrigin.y + detailedGlyphs[c].mYOffset * devUnitsPerAppUnit; |
michael@0 | 2646 | glyphBuf.push_back(newGlyph); |
michael@0 | 2647 | advanceSum += detailedGlyphs[c].mAdvance * devUnitsPerAppUnit; |
michael@0 | 2648 | } |
michael@0 | 2649 | } |
michael@0 | 2650 | |
michael@0 | 2651 | if (!glyphBuf.size()) { |
michael@0 | 2652 | // This may happen for glyph runs for a 0 size font. |
michael@0 | 2653 | continue; |
michael@0 | 2654 | } |
michael@0 | 2655 | |
michael@0 | 2656 | buffer.mGlyphs = &glyphBuf.front(); |
michael@0 | 2657 | buffer.mNumGlyphs = glyphBuf.size(); |
michael@0 | 2658 | |
michael@0 | 2659 | Rect bounds = mCtx->mTarget->GetTransform(). |
michael@0 | 2660 | TransformBounds(Rect(mBoundingBox.x, mBoundingBox.y, |
michael@0 | 2661 | mBoundingBox.width, mBoundingBox.height)); |
michael@0 | 2662 | if (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) { |
michael@0 | 2663 | AdjustedTarget(mCtx, &bounds)-> |
michael@0 | 2664 | FillGlyphs(scaledFont, buffer, |
michael@0 | 2665 | CanvasGeneralPattern(). |
michael@0 | 2666 | ForStyle(mCtx, CanvasRenderingContext2D::Style::FILL, mCtx->mTarget), |
michael@0 | 2667 | DrawOptions(mState->globalAlpha, mCtx->UsedOperation()), |
michael@0 | 2668 | renderingOptions); |
michael@0 | 2669 | } else if (mOp == CanvasRenderingContext2D::TextDrawOperation::STROKE) { |
michael@0 | 2670 | // stroke glyphs one at a time to avoid poor CoreGraphics performance |
michael@0 | 2671 | // when stroking a path with a very large number of points |
michael@0 | 2672 | buffer.mGlyphs = &glyphBuf.front(); |
michael@0 | 2673 | buffer.mNumGlyphs = 1; |
michael@0 | 2674 | const ContextState& state = *mState; |
michael@0 | 2675 | AdjustedTarget target(mCtx, &bounds); |
michael@0 | 2676 | const StrokeOptions strokeOpts(state.lineWidth, state.lineJoin, |
michael@0 | 2677 | state.lineCap, state.miterLimit, |
michael@0 | 2678 | state.dash.Length(), |
michael@0 | 2679 | state.dash.Elements(), |
michael@0 | 2680 | state.dashOffset); |
michael@0 | 2681 | CanvasGeneralPattern cgp; |
michael@0 | 2682 | const Pattern& patForStyle |
michael@0 | 2683 | (cgp.ForStyle(mCtx, CanvasRenderingContext2D::Style::STROKE, mCtx->mTarget)); |
michael@0 | 2684 | const DrawOptions drawOpts(state.globalAlpha, mCtx->UsedOperation()); |
michael@0 | 2685 | |
michael@0 | 2686 | for (unsigned i = glyphBuf.size(); i > 0; --i) { |
michael@0 | 2687 | RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget); |
michael@0 | 2688 | target->Stroke(path, patForStyle, strokeOpts, drawOpts); |
michael@0 | 2689 | buffer.mGlyphs++; |
michael@0 | 2690 | } |
michael@0 | 2691 | } |
michael@0 | 2692 | } |
michael@0 | 2693 | } |
michael@0 | 2694 | |
michael@0 | 2695 | // current text run |
michael@0 | 2696 | nsAutoPtr<gfxTextRun> mTextRun; |
michael@0 | 2697 | |
michael@0 | 2698 | // pointer to a screen reference context used to measure text and such |
michael@0 | 2699 | nsRefPtr<gfxContext> mThebes; |
michael@0 | 2700 | |
michael@0 | 2701 | // Pointer to the draw target we should fill our text to |
michael@0 | 2702 | CanvasRenderingContext2D *mCtx; |
michael@0 | 2703 | |
michael@0 | 2704 | // position of the left side of the string, alphabetic baseline |
michael@0 | 2705 | gfxPoint mPt; |
michael@0 | 2706 | |
michael@0 | 2707 | // current font |
michael@0 | 2708 | gfxFontGroup* mFontgrp; |
michael@0 | 2709 | |
michael@0 | 2710 | // dev pixel conversion factor |
michael@0 | 2711 | int32_t mAppUnitsPerDevPixel; |
michael@0 | 2712 | |
michael@0 | 2713 | // operation (fill or stroke) |
michael@0 | 2714 | CanvasRenderingContext2D::TextDrawOperation mOp; |
michael@0 | 2715 | |
michael@0 | 2716 | // context state |
michael@0 | 2717 | ContextState *mState; |
michael@0 | 2718 | |
michael@0 | 2719 | // union of bounding boxes of all runs, needed for shadows |
michael@0 | 2720 | gfxRect mBoundingBox; |
michael@0 | 2721 | |
michael@0 | 2722 | // true iff the bounding box should be measured |
michael@0 | 2723 | bool mDoMeasureBoundingBox; |
michael@0 | 2724 | }; |
michael@0 | 2725 | |
michael@0 | 2726 | nsresult |
michael@0 | 2727 | CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, |
michael@0 | 2728 | float aX, |
michael@0 | 2729 | float aY, |
michael@0 | 2730 | const Optional<double>& aMaxWidth, |
michael@0 | 2731 | TextDrawOperation aOp, |
michael@0 | 2732 | float* aWidth) |
michael@0 | 2733 | { |
michael@0 | 2734 | nsresult rv; |
michael@0 | 2735 | |
michael@0 | 2736 | // spec isn't clear on what should happen if aMaxWidth <= 0, so |
michael@0 | 2737 | // treat it as an invalid argument |
michael@0 | 2738 | // technically, 0 should be an invalid value as well, but 0 is the default |
michael@0 | 2739 | // arg, and there is no way to tell if the default was used |
michael@0 | 2740 | if (aMaxWidth.WasPassed() && aMaxWidth.Value() < 0) |
michael@0 | 2741 | return NS_ERROR_INVALID_ARG; |
michael@0 | 2742 | |
michael@0 | 2743 | if (!mCanvasElement && !mDocShell) { |
michael@0 | 2744 | NS_WARNING("Canvas element must be non-null or a docshell must be provided"); |
michael@0 | 2745 | return NS_ERROR_FAILURE; |
michael@0 | 2746 | } |
michael@0 | 2747 | |
michael@0 | 2748 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
michael@0 | 2749 | if (!presShell) |
michael@0 | 2750 | return NS_ERROR_FAILURE; |
michael@0 | 2751 | |
michael@0 | 2752 | nsIDocument* document = presShell->GetDocument(); |
michael@0 | 2753 | |
michael@0 | 2754 | // replace all the whitespace characters with U+0020 SPACE |
michael@0 | 2755 | nsAutoString textToDraw(aRawText); |
michael@0 | 2756 | TextReplaceWhitespaceCharacters(textToDraw); |
michael@0 | 2757 | |
michael@0 | 2758 | // for now, default to ltr if not in doc |
michael@0 | 2759 | bool isRTL = false; |
michael@0 | 2760 | |
michael@0 | 2761 | if (mCanvasElement && mCanvasElement->IsInDoc()) { |
michael@0 | 2762 | // try to find the closest context |
michael@0 | 2763 | nsRefPtr<nsStyleContext> canvasStyle = |
michael@0 | 2764 | nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement, |
michael@0 | 2765 | nullptr, |
michael@0 | 2766 | presShell); |
michael@0 | 2767 | if (!canvasStyle) { |
michael@0 | 2768 | return NS_ERROR_FAILURE; |
michael@0 | 2769 | } |
michael@0 | 2770 | |
michael@0 | 2771 | isRTL = canvasStyle->StyleVisibility()->mDirection == |
michael@0 | 2772 | NS_STYLE_DIRECTION_RTL; |
michael@0 | 2773 | } else { |
michael@0 | 2774 | isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; |
michael@0 | 2775 | } |
michael@0 | 2776 | |
michael@0 | 2777 | gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); |
michael@0 | 2778 | NS_ASSERTION(currentFontStyle, "font group is null"); |
michael@0 | 2779 | |
michael@0 | 2780 | // ensure user font set is up to date |
michael@0 | 2781 | currentFontStyle-> |
michael@0 | 2782 | SetUserFontSet(presShell->GetPresContext()->GetUserFontSet()); |
michael@0 | 2783 | |
michael@0 | 2784 | if (currentFontStyle->GetStyle()->size == 0.0F) { |
michael@0 | 2785 | if (aWidth) { |
michael@0 | 2786 | *aWidth = 0; |
michael@0 | 2787 | } |
michael@0 | 2788 | return NS_OK; |
michael@0 | 2789 | } |
michael@0 | 2790 | |
michael@0 | 2791 | const ContextState &state = CurrentState(); |
michael@0 | 2792 | |
michael@0 | 2793 | // This is only needed to know if we can know the drawing bounding box easily. |
michael@0 | 2794 | bool doDrawShadow = NeedToDrawShadow(); |
michael@0 | 2795 | |
michael@0 | 2796 | CanvasBidiProcessor processor; |
michael@0 | 2797 | |
michael@0 | 2798 | GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); |
michael@0 | 2799 | processor.mPt = gfxPoint(aX, aY); |
michael@0 | 2800 | processor.mThebes = |
michael@0 | 2801 | new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()); |
michael@0 | 2802 | |
michael@0 | 2803 | // If we don't have a target then we don't have a transform. A target won't |
michael@0 | 2804 | // be needed in the case where we're measuring the text size. This allows |
michael@0 | 2805 | // to avoid creating a target if it's only being used to measure text sizes. |
michael@0 | 2806 | if (mTarget) { |
michael@0 | 2807 | Matrix matrix = mTarget->GetTransform(); |
michael@0 | 2808 | processor.mThebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32)); |
michael@0 | 2809 | } |
michael@0 | 2810 | processor.mCtx = this; |
michael@0 | 2811 | processor.mOp = aOp; |
michael@0 | 2812 | processor.mBoundingBox = gfxRect(0, 0, 0, 0); |
michael@0 | 2813 | processor.mDoMeasureBoundingBox = doDrawShadow || !mIsEntireFrameInvalid; |
michael@0 | 2814 | processor.mState = &CurrentState(); |
michael@0 | 2815 | processor.mFontgrp = currentFontStyle; |
michael@0 | 2816 | |
michael@0 | 2817 | nscoord totalWidthCoord; |
michael@0 | 2818 | |
michael@0 | 2819 | // calls bidi algo twice since it needs the full text width and the |
michael@0 | 2820 | // bounding boxes before rendering anything |
michael@0 | 2821 | nsBidi bidiEngine; |
michael@0 | 2822 | rv = nsBidiPresUtils::ProcessText(textToDraw.get(), |
michael@0 | 2823 | textToDraw.Length(), |
michael@0 | 2824 | isRTL ? NSBIDI_RTL : NSBIDI_LTR, |
michael@0 | 2825 | presShell->GetPresContext(), |
michael@0 | 2826 | processor, |
michael@0 | 2827 | nsBidiPresUtils::MODE_MEASURE, |
michael@0 | 2828 | nullptr, |
michael@0 | 2829 | 0, |
michael@0 | 2830 | &totalWidthCoord, |
michael@0 | 2831 | &bidiEngine); |
michael@0 | 2832 | if (NS_FAILED(rv)) { |
michael@0 | 2833 | return rv; |
michael@0 | 2834 | } |
michael@0 | 2835 | |
michael@0 | 2836 | float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; |
michael@0 | 2837 | if (aWidth) { |
michael@0 | 2838 | *aWidth = totalWidth; |
michael@0 | 2839 | } |
michael@0 | 2840 | |
michael@0 | 2841 | // if only measuring, don't need to do any more work |
michael@0 | 2842 | if (aOp==TextDrawOperation::MEASURE) { |
michael@0 | 2843 | return NS_OK; |
michael@0 | 2844 | } |
michael@0 | 2845 | |
michael@0 | 2846 | // offset pt.x based on text align |
michael@0 | 2847 | gfxFloat anchorX; |
michael@0 | 2848 | |
michael@0 | 2849 | if (state.textAlign == TextAlign::CENTER) { |
michael@0 | 2850 | anchorX = .5; |
michael@0 | 2851 | } else if (state.textAlign == TextAlign::LEFT || |
michael@0 | 2852 | (!isRTL && state.textAlign == TextAlign::START) || |
michael@0 | 2853 | (isRTL && state.textAlign == TextAlign::END)) { |
michael@0 | 2854 | anchorX = 0; |
michael@0 | 2855 | } else { |
michael@0 | 2856 | anchorX = 1; |
michael@0 | 2857 | } |
michael@0 | 2858 | |
michael@0 | 2859 | processor.mPt.x -= anchorX * totalWidth; |
michael@0 | 2860 | |
michael@0 | 2861 | // offset pt.y based on text baseline |
michael@0 | 2862 | processor.mFontgrp->UpdateFontList(); // ensure user font generation is current |
michael@0 | 2863 | NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts"); |
michael@0 | 2864 | const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics(); |
michael@0 | 2865 | |
michael@0 | 2866 | gfxFloat anchorY; |
michael@0 | 2867 | |
michael@0 | 2868 | switch (state.textBaseline) |
michael@0 | 2869 | { |
michael@0 | 2870 | case TextBaseline::HANGING: |
michael@0 | 2871 | // fall through; best we can do with the information available |
michael@0 | 2872 | case TextBaseline::TOP: |
michael@0 | 2873 | anchorY = fontMetrics.emAscent; |
michael@0 | 2874 | break; |
michael@0 | 2875 | case TextBaseline::MIDDLE: |
michael@0 | 2876 | anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; |
michael@0 | 2877 | break; |
michael@0 | 2878 | case TextBaseline::IDEOGRAPHIC: |
michael@0 | 2879 | // fall through; best we can do with the information available |
michael@0 | 2880 | case TextBaseline::ALPHABETIC: |
michael@0 | 2881 | anchorY = 0; |
michael@0 | 2882 | break; |
michael@0 | 2883 | case TextBaseline::BOTTOM: |
michael@0 | 2884 | anchorY = -fontMetrics.emDescent; |
michael@0 | 2885 | break; |
michael@0 | 2886 | default: |
michael@0 | 2887 | MOZ_CRASH("unexpected TextBaseline"); |
michael@0 | 2888 | } |
michael@0 | 2889 | |
michael@0 | 2890 | processor.mPt.y += anchorY; |
michael@0 | 2891 | |
michael@0 | 2892 | // correct bounding box to get it to be the correct size/position |
michael@0 | 2893 | processor.mBoundingBox.width = totalWidth; |
michael@0 | 2894 | processor.mBoundingBox.MoveBy(processor.mPt); |
michael@0 | 2895 | |
michael@0 | 2896 | processor.mPt.x *= processor.mAppUnitsPerDevPixel; |
michael@0 | 2897 | processor.mPt.y *= processor.mAppUnitsPerDevPixel; |
michael@0 | 2898 | |
michael@0 | 2899 | EnsureTarget(); |
michael@0 | 2900 | Matrix oldTransform = mTarget->GetTransform(); |
michael@0 | 2901 | // if text is over aMaxWidth, then scale the text horizontally such that its |
michael@0 | 2902 | // width is precisely aMaxWidth |
michael@0 | 2903 | if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && |
michael@0 | 2904 | totalWidth > aMaxWidth.Value()) { |
michael@0 | 2905 | Matrix newTransform = oldTransform; |
michael@0 | 2906 | |
michael@0 | 2907 | // Translate so that the anchor point is at 0,0, then scale and then |
michael@0 | 2908 | // translate back. |
michael@0 | 2909 | newTransform.Translate(aX, 0); |
michael@0 | 2910 | newTransform.Scale(aMaxWidth.Value() / totalWidth, 1); |
michael@0 | 2911 | newTransform.Translate(-aX, 0); |
michael@0 | 2912 | /* we do this to avoid an ICE in the android compiler */ |
michael@0 | 2913 | Matrix androidCompilerBug = newTransform; |
michael@0 | 2914 | mTarget->SetTransform(androidCompilerBug); |
michael@0 | 2915 | } |
michael@0 | 2916 | |
michael@0 | 2917 | // save the previous bounding box |
michael@0 | 2918 | gfxRect boundingBox = processor.mBoundingBox; |
michael@0 | 2919 | |
michael@0 | 2920 | // don't ever need to measure the bounding box twice |
michael@0 | 2921 | processor.mDoMeasureBoundingBox = false; |
michael@0 | 2922 | |
michael@0 | 2923 | rv = nsBidiPresUtils::ProcessText(textToDraw.get(), |
michael@0 | 2924 | textToDraw.Length(), |
michael@0 | 2925 | isRTL ? NSBIDI_RTL : NSBIDI_LTR, |
michael@0 | 2926 | presShell->GetPresContext(), |
michael@0 | 2927 | processor, |
michael@0 | 2928 | nsBidiPresUtils::MODE_DRAW, |
michael@0 | 2929 | nullptr, |
michael@0 | 2930 | 0, |
michael@0 | 2931 | nullptr, |
michael@0 | 2932 | &bidiEngine); |
michael@0 | 2933 | |
michael@0 | 2934 | |
michael@0 | 2935 | mTarget->SetTransform(oldTransform); |
michael@0 | 2936 | |
michael@0 | 2937 | if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL && |
michael@0 | 2938 | !doDrawShadow) { |
michael@0 | 2939 | RedrawUser(boundingBox); |
michael@0 | 2940 | return NS_OK; |
michael@0 | 2941 | } |
michael@0 | 2942 | |
michael@0 | 2943 | Redraw(); |
michael@0 | 2944 | return NS_OK; |
michael@0 | 2945 | } |
michael@0 | 2946 | |
michael@0 | 2947 | gfxFontGroup *CanvasRenderingContext2D::GetCurrentFontStyle() |
michael@0 | 2948 | { |
michael@0 | 2949 | // use lazy initilization for the font group since it's rather expensive |
michael@0 | 2950 | if (!CurrentState().fontGroup) { |
michael@0 | 2951 | ErrorResult err; |
michael@0 | 2952 | NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); |
michael@0 | 2953 | static float kDefaultFontSize = 10.0; |
michael@0 | 2954 | SetFont(kDefaultFontStyle, err); |
michael@0 | 2955 | if (err.Failed()) { |
michael@0 | 2956 | gfxFontStyle style; |
michael@0 | 2957 | style.size = kDefaultFontSize; |
michael@0 | 2958 | CurrentState().fontGroup = |
michael@0 | 2959 | gfxPlatform::GetPlatform()->CreateFontGroup(NS_LITERAL_STRING("sans-serif"), |
michael@0 | 2960 | &style, |
michael@0 | 2961 | nullptr); |
michael@0 | 2962 | if (CurrentState().fontGroup) { |
michael@0 | 2963 | CurrentState().font = kDefaultFontStyle; |
michael@0 | 2964 | |
michael@0 | 2965 | nsIPresShell* presShell = GetPresShell(); |
michael@0 | 2966 | if (presShell) { |
michael@0 | 2967 | CurrentState().fontGroup->SetTextPerfMetrics( |
michael@0 | 2968 | presShell->GetPresContext()->GetTextPerfMetrics()); |
michael@0 | 2969 | } |
michael@0 | 2970 | } else { |
michael@0 | 2971 | NS_ERROR("Default canvas font is invalid"); |
michael@0 | 2972 | } |
michael@0 | 2973 | } |
michael@0 | 2974 | |
michael@0 | 2975 | } |
michael@0 | 2976 | |
michael@0 | 2977 | return CurrentState().fontGroup; |
michael@0 | 2978 | } |
michael@0 | 2979 | |
michael@0 | 2980 | // |
michael@0 | 2981 | // line caps/joins |
michael@0 | 2982 | // |
michael@0 | 2983 | |
michael@0 | 2984 | void |
michael@0 | 2985 | CanvasRenderingContext2D::SetLineCap(const nsAString& capstyle) |
michael@0 | 2986 | { |
michael@0 | 2987 | CapStyle cap; |
michael@0 | 2988 | |
michael@0 | 2989 | if (capstyle.EqualsLiteral("butt")) { |
michael@0 | 2990 | cap = CapStyle::BUTT; |
michael@0 | 2991 | } else if (capstyle.EqualsLiteral("round")) { |
michael@0 | 2992 | cap = CapStyle::ROUND; |
michael@0 | 2993 | } else if (capstyle.EqualsLiteral("square")) { |
michael@0 | 2994 | cap = CapStyle::SQUARE; |
michael@0 | 2995 | } else { |
michael@0 | 2996 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 2997 | return; |
michael@0 | 2998 | } |
michael@0 | 2999 | |
michael@0 | 3000 | CurrentState().lineCap = cap; |
michael@0 | 3001 | } |
michael@0 | 3002 | |
michael@0 | 3003 | void |
michael@0 | 3004 | CanvasRenderingContext2D::GetLineCap(nsAString& capstyle) |
michael@0 | 3005 | { |
michael@0 | 3006 | switch (CurrentState().lineCap) { |
michael@0 | 3007 | case CapStyle::BUTT: |
michael@0 | 3008 | capstyle.AssignLiteral("butt"); |
michael@0 | 3009 | break; |
michael@0 | 3010 | case CapStyle::ROUND: |
michael@0 | 3011 | capstyle.AssignLiteral("round"); |
michael@0 | 3012 | break; |
michael@0 | 3013 | case CapStyle::SQUARE: |
michael@0 | 3014 | capstyle.AssignLiteral("square"); |
michael@0 | 3015 | break; |
michael@0 | 3016 | } |
michael@0 | 3017 | } |
michael@0 | 3018 | |
michael@0 | 3019 | void |
michael@0 | 3020 | CanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle) |
michael@0 | 3021 | { |
michael@0 | 3022 | JoinStyle j; |
michael@0 | 3023 | |
michael@0 | 3024 | if (joinstyle.EqualsLiteral("round")) { |
michael@0 | 3025 | j = JoinStyle::ROUND; |
michael@0 | 3026 | } else if (joinstyle.EqualsLiteral("bevel")) { |
michael@0 | 3027 | j = JoinStyle::BEVEL; |
michael@0 | 3028 | } else if (joinstyle.EqualsLiteral("miter")) { |
michael@0 | 3029 | j = JoinStyle::MITER_OR_BEVEL; |
michael@0 | 3030 | } else { |
michael@0 | 3031 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 3032 | return; |
michael@0 | 3033 | } |
michael@0 | 3034 | |
michael@0 | 3035 | CurrentState().lineJoin = j; |
michael@0 | 3036 | } |
michael@0 | 3037 | |
michael@0 | 3038 | void |
michael@0 | 3039 | CanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle, ErrorResult& error) |
michael@0 | 3040 | { |
michael@0 | 3041 | switch (CurrentState().lineJoin) { |
michael@0 | 3042 | case JoinStyle::ROUND: |
michael@0 | 3043 | joinstyle.AssignLiteral("round"); |
michael@0 | 3044 | break; |
michael@0 | 3045 | case JoinStyle::BEVEL: |
michael@0 | 3046 | joinstyle.AssignLiteral("bevel"); |
michael@0 | 3047 | break; |
michael@0 | 3048 | case JoinStyle::MITER_OR_BEVEL: |
michael@0 | 3049 | joinstyle.AssignLiteral("miter"); |
michael@0 | 3050 | break; |
michael@0 | 3051 | default: |
michael@0 | 3052 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3053 | } |
michael@0 | 3054 | } |
michael@0 | 3055 | |
michael@0 | 3056 | void |
michael@0 | 3057 | CanvasRenderingContext2D::SetMozDash(JSContext* cx, |
michael@0 | 3058 | const JS::Value& mozDash, |
michael@0 | 3059 | ErrorResult& error) |
michael@0 | 3060 | { |
michael@0 | 3061 | FallibleTArray<Float> dash; |
michael@0 | 3062 | error = JSValToDashArray(cx, mozDash, dash); |
michael@0 | 3063 | if (!error.Failed()) { |
michael@0 | 3064 | ContextState& state = CurrentState(); |
michael@0 | 3065 | state.dash = dash; |
michael@0 | 3066 | if (state.dash.IsEmpty()) { |
michael@0 | 3067 | state.dashOffset = 0; |
michael@0 | 3068 | } |
michael@0 | 3069 | } |
michael@0 | 3070 | } |
michael@0 | 3071 | |
michael@0 | 3072 | void |
michael@0 | 3073 | CanvasRenderingContext2D::GetMozDash(JSContext* cx, |
michael@0 | 3074 | JS::MutableHandle<JS::Value> retval, |
michael@0 | 3075 | ErrorResult& error) |
michael@0 | 3076 | { |
michael@0 | 3077 | DashArrayToJSVal(CurrentState().dash, cx, retval, error); |
michael@0 | 3078 | } |
michael@0 | 3079 | |
michael@0 | 3080 | void |
michael@0 | 3081 | CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) |
michael@0 | 3082 | { |
michael@0 | 3083 | ContextState& state = CurrentState(); |
michael@0 | 3084 | if (!state.dash.IsEmpty()) { |
michael@0 | 3085 | state.dashOffset = mozDashOffset; |
michael@0 | 3086 | } |
michael@0 | 3087 | } |
michael@0 | 3088 | |
michael@0 | 3089 | void |
michael@0 | 3090 | CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments) |
michael@0 | 3091 | { |
michael@0 | 3092 | FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash; |
michael@0 | 3093 | dash.Clear(); |
michael@0 | 3094 | |
michael@0 | 3095 | for (uint32_t x = 0; x < aSegments.Length(); x++) { |
michael@0 | 3096 | dash.AppendElement(aSegments[x]); |
michael@0 | 3097 | } |
michael@0 | 3098 | if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again |
michael@0 | 3099 | for (uint32_t x = 0; x < aSegments.Length(); x++) { |
michael@0 | 3100 | dash.AppendElement(aSegments[x]); |
michael@0 | 3101 | } |
michael@0 | 3102 | } |
michael@0 | 3103 | } |
michael@0 | 3104 | |
michael@0 | 3105 | void |
michael@0 | 3106 | CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const { |
michael@0 | 3107 | const FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash; |
michael@0 | 3108 | aSegments.Clear(); |
michael@0 | 3109 | |
michael@0 | 3110 | for (uint32_t x = 0; x < dash.Length(); x++) { |
michael@0 | 3111 | aSegments.AppendElement(dash[x]); |
michael@0 | 3112 | } |
michael@0 | 3113 | } |
michael@0 | 3114 | |
michael@0 | 3115 | void |
michael@0 | 3116 | CanvasRenderingContext2D::SetLineDashOffset(double mOffset) { |
michael@0 | 3117 | CurrentState().dashOffset = mOffset; |
michael@0 | 3118 | } |
michael@0 | 3119 | |
michael@0 | 3120 | double |
michael@0 | 3121 | CanvasRenderingContext2D::LineDashOffset() const { |
michael@0 | 3122 | return CurrentState().dashOffset; |
michael@0 | 3123 | } |
michael@0 | 3124 | |
michael@0 | 3125 | bool |
michael@0 | 3126 | CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double x, double y, const CanvasWindingRule& winding) |
michael@0 | 3127 | { |
michael@0 | 3128 | if (!FloatValidate(x,y)) { |
michael@0 | 3129 | return false; |
michael@0 | 3130 | } |
michael@0 | 3131 | |
michael@0 | 3132 | // Check for site-specific permission and return false if no permission. |
michael@0 | 3133 | if (mCanvasElement) { |
michael@0 | 3134 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
michael@0 | 3135 | if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) |
michael@0 | 3136 | return false; |
michael@0 | 3137 | } |
michael@0 | 3138 | |
michael@0 | 3139 | EnsureUserSpacePath(winding); |
michael@0 | 3140 | if (!mPath) { |
michael@0 | 3141 | return false; |
michael@0 | 3142 | } |
michael@0 | 3143 | |
michael@0 | 3144 | if (mPathTransformWillUpdate) { |
michael@0 | 3145 | return mPath->ContainsPoint(Point(x, y), mPathToDS); |
michael@0 | 3146 | } |
michael@0 | 3147 | |
michael@0 | 3148 | return mPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); |
michael@0 | 3149 | } |
michael@0 | 3150 | |
michael@0 | 3151 | bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& mPath, double x, double y, const CanvasWindingRule& mWinding) |
michael@0 | 3152 | { |
michael@0 | 3153 | if (!FloatValidate(x,y)) { |
michael@0 | 3154 | return false; |
michael@0 | 3155 | } |
michael@0 | 3156 | |
michael@0 | 3157 | // Check for site-specific permission and return false if no permission. |
michael@0 | 3158 | if (mCanvasElement) { |
michael@0 | 3159 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
michael@0 | 3160 | if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) |
michael@0 | 3161 | return false; |
michael@0 | 3162 | } |
michael@0 | 3163 | |
michael@0 | 3164 | EnsureTarget(); |
michael@0 | 3165 | RefPtr<gfx::Path> tempPath = mPath.GetPath(mWinding, mTarget); |
michael@0 | 3166 | |
michael@0 | 3167 | return tempPath->ContainsPoint(Point(x, y), mTarget->GetTransform()); |
michael@0 | 3168 | } |
michael@0 | 3169 | |
michael@0 | 3170 | bool |
michael@0 | 3171 | CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double x, double y) |
michael@0 | 3172 | { |
michael@0 | 3173 | if (!FloatValidate(x,y)) { |
michael@0 | 3174 | return false; |
michael@0 | 3175 | } |
michael@0 | 3176 | |
michael@0 | 3177 | // Check for site-specific permission and return false if no permission. |
michael@0 | 3178 | if (mCanvasElement) { |
michael@0 | 3179 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
michael@0 | 3180 | if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) |
michael@0 | 3181 | return false; |
michael@0 | 3182 | } |
michael@0 | 3183 | |
michael@0 | 3184 | EnsureUserSpacePath(); |
michael@0 | 3185 | if (!mPath) { |
michael@0 | 3186 | return false; |
michael@0 | 3187 | } |
michael@0 | 3188 | |
michael@0 | 3189 | const ContextState &state = CurrentState(); |
michael@0 | 3190 | |
michael@0 | 3191 | StrokeOptions strokeOptions(state.lineWidth, |
michael@0 | 3192 | state.lineJoin, |
michael@0 | 3193 | state.lineCap, |
michael@0 | 3194 | state.miterLimit, |
michael@0 | 3195 | state.dash.Length(), |
michael@0 | 3196 | state.dash.Elements(), |
michael@0 | 3197 | state.dashOffset); |
michael@0 | 3198 | |
michael@0 | 3199 | if (mPathTransformWillUpdate) { |
michael@0 | 3200 | return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mPathToDS); |
michael@0 | 3201 | } |
michael@0 | 3202 | return mPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); |
michael@0 | 3203 | } |
michael@0 | 3204 | |
michael@0 | 3205 | bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& mPath, double x, double y) |
michael@0 | 3206 | { |
michael@0 | 3207 | if (!FloatValidate(x,y)) { |
michael@0 | 3208 | return false; |
michael@0 | 3209 | } |
michael@0 | 3210 | |
michael@0 | 3211 | // Check for site-specific permission and return false if no permission. |
michael@0 | 3212 | if (mCanvasElement) { |
michael@0 | 3213 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
michael@0 | 3214 | if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx)) |
michael@0 | 3215 | return false; |
michael@0 | 3216 | } |
michael@0 | 3217 | |
michael@0 | 3218 | EnsureTarget(); |
michael@0 | 3219 | RefPtr<gfx::Path> tempPath = mPath.GetPath(CanvasWindingRule::Nonzero, mTarget); |
michael@0 | 3220 | |
michael@0 | 3221 | const ContextState &state = CurrentState(); |
michael@0 | 3222 | |
michael@0 | 3223 | StrokeOptions strokeOptions(state.lineWidth, |
michael@0 | 3224 | state.lineJoin, |
michael@0 | 3225 | state.lineCap, |
michael@0 | 3226 | state.miterLimit, |
michael@0 | 3227 | state.dash.Length(), |
michael@0 | 3228 | state.dash.Elements(), |
michael@0 | 3229 | state.dashOffset); |
michael@0 | 3230 | |
michael@0 | 3231 | return tempPath->StrokeContainsPoint(strokeOptions, Point(x, y), mTarget->GetTransform()); |
michael@0 | 3232 | } |
michael@0 | 3233 | |
michael@0 | 3234 | // |
michael@0 | 3235 | // image |
michael@0 | 3236 | // |
michael@0 | 3237 | |
michael@0 | 3238 | // drawImage(in HTMLImageElement image, in float dx, in float dy); |
michael@0 | 3239 | // -- render image from 0,0 at dx,dy top-left coords |
michael@0 | 3240 | // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh); |
michael@0 | 3241 | // -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh |
michael@0 | 3242 | // 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 | 3243 | // -- 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 | 3244 | |
michael@0 | 3245 | // If only dx and dy are passed in then optional_argc should be 0. If only |
michael@0 | 3246 | // dx, dy, dw and dh are passed in then optional_argc should be 2. The only |
michael@0 | 3247 | // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh |
michael@0 | 3248 | // are all passed in. |
michael@0 | 3249 | |
michael@0 | 3250 | void |
michael@0 | 3251 | CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, |
michael@0 | 3252 | double sx, double sy, double sw, |
michael@0 | 3253 | double sh, double dx, double dy, |
michael@0 | 3254 | double dw, double dh, |
michael@0 | 3255 | uint8_t optional_argc, |
michael@0 | 3256 | ErrorResult& error) |
michael@0 | 3257 | { |
michael@0 | 3258 | MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6); |
michael@0 | 3259 | |
michael@0 | 3260 | RefPtr<SourceSurface> srcSurf; |
michael@0 | 3261 | gfxIntSize imgSize; |
michael@0 | 3262 | |
michael@0 | 3263 | Element* element; |
michael@0 | 3264 | |
michael@0 | 3265 | EnsureTarget(); |
michael@0 | 3266 | if (image.IsHTMLCanvasElement()) { |
michael@0 | 3267 | HTMLCanvasElement* canvas = &image.GetAsHTMLCanvasElement(); |
michael@0 | 3268 | element = canvas; |
michael@0 | 3269 | nsIntSize size = canvas->GetSize(); |
michael@0 | 3270 | if (size.width == 0 || size.height == 0) { |
michael@0 | 3271 | error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
michael@0 | 3272 | return; |
michael@0 | 3273 | } |
michael@0 | 3274 | } else { |
michael@0 | 3275 | if (image.IsHTMLImageElement()) { |
michael@0 | 3276 | HTMLImageElement* img = &image.GetAsHTMLImageElement(); |
michael@0 | 3277 | element = img; |
michael@0 | 3278 | } else { |
michael@0 | 3279 | HTMLVideoElement* video = &image.GetAsHTMLVideoElement(); |
michael@0 | 3280 | element = video; |
michael@0 | 3281 | } |
michael@0 | 3282 | |
michael@0 | 3283 | srcSurf = |
michael@0 | 3284 | CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); |
michael@0 | 3285 | } |
michael@0 | 3286 | |
michael@0 | 3287 | nsLayoutUtils::DirectDrawInfo drawInfo; |
michael@0 | 3288 | |
michael@0 | 3289 | if (!srcSurf) { |
michael@0 | 3290 | // The canvas spec says that drawImage should draw the first frame |
michael@0 | 3291 | // of animated images. We also don't want to rasterize vector images. |
michael@0 | 3292 | uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME | |
michael@0 | 3293 | nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS; |
michael@0 | 3294 | nsLayoutUtils::SurfaceFromElementResult res = |
michael@0 | 3295 | nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget); |
michael@0 | 3296 | |
michael@0 | 3297 | if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) { |
michael@0 | 3298 | // Spec says to silently do nothing if the element is still loading. |
michael@0 | 3299 | if (!res.mIsStillLoading) { |
michael@0 | 3300 | error.Throw(NS_ERROR_NOT_AVAILABLE); |
michael@0 | 3301 | } |
michael@0 | 3302 | return; |
michael@0 | 3303 | } |
michael@0 | 3304 | |
michael@0 | 3305 | imgSize = res.mSize; |
michael@0 | 3306 | |
michael@0 | 3307 | // Scale sw/sh based on aspect ratio |
michael@0 | 3308 | if (image.IsHTMLVideoElement()) { |
michael@0 | 3309 | HTMLVideoElement* video = &image.GetAsHTMLVideoElement(); |
michael@0 | 3310 | int32_t displayWidth = video->VideoWidth(); |
michael@0 | 3311 | int32_t displayHeight = video->VideoHeight(); |
michael@0 | 3312 | sw *= (double)imgSize.width / (double)displayWidth; |
michael@0 | 3313 | sh *= (double)imgSize.height / (double)displayHeight; |
michael@0 | 3314 | } |
michael@0 | 3315 | |
michael@0 | 3316 | if (mCanvasElement) { |
michael@0 | 3317 | CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, |
michael@0 | 3318 | res.mPrincipal, res.mIsWriteOnly, |
michael@0 | 3319 | res.mCORSUsed); |
michael@0 | 3320 | } |
michael@0 | 3321 | |
michael@0 | 3322 | if (res.mSourceSurface) { |
michael@0 | 3323 | if (res.mImageRequest) { |
michael@0 | 3324 | CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest, |
michael@0 | 3325 | res.mSourceSurface, imgSize); |
michael@0 | 3326 | } |
michael@0 | 3327 | |
michael@0 | 3328 | srcSurf = res.mSourceSurface; |
michael@0 | 3329 | } else { |
michael@0 | 3330 | drawInfo = res.mDrawInfo; |
michael@0 | 3331 | } |
michael@0 | 3332 | } |
michael@0 | 3333 | |
michael@0 | 3334 | if (optional_argc == 0) { |
michael@0 | 3335 | sx = sy = 0.0; |
michael@0 | 3336 | dw = sw = (double) imgSize.width; |
michael@0 | 3337 | dh = sh = (double) imgSize.height; |
michael@0 | 3338 | } else if (optional_argc == 2) { |
michael@0 | 3339 | sx = sy = 0.0; |
michael@0 | 3340 | sw = (double) imgSize.width; |
michael@0 | 3341 | sh = (double) imgSize.height; |
michael@0 | 3342 | } |
michael@0 | 3343 | |
michael@0 | 3344 | if (sw == 0.0 || sh == 0.0) { |
michael@0 | 3345 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 3346 | return; |
michael@0 | 3347 | } |
michael@0 | 3348 | |
michael@0 | 3349 | if (dw == 0.0 || dh == 0.0) { |
michael@0 | 3350 | // not really failure, but nothing to do -- |
michael@0 | 3351 | // and noone likes a divide-by-zero |
michael@0 | 3352 | return; |
michael@0 | 3353 | } |
michael@0 | 3354 | |
michael@0 | 3355 | if (sx < 0.0 || sy < 0.0 || |
michael@0 | 3356 | sw < 0.0 || sw > (double) imgSize.width || |
michael@0 | 3357 | sh < 0.0 || sh > (double) imgSize.height || |
michael@0 | 3358 | dw < 0.0 || dh < 0.0) { |
michael@0 | 3359 | // XXX - Unresolved spec issues here, for now return error. |
michael@0 | 3360 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 3361 | return; |
michael@0 | 3362 | } |
michael@0 | 3363 | |
michael@0 | 3364 | Filter filter; |
michael@0 | 3365 | |
michael@0 | 3366 | if (CurrentState().imageSmoothingEnabled) |
michael@0 | 3367 | filter = mgfx::Filter::LINEAR; |
michael@0 | 3368 | else |
michael@0 | 3369 | filter = mgfx::Filter::POINT; |
michael@0 | 3370 | |
michael@0 | 3371 | mgfx::Rect bounds; |
michael@0 | 3372 | |
michael@0 | 3373 | if (NeedToDrawShadow()) { |
michael@0 | 3374 | bounds = mgfx::Rect(dx, dy, dw, dh); |
michael@0 | 3375 | bounds = mTarget->GetTransform().TransformBounds(bounds); |
michael@0 | 3376 | } |
michael@0 | 3377 | |
michael@0 | 3378 | if (srcSurf) { |
michael@0 | 3379 | AdjustedTarget(this, bounds.IsEmpty() ? nullptr : &bounds)-> |
michael@0 | 3380 | DrawSurface(srcSurf, |
michael@0 | 3381 | mgfx::Rect(dx, dy, dw, dh), |
michael@0 | 3382 | mgfx::Rect(sx, sy, sw, sh), |
michael@0 | 3383 | DrawSurfaceOptions(filter), |
michael@0 | 3384 | DrawOptions(CurrentState().globalAlpha, UsedOperation())); |
michael@0 | 3385 | } else { |
michael@0 | 3386 | DrawDirectlyToCanvas(drawInfo, &bounds, dx, dy, dw, dh, |
michael@0 | 3387 | sx, sy, sw, sh, imgSize); |
michael@0 | 3388 | } |
michael@0 | 3389 | |
michael@0 | 3390 | RedrawUser(gfxRect(dx, dy, dw, dh)); |
michael@0 | 3391 | } |
michael@0 | 3392 | |
michael@0 | 3393 | void |
michael@0 | 3394 | CanvasRenderingContext2D::DrawDirectlyToCanvas( |
michael@0 | 3395 | const nsLayoutUtils::DirectDrawInfo& image, |
michael@0 | 3396 | mgfx::Rect* bounds, double dx, double dy, |
michael@0 | 3397 | double dw, double dh, double sx, double sy, |
michael@0 | 3398 | double sw, double sh, gfxIntSize imgSize) |
michael@0 | 3399 | { |
michael@0 | 3400 | gfxMatrix contextMatrix; |
michael@0 | 3401 | |
michael@0 | 3402 | AdjustedTarget tempTarget(this, bounds->IsEmpty() ? nullptr: bounds); |
michael@0 | 3403 | |
michael@0 | 3404 | // get any already existing transforms on the context. Include transformations used for context shadow |
michael@0 | 3405 | if (tempTarget) { |
michael@0 | 3406 | Matrix matrix = tempTarget->GetTransform(); |
michael@0 | 3407 | contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21, |
michael@0 | 3408 | matrix._22, matrix._31, matrix._32); |
michael@0 | 3409 | } |
michael@0 | 3410 | |
michael@0 | 3411 | gfxMatrix transformMatrix; |
michael@0 | 3412 | transformMatrix.Translate(gfxPoint(sx, sy)); |
michael@0 | 3413 | if (dw > 0 && dh > 0) { |
michael@0 | 3414 | transformMatrix.Scale(sw/dw, sh/dh); |
michael@0 | 3415 | } |
michael@0 | 3416 | transformMatrix.Translate(gfxPoint(-dx, -dy)); |
michael@0 | 3417 | |
michael@0 | 3418 | nsRefPtr<gfxContext> context = new gfxContext(tempTarget); |
michael@0 | 3419 | context->SetMatrix(contextMatrix); |
michael@0 | 3420 | |
michael@0 | 3421 | // FLAG_CLAMP is added for increased performance |
michael@0 | 3422 | uint32_t modifiedFlags = image.mDrawingFlags | imgIContainer::FLAG_CLAMP; |
michael@0 | 3423 | |
michael@0 | 3424 | nsresult rv = image.mImgContainer-> |
michael@0 | 3425 | Draw(context, GraphicsFilter::FILTER_GOOD, transformMatrix, |
michael@0 | 3426 | gfxRect(gfxPoint(dx, dy), gfxIntSize(dw, dh)), |
michael@0 | 3427 | nsIntRect(nsIntPoint(0, 0), gfxIntSize(imgSize.width, imgSize.height)), |
michael@0 | 3428 | gfxIntSize(imgSize.width, imgSize.height), nullptr, image.mWhichFrame, |
michael@0 | 3429 | modifiedFlags); |
michael@0 | 3430 | |
michael@0 | 3431 | NS_ENSURE_SUCCESS_VOID(rv); |
michael@0 | 3432 | } |
michael@0 | 3433 | |
michael@0 | 3434 | static bool |
michael@0 | 3435 | IsStandardCompositeOp(CompositionOp op) |
michael@0 | 3436 | { |
michael@0 | 3437 | return (op == CompositionOp::OP_SOURCE || |
michael@0 | 3438 | op == CompositionOp::OP_ATOP || |
michael@0 | 3439 | op == CompositionOp::OP_IN || |
michael@0 | 3440 | op == CompositionOp::OP_OUT || |
michael@0 | 3441 | op == CompositionOp::OP_OVER || |
michael@0 | 3442 | op == CompositionOp::OP_DEST_IN || |
michael@0 | 3443 | op == CompositionOp::OP_DEST_OUT || |
michael@0 | 3444 | op == CompositionOp::OP_DEST_OVER || |
michael@0 | 3445 | op == CompositionOp::OP_DEST_ATOP || |
michael@0 | 3446 | op == CompositionOp::OP_ADD || |
michael@0 | 3447 | op == CompositionOp::OP_XOR); |
michael@0 | 3448 | } |
michael@0 | 3449 | |
michael@0 | 3450 | void |
michael@0 | 3451 | CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op, |
michael@0 | 3452 | ErrorResult& error) |
michael@0 | 3453 | { |
michael@0 | 3454 | CompositionOp comp_op; |
michael@0 | 3455 | |
michael@0 | 3456 | #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ |
michael@0 | 3457 | if (op.EqualsLiteral(cvsop)) \ |
michael@0 | 3458 | comp_op = CompositionOp::OP_##op2d; |
michael@0 | 3459 | |
michael@0 | 3460 | CANVAS_OP_TO_GFX_OP("copy", SOURCE) |
michael@0 | 3461 | else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) |
michael@0 | 3462 | else CANVAS_OP_TO_GFX_OP("source-in", IN) |
michael@0 | 3463 | else CANVAS_OP_TO_GFX_OP("source-out", OUT) |
michael@0 | 3464 | else CANVAS_OP_TO_GFX_OP("source-over", OVER) |
michael@0 | 3465 | else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) |
michael@0 | 3466 | else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) |
michael@0 | 3467 | else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) |
michael@0 | 3468 | else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) |
michael@0 | 3469 | else CANVAS_OP_TO_GFX_OP("lighter", ADD) |
michael@0 | 3470 | else CANVAS_OP_TO_GFX_OP("xor", XOR) |
michael@0 | 3471 | else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) |
michael@0 | 3472 | else CANVAS_OP_TO_GFX_OP("screen", SCREEN) |
michael@0 | 3473 | else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) |
michael@0 | 3474 | else CANVAS_OP_TO_GFX_OP("darken", DARKEN) |
michael@0 | 3475 | else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) |
michael@0 | 3476 | else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) |
michael@0 | 3477 | else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) |
michael@0 | 3478 | else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) |
michael@0 | 3479 | else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) |
michael@0 | 3480 | else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) |
michael@0 | 3481 | else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) |
michael@0 | 3482 | else CANVAS_OP_TO_GFX_OP("hue", HUE) |
michael@0 | 3483 | else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) |
michael@0 | 3484 | else CANVAS_OP_TO_GFX_OP("color", COLOR) |
michael@0 | 3485 | else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) |
michael@0 | 3486 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 3487 | else return; |
michael@0 | 3488 | |
michael@0 | 3489 | if (!IsStandardCompositeOp(comp_op)) { |
michael@0 | 3490 | Demote(); |
michael@0 | 3491 | } |
michael@0 | 3492 | |
michael@0 | 3493 | #undef CANVAS_OP_TO_GFX_OP |
michael@0 | 3494 | CurrentState().op = comp_op; |
michael@0 | 3495 | } |
michael@0 | 3496 | |
michael@0 | 3497 | void |
michael@0 | 3498 | CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op, |
michael@0 | 3499 | ErrorResult& error) |
michael@0 | 3500 | { |
michael@0 | 3501 | CompositionOp comp_op = CurrentState().op; |
michael@0 | 3502 | |
michael@0 | 3503 | #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ |
michael@0 | 3504 | if (comp_op == CompositionOp::OP_##op2d) \ |
michael@0 | 3505 | op.AssignLiteral(cvsop); |
michael@0 | 3506 | |
michael@0 | 3507 | CANVAS_OP_TO_GFX_OP("copy", SOURCE) |
michael@0 | 3508 | else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) |
michael@0 | 3509 | else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) |
michael@0 | 3510 | else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) |
michael@0 | 3511 | else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) |
michael@0 | 3512 | else CANVAS_OP_TO_GFX_OP("lighter", ADD) |
michael@0 | 3513 | else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) |
michael@0 | 3514 | else CANVAS_OP_TO_GFX_OP("source-in", IN) |
michael@0 | 3515 | else CANVAS_OP_TO_GFX_OP("source-out", OUT) |
michael@0 | 3516 | else CANVAS_OP_TO_GFX_OP("source-over", OVER) |
michael@0 | 3517 | else CANVAS_OP_TO_GFX_OP("xor", XOR) |
michael@0 | 3518 | else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) |
michael@0 | 3519 | else CANVAS_OP_TO_GFX_OP("screen", SCREEN) |
michael@0 | 3520 | else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) |
michael@0 | 3521 | else CANVAS_OP_TO_GFX_OP("darken", DARKEN) |
michael@0 | 3522 | else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) |
michael@0 | 3523 | else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) |
michael@0 | 3524 | else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) |
michael@0 | 3525 | else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) |
michael@0 | 3526 | else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) |
michael@0 | 3527 | else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) |
michael@0 | 3528 | else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) |
michael@0 | 3529 | else CANVAS_OP_TO_GFX_OP("hue", HUE) |
michael@0 | 3530 | else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) |
michael@0 | 3531 | else CANVAS_OP_TO_GFX_OP("color", COLOR) |
michael@0 | 3532 | else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) |
michael@0 | 3533 | else { |
michael@0 | 3534 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3535 | } |
michael@0 | 3536 | |
michael@0 | 3537 | if (!IsStandardCompositeOp(comp_op)) { |
michael@0 | 3538 | Demote(); |
michael@0 | 3539 | } |
michael@0 | 3540 | |
michael@0 | 3541 | #undef CANVAS_OP_TO_GFX_OP |
michael@0 | 3542 | } |
michael@0 | 3543 | |
michael@0 | 3544 | void |
michael@0 | 3545 | CanvasRenderingContext2D::DrawWindow(nsGlobalWindow& window, double x, |
michael@0 | 3546 | double y, double w, double h, |
michael@0 | 3547 | const nsAString& bgColor, |
michael@0 | 3548 | uint32_t flags, ErrorResult& error) |
michael@0 | 3549 | { |
michael@0 | 3550 | // protect against too-large surfaces that will cause allocation |
michael@0 | 3551 | // or overflow issues |
michael@0 | 3552 | if (!gfxASurface::CheckSurfaceSize(gfxIntSize(int32_t(w), int32_t(h)), |
michael@0 | 3553 | 0xffff)) { |
michael@0 | 3554 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3555 | return; |
michael@0 | 3556 | } |
michael@0 | 3557 | |
michael@0 | 3558 | EnsureTarget(); |
michael@0 | 3559 | // We can't allow web apps to call this until we fix at least the |
michael@0 | 3560 | // following potential security issues: |
michael@0 | 3561 | // -- rendering cross-domain IFRAMEs and then extracting the results |
michael@0 | 3562 | // -- rendering the user's theme and then extracting the results |
michael@0 | 3563 | // -- rendering native anonymous content (e.g., file input paths; |
michael@0 | 3564 | // scrollbars should be allowed) |
michael@0 | 3565 | if (!nsContentUtils::IsCallerChrome()) { |
michael@0 | 3566 | // not permitted to use DrawWindow |
michael@0 | 3567 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 3568 | error.Throw(NS_ERROR_DOM_SECURITY_ERR); |
michael@0 | 3569 | return; |
michael@0 | 3570 | } |
michael@0 | 3571 | |
michael@0 | 3572 | // Flush layout updates |
michael@0 | 3573 | if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH)) { |
michael@0 | 3574 | nsContentUtils::FlushLayoutForTree(&window); |
michael@0 | 3575 | } |
michael@0 | 3576 | |
michael@0 | 3577 | nsRefPtr<nsPresContext> presContext; |
michael@0 | 3578 | nsIDocShell* docshell = window.GetDocShell(); |
michael@0 | 3579 | if (docshell) { |
michael@0 | 3580 | docshell->GetPresContext(getter_AddRefs(presContext)); |
michael@0 | 3581 | } |
michael@0 | 3582 | if (!presContext) { |
michael@0 | 3583 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3584 | return; |
michael@0 | 3585 | } |
michael@0 | 3586 | |
michael@0 | 3587 | nscolor backgroundColor; |
michael@0 | 3588 | if (!ParseColor(bgColor, &backgroundColor)) { |
michael@0 | 3589 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3590 | return; |
michael@0 | 3591 | } |
michael@0 | 3592 | |
michael@0 | 3593 | nsRect r(nsPresContext::CSSPixelsToAppUnits((float)x), |
michael@0 | 3594 | nsPresContext::CSSPixelsToAppUnits((float)y), |
michael@0 | 3595 | nsPresContext::CSSPixelsToAppUnits((float)w), |
michael@0 | 3596 | nsPresContext::CSSPixelsToAppUnits((float)h)); |
michael@0 | 3597 | uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | |
michael@0 | 3598 | nsIPresShell::RENDER_DOCUMENT_RELATIVE); |
michael@0 | 3599 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { |
michael@0 | 3600 | renderDocFlags |= nsIPresShell::RENDER_CARET; |
michael@0 | 3601 | } |
michael@0 | 3602 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { |
michael@0 | 3603 | renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | |
michael@0 | 3604 | nsIPresShell::RENDER_DOCUMENT_RELATIVE); |
michael@0 | 3605 | } |
michael@0 | 3606 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_USE_WIDGET_LAYERS) { |
michael@0 | 3607 | renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; |
michael@0 | 3608 | } |
michael@0 | 3609 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_ASYNC_DECODE_IMAGES) { |
michael@0 | 3610 | renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; |
michael@0 | 3611 | } |
michael@0 | 3612 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) { |
michael@0 | 3613 | renderDocFlags |= nsIPresShell::RENDER_DRAWWINDOW_NOT_FLUSHING; |
michael@0 | 3614 | } |
michael@0 | 3615 | |
michael@0 | 3616 | // gfxContext-over-Azure may modify the DrawTarget's transform, so |
michael@0 | 3617 | // save and restore it |
michael@0 | 3618 | Matrix matrix = mTarget->GetTransform(); |
michael@0 | 3619 | double sw = matrix._11 * w; |
michael@0 | 3620 | double sh = matrix._22 * h; |
michael@0 | 3621 | if (!sw || !sh) { |
michael@0 | 3622 | return; |
michael@0 | 3623 | } |
michael@0 | 3624 | nsRefPtr<gfxContext> thebes; |
michael@0 | 3625 | RefPtr<DrawTarget> drawDT; |
michael@0 | 3626 | if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget)) { |
michael@0 | 3627 | thebes = new gfxContext(mTarget); |
michael@0 | 3628 | thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21, |
michael@0 | 3629 | matrix._22, matrix._31, matrix._32)); |
michael@0 | 3630 | } else { |
michael@0 | 3631 | drawDT = |
michael@0 | 3632 | gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)), |
michael@0 | 3633 | SurfaceFormat::B8G8R8A8); |
michael@0 | 3634 | if (!drawDT) { |
michael@0 | 3635 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3636 | return; |
michael@0 | 3637 | } |
michael@0 | 3638 | |
michael@0 | 3639 | thebes = new gfxContext(drawDT); |
michael@0 | 3640 | thebes->Scale(matrix._11, matrix._22); |
michael@0 | 3641 | } |
michael@0 | 3642 | |
michael@0 | 3643 | nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); |
michael@0 | 3644 | unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); |
michael@0 | 3645 | if (drawDT) { |
michael@0 | 3646 | RefPtr<SourceSurface> snapshot = drawDT->Snapshot(); |
michael@0 | 3647 | RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); |
michael@0 | 3648 | |
michael@0 | 3649 | RefPtr<SourceSurface> source = |
michael@0 | 3650 | mTarget->CreateSourceSurfaceFromData(data->GetData(), |
michael@0 | 3651 | data->GetSize(), |
michael@0 | 3652 | data->Stride(), |
michael@0 | 3653 | data->GetFormat()); |
michael@0 | 3654 | |
michael@0 | 3655 | if (!source) { |
michael@0 | 3656 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3657 | return; |
michael@0 | 3658 | } |
michael@0 | 3659 | |
michael@0 | 3660 | mgfx::Rect destRect(0, 0, w, h); |
michael@0 | 3661 | mgfx::Rect sourceRect(0, 0, sw, sh); |
michael@0 | 3662 | mTarget->DrawSurface(source, destRect, sourceRect, |
michael@0 | 3663 | DrawSurfaceOptions(mgfx::Filter::POINT), |
michael@0 | 3664 | DrawOptions(1.0f, CompositionOp::OP_OVER, |
michael@0 | 3665 | AntialiasMode::NONE)); |
michael@0 | 3666 | mTarget->Flush(); |
michael@0 | 3667 | } else { |
michael@0 | 3668 | mTarget->SetTransform(matrix); |
michael@0 | 3669 | } |
michael@0 | 3670 | |
michael@0 | 3671 | // note that x and y are coordinates in the document that |
michael@0 | 3672 | // we're drawing; x and y are drawn to 0,0 in current user |
michael@0 | 3673 | // space. |
michael@0 | 3674 | RedrawUser(gfxRect(0, 0, w, h)); |
michael@0 | 3675 | } |
michael@0 | 3676 | |
michael@0 | 3677 | void |
michael@0 | 3678 | CanvasRenderingContext2D::AsyncDrawXULElement(nsXULElement& elem, |
michael@0 | 3679 | double x, double y, |
michael@0 | 3680 | double w, double h, |
michael@0 | 3681 | const nsAString& bgColor, |
michael@0 | 3682 | uint32_t flags, |
michael@0 | 3683 | ErrorResult& error) |
michael@0 | 3684 | { |
michael@0 | 3685 | // We can't allow web apps to call this until we fix at least the |
michael@0 | 3686 | // following potential security issues: |
michael@0 | 3687 | // -- rendering cross-domain IFRAMEs and then extracting the results |
michael@0 | 3688 | // -- rendering the user's theme and then extracting the results |
michael@0 | 3689 | // -- rendering native anonymous content (e.g., file input paths; |
michael@0 | 3690 | // scrollbars should be allowed) |
michael@0 | 3691 | if (!nsContentUtils::IsCallerChrome()) { |
michael@0 | 3692 | // not permitted to use DrawWindow |
michael@0 | 3693 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 3694 | error.Throw(NS_ERROR_DOM_SECURITY_ERR); |
michael@0 | 3695 | return; |
michael@0 | 3696 | } |
michael@0 | 3697 | |
michael@0 | 3698 | #if 0 |
michael@0 | 3699 | nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(&elem); |
michael@0 | 3700 | if (!loaderOwner) { |
michael@0 | 3701 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3702 | return; |
michael@0 | 3703 | } |
michael@0 | 3704 | |
michael@0 | 3705 | nsRefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader(); |
michael@0 | 3706 | if (!frameloader) { |
michael@0 | 3707 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3708 | return; |
michael@0 | 3709 | } |
michael@0 | 3710 | |
michael@0 | 3711 | PBrowserParent *child = frameloader->GetRemoteBrowser(); |
michael@0 | 3712 | if (!child) { |
michael@0 | 3713 | nsCOMPtr<nsIDOMWindow> window = |
michael@0 | 3714 | do_GetInterface(frameloader->GetExistingDocShell()); |
michael@0 | 3715 | if (!window) { |
michael@0 | 3716 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3717 | return; |
michael@0 | 3718 | } |
michael@0 | 3719 | |
michael@0 | 3720 | return DrawWindow(window, x, y, w, h, bgColor, flags); |
michael@0 | 3721 | } |
michael@0 | 3722 | |
michael@0 | 3723 | // protect against too-large surfaces that will cause allocation |
michael@0 | 3724 | // or overflow issues |
michael@0 | 3725 | if (!gfxASurface::CheckSurfaceSize(gfxIntSize(w, h), 0xffff)) { |
michael@0 | 3726 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3727 | return; |
michael@0 | 3728 | } |
michael@0 | 3729 | |
michael@0 | 3730 | bool flush = |
michael@0 | 3731 | (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0; |
michael@0 | 3732 | |
michael@0 | 3733 | uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; |
michael@0 | 3734 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) { |
michael@0 | 3735 | renderDocFlags |= nsIPresShell::RENDER_CARET; |
michael@0 | 3736 | } |
michael@0 | 3737 | if (flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) { |
michael@0 | 3738 | renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING; |
michael@0 | 3739 | } |
michael@0 | 3740 | |
michael@0 | 3741 | nsRect rect(nsPresContext::CSSPixelsToAppUnits(x), |
michael@0 | 3742 | nsPresContext::CSSPixelsToAppUnits(y), |
michael@0 | 3743 | nsPresContext::CSSPixelsToAppUnits(w), |
michael@0 | 3744 | nsPresContext::CSSPixelsToAppUnits(h)); |
michael@0 | 3745 | if (mIPC) { |
michael@0 | 3746 | PDocumentRendererParent *pdocrender = |
michael@0 | 3747 | child->SendPDocumentRendererConstructor(rect, |
michael@0 | 3748 | mThebes->CurrentMatrix(), |
michael@0 | 3749 | nsString(aBGColor), |
michael@0 | 3750 | renderDocFlags, flush, |
michael@0 | 3751 | nsIntSize(mWidth, mHeight)); |
michael@0 | 3752 | if (!pdocrender) |
michael@0 | 3753 | return NS_ERROR_FAILURE; |
michael@0 | 3754 | |
michael@0 | 3755 | DocumentRendererParent *docrender = |
michael@0 | 3756 | static_cast<DocumentRendererParent *>(pdocrender); |
michael@0 | 3757 | |
michael@0 | 3758 | docrender->SetCanvasContext(this, mThebes); |
michael@0 | 3759 | } |
michael@0 | 3760 | #endif |
michael@0 | 3761 | } |
michael@0 | 3762 | |
michael@0 | 3763 | // |
michael@0 | 3764 | // device pixel getting/setting |
michael@0 | 3765 | // |
michael@0 | 3766 | |
michael@0 | 3767 | already_AddRefed<ImageData> |
michael@0 | 3768 | CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, |
michael@0 | 3769 | double aSy, double aSw, |
michael@0 | 3770 | double aSh, ErrorResult& error) |
michael@0 | 3771 | { |
michael@0 | 3772 | EnsureTarget(); |
michael@0 | 3773 | if (!IsTargetValid()) { |
michael@0 | 3774 | error.Throw(NS_ERROR_FAILURE); |
michael@0 | 3775 | return nullptr; |
michael@0 | 3776 | } |
michael@0 | 3777 | |
michael@0 | 3778 | if (!mCanvasElement && !mDocShell) { |
michael@0 | 3779 | NS_ERROR("No canvas element and no docshell in GetImageData!!!"); |
michael@0 | 3780 | error.Throw(NS_ERROR_DOM_SECURITY_ERR); |
michael@0 | 3781 | return nullptr; |
michael@0 | 3782 | } |
michael@0 | 3783 | |
michael@0 | 3784 | // Check only if we have a canvas element; if we were created with a docshell, |
michael@0 | 3785 | // then it's special internal use. |
michael@0 | 3786 | if (mCanvasElement && mCanvasElement->IsWriteOnly() && |
michael@0 | 3787 | !nsContentUtils::IsCallerChrome()) |
michael@0 | 3788 | { |
michael@0 | 3789 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
michael@0 | 3790 | error.Throw(NS_ERROR_DOM_SECURITY_ERR); |
michael@0 | 3791 | return nullptr; |
michael@0 | 3792 | } |
michael@0 | 3793 | |
michael@0 | 3794 | if (!NS_finite(aSx) || !NS_finite(aSy) || |
michael@0 | 3795 | !NS_finite(aSw) || !NS_finite(aSh)) { |
michael@0 | 3796 | error.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
michael@0 | 3797 | return nullptr; |
michael@0 | 3798 | } |
michael@0 | 3799 | |
michael@0 | 3800 | if (!aSw || !aSh) { |
michael@0 | 3801 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 3802 | return nullptr; |
michael@0 | 3803 | } |
michael@0 | 3804 | |
michael@0 | 3805 | int32_t x = JS_DoubleToInt32(aSx); |
michael@0 | 3806 | int32_t y = JS_DoubleToInt32(aSy); |
michael@0 | 3807 | int32_t wi = JS_DoubleToInt32(aSw); |
michael@0 | 3808 | int32_t hi = JS_DoubleToInt32(aSh); |
michael@0 | 3809 | |
michael@0 | 3810 | // Handle negative width and height by flipping the rectangle over in the |
michael@0 | 3811 | // relevant direction. |
michael@0 | 3812 | uint32_t w, h; |
michael@0 | 3813 | if (aSw < 0) { |
michael@0 | 3814 | w = -wi; |
michael@0 | 3815 | x -= w; |
michael@0 | 3816 | } else { |
michael@0 | 3817 | w = wi; |
michael@0 | 3818 | } |
michael@0 | 3819 | if (aSh < 0) { |
michael@0 | 3820 | h = -hi; |
michael@0 | 3821 | y -= h; |
michael@0 | 3822 | } else { |
michael@0 | 3823 | h = hi; |
michael@0 | 3824 | } |
michael@0 | 3825 | |
michael@0 | 3826 | if (w == 0) { |
michael@0 | 3827 | w = 1; |
michael@0 | 3828 | } |
michael@0 | 3829 | if (h == 0) { |
michael@0 | 3830 | h = 1; |
michael@0 | 3831 | } |
michael@0 | 3832 | |
michael@0 | 3833 | JS::Rooted<JSObject*> array(aCx); |
michael@0 | 3834 | error = GetImageDataArray(aCx, x, y, w, h, array.address()); |
michael@0 | 3835 | if (error.Failed()) { |
michael@0 | 3836 | return nullptr; |
michael@0 | 3837 | } |
michael@0 | 3838 | MOZ_ASSERT(array); |
michael@0 | 3839 | |
michael@0 | 3840 | nsRefPtr<ImageData> imageData = new ImageData(w, h, *array); |
michael@0 | 3841 | return imageData.forget(); |
michael@0 | 3842 | } |
michael@0 | 3843 | |
michael@0 | 3844 | nsresult |
michael@0 | 3845 | CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, |
michael@0 | 3846 | int32_t aX, |
michael@0 | 3847 | int32_t aY, |
michael@0 | 3848 | uint32_t aWidth, |
michael@0 | 3849 | uint32_t aHeight, |
michael@0 | 3850 | JSObject** aRetval) |
michael@0 | 3851 | { |
michael@0 | 3852 | MOZ_ASSERT(aWidth && aHeight); |
michael@0 | 3853 | |
michael@0 | 3854 | CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4; |
michael@0 | 3855 | if (!len.isValid()) { |
michael@0 | 3856 | return NS_ERROR_DOM_INDEX_SIZE_ERR; |
michael@0 | 3857 | } |
michael@0 | 3858 | |
michael@0 | 3859 | CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth; |
michael@0 | 3860 | CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight; |
michael@0 | 3861 | |
michael@0 | 3862 | if (!rightMost.isValid() || !bottomMost.isValid()) { |
michael@0 | 3863 | return NS_ERROR_DOM_SYNTAX_ERR; |
michael@0 | 3864 | } |
michael@0 | 3865 | |
michael@0 | 3866 | IntRect srcRect(0, 0, mWidth, mHeight); |
michael@0 | 3867 | IntRect destRect(aX, aY, aWidth, aHeight); |
michael@0 | 3868 | IntRect srcReadRect = srcRect.Intersect(destRect); |
michael@0 | 3869 | RefPtr<DataSourceSurface> readback; |
michael@0 | 3870 | if (!srcReadRect.IsEmpty() && !mZero) { |
michael@0 | 3871 | RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); |
michael@0 | 3872 | if (snapshot) { |
michael@0 | 3873 | readback = snapshot->GetDataSurface(); |
michael@0 | 3874 | } |
michael@0 | 3875 | if (!readback || !readback->GetData()) { |
michael@0 | 3876 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 3877 | } |
michael@0 | 3878 | } |
michael@0 | 3879 | |
michael@0 | 3880 | JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value())); |
michael@0 | 3881 | if (!darray) { |
michael@0 | 3882 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 3883 | } |
michael@0 | 3884 | |
michael@0 | 3885 | if (mZero) { |
michael@0 | 3886 | *aRetval = darray; |
michael@0 | 3887 | return NS_OK; |
michael@0 | 3888 | } |
michael@0 | 3889 | |
michael@0 | 3890 | uint8_t* data = JS_GetUint8ClampedArrayData(darray); |
michael@0 | 3891 | |
michael@0 | 3892 | // Check for site-specific permission and return all-white, opaque pixel |
michael@0 | 3893 | // data if no permission. This check is not needed if the canvas was |
michael@0 | 3894 | // created with a docshell (that is only done for special internal uses). |
michael@0 | 3895 | bool usePlaceholder = false; |
michael@0 | 3896 | if (mCanvasElement) { |
michael@0 | 3897 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
michael@0 | 3898 | usePlaceholder = !ownerDoc || |
michael@0 | 3899 | !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx); |
michael@0 | 3900 | } |
michael@0 | 3901 | |
michael@0 | 3902 | if (usePlaceholder) { |
michael@0 | 3903 | memset(data, 0xFF, len.value()); |
michael@0 | 3904 | *aRetval = darray; |
michael@0 | 3905 | return NS_OK; |
michael@0 | 3906 | } |
michael@0 | 3907 | |
michael@0 | 3908 | IntRect dstWriteRect = srcReadRect; |
michael@0 | 3909 | dstWriteRect.MoveBy(-aX, -aY); |
michael@0 | 3910 | |
michael@0 | 3911 | uint8_t* src = data; |
michael@0 | 3912 | uint32_t srcStride = aWidth * 4; |
michael@0 | 3913 | if (readback) { |
michael@0 | 3914 | srcStride = readback->Stride(); |
michael@0 | 3915 | src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4; |
michael@0 | 3916 | } |
michael@0 | 3917 | |
michael@0 | 3918 | // NOTE! dst is the same as src, and this relies on reading |
michael@0 | 3919 | // from src and advancing that ptr before writing to dst. |
michael@0 | 3920 | // NOTE! I'm not sure that it is, I think this comment might have been |
michael@0 | 3921 | // inherited from Thebes canvas and is no longer true |
michael@0 | 3922 | uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; |
michael@0 | 3923 | |
michael@0 | 3924 | if (mOpaque) { |
michael@0 | 3925 | for (int32_t j = 0; j < dstWriteRect.height; ++j) { |
michael@0 | 3926 | for (int32_t i = 0; i < dstWriteRect.width; ++i) { |
michael@0 | 3927 | // XXX Is there some useful swizzle MMX we can use here? |
michael@0 | 3928 | #if MOZ_LITTLE_ENDIAN |
michael@0 | 3929 | uint8_t b = *src++; |
michael@0 | 3930 | uint8_t g = *src++; |
michael@0 | 3931 | uint8_t r = *src++; |
michael@0 | 3932 | src++; |
michael@0 | 3933 | #else |
michael@0 | 3934 | src++; |
michael@0 | 3935 | uint8_t r = *src++; |
michael@0 | 3936 | uint8_t g = *src++; |
michael@0 | 3937 | uint8_t b = *src++; |
michael@0 | 3938 | #endif |
michael@0 | 3939 | *dst++ = r; |
michael@0 | 3940 | *dst++ = g; |
michael@0 | 3941 | *dst++ = b; |
michael@0 | 3942 | *dst++ = 255; |
michael@0 | 3943 | } |
michael@0 | 3944 | src += srcStride - (dstWriteRect.width * 4); |
michael@0 | 3945 | dst += (aWidth * 4) - (dstWriteRect.width * 4); |
michael@0 | 3946 | } |
michael@0 | 3947 | } else |
michael@0 | 3948 | for (int32_t j = 0; j < dstWriteRect.height; ++j) { |
michael@0 | 3949 | for (int32_t i = 0; i < dstWriteRect.width; ++i) { |
michael@0 | 3950 | // XXX Is there some useful swizzle MMX we can use here? |
michael@0 | 3951 | #if MOZ_LITTLE_ENDIAN |
michael@0 | 3952 | uint8_t b = *src++; |
michael@0 | 3953 | uint8_t g = *src++; |
michael@0 | 3954 | uint8_t r = *src++; |
michael@0 | 3955 | uint8_t a = *src++; |
michael@0 | 3956 | #else |
michael@0 | 3957 | uint8_t a = *src++; |
michael@0 | 3958 | uint8_t r = *src++; |
michael@0 | 3959 | uint8_t g = *src++; |
michael@0 | 3960 | uint8_t b = *src++; |
michael@0 | 3961 | #endif |
michael@0 | 3962 | // Convert to non-premultiplied color |
michael@0 | 3963 | *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + r]; |
michael@0 | 3964 | *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + g]; |
michael@0 | 3965 | *dst++ = gfxUtils::sUnpremultiplyTable[a * 256 + b]; |
michael@0 | 3966 | *dst++ = a; |
michael@0 | 3967 | } |
michael@0 | 3968 | src += srcStride - (dstWriteRect.width * 4); |
michael@0 | 3969 | dst += (aWidth * 4) - (dstWriteRect.width * 4); |
michael@0 | 3970 | } |
michael@0 | 3971 | |
michael@0 | 3972 | *aRetval = darray; |
michael@0 | 3973 | return NS_OK; |
michael@0 | 3974 | } |
michael@0 | 3975 | |
michael@0 | 3976 | void |
michael@0 | 3977 | CanvasRenderingContext2D::EnsureErrorTarget() |
michael@0 | 3978 | { |
michael@0 | 3979 | if (sErrorTarget) { |
michael@0 | 3980 | return; |
michael@0 | 3981 | } |
michael@0 | 3982 | |
michael@0 | 3983 | RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8); |
michael@0 | 3984 | MOZ_ASSERT(errorTarget, "Failed to allocate the error target!"); |
michael@0 | 3985 | |
michael@0 | 3986 | sErrorTarget = errorTarget; |
michael@0 | 3987 | NS_ADDREF(sErrorTarget); |
michael@0 | 3988 | } |
michael@0 | 3989 | |
michael@0 | 3990 | void |
michael@0 | 3991 | CanvasRenderingContext2D::FillRuleChanged() |
michael@0 | 3992 | { |
michael@0 | 3993 | if (mPath) { |
michael@0 | 3994 | mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); |
michael@0 | 3995 | mPath = nullptr; |
michael@0 | 3996 | } |
michael@0 | 3997 | } |
michael@0 | 3998 | |
michael@0 | 3999 | void |
michael@0 | 4000 | CanvasRenderingContext2D::PutImageData(ImageData& imageData, double dx, |
michael@0 | 4001 | double dy, ErrorResult& error) |
michael@0 | 4002 | { |
michael@0 | 4003 | dom::Uint8ClampedArray arr(imageData.GetDataObject()); |
michael@0 | 4004 | |
michael@0 | 4005 | error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), |
michael@0 | 4006 | imageData.Width(), imageData.Height(), |
michael@0 | 4007 | &arr, false, 0, 0, 0, 0); |
michael@0 | 4008 | } |
michael@0 | 4009 | |
michael@0 | 4010 | void |
michael@0 | 4011 | CanvasRenderingContext2D::PutImageData(ImageData& imageData, double dx, |
michael@0 | 4012 | double dy, double dirtyX, |
michael@0 | 4013 | double dirtyY, double dirtyWidth, |
michael@0 | 4014 | double dirtyHeight, |
michael@0 | 4015 | ErrorResult& error) |
michael@0 | 4016 | { |
michael@0 | 4017 | dom::Uint8ClampedArray arr(imageData.GetDataObject()); |
michael@0 | 4018 | |
michael@0 | 4019 | error = PutImageData_explicit(JS_DoubleToInt32(dx), JS_DoubleToInt32(dy), |
michael@0 | 4020 | imageData.Width(), imageData.Height(), |
michael@0 | 4021 | &arr, true, |
michael@0 | 4022 | JS_DoubleToInt32(dirtyX), |
michael@0 | 4023 | JS_DoubleToInt32(dirtyY), |
michael@0 | 4024 | JS_DoubleToInt32(dirtyWidth), |
michael@0 | 4025 | JS_DoubleToInt32(dirtyHeight)); |
michael@0 | 4026 | } |
michael@0 | 4027 | |
michael@0 | 4028 | // void putImageData (in ImageData d, in float x, in float y); |
michael@0 | 4029 | // 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 | 4030 | |
michael@0 | 4031 | nsresult |
michael@0 | 4032 | CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h, |
michael@0 | 4033 | dom::Uint8ClampedArray* aArray, |
michael@0 | 4034 | bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY, |
michael@0 | 4035 | int32_t dirtyWidth, int32_t dirtyHeight) |
michael@0 | 4036 | { |
michael@0 | 4037 | if (w == 0 || h == 0) { |
michael@0 | 4038 | return NS_ERROR_DOM_SYNTAX_ERR; |
michael@0 | 4039 | } |
michael@0 | 4040 | |
michael@0 | 4041 | IntRect dirtyRect; |
michael@0 | 4042 | IntRect imageDataRect(0, 0, w, h); |
michael@0 | 4043 | |
michael@0 | 4044 | if (hasDirtyRect) { |
michael@0 | 4045 | // fix up negative dimensions |
michael@0 | 4046 | if (dirtyWidth < 0) { |
michael@0 | 4047 | NS_ENSURE_TRUE(dirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 4048 | |
michael@0 | 4049 | CheckedInt32 checkedDirtyX = CheckedInt32(dirtyX) + dirtyWidth; |
michael@0 | 4050 | |
michael@0 | 4051 | if (!checkedDirtyX.isValid()) |
michael@0 | 4052 | return NS_ERROR_DOM_INDEX_SIZE_ERR; |
michael@0 | 4053 | |
michael@0 | 4054 | dirtyX = checkedDirtyX.value(); |
michael@0 | 4055 | dirtyWidth = -dirtyWidth; |
michael@0 | 4056 | } |
michael@0 | 4057 | |
michael@0 | 4058 | if (dirtyHeight < 0) { |
michael@0 | 4059 | NS_ENSURE_TRUE(dirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 4060 | |
michael@0 | 4061 | CheckedInt32 checkedDirtyY = CheckedInt32(dirtyY) + dirtyHeight; |
michael@0 | 4062 | |
michael@0 | 4063 | if (!checkedDirtyY.isValid()) |
michael@0 | 4064 | return NS_ERROR_DOM_INDEX_SIZE_ERR; |
michael@0 | 4065 | |
michael@0 | 4066 | dirtyY = checkedDirtyY.value(); |
michael@0 | 4067 | dirtyHeight = -dirtyHeight; |
michael@0 | 4068 | } |
michael@0 | 4069 | |
michael@0 | 4070 | // bound the dirty rect within the imageData rectangle |
michael@0 | 4071 | dirtyRect = imageDataRect.Intersect(IntRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); |
michael@0 | 4072 | |
michael@0 | 4073 | if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) |
michael@0 | 4074 | return NS_OK; |
michael@0 | 4075 | } else { |
michael@0 | 4076 | dirtyRect = imageDataRect; |
michael@0 | 4077 | } |
michael@0 | 4078 | |
michael@0 | 4079 | dirtyRect.MoveBy(IntPoint(x, y)); |
michael@0 | 4080 | dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); |
michael@0 | 4081 | |
michael@0 | 4082 | if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { |
michael@0 | 4083 | return NS_OK; |
michael@0 | 4084 | } |
michael@0 | 4085 | |
michael@0 | 4086 | aArray->ComputeLengthAndData(); |
michael@0 | 4087 | |
michael@0 | 4088 | uint32_t dataLen = aArray->Length(); |
michael@0 | 4089 | |
michael@0 | 4090 | uint32_t len = w * h * 4; |
michael@0 | 4091 | if (dataLen != len) { |
michael@0 | 4092 | return NS_ERROR_DOM_SYNTAX_ERR; |
michael@0 | 4093 | } |
michael@0 | 4094 | |
michael@0 | 4095 | nsRefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(gfxIntSize(w, h), |
michael@0 | 4096 | gfxImageFormat::ARGB32, |
michael@0 | 4097 | false); |
michael@0 | 4098 | if (!imgsurf || imgsurf->CairoStatus()) { |
michael@0 | 4099 | return NS_ERROR_FAILURE; |
michael@0 | 4100 | } |
michael@0 | 4101 | |
michael@0 | 4102 | uint8_t *src = aArray->Data(); |
michael@0 | 4103 | uint8_t *dst = imgsurf->Data(); |
michael@0 | 4104 | |
michael@0 | 4105 | for (uint32_t j = 0; j < h; j++) { |
michael@0 | 4106 | for (uint32_t i = 0; i < w; i++) { |
michael@0 | 4107 | uint8_t r = *src++; |
michael@0 | 4108 | uint8_t g = *src++; |
michael@0 | 4109 | uint8_t b = *src++; |
michael@0 | 4110 | uint8_t a = *src++; |
michael@0 | 4111 | |
michael@0 | 4112 | // Convert to premultiplied color (losslessly if the input came from getImageData) |
michael@0 | 4113 | #if MOZ_LITTLE_ENDIAN |
michael@0 | 4114 | *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b]; |
michael@0 | 4115 | *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g]; |
michael@0 | 4116 | *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r]; |
michael@0 | 4117 | *dst++ = a; |
michael@0 | 4118 | #else |
michael@0 | 4119 | *dst++ = a; |
michael@0 | 4120 | *dst++ = gfxUtils::sPremultiplyTable[a * 256 + r]; |
michael@0 | 4121 | *dst++ = gfxUtils::sPremultiplyTable[a * 256 + g]; |
michael@0 | 4122 | *dst++ = gfxUtils::sPremultiplyTable[a * 256 + b]; |
michael@0 | 4123 | #endif |
michael@0 | 4124 | } |
michael@0 | 4125 | } |
michael@0 | 4126 | |
michael@0 | 4127 | EnsureTarget(); |
michael@0 | 4128 | if (!IsTargetValid()) { |
michael@0 | 4129 | return NS_ERROR_FAILURE; |
michael@0 | 4130 | } |
michael@0 | 4131 | |
michael@0 | 4132 | RefPtr<SourceSurface> sourceSurface = |
michael@0 | 4133 | mTarget->CreateSourceSurfaceFromData(imgsurf->Data(), IntSize(w, h), imgsurf->Stride(), SurfaceFormat::B8G8R8A8); |
michael@0 | 4134 | |
michael@0 | 4135 | // In certain scenarios, requesting larger than 8k image fails. Bug 803568 |
michael@0 | 4136 | // covers the details of how to run into it, but the full detailed |
michael@0 | 4137 | // investigation hasn't been done to determine the underlying cause. We |
michael@0 | 4138 | // will just handle the failure to allocate the surface to avoid a crash. |
michael@0 | 4139 | if (!sourceSurface) { |
michael@0 | 4140 | return NS_ERROR_FAILURE; |
michael@0 | 4141 | } |
michael@0 | 4142 | |
michael@0 | 4143 | mTarget->CopySurface(sourceSurface, |
michael@0 | 4144 | IntRect(dirtyRect.x - x, dirtyRect.y - y, |
michael@0 | 4145 | dirtyRect.width, dirtyRect.height), |
michael@0 | 4146 | IntPoint(dirtyRect.x, dirtyRect.y)); |
michael@0 | 4147 | |
michael@0 | 4148 | Redraw(mgfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); |
michael@0 | 4149 | |
michael@0 | 4150 | return NS_OK; |
michael@0 | 4151 | } |
michael@0 | 4152 | |
michael@0 | 4153 | static already_AddRefed<ImageData> |
michael@0 | 4154 | CreateImageData(JSContext* cx, CanvasRenderingContext2D* context, |
michael@0 | 4155 | uint32_t w, uint32_t h, ErrorResult& error) |
michael@0 | 4156 | { |
michael@0 | 4157 | if (w == 0) |
michael@0 | 4158 | w = 1; |
michael@0 | 4159 | if (h == 0) |
michael@0 | 4160 | h = 1; |
michael@0 | 4161 | |
michael@0 | 4162 | CheckedInt<uint32_t> len = CheckedInt<uint32_t>(w) * h * 4; |
michael@0 | 4163 | if (!len.isValid()) { |
michael@0 | 4164 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 4165 | return nullptr; |
michael@0 | 4166 | } |
michael@0 | 4167 | |
michael@0 | 4168 | // Create the fast typed array; it's initialized to 0 by default. |
michael@0 | 4169 | JSObject* darray = Uint8ClampedArray::Create(cx, context, len.value()); |
michael@0 | 4170 | if (!darray) { |
michael@0 | 4171 | error.Throw(NS_ERROR_OUT_OF_MEMORY); |
michael@0 | 4172 | return nullptr; |
michael@0 | 4173 | } |
michael@0 | 4174 | |
michael@0 | 4175 | nsRefPtr<mozilla::dom::ImageData> imageData = |
michael@0 | 4176 | new mozilla::dom::ImageData(w, h, *darray); |
michael@0 | 4177 | return imageData.forget(); |
michael@0 | 4178 | } |
michael@0 | 4179 | |
michael@0 | 4180 | already_AddRefed<ImageData> |
michael@0 | 4181 | CanvasRenderingContext2D::CreateImageData(JSContext* cx, double sw, |
michael@0 | 4182 | double sh, ErrorResult& error) |
michael@0 | 4183 | { |
michael@0 | 4184 | if (!sw || !sh) { |
michael@0 | 4185 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 4186 | return nullptr; |
michael@0 | 4187 | } |
michael@0 | 4188 | |
michael@0 | 4189 | int32_t wi = JS_DoubleToInt32(sw); |
michael@0 | 4190 | int32_t hi = JS_DoubleToInt32(sh); |
michael@0 | 4191 | |
michael@0 | 4192 | uint32_t w = Abs(wi); |
michael@0 | 4193 | uint32_t h = Abs(hi); |
michael@0 | 4194 | return mozilla::dom::CreateImageData(cx, this, w, h, error); |
michael@0 | 4195 | } |
michael@0 | 4196 | |
michael@0 | 4197 | already_AddRefed<ImageData> |
michael@0 | 4198 | CanvasRenderingContext2D::CreateImageData(JSContext* cx, |
michael@0 | 4199 | ImageData& imagedata, |
michael@0 | 4200 | ErrorResult& error) |
michael@0 | 4201 | { |
michael@0 | 4202 | return mozilla::dom::CreateImageData(cx, this, imagedata.Width(), |
michael@0 | 4203 | imagedata.Height(), error); |
michael@0 | 4204 | } |
michael@0 | 4205 | |
michael@0 | 4206 | static uint8_t g2DContextLayerUserData; |
michael@0 | 4207 | |
michael@0 | 4208 | already_AddRefed<CanvasLayer> |
michael@0 | 4209 | CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, |
michael@0 | 4210 | CanvasLayer *aOldLayer, |
michael@0 | 4211 | LayerManager *aManager) |
michael@0 | 4212 | { |
michael@0 | 4213 | // Don't call EnsureTarget() ... if there isn't already a surface, then |
michael@0 | 4214 | // we have nothing to paint and there is no need to create a surface just |
michael@0 | 4215 | // to paint nothing. Also, EnsureTarget() can cause creation of a persistent |
michael@0 | 4216 | // layer manager which must NOT happen during a paint. |
michael@0 | 4217 | if (!mTarget || !IsTargetValid()) { |
michael@0 | 4218 | // No DidTransactionCallback will be received, so mark the context clean |
michael@0 | 4219 | // now so future invalidations will be dispatched. |
michael@0 | 4220 | MarkContextClean(); |
michael@0 | 4221 | return nullptr; |
michael@0 | 4222 | } |
michael@0 | 4223 | |
michael@0 | 4224 | mTarget->Flush(); |
michael@0 | 4225 | |
michael@0 | 4226 | if (!mResetLayer && aOldLayer) { |
michael@0 | 4227 | CanvasRenderingContext2DUserData* userData = |
michael@0 | 4228 | static_cast<CanvasRenderingContext2DUserData*>( |
michael@0 | 4229 | aOldLayer->GetUserData(&g2DContextLayerUserData)); |
michael@0 | 4230 | |
michael@0 | 4231 | CanvasLayer::Data data; |
michael@0 | 4232 | if (mStream) { |
michael@0 | 4233 | #ifdef USE_SKIA |
michael@0 | 4234 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
michael@0 | 4235 | |
michael@0 | 4236 | if (glue) { |
michael@0 | 4237 | data.mGLContext = glue->GetGLContext(); |
michael@0 | 4238 | data.mStream = mStream.get(); |
michael@0 | 4239 | } |
michael@0 | 4240 | #endif |
michael@0 | 4241 | } else { |
michael@0 | 4242 | data.mDrawTarget = mTarget; |
michael@0 | 4243 | } |
michael@0 | 4244 | |
michael@0 | 4245 | if (userData && userData->IsForContext(this) && aOldLayer->IsDataValid(data)) { |
michael@0 | 4246 | nsRefPtr<CanvasLayer> ret = aOldLayer; |
michael@0 | 4247 | return ret.forget(); |
michael@0 | 4248 | } |
michael@0 | 4249 | } |
michael@0 | 4250 | |
michael@0 | 4251 | nsRefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer(); |
michael@0 | 4252 | if (!canvasLayer) { |
michael@0 | 4253 | NS_WARNING("CreateCanvasLayer returned null!"); |
michael@0 | 4254 | // No DidTransactionCallback will be received, so mark the context clean |
michael@0 | 4255 | // now so future invalidations will be dispatched. |
michael@0 | 4256 | MarkContextClean(); |
michael@0 | 4257 | return nullptr; |
michael@0 | 4258 | } |
michael@0 | 4259 | CanvasRenderingContext2DUserData *userData = nullptr; |
michael@0 | 4260 | // Make the layer tell us whenever a transaction finishes (including |
michael@0 | 4261 | // the current transaction), so we can clear our invalidation state and |
michael@0 | 4262 | // start invalidating again. We need to do this for all layers since |
michael@0 | 4263 | // callers of DrawWindow may be expecting to receive normal invalidation |
michael@0 | 4264 | // notifications after this paint. |
michael@0 | 4265 | |
michael@0 | 4266 | // The layer will be destroyed when we tear down the presentation |
michael@0 | 4267 | // (at the latest), at which time this userData will be destroyed, |
michael@0 | 4268 | // releasing the reference to the element. |
michael@0 | 4269 | // The userData will receive DidTransactionCallbacks, which flush the |
michael@0 | 4270 | // the invalidation state to indicate that the canvas is up to date. |
michael@0 | 4271 | userData = new CanvasRenderingContext2DUserData(this); |
michael@0 | 4272 | canvasLayer->SetDidTransactionCallback( |
michael@0 | 4273 | CanvasRenderingContext2DUserData::DidTransactionCallback, userData); |
michael@0 | 4274 | canvasLayer->SetUserData(&g2DContextLayerUserData, userData); |
michael@0 | 4275 | |
michael@0 | 4276 | CanvasLayer::Data data; |
michael@0 | 4277 | if (mStream) { |
michael@0 | 4278 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
michael@0 | 4279 | |
michael@0 | 4280 | if (glue) { |
michael@0 | 4281 | canvasLayer->SetPreTransactionCallback( |
michael@0 | 4282 | CanvasRenderingContext2DUserData::PreTransactionCallback, userData); |
michael@0 | 4283 | #if USE_SKIA |
michael@0 | 4284 | data.mGLContext = glue->GetGLContext(); |
michael@0 | 4285 | #endif |
michael@0 | 4286 | data.mStream = mStream.get(); |
michael@0 | 4287 | data.mTexID = (uint32_t)((uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE)); |
michael@0 | 4288 | } |
michael@0 | 4289 | } else { |
michael@0 | 4290 | data.mDrawTarget = mTarget; |
michael@0 | 4291 | } |
michael@0 | 4292 | |
michael@0 | 4293 | data.mSize = nsIntSize(mWidth, mHeight); |
michael@0 | 4294 | |
michael@0 | 4295 | canvasLayer->Initialize(data); |
michael@0 | 4296 | uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; |
michael@0 | 4297 | canvasLayer->SetContentFlags(flags); |
michael@0 | 4298 | canvasLayer->Updated(); |
michael@0 | 4299 | |
michael@0 | 4300 | mResetLayer = false; |
michael@0 | 4301 | |
michael@0 | 4302 | return canvasLayer.forget(); |
michael@0 | 4303 | } |
michael@0 | 4304 | |
michael@0 | 4305 | void |
michael@0 | 4306 | CanvasRenderingContext2D::MarkContextClean() |
michael@0 | 4307 | { |
michael@0 | 4308 | if (mInvalidateCount > 0) { |
michael@0 | 4309 | mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; |
michael@0 | 4310 | } |
michael@0 | 4311 | mIsEntireFrameInvalid = false; |
michael@0 | 4312 | mInvalidateCount = 0; |
michael@0 | 4313 | } |
michael@0 | 4314 | |
michael@0 | 4315 | |
michael@0 | 4316 | bool |
michael@0 | 4317 | CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager *aManager) |
michael@0 | 4318 | { |
michael@0 | 4319 | return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight)); |
michael@0 | 4320 | } |
michael@0 | 4321 | |
michael@0 | 4322 | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef) |
michael@0 | 4323 | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release) |
michael@0 | 4324 | |
michael@0 | 4325 | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CanvasPath, mParent) |
michael@0 | 4326 | |
michael@0 | 4327 | CanvasPath::CanvasPath(nsISupports* aParent) |
michael@0 | 4328 | : mParent(aParent) |
michael@0 | 4329 | { |
michael@0 | 4330 | SetIsDOMBinding(); |
michael@0 | 4331 | |
michael@0 | 4332 | mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); |
michael@0 | 4333 | } |
michael@0 | 4334 | |
michael@0 | 4335 | CanvasPath::CanvasPath(nsISupports* aParent, RefPtr<PathBuilder> aPathBuilder) |
michael@0 | 4336 | : mParent(aParent), mPathBuilder(aPathBuilder) |
michael@0 | 4337 | { |
michael@0 | 4338 | SetIsDOMBinding(); |
michael@0 | 4339 | |
michael@0 | 4340 | if (!mPathBuilder) { |
michael@0 | 4341 | mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); |
michael@0 | 4342 | } |
michael@0 | 4343 | } |
michael@0 | 4344 | |
michael@0 | 4345 | JSObject* |
michael@0 | 4346 | CanvasPath::WrapObject(JSContext* aCx) |
michael@0 | 4347 | { |
michael@0 | 4348 | return Path2DBinding::Wrap(aCx, this); |
michael@0 | 4349 | } |
michael@0 | 4350 | |
michael@0 | 4351 | already_AddRefed<CanvasPath> |
michael@0 | 4352 | CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) |
michael@0 | 4353 | { |
michael@0 | 4354 | nsRefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports()); |
michael@0 | 4355 | return path.forget(); |
michael@0 | 4356 | } |
michael@0 | 4357 | |
michael@0 | 4358 | already_AddRefed<CanvasPath> |
michael@0 | 4359 | CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv) |
michael@0 | 4360 | { |
michael@0 | 4361 | RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath(CanvasWindingRule::Nonzero, |
michael@0 | 4362 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()); |
michael@0 | 4363 | |
michael@0 | 4364 | nsRefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); |
michael@0 | 4365 | return path.forget(); |
michael@0 | 4366 | } |
michael@0 | 4367 | |
michael@0 | 4368 | already_AddRefed<CanvasPath> |
michael@0 | 4369 | CanvasPath::Constructor(const GlobalObject& aGlobal, const nsAString& aPathString, ErrorResult& aRv) |
michael@0 | 4370 | { |
michael@0 | 4371 | RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString); |
michael@0 | 4372 | if (!tempPath) { |
michael@0 | 4373 | return Constructor(aGlobal, aRv); |
michael@0 | 4374 | } |
michael@0 | 4375 | |
michael@0 | 4376 | nsRefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); |
michael@0 | 4377 | return path.forget(); |
michael@0 | 4378 | } |
michael@0 | 4379 | |
michael@0 | 4380 | void |
michael@0 | 4381 | CanvasPath::ClosePath() |
michael@0 | 4382 | { |
michael@0 | 4383 | EnsurePathBuilder(); |
michael@0 | 4384 | |
michael@0 | 4385 | mPathBuilder->Close(); |
michael@0 | 4386 | } |
michael@0 | 4387 | |
michael@0 | 4388 | void |
michael@0 | 4389 | CanvasPath::MoveTo(double x, double y) |
michael@0 | 4390 | { |
michael@0 | 4391 | EnsurePathBuilder(); |
michael@0 | 4392 | |
michael@0 | 4393 | mPathBuilder->MoveTo(Point(ToFloat(x), ToFloat(y))); |
michael@0 | 4394 | } |
michael@0 | 4395 | |
michael@0 | 4396 | void |
michael@0 | 4397 | CanvasPath::LineTo(double x, double y) |
michael@0 | 4398 | { |
michael@0 | 4399 | EnsurePathBuilder(); |
michael@0 | 4400 | |
michael@0 | 4401 | mPathBuilder->LineTo(Point(ToFloat(x), ToFloat(y))); |
michael@0 | 4402 | } |
michael@0 | 4403 | |
michael@0 | 4404 | void |
michael@0 | 4405 | CanvasPath::QuadraticCurveTo(double cpx, double cpy, double x, double y) |
michael@0 | 4406 | { |
michael@0 | 4407 | EnsurePathBuilder(); |
michael@0 | 4408 | |
michael@0 | 4409 | mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(cpx), ToFloat(cpy)), |
michael@0 | 4410 | gfx::Point(ToFloat(x), ToFloat(y))); |
michael@0 | 4411 | } |
michael@0 | 4412 | |
michael@0 | 4413 | void |
michael@0 | 4414 | CanvasPath::BezierCurveTo(double cp1x, double cp1y, |
michael@0 | 4415 | double cp2x, double cp2y, |
michael@0 | 4416 | double x, double y) |
michael@0 | 4417 | { |
michael@0 | 4418 | BezierTo(gfx::Point(ToFloat(cp1x), ToFloat(cp1y)), |
michael@0 | 4419 | gfx::Point(ToFloat(cp2x), ToFloat(cp2y)), |
michael@0 | 4420 | gfx::Point(ToFloat(x), ToFloat(y))); |
michael@0 | 4421 | } |
michael@0 | 4422 | |
michael@0 | 4423 | void |
michael@0 | 4424 | CanvasPath::ArcTo(double x1, double y1, double x2, double y2, double radius, |
michael@0 | 4425 | ErrorResult& error) |
michael@0 | 4426 | { |
michael@0 | 4427 | if (radius < 0) { |
michael@0 | 4428 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 4429 | return; |
michael@0 | 4430 | } |
michael@0 | 4431 | |
michael@0 | 4432 | // Current point in user space! |
michael@0 | 4433 | Point p0 = mPathBuilder->CurrentPoint(); |
michael@0 | 4434 | Point p1(x1, y1); |
michael@0 | 4435 | Point p2(x2, y2); |
michael@0 | 4436 | |
michael@0 | 4437 | // Execute these calculations in double precision to avoid cumulative |
michael@0 | 4438 | // rounding errors. |
michael@0 | 4439 | double dir, a2, b2, c2, cosx, sinx, d, anx, any, |
michael@0 | 4440 | bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; |
michael@0 | 4441 | bool anticlockwise; |
michael@0 | 4442 | |
michael@0 | 4443 | if (p0 == p1 || p1 == p2 || radius == 0) { |
michael@0 | 4444 | LineTo(p1.x, p1.y); |
michael@0 | 4445 | return; |
michael@0 | 4446 | } |
michael@0 | 4447 | |
michael@0 | 4448 | // Check for colinearity |
michael@0 | 4449 | dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); |
michael@0 | 4450 | if (dir == 0) { |
michael@0 | 4451 | LineTo(p1.x, p1.y); |
michael@0 | 4452 | return; |
michael@0 | 4453 | } |
michael@0 | 4454 | |
michael@0 | 4455 | |
michael@0 | 4456 | // XXX - Math for this code was already available from the non-azure code |
michael@0 | 4457 | // and would be well tested. Perhaps converting to bezier directly might |
michael@0 | 4458 | // be more efficient longer run. |
michael@0 | 4459 | a2 = (p0.x-x1)*(p0.x-x1) + (p0.y-y1)*(p0.y-y1); |
michael@0 | 4460 | b2 = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2); |
michael@0 | 4461 | c2 = (p0.x-x2)*(p0.x-x2) + (p0.y-y2)*(p0.y-y2); |
michael@0 | 4462 | cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); |
michael@0 | 4463 | |
michael@0 | 4464 | sinx = sqrt(1 - cosx*cosx); |
michael@0 | 4465 | d = radius / ((1 - cosx) / sinx); |
michael@0 | 4466 | |
michael@0 | 4467 | anx = (x1-p0.x) / sqrt(a2); |
michael@0 | 4468 | any = (y1-p0.y) / sqrt(a2); |
michael@0 | 4469 | bnx = (x1-x2) / sqrt(b2); |
michael@0 | 4470 | bny = (y1-y2) / sqrt(b2); |
michael@0 | 4471 | x3 = x1 - anx*d; |
michael@0 | 4472 | y3 = y1 - any*d; |
michael@0 | 4473 | x4 = x1 - bnx*d; |
michael@0 | 4474 | y4 = y1 - bny*d; |
michael@0 | 4475 | anticlockwise = (dir < 0); |
michael@0 | 4476 | cx = x3 + any*radius*(anticlockwise ? 1 : -1); |
michael@0 | 4477 | cy = y3 - anx*radius*(anticlockwise ? 1 : -1); |
michael@0 | 4478 | angle0 = atan2((y3-cy), (x3-cx)); |
michael@0 | 4479 | angle1 = atan2((y4-cy), (x4-cx)); |
michael@0 | 4480 | |
michael@0 | 4481 | |
michael@0 | 4482 | LineTo(x3, y3); |
michael@0 | 4483 | |
michael@0 | 4484 | Arc(cx, cy, radius, angle0, angle1, anticlockwise, error); |
michael@0 | 4485 | } |
michael@0 | 4486 | |
michael@0 | 4487 | void |
michael@0 | 4488 | CanvasPath::Rect(double x, double y, double w, double h) |
michael@0 | 4489 | { |
michael@0 | 4490 | MoveTo(x, y); |
michael@0 | 4491 | LineTo(x + w, y); |
michael@0 | 4492 | LineTo(x + w, y + h); |
michael@0 | 4493 | LineTo(x, y + h); |
michael@0 | 4494 | ClosePath(); |
michael@0 | 4495 | } |
michael@0 | 4496 | |
michael@0 | 4497 | void |
michael@0 | 4498 | CanvasPath::Arc(double x, double y, double radius, |
michael@0 | 4499 | double startAngle, double endAngle, bool anticlockwise, |
michael@0 | 4500 | ErrorResult& error) |
michael@0 | 4501 | { |
michael@0 | 4502 | if (radius < 0.0) { |
michael@0 | 4503 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
michael@0 | 4504 | return; |
michael@0 | 4505 | } |
michael@0 | 4506 | |
michael@0 | 4507 | ArcToBezier(this, Point(x, y), Size(radius, radius), startAngle, endAngle, anticlockwise); |
michael@0 | 4508 | } |
michael@0 | 4509 | |
michael@0 | 4510 | void |
michael@0 | 4511 | CanvasPath::LineTo(const gfx::Point& aPoint) |
michael@0 | 4512 | { |
michael@0 | 4513 | EnsurePathBuilder(); |
michael@0 | 4514 | |
michael@0 | 4515 | mPathBuilder->LineTo(aPoint); |
michael@0 | 4516 | } |
michael@0 | 4517 | |
michael@0 | 4518 | void |
michael@0 | 4519 | CanvasPath::BezierTo(const gfx::Point& aCP1, |
michael@0 | 4520 | const gfx::Point& aCP2, |
michael@0 | 4521 | const gfx::Point& aCP3) |
michael@0 | 4522 | { |
michael@0 | 4523 | EnsurePathBuilder(); |
michael@0 | 4524 | |
michael@0 | 4525 | mPathBuilder->BezierTo(aCP1, aCP2, aCP3); |
michael@0 | 4526 | } |
michael@0 | 4527 | |
michael@0 | 4528 | RefPtr<gfx::Path> |
michael@0 | 4529 | CanvasPath::GetPath(const CanvasWindingRule& winding, const mozilla::RefPtr<mozilla::gfx::DrawTarget>& mTarget) const |
michael@0 | 4530 | { |
michael@0 | 4531 | FillRule fillRule = FillRule::FILL_WINDING; |
michael@0 | 4532 | if (winding == CanvasWindingRule::Evenodd) { |
michael@0 | 4533 | fillRule = FillRule::FILL_EVEN_ODD; |
michael@0 | 4534 | } |
michael@0 | 4535 | |
michael@0 | 4536 | if (mPath && |
michael@0 | 4537 | (mPath->GetBackendType() == mTarget->GetType()) && |
michael@0 | 4538 | (mPath->GetFillRule() == fillRule)) { |
michael@0 | 4539 | return mPath; |
michael@0 | 4540 | } |
michael@0 | 4541 | |
michael@0 | 4542 | if (!mPath) { |
michael@0 | 4543 | // if there is no path, there must be a pathbuilder |
michael@0 | 4544 | MOZ_ASSERT(mPathBuilder); |
michael@0 | 4545 | mPath = mPathBuilder->Finish(); |
michael@0 | 4546 | if (!mPath) |
michael@0 | 4547 | return mPath; |
michael@0 | 4548 | |
michael@0 | 4549 | mPathBuilder = nullptr; |
michael@0 | 4550 | } |
michael@0 | 4551 | |
michael@0 | 4552 | // retarget our backend if we're used with a different backend |
michael@0 | 4553 | if (mPath->GetBackendType() != mTarget->GetType()) { |
michael@0 | 4554 | RefPtr<PathBuilder> tmpPathBuilder = mTarget->CreatePathBuilder(fillRule); |
michael@0 | 4555 | mPath->StreamToSink(tmpPathBuilder); |
michael@0 | 4556 | mPath = tmpPathBuilder->Finish(); |
michael@0 | 4557 | } else if (mPath->GetFillRule() != fillRule) { |
michael@0 | 4558 | RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule); |
michael@0 | 4559 | mPath = tmpPathBuilder->Finish(); |
michael@0 | 4560 | } |
michael@0 | 4561 | |
michael@0 | 4562 | return mPath; |
michael@0 | 4563 | } |
michael@0 | 4564 | |
michael@0 | 4565 | void |
michael@0 | 4566 | CanvasPath::EnsurePathBuilder() const |
michael@0 | 4567 | { |
michael@0 | 4568 | if (mPathBuilder) { |
michael@0 | 4569 | return; |
michael@0 | 4570 | } |
michael@0 | 4571 | |
michael@0 | 4572 | // if there is not pathbuilder, there must be a path |
michael@0 | 4573 | MOZ_ASSERT(mPath); |
michael@0 | 4574 | mPathBuilder = mPath->CopyToBuilder(); |
michael@0 | 4575 | mPath = nullptr; |
michael@0 | 4576 | } |
michael@0 | 4577 | |
michael@0 | 4578 | } |
michael@0 | 4579 | } |