Renamed JsCanvas to JsContext and moved into it's own file.

Fleshed out a lot of the Path interface.

BUG=
R=robertphillips@google.com, tfarina@chromium.org, reed@google.com

Author: jcgregorio@google.com

Review URL: https://codereview.chromium.org/121303004

git-svn-id: http://skia.googlecode.com/svn/trunk@12913 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/experimental/SkV8Example/Global.cpp b/experimental/SkV8Example/Global.cpp
index b45d2de..998f92b 100644
--- a/experimental/SkV8Example/Global.cpp
+++ b/experimental/SkV8Example/Global.cpp
@@ -190,6 +190,17 @@
   return Context::New(fIsolate, NULL, global);
 }
 
+void Global::initialize() {
+    // Create a stack-allocated handle scope.
+    HandleScope handleScope(fIsolate);
+
+    // Create a new context.
+    Handle<Context> context = this->createRootContext();
+
+    // Make the context persistent.
+    fContext.Reset(fIsolate, context);
+}
+
 
 // Creates the root context, parses the script into it, then stores the
 // context in a global.
@@ -201,10 +212,8 @@
     // Create a stack-allocated handle scope.
     HandleScope handleScope(fIsolate);
 
-    printf("Before create context\n");
-
-    // Create a new context.
-    Handle<Context> context = this->createRootContext();
+    // Get the global context.
+    Handle<Context> context = this->getContext();
 
     // Enter the scope so all operations take place in the scope.
     Context::Scope contextScope(context);
@@ -213,16 +222,13 @@
 
     // Compile the source code.
     Handle<String> source = String::NewFromUtf8(fIsolate, script);
-    printf("Before Compile\n");
     Handle<Script> compiledScript = Script::Compile(source);
-    printf("After Compile\n");
 
     if (compiledScript.IsEmpty()) {
         // Print errors that happened during compilation.
         this->reportException(&tryCatch);
         return false;
     }
-    printf("After Exception.\n");
 
     // Try running it now to create the onDraw function.
     Handle<Value> result = compiledScript->Run();
@@ -235,7 +241,5 @@
         return false;
     }
 
-    // Also make the context persistent.
-    fContext.Reset(fIsolate, context);
     return true;
 }
diff --git a/experimental/SkV8Example/Global.h b/experimental/SkV8Example/Global.h
index 3377e21..6bd3c70 100644
--- a/experimental/SkV8Example/Global.h
+++ b/experimental/SkV8Example/Global.h
@@ -33,6 +33,7 @@
         , fLastTimerID(0)
     {
         gGlobal = this;
+        this->initialize();
     }
     virtual ~Global() {}
 
@@ -57,6 +58,7 @@
     void reportException(TryCatch* tryCatch);
 
 private:
+    void initialize();
     Handle<Context> createRootContext();
     int32_t getNextTimerID();
 
