Tj Holowaychuk
14 years ago
3 changed files with 476 additions and 442 deletions
@ -0,0 +1,447 @@ |
|||
|
|||
/*! |
|||
* Canvas - Context2d |
|||
* Copyright (c) 2010 LearnBoost <tj@learnboost.com> |
|||
* MIT Licensed |
|||
*/ |
|||
|
|||
/** |
|||
* Module dependencies. |
|||
*/ |
|||
|
|||
var canvas = require('../build/default/canvas') |
|||
, Context2d = canvas.CanvasRenderingContext2d |
|||
, CanvasGradient = canvas.CanvasGradient |
|||
, colors = require('./colors'); |
|||
|
|||
/** |
|||
* Export `Context2d` as the module. |
|||
*/ |
|||
|
|||
var Context2d = exports = module.exports = Context2d; |
|||
|
|||
/** |
|||
* Cache color string RGBA values. |
|||
*/ |
|||
|
|||
var cache = {}; |
|||
|
|||
/** |
|||
* Text baselines. |
|||
*/ |
|||
|
|||
var baselines = ['alphabetic', 'top', 'bottom', 'middle', 'ideographic', 'hanging']; |
|||
|
|||
/** |
|||
* Font RegExp helpers. |
|||
*/ |
|||
|
|||
var weights = 'normal|bold|bolder|lighter|[1-9](?:00)' |
|||
, styles = 'normal|italic|oblique' |
|||
, units = 'px|pt|pc|in|cm|mm|%' |
|||
, string = '"([^"]+)"|[\\w-]+'; |
|||
|
|||
/** |
|||
* Font parser RegExp; |
|||
*/ |
|||
|
|||
var fontre = new RegExp('^ *' |
|||
+ '(?:(' + weights + ') *)?' |
|||
+ '(?:(' + styles + ') *)?' |
|||
+ '(\\d+)(' + units + ') *' |
|||
+ '((?:' + string + ')( *, *(?:' + string + '))*)' |
|||
); |
|||
|
|||
/** |
|||
* Return a function used to normalize an RGBA color `prop`. |
|||
* |
|||
* @param {String} prop |
|||
* @return {Function} |
|||
* @api private |
|||
*/ |
|||
|
|||
function normalizedColor(prop) { |
|||
return function(){ |
|||
var rgba = this[prop]; |
|||
if (1 == rgba[3]) { |
|||
return '#' |
|||
+ rgba[0].toString(16) |
|||
+ rgba[1].toString(16) |
|||
+ rgba[2].toString(16); |
|||
} else { |
|||
return 'rgba(' |
|||
+ rgba[0] + ', ' |
|||
+ rgba[1] + ', ' |
|||
+ rgba[2] + ', ' |
|||
+ rgba[3] + ')'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Parse font `str`. |
|||
* |
|||
* @param {String} str |
|||
* @return {Object} |
|||
* @api private |
|||
*/ |
|||
|
|||
var parseFont = exports.parseFont = function(str){ |
|||
var font = {} |
|||
, captures = fontre.exec(str); |
|||
|
|||
// Invalid
|
|||
if (!captures) return; |
|||
|
|||
// Populate font object
|
|||
font.weight = captures[1] || 'normal'; |
|||
font.style = captures[2] || 'normal'; |
|||
font.size = parseInt(captures[3], 10); |
|||
font.unit = captures[4]; |
|||
font.family = captures[5]; |
|||
|
|||
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) |
|||
* - 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 |
|||
]; |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* 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)`. |
|||
* |
|||
* @param {Number} x0 |
|||
* @param {Number} y0 |
|||
* @param {Number} x1 |
|||
* @param {Number} y1 |
|||
* @return {CanvasGradient} |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.createLinearGradient = function(x0, y0, x1, y1){ |
|||
return new CanvasGradient(x0, y0, x1, y1); |
|||
}; |
|||
|
|||
/** |
|||
* Create a radial gradient at the given point `(x0, y0)` and `(x1, y1)` |
|||
* and radius `r0` and `r1`. |
|||
* |
|||
* @param {Number} x0 |
|||
* @param {Number} y0 |
|||
* @param {Number} r0 |
|||
* @param {Number} x1 |
|||
* @param {Number} y1 |
|||
* @param {Number} r1 |
|||
* @return {CanvasGradient} |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1){ |
|||
return new CanvasGradient(x0, y0, r0, x1, y1, r1); |
|||
}; |
|||
|
|||
/** |
|||
* Reset transform matrix to identity, then apply the given args. |
|||
* |
|||
* @param {...} |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.setTransform = function(){ |
|||
this.resetTransform(); |
|||
this.transform.apply(this, arguments); |
|||
}; |
|||
|
|||
/** |
|||
* Set the fill style with the given css color string. |
|||
* |
|||
* @see exports.parseColor() |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineSetter__('fillStyle', function(val){ |
|||
if (val instanceof CanvasGradient) { |
|||
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]); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Get the current fill style string. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineGetter__('fillStyle', normalizedColor('lastFillStyle')); |
|||
|
|||
/** |
|||
* Set the stroke style with the given css color string. |
|||
* |
|||
* @see exports.parseColor() |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineSetter__('strokeStyle', function(val){ |
|||
if (val instanceof CanvasGradient) { |
|||
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]); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Get the current stroke style string. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineGetter__('strokeStyle', normalizedColor('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]); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Get the current shadow color string. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineGetter__('shadowColor', normalizedColor('lastShadowColor')); |
|||
|
|||
/** |
|||
* Set font. |
|||
* |
|||
* @see exports.parseFont() |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineSetter__('font', function(val){ |
|||
if ('string' == typeof val) { |
|||
var font; |
|||
if (font = cache[val] || parseFont(val)) { |
|||
this.lastFontString = val; |
|||
|
|||
// TODO: dpi
|
|||
// TODO: remaining unit conversion
|
|||
switch (font.unit) { |
|||
case 'pt': |
|||
font.size /= .75; |
|||
break; |
|||
case 'in': |
|||
font.size *= 96; |
|||
break; |
|||
case 'mm': |
|||
font.size *= 96.0 / 25.4; |
|||
break; |
|||
case 'cm': |
|||
font.size *= 96.0 / 2.54; |
|||
break; |
|||
} |
|||
|
|||
// Cache font object
|
|||
cache[val] = font; |
|||
|
|||
// Set font
|
|||
this.setFont( |
|||
font.weight |
|||
, font.style |
|||
, font.size |
|||
, font.unit |
|||
, font.family); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Get the current font. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineGetter__('font', function(){ |
|||
return this.lastFontString || '10px sans-serif'; |
|||
}); |
|||
|
|||
/** |
|||
* Set text baseline. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineSetter__('textBaseline', function(val){ |
|||
var n = baselines.indexOf(val); |
|||
if (~n) { |
|||
this.lastBaseline = val; |
|||
this.setTextBaseline(n); |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Get the current baseline setting. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineGetter__('textAlign', function(){ |
|||
return this.lastBaseline || 'alphabetic'; |
|||
}); |
|||
|
|||
/** |
|||
* Set text alignment. |
|||
* |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineSetter__('textAlign', function(val){ |
|||
switch (val) { |
|||
case 'center': |
|||
this.setTextAlignment(0); |
|||
this.lastTextAlignment = val; |
|||
break; |
|||
case 'left': |
|||
case 'start': |
|||
this.setTextAlignment(-1); |
|||
this.lastTextAlignment = val; |
|||
break; |
|||
case 'right': |
|||
case 'end': |
|||
this.setTextAlignment(1); |
|||
this.lastTextAlignment = val; |
|||
break; |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* Get the current font. |
|||
* |
|||
* @see exports.parseFont() |
|||
* @api public |
|||
*/ |
|||
|
|||
Context2d.prototype.__defineGetter__('textAlign', function(){ |
|||
return this.lastTextAlignment || 'start'; |
|||
}); |
Loading…
Reference in new issue