content/canvas/src/CanvasRenderingContext2D.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rwxr-xr-x

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 }

mercurial