diff --git a/experimental/SkV8Example/JsContext.cpp b/experimental/SkV8Example/JsContext.cpp
new file mode 100644
index 0000000..87f7d0e
--- /dev/null
+++ b/experimental/SkV8Example/JsContext.cpp
@@ -0,0 +1,336 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+#include <v8.h>
+
+using namespace v8;
+
+#include "Global.h"
+#include "JsContext.h"
+#include "Path.h"
+#include "SkCanvas.h"
+
+
+// Extracts a C string from a V8 Utf8Value.
+// TODO(jcgregrio) Currently dup'd in two files, fix.
+static const char* to_cstring(const v8::String::Utf8Value& value) {
+    return *value ? *value : "<string conversion failed>";
+}
+
+JsContext* JsContext::Unwrap(Handle<Object> obj) {
+    Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
+    void* ptr = field->Value();
+    return static_cast<JsContext*>(ptr);
+}
+
+void JsContext::FillRect(const v8::FunctionCallbackInfo<Value>& args) {
+    JsContext* jsContext = Unwrap(args.This());
+    SkCanvas* canvas = jsContext->fCanvas;
+
+    if (args.Length() != 4) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 4 arguments required."));
+        return;
+    }
+    // TODO(jcgregorio) Really figure out the conversion from JS numbers to
+    // SkScalars. Maybe test if int first? Not sure of the performance impact.
+    double x = args[0]->NumberValue();
+    double y = args[1]->NumberValue();
+    double w = args[2]->NumberValue();
+    double h = args[3]->NumberValue();
+
+    SkRect rect = {
+        SkDoubleToScalar(x),
+        SkDoubleToScalar(y),
+        SkDoubleToScalar(x) + SkDoubleToScalar(w),
+        SkDoubleToScalar(y) + SkDoubleToScalar(h)
+    };
+    jsContext->fFillStyle.setStyle(SkPaint::kFill_Style);
+    canvas->drawRect(rect, jsContext->fFillStyle);
+}
+
+void JsContext::Translate(const v8::FunctionCallbackInfo<Value>& args) {
+    JsContext* jsContext = Unwrap(args.This());
+    SkCanvas* canvas = jsContext->fCanvas;
+
+    if (args.Length() != 2) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 2 arguments required."));
+        return;
+    }
+    double dx = args[0]->NumberValue();
+    double dy = args[1]->NumberValue();
+    canvas->translate(SkDoubleToScalar(dx), SkDoubleToScalar(dy));
+}
+
+void JsContext::ResetTransform(const v8::FunctionCallbackInfo<Value>& args) {
+    JsContext* jsContext = Unwrap(args.This());
+    SkCanvas* canvas = jsContext->fCanvas;
+
+    canvas->resetMatrix();
+}
+
+void JsContext::Stroke(const v8::FunctionCallbackInfo<Value>& args) {
+    JsContext* jsContext = Unwrap(args.This());
+    SkCanvas* canvas = jsContext->fCanvas;
+
+    if (args.Length() != 1) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 1 arguments required."));
+        return;
+    }
+
+    Handle<External> field = Handle<External>::Cast(
+            args[0]->ToObject()->GetInternalField(0));
+    void* ptr = field->Value();
+    Path* path = static_cast<Path*>(ptr);
+
+    jsContext->fFillStyle.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path->getSkPath(), jsContext->fFillStyle);
+}
+
+
+void JsContext::Fill(const v8::FunctionCallbackInfo<Value>& args) {
+    JsContext* jsContext = Unwrap(args.This());
+    SkCanvas* canvas = jsContext->fCanvas;
+
+    if (args.Length() != 1) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 1 arguments required."));
+        return;
+    }
+
+    Handle<External> field = Handle<External>::Cast(
+            args[0]->ToObject()->GetInternalField(0));
+    void* ptr = field->Value();
+    Path* path = static_cast<Path*>(ptr);
+
+    jsContext->fFillStyle.setStyle(SkPaint::kFill_Style);
+    canvas->drawPath(path->getSkPath(), jsContext->fFillStyle);
+}
+
+
+void JsContext::GetFillStyle(Local<String> name,
+                            const PropertyCallbackInfo<Value>& info) {
+    JsContext* jsContext = Unwrap(info.This());
+    SkColor color = jsContext->fFillStyle.getColor();
+    char buf[8];
+    sprintf(buf, "#%02X%02X%02X", SkColorGetR(color), SkColorGetG(color),
+            SkColorGetB(color));
+
+    info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buf));
+}
+
+void JsContext::SetFillStyle(Local<String> name, Local<Value> value,
+                            const PropertyCallbackInfo<void>& info) {
+    JsContext* jsContext = Unwrap(info.This());
+    Local<String> s = value->ToString();
+    if (s->Length() != 7) {
+        info.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        info.GetIsolate(), "Invalid fill style format."));
+        return;
+    }
+    char buf[8];
+    s->WriteUtf8(buf, sizeof(buf));
+
+    if (buf[0] != '#') {
+        info.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        info.GetIsolate(), "Invalid fill style format."));
+        return;
+    }
+
+    long color = strtol(buf+1, NULL, 16);
+    jsContext->fFillStyle.setColor(SkColorSetA(SkColor(color), SK_AlphaOPAQUE));
+}
+
+
+void JsContext::GetWidth(Local<String> name,
+                         const PropertyCallbackInfo<Value>& info) {
+    JsContext* jsContext = Unwrap(info.This());
+    SkISize size = jsContext->fCanvas->getDeviceSize();
+
+    info.GetReturnValue().Set(Int32::New(size.fWidth));
+}
+
+void JsContext::GetHeight(Local<String> name,
+                         const PropertyCallbackInfo<Value>& info) {
+    JsContext* jsContext = Unwrap(info.This());
+    SkISize size = jsContext->fCanvas->getDeviceSize();
+
+    info.GetReturnValue().Set(Int32::New(size.fHeight));
+}
+
+
+Persistent<ObjectTemplate> JsContext::gContextTemplate;
+
+Handle<ObjectTemplate> JsContext::makeContextTemplate() {
+    EscapableHandleScope handleScope(fGlobal->getIsolate());
+
+    Local<ObjectTemplate> result = ObjectTemplate::New();
+
+    // Add a field to store the pointer to a JsContext instance.
+    result->SetInternalFieldCount(1);
+
+    // Add accessors for each of the fields of the context object.
+    result->SetAccessor(String::NewFromUtf8(
+            fGlobal->getIsolate(), "fillStyle", String::kInternalizedString),
+                        GetFillStyle, SetFillStyle);
+    result->SetAccessor(String::NewFromUtf8(
+            fGlobal->getIsolate(), "width", String::kInternalizedString),
+                        GetWidth);
+    result->SetAccessor(String::NewFromUtf8(
+            fGlobal->getIsolate(), "height", String::kInternalizedString),
+                        GetHeight);
+
+    // Add methods.
+    result->Set(
+            String::NewFromUtf8(
+                    fGlobal->getIsolate(), "fillRect",
+                    String::kInternalizedString),
+            FunctionTemplate::New(FillRect));
+    result->Set(
+            String::NewFromUtf8(
+                    fGlobal->getIsolate(), "stroke",
+                    String::kInternalizedString),
+            FunctionTemplate::New(Stroke));
+    result->Set(
+            String::NewFromUtf8(
+                    fGlobal->getIsolate(), "fill",
+                    String::kInternalizedString),
+            FunctionTemplate::New(Fill));
+    result->Set(
+            String::NewFromUtf8(
+                    fGlobal->getIsolate(), "translate",
+                    String::kInternalizedString),
+            FunctionTemplate::New(Translate));
+    result->Set(
+            String::NewFromUtf8(
+                    fGlobal->getIsolate(), "resetTransform",
+                    String::kInternalizedString),
+            FunctionTemplate::New(ResetTransform));
+
+    // Return the result through the current handle scope.
+    return handleScope.Escape(result);
+}
+
+
+// Wraps 'this' in a Javascript object.
+Handle<Object> JsContext::wrap() {
+    // Handle scope for temporary handles.
+    EscapableHandleScope handleScope(fGlobal->getIsolate());
+
+    // Fetch the template for creating JavaScript JsContext wrappers.
+    // It only has to be created once, which we do on demand.
+    if (gContextTemplate.IsEmpty()) {
+        Handle<ObjectTemplate> raw_template = this->makeContextTemplate();
+        gContextTemplate.Reset(fGlobal->getIsolate(), raw_template);
+    }
+    Handle<ObjectTemplate> templ =
+            Local<ObjectTemplate>::New(fGlobal->getIsolate(), gContextTemplate);
+
+    // Create an empty JsContext wrapper.
+    Local<Object> result = templ->NewInstance();
+
+    // Wrap the raw C++ pointer in an External so it can be referenced
+    // from within JavaScript.
+    Handle<External> contextPtr = External::New(fGlobal->getIsolate(), this);
+
+    // Store the context pointer in the JavaScript wrapper.
+    result->SetInternalField(0, contextPtr);
+
+    // Return the result through the current handle scope.  Since each
+    // of these handles will go away when the handle scope is deleted
+    // we need to call Close to let one, the result, escape into the
+    // outer handle scope.
+    return handleScope.Escape(result);
+}
+
+void JsContext::onDraw(SkCanvas* canvas) {
+    // Record canvas and window in this.
+    fCanvas = canvas;
+
+    // Create a handle scope to keep the temporary object references.
+    HandleScope handleScope(fGlobal->getIsolate());
+
+    // Create a local context from our global context.
+    Local<Context> context = fGlobal->getContext();
+
+    // Enter the context so all the remaining operations take place there.
+    Context::Scope contextScope(context);
+
+    // Wrap the C++ this pointer in a JavaScript wrapper.
+    Handle<Object> contextObj = this->wrap();
+
+    // Set up an exception handler before calling the Process function.
+    TryCatch tryCatch;
+
+    // Invoke the process function, giving the global object as 'this'
+    // and one argument, this JsContext.
+    const int argc = 1;
+    Handle<Value> argv[argc] = { contextObj };
+    Local<Function> onDraw =
+            Local<Function>::New(fGlobal->getIsolate(), fOnDraw);
+    Handle<Value> result = onDraw->Call(context->Global(), argc, argv);
+
+    // Handle any exceptions or output.
+    if (result.IsEmpty()) {
+        SkASSERT(tryCatch.HasCaught());
+        // Print errors that happened during execution.
+        fGlobal->reportException(&tryCatch);
+    } else {
+        SkASSERT(!tryCatch.HasCaught());
+        if (!result->IsUndefined()) {
+            // If all went well and the result wasn't undefined then print
+            // the returned value.
+            String::Utf8Value str(result);
+            const char* cstr = to_cstring(str);
+            printf("%s\n", cstr);
+        }
+    }
+}
+
+// Fetch the onDraw function from the global context.
+bool JsContext::initialize() {
+
+    // Create a stack-allocated handle scope.
+    HandleScope handleScope(fGlobal->getIsolate());
+
+    // Create a local context from our global context.
+    Local<Context> context = fGlobal->getContext();
+
+    // Enter the scope so all operations take place in the scope.
+    Context::Scope contextScope(context);
+
+    v8::TryCatch try_catch;
+
+    Handle<String> fn_name = String::NewFromUtf8(
+        fGlobal->getIsolate(), "onDraw");
+    Handle<Value> fn_val = context->Global()->Get(fn_name);
+
+    if (!fn_val->IsFunction()) {
+        printf("Not a function.\n");
+        return false;
+    }
+
+    // It is a function; cast it to a Function.
+    Handle<Function> fn_fun = Handle<Function>::Cast(fn_val);
+
+    // Store the function in a Persistent handle, since we also want that to
+    // remain after this call returns.
+    fOnDraw.Reset(fGlobal->getIsolate(), fn_fun);
+
+    return true;
+}
+
diff --git a/experimental/SkV8Example/JsContext.h b/experimental/SkV8Example/JsContext.h
new file mode 100644
index 0000000..0fc180d
--- /dev/null
+++ b/experimental/SkV8Example/JsContext.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#ifndef SkV8Example_JsContext_DEFINED
+#define SkV8Example_JsContext_DEFINED
+
+#include <v8.h>
+
+#include "SkPaint.h"
+
+using namespace v8;
+
+class SkCanvas;
+class Global;
+
+// Provides the canvas context implementation in JS, and the OnDraw() method in
+// C++ that's used to bridge from C++ to JS. Should be used in JS as:
+//
+//  function onDraw(context) {
+//    context.fillStyle="#FF0000";
+//    context.fillRect(x, y, w, h);
+//  }
+class JsContext {
+public:
+    JsContext(Global* global)
+            : fGlobal(global)
+            , fCanvas(NULL)
+    {
+        fFillStyle.setColor(SK_ColorRED);
+    }
+    ~JsContext();
+
+    // Parse the script.
+    bool initialize();
+
+    // Call this with the SkCanvas you want onDraw to draw on.
+    void onDraw(SkCanvas* canvas);
+
+private:
+    // Implementation of the context.fillStyle field.
+    static void GetFillStyle(Local<String> name,
+                             const PropertyCallbackInfo<Value>& info);
+    static void SetFillStyle(Local<String> name, Local<Value> value,
+                             const PropertyCallbackInfo<void>& info);
+    static void GetWidth(Local<String> name,
+                         const PropertyCallbackInfo<Value>& info);
+    static void GetHeight(Local<String> name,
+                          const PropertyCallbackInfo<Value>& info);
+
+    // Implementation of the context.fillRect() JS function.
+    static void FillRect(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Implementation of the context.stroke(Path path) JS function.
+    static void Stroke(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Implementation of the context.fill(Path path) JS function.
+    static void Fill(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Implementation of the context.translate(dx, dy) JS function.
+    static void Translate(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Implementation of the context.resetTransform() JS function.
+    static void ResetTransform(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Get the pointer out of obj.
+    static JsContext* Unwrap(Handle<Object> obj);
+
+    // Create a template for JS object associated with JsContext, called lazily
+    // by Wrap() and the results are stored in gContextTemplate;
+    Handle<ObjectTemplate> makeContextTemplate();
+
+    // Wrap the 'this' pointer into an Object. Can be retrieved via Unwrap.
+    Handle<Object> wrap();
+
+    Global* fGlobal;
+
+    // Only valid when inside OnDraw().
+    SkCanvas* fCanvas;
+
+    SkPaint fFillStyle;
+
+    // A handle to the onDraw function defined in the script.
+    Persistent<Function> fOnDraw;
+
+    // The template for what a canvas context object looks like. The canvas
+    // context object is what's passed into the JS onDraw() function.
+    static Persistent<ObjectTemplate> gContextTemplate;
+};
+
+#endif
diff --git a/experimental/SkV8Example/Path.cpp b/experimental/SkV8Example/Path.cpp
new file mode 100644
index 0000000..a2d1030
--- /dev/null
+++ b/experimental/SkV8Example/Path.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#include "Path.h"
+#include "Global.h"
+
+Global* Path::gGlobal = NULL;
+
+void Path::ConstructPath(const v8::FunctionCallbackInfo<Value>& args) {
+    HandleScope handleScope(gGlobal->getIsolate());
+    Path* path = new Path();
+    args.This()->SetInternalField(0, External::New(path));
+}
+
+#define ADD_METHOD(name, fn) \
+    constructor->InstanceTemplate()->Set( \
+            String::NewFromUtf8( \
+                    global->getIsolate(), name, \
+                    String::kInternalizedString), \
+            FunctionTemplate::New(fn))
+
+// Install the constructor in the global scope so Paths can be constructed
+// in JS.
+void Path::AddToGlobal(Global* global) {
+    gGlobal = global;
+
+    // Create a stack-allocated handle scope.
+    HandleScope handleScope(gGlobal->getIsolate());
+
+    Handle<Context> context = gGlobal->getContext();
+
+    // Enter the scope so all operations take place in the scope.
+    Context::Scope contextScope(context);
+
+    Local<FunctionTemplate> constructor = FunctionTemplate::New(
+            Path::ConstructPath);
+    constructor->InstanceTemplate()->SetInternalFieldCount(1);
+
+    ADD_METHOD("close", ClosePath);
+    ADD_METHOD("moveTo", MoveTo);
+    ADD_METHOD("lineTo", LineTo);
+    ADD_METHOD("quadraticCurveTo", QuadraticCurveTo);
+    ADD_METHOD("bezierCurveTo", BezierCurveTo);
+    ADD_METHOD("arc", Arc);
+    ADD_METHOD("rect", Rect);
+
+    context->Global()->Set(String::New("Path"), constructor->GetFunction());
+}
+
+Path* Path::Unwrap(const v8::FunctionCallbackInfo<Value>& args) {
+    Handle<External> field = Handle<External>::Cast(
+            args.This()->GetInternalField(0));
+    void* ptr = field->Value();
+    return static_cast<Path*>(ptr);
+}
+
+void Path::ClosePath(const v8::FunctionCallbackInfo<Value>& args) {
+    Path* path = Unwrap(args);
+    path->fSkPath.close();
+}
+
+void Path::MoveTo(const v8::FunctionCallbackInfo<Value>& args) {
+    if (args.Length() != 2) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 2 arguments required."));
+        return;
+    }
+    double x = args[0]->NumberValue();
+    double y = args[1]->NumberValue();
+    Path* path = Unwrap(args);
+    path->fSkPath.moveTo(SkDoubleToScalar(x), SkDoubleToScalar(y));
+}
+
+void Path::LineTo(const v8::FunctionCallbackInfo<Value>& args) {
+    if (args.Length() != 2) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 2 arguments required."));
+        return;
+    }
+    double x = args[0]->NumberValue();
+    double y = args[1]->NumberValue();
+    Path* path = Unwrap(args);
+    path->fSkPath.lineTo(SkDoubleToScalar(x), SkDoubleToScalar(y));
+}
+
+void Path::QuadraticCurveTo(const v8::FunctionCallbackInfo<Value>& args) {
+    if (args.Length() != 4) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 4 arguments required."));
+        return;
+    }
+    double cpx = args[0]->NumberValue();
+    double cpy = args[1]->NumberValue();
+    double x = args[2]->NumberValue();
+    double y = args[3]->NumberValue();
+    Path* path = Unwrap(args);
+    // TODO(jcgregorio) Doesn't handle the empty last path case correctly per
+    // the HTML 5 spec.
+    path->fSkPath.quadTo(
+            SkDoubleToScalar(cpx), SkDoubleToScalar(cpy),
+            SkDoubleToScalar(x), SkDoubleToScalar(y));
+}
+
+void Path::BezierCurveTo(const v8::FunctionCallbackInfo<Value>& args) {
+    if (args.Length() != 6) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 6 arguments required."));
+        return;
+    }
+    double cp1x = args[0]->NumberValue();
+    double cp1y = args[1]->NumberValue();
+    double cp2x = args[2]->NumberValue();
+    double cp2y = args[3]->NumberValue();
+    double x = args[4]->NumberValue();
+    double y = args[5]->NumberValue();
+    Path* path = Unwrap(args);
+    // TODO(jcgregorio) Doesn't handle the empty last path case correctly per
+    // the HTML 5 spec.
+    path->fSkPath.cubicTo(
+            SkDoubleToScalar(cp1x), SkDoubleToScalar(cp1y),
+            SkDoubleToScalar(cp2x), SkDoubleToScalar(cp2y),
+            SkDoubleToScalar(x), SkDoubleToScalar(y));
+}
+
+void Path::Arc(const v8::FunctionCallbackInfo<Value>& args) {
+    if (args.Length() != 5 && args.Length() != 6) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 5 or 6 args required."));
+        return;
+    }
+    double x          = args[0]->NumberValue();
+    double y          = args[1]->NumberValue();
+    double radius     = args[2]->NumberValue();
+    double startAngle = args[3]->NumberValue();
+    double endAngle   = args[4]->NumberValue();
+    bool antiClockwise = false;
+    if (args.Length() == 6) {
+       antiClockwise = args[5]->BooleanValue();
+    }
+    double sweepAngle;
+    if (!antiClockwise) {
+      sweepAngle = endAngle - startAngle;
+    } else {
+      sweepAngle = startAngle - endAngle;
+      startAngle = endAngle;
+    }
+
+    Path* path = Unwrap(args);
+    SkRect rect = {
+        SkDoubleToScalar(x-radius),
+        SkDoubleToScalar(y-radius),
+        SkDoubleToScalar(x+radius),
+        SkDoubleToScalar(y+radius)
+    };
+
+    path->fSkPath.addArc(rect, SkRadiansToDegrees(startAngle),
+                         SkRadiansToDegrees(sweepAngle));
+}
+
+void Path::Rect(const v8::FunctionCallbackInfo<Value>& args) {
+    if (args.Length() != 4) {
+        args.GetIsolate()->ThrowException(
+                v8::String::NewFromUtf8(
+                        args.GetIsolate(), "Error: 4 arguments required."));
+        return;
+    }
+    double x = args[0]->NumberValue();
+    double y = args[1]->NumberValue();
+    double w = args[2]->NumberValue();
+    double h = args[3]->NumberValue();
+
+    SkRect rect = {
+        SkDoubleToScalar(x),
+        SkDoubleToScalar(y),
+        SkDoubleToScalar(x) + SkDoubleToScalar(w),
+        SkDoubleToScalar(y) + SkDoubleToScalar(h)
+    };
+    Path* path = Unwrap(args);
+    path->fSkPath.addRect(rect);
+}
+
diff --git a/experimental/SkV8Example/Path.h b/experimental/SkV8Example/Path.h
new file mode 100644
index 0000000..0c69770
--- /dev/null
+++ b/experimental/SkV8Example/Path.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ */
+
+#ifndef SkV8Example_Path_DEFINED
+#define SkV8Example_Path_DEFINED
+
+#include <v8.h>
+
+#include "SkPath.h"
+#include "SkTypes.h"
+
+class Global;
+
+class Path : SkNoncopyable {
+public:
+    Path() : fSkPath() {}
+    virtual ~Path() {}
+
+    const SkPath& getSkPath() { return fSkPath; }
+
+    // The JS Path constuctor implementation.
+    static void ConstructPath(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+    // Add the Path JS constructor to the global context.
+    static void AddToGlobal(Global* global);
+
+    // Path JS methods.
+    static void ClosePath(const v8::FunctionCallbackInfo<v8::Value>& args);
+    static void MoveTo(const v8::FunctionCallbackInfo<v8::Value>& args);
+    static void LineTo(const v8::FunctionCallbackInfo<v8::Value>& args);
+    static void QuadraticCurveTo(
+            const v8::FunctionCallbackInfo<v8::Value>& args);
+    static void BezierCurveTo(const v8::FunctionCallbackInfo<v8::Value>& args);
+    static void Arc(const v8::FunctionCallbackInfo<v8::Value>& args);
+    static void Rect(const v8::FunctionCallbackInfo<v8::Value>& args);
+private:
+    SkPath fSkPath;
+
+    static Path* Unwrap(const v8::FunctionCallbackInfo<v8::Value>& args);
+
+    static Global* gGlobal;
+};
+
+#endif
diff --git a/experimental/SkV8Example/SkV8Example.cpp b/experimental/SkV8Example/SkV8Example.cpp
index fb4f42d..7dc7db0 100644
--- a/experimental/SkV8Example/SkV8Example.cpp
+++ b/experimental/SkV8Example/SkV8Example.cpp
@@ -12,6 +12,8 @@
 
 #include "SkV8Example.h"
 #include "Global.h"
+#include "JsContext.h"
+#include "Path.h"
 
 #include "gl/GrGLUtil.h"
 #include "gl/GrGLDefines.h"
@@ -37,221 +39,10 @@
     SkGraphics::Term();
 }
 
