Browse Source

Merge branch 'c-color-parser'

v1.x
Tj Holowaychuk 14 years ago
parent
commit
579f9c4d18
  1. 153
      lib/colors.js
  2. 249
      lib/context2d.js
  3. 18
      src/Canvas.h
  4. 27
      src/CanvasGradient.cc
  5. 2
      src/CanvasGradient.h
  6. 102
      src/CanvasRenderingContext2d.cc
  7. 18
      src/CanvasRenderingContext2d.h
  8. 423
      src/color.cc
  9. 39
      src/color.h
  10. 104
      test/canvas.test.js

153
lib/colors.js

@ -1,153 +0,0 @@
/*!
* Canvas - colors
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
* 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'
};

249
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.
*
* @api public
*/
Context2d.prototype.__defineGetter__('strokeStyle', getter('lastStrokeStyle'));
/**
* Set the shadow color with the given css color string.
* Get previous stroke style.
*
* @see exports.parseColor()
* @return {CanvasGradient|String}
* @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.

18
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.
*/

27
src/CanvasGradient.cc

@ -5,6 +5,7 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "color.h"
#include "Canvas.h"
#include "CanvasGradient.h"
@ -24,7 +25,7 @@ Gradient::Initialize(Handle<Object> 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<Value>
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<Gradient>(args.This());
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()
, r / 255 * 1
, g / 255 * 1
, b / 255 * 1
, a);
, color.r
, color.g
, color.b
, color.a);
}
return Undefined();
}

2
src/CanvasGradient.h

@ -15,7 +15,7 @@ class Gradient: public node::ObjectWrap {
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments &args);
static Handle<Value> AddColorStopRGBA(const Arguments &args);
static Handle<Value> 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; }

102
src/CanvasRenderingContext2d.cc

@ -17,16 +17,6 @@
Persistent<FunctionTemplate> 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<Object> 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<String> prop, Local<Value> 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<Context2d>(info.This());
context->state->shadow = rgba_create(rgba);
}
}
/*
* Get shadow color.
*/
Handle<Value>
Context2d::SetShadowRGBA(const Arguments &args) {
HandleScope scope;
RGBA_ARGS(0);
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
RGBA(context->state->shadow,r,g,b,a);
return Undefined();
Context2d::GetShadowColor(Local<String> prop, const AccessorInfo &info) {
char buf[64];
Context2d *context = ObjectWrap::Unwrap<Context2d>(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<Value>
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<Context2d>(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<Value>
Context2d::GetFillColor(Local<String> prop, const AccessorInfo &info) {
char buf[64];
Context2d *context = ObjectWrap::Unwrap<Context2d>(info.This());
rgba_to_string(context->state->fill, buf);
return String::New(buf);
}
/*
* Set stroke color, used internally for strokeStyle=
*/
Handle<Value>
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<Context2d>(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<Value>
Context2d::GetStrokeColor(Local<String> prop, const AccessorInfo &info) {
char buf[64];
Context2d *context = ObjectWrap::Unwrap<Context2d>(info.This());
rgba_to_string(context->state->stroke, buf);
return String::New(buf);
}
/*
* Bezier curve.
*/

18
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<Value> FillText(const Arguments &args);
static Handle<Value> StrokeText(const Arguments &args);
static Handle<Value> SetFont(const Arguments &args);
static Handle<Value> SetFillRGBA(const Arguments &args);
static Handle<Value> SetStrokeRGBA(const Arguments &args);
static Handle<Value> SetShadowRGBA(const Arguments &args);
static Handle<Value> SetFillColor(const Arguments &args);
static Handle<Value> SetStrokeColor(const Arguments &args);
static Handle<Value> SetFillPattern(const Arguments &args);
static Handle<Value> SetStrokePattern(const Arguments &args);
static Handle<Value> SetTextBaseline(const Arguments &args);
@ -87,6 +79,9 @@ class Context2d: public node::ObjectWrap {
static Handle<Value> ArcTo(const Arguments &args);
static Handle<Value> GetGlobalCompositeOperation(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetGlobalAlpha(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetShadowColor(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetFillColor(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetStrokeColor(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetMiterLimit(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetLineCap(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetLineJoin(Local<String> prop, const AccessorInfo &info);
@ -97,6 +92,7 @@ class Context2d: public node::ObjectWrap {
static Handle<Value> GetAntiAlias(Local<String> prop, const AccessorInfo &info);
static void SetGlobalCompositeOperation(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetGlobalAlpha(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetShadowColor(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetMiterLimit(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetLineCap(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetLineJoin(Local<String> prop, Local<Value> val, const AccessorInfo &info);

423
src/color.cc

@ -0,0 +1,423 @@
//
// color.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "color.h"
#include <stdlib.h>
/*
* 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
);
}

39
src/color.h

@ -0,0 +1,39 @@
//
// color.h
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#ifndef __COLOR_PARSER_H__
#define __COLOR_PARSER_H__
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/*
* 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__ */

104
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;

Loading…
Cancel
Save