SkV8Example.cpp revision 48d94b8ce51a4b75fe1fe996b135fcd5b2d34779
1/*
2 * Copyright 2013 Google Inc.
3 *
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 *
8 */
9#include <v8.h>
10
11using namespace v8;
12
13#include "SkV8Example.h"
14
15#include "gl/GrGLUtil.h"
16#include "gl/GrGLDefines.h"
17#include "gl/GrGLInterface.h"
18#include "SkApplication.h"
19#include "SkCommandLineFlags.h"
20#include "SkData.h"
21#include "SkDraw.h"
22#include "SkGpuDevice.h"
23#include "SkGraphics.h"
24#include "SkScalar.h"
25
26
27DEFINE_string2(infile, i, NULL, "Name of file to load JS from.\n");
28
29void application_init() {
30    SkGraphics::Init();
31    SkEvent::Init();
32}
33
34void application_term() {
35    SkEvent::Term();
36    SkGraphics::Term();
37}
38
39// Extracts a C string from a V8 Utf8Value.
40const char* ToCString(const v8::String::Utf8Value& value) {
41    return *value ? *value : "<string conversion failed>";
42}
43
44// Slight modification to an original function found in the V8 sample shell.cc.
45void reportException(Isolate* isolate, TryCatch* try_catch) {
46    HandleScope handleScope(isolate);
47    String::Utf8Value exception(try_catch->Exception());
48    const char* exception_string = ToCString(exception);
49    Handle<Message> message = try_catch->Message();
50    if (message.IsEmpty()) {
51        // V8 didn't provide any extra information about this error; just
52        // print the exception.
53        fprintf(stderr, "%s\n", exception_string);
54    } else {
55        // Print (filename):(line number): (message).
56        String::Utf8Value filename(message->GetScriptResourceName());
57        const char* filename_string = ToCString(filename);
58        int linenum = message->GetLineNumber();
59        fprintf(stderr,
60                "%s:%i: %s\n", filename_string, linenum, exception_string);
61        // Print line of source code.
62        String::Utf8Value sourceline(message->GetSourceLine());
63        const char* sourceline_string = ToCString(sourceline);
64        fprintf(stderr, "%s\n", sourceline_string);
65        // Print wavy underline.
66        int start = message->GetStartColumn();
67        for (int i = 0; i < start; i++) {
68            fprintf(stderr, " ");
69        }
70        int end = message->GetEndColumn();
71        for (int i = start; i < end; i++) {
72            fprintf(stderr, "^");
73        }
74        fprintf(stderr, "\n");
75        String::Utf8Value stack_trace(try_catch->StackTrace());
76        if (stack_trace.length() > 0) {
77            const char* stack_trace_string = ToCString(stack_trace);
78            fprintf(stderr, "%s\n", stack_trace_string);
79        }
80    }
81}
82
83SkV8ExampleWindow::SkV8ExampleWindow(void* hwnd, JsCanvas* canvas)
84    : INHERITED(hwnd)
85    , fJsCanvas(canvas)
86{
87    this->setConfig(SkBitmap::kARGB_8888_Config);
88    this->setVisibleP(true);
89    this->setClipToBounds(false);
90}
91
92JsCanvas* JsCanvas::Unwrap(Handle<Object> obj) {
93    Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
94    void* ptr = field->Value();
95    return static_cast<JsCanvas*>(ptr);
96}
97
98void JsCanvas::Inval(const v8::FunctionCallbackInfo<Value>& args) {
99    Unwrap(args.This())->fWindow->inval(NULL);
100}
101
102void JsCanvas::FillRect(const v8::FunctionCallbackInfo<Value>& args) {
103    JsCanvas* jsCanvas = Unwrap(args.This());
104    SkCanvas* canvas = jsCanvas->fCanvas;
105
106    if (args.Length() != 4) {
107        args.GetIsolate()->ThrowException(
108                v8::String::NewFromUtf8(
109                        args.GetIsolate(), "Error: 4 arguments required."));
110        return;
111    }
112    // TODO(jcgregorio) Really figure out the conversion from JS numbers to
113    // SkScalars. Maybe test if int first? Not sure of the performance impact.
114    double x = args[0]->NumberValue();
115    double y = args[1]->NumberValue();
116    double w = args[2]->NumberValue();
117    double h = args[3]->NumberValue();
118
119    SkRect rect = {
120        SkDoubleToScalar(x),
121        SkDoubleToScalar(y),
122        SkDoubleToScalar(x) + SkDoubleToScalar(w),
123        SkDoubleToScalar(y) + SkDoubleToScalar(h)
124    };
125    canvas->drawRect(rect, jsCanvas->fFillStyle);
126}
127
128void JsCanvas::GetFillStyle(Local<String> name,
129                            const PropertyCallbackInfo<Value>& info) {
130    JsCanvas* jsCanvas = Unwrap(info.This());
131    SkColor color = jsCanvas->fFillStyle.getColor();
132    char buf[8];
133    sprintf(buf, "#%02X%02X%02X", SkColorGetR(color), SkColorGetG(color),
134            SkColorGetB(color));
135
136    info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buf));
137}
138
139void JsCanvas::SetFillStyle(Local<String> name, Local<Value> value,
140                            const PropertyCallbackInfo<void>& info) {
141    JsCanvas* jsCanvas = Unwrap(info.This());
142    Local<String> s = value->ToString();
143    if (s->Length() != 7) {
144        info.GetIsolate()->ThrowException(
145                v8::String::NewFromUtf8(
146                        info.GetIsolate(), "Invalid fill style format."));
147        return;
148    }
149    char buf[8];
150    s->WriteUtf8(buf, sizeof(buf));
151
152    if (buf[0] != '#') {
153        info.GetIsolate()->ThrowException(
154                v8::String::NewFromUtf8(
155                        info.GetIsolate(), "Invalid fill style format."));
156        return;
157    }
158
159    long color = strtol(buf+1, NULL, 16);
160    jsCanvas->fFillStyle.setColor(SkColorSetA(SkColor(color), SK_AlphaOPAQUE));
161}
162
163
164Persistent<ObjectTemplate> JsCanvas::fCanvasTemplate;
165
166Handle<ObjectTemplate> JsCanvas::makeCanvasTemplate() {
167    EscapableHandleScope handleScope(fIsolate);
168
169    Local<ObjectTemplate> result = ObjectTemplate::New();
170
171    // Add a field to store the pointer to a JsCanvas instance.
172    result->SetInternalFieldCount(1);
173
174    // Add accessors for each of the fields of the canvas object.
175    result->SetAccessor(
176      String::NewFromUtf8(fIsolate, "fillStyle", String::kInternalizedString),
177      GetFillStyle, SetFillStyle);
178
179    // Add methods.
180    result->Set(
181            String::NewFromUtf8(
182                    fIsolate, "fillRect", String::kInternalizedString),
183            FunctionTemplate::New(FillRect));
184    result->Set(
185            String::NewFromUtf8(
186                    fIsolate, "inval", String::kInternalizedString),
187            FunctionTemplate::New(Inval));
188
189    // Return the result through the current handle scope.
190    return handleScope.Escape(result);
191}
192
193
194// Wraps 'this' in a Javascript object.
195Handle<Object> JsCanvas::wrap() {
196    // Handle scope for temporary handles.
197    EscapableHandleScope handleScope(fIsolate);
198
199    // Fetch the template for creating JavaScript JsCanvas wrappers.
200    // It only has to be created once, which we do on demand.
201    if (fCanvasTemplate.IsEmpty()) {
202        Handle<ObjectTemplate> raw_template = this->makeCanvasTemplate();
203        fCanvasTemplate.Reset(fIsolate, raw_template);
204    }
205    Handle<ObjectTemplate> templ =
206            Local<ObjectTemplate>::New(fIsolate, fCanvasTemplate);
207
208    // Create an empty JsCanvas wrapper.
209    Local<Object> result = templ->NewInstance();
210
211    // Wrap the raw C++ pointer in an External so it can be referenced
212    // from within JavaScript.
213    Handle<External> canvasPtr = External::New(fIsolate, this);
214
215    // Store the canvas pointer in the JavaScript wrapper.
216    result->SetInternalField(0, canvasPtr);
217
218    // Return the result through the current handle scope.  Since each
219    // of these handles will go away when the handle scope is deleted
220    // we need to call Close to let one, the result, escape into the
221    // outer handle scope.
222    return handleScope.Escape(result);
223}
224
225void JsCanvas::onDraw(SkCanvas* canvas, SkOSWindow* window) {
226    // Record canvas and window in this.
227    fCanvas = canvas;
228    fWindow = window;
229
230    // Create a handle scope to keep the temporary object references.
231    HandleScope handleScope(fIsolate);
232
233    // Create a local context from our global context.
234    Local<Context> context = Local<Context>::New(fIsolate, fContext);
235
236    // Enter the context so all the remaining operations take place there.
237    Context::Scope contextScope(context);
238
239    // Wrap the C++ this pointer in a JavaScript wrapper.
240    Handle<Object> canvasObj = this->wrap();
241
242    // Set up an exception handler before calling the Process function.
243    TryCatch tryCatch;
244
245    // Invoke the process function, giving the global object as 'this'
246    // and one argument, this JsCanvas.
247    const int argc = 1;
248    Handle<Value> argv[argc] = { canvasObj };
249    Local<Function> onDraw =
250            Local<Function>::New(fIsolate, fOnDraw);
251    Handle<Value> result = onDraw->Call(context->Global(), argc, argv);
252
253    // Handle any exceptions or output.
254    if (result.IsEmpty()) {
255        SkASSERT(tryCatch.HasCaught());
256        // Print errors that happened during execution.
257        reportException(fIsolate, &tryCatch);
258    } else {
259        SkASSERT(!tryCatch.HasCaught());
260        if (!result->IsUndefined()) {
261            // If all went well and the result wasn't undefined then print
262            // the returned value.
263            String::Utf8Value str(result);
264            const char* cstr = ToCString(str);
265            printf("%s\n", cstr);
266        }
267    }
268}
269
270void SkV8ExampleWindow::onDraw(SkCanvas* canvas) {
271
272    canvas->save();
273    canvas->drawColor(SK_ColorWHITE);
274
275    // Now jump into JS and call the onDraw(canvas) method defined there.
276    fJsCanvas->onDraw(canvas, this);
277
278    canvas->restore();
279
280    INHERITED::onDraw(canvas);
281}
282
283
284#ifdef SK_BUILD_FOR_WIN
285void SkV8ExampleWindow::onHandleInval(const SkIRect& rect) {
286    RECT winRect;
287    winRect.top = rect.top();
288    winRect.bottom = rect.bottom();
289    winRect.right = rect.right();
290    winRect.left = rect.left();
291    InvalidateRect((HWND)this->getHWND(), &winRect, false);
292}
293#endif
294
295// Creates a new execution environment containing the built-in
296// function draw().
297Handle<Context> createRootContext(Isolate* isolate) {
298  // Create a template for the global object.
299  Handle<ObjectTemplate> global = ObjectTemplate::New();
300
301  // This is where we would inject any globals into the root Context
302  // using global->Set(...)
303
304  return Context::New(isolate, NULL, global);
305}
306
307
308// Parse and run script. Then fetch out the onDraw function from the global
309// object.
310bool JsCanvas::initialize(const char script[]) {
311
312    // Create a stack-allocated handle scope.
313    HandleScope handleScope(fIsolate);
314
315    printf("Before create context\n");
316
317    // Create a new context.
318    Handle<Context> context = createRootContext(fIsolate);
319
320    // Enter the scope so all operations take place in the scope.
321    Context::Scope contextScope(context);
322
323    v8::TryCatch try_catch;
324
325    // Compile the source code.
326    Handle<String> source = String::NewFromUtf8(fIsolate, script);
327    printf("Before Compile\n");
328    Handle<Script> compiled_script = Script::Compile(source);
329    printf("After Compile\n");
330
331    if (compiled_script.IsEmpty()) {
332        // Print errors that happened during compilation.
333        reportException(fIsolate, &try_catch);
334        return false;
335    }
336    printf("After Exception.\n");
337
338    // Try running it now to create the onDraw function.
339    Handle<Value> result = compiled_script->Run();
340
341    // Handle any exceptions or output.
342    if (result.IsEmpty()) {
343        SkASSERT(try_catch.HasCaught());
344        // Print errors that happened during execution.
345        reportException(fIsolate, &try_catch);
346        return false;
347    } else {
348        SkASSERT(!try_catch.HasCaught());
349        if (!result->IsUndefined()) {
350            // If all went well and the result wasn't undefined then print
351            // the returned value.
352            String::Utf8Value str(result);
353            const char* cstr = ToCString(str);
354            printf("%s\n", cstr);
355            return false;
356        }
357    }
358
359    Handle<String> fn_name = String::NewFromUtf8(fIsolate, "onDraw");
360    Handle<Value> fn_val = context->Global()->Get(fn_name);
361
362    if (!fn_val->IsFunction()) {
363        printf("Not a function.\n");
364        return false;
365    }
366
367    // It is a function; cast it to a Function.
368    Handle<Function> fn_fun = Handle<Function>::Cast(fn_val);
369
370    // Store the function in a Persistent handle, since we also want that to
371    // remain after this call returns.
372    fOnDraw.Reset(fIsolate, fn_fun);
373
374    // Also make the context persistent.
375    fContext.Reset(fIsolate, context);
376    return true;
377}
378
379SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv) {
380    printf("Started\n");
381
382    SkCommandLineFlags::Parse(argc, argv);
383
384    // Get the default Isolate created at startup.
385    Isolate* isolate = Isolate::GetCurrent();
386
387    JsCanvas* jsCanvas = new JsCanvas(isolate);
388
389    const char* script =
390"function onDraw(canvas) {              \n"
391"    canvas.fillStyle = '#00FF00';      \n"
392"    canvas.fillRect(20, 20, 100, 100); \n"
393"    canvas.inval();                    \n"
394"}                                      \n";
395
396    SkAutoTUnref<SkData> data;
397    if (FLAGS_infile.count()) {
398        data.reset(SkData::NewFromFileName(FLAGS_infile[0]));
399        script = static_cast<const char*>(data->data());
400    }
401    if (NULL == script) {
402        printf("Could not load file: %s.\n", FLAGS_infile[0]);
403        exit(1);
404    }
405    if (!jsCanvas->initialize(script)) {
406        printf("Failed to initialize.\n");
407        exit(1);
408    }
409    return new SkV8ExampleWindow(hwnd, jsCanvas);
410}
411