diff options
Diffstat (limited to 'experimental')
-rw-r--r-- | experimental/SkV8Example/Global.cpp | 22 | ||||
-rw-r--r-- | experimental/SkV8Example/Global.h | 2 | ||||
-rw-r--r-- | experimental/SkV8Example/JsContext.cpp | 336 | ||||
-rw-r--r-- | experimental/SkV8Example/JsContext.h | 96 | ||||
-rw-r--r-- | experimental/SkV8Example/Path.cpp | 192 | ||||
-rw-r--r-- | experimental/SkV8Example/Path.h | 50 | ||||
-rw-r--r-- | experimental/SkV8Example/SkV8Example.cpp | 226 | ||||
-rw-r--r-- | experimental/SkV8Example/SkV8Example.h | 72 | ||||
-rw-r--r-- | experimental/SkV8Example/path.js | 34 | ||||
-rw-r--r-- | experimental/SkV8Example/sample.js | 6 | ||||
-rw-r--r-- | experimental/SkV8Example/speed.js | 23 |
11 files changed, 761 insertions, 298 deletions
diff --git a/experimental/SkV8Example/Global.cpp b/experimental/SkV8Example/Global.cpp index b45d2de3c6..998f92bb73 100644 --- a/experimental/SkV8Example/Global.cpp +++ b/experimental/SkV8Example/Global.cpp @@ -190,6 +190,17 @@ Handle<Context> Global::createRootContext() { 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 @@ bool Global::parseScript(const char script[]) { // 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 @@ bool Global::parseScript(const char script[]) { // 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 @@ bool Global::parseScript(const char script[]) { 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 3377e21ede..6bd3c70962 100644 --- a/experimental/SkV8Example/Global.h +++ b/experimental/SkV8Example/Global.h @@ -33,6 +33,7 @@ public: , fLastTimerID(0) { gGlobal = this; + this->initialize(); } virtual ~Global() {} @@ -57,6 +58,7 @@ public: 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 0000000000..87f7d0efe3 --- /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 0000000000..0fc180d956 --- /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 0000000000..a2d1030326 --- /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 0000000000..0c697700cd --- /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 fb4f42d705..7dc7db0b4e 100644 --- a/experimental/SkV8Example/SkV8Example.cpp +++ b/experimental/SkV8Example/SkV8Example.cpp @@ -12,6 +12,8 @@ using namespace v8; #include "SkV8Example.h" #include "Global.h" +#include "JsContext.h" +#include "Path.h" #include "gl/GrGLUtil.h" #include "gl/GrGLDefines.h" @@ -37,221 +39,10 @@ void application_term() { 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 @@ void SkV8ExampleWindow::onDraw(SkCanvas* canvas) { 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 @@ SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv) { 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 30737963f3..770597f55d 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 @@ protected: 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 0000000000..a7b2c28d46 --- /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 841dd5daa7..81c717c7e8 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 0000000000..5b0f001354 --- /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; +}(); + |