Bug 662038, parts 1, 4-5: Add canvas.mozDash and .mozDashOffset. r=joe sr=vlad
authorChris Jones <jones.chris.g@gmail.com>
Wed, 29 Jun 2011 14:34:58 -0700
changeset 72034 ddce22d83b0d85438140b7fc978f5724f3a70722
parent 72033 b0effc7c7f7ff1e1009208bee597602ff84ffa2c
child 72035 1382428eb6cefd00ad7c74fb9a2bbdf302ab53cf
push id20648
push usermak77@bonardo.net
push dateThu, 30 Jun 2011 10:55:29 +0000
treeherdermozilla-central@5c246f2bccb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe, vlad
bugs662038
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 662038, parts 1, 4-5: Add canvas.mozDash and .mozDashOffset. r=joe sr=vlad
content/canvas/src/CanvasUtils.cpp
content/canvas/src/CanvasUtils.h
content/canvas/src/nsCanvasRenderingContext2D.cpp
content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl
--- a/content/canvas/src/CanvasUtils.cpp
+++ b/content/canvas/src/CanvasUtils.cpp
@@ -189,17 +189,18 @@ JSValToMatrix(JSContext* cx, const jsval
 }
 
 bool
 JSValToMatrix(JSContext* cx, const jsval& val, Matrix* matrix, nsresult* rv)
 {
     gfxMatrix m;
     if (!JSValToMatrix(cx, val, &m, rv))
         return false;
-    *matrix = Matrix(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0);
+    *matrix = Matrix(Float(m.xx), Float(m.yx), Float(m.xy), Float(m.yy),
+                     Float(m.x0), Float(m.y0));
     return true;
 }
 
 template<size_t N>
 static nsresult
 MatrixEltsToJSVal(/*const*/ jsval (&elts)[N], JSContext* cx, jsval* val)
 {
     JSObject* obj = JS_NewArrayObject(cx, N, elts);
--- a/content/canvas/src/CanvasUtils.h
+++ b/content/canvas/src/CanvasUtils.h
@@ -126,12 +126,99 @@ inline PRBool FloatValidate (double f1, 
 
 inline PRBool FloatValidate (double f1, double f2, double f3, double f4, double f5, double f6) {
     VALIDATE(f1); VALIDATE(f2); VALIDATE(f3); VALIDATE(f4); VALIDATE(f5); VALIDATE(f6);
     return PR_TRUE;
 }
 
 #undef VALIDATE
 
+template<typename T>
+nsresult
+JSValToDashArray(JSContext* cx, const jsval& val,
+                 FallibleTArray<T>& dashArray);
+
+template<typename T>
+nsresult
+DashArrayToJSVal(FallibleTArray<T>& dashArray,
+                 JSContext* cx, jsval* val);
+
+template<typename T>
+nsresult
+JSValToDashArray(JSContext* cx, const jsval& patternArray,
+                 FallibleTArray<T>& dashes)
+{
+    // The cap is pretty arbitrary.  16k should be enough for
+    // anybody...
+    static const jsuint MAX_NUM_DASHES = 1 << 14;
+
+    if (!JSVAL_IS_PRIMITIVE(patternArray)) {
+        JSObject* obj = JSVAL_TO_OBJECT(patternArray);
+        jsuint length;
+        if (!JS_GetArrayLength(cx, obj, &length)) {
+            // Not an array-like thing
+            return NS_ERROR_INVALID_ARG;
+        } else if (length > MAX_NUM_DASHES) {
+            // Too many dashes in the pattern
+            return NS_ERROR_ILLEGAL_VALUE;
+        }
+
+        bool haveNonzeroElement = false;
+        for (jsint i = 0; i < jsint(length); ++i) {
+            jsval elt;
+            double d;
+            if (!JS_GetElement(cx, obj, i, &elt)) {
+                return NS_ERROR_FAILURE;
+            }
+            if (!(CoerceDouble(elt, &d) &&
+                  FloatValidate(d) &&
+                  d >= 0.0)) {
+                // Pattern elements must be finite "numbers" >= 0.
+                return NS_ERROR_INVALID_ARG;
+            } else if (d > 0.0) {
+                haveNonzeroElement = true;
+            }
+            if (!dashes.AppendElement(d)) {
+                return NS_ERROR_OUT_OF_MEMORY;
+            }
+        }
+
+        if (dashes.Length() > 0 && !haveNonzeroElement) {
+            // An all-zero pattern makes no sense.
+            return NS_ERROR_ILLEGAL_VALUE;
+        }
+    } else if (!(JSVAL_IS_VOID(patternArray) || JSVAL_IS_NULL(patternArray))) {
+        // undefined and null mean "reset to no dash".  Any other
+        // random garbage is a type error.
+        return NS_ERROR_INVALID_ARG;
+    }
+
+    return NS_OK;
+}
+
+template<typename T>
+nsresult
+DashArrayToJSVal(FallibleTArray<T>& dashes,
+                 JSContext* cx, jsval* val)
+{
+    if (dashes.IsEmpty()) {
+        *val = JSVAL_NULL;
+    } else {
+        JSObject* obj = JS_NewArrayObject(cx, dashes.Length(), nsnull);
+        if (!obj) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+        for (PRUint32 i = 0; i < dashes.Length(); ++i) {
+            double d = dashes[i];
+            jsval elt = DOUBLE_TO_JSVAL(d);
+            if (!JS_SetElement(cx, obj, i, &elt)) {
+                return NS_ERROR_FAILURE;
+            }
+        }
+        *val = OBJECT_TO_JSVAL(obj);
+    }
+    return NS_OK;
+}
+
 }
 }
 
 #endif /* _CANVASUTILS_H_ */
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -162,16 +162,22 @@ CopyContext(gfxContext* dest, gfxContext
 
     dest->SetLineWidth(src->CurrentLineWidth());
     dest->SetLineCap(src->CurrentLineCap());
     dest->SetLineJoin(src->CurrentLineJoin());
     dest->SetMiterLimit(src->CurrentMiterLimit());
     dest->SetFillRule(src->CurrentFillRule());
 
     dest->SetAntialiasMode(src->CurrentAntialiasMode());
+
+    AutoFallibleTArray<gfxFloat, 10> dashes;
+    double dashOffset;
+    if (src->CurrentDash(dashes, &dashOffset)) {
+        dest->SetDash(dashes.Elements(), dashes.Length(), dashOffset);
+    }
 }
 
 /**
  ** nsCanvasGradient
  **/
 #define NS_CANVASGRADIENT_PRIVATE_IID \
     { 0x491d39d8, 0x4058, 0x42bd, { 0xac, 0x76, 0x70, 0xd5, 0x62, 0x7f, 0x02, 0x10 } }
 class nsCanvasGradient : public nsIDOMCanvasGradient
