Follow-on fix for bug 457825. Use sheet principal for agent and user sheets. r=dbaron...
[wine-gecko.git] / content / canvas / src / nsCanvasRenderingContext2D.cpp
blob6bd6ca4724199ea8756e6601e441e1c1b0b96bc0
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Vladimir Vukicevic <vladimir@pobox.com>
19 * Portions created by the Initial Developer are Copyright (C) 2005
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Eric Butler <zantifon@gmail.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #ifdef _MSC_VER
40 #define _USE_MATH_DEFINES
41 #endif
42 #include <math.h>
44 #include "prmem.h"
46 #include "nsIServiceManager.h"
48 #include "nsContentUtils.h"
50 #include "nsIDOMDocument.h"
51 #include "nsIDocument.h"
52 #include "nsIDOMCanvasRenderingContext2D.h"
53 #include "nsICanvasRenderingContextInternal.h"
54 #include "nsPresContext.h"
55 #include "nsIPresShell.h"
56 #include "nsIVariant.h"
58 #include "imgIRequest.h"
59 #include "imgIContainer.h"
60 #include "gfxIImageFrame.h"
61 #include "nsIDOMHTMLCanvasElement.h"
62 #include "nsICanvasElement.h"
63 #include "nsIDOMHTMLImageElement.h"
64 #include "nsIImageLoadingContent.h"
65 #include "nsIInterfaceRequestorUtils.h"
66 #include "nsIImage.h"
67 #include "nsIFrame.h"
68 #include "nsDOMError.h"
69 #include "nsIScriptError.h"
71 #include "nsICSSParser.h"
72 #include "nsICSSStyleRule.h"
73 #include "nsInspectorCSSUtils.h"
74 #include "nsStyleSet.h"
76 #include "nsPrintfCString.h"
78 #include "nsReadableUtils.h"
80 #include "nsColor.h"
81 #include "nsIRenderingContext.h"
82 #include "nsIDeviceContext.h"
83 #include "nsGfxCIID.h"
84 #include "nsIScriptSecurityManager.h"
85 #include "nsIDocShell.h"
86 #include "nsPresContext.h"
87 #include "nsIPresShell.h"
88 #include "nsIDOMWindow.h"
89 #include "nsPIDOMWindow.h"
90 #include "nsIDocShell.h"
91 #include "nsIDocShellTreeItem.h"
92 #include "nsIDocShellTreeNode.h"
93 #include "nsIXPConnect.h"
94 #include "jsapi.h"
95 #include "jsnum.h"
97 #include "nsTArray.h"
99 #include "imgIEncoder.h"
101 #include "gfxContext.h"
102 #include "gfxASurface.h"
103 #include "gfxImageSurface.h"
104 #include "gfxPlatform.h"
105 #include "gfxFont.h"
106 #include "gfxTextRunCache.h"
107 #include "gfxBlur.h"
109 #include "nsFrameManager.h"
111 #include "nsBidiPresUtils.h"
113 #ifdef MOZ_MEDIA
114 #include "nsHTMLVideoElement.h"
115 #endif
117 #ifndef M_PI
118 #define M_PI 3.14159265358979323846
119 #define M_PI_2 1.57079632679489661923
120 #endif
122 static PRBool CheckSaneSubrectSize (PRInt32 x, PRInt32 y, PRInt32 w, PRInt32 h, PRInt32 realWidth, PRInt32 realHeight);
124 /* Float validation stuff */
126 #define VALIDATE(_f) if (!JSDOUBLE_IS_FINITE(_f)) return PR_FALSE
128 /* These must take doubles as args, because JSDOUBLE_IS_FINITE expects
129 * to take the address of its argument; we can't cast/convert in the
130 * macro.
133 static PRBool FloatValidate (double f1) {
134 VALIDATE(f1);
135 return PR_TRUE;
138 static PRBool FloatValidate (double f1, double f2) {
139 VALIDATE(f1); VALIDATE(f2);
140 return PR_TRUE;
143 static PRBool FloatValidate (double f1, double f2, double f3) {
144 VALIDATE(f1); VALIDATE(f2); VALIDATE(f3);
145 return PR_TRUE;
148 static PRBool FloatValidate (double f1, double f2, double f3, double f4) {
149 VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4);
150 return PR_TRUE;
153 static PRBool FloatValidate (double f1, double f2, double f3, double f4, double f5) {
154 VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4); VALIDATE(f5);
155 return PR_TRUE;
158 static PRBool FloatValidate (double f1, double f2, double f3, double f4, double f5, double f6) {
159 VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4); VALIDATE(f5); VALIDATE(f6);
160 return PR_TRUE;
163 #undef VALIDATE
166 ** nsCanvasGradient
168 #define NS_CANVASGRADIENT_PRIVATE_IID \
169 { 0x491d39d8, 0x4058, 0x42bd, { 0xac, 0x76, 0x70, 0xd5, 0x62, 0x7f, 0x02, 0x10 } }
170 class nsCanvasGradient : public nsIDOMCanvasGradient
172 public:
173 NS_DECLARE_STATIC_IID_ACCESSOR(NS_CANVASGRADIENT_PRIVATE_IID)
175 nsCanvasGradient(gfxPattern* pat, nsICSSParser* cssparser)
176 : mPattern(pat), mCSSParser(cssparser)
180 void Apply(gfxContext* ctx) {
181 ctx->SetPattern(mPattern);
184 /* nsIDOMCanvasGradient */
185 NS_IMETHOD AddColorStop (float offset,
186 const nsAString& colorstr)
188 nscolor color;
190 if (!FloatValidate(offset))
191 return NS_ERROR_DOM_SYNTAX_ERR;
193 if (offset < 0.0 || offset > 1.0)
194 return NS_ERROR_DOM_INDEX_SIZE_ERR;
196 nsresult rv = mCSSParser->ParseColorString(nsString(colorstr), nsnull, 0, &color);
197 if (NS_FAILED(rv))
198 return NS_ERROR_DOM_SYNTAX_ERR;
200 mPattern->AddColorStop(offset, gfxRGBA(color));
202 return NS_OK;
205 NS_DECL_ISUPPORTS
207 protected:
208 nsRefPtr<gfxPattern> mPattern;
209 nsCOMPtr<nsICSSParser> mCSSParser;
212 NS_DEFINE_STATIC_IID_ACCESSOR(nsCanvasGradient, NS_CANVASGRADIENT_PRIVATE_IID)
214 NS_IMPL_ADDREF(nsCanvasGradient)
215 NS_IMPL_RELEASE(nsCanvasGradient)
217 NS_INTERFACE_MAP_BEGIN(nsCanvasGradient)
218 NS_INTERFACE_MAP_ENTRY(nsCanvasGradient)
219 NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasGradient)
220 NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CanvasGradient)
221 NS_INTERFACE_MAP_ENTRY(nsISupports)
222 NS_INTERFACE_MAP_END
225 ** nsCanvasPattern
227 #define NS_CANVASPATTERN_PRIVATE_IID \
228 { 0xb85c6c8a, 0x0624, 0x4530, { 0xb8, 0xee, 0xff, 0xdf, 0x42, 0xe8, 0x21, 0x6d } }
229 class nsCanvasPattern : public nsIDOMCanvasPattern
231 public:
232 NS_DECLARE_STATIC_IID_ACCESSOR(NS_CANVASPATTERN_PRIVATE_IID)
234 nsCanvasPattern(gfxPattern* pat,
235 nsIPrincipal* principalForSecurityCheck,
236 PRBool forceWriteOnly)
237 : mPattern(pat),
238 mPrincipal(principalForSecurityCheck),
239 mForceWriteOnly(forceWriteOnly)
243 void Apply(gfxContext* ctx) {
244 ctx->SetPattern(mPattern);
247 nsIPrincipal* Principal() { return mPrincipal; }
248 PRBool GetForceWriteOnly() { return mForceWriteOnly; }
250 NS_DECL_ISUPPORTS
252 protected:
253 nsRefPtr<gfxPattern> mPattern;
254 nsCOMPtr<nsIPrincipal> mPrincipal;
255 PRPackedBool mForceWriteOnly;
258 NS_DEFINE_STATIC_IID_ACCESSOR(nsCanvasPattern, NS_CANVASPATTERN_PRIVATE_IID)
260 NS_IMPL_ADDREF(nsCanvasPattern)
261 NS_IMPL_RELEASE(nsCanvasPattern)
263 NS_INTERFACE_MAP_BEGIN(nsCanvasPattern)
264 NS_INTERFACE_MAP_ENTRY(nsCanvasPattern)
265 NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasPattern)
266 NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CanvasPattern)
267 NS_INTERFACE_MAP_ENTRY(nsISupports)
268 NS_INTERFACE_MAP_END
271 ** nsTextMetrics
273 #define NS_TEXTMETRICS_PRIVATE_IID \
274 { 0xc5b1c2f9, 0xcb4f, 0x4394, { 0xaf, 0xe0, 0xc6, 0x59, 0x33, 0x80, 0x8b, 0xf3 } }
275 class nsTextMetrics : public nsIDOMTextMetrics
277 public:
278 nsTextMetrics(float w) : width(w) { }
280 virtual ~nsTextMetrics() { }
282 NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEXTMETRICS_PRIVATE_IID)
284 NS_IMETHOD GetWidth(float* w) {
285 *w = width;
286 return NS_OK;
289 NS_DECL_ISUPPORTS
291 private:
292 float width;
295 NS_DEFINE_STATIC_IID_ACCESSOR(nsTextMetrics, NS_TEXTMETRICS_PRIVATE_IID)
297 NS_IMPL_ADDREF(nsTextMetrics)
298 NS_IMPL_RELEASE(nsTextMetrics)
300 NS_INTERFACE_MAP_BEGIN(nsTextMetrics)
301 NS_INTERFACE_MAP_ENTRY(nsTextMetrics)
302 NS_INTERFACE_MAP_ENTRY(nsIDOMTextMetrics)
303 NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(TextMetrics)
304 NS_INTERFACE_MAP_ENTRY(nsISupports)
305 NS_INTERFACE_MAP_END
307 struct nsCanvasBidiProcessor;
310 ** nsCanvasRenderingContext2D
312 class nsCanvasRenderingContext2D :
313 public nsIDOMCanvasRenderingContext2D,
314 public nsICanvasRenderingContextInternal
316 public:
317 nsCanvasRenderingContext2D();
318 virtual ~nsCanvasRenderingContext2D();
320 nsresult Redraw();
322 // nsICanvasRenderingContextInternal
323 NS_IMETHOD SetCanvasElement(nsICanvasElement* aParentCanvas);
324 NS_IMETHOD SetDimensions(PRInt32 width, PRInt32 height);
325 NS_IMETHOD Render(gfxContext *ctx);
326 NS_IMETHOD GetInputStream(const char* aMimeType,
327 const PRUnichar* aEncoderOptions,
328 nsIInputStream **aStream);
329 NS_IMETHOD GetThebesSurface(gfxASurface **surface);
330 NS_IMETHOD SetIsOpaque(PRBool isOpaque);
332 // nsISupports interface
333 NS_DECL_ISUPPORTS
335 // nsIDOMCanvasRenderingContext2D interface
336 NS_DECL_NSIDOMCANVASRENDERINGCONTEXT2D
338 enum Style {
339 STYLE_STROKE = 0,
340 STYLE_FILL,
341 STYLE_SHADOW,
342 STYLE_MAX
345 protected:
346 // destroy thebes/image stuff, in preparation for possibly recreating
347 void Destroy();
349 // Some helpers. Doesn't modify acolor on failure.
350 nsresult SetStyleFromVariant(nsIVariant* aStyle, Style aWhichStyle);
351 void StyleColorToString(const nscolor& aColor, nsAString& aStr);
353 void DirtyAllStyles();
355 * applies the given style as the current source. If the given style is
356 * a solid color, aUseGlobalAlpha indicates whether to multiply the alpha
357 * by global alpha, and is ignored otherwise.
359 void ApplyStyle(Style aWhichStyle, PRBool aUseGlobalAlpha = PR_TRUE);
361 // If aPrincipal is not subsumed by this canvas element, then
362 // we make the canvas write-only so bad guys can't extract the pixel
363 // data. If forceWriteOnly is set, we force write only to be set
364 // and ignore aPrincipal. (This is used for when the original data came
365 // from a <canvas> that had write-only set.)
366 void DoDrawImageSecurityCheck(nsIPrincipal* aPrincipal,
367 PRBool forceWriteOnly);
369 // Member vars
370 PRInt32 mWidth, mHeight;
371 PRPackedBool mValid;
372 PRPackedBool mOpaque;
374 // the canvas element informs us when it's going away,
375 // so these are not nsCOMPtrs
376 nsICanvasElement* mCanvasElement;
378 // our CSS parser, for colors and whatnot
379 nsCOMPtr<nsICSSParser> mCSSParser;
381 // yay thebes
382 nsRefPtr<gfxContext> mThebes;
383 nsRefPtr<gfxASurface> mSurface;
385 PRUint32 mSaveCount;
388 * Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever
389 * Redraw is called, reset to false when Render is called.
391 PRBool mIsFrameInvalid;
394 * Returns true iff the the given operator should affect areas of the
395 * destination where the source is transparent. Among other things, this
396 * implies that a fully transparent source would still affect the canvas.
398 PRBool OperatorAffectsUncoveredAreas(gfxContext::GraphicsOperator op) const
400 return PR_FALSE;
401 // XXX certain operators cause 2d.composite.uncovered.* tests to fail
402 #if 0
403 return op == gfxContext::OPERATOR_IN ||
404 op == gfxContext::OPERATOR_OUT ||
405 op == gfxContext::OPERATOR_DEST_IN ||
406 op == gfxContext::OPERATOR_DEST_ATOP ||
407 op == gfxContext::OPERATOR_SOURCE;
408 #endif
412 * Returns true iff a shadow should be drawn along with a
413 * drawing operation.
415 PRBool NeedToDrawShadow()
417 ContextState& state = CurrentState();
419 // special case the default values as a "don't draw shadows" mode
420 PRBool doDraw = state.colorStyles[STYLE_SHADOW] != 0 ||
421 state.shadowOffset.x != 0 ||
422 state.shadowOffset.y != 0;
423 PRBool isColor = CurrentState().StyleIsColor(STYLE_SHADOW);
425 // if not using one of the cooky operators, can avoid drawing a shadow
426 // if the color is fully transparent
427 return (doDraw || !isColor) && (!isColor ||
428 NS_GET_A(state.colorStyles[STYLE_SHADOW]) != 0 ||
429 OperatorAffectsUncoveredAreas(mThebes->CurrentOperator()));
433 * Checks the current state to determine if an intermediate surface would
434 * be necessary to complete a drawing operation. Does not check the
435 * condition pertaining to global alpha and patterns since that does not
436 * pertain to all drawing operations.
438 PRBool NeedToUseIntermediateSurface()
440 // certain operators always need an intermediate surface, except
441 // with quartz since quartz does compositing differently than cairo
442 return mThebes->OriginalSurface()->GetType() != gfxASurface::SurfaceTypeQuartz &&
443 OperatorAffectsUncoveredAreas(mThebes->CurrentOperator());
445 // XXX there are other unhandled cases but they should be investigated
446 // first to ensure we aren't using an intermediate surface unecessarily
450 * Returns true iff the current source is such that global alpha would not
451 * be handled correctly without the use of an intermediate surface.
453 PRBool NeedIntermediateSurfaceToHandleGlobalAlpha(Style aWhichStyle)
455 return CurrentState().globalAlpha != 1.0 && !CurrentState().StyleIsColor(aWhichStyle);
459 * Initializes the drawing of a shadow onto the canvas. The returned context
460 * should have the shadow shape drawn onto it, and then ShadowFinalize
461 * should be called. The return value is null if an error occurs.
462 * @param extents The extents of the shadow object, in device space.
463 * @param blur A newly contructed gfxAlphaBoxBlur, made with the default
464 * constructor and left uninitialized.
465 * @remark The lifetime of the return value is tied to the lifetime of
466 * the gfxAlphaBoxBlur, so it does not need to be ref counted.
468 gfxContext* ShadowInitialize(const gfxRect& extents, gfxAlphaBoxBlur& blur);
471 * Completes a shadow drawing operation.
472 * @param blur The gfxAlphaBoxBlur that was passed to ShadowInitialize.
474 void ShadowFinalize(gfxAlphaBoxBlur& blur);
477 * Draws the current path in the given style. Takes care of
478 * any shadow drawing and will use intermediate surfaces as needed.
480 nsresult DrawPath(Style style);
483 * Draws a rectangle in the given style; used by FillRect and StrokeRect.
485 nsresult DrawRect(const gfxRect& rect, Style style);
487 // text
488 enum TextAlign {
489 TEXT_ALIGN_START,
490 TEXT_ALIGN_END,
491 TEXT_ALIGN_LEFT,
492 TEXT_ALIGN_RIGHT,
493 TEXT_ALIGN_CENTER
496 enum TextBaseline {
497 TEXT_BASELINE_TOP,
498 TEXT_BASELINE_HANGING,
499 TEXT_BASELINE_MIDDLE,
500 TEXT_BASELINE_ALPHABETIC,
501 TEXT_BASELINE_IDEOGRAPHIC,
502 TEXT_BASELINE_BOTTOM
505 gfxFontGroup *GetCurrentFontStyle();
507 enum TextDrawOperation {
508 TEXT_DRAW_OPERATION_FILL,
509 TEXT_DRAW_OPERATION_STROKE,
510 TEXT_DRAW_OPERATION_MEASURE
514 * Implementation of the fillText, strokeText, and measure functions with
515 * the operation abstracted to a flag.
517 nsresult DrawOrMeasureText(const nsAString& text,
518 float x,
519 float y,
520 float maxWidth,
521 TextDrawOperation op,
522 float* aWidth);
524 // style handling
526 * The previous set style. Is equal to STYLE_MAX when there is no valid
527 * previous style.
529 Style mLastStyle;
530 PRPackedBool mDirtyStyle[STYLE_MAX];
532 // state stack handling
533 class ContextState {
534 public:
535 ContextState() : shadowOffset(0.0, 0.0),
536 globalAlpha(1.0),
537 shadowBlur(0.0),
538 textAlign(TEXT_ALIGN_START),
539 textBaseline(TEXT_BASELINE_ALPHABETIC) { }
541 ContextState(const ContextState& other)
542 : shadowOffset(other.shadowOffset),
543 globalAlpha(other.globalAlpha),
544 shadowBlur(other.shadowBlur),
545 font(other.font),
546 fontGroup(other.fontGroup),
547 textAlign(other.textAlign),
548 textBaseline(other.textBaseline)
550 for (int i = 0; i < STYLE_MAX; i++) {
551 colorStyles[i] = other.colorStyles[i];
552 gradientStyles[i] = other.gradientStyles[i];
553 patternStyles[i] = other.patternStyles[i];
557 inline void SetColorStyle(Style whichStyle, nscolor color) {
558 colorStyles[whichStyle] = color;
559 gradientStyles[whichStyle] = nsnull;
560 patternStyles[whichStyle] = nsnull;
563 inline void SetPatternStyle(Style whichStyle, nsCanvasPattern* pat) {
564 gradientStyles[whichStyle] = nsnull;
565 patternStyles[whichStyle] = pat;
568 inline void SetGradientStyle(Style whichStyle, nsCanvasGradient* grad) {
569 gradientStyles[whichStyle] = grad;
570 patternStyles[whichStyle] = nsnull;
574 * returns true iff the given style is a solid color.
576 inline PRBool StyleIsColor(Style whichStyle) const
578 return !(patternStyles[whichStyle] ||
579 gradientStyles[whichStyle]);
582 gfxPoint shadowOffset;
583 float globalAlpha;
584 float shadowBlur;
586 nsString font;
587 nsRefPtr<gfxFontGroup> fontGroup;
588 TextAlign textAlign;
589 TextBaseline textBaseline;
591 nscolor colorStyles[STYLE_MAX];
592 nsCOMPtr<nsCanvasGradient> gradientStyles[STYLE_MAX];
593 nsCOMPtr<nsCanvasPattern> patternStyles[STYLE_MAX];
596 nsTArray<ContextState> mStyleStack;
598 inline ContextState& CurrentState() {
599 return mStyleStack[mSaveCount];
602 // stolen from nsJSUtils
603 static PRBool ConvertJSValToUint32(PRUint32* aProp, JSContext* aContext,
604 jsval aValue);
605 static PRBool ConvertJSValToXPCObject(nsISupports** aSupports, REFNSIID aIID,
606 JSContext* aContext, jsval aValue);
607 static PRBool ConvertJSValToDouble(double* aProp, JSContext* aContext,
608 jsval aValue);
610 // thebes helpers
611 nsresult ThebesSurfaceFromElement(nsIDOMElement *imgElt,
612 PRBool forceCopy,
613 gfxASurface **aSurface,
614 PRInt32 *widthOut, PRInt32 *heightOut,
615 nsIPrincipal **prinOut,
616 PRBool *forceWriteOnlyOut);
618 // other helpers
619 void GetAppUnitsValues(PRUint32 *perDevPixel, PRUint32 *perCSSPixel) {
620 // If we don't have a canvas element, we just return something generic.
621 PRUint32 devPixel = 60;
622 PRUint32 cssPixel = 60;
624 nsCOMPtr<nsINode> elem = do_QueryInterface(mCanvasElement);
625 if (elem) {
626 nsIDocument *doc = elem->GetOwnerDoc();
627 if (!doc) goto FINISH;
628 nsIPresShell *ps = doc->GetPrimaryShell();
629 if (!ps) goto FINISH;
630 nsPresContext *pc = ps->GetPresContext();
631 if (!pc) goto FINISH;
632 devPixel = pc->AppUnitsPerDevPixel();
633 cssPixel = pc->AppUnitsPerCSSPixel();
636 FINISH:
637 if (perDevPixel)
638 *perDevPixel = devPixel;
639 if (perCSSPixel)
640 *perCSSPixel = cssPixel;
643 friend struct nsCanvasBidiProcessor;
646 NS_IMPL_ADDREF(nsCanvasRenderingContext2D)
647 NS_IMPL_RELEASE(nsCanvasRenderingContext2D)
649 NS_INTERFACE_MAP_BEGIN(nsCanvasRenderingContext2D)
650 NS_INTERFACE_MAP_ENTRY(nsIDOMCanvasRenderingContext2D)
651 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
652 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMCanvasRenderingContext2D)
653 NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CanvasRenderingContext2D)
654 NS_INTERFACE_MAP_END
657 ** CanvasRenderingContext2D impl
660 nsresult
661 NS_NewCanvasRenderingContext2D(nsIDOMCanvasRenderingContext2D** aResult)
663 nsRefPtr<nsIDOMCanvasRenderingContext2D> ctx = new nsCanvasRenderingContext2D();
664 if (!ctx)
665 return NS_ERROR_OUT_OF_MEMORY;
667 *aResult = ctx.forget().get();
668 return NS_OK;
671 nsCanvasRenderingContext2D::nsCanvasRenderingContext2D()
672 : mValid(PR_FALSE), mOpaque(PR_FALSE), mCanvasElement(nsnull),
673 mSaveCount(0), mIsFrameInvalid(PR_FALSE), mStyleStack(20)
677 nsCanvasRenderingContext2D::~nsCanvasRenderingContext2D()
679 Destroy();
682 void
683 nsCanvasRenderingContext2D::Destroy()
685 mSurface = nsnull;
686 mThebes = nsnull;
687 mValid = PR_FALSE;
688 mIsFrameInvalid = PR_FALSE;
691 nsresult
692 nsCanvasRenderingContext2D::SetStyleFromVariant(nsIVariant* aStyle, Style aWhichStyle)
694 nsresult rv;
695 nscolor color;
697 PRUint16 paramType;
698 rv = aStyle->GetDataType(&paramType);
699 NS_ENSURE_SUCCESS(rv, rv);
701 if (paramType == nsIDataType::VTYPE_DOMSTRING ||
702 paramType == nsIDataType::VTYPE_WSTRING_SIZE_IS) {
703 nsAutoString str;
705 if (paramType == nsIDataType::VTYPE_DOMSTRING) {
706 rv = aStyle->GetAsDOMString(str);
707 } else {
708 rv = aStyle->GetAsAString(str);
710 NS_ENSURE_SUCCESS(rv, rv);
712 rv = mCSSParser->ParseColorString(str, nsnull, 0, &color);
713 if (NS_FAILED(rv)) {
714 // Error reporting happens inside the CSS parser
715 return NS_OK;
718 CurrentState().SetColorStyle(aWhichStyle, color);
720 mDirtyStyle[aWhichStyle] = PR_TRUE;
721 return NS_OK;
722 } else if (paramType == nsIDataType::VTYPE_INTERFACE ||
723 paramType == nsIDataType::VTYPE_INTERFACE_IS)
725 nsID *iid;
726 nsCOMPtr<nsISupports> iface;
727 rv = aStyle->GetAsInterface(&iid, getter_AddRefs(iface));
729 nsCOMPtr<nsCanvasGradient> grad(do_QueryInterface(iface));
730 if (grad) {
731 CurrentState().SetGradientStyle(aWhichStyle, grad);
732 mDirtyStyle[aWhichStyle] = PR_TRUE;
733 return NS_OK;
736 nsCOMPtr<nsCanvasPattern> pattern(do_QueryInterface(iface));
737 if (pattern) {
738 CurrentState().SetPatternStyle(aWhichStyle, pattern);
739 mDirtyStyle[aWhichStyle] = PR_TRUE;
740 return NS_OK;
744 nsContentUtils::ReportToConsole(
745 nsContentUtils::eDOM_PROPERTIES,
746 "UnexpectedCanvasVariantStyle",
747 nsnull, 0,
748 nsnull,
749 EmptyString(), 0, 0,
750 nsIScriptError::warningFlag,
751 "Canvas");
753 return NS_OK;
756 void
757 nsCanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr)
759 if (NS_GET_A(aColor) == 255) {
760 CopyUTF8toUTF16(nsPrintfCString(100, "#%02x%02x%02x",
761 NS_GET_R(aColor),
762 NS_GET_G(aColor),
763 NS_GET_B(aColor)),
764 aStr);
765 } else {
766 // "%0.5f" in nsPrintfCString would use the locale-specific
767 // decimal separator. That's why we have to do this:
768 PRUint32 alpha = NS_GET_A(aColor) * 100000 / 255;
769 CopyUTF8toUTF16(nsPrintfCString(100, "rgba(%d, %d, %d, 0.%d)",
770 NS_GET_R(aColor),
771 NS_GET_G(aColor),
772 NS_GET_B(aColor),
773 alpha),
774 aStr);
778 void
779 nsCanvasRenderingContext2D::DirtyAllStyles()
781 for (int i = 0; i < STYLE_MAX; i++) {
782 mDirtyStyle[i] = PR_TRUE;
786 void
787 nsCanvasRenderingContext2D::DoDrawImageSecurityCheck(nsIPrincipal* aPrincipal,
788 PRBool forceWriteOnly)
790 // Callers should ensure that mCanvasElement is non-null before calling this
791 if (!mCanvasElement) {
792 NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
793 return;
796 if (mCanvasElement->IsWriteOnly())
797 return;
799 // If we explicitly set WriteOnly just do it and get out
800 if (forceWriteOnly) {
801 mCanvasElement->SetWriteOnly();
802 return;
805 if (aPrincipal == nsnull)
806 return;
808 nsCOMPtr<nsINode> elem = do_QueryInterface(mCanvasElement);
809 if (elem) { // XXXbz How could this actually be null?
810 PRBool subsumes;
811 nsresult rv =
812 elem->NodePrincipal()->Subsumes(aPrincipal, &subsumes);
814 if (NS_SUCCEEDED(rv) && subsumes) {
815 // This canvas has access to that image anyway
816 return;
820 mCanvasElement->SetWriteOnly();
823 void
824 nsCanvasRenderingContext2D::ApplyStyle(Style aWhichStyle,
825 PRBool aUseGlobalAlpha)
827 if (mLastStyle == aWhichStyle &&
828 !mDirtyStyle[aWhichStyle] &&
829 aUseGlobalAlpha)
831 // nothing to do, this is already the set style
832 return;
835 // if not using global alpha, don't optimize with dirty bit
836 if (aUseGlobalAlpha)
837 mDirtyStyle[aWhichStyle] = PR_FALSE;
838 mLastStyle = aWhichStyle;
840 nsCanvasPattern* pattern = CurrentState().patternStyles[aWhichStyle];
841 if (pattern) {
842 if (!mCanvasElement)
843 return;
845 DoDrawImageSecurityCheck(pattern->Principal(),
846 pattern->GetForceWriteOnly());
847 pattern->Apply(mThebes);
848 return;
851 if (CurrentState().gradientStyles[aWhichStyle]) {
852 CurrentState().gradientStyles[aWhichStyle]->Apply(mThebes);
853 return;
856 gfxRGBA color(CurrentState().colorStyles[aWhichStyle]);
857 if (aUseGlobalAlpha)
858 color.a *= CurrentState().globalAlpha;
860 mThebes->SetColor(color);
863 nsresult
864 nsCanvasRenderingContext2D::Redraw()
866 if (!mCanvasElement)
867 return NS_OK;
869 if (!mIsFrameInvalid) {
870 mIsFrameInvalid = PR_TRUE;
871 return mCanvasElement->InvalidateFrame();
874 return NS_OK;
877 NS_IMETHODIMP
878 nsCanvasRenderingContext2D::SetDimensions(PRInt32 width, PRInt32 height)
880 Destroy();
882 mWidth = width;
883 mHeight = height;
885 // Check that the dimensions are sane
886 if (gfxASurface::CheckSurfaceSize(gfxIntSize(width, height), 0xffff)) {
887 gfxASurface::gfxImageFormat format = gfxASurface::ImageFormatARGB32;
888 if (mOpaque)
889 format = gfxASurface::ImageFormatRGB24;
891 mSurface = gfxPlatform::GetPlatform()->CreateOffscreenSurface
892 (gfxIntSize(width, height), format);
894 if (mSurface->CairoStatus() == 0) {
895 mThebes = new gfxContext(mSurface);
899 /* Create dummy surfaces here */
900 if (mSurface == nsnull || mSurface->CairoStatus() != 0 ||
901 mThebes == nsnull || mThebes->HasError())
903 mSurface = new gfxImageSurface(gfxIntSize(1,1), gfxASurface::ImageFormatARGB32);
904 mThebes = new gfxContext(mSurface);
905 } else {
906 mValid = PR_TRUE;
909 // set up the initial canvas defaults
910 mStyleStack.Clear();
911 mSaveCount = 0;
913 ContextState *state = mStyleStack.AppendElement();
914 state->globalAlpha = 1.0;
916 state->colorStyles[STYLE_FILL] = NS_RGB(0,0,0);
917 state->colorStyles[STYLE_STROKE] = NS_RGB(0,0,0);
918 state->colorStyles[STYLE_SHADOW] = NS_RGBA(0,0,0,0);
919 DirtyAllStyles();
921 mThebes->SetOperator(gfxContext::OPERATOR_CLEAR);
922 mThebes->NewPath();
923 mThebes->Rectangle(gfxRect(0, 0, mWidth, mHeight));
924 mThebes->Fill();
926 mThebes->SetLineWidth(1.0);
927 mThebes->SetOperator(gfxContext::OPERATOR_OVER);
928 mThebes->SetMiterLimit(10.0);
929 mThebes->SetLineCap(gfxContext::LINE_CAP_BUTT);
930 mThebes->SetLineJoin(gfxContext::LINE_JOIN_MITER);
932 mThebes->NewPath();
934 return NS_OK;
937 NS_IMETHODIMP
938 nsCanvasRenderingContext2D::SetIsOpaque(PRBool isOpaque)
940 if (isOpaque == mOpaque)
941 return NS_OK;
943 mOpaque = isOpaque;
945 if (mValid) {
946 /* If we've already been created, let SetDimensions take care of
947 * recreating our surface
949 return SetDimensions(mWidth, mHeight);
952 return NS_OK;
955 NS_IMETHODIMP
956 nsCanvasRenderingContext2D::Render(gfxContext *ctx)
958 nsresult rv = NS_OK;
960 if (!mValid || !mSurface ||
961 mSurface->CairoStatus() ||
962 mThebes->HasError())
963 return NS_ERROR_FAILURE;
965 if (!mSurface)
966 return NS_ERROR_FAILURE;
968 nsRefPtr<gfxPattern> pat = new gfxPattern(mSurface);
970 gfxContext::GraphicsOperator op = ctx->CurrentOperator();
971 if (mOpaque)
972 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
974 // XXX I don't want to use PixelSnapped here, but layout doesn't guarantee
975 // pixel alignment for this stuff!
976 ctx->NewPath();
977 ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat);
978 ctx->Fill();
980 if (mOpaque)
981 ctx->SetOperator(op);
983 mIsFrameInvalid = PR_FALSE;
984 return rv;
987 NS_IMETHODIMP
988 nsCanvasRenderingContext2D::GetInputStream(const char *aMimeType,
989 const PRUnichar *aEncoderOptions,
990 nsIInputStream **aStream)
992 if (!mValid || !mSurface ||
993 mSurface->CairoStatus() ||
994 mThebes->HasError())
995 return NS_ERROR_FAILURE;
997 nsresult rv;
998 const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type=";
999 nsAutoArrayPtr<char> conid(new (std::nothrow) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]);
1001 if (!conid)
1002 return NS_ERROR_OUT_OF_MEMORY;
1004 strcpy(conid, encoderPrefix);
1005 strcat(conid, aMimeType);
1007 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid);
1008 if (!encoder)
1009 return NS_ERROR_FAILURE;
1011 nsAutoArrayPtr<PRUint8> imageBuffer(new (std::nothrow) PRUint8[mWidth * mHeight * 4]);
1012 if (!imageBuffer)
1013 return NS_ERROR_OUT_OF_MEMORY;
1015 nsRefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(imageBuffer.get(),
1016 gfxIntSize(mWidth, mHeight),
1017 mWidth * 4,
1018 gfxASurface::ImageFormatARGB32);
1020 if (!imgsurf || imgsurf->CairoStatus())
1021 return NS_ERROR_FAILURE;
1023 nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf);
1025 if (!ctx || ctx->HasError())
1026 return NS_ERROR_FAILURE;
1028 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
1029 ctx->SetSource(mSurface, gfxPoint(0, 0));
1030 ctx->Paint();
1032 rv = encoder->InitFromData(imageBuffer.get(),
1033 mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4,
1034 imgIEncoder::INPUT_FORMAT_HOSTARGB,
1035 nsDependentString(aEncoderOptions));
1036 NS_ENSURE_SUCCESS(rv, rv);
1038 return CallQueryInterface(encoder, aStream);
1042 // nsCanvasRenderingContext2D impl
1045 NS_IMETHODIMP
1046 nsCanvasRenderingContext2D::SetCanvasElement(nsICanvasElement* aCanvasElement)
1048 // don't hold a ref to this!
1049 mCanvasElement = aCanvasElement;
1051 // set up our css parser, if necessary
1052 if (!mCSSParser) {
1053 mCSSParser = do_CreateInstance("@mozilla.org/content/css-parser;1");
1056 return NS_OK;
1059 NS_IMETHODIMP
1060 nsCanvasRenderingContext2D::GetCanvas(nsIDOMHTMLCanvasElement **canvas)
1062 if (mCanvasElement == nsnull) {
1063 *canvas = nsnull;
1064 return NS_OK;
1067 return CallQueryInterface(mCanvasElement, canvas);
1071 // state
1074 NS_IMETHODIMP
1075 nsCanvasRenderingContext2D::Save()
1077 ContextState state = CurrentState();
1078 mStyleStack.AppendElement(state);
1079 mThebes->Save();
1080 mSaveCount++;
1081 return NS_OK;
1084 NS_IMETHODIMP
1085 nsCanvasRenderingContext2D::Restore()
1087 if (mSaveCount == 0)
1088 return NS_OK;
1089 if (mSaveCount < 0)
1090 return NS_ERROR_DOM_INVALID_STATE_ERR;
1092 mStyleStack.RemoveElementAt(mSaveCount);
1093 mThebes->Restore();
1095 mLastStyle = STYLE_MAX;
1096 DirtyAllStyles();
1098 mSaveCount--;
1099 return NS_OK;
1103 // transformations
1106 NS_IMETHODIMP
1107 nsCanvasRenderingContext2D::Scale(float x, float y)
1109 if (!FloatValidate(x,y))
1110 return NS_ERROR_DOM_SYNTAX_ERR;
1112 mThebes->Scale(x, y);
1113 return NS_OK;
1116 NS_IMETHODIMP
1117 nsCanvasRenderingContext2D::Rotate(float angle)
1119 if (!FloatValidate(angle))
1120 return NS_ERROR_DOM_SYNTAX_ERR;
1122 mThebes->Rotate(angle);
1123 return NS_OK;
1126 NS_IMETHODIMP
1127 nsCanvasRenderingContext2D::Translate(float x, float y)
1129 if (!FloatValidate(x,y))
1130 return NS_ERROR_DOM_SYNTAX_ERR;
1132 mThebes->Translate(gfxPoint(x, y));
1133 return NS_OK;
1136 NS_IMETHODIMP
1137 nsCanvasRenderingContext2D::Transform(float m11, float m12, float m21, float m22, float dx, float dy)
1139 if (!FloatValidate(m11,m12,m21,m22,dx,dy))
1140 return NS_ERROR_DOM_SYNTAX_ERR;
1142 gfxMatrix matrix(m11, m12, m21, m22, dx, dy);
1143 mThebes->Multiply(matrix);
1145 return NS_OK;
1148 NS_IMETHODIMP
1149 nsCanvasRenderingContext2D::SetTransform(float m11, float m12, float m21, float m22, float dx, float dy)
1151 if (!FloatValidate(m11,m12,m21,m22,dx,dy))
1152 return NS_ERROR_DOM_SYNTAX_ERR;
1154 gfxMatrix matrix(m11, m12, m21, m22, dx, dy);
1155 mThebes->SetMatrix(matrix);
1157 return NS_OK;
1161 // colors
1164 NS_IMETHODIMP
1165 nsCanvasRenderingContext2D::SetGlobalAlpha(float aGlobalAlpha)
1167 if (!FloatValidate(aGlobalAlpha))
1168 return NS_ERROR_DOM_SYNTAX_ERR;
1170 // ignore invalid values, as per spec
1171 if (aGlobalAlpha < 0.0 || aGlobalAlpha > 1.0)
1172 return NS_OK;
1174 CurrentState().globalAlpha = aGlobalAlpha;
1175 DirtyAllStyles();
1177 return NS_OK;
1180 NS_IMETHODIMP
1181 nsCanvasRenderingContext2D::GetGlobalAlpha(float *aGlobalAlpha)
1183 *aGlobalAlpha = CurrentState().globalAlpha;
1184 return NS_OK;
1187 NS_IMETHODIMP
1188 nsCanvasRenderingContext2D::SetStrokeStyle(nsIVariant* aStyle)
1190 return SetStyleFromVariant(aStyle, STYLE_STROKE);
1193 NS_IMETHODIMP
1194 nsCanvasRenderingContext2D::GetStrokeStyle(nsIVariant** aStyle)
1196 nsresult rv;
1198 nsCOMPtr<nsIWritableVariant> var = do_CreateInstance("@mozilla.org/variant;1");
1199 if (!var)
1200 return NS_ERROR_FAILURE;
1201 rv = var->SetWritable(PR_TRUE);
1202 NS_ENSURE_SUCCESS(rv, rv);
1204 if (CurrentState().patternStyles[STYLE_STROKE]) {
1205 rv = var->SetAsISupports(CurrentState().patternStyles[STYLE_STROKE]);
1206 NS_ENSURE_SUCCESS(rv, rv);
1207 } else if (CurrentState().gradientStyles[STYLE_STROKE]) {
1208 rv = var->SetAsISupports(CurrentState().gradientStyles[STYLE_STROKE]);
1209 NS_ENSURE_SUCCESS(rv, rv);
1210 } else {
1211 nsString styleStr;
1212 StyleColorToString(CurrentState().colorStyles[STYLE_STROKE], styleStr);
1214 rv = var->SetAsDOMString(styleStr);
1215 NS_ENSURE_SUCCESS(rv, rv);
1218 *aStyle = var.forget().get();
1219 return NS_OK;
1222 NS_IMETHODIMP
1223 nsCanvasRenderingContext2D::SetFillStyle(nsIVariant* aStyle)
1225 return SetStyleFromVariant(aStyle, STYLE_FILL);
1228 NS_IMETHODIMP
1229 nsCanvasRenderingContext2D::GetFillStyle(nsIVariant** aStyle)
1231 nsresult rv;
1233 nsCOMPtr<nsIWritableVariant> var = do_CreateInstance("@mozilla.org/variant;1");
1234 if (!var)
1235 return NS_ERROR_FAILURE;
1236 rv = var->SetWritable(PR_TRUE);
1237 NS_ENSURE_SUCCESS(rv, rv);
1239 if (CurrentState().patternStyles[STYLE_FILL]) {
1240 rv = var->SetAsISupports(CurrentState().patternStyles[STYLE_FILL]);
1241 NS_ENSURE_SUCCESS(rv, rv);
1242 } else if (CurrentState().gradientStyles[STYLE_FILL]) {
1243 rv = var->SetAsISupports(CurrentState().gradientStyles[STYLE_FILL]);
1244 NS_ENSURE_SUCCESS(rv, rv);
1245 } else {
1246 nsString styleStr;
1247 StyleColorToString(CurrentState().colorStyles[STYLE_FILL], styleStr);
1249 rv = var->SetAsDOMString(styleStr);
1250 NS_ENSURE_SUCCESS(rv, rv);
1253 *aStyle = var.forget().get();
1254 return NS_OK;
1258 // gradients and patterns
1260 NS_IMETHODIMP
1261 nsCanvasRenderingContext2D::CreateLinearGradient(float x0, float y0, float x1, float y1,
1262 nsIDOMCanvasGradient **_retval)
1264 if (!FloatValidate(x0,y0,x1,y1))
1265 return NS_ERROR_DOM_SYNTAX_ERR;
1267 nsRefPtr<gfxPattern> gradpat = new gfxPattern(x0, y0, x1, y1);
1268 if (!gradpat)
1269 return NS_ERROR_OUT_OF_MEMORY;
1271 nsRefPtr<nsIDOMCanvasGradient> grad = new nsCanvasGradient(gradpat, mCSSParser);
1272 if (!grad)
1273 return NS_ERROR_OUT_OF_MEMORY;
1275 *_retval = grad.forget().get();
1276 return NS_OK;
1279 NS_IMETHODIMP
1280 nsCanvasRenderingContext2D::CreateRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1,
1281 nsIDOMCanvasGradient **_retval)
1283 if (!FloatValidate(x0,y0,r0,x1,y1,r1))
1284 return NS_ERROR_DOM_SYNTAX_ERR;
1286 nsRefPtr<gfxPattern> gradpat = new gfxPattern(x0, y0, r0, x1, y1, r1);
1287 if (!gradpat)
1288 return NS_ERROR_OUT_OF_MEMORY;
1290 nsRefPtr<nsIDOMCanvasGradient> grad = new nsCanvasGradient(gradpat, mCSSParser);
1291 if (!grad)
1292 return NS_ERROR_OUT_OF_MEMORY;
1294 *_retval = grad.forget().get();
1295 return NS_OK;
1298 NS_IMETHODIMP
1299 nsCanvasRenderingContext2D::CreatePattern(nsIDOMHTMLElement *image,
1300 const nsAString& repeat,
1301 nsIDOMCanvasPattern **_retval)
1303 nsresult rv;
1304 gfxPattern::GraphicsExtend extend;
1306 if (repeat.IsEmpty() || repeat.EqualsLiteral("repeat")) {
1307 extend = gfxPattern::EXTEND_REPEAT;
1308 } else if (repeat.EqualsLiteral("repeat-x")) {
1309 // XX
1310 extend = gfxPattern::EXTEND_REPEAT;
1311 } else if (repeat.EqualsLiteral("repeat-y")) {
1312 // XX
1313 extend = gfxPattern::EXTEND_REPEAT;
1314 } else if (repeat.EqualsLiteral("no-repeat")) {
1315 extend = gfxPattern::EXTEND_NONE;
1316 } else {
1317 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
1318 return NS_ERROR_DOM_SYNTAX_ERR;
1321 PRInt32 imgWidth, imgHeight;
1322 nsCOMPtr<nsIPrincipal> principal;
1323 PRBool forceWriteOnly = PR_FALSE;
1324 nsRefPtr<gfxASurface> imgsurf;
1325 rv = ThebesSurfaceFromElement(image, PR_TRUE,
1326 getter_AddRefs(imgsurf), &imgWidth, &imgHeight,
1327 getter_AddRefs(principal), &forceWriteOnly);
1328 if (NS_FAILED(rv))
1329 return rv;
1331 nsRefPtr<gfxPattern> thebespat = new gfxPattern(imgsurf);
1333 thebespat->SetExtend(extend);
1335 nsRefPtr<nsCanvasPattern> pat = new nsCanvasPattern(thebespat, principal,
1336 forceWriteOnly);
1337 if (!pat)
1338 return NS_ERROR_OUT_OF_MEMORY;
1340 *_retval = pat.forget().get();
1341 return NS_OK;
1345 // shadows
1347 NS_IMETHODIMP
1348 nsCanvasRenderingContext2D::SetShadowOffsetX(float x)
1350 if (!FloatValidate(x))
1351 return NS_ERROR_DOM_SYNTAX_ERR;
1352 CurrentState().shadowOffset.x = x;
1353 return NS_OK;
1356 NS_IMETHODIMP
1357 nsCanvasRenderingContext2D::GetShadowOffsetX(float *x)
1359 *x = static_cast<float>(CurrentState().shadowOffset.x);
1360 return NS_OK;
1363 NS_IMETHODIMP
1364 nsCanvasRenderingContext2D::SetShadowOffsetY(float y)
1366 if (!FloatValidate(y))
1367 return NS_ERROR_DOM_SYNTAX_ERR;
1368 CurrentState().shadowOffset.y = y;
1369 return NS_OK;
1372 NS_IMETHODIMP
1373 nsCanvasRenderingContext2D::GetShadowOffsetY(float *y)
1375 *y = static_cast<float>(CurrentState().shadowOffset.y);
1376 return NS_OK;
1379 NS_IMETHODIMP
1380 nsCanvasRenderingContext2D::SetShadowBlur(float blur)
1382 if (!FloatValidate(blur))
1383 return NS_ERROR_DOM_SYNTAX_ERR;
1384 if (blur < 0.0)
1385 return NS_OK;
1386 CurrentState().shadowBlur = blur;
1387 return NS_OK;
1390 NS_IMETHODIMP
1391 nsCanvasRenderingContext2D::GetShadowBlur(float *blur)
1393 *blur = CurrentState().shadowBlur;
1394 return NS_OK;
1397 NS_IMETHODIMP
1398 nsCanvasRenderingContext2D::SetShadowColor(const nsAString& colorstr)
1400 nscolor color;
1402 nsresult rv = mCSSParser->ParseColorString(nsString(colorstr), nsnull, 0, &color);
1403 if (NS_FAILED(rv)) {
1404 // Error reporting happens inside the CSS parser
1405 return NS_OK;
1408 CurrentState().SetColorStyle(STYLE_SHADOW, color);
1410 mDirtyStyle[STYLE_SHADOW] = PR_TRUE;
1412 return NS_OK;
1415 NS_IMETHODIMP
1416 nsCanvasRenderingContext2D::GetShadowColor(nsAString& color)
1418 StyleColorToString(CurrentState().colorStyles[STYLE_SHADOW], color);
1420 return NS_OK;
1423 static void
1424 CopyContext(gfxContext* dest, gfxContext* src)
1426 dest->Multiply(src->CurrentMatrix());
1428 nsRefPtr<gfxPath> path = src->CopyPath();
1429 dest->NewPath();
1430 dest->AppendPath(path);
1432 nsRefPtr<gfxPattern> pattern = src->GetPattern();
1433 dest->SetPattern(pattern);
1435 dest->SetLineWidth(src->CurrentLineWidth());
1436 dest->SetLineCap(src->CurrentLineCap());
1437 dest->SetLineJoin(src->CurrentLineJoin());
1438 dest->SetMiterLimit(src->CurrentMiterLimit());
1439 dest->SetFillRule(src->CurrentFillRule());
1441 dest->SetAntialiasMode(src->CurrentAntialiasMode());
1444 static const gfxFloat SIGMA_MAX = 25;
1446 gfxContext*
1447 nsCanvasRenderingContext2D::ShadowInitialize(const gfxRect& extents, gfxAlphaBoxBlur& blur)
1449 gfxIntSize blurRadius;
1451 gfxFloat sigma = CurrentState().shadowBlur > 8 ? sqrt(CurrentState().shadowBlur) : CurrentState().shadowBlur / 2;
1452 // limit to avoid overly huge temp images
1453 if (sigma > SIGMA_MAX)
1454 sigma = SIGMA_MAX;
1455 blurRadius = gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(sigma, sigma));
1457 // calculate extents
1458 gfxRect drawExtents = extents;
1460 // intersect with clip to avoid making overly huge temp images
1461 gfxMatrix matrix = mThebes->CurrentMatrix();
1462 mThebes->IdentityMatrix();
1463 gfxRect clipExtents = mThebes->GetClipExtents();
1464 mThebes->SetMatrix(matrix);
1465 // outset by the blur radius so that blurs can leak onto the canvas even
1466 // when the shape is outside the clipping area
1467 clipExtents.Outset(blurRadius.height, blurRadius.width,
1468 blurRadius.height, blurRadius.width);
1469 drawExtents = drawExtents.Intersect(clipExtents - CurrentState().shadowOffset);
1471 gfxContext* ctx = blur.Init(drawExtents, blurRadius);
1473 if (!ctx)
1474 return nsnull;
1476 return ctx;
1479 void
1480 nsCanvasRenderingContext2D::ShadowFinalize(gfxAlphaBoxBlur& blur)
1482 ApplyStyle(STYLE_SHADOW);
1483 // canvas matrix was already applied, don't apply it twice, but do
1484 // apply the shadow offset
1485 gfxMatrix matrix = mThebes->CurrentMatrix();
1486 mThebes->IdentityMatrix();
1487 mThebes->Translate(CurrentState().shadowOffset);
1489 blur.Paint(mThebes);
1490 mThebes->SetMatrix(matrix);
1493 nsresult
1494 nsCanvasRenderingContext2D::DrawPath(Style style)
1497 * Need an intermediate surface when:
1498 * - globalAlpha != 1 and gradients/patterns are used (need to paint_with_alpha)
1499 * - certain operators are used and are not on mac (quartz/cairo composite operators don't quite line up)
1501 PRBool doUseIntermediateSurface = NeedToUseIntermediateSurface() ||
1502 NeedIntermediateSurfaceToHandleGlobalAlpha(style);
1504 PRBool doDrawShadow = NeedToDrawShadow();
1506 if (doDrawShadow) {
1507 gfxMatrix matrix = mThebes->CurrentMatrix();
1508 mThebes->IdentityMatrix();
1510 // calculate extents of path
1511 gfxRect drawExtents;
1512 if (style == STYLE_FILL)
1513 drawExtents = mThebes->GetUserFillExtent();
1514 else // STYLE_STROKE
1515 drawExtents = mThebes->GetUserStrokeExtent();
1517 mThebes->SetMatrix(matrix);
1519 gfxAlphaBoxBlur blur;
1521 // no need for a ref here, the blur owns the context
1522 gfxContext* ctx = ShadowInitialize(drawExtents, blur);
1523 if (ctx) {
1524 ApplyStyle(style, PR_FALSE);
1525 CopyContext(ctx, mThebes);
1526 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
1528 if (style == STYLE_FILL)
1529 ctx->Fill();
1530 else
1531 ctx->Stroke();
1533 ShadowFinalize(blur);
1537 if (doUseIntermediateSurface) {
1538 nsRefPtr<gfxPath> path = mThebes->CopyPath();
1539 // if the path didn't copy correctly then we can't restore it, so bail
1540 if (!path)
1541 return NS_ERROR_FAILURE;
1543 // draw onto a pushed group
1544 mThebes->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
1546 // XXX for some reason clipping messes up the path when push/popping
1547 // copying the path seems to fix it, for unknown reasons
1548 mThebes->NewPath();
1549 mThebes->AppendPath(path);
1551 // don't want operators to be applied twice
1552 mThebes->SetOperator(gfxContext::OPERATOR_SOURCE);
1555 ApplyStyle(style);
1556 if (style == STYLE_FILL)
1557 mThebes->Fill();
1558 else
1559 mThebes->Stroke();
1561 if (doUseIntermediateSurface) {
1562 mThebes->PopGroupToSource();
1563 DirtyAllStyles();
1565 mThebes->Paint(CurrentState().StyleIsColor(style) ? 1.0 : CurrentState().globalAlpha);
1568 return NS_OK;
1572 // rects
1575 NS_IMETHODIMP
1576 nsCanvasRenderingContext2D::ClearRect(float x, float y, float w, float h)
1578 if (!FloatValidate(x,y,w,h))
1579 return NS_ERROR_DOM_SYNTAX_ERR;
1581 gfxContextPathAutoSaveRestore pathSR(mThebes);
1582 gfxContextAutoSaveRestore autoSR(mThebes);
1584 mThebes->SetOperator(gfxContext::OPERATOR_CLEAR);
1585 mThebes->NewPath();
1586 mThebes->Rectangle(gfxRect(x, y, w, h));
1587 mThebes->Fill();
1589 return Redraw();
1592 nsresult
1593 nsCanvasRenderingContext2D::DrawRect(const gfxRect& rect, Style style)
1595 if (!FloatValidate(rect.pos.x, rect.pos.y, rect.size.width, rect.size.height))
1596 return NS_ERROR_DOM_SYNTAX_ERR;
1598 gfxContextPathAutoSaveRestore pathSR(mThebes);
1600 mThebes->NewPath();
1601 mThebes->Rectangle(rect);
1603 nsresult rv = DrawPath(style);
1604 if (NS_FAILED(rv))
1605 return rv;
1607 return Redraw();
1610 NS_IMETHODIMP
1611 nsCanvasRenderingContext2D::FillRect(float x, float y, float w, float h)
1613 return DrawRect(gfxRect(x, y, w, h), STYLE_FILL);
1616 NS_IMETHODIMP
1617 nsCanvasRenderingContext2D::StrokeRect(float x, float y, float w, float h)
1619 return DrawRect(gfxRect(x, y, w, h), STYLE_STROKE);
1623 // path bits
1626 NS_IMETHODIMP
1627 nsCanvasRenderingContext2D::BeginPath()
1629 mThebes->NewPath();
1630 return NS_OK;
1633 NS_IMETHODIMP
1634 nsCanvasRenderingContext2D::ClosePath()
1636 mThebes->ClosePath();
1637 return NS_OK;
1640 NS_IMETHODIMP
1641 nsCanvasRenderingContext2D::Fill()
1643 nsresult rv = DrawPath(STYLE_FILL);
1644 if (NS_FAILED(rv))
1645 return rv;
1646 return Redraw();
1649 NS_IMETHODIMP
1650 nsCanvasRenderingContext2D::Stroke()
1652 nsresult rv = DrawPath(STYLE_STROKE);
1653 if (NS_FAILED(rv))
1654 return rv;
1655 return Redraw();
1658 NS_IMETHODIMP
1659 nsCanvasRenderingContext2D::Clip()
1661 mThebes->Clip();
1662 return NS_OK;
1665 NS_IMETHODIMP
1666 nsCanvasRenderingContext2D::MoveTo(float x, float y)
1668 if (!FloatValidate(x,y))
1669 return NS_ERROR_DOM_SYNTAX_ERR;
1671 mThebes->MoveTo(gfxPoint(x, y));
1672 return NS_OK;
1675 NS_IMETHODIMP
1676 nsCanvasRenderingContext2D::LineTo(float x, float y)
1678 if (!FloatValidate(x,y))
1679 return NS_ERROR_DOM_SYNTAX_ERR;
1681 mThebes->LineTo(gfxPoint(x, y));
1682 return NS_OK;
1685 NS_IMETHODIMP
1686 nsCanvasRenderingContext2D::QuadraticCurveTo(float cpx, float cpy, float x, float y)
1688 if (!FloatValidate(cpx,cpy,x,y))
1689 return NS_ERROR_DOM_SYNTAX_ERR;
1691 // we will always have a current point, since beginPath forces
1692 // a moveto(0,0)
1693 gfxPoint c = mThebes->CurrentPoint();
1694 gfxPoint p(x,y);
1695 gfxPoint cp(cpx, cpy);
1697 mThebes->CurveTo((c+cp*2)/3.0, (p+cp*2)/3.0, p);
1699 return NS_OK;
1702 NS_IMETHODIMP
1703 nsCanvasRenderingContext2D::BezierCurveTo(float cp1x, float cp1y,
1704 float cp2x, float cp2y,
1705 float x, float y)
1707 if (!FloatValidate(cp1x,cp1y,cp2x,cp2y,x,y))
1708 return NS_ERROR_DOM_SYNTAX_ERR;
1710 mThebes->CurveTo(gfxPoint(cp1x, cp1y),
1711 gfxPoint(cp2x, cp2y),
1712 gfxPoint(x, y));
1714 return NS_OK;
1717 NS_IMETHODIMP
1718 nsCanvasRenderingContext2D::ArcTo(float x1, float y1, float x2, float y2, float radius)
1720 if (!FloatValidate(x1,y1,x2,y2,radius))
1721 return NS_ERROR_DOM_SYNTAX_ERR;
1723 if (radius <= 0)
1724 return NS_ERROR_DOM_INDEX_SIZE_ERR;
1726 /* This is an adaptation of the cairo_arc_to patch from Behdad
1727 * Esfahbod; once that patch is accepted, we should remove this
1728 * and just call cairo_arc_to() directly.
1731 double angle0, angle1, angle2, angled;
1732 double d0, d2;
1733 double sin_, cos_;
1734 double dc;
1735 int forward;
1737 gfxPoint p0 = mThebes->CurrentPoint();
1739 angle0 = atan2 (p0.y - y1, p0.x - x1); /* angle from (x1,y1) to (p0.x,p0.y) */
1740 angle2 = atan2 (y2 - y1, x2 - x1); /* angle from (x1,y1) to (x2,y2) */
1741 angle1 = (angle0 + angle2) / 2; /* angle from (x1,y1) to (xc,yc) */
1743 angled = angle2 - angle0; /* the angle (p0.x,p0.y)--(x1,y1)--(x2,y2) */
1745 /* Shall we go forward or backward? */
1746 if (angled > M_PI || (angled < 0 && angled > -M_PI)) {
1747 angle1 += M_PI;
1748 angled = 2 * M_PI - angled;
1749 forward = 1;
1750 } else {
1751 double tmp;
1752 tmp = angle0;
1753 angle0 = angle2;
1754 angle2 = tmp;
1755 forward = 0;
1758 angle0 += M_PI_2; /* angle from (xc,yc) to (p0.x,p0.y) */
1759 angle2 -= M_PI_2; /* angle from (xc,yc) to (x2,y2) */
1760 angled /= 2; /* the angle (p0.x,p0.y)--(x1,y1)--(xc,yc) */
1763 /* distance from (x1,y1) to (p0.x,p0.y) */
1764 d0 = sqrt ((p0.x-x1)*(p0.x-x1)+(p0.y-y1)*(p0.y-y1));
1765 /* distance from (x2,y2) to (p0.x,p0.y) */
1766 d2 = sqrt ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
1768 dc = -1;
1769 sin_ = sin(angled);
1770 cos_ = cos(angled);
1771 if (fabs(cos_) >= 1e-5) { /* the arc may not fit */
1772 /* min distance of end-points from corner */
1773 double min_d = d0 < d2 ? d0 : d2;
1774 /* max radius of an arc that fits */
1775 double max_r = min_d * sin_ / cos_;
1777 if (radius > max_r) {
1778 /* arc with requested radius doesn't fit */
1779 radius = (float) max_r;
1780 dc = min_d / cos_; /* distance of (xc,yc) from (x1,y1) */
1784 if (dc < 0)
1785 dc = radius / sin_; /* distance of (xc,yc) from (x1,y1) */
1788 /* find (cx,cy), the center of the arc */
1789 gfxPoint c(x1 + sin(angle1) * dc, y1 + cos(angle1) * dc);
1791 /* the arc operation draws the line from current point (p0.x,p0.y)
1792 * to arc center too. */
1794 if (forward)
1795 mThebes->Arc(c, radius, angle0, angle2);
1796 else
1797 mThebes->NegativeArc(c, radius, angle2, angle0);
1799 mThebes->LineTo(gfxPoint(x2, y2));
1801 return NS_OK;
1804 NS_IMETHODIMP
1805 nsCanvasRenderingContext2D::Arc(float x, float y, float r, float startAngle, float endAngle, int ccw)
1807 if (!FloatValidate(x,y,r,startAngle,endAngle))
1808 return NS_ERROR_DOM_SYNTAX_ERR;
1810 gfxPoint p(x,y);
1812 if (ccw)
1813 mThebes->NegativeArc(p, r, startAngle, endAngle);
1814 else
1815 mThebes->Arc(p, r, startAngle, endAngle);
1816 return NS_OK;
1819 NS_IMETHODIMP
1820 nsCanvasRenderingContext2D::Rect(float x, float y, float w, float h)
1822 if (!FloatValidate(x,y,w,h))
1823 return NS_ERROR_DOM_SYNTAX_ERR;
1825 mThebes->Rectangle(gfxRect(x, y, w, h));
1826 return NS_OK;
1830 // text
1834 * Helper function for SetFont that creates a style rule for the given font.
1835 * @param aFont The CSS font string
1836 * @param aCSSParser The CSS parser of the canvas rendering context
1837 * @param aNode The canvas element
1838 * @param aResult Pointer in which to place the new style rule.
1839 * @remark Assumes all pointer arguments are non-null.
1841 static nsresult
1842 CreateFontStyleRule(const nsAString& aFont,
1843 nsICSSParser* aCSSParser,
1844 nsINode* aNode,
1845 nsICSSStyleRule** aResult)
1847 nsresult rv;
1849 nsCOMPtr<nsICSSStyleRule> rule;
1850 PRBool changed;
1852 nsIPrincipal* principal = aNode->NodePrincipal();
1853 nsIDocument* document = aNode->GetOwnerDoc();
1855 nsIURI* docURL = document->GetDocumentURI();
1856 nsIURI* baseURL = document->GetBaseURI();
1858 rv = aCSSParser->ParseStyleAttribute(
1859 EmptyString(),
1860 docURL,
1861 baseURL,
1862 principal,
1863 getter_AddRefs(rule));
1864 if (NS_FAILED(rv))
1865 return rv;
1867 rv = aCSSParser->ParseProperty(eCSSProperty_font,
1868 aFont,
1869 docURL,
1870 baseURL,
1871 principal,
1872 rule->GetDeclaration(),
1873 &changed);
1874 if (NS_FAILED(rv))
1875 return rv;
1877 // set line height to normal, as per spec
1878 rv = aCSSParser->ParseProperty(eCSSProperty_line_height,
1879 NS_LITERAL_STRING("normal"),
1880 docURL,
1881 baseURL,
1882 principal,
1883 rule->GetDeclaration(),
1884 &changed);
1885 if (NS_FAILED(rv))
1886 return rv;
1888 rule.forget(aResult);
1889 return NS_OK;
1892 NS_IMETHODIMP
1893 nsCanvasRenderingContext2D::SetFont(const nsAString& font)
1895 nsresult rv;
1898 * If font is defined with relative units (e.g. ems) and the parent
1899 * style context changes in between calls, setting the font to the
1900 * same value as previous could result in a different computed value,
1901 * so we cannot have the optimization where we check if the new font
1902 * string is equal to the old one.
1905 nsCOMPtr<nsIContent> content = do_QueryInterface(mCanvasElement);
1906 if (!content) {
1907 NS_WARNING("Canvas element must be an nsIContent and non-null");
1908 return NS_ERROR_FAILURE;
1911 nsIDocument* document = content->GetOwnerDoc();
1913 nsIPresShell* presShell = document->GetPrimaryShell();
1914 if (!presShell)
1915 return NS_ERROR_FAILURE;
1917 nsCString langGroup;
1918 presShell->GetPresContext()->GetLangGroup()->ToUTF8String(langGroup);
1920 nsCOMArray<nsIStyleRule> rules;
1922 nsCOMPtr<nsICSSStyleRule> rule;
1923 rv = CreateFontStyleRule(font, mCSSParser.get(), content.get(), getter_AddRefs(rule));
1924 if (NS_FAILED(rv))
1925 return rv;
1927 rules.AppendObject(rule);
1929 nsStyleSet* styleSet = presShell->StyleSet();
1931 // have to get a parent style context for inherit-like relative
1932 // values (2em, bolder, etc.)
1933 nsRefPtr<nsStyleContext> parentContext;
1935 if (content->IsInDoc()) {
1936 // inherit from the canvas element
1937 parentContext = nsInspectorCSSUtils::GetStyleContextForContent(
1938 content,
1939 nsnull,
1940 presShell);
1941 } else {
1942 // otherwise inherit from default (10px sans-serif)
1943 nsCOMPtr<nsICSSStyleRule> parentRule;
1944 rv = CreateFontStyleRule(NS_LITERAL_STRING("10px sans-serif"),
1945 mCSSParser.get(),
1946 content.get(),
1947 getter_AddRefs(parentRule));
1948 if (NS_FAILED(rv))
1949 return rv;
1950 nsCOMArray<nsIStyleRule> parentRules;
1951 parentRules.AppendObject(parentRule);
1952 parentContext = styleSet->ResolveStyleForRules(nsnull, parentRules);
1955 if (!parentContext)
1956 return NS_ERROR_FAILURE;
1958 nsRefPtr<nsStyleContext> sc = styleSet->ResolveStyleForRules(parentContext, rules);
1959 if (!sc)
1960 return NS_ERROR_FAILURE;
1961 const nsStyleFont* fontStyle = sc->GetStyleFont();
1963 NS_ASSERTION(fontStyle, "Could not obtain font style");
1965 // use CSS pixels instead of dev pixels to avoid being affected by page zoom
1966 const PRUint32 aupcp = nsPresContext::AppUnitsPerCSSPixel();
1967 // un-zoom the font size to avoid being affected by text-only zoom
1968 const nscoord fontSize = nsStyleFont::UnZoomText(parentContext->PresContext(), fontStyle->mFont.size);
1970 gfxFontStyle style(fontStyle->mFont.style,
1971 fontStyle->mFont.weight,
1972 NSAppUnitsToFloatPixels(fontSize, aupcp),
1973 langGroup,
1974 fontStyle->mFont.sizeAdjust,
1975 fontStyle->mFont.systemFont,
1976 fontStyle->mFont.familyNameQuirks);
1978 CurrentState().fontGroup = gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name, &style, presShell->GetPresContext()->GetUserFontSet());
1979 NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
1980 CurrentState().font = font;
1981 return NS_OK;
1984 NS_IMETHODIMP
1985 nsCanvasRenderingContext2D::GetFont(nsAString& font)
1987 /* will initilize the value if not set, else does nothing */
1988 GetCurrentFontStyle();
1990 font = CurrentState().font;
1991 return NS_OK;
1994 NS_IMETHODIMP
1995 nsCanvasRenderingContext2D::SetTextAlign(const nsAString& ta)
1997 if (ta.EqualsLiteral("start"))
1998 CurrentState().textAlign = TEXT_ALIGN_START;
1999 else if (ta.EqualsLiteral("end"))
2000 CurrentState().textAlign = TEXT_ALIGN_END;
2001 else if (ta.EqualsLiteral("left"))
2002 CurrentState().textAlign = TEXT_ALIGN_LEFT;
2003 else if (ta.EqualsLiteral("right"))
2004 CurrentState().textAlign = TEXT_ALIGN_RIGHT;
2005 else if (ta.EqualsLiteral("center"))
2006 CurrentState().textAlign = TEXT_ALIGN_CENTER;
2007 // spec says to not throw error for invalid arg, but do it anyway
2008 else
2009 return NS_ERROR_INVALID_ARG;
2011 return NS_OK;
2014 NS_IMETHODIMP
2015 nsCanvasRenderingContext2D::GetTextAlign(nsAString& ta)
2017 switch (CurrentState().textAlign)
2019 case TEXT_ALIGN_START:
2020 ta.AssignLiteral("start");
2021 break;
2022 case TEXT_ALIGN_END:
2023 ta.AssignLiteral("end");
2024 break;
2025 case TEXT_ALIGN_LEFT:
2026 ta.AssignLiteral("left");
2027 break;
2028 case TEXT_ALIGN_RIGHT:
2029 ta.AssignLiteral("right");
2030 break;
2031 case TEXT_ALIGN_CENTER:
2032 ta.AssignLiteral("center");
2033 break;
2034 default:
2035 NS_ASSERTION(0, "textAlign holds invalid value");
2036 return NS_ERROR_FAILURE;
2039 return NS_OK;
2042 NS_IMETHODIMP
2043 nsCanvasRenderingContext2D::SetTextBaseline(const nsAString& tb)
2045 if (tb.EqualsLiteral("top"))
2046 CurrentState().textBaseline = TEXT_BASELINE_TOP;
2047 else if (tb.EqualsLiteral("hanging"))
2048 CurrentState().textBaseline = TEXT_BASELINE_HANGING;
2049 else if (tb.EqualsLiteral("middle"))
2050 CurrentState().textBaseline = TEXT_BASELINE_MIDDLE;
2051 else if (tb.EqualsLiteral("alphabetic"))
2052 CurrentState().textBaseline = TEXT_BASELINE_ALPHABETIC;
2053 else if (tb.EqualsLiteral("ideographic"))
2054 CurrentState().textBaseline = TEXT_BASELINE_IDEOGRAPHIC;
2055 else if (tb.EqualsLiteral("bottom"))
2056 CurrentState().textBaseline = TEXT_BASELINE_BOTTOM;
2057 // spec says to not throw error for invalid arg, but do it anyway
2058 else
2059 return NS_ERROR_INVALID_ARG;
2061 return NS_OK;
2064 NS_IMETHODIMP
2065 nsCanvasRenderingContext2D::GetTextBaseline(nsAString& tb)
2067 switch (CurrentState().textBaseline)
2069 case TEXT_BASELINE_TOP:
2070 tb.AssignLiteral("top");
2071 break;
2072 case TEXT_BASELINE_HANGING:
2073 tb.AssignLiteral("hanging");
2074 break;
2075 case TEXT_BASELINE_MIDDLE:
2076 tb.AssignLiteral("middle");
2077 break;
2078 case TEXT_BASELINE_ALPHABETIC:
2079 tb.AssignLiteral("alphabetic");
2080 break;
2081 case TEXT_BASELINE_IDEOGRAPHIC:
2082 tb.AssignLiteral("ideographic");
2083 break;
2084 case TEXT_BASELINE_BOTTOM:
2085 tb.AssignLiteral("bottom");
2086 break;
2087 default:
2088 NS_ASSERTION(0, "textBaseline holds invalid value");
2089 return NS_ERROR_FAILURE;
2092 return NS_OK;
2096 * Helper function that replaces the whitespace characters in a string
2097 * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE,
2098 * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE
2099 * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR).
2100 * @param str The string whose whitespace characters to replace.
2102 static inline void
2103 TextReplaceWhitespaceCharacters(nsAutoString& str)
2105 str.ReplaceChar("\x09\x0A\x0B\x0C\x0D", PRUnichar(' '));
2108 NS_IMETHODIMP
2109 nsCanvasRenderingContext2D::FillText(const nsAString& text, float x, float y, float maxWidth)
2111 return DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL, nsnull);
2114 NS_IMETHODIMP
2115 nsCanvasRenderingContext2D::StrokeText(const nsAString& text, float x, float y, float maxWidth)
2117 return DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE, nsnull);
2120 NS_IMETHODIMP
2121 nsCanvasRenderingContext2D::MeasureText(const nsAString& rawText,
2122 nsIDOMTextMetrics** _retval)
2124 float width;
2126 nsresult rv = DrawOrMeasureText(rawText, 0, 0, 0, TEXT_DRAW_OPERATION_MEASURE, &width);
2128 if (NS_FAILED(rv))
2129 return rv;
2131 nsRefPtr<nsIDOMTextMetrics> textMetrics = new nsTextMetrics(width);
2132 if (!textMetrics.get())
2133 return NS_ERROR_OUT_OF_MEMORY;
2135 *_retval = textMetrics.forget().get();
2137 return NS_OK;
2141 * Used for nsBidiPresUtils::ProcessText
2143 struct NS_STACK_CLASS nsCanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
2145 virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
2147 mTextRun = gfxTextRunCache::MakeTextRun(text,
2148 length,
2149 mFontgrp,
2150 mThebes,
2151 mAppUnitsPerDevPixel,
2152 direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
2155 virtual nscoord GetWidth()
2157 gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
2158 mTextRun->GetLength(),
2159 mDoMeasureBoundingBox,
2160 mThebes,
2161 nsnull);
2163 // this only measures the height; the total width is gotten from the
2164 // the return value of ProcessText.
2165 if (mDoMeasureBoundingBox) {
2166 textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel);
2167 mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox);
2170 return static_cast<nscoord>(textRunMetrics.mAdvanceWidth/gfxFloat(mAppUnitsPerDevPixel));
2173 virtual void DrawText(nscoord xOffset, nscoord width)
2175 gfxPoint point = mPt;
2176 point.x += xOffset * mAppUnitsPerDevPixel;
2178 // offset is given in terms of left side of string
2179 if (mTextRun->IsRightToLeft())
2180 point.x += width * mAppUnitsPerDevPixel;
2182 // stroke or fill the text depending on operation
2183 if (mOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE)
2184 mTextRun->DrawToPath(mThebes,
2185 point,
2187 mTextRun->GetLength(),
2188 nsnull,
2189 nsnull);
2190 else
2191 // mOp == TEXT_DRAW_OPERATION_FILL
2192 mTextRun->Draw(mThebes,
2193 point,
2195 mTextRun->GetLength(),
2196 nsnull,
2197 nsnull,
2198 nsnull);
2201 // current text run
2202 gfxTextRunCache::AutoTextRun mTextRun;
2204 // pointer to the context, may not be the canvas's context
2205 // if an intermediate surface is being used
2206 gfxContext* mThebes;
2208 // position of the left side of the string, alphabetic baseline
2209 gfxPoint mPt;
2211 // current font
2212 gfxFontGroup* mFontgrp;
2214 // dev pixel conversion factor
2215 PRUint32 mAppUnitsPerDevPixel;
2217 // operation (fill or stroke)
2218 nsCanvasRenderingContext2D::TextDrawOperation mOp;
2220 // union of bounding boxes of all runs, needed for shadows
2221 gfxRect mBoundingBox;
2223 // true iff the bounding box should be measured
2224 PRBool mDoMeasureBoundingBox;
2227 nsresult
2228 nsCanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
2229 float aX,
2230 float aY,
2231 float aMaxWidth,
2232 TextDrawOperation aOp,
2233 float* aWidth)
2235 nsresult rv;
2237 if (!FloatValidate(aX, aY, aMaxWidth))
2238 return NS_ERROR_DOM_SYNTAX_ERR;
2240 // spec isn't clear on what should happen if aMaxWidth <= 0, so
2241 // treat it as an invalid argument
2242 // technically, 0 should be an invalid value as well, but 0 is the default
2243 // arg, and there is no way to tell if the default was used
2244 if (aMaxWidth < 0)
2245 return NS_ERROR_INVALID_ARG;
2247 nsCOMPtr<nsIContent> content = do_QueryInterface(mCanvasElement);
2248 if (!content) {
2249 NS_WARNING("Canvas element must be an nsIContent and non-null");
2250 return NS_ERROR_FAILURE;
2253 nsIDocument* document = content->GetOwnerDoc();
2255 nsIPresShell* presShell = document->GetPrimaryShell();
2256 if (!presShell)
2257 return NS_ERROR_FAILURE;
2259 nsBidiPresUtils* bidiUtils = presShell->GetPresContext()->GetBidiUtils();
2260 if (!bidiUtils)
2261 return NS_ERROR_FAILURE;
2263 // replace all the whitespace characters with U+0020 SPACE
2264 nsAutoString textToDraw(aRawText);
2265 TextReplaceWhitespaceCharacters(textToDraw);
2267 // for now, default to ltr if not in doc
2268 PRBool isRTL = PR_FALSE;
2270 if (content->IsInDoc()) {
2271 // try to find the closest context
2272 nsRefPtr<nsStyleContext> canvasStyle =
2273 nsInspectorCSSUtils::GetStyleContextForContent(content,
2274 nsnull,
2275 presShell);
2276 if (!canvasStyle)
2277 return NS_ERROR_FAILURE;
2278 isRTL = canvasStyle->GetStyleVisibility()->mDirection ==
2279 NS_STYLE_DIRECTION_RTL;
2282 // don't need to take care of these with stroke since Stroke() does that
2283 PRBool doDrawShadow = aOp == TEXT_DRAW_OPERATION_FILL && NeedToDrawShadow();
2284 PRBool doUseIntermediateSurface = aOp == TEXT_DRAW_OPERATION_FILL &&
2285 (NeedToUseIntermediateSurface() || NeedIntermediateSurfaceToHandleGlobalAlpha(STYLE_FILL));
2287 nsCanvasBidiProcessor processor;
2289 GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, NULL);
2290 processor.mPt = gfxPoint(aX, aY);
2291 processor.mThebes = mThebes;
2292 processor.mOp = aOp;
2293 processor.mBoundingBox = gfxRect(0, 0, 0, 0);
2294 // need to measure size if using an intermediate surface for drawing
2295 processor.mDoMeasureBoundingBox = doDrawShadow;
2297 processor.mFontgrp = GetCurrentFontStyle();
2298 NS_ASSERTION(processor.mFontgrp, "font group is null");
2300 nscoord totalWidth;
2302 // calls bidi algo twice since it needs the full text width and the
2303 // bounding boxes before rendering anything
2304 rv = bidiUtils->ProcessText(textToDraw.get(),
2305 textToDraw.Length(),
2306 isRTL ? NSBIDI_RTL : NSBIDI_LTR,
2307 presShell->GetPresContext(),
2308 processor,
2309 nsBidiPresUtils::MODE_MEASURE,
2310 nsnull,
2312 &totalWidth);
2313 if (NS_FAILED(rv))
2314 return rv;
2316 if (aWidth)
2317 *aWidth = static_cast<float>(totalWidth);
2319 // if only measuring, don't need to do any more work
2320 if (aOp==TEXT_DRAW_OPERATION_MEASURE)
2321 return NS_OK;
2323 // offset pt.x based on text align
2324 gfxFloat anchorX;
2326 if (CurrentState().textAlign == TEXT_ALIGN_CENTER)
2327 anchorX = .5;
2328 else if (CurrentState().textAlign == TEXT_ALIGN_LEFT ||
2329 (!isRTL && CurrentState().textAlign == TEXT_ALIGN_START) ||
2330 (isRTL && CurrentState().textAlign == TEXT_ALIGN_END))
2331 anchorX = 0;
2332 else
2333 anchorX = 1;
2335 processor.mPt.x -= anchorX * totalWidth;
2337 // offset pt.y based on text baseline
2338 NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts");
2339 const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics();
2341 gfxFloat anchorY;
2343 switch (CurrentState().textBaseline)
2345 case TEXT_BASELINE_TOP:
2346 anchorY = fontMetrics.emAscent;
2347 break;
2348 case TEXT_BASELINE_HANGING:
2349 anchorY = 0; // currently unavailable
2350 break;
2351 case TEXT_BASELINE_MIDDLE:
2352 anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
2353 break;
2354 case TEXT_BASELINE_ALPHABETIC:
2355 anchorY = 0;
2356 break;
2357 case TEXT_BASELINE_IDEOGRAPHIC:
2358 anchorY = 0; // currently unvailable
2359 break;
2360 case TEXT_BASELINE_BOTTOM:
2361 anchorY = -fontMetrics.emDescent;
2362 break;
2363 default:
2364 NS_ASSERTION(0, "mTextBaseline holds invalid value");
2365 return NS_ERROR_FAILURE;
2368 processor.mPt.y += anchorY;
2370 // correct bounding box to get it to be the correct size/position
2371 processor.mBoundingBox.size.width = totalWidth;
2372 processor.mBoundingBox.MoveBy(processor.mPt);
2374 processor.mPt.x *= processor.mAppUnitsPerDevPixel;
2375 processor.mPt.y *= processor.mAppUnitsPerDevPixel;
2377 // if text is over aMaxWidth, then scale the text horizontally such that its
2378 // width is precisely aMaxWidth
2379 gfxContextAutoSaveRestore autoSR;
2380 if (aMaxWidth > 0 && totalWidth > aMaxWidth) {
2381 autoSR.SetContext(mThebes);
2382 // translate the anchor point to 0, then scale and translate back
2383 gfxPoint trans(aX, 0);
2384 mThebes->Translate(trans);
2385 mThebes->Scale(aMaxWidth/totalWidth, 1);
2386 mThebes->Translate(-trans);
2389 // don't ever need to measure the bounding box twice
2390 processor.mDoMeasureBoundingBox = PR_FALSE;
2392 if (doDrawShadow) {
2393 // for some reason the box is too tight, probably rounding error
2394 processor.mBoundingBox.Outset(2.0);
2396 // this is unnecessarily big is max-width scaling is involved, but it
2397 // will still produce correct output
2398 gfxRect drawExtents = mThebes->UserToDevice(processor.mBoundingBox);
2399 gfxAlphaBoxBlur blur;
2401 gfxContext* ctx = ShadowInitialize(drawExtents, blur);
2403 if (ctx) {
2404 CopyContext(ctx, mThebes);
2405 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
2406 processor.mThebes = ctx;
2408 rv = bidiUtils->ProcessText(textToDraw.get(),
2409 textToDraw.Length(),
2410 isRTL ? NSBIDI_RTL : NSBIDI_LTR,
2411 presShell->GetPresContext(),
2412 processor,
2413 nsBidiPresUtils::MODE_DRAW,
2414 nsnull,
2416 nsnull);
2417 if (NS_FAILED(rv))
2418 return rv;
2420 ShadowFinalize(blur);
2423 processor.mThebes = mThebes;
2426 gfxContextPathAutoSaveRestore pathSR(mThebes, PR_FALSE);
2428 // back up path if stroking
2429 if (aOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE)
2430 pathSR.Save();
2431 // doUseIntermediateSurface is mutually exclusive to op == STROKE
2432 else {
2433 if (doUseIntermediateSurface) {
2434 mThebes->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
2436 // don't want operators to be applied twice
2437 mThebes->SetOperator(gfxContext::OPERATOR_SOURCE);
2440 ApplyStyle(STYLE_FILL);
2443 rv = bidiUtils->ProcessText(textToDraw.get(),
2444 textToDraw.Length(),
2445 isRTL ? NSBIDI_RTL : NSBIDI_LTR,
2446 presShell->GetPresContext(),
2447 processor,
2448 nsBidiPresUtils::MODE_DRAW,
2449 nsnull,
2451 nsnull);
2453 // this needs to be restored before function can return
2454 if (doUseIntermediateSurface) {
2455 mThebes->PopGroupToSource();
2456 DirtyAllStyles();
2459 if (NS_FAILED(rv))
2460 return rv;
2462 if (aOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE) {
2463 // DrawPath takes care of all shadows and composite oddities
2464 rv = DrawPath(STYLE_STROKE);
2465 if (NS_FAILED(rv))
2466 return rv;
2467 } else if (doUseIntermediateSurface)
2468 mThebes->Paint(CurrentState().StyleIsColor(STYLE_FILL) ? 1.0 : CurrentState().globalAlpha);
2470 return Redraw();
2473 NS_IMETHODIMP
2474 nsCanvasRenderingContext2D::SetMozTextStyle(const nsAString& textStyle)
2476 // font and mozTextStyle are the same value
2477 return SetFont(textStyle);
2480 NS_IMETHODIMP
2481 nsCanvasRenderingContext2D::GetMozTextStyle(nsAString& textStyle)
2483 // font and mozTextStyle are the same value
2484 return GetFont(textStyle);
2487 gfxFontGroup *nsCanvasRenderingContext2D::GetCurrentFontStyle()
2489 // use lazy initilization for the font group since it's rather expensive
2490 if(!CurrentState().fontGroup) {
2491 #ifdef DEBUG
2492 nsresult res =
2493 #endif
2494 SetMozTextStyle(NS_LITERAL_STRING("10px sans-serif"));
2495 NS_ASSERTION(res == NS_OK, "Default canvas font is invalid");
2498 return CurrentState().fontGroup;
2501 NS_IMETHODIMP
2502 nsCanvasRenderingContext2D::MozDrawText(const nsAString& textToDraw)
2504 const PRUnichar* textdata;
2505 textToDraw.GetData(&textdata);
2507 PRUint32 textrunflags = 0;
2509 PRUint32 aupdp;
2510 GetAppUnitsValues(&aupdp, NULL);
2512 gfxTextRunCache::AutoTextRun textRun;
2513 textRun = gfxTextRunCache::MakeTextRun(textdata,
2514 textToDraw.Length(),
2515 GetCurrentFontStyle(),
2516 mThebes,
2517 aupdp,
2518 textrunflags);
2520 if(!textRun.get())
2521 return NS_ERROR_FAILURE;
2523 gfxPoint pt(0.0f,0.0f);
2525 // Fill color is text color
2526 ApplyStyle(STYLE_FILL);
2528 textRun->Draw(mThebes,
2530 /* offset = */ 0,
2531 textToDraw.Length(),
2532 nsnull,
2533 nsnull,
2534 nsnull);
2535 return NS_OK;
2538 NS_IMETHODIMP
2539 nsCanvasRenderingContext2D::MozMeasureText(const nsAString& textToMeasure, float *retVal)
2541 nsCOMPtr<nsIDOMTextMetrics> metrics;
2542 nsresult rv;
2543 rv = MeasureText(textToMeasure, getter_AddRefs(metrics));
2544 if (NS_FAILED(rv))
2545 return rv;
2546 return metrics->GetWidth(retVal);
2549 NS_IMETHODIMP
2550 nsCanvasRenderingContext2D::MozPathText(const nsAString& textToPath)
2552 const PRUnichar* textdata;
2553 textToPath.GetData(&textdata);
2555 PRUint32 textrunflags = 0;
2557 PRUint32 aupdp;
2558 GetAppUnitsValues(&aupdp, NULL);
2560 gfxTextRunCache::AutoTextRun textRun;
2561 textRun = gfxTextRunCache::MakeTextRun(textdata,
2562 textToPath.Length(),
2563 GetCurrentFontStyle(),
2564 mThebes,
2565 aupdp,
2566 textrunflags);
2568 if(!textRun.get())
2569 return NS_ERROR_FAILURE;
2571 gfxPoint pt(0.0f,0.0f);
2573 textRun->DrawToPath(mThebes,
2575 /* offset = */ 0,
2576 textToPath.Length(),
2577 nsnull,
2578 nsnull);
2579 return NS_OK;
2582 NS_IMETHODIMP
2583 nsCanvasRenderingContext2D::MozTextAlongPath(const nsAString& textToDraw, PRBool stroke)
2585 // Most of this code is copied from its svg equivalent
2586 nsRefPtr<gfxFlattenedPath> path(mThebes->GetFlattenedPath());
2588 const PRUnichar* textdata;
2589 textToDraw.GetData(&textdata);
2591 PRUint32 textrunflags = 0;
2593 PRUint32 aupdp;
2594 GetAppUnitsValues(&aupdp, NULL);
2596 gfxTextRunCache::AutoTextRun textRun;
2597 textRun = gfxTextRunCache::MakeTextRun(textdata,
2598 textToDraw.Length(),
2599 GetCurrentFontStyle(),
2600 mThebes,
2601 aupdp,
2602 textrunflags);
2604 if(!textRun.get())
2605 return NS_ERROR_FAILURE;
2607 struct PathChar
2609 PRBool draw;
2610 gfxFloat angle;
2611 gfxPoint pos;
2612 PathChar() : draw(PR_FALSE), angle(0.0), pos(0.0,0.0) {}
2615 gfxFloat length = path->GetLength();
2616 PRUint32 strLength = textToDraw.Length();
2618 PathChar *cp = new PathChar[strLength];
2620 if (!cp) {
2621 return NS_ERROR_OUT_OF_MEMORY;
2624 gfxPoint position(0.0,0.0);
2625 gfxFloat x = position.x;
2626 for (PRUint32 i = 0; i < strLength; i++)
2628 gfxFloat halfAdvance = textRun->GetAdvanceWidth(i, 1, nsnull) / (2.0 * aupdp);
2630 // Check for end of path
2631 if(x + halfAdvance > length)
2632 break;
2634 if(x + halfAdvance >= 0)
2636 cp[i].draw = PR_TRUE;
2637 gfxPoint pt = path->FindPoint(gfxPoint(x + halfAdvance, position.y), &(cp[i].angle));
2639 cp[i].pos = pt - gfxPoint(cos(cp[i].angle), sin(cp[i].angle)) * halfAdvance;
2641 x += 2 * halfAdvance;
2644 if(stroke)
2645 ApplyStyle(STYLE_STROKE);
2646 else
2647 ApplyStyle(STYLE_FILL);
2649 for(PRUint32 i = 0; i < strLength; i++)
2651 // Skip non-visible characters
2652 if(!cp[i].draw) continue;
2654 gfxMatrix matrix = mThebes->CurrentMatrix();
2656 gfxMatrix rot;
2657 rot.Rotate(cp[i].angle);
2658 mThebes->Multiply(rot);
2660 rot.Invert();
2661 rot.Scale(aupdp,aupdp);
2662 gfxPoint pt = rot.Transform(cp[i].pos);
2664 if(stroke) {
2665 textRun->DrawToPath(mThebes, pt, i, 1, nsnull, nsnull);
2666 } else {
2667 textRun->Draw(mThebes, pt, i, 1, nsnull, nsnull, nsnull);
2669 mThebes->SetMatrix(matrix);
2672 delete [] cp;
2674 return NS_OK;
2678 // line caps/joins
2680 NS_IMETHODIMP
2681 nsCanvasRenderingContext2D::SetLineWidth(float width)
2683 if (!FloatValidate(width))
2684 return NS_ERROR_DOM_SYNTAX_ERR;
2686 mThebes->SetLineWidth(width);
2687 return NS_OK;
2690 NS_IMETHODIMP
2691 nsCanvasRenderingContext2D::GetLineWidth(float *width)
2693 gfxFloat d = mThebes->CurrentLineWidth();
2694 *width = static_cast<float>(d);
2695 return NS_OK;
2698 NS_IMETHODIMP
2699 nsCanvasRenderingContext2D::SetLineCap(const nsAString& capstyle)
2701 gfxContext::GraphicsLineCap cap;
2703 if (capstyle.EqualsLiteral("butt"))
2704 cap = gfxContext::LINE_CAP_BUTT;
2705 else if (capstyle.EqualsLiteral("round"))
2706 cap = gfxContext::LINE_CAP_ROUND;
2707 else if (capstyle.EqualsLiteral("square"))
2708 cap = gfxContext::LINE_CAP_SQUARE;
2709 else
2710 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
2711 return NS_ERROR_NOT_IMPLEMENTED;
2713 mThebes->SetLineCap(cap);
2714 return NS_OK;
2717 NS_IMETHODIMP
2718 nsCanvasRenderingContext2D::GetLineCap(nsAString& capstyle)
2720 gfxContext::GraphicsLineCap cap = mThebes->CurrentLineCap();
2722 if (cap == gfxContext::LINE_CAP_BUTT)
2723 capstyle.AssignLiteral("butt");
2724 else if (cap == gfxContext::LINE_CAP_ROUND)
2725 capstyle.AssignLiteral("round");
2726 else if (cap == gfxContext::LINE_CAP_SQUARE)
2727 capstyle.AssignLiteral("square");
2728 else
2729 return NS_ERROR_FAILURE;
2731 return NS_OK;
2734 NS_IMETHODIMP
2735 nsCanvasRenderingContext2D::SetLineJoin(const nsAString& joinstyle)
2737 gfxContext::GraphicsLineJoin j;
2739 if (joinstyle.EqualsLiteral("round"))
2740 j = gfxContext::LINE_JOIN_ROUND;
2741 else if (joinstyle.EqualsLiteral("bevel"))
2742 j = gfxContext::LINE_JOIN_BEVEL;
2743 else if (joinstyle.EqualsLiteral("miter"))
2744 j = gfxContext::LINE_JOIN_MITER;
2745 else
2746 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
2747 return NS_ERROR_NOT_IMPLEMENTED;
2749 mThebes->SetLineJoin(j);
2750 return NS_OK;
2753 NS_IMETHODIMP
2754 nsCanvasRenderingContext2D::GetLineJoin(nsAString& joinstyle)
2756 gfxContext::GraphicsLineJoin j = mThebes->CurrentLineJoin();
2758 if (j == gfxContext::LINE_JOIN_ROUND)
2759 joinstyle.AssignLiteral("round");
2760 else if (j == gfxContext::LINE_JOIN_BEVEL)
2761 joinstyle.AssignLiteral("bevel");
2762 else if (j == gfxContext::LINE_JOIN_MITER)
2763 joinstyle.AssignLiteral("miter");
2764 else
2765 return NS_ERROR_FAILURE;
2767 return NS_OK;
2770 NS_IMETHODIMP
2771 nsCanvasRenderingContext2D::SetMiterLimit(float miter)
2773 if (!FloatValidate(miter))
2774 return NS_ERROR_DOM_SYNTAX_ERR;
2776 mThebes->SetMiterLimit(miter);
2777 return NS_OK;
2780 NS_IMETHODIMP
2781 nsCanvasRenderingContext2D::GetMiterLimit(float *miter)
2783 gfxFloat d = mThebes->CurrentMiterLimit();
2784 *miter = static_cast<float>(d);
2785 return NS_OK;
2788 NS_IMETHODIMP
2789 nsCanvasRenderingContext2D::IsPointInPath(float x, float y, PRBool *retVal)
2791 if (!FloatValidate(x,y))
2792 return NS_ERROR_DOM_SYNTAX_ERR;
2794 *retVal = mThebes->PointInFill(gfxPoint(x,y));
2795 return NS_OK;
2799 // image
2802 // drawImage(in HTMLImageElement image, in float dx, in float dy);
2803 // -- render image from 0,0 at dx,dy top-left coords
2804 // drawImage(in HTMLImageElement image, in float dx, in float dy, in float sw, in float sh);
2805 // -- render image from 0,0 at dx,dy top-left coords clipping it to sw,sh
2806 // 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);
2807 // -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas
2809 NS_IMETHODIMP
2810 nsCanvasRenderingContext2D::DrawImage()
2812 nsresult rv;
2814 // we can't do a security check without a canvas element, so
2815 // just skip this entirely
2816 if (!mCanvasElement)
2817 return NS_ERROR_FAILURE;
2819 nsAXPCNativeCallContext *ncc = nsnull;
2820 rv = nsContentUtils::XPConnect()->
2821 GetCurrentNativeCallContext(&ncc);
2822 NS_ENSURE_SUCCESS(rv, rv);
2824 if (!ncc)
2825 return NS_ERROR_FAILURE;
2827 JSContext *ctx = nsnull;
2829 rv = ncc->GetJSContext(&ctx);
2830 NS_ENSURE_SUCCESS(rv, rv);
2832 PRUint32 argc;
2833 jsval *argv = nsnull;
2835 ncc->GetArgc(&argc);
2836 ncc->GetArgvPtr(&argv);
2838 // we always need at least an image and a dx,dy
2839 if (argc < 3)
2840 return NS_ERROR_INVALID_ARG;
2842 JSAutoRequest ar(ctx);
2844 double sx,sy,sw,sh;
2845 double dx,dy,dw,dh;
2847 nsCOMPtr<nsIDOMElement> imgElt;
2848 if (!ConvertJSValToXPCObject(getter_AddRefs(imgElt),
2849 NS_GET_IID(nsIDOMElement),
2850 ctx, argv[0]))
2851 return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
2853 PRInt32 imgWidth, imgHeight;
2854 nsCOMPtr<nsIPrincipal> principal;
2855 PRBool forceWriteOnly = PR_FALSE;
2856 gfxMatrix matrix;
2857 nsRefPtr<gfxPattern> pattern;
2858 nsRefPtr<gfxPath> path;
2859 nsRefPtr<gfxASurface> imgsurf;
2860 rv = ThebesSurfaceFromElement(imgElt, PR_FALSE,
2861 getter_AddRefs(imgsurf), &imgWidth, &imgHeight,
2862 getter_AddRefs(principal), &forceWriteOnly);
2863 if (NS_FAILED(rv))
2864 return rv;
2865 DoDrawImageSecurityCheck(principal, forceWriteOnly);
2867 gfxContextPathAutoSaveRestore pathSR(mThebes, PR_FALSE);
2869 #define GET_ARG(dest,whicharg) \
2870 do { if (!ConvertJSValToDouble(dest, ctx, whicharg)) { rv = NS_ERROR_INVALID_ARG; goto FINISH; } } while (0)
2872 rv = NS_OK;
2874 if (argc == 3) {
2875 GET_ARG(&dx, argv[1]);
2876 GET_ARG(&dy, argv[2]);
2877 sx = sy = 0.0;
2878 dw = sw = (double) imgWidth;
2879 dh = sh = (double) imgHeight;
2880 } else if (argc == 5) {
2881 GET_ARG(&dx, argv[1]);
2882 GET_ARG(&dy, argv[2]);
2883 GET_ARG(&dw, argv[3]);
2884 GET_ARG(&dh, argv[4]);
2885 sx = sy = 0.0;
2886 sw = (double) imgWidth;
2887 sh = (double) imgHeight;
2888 } else if (argc == 9) {
2889 GET_ARG(&sx, argv[1]);
2890 GET_ARG(&sy, argv[2]);
2891 GET_ARG(&sw, argv[3]);
2892 GET_ARG(&sh, argv[4]);
2893 GET_ARG(&dx, argv[5]);
2894 GET_ARG(&dy, argv[6]);
2895 GET_ARG(&dw, argv[7]);
2896 GET_ARG(&dh, argv[8]);
2897 } else {
2898 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
2899 rv = NS_ERROR_INVALID_ARG;
2900 goto FINISH;
2902 #undef GET_ARG
2904 if (dw == 0.0 || dh == 0.0) {
2905 rv = NS_OK;
2906 // not really failure, but nothing to do --
2907 // and noone likes a divide-by-zero
2908 goto FINISH;
2911 if (!FloatValidate(sx,sy,sw,sh) || !FloatValidate(dx,dy,dw,dh)) {
2912 rv = NS_ERROR_DOM_SYNTAX_ERR;
2913 goto FINISH;
2916 // check args
2917 if (sx < 0.0 || sy < 0.0 ||
2918 sw < 0.0 || sw > (double) imgWidth ||
2919 sh < 0.0 || sh > (double) imgHeight ||
2920 dw < 0.0 || dh < 0.0)
2922 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
2923 rv = NS_ERROR_DOM_INDEX_SIZE_ERR;
2924 goto FINISH;
2927 matrix.Translate(gfxPoint(sx, sy));
2928 matrix.Scale(sw/dw, sh/dh);
2930 pattern = new gfxPattern(imgsurf);
2931 pattern->SetMatrix(matrix);
2933 pathSR.Save();
2936 gfxContextAutoSaveRestore autoSR(mThebes);
2937 mThebes->Translate(gfxPoint(dx, dy));
2938 mThebes->SetPattern(pattern);
2940 gfxRect clip(0, 0, dw, dh);
2942 if (NeedToDrawShadow()) {
2943 gfxRect drawExtents = mThebes->UserToDevice(clip);
2944 gfxAlphaBoxBlur blur;
2946 gfxContext* ctx = ShadowInitialize(drawExtents, blur);
2948 if (ctx) {
2949 CopyContext(ctx, mThebes);
2950 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
2951 ctx->Clip(clip);
2952 ctx->Paint();
2954 ShadowFinalize(blur);
2958 PRBool doUseIntermediateSurface = NeedToUseIntermediateSurface();
2960 mThebes->SetPattern(pattern);
2961 DirtyAllStyles();
2963 if (doUseIntermediateSurface) {
2964 // draw onto a pushed group
2965 mThebes->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
2966 mThebes->Clip(clip);
2968 // don't want operators to be applied twice
2969 mThebes->SetOperator(gfxContext::OPERATOR_SOURCE);
2971 mThebes->Paint();
2972 mThebes->PopGroupToSource();
2973 } else
2974 mThebes->Clip(clip);
2976 mThebes->Paint(CurrentState().globalAlpha);
2979 #if 1
2980 // XXX cairo bug workaround; force a clip update on mThebes.
2981 // Otherwise, a pixman clip gets left around somewhere, and pixman
2982 // (Render) does source clipping as well -- so we end up
2983 // compositing with an incorrect clip. This only seems to affect
2984 // fallback cases, which happen when we have CSS scaling going on.
2985 // This will blow away the current path, but we already blew it
2986 // away in this function earlier.
2987 mThebes->UpdateSurfaceClip();
2988 #endif
2990 FINISH:
2991 if (NS_SUCCEEDED(rv))
2992 rv = Redraw();
2994 return rv;
2997 NS_IMETHODIMP
2998 nsCanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& op)
3000 gfxContext::GraphicsOperator thebes_op;
3002 #define CANVAS_OP_TO_THEBES_OP(cvsop,thebesop) \
3003 if (op.EqualsLiteral(cvsop)) \
3004 thebes_op = gfxContext::OPERATOR_##thebesop;
3006 // XXX "darker" isn't really correct
3007 CANVAS_OP_TO_THEBES_OP("clear", CLEAR)
3008 else CANVAS_OP_TO_THEBES_OP("copy", SOURCE)
3009 else CANVAS_OP_TO_THEBES_OP("darker", SATURATE) // XXX
3010 else CANVAS_OP_TO_THEBES_OP("destination-atop", DEST_ATOP)
3011 else CANVAS_OP_TO_THEBES_OP("destination-in", DEST_IN)
3012 else CANVAS_OP_TO_THEBES_OP("destination-out", DEST_OUT)
3013 else CANVAS_OP_TO_THEBES_OP("destination-over", DEST_OVER)
3014 else CANVAS_OP_TO_THEBES_OP("lighter", ADD)
3015 else CANVAS_OP_TO_THEBES_OP("source-atop", ATOP)
3016 else CANVAS_OP_TO_THEBES_OP("source-in", IN)
3017 else CANVAS_OP_TO_THEBES_OP("source-out", OUT)
3018 else CANVAS_OP_TO_THEBES_OP("source-over", OVER)
3019 else CANVAS_OP_TO_THEBES_OP("xor", XOR)
3020 // not part of spec, kept here for compat
3021 else CANVAS_OP_TO_THEBES_OP("over", OVER)
3022 else return NS_ERROR_NOT_IMPLEMENTED;
3024 #undef CANVAS_OP_TO_THEBES_OP
3026 mThebes->SetOperator(thebes_op);
3027 return NS_OK;
3030 NS_IMETHODIMP
3031 nsCanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& op)
3033 gfxContext::GraphicsOperator thebes_op = mThebes->CurrentOperator();
3035 #define CANVAS_OP_TO_THEBES_OP(cvsop,thebesop) \
3036 if (thebes_op == gfxContext::OPERATOR_##thebesop) \
3037 op.AssignLiteral(cvsop);
3039 // XXX "darker" isn't really correct
3040 CANVAS_OP_TO_THEBES_OP("clear", CLEAR)
3041 else CANVAS_OP_TO_THEBES_OP("copy", SOURCE)
3042 else CANVAS_OP_TO_THEBES_OP("darker", SATURATE) // XXX
3043 else CANVAS_OP_TO_THEBES_OP("destination-atop", DEST_ATOP)
3044 else CANVAS_OP_TO_THEBES_OP("destination-in", DEST_IN)
3045 else CANVAS_OP_TO_THEBES_OP("destination-out", DEST_OUT)
3046 else CANVAS_OP_TO_THEBES_OP("destination-over", DEST_OVER)
3047 else CANVAS_OP_TO_THEBES_OP("lighter", ADD)
3048 else CANVAS_OP_TO_THEBES_OP("source-atop", ATOP)
3049 else CANVAS_OP_TO_THEBES_OP("source-in", IN)
3050 else CANVAS_OP_TO_THEBES_OP("source-out", OUT)
3051 else CANVAS_OP_TO_THEBES_OP("source-over", OVER)
3052 else CANVAS_OP_TO_THEBES_OP("xor", XOR)
3053 else return NS_ERROR_FAILURE;
3055 #undef CANVAS_OP_TO_THEBES_OP
3057 return NS_OK;
3062 // Utils
3064 PRBool
3065 nsCanvasRenderingContext2D::ConvertJSValToUint32(PRUint32* aProp, JSContext* aContext,
3066 jsval aValue)
3068 uint32 temp;
3069 if (::JS_ValueToECMAUint32(aContext, aValue, &temp)) {
3070 *aProp = (PRUint32)temp;
3072 else {
3073 ::JS_ReportError(aContext, "Parameter must be an integer");
3074 return JS_FALSE;
3077 return JS_TRUE;
3080 PRBool
3081 nsCanvasRenderingContext2D::ConvertJSValToDouble(double* aProp, JSContext* aContext,
3082 jsval aValue)
3084 jsdouble temp;
3085 if (::JS_ValueToNumber(aContext, aValue, &temp)) {
3086 *aProp = (jsdouble)temp;
3088 else {
3089 ::JS_ReportError(aContext, "Parameter must be a number");
3090 return JS_FALSE;
3093 return JS_TRUE;
3096 PRBool
3097 nsCanvasRenderingContext2D::ConvertJSValToXPCObject(nsISupports** aSupports, REFNSIID aIID,
3098 JSContext* aContext, jsval aValue)
3100 *aSupports = nsnull;
3101 if (JSVAL_IS_NULL(aValue)) {
3102 return JS_TRUE;
3105 if (JSVAL_IS_OBJECT(aValue)) {
3106 // WrapJS does all the work to recycle an existing wrapper and/or do a QI
3107 nsresult rv = nsContentUtils::XPConnect()->
3108 WrapJS(aContext, JSVAL_TO_OBJECT(aValue), aIID, (void**)aSupports);
3110 return NS_SUCCEEDED(rv);
3113 return JS_FALSE;
3116 /* thebes ARGB32 surfaces are ARGB stored as a packed 32-bit integer; on little-endian
3117 * platforms, they appear as BGRA bytes in the surface data. The color values are also
3118 * stored with premultiplied alpha.
3120 * If forceCopy is FALSE, a surface may be returned that's only valid during the current
3121 * operation. If it's TRUE, a copy will always be made that can safely be retained.
3124 nsresult
3125 nsCanvasRenderingContext2D::ThebesSurfaceFromElement(nsIDOMElement *imgElt,
3126 PRBool forceCopy,
3127 gfxASurface **aSurface,
3128 PRInt32 *widthOut,
3129 PRInt32 *heightOut,
3130 nsIPrincipal **prinOut,
3131 PRBool *forceWriteOnlyOut)
3133 nsresult rv;
3135 nsCOMPtr<nsINode> node = do_QueryInterface(imgElt);
3137 /* If it's a Canvas, grab its internal surface as necessary */
3138 nsCOMPtr<nsICanvasElement> canvas = do_QueryInterface(imgElt);
3139 if (node && canvas) {
3140 PRUint32 w, h;
3141 rv = canvas->GetSize(&w, &h);
3142 NS_ENSURE_SUCCESS(rv, rv);
3144 nsRefPtr<gfxASurface> sourceSurface;
3146 if (!forceCopy && canvas->CountContexts() == 1) {
3147 nsICanvasRenderingContextInternal *srcCanvas = canvas->GetContextAtIndex(0);
3148 rv = srcCanvas->GetThebesSurface(getter_AddRefs(sourceSurface));
3149 // force a copy if we couldn't get the surface, or if it's
3150 // the same as what we have
3151 if (sourceSurface == mSurface || NS_FAILED(rv))
3152 sourceSurface = nsnull;
3155 if (sourceSurface == nsnull) {
3156 nsRefPtr<gfxASurface> surf =
3157 gfxPlatform::GetPlatform()->CreateOffscreenSurface
3158 (gfxIntSize(w, h), gfxASurface::ImageFormatARGB32);
3159 nsRefPtr<gfxContext> ctx = new gfxContext(surf);
3160 rv = canvas->RenderContexts(ctx);
3161 if (NS_FAILED(rv))
3162 return rv;
3163 sourceSurface = surf;
3166 *aSurface = sourceSurface.forget().get();
3167 *widthOut = w;
3168 *heightOut = h;
3170 NS_ADDREF(*prinOut = node->NodePrincipal());
3171 *forceWriteOnlyOut = canvas->IsWriteOnly();
3173 return NS_OK;
3176 #ifdef MOZ_MEDIA
3177 /* Maybe it's <video>? */
3178 nsCOMPtr<nsIDOMHTMLVideoElement> ve = do_QueryInterface(imgElt);
3179 if (node && ve) {
3180 nsHTMLVideoElement *video = static_cast<nsHTMLVideoElement*>(ve.get());
3182 /* If it doesn't have a principal, just bail */
3183 nsCOMPtr<nsIPrincipal> principal = video->GetCurrentPrincipal();
3184 if (!principal)
3185 return NS_ERROR_DOM_SECURITY_ERR;
3187 PRUint32 videoWidth, videoHeight;
3188 rv = video->GetVideoWidth(&videoWidth);
3189 rv |= video->GetVideoHeight(&videoHeight);
3190 if (NS_FAILED(rv))
3191 return NS_ERROR_NOT_AVAILABLE;
3193 nsRefPtr<gfxASurface> surf =
3194 gfxPlatform::GetPlatform()->CreateOffscreenSurface
3195 (gfxIntSize(videoWidth, videoHeight), gfxASurface::ImageFormatARGB32);
3196 nsRefPtr<gfxContext> ctx = new gfxContext(surf);
3198 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
3200 video->Paint(ctx, gfxRect(0, 0, videoWidth, videoHeight));
3202 *aSurface = surf.forget().get();
3203 *widthOut = videoWidth;
3204 *heightOut = videoHeight;
3206 *prinOut = principal.forget().get();
3207 *forceWriteOnlyOut = PR_FALSE;
3209 return NS_OK;
3211 #endif
3213 /* Finally, check if it's a normal image */
3214 nsCOMPtr<imgIContainer> imgContainer;
3215 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(imgElt);
3217 if (!imageLoader)
3218 return NS_ERROR_NOT_AVAILABLE;
3220 nsCOMPtr<imgIRequest> imgRequest;
3221 rv = imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
3222 getter_AddRefs(imgRequest));
3223 NS_ENSURE_SUCCESS(rv, rv);
3224 if (!imgRequest)
3225 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3226 return NS_ERROR_NOT_AVAILABLE;
3228 PRUint32 status;
3229 imgRequest->GetImageStatus(&status);
3230 if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0)
3231 return NS_ERROR_NOT_AVAILABLE;
3233 // In case of data: URIs, we want to ignore principals;
3234 // they should have the originating content's principal,
3235 // but that's broken at the moment in imgLib.
3236 nsCOMPtr<nsIURI> uri;
3237 rv = imgRequest->GetURI(getter_AddRefs(uri));
3238 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
3240 PRBool isDataURI = PR_FALSE;
3241 rv = uri->SchemeIs("data", &isDataURI);
3242 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
3244 // Data URIs are always OK; set the principal
3245 // to null to indicate that.
3246 if (isDataURI) {
3247 *prinOut = nsnull;
3248 } else {
3249 rv = imgRequest->GetImagePrincipal(prinOut);
3250 NS_ENSURE_SUCCESS(rv, rv);
3251 NS_ENSURE_TRUE(*prinOut, NS_ERROR_DOM_SECURITY_ERR);
3254 *forceWriteOnlyOut = PR_FALSE;
3256 rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
3257 NS_ENSURE_SUCCESS(rv, rv);
3259 if (!imgContainer)
3260 return NS_ERROR_NOT_AVAILABLE;
3262 nsCOMPtr<gfxIImageFrame> frame;
3263 rv = imgContainer->GetCurrentFrame(getter_AddRefs(frame));
3264 NS_ENSURE_SUCCESS(rv, rv);
3266 nsCOMPtr<nsIImage> img(do_GetInterface(frame));
3268 PRInt32 imgWidth, imgHeight;
3269 rv = frame->GetWidth(&imgWidth);
3270 rv |= frame->GetHeight(&imgHeight);
3271 if (NS_FAILED(rv))
3272 return NS_ERROR_FAILURE;
3274 if (widthOut)
3275 *widthOut = imgWidth;
3276 if (heightOut)
3277 *heightOut = imgHeight;
3279 nsRefPtr<gfxPattern> gfxpattern;
3280 img->GetPattern(getter_AddRefs(gfxpattern));
3281 nsRefPtr<gfxASurface> gfxsurf = gfxpattern->GetSurface();
3283 if (!gfxsurf) {
3284 gfxsurf = new gfxImageSurface (gfxIntSize(imgWidth, imgHeight), gfxASurface::ImageFormatARGB32);
3285 nsRefPtr<gfxContext> ctx = new gfxContext(gfxsurf);
3287 ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
3288 ctx->SetPattern(gfxpattern);
3289 ctx->Paint();
3292 *aSurface = gfxsurf.forget().get();
3294 return NS_OK;
3297 /* Check that the rect [x,y,w,h] is a valid subrect of [0,0,realWidth,realHeight]
3298 * without overflowing any integers and the like.
3300 PRBool
3301 CheckSaneSubrectSize (PRInt32 x, PRInt32 y, PRInt32 w, PRInt32 h, PRInt32 realWidth, PRInt32 realHeight)
3303 if (w <= 0 || h <= 0 || x < 0 || y < 0)
3304 return PR_FALSE;
3306 if (x >= realWidth || w > (realWidth - x) ||
3307 y >= realHeight || h > (realHeight - y))
3308 return PR_FALSE;
3310 return PR_TRUE;
3313 static void
3314 FlushLayoutForTree(nsIDOMWindow* aWindow)
3316 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
3317 if (!piWin)
3318 return;
3320 // Note that because FlushPendingNotifications flushes parents, this
3321 // is O(N^2) in docshell tree depth. However, the docshell tree is
3322 // usually pretty shallow.
3324 nsCOMPtr<nsIDOMDocument> domDoc;
3325 aWindow->GetDocument(getter_AddRefs(domDoc));
3326 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
3327 if (doc) {
3328 doc->FlushPendingNotifications(Flush_Layout);
3331 nsCOMPtr<nsIDocShellTreeNode> node =
3332 do_QueryInterface(piWin->GetDocShell());
3333 if (node) {
3334 PRInt32 i = 0, i_end;
3335 node->GetChildCount(&i_end);
3336 for (; i < i_end; ++i) {
3337 nsCOMPtr<nsIDocShellTreeItem> item;
3338 node->GetChildAt(i, getter_AddRefs(item));
3339 nsCOMPtr<nsIDOMWindow> win = do_GetInterface(item);
3340 if (win) {
3341 FlushLayoutForTree(win);
3347 NS_IMETHODIMP
3348 nsCanvasRenderingContext2D::DrawWindow(nsIDOMWindow* aWindow, PRInt32 aX, PRInt32 aY,
3349 PRInt32 aW, PRInt32 aH,
3350 const nsAString& aBGColor,
3351 PRUint32 flags)
3353 NS_ENSURE_ARG(aWindow != nsnull);
3355 // protect against too-large surfaces that will cause allocation
3356 // or overflow issues
3357 if (!gfxASurface::CheckSurfaceSize(gfxIntSize(aW, aH), 0xffff))
3358 return NS_ERROR_FAILURE;
3360 // We can't allow web apps to call this until we fix at least the
3361 // following potential security issues:
3362 // -- rendering cross-domain IFRAMEs and then extracting the results
3363 // -- rendering the user's theme and then extracting the results
3364 // -- rendering native anonymous content (e.g., file input paths;
3365 // scrollbars should be allowed)
3366 if (!nsContentUtils::IsCallerTrustedForRead()) {
3367 // not permitted to use DrawWindow
3368 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3369 return NS_ERROR_DOM_SECURITY_ERR;
3372 // Flush layout updates
3373 if (!(flags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH))
3374 FlushLayoutForTree(aWindow);
3376 nsCOMPtr<nsPresContext> presContext;
3377 nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aWindow);
3378 if (win) {
3379 nsIDocShell* docshell = win->GetDocShell();
3380 if (docshell) {
3381 docshell->GetPresContext(getter_AddRefs(presContext));
3384 if (!presContext)
3385 return NS_ERROR_FAILURE;
3387 nscolor bgColor;
3388 nsresult rv = mCSSParser->ParseColorString(PromiseFlatString(aBGColor),
3389 nsnull, 0, &bgColor);
3390 NS_ENSURE_SUCCESS(rv, rv);
3392 nsIPresShell* presShell = presContext->PresShell();
3393 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
3395 nsRect r(nsPresContext::CSSPixelsToAppUnits(aX),
3396 nsPresContext::CSSPixelsToAppUnits(aY),
3397 nsPresContext::CSSPixelsToAppUnits(aW),
3398 nsPresContext::CSSPixelsToAppUnits(aH));
3399 presShell->RenderDocument(r, PR_FALSE, PR_TRUE, bgColor,
3400 mThebes);
3402 // get rid of the pattern surface ref, just in case
3403 mThebes->SetColor(gfxRGBA(1,1,1,1));
3404 DirtyAllStyles();
3406 Redraw();
3408 return rv;
3412 // device pixel getting/setting
3415 // ImageData getImageData (in float x, in float y, in float width, in float height);
3416 NS_IMETHODIMP
3417 nsCanvasRenderingContext2D::GetImageData()
3419 if (!mValid || !mCanvasElement)
3420 return NS_ERROR_FAILURE;
3422 if (mCanvasElement->IsWriteOnly() && !nsContentUtils::IsCallerTrustedForRead()) {
3423 // XXX ERRMSG we need to report an error to developers here! (bug 329026)
3424 return NS_ERROR_DOM_SECURITY_ERR;
3427 nsAXPCNativeCallContext *ncc = nsnull;
3428 nsresult rv = nsContentUtils::XPConnect()->
3429 GetCurrentNativeCallContext(&ncc);
3430 NS_ENSURE_SUCCESS(rv, rv);
3432 if (!ncc)
3433 return NS_ERROR_FAILURE;
3435 JSContext *ctx = nsnull;
3437 rv = ncc->GetJSContext(&ctx);
3438 NS_ENSURE_SUCCESS(rv, rv);
3440 PRUint32 argc;
3441 jsval *argv = nsnull;
3443 ncc->GetArgc(&argc);
3444 ncc->GetArgvPtr(&argv);
3446 JSAutoRequest ar(ctx);
3448 int32 x, y, w, h;
3449 if (!JS_ConvertArguments (ctx, argc, argv, "jjjj", &x, &y, &w, &h))
3450 return NS_ERROR_DOM_SYNTAX_ERR;
3452 if (!CheckSaneSubrectSize (x, y, w, h, mWidth, mHeight))
3453 return NS_ERROR_DOM_SYNTAX_ERR;
3455 nsAutoArrayPtr<PRUint8> surfaceData (new (std::nothrow) PRUint8[w * h * 4]);
3456 int surfaceDataStride = w*4;
3457 int surfaceDataOffset = 0;
3459 if (!surfaceData)
3460 return NS_ERROR_OUT_OF_MEMORY;
3462 nsRefPtr<gfxImageSurface> tmpsurf = new gfxImageSurface(surfaceData,
3463 gfxIntSize(w, h),
3464 w * 4,
3465 gfxASurface::ImageFormatARGB32);
3466 if (!tmpsurf || tmpsurf->CairoStatus())
3467 return NS_ERROR_FAILURE;
3469 nsRefPtr<gfxContext> tmpctx = new gfxContext(tmpsurf);
3471 if (!tmpctx || tmpctx->HasError())
3472 return NS_ERROR_FAILURE;
3474 tmpctx->SetOperator(gfxContext::OPERATOR_SOURCE);
3475 tmpctx->SetSource(mSurface, gfxPoint(-(int)x, -(int)y));
3476 tmpctx->Paint();
3478 tmpctx = nsnull;
3479 tmpsurf = nsnull;
3481 PRUint32 len = w * h * 4;
3482 if (len > (((PRUint32)0xfff00000)/sizeof(jsval)))
3483 return NS_ERROR_INVALID_ARG;
3485 nsAutoArrayPtr<jsval> jsvector(new (std::nothrow) jsval[w * h * 4]);
3486 if (!jsvector)
3487 return NS_ERROR_OUT_OF_MEMORY;
3488 jsval *dest = jsvector.get();
3489 PRUint8 *row;
3490 for (int j = 0; j < h; j++) {
3491 row = surfaceData + surfaceDataOffset + (surfaceDataStride * j);
3492 for (int i = 0; i < w; i++) {
3493 // XXX Is there some useful swizzle MMX we can use here?
3494 // I guess we have to INT_TO_JSVAL still
3495 #ifdef IS_LITTLE_ENDIAN
3496 PRUint8 b = *row++;
3497 PRUint8 g = *row++;
3498 PRUint8 r = *row++;
3499 PRUint8 a = *row++;
3500 #else
3501 PRUint8 a = *row++;
3502 PRUint8 r = *row++;
3503 PRUint8 g = *row++;
3504 PRUint8 b = *row++;
3505 #endif
3506 // Convert to non-premultiplied color
3507 if (a != 0) {
3508 r = (r * 255) / a;
3509 g = (g * 255) / a;
3510 b = (b * 255) / a;
3513 *dest++ = INT_TO_JSVAL(r);
3514 *dest++ = INT_TO_JSVAL(g);
3515 *dest++ = INT_TO_JSVAL(b);
3516 *dest++ = INT_TO_JSVAL(a);
3520 JSObject *dataArray = JS_NewArrayObject(ctx, w*h*4, jsvector.get());
3521 if (!dataArray)
3522 return NS_ERROR_OUT_OF_MEMORY;
3524 nsAutoGCRoot arrayGCRoot(&dataArray, &rv);
3525 NS_ENSURE_SUCCESS(rv, rv);
3527 JSObject *result = JS_NewObject(ctx, NULL, NULL, NULL);
3528 if (!result)
3529 return NS_ERROR_OUT_OF_MEMORY;
3531 nsAutoGCRoot resultGCRoot(&result, &rv);
3532 NS_ENSURE_SUCCESS(rv, rv);
3534 if (!JS_DefineProperty(ctx, result, "width", INT_TO_JSVAL(w), NULL, NULL, 0) ||
3535 !JS_DefineProperty(ctx, result, "height", INT_TO_JSVAL(h), NULL, NULL, 0) ||
3536 !JS_DefineProperty(ctx, result, "data", OBJECT_TO_JSVAL(dataArray), NULL, NULL, 0))
3537 return NS_ERROR_FAILURE;
3539 jsval *retvalPtr;
3540 ncc->GetRetValPtr(&retvalPtr);
3541 *retvalPtr = OBJECT_TO_JSVAL(result);
3542 ncc->SetReturnValueWasSet(PR_TRUE);
3544 return NS_OK;
3547 extern "C" {
3548 #include "jstypes.h"
3549 JS_FRIEND_API(JSBool)
3550 js_ArrayToJSUint8Buffer(JSContext *cx, JSObject *obj, jsuint offset, jsuint count,
3551 JSUint8 *dest);
3554 // void putImageData (in ImageData d, in float x, in float y);
3555 NS_IMETHODIMP
3556 nsCanvasRenderingContext2D::PutImageData()
3558 nsresult rv;
3560 if (!mValid)
3561 return NS_ERROR_FAILURE;
3563 nsAXPCNativeCallContext *ncc = nsnull;
3564 rv = nsContentUtils::XPConnect()->
3565 GetCurrentNativeCallContext(&ncc);
3566 NS_ENSURE_SUCCESS(rv, rv);
3568 if (!ncc)
3569 return NS_ERROR_FAILURE;
3571 JSContext *ctx = nsnull;
3573 rv = ncc->GetJSContext(&ctx);
3574 NS_ENSURE_SUCCESS(rv, rv);
3576 PRUint32 argc;
3577 jsval *argv = nsnull;
3579 ncc->GetArgc(&argc);
3580 ncc->GetArgvPtr(&argv);
3582 JSAutoRequest ar(ctx);
3584 JSObject *dataObject;
3585 int32 x, y;
3587 if (!JS_ConvertArguments (ctx, argc, argv, "ojj", &dataObject, &x, &y))
3588 return NS_ERROR_DOM_SYNTAX_ERR;
3590 if (!dataObject)
3591 return NS_ERROR_DOM_SYNTAX_ERR;
3593 int32 w, h;
3594 JSObject *dataArray;
3595 jsval v;
3597 if (!JS_GetProperty(ctx, dataObject, "width", &v) ||
3598 !JS_ValueToInt32(ctx, v, &w))
3599 return NS_ERROR_DOM_SYNTAX_ERR;
3601 if (!JS_GetProperty(ctx, dataObject, "height", &v) ||
3602 !JS_ValueToInt32(ctx, v, &h))
3603 return NS_ERROR_DOM_SYNTAX_ERR;
3605 if (!JS_GetProperty(ctx, dataObject, "data", &v) ||
3606 !JSVAL_IS_OBJECT(v))
3607 return NS_ERROR_DOM_SYNTAX_ERR;
3608 dataArray = JSVAL_TO_OBJECT(v);
3610 if (!CheckSaneSubrectSize (x, y, w, h, mWidth, mHeight))
3611 return NS_ERROR_DOM_SYNTAX_ERR;
3613 jsuint arrayLen;
3614 if (!JS_IsArrayObject(ctx, dataArray) ||
3615 !JS_GetArrayLength(ctx, dataArray, &arrayLen) ||
3616 arrayLen < (jsuint)(w * h * 4))
3617 return NS_ERROR_DOM_SYNTAX_ERR;
3619 nsAutoArrayPtr<PRUint8> imageBuffer(new (std::nothrow) PRUint8[w * h * 4]);
3620 if (!imageBuffer)
3621 return NS_ERROR_OUT_OF_MEMORY;
3623 PRUint8 *imgPtr = imageBuffer.get();
3625 JSBool ok = js_ArrayToJSUint8Buffer(ctx, dataArray, 0, w*h*4, imageBuffer);
3627 // no fast path? go slow.
3628 if (!ok) {
3629 jsval vr, vg, vb, va;
3630 PRUint8 ir, ig, ib, ia;
3631 for (int32 j = 0; j < h; j++) {
3632 for (int32 i = 0; i < w; i++) {
3633 if (!JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 0, &vr) ||
3634 !JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 1, &vg) ||
3635 !JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 2, &vb) ||
3636 !JS_GetElement(ctx, dataArray, (j*w*4) + i*4 + 3, &va))
3637 return NS_ERROR_DOM_SYNTAX_ERR;
3639 if (JSVAL_IS_INT(vr)) ir = (PRUint8) JSVAL_TO_INT(vr);
3640 else if (JSVAL_IS_DOUBLE(vr)) ir = (PRUint8) (*JSVAL_TO_DOUBLE(vr));
3641 else return NS_ERROR_DOM_SYNTAX_ERR;
3643 if (JSVAL_IS_INT(vg)) ig = (PRUint8) JSVAL_TO_INT(vg);
3644 else if (JSVAL_IS_DOUBLE(vg)) ig = (PRUint8) (*JSVAL_TO_DOUBLE(vg));
3645 else return NS_ERROR_DOM_SYNTAX_ERR;
3647 if (JSVAL_IS_INT(vb)) ib = (PRUint8) JSVAL_TO_INT(vb);
3648 else if (JSVAL_IS_DOUBLE(vb)) ib = (PRUint8) (*JSVAL_TO_DOUBLE(vb));
3649 else return NS_ERROR_DOM_SYNTAX_ERR;
3651 if (JSVAL_IS_INT(va)) ia = (PRUint8) JSVAL_TO_INT(va);
3652 else if (JSVAL_IS_DOUBLE(va)) ia = (PRUint8) (*JSVAL_TO_DOUBLE(va));
3653 else return NS_ERROR_DOM_SYNTAX_ERR;
3655 // Convert to premultiplied color (losslessly if the input came from getImageData)
3656 ir = (ir*ia + 254) / 255;
3657 ig = (ig*ia + 254) / 255;
3658 ib = (ib*ia + 254) / 255;
3660 #ifdef IS_LITTLE_ENDIAN
3661 *imgPtr++ = ib;
3662 *imgPtr++ = ig;
3663 *imgPtr++ = ir;
3664 *imgPtr++ = ia;
3665 #else
3666 *imgPtr++ = ia;
3667 *imgPtr++ = ir;
3668 *imgPtr++ = ig;
3669 *imgPtr++ = ib;
3670 #endif
3673 } else {
3674 /* Walk through and premultiply and swap rgba */
3675 /* XXX SSE me */
3676 PRUint8 ir, ig, ib, ia;
3677 PRUint8 *ptr = imgPtr;
3678 for (int32 i = 0; i < w*h; i++) {
3679 #ifdef IS_LITTLE_ENDIAN
3680 ir = ptr[0];
3681 ig = ptr[1];
3682 ib = ptr[2];
3683 ia = ptr[3];
3684 ptr[0] = (ib*ia + 254) / 255;
3685 ptr[1] = (ig*ia + 254) / 255;
3686 ptr[2] = (ir*ia + 254) / 255;
3687 #else
3688 ptr[0] = (ptr[0]*ptr[3] + 254) / 255;
3689 ptr[1] = (ptr[1]*ptr[3] + 254) / 255;
3690 ptr[2] = (ptr[2]*ptr[3] + 254) / 255;
3691 #endif
3692 ptr += 4;
3696 nsRefPtr<gfxImageSurface> imgsurf = new gfxImageSurface(imageBuffer.get(),
3697 gfxIntSize(w, h),
3698 w * 4,
3699 gfxASurface::ImageFormatARGB32);
3700 if (!imgsurf || imgsurf->CairoStatus())
3701 return NS_ERROR_FAILURE;
3703 gfxContextPathAutoSaveRestore pathSR(mThebes);
3704 gfxContextAutoSaveRestore autoSR(mThebes);
3706 mThebes->IdentityMatrix();
3707 mThebes->Translate(gfxPoint(x, y));
3708 mThebes->NewPath();
3709 mThebes->Rectangle(gfxRect(0, 0, w, h));
3710 mThebes->SetSource(imgsurf, gfxPoint(0, 0));
3711 mThebes->SetOperator(gfxContext::OPERATOR_SOURCE);
3712 mThebes->Fill();
3714 return Redraw();
3717 NS_IMETHODIMP
3718 nsCanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface)
3720 if (!mSurface) {
3721 *surface = nsnull;
3722 return NS_ERROR_NOT_AVAILABLE;
3725 *surface = mSurface.get();
3726 NS_ADDREF(*surface);
3728 return NS_OK;
3731 NS_IMETHODIMP
3732 nsCanvasRenderingContext2D::CreateImageData()
3734 if (!mValid || !mCanvasElement)
3735 return NS_ERROR_FAILURE;
3737 nsAXPCNativeCallContext *ncc = nsnull;
3738 nsresult rv = nsContentUtils::XPConnect()->
3739 GetCurrentNativeCallContext(&ncc);
3740 NS_ENSURE_SUCCESS(rv, rv);
3742 if (!ncc)
3743 return NS_ERROR_FAILURE;
3745 JSContext *ctx = nsnull;
3747 rv = ncc->GetJSContext(&ctx);
3748 NS_ENSURE_SUCCESS(rv, rv);
3750 PRUint32 argc;
3751 jsval *argv = nsnull;
3753 ncc->GetArgc(&argc);
3754 ncc->GetArgvPtr(&argv);
3756 JSAutoRequest ar(ctx);
3758 int32 w, h;
3759 if (!JS_ConvertArguments (ctx, argc, argv, "jj", &w, &h))
3760 return NS_ERROR_DOM_SYNTAX_ERR;
3762 if (w <= 0 || h <= 0)
3763 return NS_ERROR_DOM_INDEX_SIZE_ERR;
3765 // check for overflow when calculating len
3766 PRUint32 len0 = w * h;
3767 if (len0 / w != h)
3768 return NS_ERROR_DOM_INDEX_SIZE_ERR;
3769 PRUint32 len = len0 * 4;
3770 if (len / 4 != len0)
3771 return NS_ERROR_DOM_INDEX_SIZE_ERR;
3773 nsAutoArrayPtr<jsval> jsvector(new (std::nothrow) jsval[w * h * 4]);
3774 if (!jsvector)
3775 return NS_ERROR_OUT_OF_MEMORY;
3777 jsval *dest = jsvector.get();
3778 for (PRUint32 i = 0; i < len; i++)
3779 *dest++ = JSVAL_ZERO;
3781 JSObject *dataArray = JS_NewArrayObject(ctx, w*h*4, jsvector.get());
3782 if (!dataArray)
3783 return NS_ERROR_OUT_OF_MEMORY;
3785 nsAutoGCRoot arrayGCRoot(&dataArray, &rv);
3786 NS_ENSURE_SUCCESS(rv, rv);
3788 JSObject *result = JS_NewObject(ctx, NULL, NULL, NULL);
3789 if (!result)
3790 return NS_ERROR_OUT_OF_MEMORY;
3792 nsAutoGCRoot resultGCRoot(&result, &rv);
3793 NS_ENSURE_SUCCESS(rv, rv);
3795 if (!JS_DefineProperty(ctx, result, "width", INT_TO_JSVAL(w), NULL, NULL, 0) ||
3796 !JS_DefineProperty(ctx, result, "height", INT_TO_JSVAL(h), NULL, NULL, 0) ||
3797 !JS_DefineProperty(ctx, result, "data", OBJECT_TO_JSVAL(dataArray), NULL, NULL, 0))
3798 return NS_ERROR_FAILURE;
3800 jsval *retvalPtr;
3801 ncc->GetRetValPtr(&retvalPtr);
3802 *retvalPtr = OBJECT_TO_JSVAL(result);
3803 ncc->SetReturnValueWasSet(PR_TRUE);
3805 return NS_OK;