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