-// Extracts a C string from a V8 Utf8Value.
-// TODO(jcgregrio) Currently dup'd in two files, fix.
-static const char* to_cstring(const v8::String::Utf8Value& value) {
-    return *value ? *value : "<string conversion failed>";
-}
 
-
-JsCanvas* JsCanvas::Unwrap(Handle<Object> obj) {
-    Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
-    void* ptr = field->Value();
-    return static_cast<JsCanvas*>(ptr);
-}
-
-void JsCanvas::FillRect(const v8::FunctionCallbackInfo<Value>& args) {
-    JsCanvas* jsCanvas = Unwrap(args.This());
-    SkCanvas* canvas = jsCanvas->fCanvas;
-
-    if (args.Length() != 4) {
-        args.GetIsolate()->ThrowException(
-                v8::String::NewFromUtf8(
-                        args.GetIsolate(), "Error: 4 arguments required."));
-        return;
-    }
-    // TODO(jcgregorio) Really figure out the conversion from JS numbers to
-    // SkScalars. Maybe test if int first? Not sure of the performance impact.
-    double x = args[0]->NumberValue();
-    double y = args[1]->NumberValue();
-    double w = args[2]->NumberValue();
-    double h = args[3]->NumberValue();
-
-    SkRect rect = {
-        SkDoubleToScalar(x),
-        SkDoubleToScalar(y),
-        SkDoubleToScalar(x) + SkDoubleToScalar(w),
-        SkDoubleToScalar(y) + SkDoubleToScalar(h)
-    };
-    canvas->drawRect(rect, jsCanvas->fFillStyle);
-}
-
-void JsCanvas::GetFillStyle(Local<String> name,
-                            const PropertyCallbackInfo<Value>& info) {
-    JsCanvas* jsCanvas = Unwrap(info.This());
-    SkColor color = jsCanvas->fFillStyle.getColor();
-    char buf[8];
-    sprintf(buf, "#%02X%02X%02X", SkColorGetR(color), SkColorGetG(color),
-            SkColorGetB(color));
-
-    info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buf));
-}
-
-void JsCanvas::SetFillStyle(Local<String> name, Local<Value> value,
-                            const PropertyCallbackInfo<void>& info) {
-    JsCanvas* jsCanvas = Unwrap(info.This());
-    Local<String> s = value->ToString();
-    if (s->Length() != 7) {
-        info.GetIsolate()->ThrowException(
-                v8::String::NewFromUtf8(
-                        info.GetIsolate(), "Invalid fill style format."));
-        return;
-    }
-    char buf[8];
-    s->WriteUtf8(buf, sizeof(buf));
-
-    if (buf[0] != '#') {
-        info.GetIsolate()->ThrowException(
-                v8::String::NewFromUtf8(
-                        info.GetIsolate(), "Invalid fill style format."));
-        return;
-    }
-
-    long color = strtol(buf+1, NULL, 16);
-    jsCanvas->fFillStyle.setColor(SkColorSetA(SkColor(color), SK_AlphaOPAQUE));
-}
-
-
-Persistent<ObjectTemplate> JsCanvas::fCanvasTemplate;
-
-Handle<ObjectTemplate> JsCanvas::makeCanvasTemplate() {
-    EscapableHandleScope handleScope(fGlobal->getIsolate());
-
-    Local<ObjectTemplate> result = ObjectTemplate::New();
-
-    // Add a field to store the pointer to a JsCanvas instance.
-    result->SetInternalFieldCount(1);
-
-    // Add accessors for each of the fields of the canvas object.
-    result->SetAccessor(
-      String::NewFromUtf8(
-        fGlobal->getIsolate(), "fillStyle", String::kInternalizedString),
-      GetFillStyle, SetFillStyle);
-
-    // Add methods.
-    result->Set(
-            String::NewFromUtf8(
-                    fGlobal->getIsolate(), "fillRect",
-                    String::kInternalizedString),
-            FunctionTemplate::New(FillRect));
-
-    // Return the result through the current handle scope.
-    return handleScope.Escape(result);
-}
-
-
-// Wraps 'this' in a Javascript object.
-Handle<Object> JsCanvas::wrap() {
-    // Handle scope for temporary handles.
-    EscapableHandleScope handleScope(fGlobal->getIsolate());
-
-    // Fetch the template for creating JavaScript JsCanvas wrappers.
-    // It only has to be created once, which we do on demand.
-    if (fCanvasTemplate.IsEmpty()) {
-        Handle<ObjectTemplate> raw_template = this->makeCanvasTemplate();
-        fCanvasTemplate.Reset(fGlobal->getIsolate(), raw_template);
-    }
-    Handle<ObjectTemplate> templ =
-            Local<ObjectTemplate>::New(fGlobal->getIsolate(), fCanvasTemplate);
-
-    // Create an empty JsCanvas wrapper.
-    Local<Object> result = templ->NewInstance();
-
-    // Wrap the raw C++ pointer in an External so it can be referenced
-    // from within JavaScript.
-    Handle<External> canvasPtr = External::New(fGlobal->getIsolate(), this);
-
-    // Store the canvas pointer in the JavaScript wrapper.
-    result->SetInternalField(0, canvasPtr);
-
-    // Return the result through the current handle scope.  Since each
-    // of these handles will go away when the handle scope is deleted
-    // we need to call Close to let one, the result, escape into the
-    // outer handle scope.
-    return handleScope.Escape(result);
-}
-
-void JsCanvas::onDraw(SkCanvas* canvas) {
-    // Record canvas and window in this.
-    fCanvas = canvas;
-
-    // Create a handle scope to keep the temporary object references.
-    HandleScope handleScope(fGlobal->getIsolate());
-
-    // Create a local context from our global context.
-    Local<Context> context = fGlobal->getContext();
-
-    // Enter the context so all the remaining operations take place there.
-    Context::Scope contextScope(context);
-
-    // Wrap the C++ this pointer in a JavaScript wrapper.
-    Handle<Object> canvasObj = this->wrap();
-
-    // Set up an exception handler before calling the Process function.
-    TryCatch tryCatch;
-
-    // Invoke the process function, giving the global object as 'this'
-    // and one argument, this JsCanvas.
-    const int argc = 1;
-    Handle<Value> argv[argc] = { canvasObj };
-    Local<Function> onDraw =
-            Local<Function>::New(fGlobal->getIsolate(), fOnDraw);
-    Handle<Value> result = onDraw->Call(context->Global(), argc, argv);
-
-    // Handle any exceptions or output.
-    if (result.IsEmpty()) {
-        SkASSERT(tryCatch.HasCaught());
-        // Print errors that happened during execution.
-        fGlobal->reportException(&tryCatch);
-    } else {
-        SkASSERT(!tryCatch.HasCaught());
-        if (!result->IsUndefined()) {
-            // If all went well and the result wasn't undefined then print
-            // the returned value.
-            String::Utf8Value str(result);
-            const char* cstr = to_cstring(str);
-            printf("%s\n", cstr);
-        }
-    }
-}
-
-// Fetch the onDraw function from the global context.
-bool JsCanvas::initialize() {
-
-    // Create a stack-allocated handle scope.
-    HandleScope handleScope(fGlobal->getIsolate());
-
-    // Create a local context from our global context.
-    Local<Context> context = fGlobal->getContext();
-
-    // Enter the scope so all operations take place in the scope.
-    Context::Scope contextScope(context);
-
-    v8::TryCatch try_catch;
-
-    Handle<String> fn_name = String::NewFromUtf8(
-        fGlobal->getIsolate(), "onDraw");
-    Handle<Value> fn_val = context->Global()->Get(fn_name);
-
-    if (!fn_val->IsFunction()) {
-        printf("Not a function.\n");
-        return false;
-    }
-
-    // It is a function; cast it to a Function.
-    Handle<Function> fn_fun = Handle<Function>::Cast(fn_val);
-
-    // Store the function in a Persistent handle, since we also want that to
-    // remain after this call returns.
-    fOnDraw.Reset(fGlobal->getIsolate(), fn_fun);
-
-    return true;
-}
-
-
-SkV8ExampleWindow::SkV8ExampleWindow(void* hwnd, JsCanvas* canvas)
+SkV8ExampleWindow::SkV8ExampleWindow(void* hwnd, JsContext* context)
     : INHERITED(hwnd)
