diff --git a/lib/colors.js b/lib/colors.js deleted file mode 100644 index 5ced002..0000000 --- a/lib/colors.js +++ /dev/null @@ -1,153 +0,0 @@ - -/*! - * Canvas - colors - * Copyright (c) 2010 LearnBoost - * MIT Licensed - */ - -module.exports = { - transparent: 'rgba(255,255,255,0)' - , aliceblue: '#f0f8ff' - , antiquewhite: '#faebd7' - , aqua: '#00ffff' - , aquamarine: '#7fffd4' - , azure: '#f0ffff' - , beige: '#f5f5dc' - , bisque: '#ffe4c4' - , black: '#000000' - , blanchedalmond: '#ffebcd' - , blue: '#0000ff' - , blueviolet: '#8a2be2' - , brown: '#a52a2a' - , burlywood: '#deb887' - , cadetblue: '#5f9ea0' - , chartreuse: '#7fff00' - , chocolate: '#d2691e' - , coral: '#ff7f50' - , cornflowerblue: '#6495ed' - , cornsilk: '#fff8dc' - , crimson: '#dc143c' - , cyan: '#00ffff' - , darkblue: '#00008b' - , darkcyan: '#008b8b' - , darkgoldenrod: '#b8860b' - , darkgray: '#a9a9a9' - , darkgreen: '#006400' - , darkkhaki: '#bdb76b' - , darkmagenta: '#8b008b' - , darkolivegreen: '#556b2f' - , darkorange: '#ff8c00' - , darkorchid: '#9932cc' - , darkred: '#8b0000' - , darksalmon: '#e9967a' - , darkseagreen: '#8fbc8f' - , darkslateblue: '#483d8b' - , darkslategray: '#2f4f4f' - , darkturquoise: '#00ced1' - , darkviolet: '#9400d3' - , deeppink: '#ff1493' - , deepskyblue: '#00bfff' - , dimgray: '#696969' - , dodgerblue: '#1e90ff' - , feldspar: '#d19275' - , firebrick: '#b22222' - , floralwhite: '#fffaf0' - , forestgreen: '#228b22' - , fuchsia: '#ff00ff' - , gainsboro: '#dcdcdc' - , ghostwhite: '#f8f8ff' - , gold: '#ffd700' - , goldenrod: '#daa520' - , gray: '#808080' - , green: '#008000' - , greenyellow: '#adff2f' - , honeydew: '#f0fff0' - , hotpink: '#ff69b4' - , indianred : '#cd5c5c' - , indigo : '#4b0082' - , ivory: '#fffff0' - , khaki: '#f0e68c' - , lavender: '#e6e6fa' - , lavenderblush: '#fff0f5' - , lawngreen: '#7cfc00' - , lemonchiffon: '#fffacd' - , lightblue: '#add8e6' - , lightcoral: '#f08080' - , lightcyan: '#e0ffff' - , lightgoldenrodyellow: '#fafad2' - , lightgrey: '#d3d3d3' - , lightgreen: '#90ee90' - , lightpink: '#ffb6c1' - , lightsalmon: '#ffa07a' - , lightseagreen: '#20b2aa' - , lightskyblue: '#87cefa' - , lightslateblue: '#8470ff' - , lightslategray: '#778899' - , lightsteelblue: '#b0c4de' - , lightyellow: '#ffffe0' - , lime: '#00ff00' - , limegreen: '#32cd32' - , linen: '#faf0e6' - , magenta: '#ff00ff' - , maroon: '#800000' - , mediumaquamarine: '#66cdaa' - , mediumblue: '#0000cd' - , mediumorchid: '#ba55d3' - , mediumpurple: '#9370d8' - , mediumseagreen: '#3cb371' - , mediumslateblue: '#7b68ee' - , mediumspringgreen: '#00fa9a' - , mediumturquoise: '#48d1cc' - , mediumvioletred: '#c71585' - , midnightblue: '#191970' - , mintcream: '#f5fffa' - , mistyrose: '#ffe4e1' - , moccasin: '#ffe4b5' - , navajowhite: '#ffdead' - , navy: '#000080' - , oldlace: '#fdf5e6' - , olive: '#808000' - , olivedrab: '#6b8e23' - , orange: '#ffa500' - , orangered: '#ff4500' - , orchid: '#da70d6' - , palegoldenrod: '#eee8aa' - , palegreen: '#98fb98' - , paleturquoise: '#afeeee' - , palevioletred: '#d87093' - , papayawhip: '#ffefd5' - , peachpuff: '#ffdab9' - , peru: '#cd853f' - , pink: '#ffc0cb' - , plum: '#dda0dd' - , powderblue: '#b0e0e6' - , purple: '#800080' - , red: '#ff0000' - , rosybrown: '#bc8f8f' - , royalblue: '#4169e1' - , saddlebrown: '#8b4513' - , salmon: '#fa8072' - , sandybrown: '#f4a460' - , seagreen: '#2e8b57' - , seashell: '#fff5ee' - , sienna: '#a0522d' - , silver: '#c0c0c0' - , skyblue: '#87ceeb' - , slateblue: '#6a5acd' - , slategray: '#708090' - , snow: '#fffafa' - , springgreen: '#00ff7f' - , steelblue: '#4682b4' - , tan: '#d2b48c' - , teal: '#008080' - , thistle: '#d8bfd8' - , tomato: '#ff6347' - , turquoise: '#40e0d0' - , violet: '#ee82ee' - , violetred: '#d02090' - , wheat: '#f5deb3' - , white: '#ffffff' - , whitesmoke: '#f5f5f5' - , yellow: '#ffff00' - , yellowgreen: '#9acd32' -}; \ No newline at end of file diff --git a/lib/context2d.js b/lib/context2d.js index f637416..43fc6bf 100644 --- a/lib/context2d.js +++ b/lib/context2d.js @@ -13,8 +13,7 @@ var canvas = require('../build/default/canvas') , Context2d = canvas.CanvasRenderingContext2d , CanvasGradient = canvas.CanvasGradient , ImageData = canvas.ImageData - , PixelArray = canvas.CanvasPixelArray - , colors = require('./colors'); + , PixelArray = canvas.CanvasPixelArray; /** * Export `Context2d` as the module. @@ -54,34 +53,6 @@ var fontre = new RegExp('^ *' + '((?:' + string + ')( *, *(?:' + string + '))*)' ); -/** - * Return a function used to normalize an RGBA color `prop` - * or the pattern previously set. - * - * @param {String} prop - * @return {Function} - * @api private - */ - -function getter(prop) { - return function(){ - var val = this[prop]; - if (val instanceof CanvasGradient) return val; - if (1 == val[3]) { - return '#' - + val[0].toString(16) - + val[1].toString(16) - + val[2].toString(16); - } else { - return 'rgba(' - + val[0] + ', ' - + val[1] + ', ' - + val[2] + ', ' - + val[3] + ')'; - } - } -} - /** * Parse font `str`. * @@ -107,162 +78,6 @@ var parseFont = exports.parseFont = function(str){ return font; }; -/** - * Parse the given color `str`. - * - * Current supports: - * - * - #nnn - * - #nnnnnn - * - rgb(r,g,b) - * - rgba(r,g,b,a) - * - color - * - * Examples - * - * - #fff - * - #FFF - * - #FFFFFF - * - rgb(255,255,5) - * - rgba(255,255,5,.8) - * - rgba(255,255,5,0.8) - * - hsl(300,100%,50%) - * - hsla(300,100%,50%,0.5) - * - white - * - red - * - * @param {String} str - * @return {Array} - * @api private - */ - -var parseColor = exports.parseColor = function(str){ - if (cache[str]) return cache[str]; - str = colors[str] || String(str); - // RGBA - if (0 == str.indexOf('rgba')) { - var captures = /rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.\d+|\.\d+|\d+) *\)/.exec(str); - if (!captures) return; - return cache[str] = [ - parseInt(captures[1], 10) - , parseInt(captures[2], 10) - , parseInt(captures[3], 10) - , parseFloat(captures[4], 10) - ]; - // RGB - } else if (0 == str.indexOf('rgb')) { - var captures = /rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *\)/.exec(str); - if (!captures) return; - return cache[str] = [ - parseInt(captures[1], 10) - , parseInt(captures[2], 10) - , parseInt(captures[3], 10) - , 1 - ]; - // #RRGGBB - } else if ('#' == str[0] && str.length > 4) { - var captures = /#([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})/.exec(str); - if (!captures) return; - return cache[str] = [ - parseInt(captures[1], 16) - , parseInt(captures[2], 16) - , parseInt(captures[3], 16) - , 1 - ]; - // #RGB - } else if ('#' == str[0]) { - var captures = /#([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])/.exec(str); - if (!captures) return; - return cache[str] = [ - parseInt(captures[1] + captures[1], 16) - , parseInt(captures[2] + captures[2], 16) - , parseInt(captures[3] + captures[3], 16) - , 1 - ]; - // HSLA - } else if (0 == str.indexOf('hsla')) { - var captures = /hsla\((\d+\.\d+|\.\d+|\d+) *, *(\d+\.\d+|\.\d+|\d+)%? *, *(\d+\.\d+|\.\d+|\d+)%? *, *(\d+\.\d+|\.\d+|\d+) *\)/.exec(str); - if (!captures) return; - return cache[str] = hsl2rgb( - (((parseFloat(captures[1], 10) % 360) + 360) % 360) / 360 - , parseFloat(captures[2], 10) / 100.0 - , parseFloat(captures[3], 10) / 100.0 - , parseFloat(captures[4], 10) - ); - // HSL - } else if (0 == str.indexOf('hsl')) { - var captures = /hsl\((\d+\.\d+|\.\d+|\d+) *, *(\d+\.\d+|\.\d+|\d+)%? *, *(\d+\.\d+|\.\d+|\d+)%? *\)/.exec(str); - if (!captures) return; - return cache[str] = hsl2rgb( - (((parseFloat(captures[1], 10) % 360) + 360) % 360) / 360 - , parseFloat(captures[2], 10) / 100.0 - , parseFloat(captures[3], 10) / 100.0 - , 1 - ); - } -}; - -/** - * HSLA -> RGB. - * - * @param {Number} h - * @param {Number} s - * @param {Number} l - * @param {Number} a - * @return {Array} - * @api private - */ - -function hsl2rgb(h,s,l,a) { - var m2 = l <= 0.5 ? l*(s+1) : l+s-l*s - , m1 = l*2-m2; - return [ - Math.round(hue2rgb(m1, m2, h+1/3) * 255) - , Math.round(hue2rgb(m1, m2, h) * 255) - , Math.round(hue2rgb(m1, m2, h-1/3) * 255) - , a - ]; -} - -/** - * Hue to RGB - * - * @param {Number} m1 - * @param {Number} m2 - * @param {Number} h - * @return {Number} - * @api private - */ - -function hue2rgb(m1,m2,h) { - if (h<0) h = h+1; - if (h>1) h = h-1; - if (h*6<1) return m1+(m2-m1)*h*6; - if (h*2<1) return m2; - if (h*3<2) return m1+(m2-m1)*(2/3-h)*6; - return m1; -} - -/** - * Add `color` stop at the given `offset`. - * - * @param {Number} offset - * @param {String} color - * @api public - */ - -CanvasGradient.prototype.addColorStop = function(offset, color){ - var rgba; - if (rgba = parseColor(color)) { - this.addColorStopRGBA( - offset - , rgba[0] - , rgba[1] - , rgba[2] - , rgba[3]); - } -}; - /** * Create a linear gradient at the given point `(x0, y0)` and `(x1, y1)`. * @@ -311,7 +126,6 @@ Context2d.prototype.setTransform = function(){ /** * Set the fill style with the given css color string. * - * @see exports.parseColor() * @api public */ @@ -320,30 +134,24 @@ Context2d.prototype.__defineSetter__('fillStyle', function(val){ this.lastFillStyle = val; this.setFillPattern(val); } else if ('string' == typeof val) { - var rgba; - if (rgba = parseColor(val)) { - this.lastFillStyle = rgba; - this.setFillRGBA( - rgba[0] - , rgba[1] - , rgba[2] - , rgba[3]); - } + this.setFillColor(val); } }); /** - * Get the current fill style string. + * Get previous fill style. * + * @return {CanvasGradient|String} * @api public */ -Context2d.prototype.__defineGetter__('fillStyle', getter('lastFillStyle')); +Context2d.prototype.__defineGetter__('fillStyle', function(){ + return this.lastFillStyle || this.fillColor; +}); /** * Set the stroke style with the given css color string. * - * @see exports.parseColor() * @api public */ @@ -352,54 +160,21 @@ Context2d.prototype.__defineSetter__('strokeStyle', function(val){ this.lastStrokeStyle = val; this.setStrokePattern(val); } else if ('string' == typeof val) { - var rgba; - if (rgba = parseColor(val)) { - this.lastStrokeStyle = rgba; - this.setStrokeRGBA( - rgba[0] - , rgba[1] - , rgba[2] - , rgba[3]); - } + this.setStrokeColor(val); } }); /** - * Get the current stroke style string. + * Get previous stroke style. * + * @return {CanvasGradient|String} * @api public */ -Context2d.prototype.__defineGetter__('strokeStyle', getter('lastStrokeStyle')); - -/** - * Set the shadow color with the given css color string. - * - * @see exports.parseColor() - * @api public - */ - -Context2d.prototype.__defineSetter__('shadowColor', function(val){ - if ('string' == typeof val) { - var rgba; - if (rgba = parseColor(val)) { - this.lastShadowColor = rgba; - this.setShadowRGBA( - rgba[0] - , rgba[1] - , rgba[2] - , rgba[3]); - } - } +Context2d.prototype.__defineGetter__('strokeStyle', function(){ + return this.lastStrokeStyle || this.strokeColor; }); -/** - * Get the current shadow color string. - * - * @api public - */ - -Context2d.prototype.__defineGetter__('shadowColor', getter('lastShadowColor')); /** * Set font. diff --git a/src/Canvas.h b/src/Canvas.h index 54797a3..a2c03b1 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -25,24 +25,6 @@ using namespace node; #define CANVAS_MAX_STATES 64 #endif -/* - * RGBA arg assertions. - */ - -#define RGBA_ARGS(N) \ - if (!args[N]->IsNumber()) \ - return ThrowException(Exception::TypeError(String::New("r required"))); \ - if (!args[N+1]->IsNumber()) \ - return ThrowException(Exception::TypeError(String::New("g required"))); \ - if (!args[N+2]->IsNumber()) \ - return ThrowException(Exception::TypeError(String::New("b required"))); \ - if (!args[N+3]->IsNumber()) \ - return ThrowException(Exception::TypeError(String::New("alpha required"))); \ - double r = args[N]->Int32Value(); \ - double g = args[N+1]->Int32Value(); \ - double b = args[N+2]->Int32Value(); \ - double a = args[N+3]->NumberValue(); - /* * Canvas. */ diff --git a/src/CanvasGradient.cc b/src/CanvasGradient.cc index cf99585..32d26ad 100644 --- a/src/CanvasGradient.cc +++ b/src/CanvasGradient.cc @@ -5,6 +5,7 @@ // Copyright (c) 2010 LearnBoost // +#include "color.h" #include "Canvas.h" #include "CanvasGradient.h" @@ -24,7 +25,7 @@ Gradient::Initialize(Handle target) { constructor->SetClassName(String::NewSymbol("CanvasGradient")); // Prototype - NODE_SET_PROTOTYPE_METHOD(constructor, "addColorStopRGBA", AddColorStopRGBA); + NODE_SET_PROTOTYPE_METHOD(constructor, "addColorStop", AddColorStop); target->Set(String::NewSymbol("CanvasGradient"), constructor->GetFunction()); } @@ -64,23 +65,33 @@ Gradient::New(const Arguments &args) { } /* - * Add RGBA color stop. + * Add color stop. */ Handle -Gradient::AddColorStopRGBA(const Arguments &args) { +Gradient::AddColorStop(const Arguments &args) { HandleScope scope; if (!args[0]->IsNumber()) return ThrowException(Exception::TypeError(String::New("offset required"))); - RGBA_ARGS(1); + if (!args[1]->IsString()) + return ThrowException(Exception::TypeError(String::New("color string required"))); + Gradient *grad = ObjectWrap::Unwrap(args.This()); - cairo_pattern_add_color_stop_rgba( - grad->pattern() - , args[0]->NumberValue() - , r / 255 * 1 - , g / 255 * 1 - , b / 255 * 1 - , a); + short ok; + String::AsciiValue str(args[1]); + uint32_t rgba = rgba_from_string(*str, &ok); + + if (ok) { + rgba_t color = rgba_create(rgba); + cairo_pattern_add_color_stop_rgba( + grad->pattern() + , args[0]->NumberValue() + , color.r + , color.g + , color.b + , color.a); + } + return Undefined(); } diff --git a/src/CanvasGradient.h b/src/CanvasGradient.h index adcf2bc..db17096 100644 --- a/src/CanvasGradient.h +++ b/src/CanvasGradient.h @@ -15,7 +15,7 @@ class Gradient: public node::ObjectWrap { static Persistent constructor; static void Initialize(Handle target); static Handle New(const Arguments &args); - static Handle AddColorStopRGBA(const Arguments &args); + static Handle AddColorStop(const Arguments &args); Gradient(double x0, double y0, double x1, double y1); Gradient(double x0, double y0, double r0, double x1, double y1, double r1); inline cairo_pattern_t *pattern(){ return _pattern; } diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 5ac2756..96850fb 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -17,16 +17,6 @@ Persistent Context2d::constructor; -/* - * Set RGBA. - */ - -#define RGBA(_,R,G,B,A) \ - _.r = R / 255 * 1; \ - _.g = G / 255 * 1; \ - _.b = B / 255 * 1; \ - _.a = A; \ - /* * Rectangle arg assertions. */ @@ -100,13 +90,15 @@ Context2d::Initialize(Handle target) { NODE_SET_PROTOTYPE_METHOD(constructor, "arc", Arc); NODE_SET_PROTOTYPE_METHOD(constructor, "arcTo", ArcTo); NODE_SET_PROTOTYPE_METHOD(constructor, "setFont", SetFont); - NODE_SET_PROTOTYPE_METHOD(constructor, "setShadowRGBA", SetShadowRGBA); - NODE_SET_PROTOTYPE_METHOD(constructor, "setFillRGBA", SetFillRGBA); - NODE_SET_PROTOTYPE_METHOD(constructor, "setStrokeRGBA", SetStrokeRGBA); + NODE_SET_PROTOTYPE_METHOD(constructor, "setFillColor", SetFillColor); + NODE_SET_PROTOTYPE_METHOD(constructor, "setStrokeColor", SetStrokeColor); NODE_SET_PROTOTYPE_METHOD(constructor, "setFillPattern", SetFillPattern); NODE_SET_PROTOTYPE_METHOD(constructor, "setStrokePattern", SetStrokePattern); proto->SetAccessor(String::NewSymbol("globalCompositeOperation"), GetGlobalCompositeOperation, SetGlobalCompositeOperation); proto->SetAccessor(String::NewSymbol("globalAlpha"), GetGlobalAlpha, SetGlobalAlpha); + proto->SetAccessor(String::NewSymbol("shadowColor"), GetShadowColor, SetShadowColor); + proto->SetAccessor(String::NewSymbol("fillColor"), GetFillColor); + proto->SetAccessor(String::NewSymbol("strokeColor"), GetStrokeColor); proto->SetAccessor(String::NewSymbol("miterLimit"), GetMiterLimit, SetMiterLimit); proto->SetAccessor(String::NewSymbol("lineWidth"), GetLineWidth, SetLineWidth); proto->SetAccessor(String::NewSymbol("lineCap"), GetLineCap, SetLineCap); @@ -132,9 +124,11 @@ Context2d::Context2d(Canvas *canvas) { 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); + rgba_t transparent = { 0,0,0,1 }; + rgba_t transparent_black = { 0,0,0,0 }; + state->fill = transparent; + state->stroke = transparent; + state->shadow = transparent_black; } /* @@ -997,46 +991,92 @@ Context2d::SetStrokePattern(const Arguments &args) { } /* - * Set shadow RGBA, used internally for shadowColor= + * Set shadow color. + */ + +void +Context2d::SetShadowColor(Local prop, Local val, const AccessorInfo &info) { + short ok; + String::AsciiValue str(val->ToString()); + uint32_t rgba = rgba_from_string(*str, &ok); + if (ok) { + Context2d *context = ObjectWrap::Unwrap(info.This()); + context->state->shadow = rgba_create(rgba); + } +} + +/* + * Get shadow color. */ 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(); +Context2d::GetShadowColor(Local prop, const AccessorInfo &info) { + char buf[64]; + Context2d *context = ObjectWrap::Unwrap(info.This()); + rgba_to_string(context->state->shadow, buf); + return String::New(buf); } /* - * Set fill RGBA, used internally for fillStyle= + * Set fill color, used internally for fillStyle= */ Handle -Context2d::SetFillRGBA(const Arguments &args) { +Context2d::SetFillColor(const Arguments &args) { HandleScope scope; - RGBA_ARGS(0); + short ok; + if (!args[0]->IsString()) return Undefined(); + String::AsciiValue str(args[0]); + uint32_t rgba = rgba_from_string(*str, &ok); + if (!ok) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); context->state->fillPattern = NULL; - RGBA(context->state->fill,r,g,b,a); + context->state->fill = rgba_create(rgba); return Undefined(); } /* - * Set stroke RGBA, used internally for strokeStyle= + * Get fill color. + */ + +Handle +Context2d::GetFillColor(Local prop, const AccessorInfo &info) { + char buf[64]; + Context2d *context = ObjectWrap::Unwrap(info.This()); + rgba_to_string(context->state->fill, buf); + return String::New(buf); +} + +/* + * Set stroke color, used internally for strokeStyle= */ Handle -Context2d::SetStrokeRGBA(const Arguments &args) { +Context2d::SetStrokeColor(const Arguments &args) { HandleScope scope; - RGBA_ARGS(0); + short ok; + if (!args[0]->IsString()) return Undefined(); + String::AsciiValue str(args[0]); + uint32_t rgba = rgba_from_string(*str, &ok); + if (!ok) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); context->state->strokePattern = NULL; - RGBA(context->state->stroke,r,g,b,a); + context->state->stroke = rgba_create(rgba); return Undefined(); } +/* + * Get stroke color. + */ + +Handle +Context2d::GetStrokeColor(Local prop, const AccessorInfo &info) { + char buf[64]; + Context2d *context = ObjectWrap::Unwrap(info.This()); + rgba_to_string(context->state->stroke, buf); + return String::New(buf); +} + /* * Bezier curve. */ diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index b11409a..32e88d2 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -8,17 +8,10 @@ #ifndef __NODE_CONTEXT2D_H__ #define __NODE_CONTEXT2D_H__ +#include "color.h" #include "Canvas.h" #include "CanvasGradient.h" -/* - * RGBA struct used to retain values of fill/stroke. - */ - -typedef struct { - double r, g, b, a; -} rgba_t; - /* * State struct. * @@ -67,9 +60,8 @@ class Context2d: public node::ObjectWrap { static Handle FillText(const Arguments &args); static Handle StrokeText(const Arguments &args); static Handle SetFont(const Arguments &args); - static Handle SetFillRGBA(const Arguments &args); - static Handle SetStrokeRGBA(const Arguments &args); - static Handle SetShadowRGBA(const Arguments &args); + static Handle SetFillColor(const Arguments &args); + static Handle SetStrokeColor(const Arguments &args); static Handle SetFillPattern(const Arguments &args); static Handle SetStrokePattern(const Arguments &args); static Handle SetTextBaseline(const Arguments &args); @@ -87,6 +79,9 @@ class Context2d: public node::ObjectWrap { static Handle ArcTo(const Arguments &args); static Handle GetGlobalCompositeOperation(Local prop, const AccessorInfo &info); static Handle GetGlobalAlpha(Local prop, const AccessorInfo &info); + static Handle GetShadowColor(Local prop, const AccessorInfo &info); + static Handle GetFillColor(Local prop, const AccessorInfo &info); + static Handle GetStrokeColor(Local prop, const AccessorInfo &info); static Handle GetMiterLimit(Local prop, const AccessorInfo &info); static Handle GetLineCap(Local prop, const AccessorInfo &info); static Handle GetLineJoin(Local prop, const AccessorInfo &info); @@ -97,6 +92,7 @@ class Context2d: public node::ObjectWrap { static Handle GetAntiAlias(Local prop, const AccessorInfo &info); static void SetGlobalCompositeOperation(Local prop, Local val, const AccessorInfo &info); static void SetGlobalAlpha(Local prop, Local val, const AccessorInfo &info); + static void SetShadowColor(Local prop, Local val, const AccessorInfo &info); static void SetMiterLimit(Local prop, Local val, const AccessorInfo &info); static void SetLineCap(Local prop, Local val, const AccessorInfo &info); static void SetLineJoin(Local prop, Local val, const AccessorInfo &info); diff --git a/src/color.cc b/src/color.cc new file mode 100644 index 0000000..b5d81d5 --- /dev/null +++ b/src/color.cc @@ -0,0 +1,423 @@ + +// +// color.cc +// +// Copyright (c) 2010 LearnBoost +// + +#include "color.h" +#include + +/* + * Consume whitespace. + */ + +#define WHITESPACE \ + while (' ' == *str) ++str; + +/* + * Parse color channel value + */ + +#define CHANNEL(NAME) \ + if (*str >= '0' && *str <= '9') { \ + do { \ + NAME *= 10; \ + NAME += *str++ - '0'; \ + } while (*str >= '0' && *str <= '9'); \ + } else { \ + return 0; \ + } \ + while (' ' == *str || ',' == *str) str++; + +/* + * Named colors. + */ + +static struct named_color { + const char *name; + uint32_t val; +} named_colors[] = { + {"transparent", 0xffffff00} + , {"aliceblue", 0xf0f8ffff} + , {"antiquewhite", 0xfaebd7ff} + , {"aqua", 0x00ffffff} + , {"aquamarine", 0x7fffd4ff} + , {"azure", 0xf0ffffff} + , {"beige", 0xf5f5dcff} + , {"bisque", 0xffe4c4ff} + , {"black", 0x000000ff} + , {"blanchedalmond", 0xffebcdff} + , {"blue", 0x0000ffff} + , {"blueviolet", 0x8a2be2ff} + , {"brown", 0xa52a2aff} + , {"burlywood", 0xdeb887ff} + , {"cadetblue", 0x5f9ea0ff} + , {"chartreuse", 0x7fff00ff} + , {"chocolate", 0xd2691eff} + , {"coral", 0xff7f50ff} + , {"cornflowerblue", 0x6495edff} + , {"cornsilk", 0xfff8dcff} + , {"crimson", 0xdc143cff} + , {"cyan", 0x00ffffff} + , {"darkblue", 0x00008bff} + , {"darkcyan", 0x008b8bff} + , {"darkgoldenrod", 0xb8860bff} + , {"darkgray", 0xa9a9a9ff} + , {"darkgreen", 0x006400ff} + , {"darkkhaki", 0xbdb76bff} + , {"darkmagenta", 0x8b008bff} + , {"darkolivegreen", 0x556b2fff} + , {"darkorange", 0xff8c00ff} + , {"darkorchid", 0x9932ccff} + , {"darkred", 0x8b0000ff} + , {"darksalmon", 0xe9967aff} + , {"darkseagreen", 0x8fbc8fff} + , {"darkslateblue", 0x483d8bff} + , {"darkslategray", 0x2f4f4fff} + , {"darkturquoise", 0x00ced1ff} + , {"darkviolet", 0x9400d3ff} + , {"deeppink", 0xff1493ff} + , {"deepskyblue", 0x00bfffff} + , {"dimgray", 0x696969ff} + , {"dodgerblue", 0x1e90ffff} + , {"feldspar", 0xd19275ff} + , {"firebrick", 0xb22222ff} + , {"floralwhite", 0xfffaf0ff} + , {"forestgreen", 0x228b22ff} + , {"fuchsia", 0xff00ffff} + , {"gainsboro", 0xdcdcdcff} + , {"ghostwhite", 0xf8f8ffff} + , {"gold", 0xffd700ff} + , {"goldenrod", 0xdaa520ff} + , {"gray", 0x808080ff} + , {"green", 0x008000ff} + , {"greenyellow", 0xadff2fff} + , {"honeydew", 0xf0fff0ff} + , {"hotpink", 0xff69b4ff} + , {"indianred ", 0xcd5c5cff} + , {"indigo ", 0x4b0082ff} + , {"ivory", 0xfffff0ff} + , {"khaki", 0xf0e68cff} + , {"lavender", 0xe6e6faff} + , {"lavenderblush", 0xfff0f5ff} + , {"lawngreen", 0x7cfc00ff} + , {"lemonchiffon", 0xfffacdff} + , {"lightblue", 0xadd8e6ff} + , {"lightcoral", 0xf08080ff} + , {"lightcyan", 0xe0ffffff} + , {"lightgoldenrodyellow", 0xfafa} + , {"lightgrey", 0xd3d3d3ff} + , {"lightgreen", 0x90ee90ff} + , {"lightpink", 0xffb6c1ff} + , {"lightsalmon", 0xffa07aff} + , {"lightseagreen", 0x20b2aaff} + , {"lightskyblue", 0x87cefaff} + , {"lightslateblue", 0x8470ffff} + , {"lightslategray", 0x778899ff} + , {"lightsteelblue", 0xb0c4deff} + , {"lightyellow", 0xffffe0ff} + , {"lime", 0x00ff00ff} + , {"limegreen", 0x32cd32ff} + , {"linen", 0xfaf0e6ff} + , {"magenta", 0xff00ffff} + , {"maroon", 0x800000ff} + , {"mediumaquamarine", 0x66cdaaff} + , {"mediumblue", 0x0000cdff} + , {"mediumorchid", 0xba55d3ff} + , {"mediumpurple", 0x9370d8ff} + , {"mediumseagreen", 0x3cb371ff} + , {"mediumslateblue", 0x7b68eeff} + , {"mediumspringgreen", 0x00fa9af} + , {"mediumturquoise", 0x48d1ccff} + , {"mediumvioletred", 0xc71585ff} + , {"midnightblue", 0x191970ff} + , {"mintcream", 0xf5fffaff} + , {"mistyrose", 0xffe4e1ff} + , {"moccasin", 0xffe4b5ff} + , {"navajowhite", 0xffdeadff} + , {"navy", 0x000080ff} + , {"oldlace", 0xfdf5e6ff} + , {"olive", 0x808000ff} + , {"olivedrab", 0x6b8e23ff} + , {"orange", 0xffa500ff} + , {"orangered", 0xff4500ff} + , {"orchid", 0xda70d6ff} + , {"palegoldenrod", 0xeee8aaff} + , {"palegreen", 0x98fb98ff} + , {"paleturquoise", 0xafeeeeff} + , {"palevioletred", 0xd87093ff} + , {"papayawhip", 0xffefd5ff} + , {"peachpuff", 0xffdab9ff} + , {"peru", 0xcd853fff} + , {"pink", 0xffc0cbff} + , {"plum", 0xdda0ddff} + , {"powderblue", 0xb0e0e6ff} + , {"purple", 0x800080ff} + , {"red", 0xff0000ff} + , {"rosybrown", 0xbc8f8fff} + , {"royalblue", 0x4169e1ff} + , {"saddlebrown", 0x8b4513ff} + , {"salmon", 0xfa8072ff} + , {"sandybrown", 0xf4a460ff} + , {"seagreen", 0x2e8b57ff} + , {"seashell", 0xfff5eeff} + , {"sienna", 0xa0522dff} + , {"silver", 0xc0c0c0ff} + , {"skyblue", 0x87ceebff} + , {"slateblue", 0x6a5acdff} + , {"slategray", 0x708090ff} + , {"snow", 0xfffafaff} + , {"springgreen", 0x00ff7fff} + , {"steelblue", 0x4682b4ff} + , {"tan", 0xd2b48cff} + , {"teal", 0x008080ff} + , {"thistle", 0xd8bfd8ff} + , {"tomato", 0xff6347ff} + , {"turquoise", 0x40e0d0ff} + , {"violet", 0xee82eeff} + , {"violetred", 0xd02090ff} + , {"wheat", 0xf5deb3ff} + , {"white", 0xffffffff} + , {"whitesmoke", 0xf5f5f5ff} + , {"yellow", 0xffff00ff} + , {"yellowgreen", 0x9acd32ff} + , {NULL, NULL} +}; + +/* + * Hex digit int val. + */ + +static int +h(char c) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return c - '0'; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return (c - 'a') + 10; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return (c - 'A') + 10; + } + return 0; +} + +/* + * Return rgba_t from rgba. + */ + +rgba_t +rgba_create(uint32_t rgba) { + rgba_t color; + color.r = (double) (rgba >> 24) / 255; + color.g = (double) ((rgba & 0x00ff0000) >> 16) / 255; + color.b = (double) ((rgba & 0x0000ff00) >> 8) / 255; + color.a = (double) (rgba & 0xff) / 255; + return color; +} + +/* + * Return a string representation of the color. + */ + +void +rgba_to_string(rgba_t rgba, char *buf) { + if (1 == rgba.a) { + sprintf(buf, "#%.2x%.2x%.2x" + , (int) (rgba.r * 255) + , (int) (rgba.g * 255) + , (int) (rgba.b * 255)); + } else { + sprintf(buf, "rgba(%d, %d, %d, %.2f)" + , (int) (rgba.r * 255) + , (int) (rgba.g * 255) + , (int) (rgba.b * 255) + , rgba.a); + } +} + +/* + * Return rgba from (r,g,b,a). + */ + +static inline int32_t +rgba_from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return + r << 24 + | g << 16 + | b << 8 + | a; +} + +/* + * Return rgba from (r,g,b). + */ + +static int32_t +rgba_from_rgb(uint8_t r, uint8_t g, uint8_t b) { + return rgba_from_rgba(r, g, b, 255); +} + +/* + * Return rgb from "#RRGGBB". + */ + +static int32_t +rgba_from_hex6_string(const char *str) { + return rgba_from_rgb( + (h(str[0]) << 4) + h(str[1]) + , (h(str[2]) << 4) + h(str[3]) + , (h(str[4]) << 4) + h(str[5]) + ); +} + +/* + * Return rgb from "#RGB" + */ + +static int32_t +rgba_from_hex3_string(const char *str) { + return rgba_from_rgb( + (h(str[0]) << 4) + h(str[0]) + , (h(str[1]) << 4) + h(str[1]) + , (h(str[2]) << 4) + h(str[2]) + ); +} + +/* + * Return rgb from "rgb()" + */ + +static int32_t +rgba_from_rgb_string(const char *str, short *ok) { + if (str == strnstr(str, "rgb(", 4)) { + str += 4; + WHITESPACE; + uint8_t r = 0, g = 0, b = 0; + CHANNEL(r); + CHANNEL(g); + CHANNEL(b); + return *ok = 1, rgba_from_rgb(r, g, b); + } + return *ok = 0; +} + +/* + * Return rgb from "rgba()" + */ + +static int32_t +rgba_from_rgba_string(const char *str, short *ok) { + if (str == strnstr(str, "rgba(", 5)) { + str += 5; + WHITESPACE; + uint8_t r = 0, g = 0, b = 0; + float a = 0; + CHANNEL(r); + CHANNEL(g); + CHANNEL(b); + // TODO: less strict + if ('1' == *str) { + a = 1; + } else { + if ('0' == *str) a = 0, ++str; + if ('.' == *str) { + ++str; + float n = .1; + while (*str >= '0' && *str <= '9') { + a += (*str++ - '0') * n; + n *= .1; + } + } + } + return *ok = 1, rgba_from_rgba(r, g, b, a * 255); + } + return *ok = 0; +} + +/* + * Return rgb from: + * + * - "#RGB" + * - "#RRGGBB" + * + */ + +static int32_t +rgba_from_hex_string(const char *str, short *ok) { + size_t len = strlen(str); + *ok = 1; + if (6 == len) return rgba_from_hex6_string(str); + if (3 == len) return rgba_from_hex3_string(str); + return *ok = 0; +} + +/* + * Return named color value. + */ + +static int32_t +rgba_from_name_string(const char *str, short *ok) { + int i = 0; + struct named_color color; + while ((color = named_colors[i++]).name) { + if (*str == *color.name && 0 == strcmp(str, color.name)) + return *ok = 1, color.val; + } + return *ok = 0; +} + +/* + * Return rgb from: + * + * - #RGB + * - #RRGGBB + * - rgb(r,g,b) + * - rgba(r,g,b,a) + * - name + * + */ + +int32_t +rgba_from_string(const char *str, short *ok) { + if ('#' == str[0]) + return rgba_from_hex_string(++str, ok); + if (str == strnstr(str, "rgba", 4)) + return rgba_from_rgba_string(str, ok); + if (str == strnstr(str, "rgb", 3)) + return rgba_from_rgb_string(str, ok); + return rgba_from_name_string(str, ok); +} + +/* + * Inspect the given rgba color. + */ + +void +rgba_inspect(int32_t rgba) { + printf("rgba(%d,%d,%d,%d)\n" + , rgba >> 24 & 0xFF + , rgba >> 16 & 0xFF + , rgba >> 8 & 0xFF + , rgba & 0xFF + ); +} diff --git a/src/color.h b/src/color.h new file mode 100644 index 0000000..10de606 --- /dev/null +++ b/src/color.h @@ -0,0 +1,39 @@ + +// +// color.h +// +// Copyright (c) 2010 LearnBoost +// + +#ifndef __COLOR_PARSER_H__ +#define __COLOR_PARSER_H__ + +#include +#include +#include + +/* + * RGBA struct. + */ + +typedef struct { + double r, g, b, a; +} rgba_t; + +/* + * Prototypes. + */ + +rgba_t +rgba_create(uint32_t rgba); + +int32_t +rgba_from_string(const char *str, short *ok); + +void +rgba_to_string(rgba_t rgba, char *buf); + +void +rgba_inspect(int32_t rgba); + +#endif /* __COLOR_PARSER_H__ */ diff --git a/test/canvas.test.js b/test/canvas.test.js index b758395..48065e0 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -5,7 +5,6 @@ var Canvas = require('canvas') , assert = require('assert') - , parseColor = Canvas.Context2d.parseColor , parseFont = Canvas.Context2d.parseFont , sys = require('sys') , fs = require('fs'); @@ -19,104 +18,6 @@ module.exports = { assert.match(Canvas.cairoVersion, /^\d+\.\d+\.\d+$/); }, - 'test .parseColor()': function(assert){ - assert.equal(null, parseColor()); - assert.equal(null, parseColor('')); - - // rgb() - assert.eql([255,165,0,1], parseColor('rgb(255,165,0)')); - assert.eql([255,165,0,1], parseColor('rgb(255, 165, 0)')); - assert.eql([255,165,0,1], parseColor('rgb(255 , 165 , 0)')); - assert.equal(null, parseColor('rgb()')); - - // rgba() - assert.eql([255,165,0,1], parseColor('rgba(255,165,0,1)')); - assert.eql([255,165,0,1], parseColor('rgba(255,165,0,1)')); - assert.eql([255,165,0,.6], parseColor('rgba(255,165,0,0.6)')); - assert.eql([255,165,0,.6], parseColor('rgba(255,165, 0, 0.6)')); - assert.eql([255,165,0,.6], parseColor('rgba(255,165 , 0 ,.6)')); - assert.equal(null, parseColor('rgba(2554,165 , 0 ,.6)')); - assert.equal(null, parseColor('rgba()')); - - // hsl() - assert.eql([255,0,0,1], parseColor('hsl(0,100.0,50.0)')); - assert.eql([255,0,0,1], parseColor('hsl(360,100.0,50.0)')); - assert.eql([0,255,0,1], parseColor('hsl(120,100.0,50.0)')); - assert.eql([0,0,255,1], parseColor('hsl(240,100.0,50.0)')); - assert.equal(null, parseColor('hsl()')); - - // adapted from tables at http://www.w3.org/TR/css3-color/#hsl-examples - // NB:- corrected rounded percents to precise percents - // e.g. 13% --> 12.5%, etc. - // ...presumably the precise values were used to generate the tables? - assert.eql(parseColor('#FFFFFF'), parseColor('hsl(0,100%,100%)')) - assert.eql(parseColor('#FFFFFF'), parseColor('hsl(0,75%,100%)')) - assert.eql(parseColor('#FFFFFF'), parseColor('hsl(0,50%,100%)')) - assert.eql(parseColor('#FFFFFF'), parseColor('hsl(0,25%,100%)')) - assert.eql(parseColor('#FFFFFF'), parseColor('hsl(0,0%,100%)')) - assert.eql(parseColor('#FFBFBF'), parseColor('hsl(0,100%,87.5%)')) - assert.eql(parseColor('#F7C7C7'), parseColor('hsl(0,75%,87.5%)')) - assert.eql(parseColor('#EFCFCF'), parseColor('hsl(0,50%,87.5%)')) - assert.eql(parseColor('#E7D7D7'), parseColor('hsl(0,25%,87.5%)')) - assert.eql(parseColor('#DFDFDF'), parseColor('hsl(0,0%,87.5%)')) - assert.eql(parseColor('#FF8080'), parseColor('hsl(0,100%,75%)')) - assert.eql(parseColor('#EF8F8F'), parseColor('hsl(0,75%,75%)')) - assert.eql(parseColor('#DF9F9F'), parseColor('hsl(0,50%,75%)')) - assert.eql(parseColor('#CFAFAF'), parseColor('hsl(0,25%,75%)')) - assert.eql(parseColor('#BFBFBF'), parseColor('hsl(0,0%,75%)')) - assert.eql(parseColor('#FF4040'), parseColor('hsl(0,100%,62.5%)')) - assert.eql(parseColor('#E75858'), parseColor('hsl(0,75%,62.5%)')) - assert.eql(parseColor('#CF7070'), parseColor('hsl(0,50%,62.5%)')) - assert.eql(parseColor('#B78787'), parseColor('hsl(0,25%,62.5%)')) - assert.eql(parseColor('#9F9F9F'), parseColor('hsl(0,0%,62.5%)')) - assert.eql(parseColor('#FF0000'), parseColor('hsl(0,100%,50%)')) - assert.eql(parseColor('#DF2020'), parseColor('hsl(0,75%,50%)')) - assert.eql(parseColor('#BF4040'), parseColor('hsl(0,50%,50%)')) - assert.eql(parseColor('#9F6060'), parseColor('hsl(0,25%,50%)')) - assert.eql(parseColor('#808080'), parseColor('hsl(0,0%,50%)')) - assert.eql(parseColor('#BF0000'), parseColor('hsl(0,100%,37.5%)')) - assert.eql(parseColor('#A71818'), parseColor('hsl(0,75%,37.5%)')) - assert.eql(parseColor('#8F3030'), parseColor('hsl(0,50%,37.5%)')) - assert.eql(parseColor('#784848'), parseColor('hsl(0,25%,37.5%)')) - assert.eql(parseColor('#606060'), parseColor('hsl(0,0%,37.5%)')) - assert.eql(parseColor('#800000'), parseColor('hsl(0,100%,25%)')) - assert.eql(parseColor('#701010'), parseColor('hsl(0,75%,25%)')) - assert.eql(parseColor('#602020'), parseColor('hsl(0,50%,25%)')) - assert.eql(parseColor('#503030'), parseColor('hsl(0,25%,25%)')) - assert.eql(parseColor('#404040'), parseColor('hsl(0,0%,25%)')) - assert.eql(parseColor('#400000'), parseColor('hsl(0,100%,12.5%)')) - assert.eql(parseColor('#380808'), parseColor('hsl(0,75%,12.5%)')) - assert.eql(parseColor('#301010'), parseColor('hsl(0,50%,12.5%)')) - assert.eql(parseColor('#281818'), parseColor('hsl(0,25%,12.5%)')) - assert.eql(parseColor('#202020'), parseColor('hsl(0,0%,12.5%)')) - assert.eql(parseColor('#000000'), parseColor('hsl(0,100%,0%)')) - assert.eql(parseColor('#000000'), parseColor('hsl(0,75%,0%)')) - assert.eql(parseColor('#000000'), parseColor('hsl(0,50%,0%)')) - assert.eql(parseColor('#000000'), parseColor('hsl(0,25%,0%)')) - assert.eql(parseColor('#000000'), parseColor('hsl(0,0%,0%)')) - - // TODO: there are 11 more tables to adapt from - // http://www.w3.org/TR/css3-color/#hsl-examples :) - - // hsla() - assert.eql([255,0,0,1], parseColor('hsla(0,100.0,50.0,1.0)')); - assert.eql([255,0,0,1], parseColor('hsla(360,100.0,50.0,1.0)')); - assert.eql([0,255,0,1], parseColor('hsla(120,100.0,50.0,1.0)')); - assert.eql([0,0,255,1], parseColor('hsla(240,100.0,50.0,1.0)')); - assert.equal(null, parseColor('hsl()')); - - // hex - assert.eql([165,89,89,1], parseColor('#A55959')); - assert.eql([255,255,255,1], parseColor('#FFFFFF')); - assert.eql([255,255,255,1], parseColor('#ffffff')); - assert.eql([255,255,255,1], parseColor('#FFF')); - assert.eql([255,255,255,1], parseColor('#fff')); - - // name - assert.eql([255,255,255,1], parseColor('white')); - assert.eql([0,0,0,1], parseColor('black')); - }, - 'test .parseFont()': function(assert){ var tests = [ '20px Arial' @@ -191,7 +92,10 @@ module.exports = { assert.equal('#80c880', ctx[prop], prop + ' rgba(128, 200, 128, 1) -> #80c880, got ' + ctx[prop]); ctx[prop] = 'rgba(128,80,0,0.5)'; - assert.equal('rgba(128, 80, 0, 0.5)', ctx[prop], prop + ' rgba(128,80,0,0.5) -> rgba(128, 80, 0, 0.5), got ' + ctx[prop]); + assert.equal('rgba(128, 80, 0, 0.50)', ctx[prop], prop + ' rgba(128,80,0,0.5) -> rgba(128, 80, 0, 0.5), got ' + ctx[prop]); + + ctx[prop] = 'rgba(128,80,0,0.75)'; + assert.equal('rgba(128, 80, 0, 0.75)', ctx[prop], prop + ' rgba(128,80,0,0.75) -> rgba(128, 80, 0, 0.75), got ' + ctx[prop]); if ('shadowColor' == prop) return;