@@ -3283,16 +3289,69 @@ NS_IMETHODIMP
 nsCanvasRenderingContext2D::GetMiterLimit(float *miter)
 {
     gfxFloat d = mThebes->CurrentMiterLimit();
     *miter = static_cast<float>(d);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsCanvasRenderingContext2D::SetMozDash(JSContext *cx, const jsval& patternArray)
+{
+    AutoFallibleTArray<gfxFloat, 10> dashes;
+    nsresult rv = JSValToDashArray(cx, patternArray, dashes);
+    if (NS_SUCCEEDED(rv)) {
+        mThebes->SetDash(dashes.Elements(), dashes.Length(),
+                         mThebes->CurrentDashOffset());
+    }
+    return rv;
+}
+
+NS_IMETHODIMP
+nsCanvasRenderingContext2D::GetMozDash(JSContext* cx, jsval* dashArray)
+{
+    AutoFallibleTArray<gfxFloat, 10> dashes;
+    if (!mThebes->CurrentDash(dashes, nsnull)) {
+        dashes.SetLength(0);
+    }
+    return DashArrayToJSVal(dashes, cx, dashArray);
+}
+
+NS_IMETHODIMP
+nsCanvasRenderingContext2D::SetMozDashOffset(float offset)
+{
+    if (!FloatValidate(offset)) {
+        return NS_ERROR_ILLEGAL_VALUE;
+    }
+
+    AutoFallibleTArray<gfxFloat, 10> dashes;
+    if (!mThebes->CurrentDash(dashes, nsnull)) {
+        // Either no dash is set or the cairo call failed.  Either
+        // way, eat the error.
+
+        // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+        return NS_OK;
+    }
+    NS_ABORT_IF_FALSE(dashes.Length() > 0,
+                      "CurrentDash() should have returned false");
+
+    mThebes->SetDash(dashes.Elements(), dashes.Length(),
+                     gfxFloat(offset));
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCanvasRenderingContext2D::GetMozDashOffset(float* offset)
+{
+    *offset = float(mThebes->CurrentDashOffset());
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsCanvasRenderingContext2D::IsPointInPath(float x, float y, PRBool *retVal)
 {
     if (!FloatValidate(x,y)) {
         *retVal = PR_FALSE;
         return NS_OK;
     }
 
     gfxPoint pt(x, y);
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -679,16 +679,17 @@ protected:
   class ContextState {
   public:
       ContextState() : textAlign(TEXT_ALIGN_START),
                        textBaseline(TEXT_BASELINE_ALPHABETIC),
                        lineWidth(1.0f),
                        miterLimit(10.0f),
                        globalAlpha(1.0f),
                        shadowBlur(0.0),
+                       dashOffset(0.0f),
                        op(OP_OVER),
                        fillRule(FILL_WINDING),
                        lineCap(CAP_BUTT),
                        lineJoin(JOIN_MITER_OR_BEVEL),
                        imageSmoothingEnabled(PR_TRUE)
       { }
 
       ContextState(const ContextState& other)
@@ -698,16 +699,18 @@ protected:
             textBaseline(other.textBaseline),
             shadowColor(other.shadowColor),
             transform(other.transform),
             shadowOffset(other.shadowOffset),
             lineWidth(other.lineWidth),
             miterLimit(other.miterLimit),
             globalAlpha(other.globalAlpha),
             shadowBlur(other.shadowBlur),
+            dash(other.dash),
+            dashOffset(other.dashOffset),
             op(other.op),
             fillRule(FILL_WINDING),
             lineCap(other.lineCap),
             lineJoin(other.lineJoin),
             imageSmoothingEnabled(other.imageSmoothingEnabled)
       {
           for (int i = 0; i < STYLE_MAX; i++) {
               colorStyles[i] = other.colorStyles[i];
@@ -756,16 +759,18 @@ protected:
       nscolor shadowColor;
 
       Matrix transform;
       Point shadowOffset;
       Float lineWidth;
       Float miterLimit;
       Float globalAlpha;
       Float shadowBlur;
+      FallibleTArray<Float> dash;
+      Float dashOffset;
 
       CompositionOp op;
       FillRule fillRule;
       CapStyle lineCap;
       JoinStyle lineJoin;
 
       PRPackedBool imageSmoothingEnabled;
   };
@@ -2142,38 +2147,47 @@ nsCanvasRenderingContext2DAzure::StrokeR
     CapStyle cap = CAP_BUTT;
     if (state.lineJoin == JOIN_ROUND) {
       cap = CAP_ROUND;
     }
     AdjustedTarget(this)->
       StrokeLine(Point(x, y), Point(x + w, y),
                   GeneralPattern().ForStyle(this, STYLE_STROKE, mTarget),
                   StrokeOptions(state.lineWidth, state.lineJoin,
-                                cap, state.miterLimit),
+                                cap, state.miterLimit,
+                                state.dash.Length(),
+                                state.dash.Elements(),
+                                state.dashOffset),
                   DrawOptions(state.globalAlpha, state.op));
     return NS_OK;
   } else if (!w) {
     CapStyle cap = CAP_BUTT;
     if (state.lineJoin == JOIN_ROUND) {
       cap = CAP_ROUND;
     }
     AdjustedTarget(this)->
       StrokeLine(Point(x, y), Point(x, y + h),
                   GeneralPattern().ForStyle(this, STYLE_STROKE, mTarget),
                   StrokeOptions(state.lineWidth, state.lineJoin,
-                                cap, state.miterLimit),
+                                cap, state.miterLimit,
+                                state.dash.Length(),
+                                state.dash.Elements(),
+                                state.dashOffset),
                   DrawOptions(state.globalAlpha, state.op));
     return NS_OK;
   }
 
   AdjustedTarget(this)->
     StrokeRect(mgfx::Rect(x, y, w, h),
                 GeneralPattern().ForStyle(this, STYLE_STROKE, mTarget),
                 StrokeOptions(state.lineWidth, state.lineJoin,
-                              state.lineCap, state.miterLimit),
+                              state.lineCap, state.miterLimit,
+                              state.dash.Length(),
+                              state.dash.Elements(),
+                              state.dashOffset),
                 DrawOptions(state.globalAlpha, state.op));
 
   return Redraw();
 }
 
 //
 // path bits
 //
@@ -2227,17 +2241,20 @@ nsCanvasRenderingContext2DAzure::Stroke(
     return NS_OK;
   }
 
   const ContextState &state = CurrentState();
 
   AdjustedTarget(this)->
     Stroke(mPath, GeneralPattern().ForStyle(this, STYLE_STROKE, mTarget),
             StrokeOptions(state.lineWidth, state.lineJoin,
-                          state.lineCap, state.miterLimit),
+                          state.lineCap, state.miterLimit,
+                          state.dash.Length(),
+                          state.dash.Elements(),
+                          state.dashOffset),
             DrawOptions(state.globalAlpha, state.op));
 
   return Redraw();
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::Clip()
 {
@@ -2966,16 +2983,18 @@ nsCanvasRenderingContext2DAzure::Measure
   return NS_OK;
 }
 
 /**
  * Used for nsBidiPresUtils::ProcessText
  */
 struct NS_STACK_CLASS nsCanvasBidiProcessorAzure : public nsBidiPresUtils::BidiProcessor
 {
+  typedef nsCanvasRenderingContext2DAzure::ContextState ContextState;
+
   virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
   {
     mTextRun = gfxTextRunCache::MakeTextRun(text,
                                             length,
                                             mFontgrp,
                                             mThebes,
                                             mAppUnitsPerDevPixel,
                                             direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
@@ -3106,22 +3125,26 @@ struct NS_STACK_CLASS nsCanvasBidiProces
                       nsCanvasRenderingContext2DAzure::GeneralPattern().
                         ForStyle(mCtx, nsCanvasRenderingContext2DAzure::STYLE_FILL, mCtx->mTarget),
                       DrawOptions(mState->globalAlpha, mState->op));
       } else if (mOp == nsCanvasRenderingContext2DAzure::TEXT_DRAW_OPERATION_STROKE) {
         RefPtr<Path> path = scaledFont->GetPathForGlyphs(buffer, mCtx->mTarget);
             
         Matrix oldTransform = mCtx->mTarget->GetTransform();
 
+        const ContextState& state = *mState;
         nsCanvasRenderingContext2DAzure::AdjustedTarget(mCtx)->
           Stroke(path, nsCanvasRenderingContext2DAzure::GeneralPattern().
                     ForStyle(mCtx, nsCanvasRenderingContext2DAzure::STYLE_STROKE, mCtx->mTarget),
-                  StrokeOptions(mCtx->CurrentState().lineWidth, mCtx->CurrentState().lineJoin,
-                                mCtx->CurrentState().lineCap, mCtx->CurrentState().miterLimit),
-                  DrawOptions(mState->globalAlpha, mState->op));
+                  StrokeOptions(state.lineWidth, state.lineJoin,
+                                state.lineCap, state.miterLimit,
+                                state.dash.Length(),
+                                state.dash.Elements(),
+                                state.dashOffset),
+                  DrawOptions(state.globalAlpha, state.op));
 
       }
     }
   }
 
   // current text run
   gfxTextRunCache::AutoTextRun mTextRun;
 