-    , fJsCanvas(canvas)
+    , fJsContext(context)
 {
     this->setConfig(SkBitmap::kARGB_8888_Config);
     this->setVisibleP(true);
@@ -264,7 +55,7 @@
     canvas->drawColor(SK_ColorWHITE);
 
     // Now jump into JS and call the onDraw(canvas) method defined there.
-    fJsCanvas->onDraw(canvas);
+    fJsContext->onDraw(canvas);
 
     canvas->restore();
 
@@ -308,19 +99,20 @@
         printf("Could not load file: %s.\n", FLAGS_infile[0]);
         exit(1);
     }
+    Path::AddToGlobal(global);
 
     if (!global->parseScript(script)) {
         printf("Failed to parse file: %s.\n", FLAGS_infile[0]);
         exit(1);
     }
 
-    JsCanvas* jsCanvas = new JsCanvas(global);
+    JsContext* jsContext = new JsContext(global);
 
-    if (!jsCanvas->initialize()) {
+    if (!jsContext->initialize()) {
         printf("Failed to initialize.\n");
         exit(1);
     }
-    SkV8ExampleWindow* win = new SkV8ExampleWindow(hwnd, jsCanvas);
+    SkV8ExampleWindow* win = new SkV8ExampleWindow(hwnd, jsContext);
     global->setWindow(win);
     return win;
 }
