|
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 } |