// // CanvasRenderingContext2d.cc // // Copyright (c) 2010 LearnBoost // #include #include #include #include "Canvas.h" #include "CanvasRenderingContext2d.h" #include "CanvasGradient.h" using namespace v8; using namespace node; /* * Set RGBA. */ #define RGBA(_,R,G,B,A) \ _.r = R / 255 * 1; \ _.g = G / 255 * 1; \ _.b = B / 255 * 1; \ _.a = A; \ /* * Set source. */ #define SET_SOURCE(_) \ if (_##Pattern) \ cairo_set_source(ctx, _##Pattern); \ else \ SET_SOURCE_RGBA(_) /* * Set source RGBA. */ #define SET_SOURCE_RGBA(_) \ cairo_set_source_rgba(ctx \ , _.r \ , _.g \ , _.b \ , _.a * context->state->globalAlpha); /* * Rectangle arg assertions. */ #define RECT_ARGS \ if (!args[0]->IsNumber() \ ||!args[1]->IsNumber() \ ||!args[2]->IsNumber() \ ||!args[3]->IsNumber()) return Undefined(); \ double x = args[0]->Int32Value(); \ double y = args[1]->Int32Value(); \ double width = args[2]->Int32Value(); \ double height = args[3]->Int32Value(); /* * Text baselines. */ enum { TEXT_BASELINE_ALPHABETIC , TEXT_BASELINE_TOP , TEXT_BASELINE_BOTTOM , TEXT_BASELINE_MIDDLE , TEXT_BASELINE_IDEOGRAPHIC , TEXT_BASELINE_HANGING }; /* * Initialize Context2d. */ void Context2d::Initialize(Handle target) { HandleScope scope; // Constructor Local t = FunctionTemplate::New(Context2d::New); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(String::NewSymbol("CanvasRenderingContext2d")); // Prototype Local proto = t->PrototypeTemplate(); NODE_SET_PROTOTYPE_METHOD(t, "save", Save); NODE_SET_PROTOTYPE_METHOD(t, "restore", Restore); NODE_SET_PROTOTYPE_METHOD(t, "rotate", Rotate); NODE_SET_PROTOTYPE_METHOD(t, "translate", Translate); NODE_SET_PROTOTYPE_METHOD(t, "transform", Transform); NODE_SET_PROTOTYPE_METHOD(t, "resetTransform", ResetTransform); NODE_SET_PROTOTYPE_METHOD(t, "isPointInPath", IsPointInPath); NODE_SET_PROTOTYPE_METHOD(t, "scale", Scale); NODE_SET_PROTOTYPE_METHOD(t, "clip", Clip); NODE_SET_PROTOTYPE_METHOD(t, "fill", Fill); NODE_SET_PROTOTYPE_METHOD(t, "stroke", Stroke); NODE_SET_PROTOTYPE_METHOD(t, "fillText", FillText); NODE_SET_PROTOTYPE_METHOD(t, "strokeText", StrokeText); NODE_SET_PROTOTYPE_METHOD(t, "fillRect", FillRect); NODE_SET_PROTOTYPE_METHOD(t, "strokeRect", StrokeRect); NODE_SET_PROTOTYPE_METHOD(t, "clearRect", ClearRect); NODE_SET_PROTOTYPE_METHOD(t, "rect", Rect); NODE_SET_PROTOTYPE_METHOD(t, "setTextBaseline", SetTextBaseline); NODE_SET_PROTOTYPE_METHOD(t, "setTextAlignment", SetTextAlignment); NODE_SET_PROTOTYPE_METHOD(t, "measureText", MeasureText); NODE_SET_PROTOTYPE_METHOD(t, "moveTo", MoveTo); NODE_SET_PROTOTYPE_METHOD(t, "lineTo", LineTo); NODE_SET_PROTOTYPE_METHOD(t, "bezierCurveTo", BezierCurveTo); NODE_SET_PROTOTYPE_METHOD(t, "quadraticCurveTo", QuadraticCurveTo); NODE_SET_PROTOTYPE_METHOD(t, "beginPath", BeginPath); NODE_SET_PROTOTYPE_METHOD(t, "closePath", ClosePath); NODE_SET_PROTOTYPE_METHOD(t, "arc", Arc); NODE_SET_PROTOTYPE_METHOD(t, "setFont", SetFont); NODE_SET_PROTOTYPE_METHOD(t, "setShadowRGBA", SetShadowRGBA); NODE_SET_PROTOTYPE_METHOD(t, "setFillRGBA", SetFillRGBA); NODE_SET_PROTOTYPE_METHOD(t, "setStrokeRGBA", SetStrokeRGBA); NODE_SET_PROTOTYPE_METHOD(t, "setFillPattern", SetFillPattern); NODE_SET_PROTOTYPE_METHOD(t, "setStrokePattern", SetStrokePattern); proto->SetAccessor(String::NewSymbol("globalCompositeOperation"), GetGlobalCompositeOperation, SetGlobalCompositeOperation); proto->SetAccessor(String::NewSymbol("globalAlpha"), GetGlobalAlpha, SetGlobalAlpha); proto->SetAccessor(String::NewSymbol("miterLimit"), GetMiterLimit, SetMiterLimit); proto->SetAccessor(String::NewSymbol("lineWidth"), GetLineWidth, SetLineWidth); proto->SetAccessor(String::NewSymbol("lineCap"), GetLineCap, SetLineCap); proto->SetAccessor(String::NewSymbol("lineJoin"), GetLineJoin, SetLineJoin); proto->SetAccessor(String::NewSymbol("shadowOffsetX"), GetShadowOffsetX, SetShadowOffsetX); proto->SetAccessor(String::NewSymbol("shadowOffsetY"), GetShadowOffsetY, SetShadowOffsetY); proto->SetAccessor(String::NewSymbol("shadowBlur"), GetShadowBlur, SetShadowBlur); target->Set(String::NewSymbol("CanvasRenderingContext2d"), t->GetFunction()); } /* * Initialize a new Context2d with the given canvas. */ Handle Context2d::New(const Arguments &args) { HandleScope scope; Canvas *canvas = ObjectWrap::Unwrap(args[0]->ToObject()); Context2d *context = new Context2d(canvas); context->Wrap(args.This()); return args.This(); } /* * Create a cairo context. */ Context2d::Context2d(Canvas *canvas): ObjectWrap() { _canvas = canvas; _context = cairo_create(canvas->getSurface()); cairo_set_line_width(_context, 1); state = states[stateno = 0] = (canvas_state_t *) malloc(sizeof(canvas_state_t)); state->shadowBlur = state->shadowOffsetX = state->shadowOffsetY = 0; state->globalAlpha = 1; state->textAlignment = -1; state->fillPattern = state->strokePattern = NULL; RGBA(state->fill,0,0,0,1); RGBA(state->stroke,0,0,0,1); RGBA(state->shadow,0,0,0,0); } /* * Destroy cairo context. */ Context2d::~Context2d() { cairo_destroy(_context); } /* * Save cairo / canvas state. */ void Context2d::save() { cairo_save(_context); saveState(); } /* * Restore cairo / canvas state. */ void Context2d::restore() { cairo_restore(_context); restoreState(); } /* * Save the current state. */ void Context2d::saveState() { if (stateno == CANVAS_MAX_STATES) return; states[++stateno] = (canvas_state_t *) malloc(sizeof(canvas_state_t)); memcpy(states[stateno], state, sizeof(canvas_state_t)); state = states[stateno]; } /* * Restore state. */ void Context2d::restoreState() { if (0 == stateno) return; state = states[--stateno]; } /* * Save flat path. */ void Context2d::savePath() { _path = cairo_copy_path_flat(_context); cairo_new_path(_context); } /* * Restore flat path. */ void Context2d::restorePath() { cairo_append_path(_context, _path); } /* * Get global alpha. */ Handle Context2d::GetGlobalAlpha(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); return Number::New(context->state->globalAlpha); } /* * Set global alpha. */ void Context2d::SetGlobalAlpha(Local prop, Local val, const AccessorInfo &info) { double n = val->NumberValue(); if (n >= 0 && n <= 1) { Context2d *context = ObjectWrap::Unwrap(info.This()); context->state->globalAlpha = n; } } /* * Get global composite operation. */ Handle Context2d::GetGlobalCompositeOperation(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); cairo_t *ctx = context->getContext(); switch (cairo_get_operator(ctx)) { case CAIRO_OPERATOR_ATOP: return String::NewSymbol("source-atop"); case CAIRO_OPERATOR_IN: return String::NewSymbol("source-in"); case CAIRO_OPERATOR_OUT: return String::NewSymbol("source-out"); case CAIRO_OPERATOR_XOR: return String::NewSymbol("xor"); case CAIRO_OPERATOR_DEST_ATOP: return String::NewSymbol("destination-atop"); case CAIRO_OPERATOR_DEST_IN: return String::NewSymbol("destination-in"); case CAIRO_OPERATOR_DEST_OUT: return String::NewSymbol("destination-out"); case CAIRO_OPERATOR_DEST_OVER: return String::NewSymbol("destination-over"); case CAIRO_OPERATOR_ADD: return String::NewSymbol("lighter"); default: return String::NewSymbol("source-over"); } } /* * Set global composite operation. */ void Context2d::SetGlobalCompositeOperation(Local prop, Local val, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); cairo_t *ctx = context->getContext(); String::AsciiValue type(val->ToString()); if (0 == strcmp("xor", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_XOR); }else if (0 == strcmp("lighter", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_ADD); }else if (0 == strcmp("source-atop", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_ATOP); } else if (0 == strcmp("source-in", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_IN); } else if (0 == strcmp("source-out", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_OUT); } else if (0 == strcmp("destination-atop", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_DEST_ATOP); } else if (0 == strcmp("destination-in", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_DEST_IN); } else if (0 == strcmp("destination-out", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_DEST_OUT); } else if (0 == strcmp("destination-over", *type)) { cairo_set_operator(ctx, CAIRO_OPERATOR_DEST_OVER); } else { cairo_set_operator(ctx, CAIRO_OPERATOR_OVER); } } /* * Get shadow offset x. */ Handle Context2d::GetShadowOffsetX(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); return Number::New(context->state->shadowOffsetX); } /* * Set shadow offset x. */ void Context2d::SetShadowOffsetX(Local prop, Local val, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); context->state->shadowOffsetX = val->NumberValue(); } /* * Get shadow offset y. */ Handle Context2d::GetShadowOffsetY(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); return Number::New(context->state->shadowOffsetY); } /* * Set shadow offset y. */ void Context2d::SetShadowOffsetY(Local prop, Local val, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); context->state->shadowOffsetY = val->NumberValue(); } /* * Get shadow blur. */ Handle Context2d::GetShadowBlur(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); return Number::New(context->state->shadowBlur); } /* * Set shadow blur. */ void Context2d::SetShadowBlur(Local prop, Local val, const AccessorInfo &info) { double n = val->NumberValue(); if (n >= 0) { Context2d *context = ObjectWrap::Unwrap(info.This()); context->state->shadowBlur = n; } } /* * Get miter limit. */ Handle Context2d::GetMiterLimit(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); return Number::New(cairo_get_miter_limit(context->getContext())); } /* * Set miter limit. */ void Context2d::SetMiterLimit(Local prop, Local val, const AccessorInfo &info) { double n = val->NumberValue(); if (n > 0) { Context2d *context = ObjectWrap::Unwrap(info.This()); cairo_set_miter_limit(context->getContext(), n); } } /* * Get line width. */ Handle Context2d::GetLineWidth(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); return Number::New(cairo_get_line_width(context->getContext())); } /* * Set line width. */ void Context2d::SetLineWidth(Local prop, Local val, const AccessorInfo &info) { double n = val->NumberValue(); if (n > 0) { Context2d *context = ObjectWrap::Unwrap(info.This()); cairo_set_line_width(context->getContext(), n); } } /* * Get line join. */ Handle Context2d::GetLineJoin(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); switch (cairo_get_line_join(context->getContext())) { case CAIRO_LINE_JOIN_BEVEL: return String::NewSymbol("bevel"); case CAIRO_LINE_JOIN_ROUND: return String::NewSymbol("round"); default: return String::NewSymbol("miter"); } } /* * Set line join. */ void Context2d::SetLineJoin(Local prop, Local val, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); cairo_t *ctx = context->getContext(); String::AsciiValue type(val->ToString()); if (0 == strcmp("round", *type)) { cairo_set_line_join(ctx, CAIRO_LINE_JOIN_ROUND); } else if (0 == strcmp("bevel", *type)) { cairo_set_line_join(ctx, CAIRO_LINE_JOIN_BEVEL); } else { cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER); } } /* * Get line cap. */ Handle Context2d::GetLineCap(Local prop, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); switch (cairo_get_line_cap(context->getContext())) { case CAIRO_LINE_CAP_ROUND: return String::NewSymbol("round"); case CAIRO_LINE_CAP_SQUARE: return String::NewSymbol("square"); default: return String::NewSymbol("butt"); } } /* * Set line cap. */ void Context2d::SetLineCap(Local prop, Local val, const AccessorInfo &info) { Context2d *context = ObjectWrap::Unwrap(info.This()); cairo_t *ctx = context->getContext(); String::AsciiValue type(val->ToString()); if (0 == strcmp("round", *type)) { cairo_set_line_cap(ctx, CAIRO_LINE_CAP_ROUND); } else if (0 == strcmp("square", *type)) { cairo_set_line_cap(ctx, CAIRO_LINE_CAP_SQUARE); } else { cairo_set_line_cap(ctx, CAIRO_LINE_CAP_BUTT); } } /* * Check if the given point is within the current path. */ Handle Context2d::IsPointInPath(const Arguments &args) { HandleScope scope; if (args[0]->IsNumber() && args[1]->IsNumber()) { Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); double x = args[0]->NumberValue() , y = args[1]->NumberValue(); return Boolean::New(cairo_in_fill(ctx, x, y) || cairo_in_stroke(ctx, x, y)); } return False(); } /* * Set fill pattern, used internally for fillStyle= */ Handle Context2d::SetFillPattern(const Arguments &args) { HandleScope scope; // TODO: HasInstance / error handling Context2d *context = ObjectWrap::Unwrap(args.This()); Gradient *grad = ObjectWrap::Unwrap(args[0]->ToObject()); context->state->fillPattern = grad->getPattern(); return Undefined(); } /* * Set stroke pattern, used internally for strokeStyle= */ Handle Context2d::SetStrokePattern(const Arguments &args) { HandleScope scope; // TODO: HasInstance / error handling Context2d *context = ObjectWrap::Unwrap(args.This()); Gradient *grad = ObjectWrap::Unwrap(args[0]->ToObject()); context->state->strokePattern = grad->getPattern(); return Undefined(); } /* * Set shadow RGBA, used internally for shadowColor= */ Handle Context2d::SetShadowRGBA(const Arguments &args) { HandleScope scope; RGBA_ARGS(0); Context2d *context = ObjectWrap::Unwrap(args.This()); RGBA(context->state->shadow,r,g,b,a); return Undefined(); } /* * Set fill RGBA, used internally for fillStyle= */ Handle Context2d::SetFillRGBA(const Arguments &args) { HandleScope scope; RGBA_ARGS(0); Context2d *context = ObjectWrap::Unwrap(args.This()); RGBA(context->state->fill,r,g,b,a); return Undefined(); } /* * Set stroke RGBA, used internally for strokeStyle= */ Handle Context2d::SetStrokeRGBA(const Arguments &args) { HandleScope scope; RGBA_ARGS(0); Context2d *context = ObjectWrap::Unwrap(args.This()); RGBA(context->state->stroke,r,g,b,a); return Undefined(); } /* * Bezier curve. */ Handle Context2d::BezierCurveTo(const Arguments &args) { HandleScope scope; if (!args[0]->IsNumber() ||!args[1]->IsNumber() ||!args[2]->IsNumber() ||!args[3]->IsNumber() ||!args[4]->IsNumber() ||!args[5]->IsNumber()) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_curve_to(context->getContext() , args[0]->NumberValue() , args[1]->NumberValue() , args[2]->NumberValue() , args[3]->NumberValue() , args[4]->NumberValue() , args[5]->NumberValue()); return Undefined(); } /* * Quadratic curve approximation from libsvg-cairo. */ Handle Context2d::QuadraticCurveTo(const Arguments &args) { HandleScope scope; if (!args[0]->IsNumber() ||!args[1]->IsNumber() ||!args[2]->IsNumber() ||!args[3]->IsNumber()) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); double x, y , x1 = args[0]->NumberValue() , y1 = args[1]->NumberValue() , x2 = args[2]->NumberValue() , y2 = args[3]->NumberValue(); cairo_get_current_point(ctx, &x, &y); cairo_curve_to(ctx , x + 2.0 / 3.0 * (x1 - x), y + 2.0 / 3.0 * (y1 - y) , x2 + 2.0 / 3.0 * (x1 - x2), y2 + 2.0 / 3.0 * (y1 - y2) , x2 , y2); return Undefined(); } /* * Save state. */ Handle Context2d::Save(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); context->save(); return Undefined(); } /* * Restore state. */ Handle Context2d::Restore(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); context->restore(); return Undefined(); } /* * Creates a new subpath. */ Handle Context2d::BeginPath(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_new_path(context->getContext()); return Undefined(); } /* * Marks the subpath as closed. */ Handle Context2d::ClosePath(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_close_path(context->getContext()); return Undefined(); } /* * Rotate transformation. */ Handle Context2d::Rotate(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_rotate(context->getContext() , args[0]->IsNumber() ? args[0]->NumberValue() : 0); return Undefined(); } /* * Modify the CTM. */ Handle Context2d::Transform(const Arguments &args) { HandleScope scope; cairo_matrix_t matrix; cairo_matrix_init(&matrix , args[0]->IsNumber() ? args[0]->NumberValue() : 0 , args[1]->IsNumber() ? args[1]->NumberValue() : 0 , args[2]->IsNumber() ? args[2]->NumberValue() : 0 , args[3]->IsNumber() ? args[3]->NumberValue() : 0 , args[4]->IsNumber() ? args[4]->NumberValue() : 0 , args[5]->IsNumber() ? args[5]->NumberValue() : 0); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_transform(context->getContext(), &matrix); return Undefined(); } /* * Reset the CTM, used internally by setTransform(). */ Handle Context2d::ResetTransform(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_identity_matrix(context->getContext()); return Undefined(); } /* * Translate transformation. */ Handle Context2d::Translate(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_translate(context->getContext() , args[0]->IsNumber() ? args[0]->NumberValue() : 0 , args[1]->IsNumber() ? args[1]->NumberValue() : 0); return Undefined(); } /* * Scale transformation. */ Handle Context2d::Scale(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_scale(context->getContext() , args[0]->IsNumber() ? args[0]->NumberValue() : 0 , args[1]->IsNumber() ? args[1]->NumberValue() : 0); return Undefined(); } /* * Use path as clipping region. */ Handle Context2d::Clip(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); cairo_clip_preserve(ctx); return Undefined(); } /* * Fill the path. */ Handle Context2d::Fill(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); SET_SOURCE(context->state->fill); cairo_fill_preserve(ctx); return Undefined(); } /* * Stroke the path. */ Handle Context2d::Stroke(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); SET_SOURCE(context->state->stroke); cairo_stroke_preserve(ctx); return Undefined(); } /* * Fill text at (x, y). */ Handle Context2d::FillText(const Arguments &args) { HandleScope scope; if (!args[0]->IsString() || !args[1]->IsNumber() || !args[2]->IsNumber()) return Undefined(); String::Utf8Value str(args[0]); double x = args[1]->NumberValue(); double y = args[2]->NumberValue(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); context->savePath(); context->setTextPath(*str, x, y); SET_SOURCE(context->state->fill); cairo_fill(ctx); context->restorePath(); return Undefined(); } /* * Stroke text at (x ,y). */ Handle Context2d::StrokeText(const Arguments &args) { HandleScope scope; if (!args[0]->IsString() || !args[1]->IsNumber() || !args[2]->IsNumber()) return Undefined(); String::Utf8Value str(args[0]); double x = args[1]->NumberValue(); double y = args[2]->NumberValue(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); context->savePath(); context->setTextPath(*str, x, y); SET_SOURCE(context->state->stroke); cairo_stroke(ctx); context->restorePath(); return Undefined(); } /* * Set text path for the given string at (x, y). */ void Context2d::setTextPath(const char *str, double x, double y) { // Text extents cairo_text_extents_t te; cairo_text_extents(_context, str, &te); // Alignment switch (state->textAlignment) { // center case 0: x -= te.width / 2 + te.x_bearing; break; // right case 1: x -= te.width + te.x_bearing; break; } // Baseline approx // TODO: switch (state->textBaseline) { case TEXT_BASELINE_TOP: case TEXT_BASELINE_HANGING: y += te.height; break; case TEXT_BASELINE_MIDDLE: y += te.height / 2; break; case TEXT_BASELINE_BOTTOM: y -= te.height / 2; break; } cairo_move_to(_context, x, y); cairo_text_path(_context, str); } /* * Adds a point to the current subpath. */ Handle Context2d::LineTo(const Arguments &args) { HandleScope scope; if (!args[0]->IsNumber()) return ThrowException(Exception::TypeError(String::New("x required"))); if (!args[1]->IsNumber()) return ThrowException(Exception::TypeError(String::New("y required"))); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_line_to(context->getContext() , args[0]->NumberValue() , args[1]->NumberValue()); return Undefined(); } /* * Creates a new subpath at the given point. */ Handle Context2d::MoveTo(const Arguments &args) { HandleScope scope; if (!args[0]->IsNumber()) return ThrowException(Exception::TypeError(String::New("x required"))); if (!args[1]->IsNumber()) return ThrowException(Exception::TypeError(String::New("y required"))); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_move_to(context->getContext() , args[0]->NumberValue() , args[1]->NumberValue()); return Undefined(); } /* * Set font: * - weight * - style * - size * - unit * - family */ Handle Context2d::SetFont(const Arguments &args) { HandleScope scope; // Ignore invalid args if (!args[0]->IsString() || !args[1]->IsString() || !args[2]->IsNumber() || !args[3]->IsString() || !args[4]->IsString()) return Undefined(); String::AsciiValue weight(args[0]); String::AsciiValue style(args[1]); double size = args[2]->NumberValue(); String::AsciiValue unit(args[3]); String::AsciiValue family(args[4]); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); // Size cairo_set_font_size(ctx, size); // Style cairo_font_slant_t s = CAIRO_FONT_SLANT_NORMAL; if (0 == strcmp("italic", *style)) { s = CAIRO_FONT_SLANT_ITALIC; } else if (0 == strcmp("oblique", *style)) { s = CAIRO_FONT_SLANT_OBLIQUE; } // Weight cairo_font_weight_t w = CAIRO_FONT_WEIGHT_NORMAL; if (0 == strcmp("bold", *weight)) { w = CAIRO_FONT_WEIGHT_BOLD; } cairo_select_font_face(ctx, *family, s, w); return Undefined(); } /* * Return the given text extents. */ Handle Context2d::MeasureText(const Arguments &args) { HandleScope scope; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); String::Utf8Value str(args[0]->ToString()); Local obj = Object::New(); cairo_text_extents_t te; cairo_text_extents(ctx, *str, &te); obj->Set(String::New("width"), Number::New(te.width)); return scope.Close(obj); } /* * Set text baseline. */ Handle Context2d::SetTextBaseline(const Arguments &args) { HandleScope scope; if (!args[0]->IsInt32()) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); context->state->textBaseline = args[0]->Int32Value(); return Undefined(); } /* * Set text alignment. -1 0 1 */ Handle Context2d::SetTextAlignment(const Arguments &args) { HandleScope scope; if (!args[0]->IsInt32()) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); context->state->textAlignment = args[0]->Int32Value(); return Undefined(); } /* * Fill the rectangle defined by x, y, width and height. */ Handle Context2d::FillRect(const Arguments &args) { HandleScope scope; RECT_ARGS; if (0 == width || 0 == height) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); cairo_new_path(ctx); if (!context->hasShadow()) { cairo_rectangle(ctx, x, y, width, height); SET_SOURCE(context->state->fill); cairo_fill(ctx); return Undefined(); } context->shadowStart(); cairo_rectangle(ctx, x, y, width, height); cairo_fill(ctx); context->shadowApply(); cairo_rectangle(ctx, x, y, width, height); cairo_fill(ctx); return Undefined(); } /* * Stroke the rectangle defined by x, y, width and height. */ Handle Context2d::StrokeRect(const Arguments &args) { HandleScope scope; RECT_ARGS; if (0 == width && 0 == height) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); cairo_new_path(ctx); if (!context->hasShadow()) { cairo_rectangle(ctx, x, y, width, height); SET_SOURCE(context->state->stroke); cairo_stroke(ctx); return Undefined(); } context->shadowStart(); cairo_rectangle(ctx, x, y, width, height); cairo_stroke(ctx); context->shadowApply(); cairo_rectangle(ctx, x, y, width, height); cairo_stroke(ctx); return Undefined(); } /* * Clears all pixels defined by x, y, width and height. */ Handle Context2d::ClearRect(const Arguments &args) { HandleScope scope; RECT_ARGS; if (0 == width || 0 == height) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); cairo_save(ctx); cairo_rectangle(ctx, x, y, width, height); cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR); cairo_fill(ctx); cairo_restore(ctx); return Undefined(); } /* * Adds a rectangle subpath. */ Handle Context2d::Rect(const Arguments &args) { HandleScope scope; RECT_ARGS; Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_rectangle(context->getContext(), x, y, width, height); return Undefined(); } /* * Adds an arc at x, y with the given radis and start/end angles. */ Handle Context2d::Arc(const Arguments &args) { HandleScope scope; if (!args[0]->IsNumber() ||!args[1]->IsNumber() ||!args[2]->IsNumber() ||!args[3]->IsNumber() ||!args[4]->IsNumber()) return Undefined(); bool anticlockwise = args[5]->BooleanValue(); Context2d *context = ObjectWrap::Unwrap(args.This()); cairo_t *ctx = context->getContext(); if (anticlockwise && M_PI * 2 != args[4]->NumberValue()) { cairo_arc_negative(ctx , args[0]->NumberValue() , args[1]->NumberValue() , args[2]->NumberValue() , args[3]->NumberValue() , args[4]->NumberValue()); } else { cairo_arc(ctx , args[0]->NumberValue() , args[1]->NumberValue() , args[2]->NumberValue() , args[3]->NumberValue() , args[4]->NumberValue()); } return Undefined(); } /* * Set source RGBA. */ void Context2d::setSourceRGBA(rgba_t color) { cairo_set_source_rgba( _context , color.r , color.g , color.b , color.a * state->globalAlpha); } /* * Start shadow context. */ void Context2d::shadowStart() { cairo_save(_context); cairo_translate( _context , state->shadowOffsetX , state->shadowOffsetY); cairo_push_group(_context); setSourceRGBA(state->shadow); } /* * Apply shadow. */ void Context2d::shadowApply() { if (state->shadowBlur) { Canvas::blur(cairo_get_group_target(_context), state->shadowBlur); } cairo_pop_group_to_source(_context); cairo_paint(_context); cairo_restore(_context); } /* * Check if the context has a drawable shadow. */ bool Context2d::hasShadow() { return state->shadow.a && (state->shadowBlur || state->shadowOffsetX || state->shadowOffsetX); }