diff --git a/experimental/SkV8Example/SkV8Example.h b/experimental/SkV8Example/SkV8Example.h
index 3073796..770597f 100644
--- a/experimental/SkV8Example/SkV8Example.h
+++ b/experimental/SkV8Example/SkV8Example.h
@@ -10,20 +10,13 @@
 #ifndef SkV8Example_DEFINED
 #define SkV8Example_DEFINED
 
-#include <v8.h>
-
 #include "SkWindow.h"
-#include "SkPaint.h"
 
-using namespace v8;
-
-class SkCanvas;
-class JsCanvas;
-class Global;
+class JsContext;
 
 class SkV8ExampleWindow : public SkOSWindow {
 public:
-    SkV8ExampleWindow(void* hwnd, JsCanvas* canvas);
+    SkV8ExampleWindow(void* hwnd, JsContext* canvas);
 
 protected:
     virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE;
@@ -34,66 +27,7 @@
 
 private:
     typedef SkOSWindow INHERITED;
-    JsCanvas* fJsCanvas;
-};
-
-
-// Provides the canvas implementation in JS, and the OnDraw() method in C++
-// that's used to bridge from C++ to JS. Should be used in JS as:
-//
-//  function onDraw(canvas) {
-//    canvas.fillStyle="#FF0000";
-//    canvas.fillRect(x, y, w, h);
-//  }
-class JsCanvas {
-public:
-    JsCanvas(Global* global)
-            : fGlobal(global)
-            , fCanvas(NULL)
-    {
-        fFillStyle.setColor(SK_ColorRED);
-    }
-    ~JsCanvas();
-
-    // Parse the script.
-    bool initialize();
-
-    // Call this with the SkCanvas you want onDraw to draw on.
-    void onDraw(SkCanvas* canvas);
-
-private:
-    // Implementation of the canvas.fillStyle field.
-    static void GetFillStyle(Local<String> name,
-                             const PropertyCallbackInfo<Value>& info);
-    static void SetFillStyle(Local<String> name, Local<Value> value,
-                             const PropertyCallbackInfo<void>& info);
-
-    // Implementation of the canvas.fillRect() JS function.
-    static void FillRect(const v8::FunctionCallbackInfo<Value>& args);
-
-    // Get the pointer out of obj.
-    static JsCanvas* Unwrap(Handle<Object> obj);
-
-    // Create a template for JS object associated with JsCanvas, called lazily
-    // by Wrap() and the results are stored in fCanvasTemplate;
-    Handle<ObjectTemplate> makeCanvasTemplate();
-
-    // Wrap the 'this' pointer into an Object. Can be retrieved via Unwrap.
-    Handle<Object> wrap();
-
-    Global* fGlobal;
-
-    // Only valid when inside OnDraw().
-    SkCanvas* fCanvas;
-
-    SkPaint fFillStyle;
-
-    // A handle to the onDraw function defined in the script.
-    Persistent<Function> fOnDraw;
-
-    // The template for what a canvas object looks like. The canvas object is
-    // what's passed into the JS onDraw() function.
-    static Persistent<ObjectTemplate> fCanvasTemplate;
+    JsContext* fJsContext;
 };
 
 #endif
