content/canvas/src/CanvasRenderingContext2D.cpp

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

mercurial