@@ -3139,17 +3162,17 @@ struct NS_STACK_CLASS nsCanvasBidiProces
 
   // dev pixel conversion factor
   PRUint32 mAppUnitsPerDevPixel;
 
   // operation (fill or stroke)
   nsCanvasRenderingContext2DAzure::TextDrawOperation mOp;
 
   // context state
-  nsCanvasRenderingContext2DAzure::ContextState *mState;
+  ContextState *mState;
 
   // union of bounding boxes of all runs, needed for shadows
   gfxRect mBoundingBox;
 
   // true iff the bounding box should be measured
   PRBool mDoMeasureBoundingBox;
 };
 
@@ -3538,16 +3561,57 @@ nsCanvasRenderingContext2DAzure::SetMite
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::GetMiterLimit(float *miter)
 {
   *miter = CurrentState().miterLimit;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsCanvasRenderingContext2DAzure::SetMozDash(JSContext *cx, const jsval& patternArray)
+{
+  FallibleTArray<Float> dash;
+  nsresult rv = JSValToDashArray(cx, patternArray, dash);
+  if (NS_SUCCEEDED(rv)) {
+    ContextState& state = CurrentState();
+    state.dash = dash;
+    if (state.dash.IsEmpty()) {
+      state.dashOffset = 0;
+    }
+  }
+  return rv;
+}
+
+NS_IMETHODIMP
+nsCanvasRenderingContext2DAzure::GetMozDash(JSContext* cx, jsval* dashArray)
+{
+  return DashArrayToJSVal(CurrentState().dash, cx, dashArray);
+}
+ 
+NS_IMETHODIMP
+nsCanvasRenderingContext2DAzure::SetMozDashOffset(float offset)
+{
+  if (!FloatValidate(offset)) {
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  ContextState& state = CurrentState();
+  if (!state.dash.IsEmpty()) {
+    state.dashOffset = offset;
+  }
+  return NS_OK;
+}
+ 
+NS_IMETHODIMP
+nsCanvasRenderingContext2DAzure::GetMozDashOffset(float* offset)
+{
+  *offset = CurrentState().dashOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::IsPointInPath(float x, float y, PRBool *retVal)
 {
   if (!FloatValidate(x,y)) {
     *retVal = PR_FALSE;
     return NS_OK;
   }
 
   EnsureUserSpacePath();
--- a/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl
+++ b/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl
@@ -116,16 +116,20 @@ enum CanvasMultiGetterType {
   nsIDOMCanvasGradient createLinearGradient (in float x0, in float y0, in float x1, in float y1);
   nsIDOMCanvasGradient createRadialGradient(in float x0, in float y0, in float r0, in float x1, in float y1, in float r1);
   nsIDOMCanvasPattern createPattern(in nsIDOMHTMLElement image, in DOMString repetition);
   attribute float lineWidth; /* default 1 */
   attribute DOMString lineCap; /* "butt", "round", "square" (default) */
   attribute DOMString lineJoin; /* "round", "bevel", "miter" (default) */
   attribute float miterLimit; /* default 10 */
 
+  [implicit_jscontext]
+  attribute jsval mozDash; /* default |null| */
+  attribute float mozDashOffset; /* default 0.0 */
+
   // shadows
   attribute float shadowOffsetX;
   attribute float shadowOffsetY;
   attribute float shadowBlur;
   attribute DOMString shadowColor;
 
   // rects
   void clearRect(in float x, in float y, in float w, in float h);