diff --git a/experimental/SkV8Example/path.js b/experimental/SkV8Example/path.js
new file mode 100644
index 0000000..a7b2c28
--- /dev/null
+++ b/experimental/SkV8Example/path.js
@@ -0,0 +1,34 @@
+/**
+ * @fileoverview Sample onDraw script for use with SkV8Example.
+ */
+var onDraw = function(){
+    var p = new Path();
+    p.moveTo(0, 0);
+    p.bezierCurveTo(0, 100, 100, 0, 200, 200);
+    p.close();
+    p.moveTo(0, 300);
+    p.arc(0, 300, 40, Math.PI/2, 3/2*Math.PI);
+    function f(context) {
+        context.translate(10, 10);
+        for (var i=0; i<256; i++) {
+            context.fillStyle = '#0000' + toHex(i);
+            context.stroke(p);
+            context.translate(1, 0);
+        }
+        context.fillStyle = '#ff0000';
+        print(context.width, context.height);
+        context.resetTransform();
+        context.fillRect(context.width/2, context.height/2, 20, 20);
+
+    };
+    return f;
+}();
+
+
+function toHex(n) {
+  var s = n.toString(16);
+  if (s.length == 1) {
+    s = "0" + s;
+  }
+  return s;
+}
diff --git a/experimental/SkV8Example/sample.js b/experimental/SkV8Example/sample.js
index 841dd5d..81c717c 100644
--- a/experimental/SkV8Example/sample.js
+++ b/experimental/SkV8Example/sample.js
@@ -3,10 +3,10 @@
  */
 var onDraw = function(){
   var tick = 0;
-  function f(canvas) {
+  function f(context) {
     tick += 0.1;
-    canvas.fillStyle = '#0000ff';
-    canvas.fillRect(100, 100, Math.sin(tick)*100, Math.cos(tick)*100);
+    context.fillStyle = '#0000ff';
+    context.fillRect(100, 100, Math.sin(tick)*100, Math.cos(tick)*100);
   };
   return f;
 }();
diff --git a/experimental/SkV8Example/speed.js b/experimental/SkV8Example/speed.js
new file mode 100644
index 0000000..5b0f001
--- /dev/null
+++ b/experimental/SkV8Example/speed.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview Sample onDraw script for use with SkV8Example.
+ */
+var onDraw = function(){
+  var tick = 0;
+  function f(canvas) {
+    tick += 0.1;
+    canvas.fillStyle = '#0000ff';
+    canvas.fillRect(100, 100, Math.sin(tick)*100, Math.cos(tick)*100);
+    inval();
+  };
+
+  function onTimeout() {
+      print(tick*10, " FPS");
+      setTimeout(onTimeout, 1000);
+      tick=0;
+  }
+
+  setTimeout(onTimeout, 1000);
+
+  return f;
+}();
+