/*!

 * Copyright (c) 2010 Simo Kinnunen.

 * Licensed under the MIT license.

 *

 * @version ${Version}

 */



var Cufon = (function() {



	var api = function() {

		return api.replace.apply(null, arguments);

	};



	var DOM = api.DOM = {



		ready: (function() {



			var complete = false, readyStatus = { loaded: 1, complete: 1 };



			var queue = [], perform = function() {

				if (complete) return;

				complete = true;

				for (var fn; fn = queue.shift(); fn());

			};



			// Gecko, Opera, WebKit r26101+



			if (document.addEventListener) {

				document.addEventListener('DOMContentLoaded', perform, false);

				window.addEventListener('pageshow', perform, false); // For cached Gecko pages

			}



			// Old WebKit, Internet Explorer



			if (!window.opera && document.readyState) (function() {

				readyStatus[document.readyState] ? perform() : setTimeout(arguments.callee, 10);

			})();



			// Internet Explorer



			if (document.readyState && document.createStyleSheet) (function() {

				try {

					document.body.doScroll('left');

					perform();

				}

				catch (e) {

					setTimeout(arguments.callee, 1);

				}

			})();



			addEvent(window, 'load', perform); // Fallback



			return function(listener) {

				if (!arguments.length) perform();

				else complete ? listener() : queue.push(listener);

			};



		})(),



		root: function() {

			return document.documentElement || document.body;

		}



	};



	var CSS = api.CSS = {



		Size: function(value, base) {



			this.value = parseFloat(value);

			this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';



			this.convert = function(value) {

				return value / base * this.value;

			};



			this.convertFrom = function(value) {

				return value / this.value * base;

			};



			this.toString = function() {

				return this.value + this.unit;

			};



		},



		addClass: function(el, className) {

			var current = el.className;

			el.className = current + (current && ' ') + className;

			return el;

		},



		color: cached(function(value) {

			var parsed = {};

			parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) {

				parsed.opacity = parseFloat($2);

				return 'rgb(' + $1 + ')';

			});

			return parsed;

		}),



		// has no direct CSS equivalent.

		// @see http://msdn.microsoft.com/en-us/library/system.windows.fontstretches.aspx

		fontStretch: cached(function(value) {

			if (typeof value == 'number') return value;

			if (/%$/.test(value)) return parseFloat(value) / 100;

			return {

				'ultra-condensed': 0.5,

				'extra-condensed': 0.625,

				condensed: 0.75,

				'semi-condensed': 0.875,

				'semi-expanded': 1.125,

				expanded: 1.25,

				'extra-expanded': 1.5,

				'ultra-expanded': 2

			}[value] || 1;

		}),



		getStyle: function(el) {

			var view = document.defaultView;

			if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));

			if (el.currentStyle) return new Style(el.currentStyle);

			return new Style(el.style);

		},



		gradient: cached(function(value) {

			var gradient = {

				id: value,

				type: value.match(/^-([a-z]+)-gradient\(/)[1],

				stops: []

			}, colors = value.substr(value.indexOf('(')).match(/([\d.]+=)?(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)/ig);

			for (var i = 0, l = colors.length, stop; i < l; ++i) {

				stop = colors[i].split('=', 2).reverse();

				gradient.stops.push([ stop[1] || i / (l - 1), stop[0] ]);

			}

			return gradient;

		}),



		quotedList: cached(function(value) {

			// doesn't work properly with empty quoted strings (""), but

			// it's not worth the extra code.

			var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;

			while (match = re.exec(value)) list.push(match[3] || match[1]);

			return list;

		}),



		recognizesMedia: cached(function(media) {

			var el = document.createElement('style'), sheet, container, supported;

			el.type = 'text/css';

			el.media = media;

			try { // this is cached anyway

				el.appendChild(document.createTextNode('/**/'));

			} catch (e) {}

			container = elementsByTagName('head')[0];

			container.insertBefore(el, container.firstChild);

			sheet = (el.sheet || el.styleSheet);

			supported = sheet && !sheet.disabled;

			container.removeChild(el);

			return supported;

		}),



		removeClass: function(el, className) {

			var re = RegExp('(?:^|\\s+)' + className +  '(?=\\s|$)', 'g');

			el.className = el.className.replace(re, '');

			return el;

		},



		supports: function(property, value) {

			var checker = document.createElement('span').style;

			if (checker[property] === undefined) return false;

			checker[property] = value;

			return checker[property] === value;

		},



		textAlign: function(word, style, position, wordCount) {

			if (style.get('textAlign') == 'right') {

				if (position > 0) word = ' ' + word;

			}

			else if (position < wordCount - 1) word += ' ';

			return word;

		},



		textShadow: cached(function(value) {

			if (value == 'none') return null;

			var shadows = [], currentShadow = {}, result, offCount = 0;

			var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/ig;

			while (result = re.exec(value)) {

				if (result[0] == ',') {

					shadows.push(currentShadow);

					currentShadow = {};

					offCount = 0;

				}

				else if (result[1]) {

					currentShadow.color = result[1];

				}

				else {

					currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2];

				}

			}

			shadows.push(currentShadow);

			return shadows;

		}),



		textTransform: (function() {

			var map = {

				uppercase: function(s) {

					return s.toUpperCase();

				},

				lowercase: function(s) {

					return s.toLowerCase();

				},

				capitalize: function(s) {

					return s.replace(/(?:^|\s)./g, function($0) {

						return $0.toUpperCase();

					});

				}

			};

			return function(text, style) {

				var transform = map[style.get('textTransform')];

				return transform ? transform(text) : text;

			};

		})(),



		whiteSpace: (function() {

			var ignore = {

				inline: 1,

				'inline-block': 1,

				'run-in': 1

			};

			var wsStart = /^\s+/, wsEnd = /\s+$/;

			return function(text, style, node, previousElement, simple) {

				if (simple) return text.replace(wsStart, '').replace(wsEnd, ''); // @fixme too simple

				if (previousElement) {

					if (previousElement.nodeName.toLowerCase() == 'br') {

						text = text.replace(wsStart, '');

					}

				}

				if (ignore[style.get('display')]) return text;

				if (!node.previousSibling) text = text.replace(wsStart, '');

				if (!node.nextSibling) text = text.replace(wsEnd, '');

				return text;

			};

		})()



	};



	CSS.ready = (function() {



		// don't do anything in Safari 2 (it doesn't recognize any media type)

		var complete = !CSS.recognizesMedia('all'), hasLayout = false;



		var queue = [], perform = function() {

			complete = true;

			for (var fn; fn = queue.shift(); fn());

		};



		var links = elementsByTagName('link'), styles = elementsByTagName('style');



		function isContainerReady(el) {

			return el.disabled || isSheetReady(el.sheet, el.media || 'screen');

		}



		function isSheetReady(sheet, media) {

			// in Opera sheet.disabled is true when it's still loading,

			// even though link.disabled is false. they stay in sync if

			// set manually.

			if (!CSS.recognizesMedia(media || 'all')) return true;

			if (!sheet || sheet.disabled) return false;

			try {

				var rules = sheet.cssRules, rule;

				if (rules) {

					// needed for Safari 3 and Chrome 1.0.

					// in standards-conforming browsers cssRules contains @-rules.

					// Chrome 1.0 weirdness: rules[<number larger than .length - 1>]

					// returns the last rule, so a for loop is the only option.

					search: for (var i = 0, l = rules.length; rule = rules[i], i < l; ++i) {

						switch (rule.type) {

							case 2: // @charset

								break;

							case 3: // @import

								if (!isSheetReady(rule.styleSheet, rule.media.mediaText)) return false;

								break;

							default:

								// only @charset can precede @import

								break search;

						}

					}

				}

			}

			catch (e) {} // probably a style sheet from another domain

			return true;

		}



		function allStylesLoaded() {

			// Internet Explorer's style sheet model, there's no need to do anything

			if (document.createStyleSheet) return true;

			// standards-compliant browsers

			var el, i;

			for (i = 0; el = links[i]; ++i) {

				if (el.rel.toLowerCase() == 'stylesheet' && !isContainerReady(el)) return false;

			}

			for (i = 0; el = styles[i]; ++i) {

				if (!isContainerReady(el)) return false;

			}

			return true;

		}



		DOM.ready(function() {

			// getComputedStyle returns null in Gecko if used in an iframe with display: none

			if (!hasLayout) hasLayout = CSS.getStyle(document.body).isUsable();

			if (complete || (hasLayout && allStylesLoaded())) perform();

			else setTimeout(arguments.callee, 10);

		});



		return function(listener) {

			if (complete) listener();

			else queue.push(listener);

		};



	})();



	function Font(data) {



		var face = this.face = data.face, wordSeparators = {

			'\u0020': 1,

			'\u00a0': 1,

			'\u3000': 1

		};



		this.glyphs = (function(glyphs) {

			var key, fallbacks = {

				'\u2011': '\u002d',

				'\u00ad': '\u2011'

			};

			for (key in fallbacks) {

				if (!hasOwnProperty(fallbacks, key)) continue;

				if (!glyphs[key]) glyphs[key] = glyphs[fallbacks[key]];

			}

			return glyphs;

		})(data.glyphs);



		this.w = data.w;

		this.baseSize = parseInt(face['units-per-em'], 10);



		this.family = face['font-family'].toLowerCase();

		this.weight = face['font-weight'];

		this.style = face['font-style'] || 'normal';



		this.viewBox = (function () {

			var parts = face.bbox.split(/\s+/);

			var box = {

				minX: parseInt(parts[0], 10),

				minY: parseInt(parts[1], 10),

				maxX: parseInt(parts[2], 10),

				maxY: parseInt(parts[3], 10)

			};

			box.width = box.maxX - box.minX;

			box.height = box.maxY - box.minY;

			box.toString = function() {

				return [ this.minX, this.minY, this.width, this.height ].join(' ');

			};

			return box;

		})();



		this.ascent = -parseInt(face.ascent, 10);

		this.descent = -parseInt(face.descent, 10);



		this.height = -this.ascent + this.descent;



		this.spacing = function(chars, letterSpacing, wordSpacing) {

			var glyphs = this.glyphs, glyph,

				kerning, k,

				jumps = [],

				width = 0, w,

				i = -1, j = -1, chr;

			while (chr = chars[++i]) {

				glyph = glyphs[chr] || this.missingGlyph;

				if (!glyph) continue;

				if (kerning) {

					width -= k = kerning[chr] || 0;

					jumps[j] -= k;

				}

				w = glyph.w;

				if (isNaN(w)) w = +this.w; // may have been a String in old fonts

				if (w > 0) {

					w += letterSpacing;

					if (wordSeparators[chr]) w += wordSpacing;

				}

				width += jumps[++j] = ~~w; // get rid of decimals

				kerning = glyph.k;

			}

			jumps.total = width;

			return jumps;

		};



	}



	function FontFamily() {



		var styles = {}, mapping = {

			oblique: 'italic',

			italic: 'oblique'

		};



		this.add = function(font) {

			(styles[font.style] || (styles[font.style] = {}))[font.weight] = font;

		};



		this.get = function(style, weight) {

			var weights = styles[style] || styles[mapping[style]]

				|| styles.normal || styles.italic || styles.oblique;

			if (!weights) return null;

			// we don't have to worry about "bolder" and "lighter"

			// because IE's currentStyle returns a numeric value for it,

			// and other browsers use the computed value anyway

			weight = {

				normal: 400,

				bold: 700

			}[weight] || parseInt(weight, 10);

			if (weights[weight]) return weights[weight];

			// http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight

			// Gecko uses x99/x01 for lighter/bolder

			var up = {

				1: 1,

				99: 0

			}[weight % 100], alts = [], min, max;

			if (up === undefined) up = weight > 400;

			if (weight == 500) weight = 400;

			for (var alt in weights) {

				if (!hasOwnProperty(weights, alt)) continue;

				alt = parseInt(alt, 10);

				if (!min || alt < min) min = alt;

				if (!max || alt > max) max = alt;

				alts.push(alt);

			}

			if (weight < min) weight = min;

			if (weight > max) weight = max;

			alts.sort(function(a, b) {

				return (up

					? (a >= weight && b >= weight) ? a < b : a > b

					: (a <= weight && b <= weight) ? a > b : a < b) ? -1 : 1;

			});

			return weights[alts[0]];

		};



	}



	function HoverHandler() {



		function contains(node, anotherNode) {

			try {

				if (node.contains) return node.contains(anotherNode);

				return node.compareDocumentPosition(anotherNode) & 16;

			}

			catch(e) {} // probably a XUL element such as a scrollbar

			return false;

		}



		function onOverOut(e) {

			var related = e.relatedTarget;

			// there might be no relatedTarget if the element is right next

			// to the window frame

			if (related && contains(this, related)) return;

			trigger(this, e.type == 'mouseover');

		}



		function onEnterLeave(e) {

			trigger(this, e.type == 'mouseenter');

		}



		function trigger(el, hoverState) {

			// A timeout is needed so that the event can actually "happen"

			// before replace is triggered. This ensures that styles are up

			// to date.

			setTimeout(function() {

				var options = sharedStorage.get(el).options;

				api.replace(el, hoverState ? merge(options, options.hover) : options, true);

			}, 10);

		}



		this.attach = function(el) {

			if (el.onmouseenter === undefined) {

				addEvent(el, 'mouseover', onOverOut);

				addEvent(el, 'mouseout', onOverOut);

			}

			else {

				addEvent(el, 'mouseenter', onEnterLeave);

				addEvent(el, 'mouseleave', onEnterLeave);

			}

		};



	}



	function ReplaceHistory() {



		var list = [], map = {};



		function filter(keys) {

			var values = [], key;

			for (var i = 0; key = keys[i]; ++i) values[i] = list[map[key]];

			return values;

		}



		this.add = function(key, args) {

			map[key] = list.push(args) - 1;

		};



		this.repeat = function() {

			var snapshot = arguments.length ? filter(arguments) : list, args;

			for (var i = 0; args = snapshot[i++];) api.replace(args[0], args[1], true);

		};



	}



	function Storage() {



		var map = {}, at = 0;



		function identify(el) {

			return el.cufid || (el.cufid = ++at);

		}



		this.get = function(el) {

			var id = identify(el);

			return map[id] || (map[id] = {});

		};



	}



	function Style(style) {



		var custom = {}, sizes = {};



		this.extend = function(styles) {

			for (var property in styles) {

				if (hasOwnProperty(styles, property)) custom[property] = styles[property];

			}

			return this;

		};



		this.get = function(property) {

			return custom[property] != undefined ? custom[property] : style[property];

		};



		this.getSize = function(property, base) {

			return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base));

		};



		this.isUsable = function() {

			return !!style;

		};



	}



	function addEvent(el, type, listener) {

		if (el.addEventListener) {

			el.addEventListener(type, listener, false);

		}

		else if (el.attachEvent) {

			el.attachEvent('on' + type, function() {

				return listener.call(el, window.event);

			});

		}

	}



	function attach(el, options) {

		var storage = sharedStorage.get(el);

		if (storage.options) return el;

		if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) {

			hoverHandler.attach(el);

		}

		storage.options = options;

		return el;

	}



	function cached(fun) {

		var cache = {};

		return function(key) {

			if (!hasOwnProperty(cache, key)) cache[key] = fun.apply(null, arguments);

			return cache[key];

		};

	}



	function getFont(el, style) {

		var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;

		for (var i = 0; family = families[i]; ++i) {

			if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight'));

		}

		return null;

	}



	function elementsByTagName(query) {

		return document.getElementsByTagName(query);

	}



	function hasOwnProperty(obj, property) {

		return obj.hasOwnProperty(property);

	}



	function merge() {

		var merged = {}, arg, key;

		for (var i = 0, l = arguments.length; arg = arguments[i], i < l; ++i) {

			for (key in arg) {

				if (hasOwnProperty(arg, key)) merged[key] = arg[key];

			}

		}

		return merged;

	}



	function process(font, text, style, options, node, el) {

		var fragment = document.createDocumentFragment(), processed;

		if (text === '') return fragment;

		var separate = options.separate;

		var parts = text.split(separators[separate]), needsAligning = (separate == 'words');

		if (needsAligning && HAS_BROKEN_REGEXP) {

			// @todo figure out a better way to do this

			if (/^\s/.test(text)) parts.unshift('');

			if (/\s$/.test(text)) parts.push('');

		}

		for (var i = 0, l = parts.length; i < l; ++i) {

			processed = engines[options.engine](font,

				needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i],

				style, options, node, el, i < l - 1);

			if (processed) fragment.appendChild(processed);

		}

		return fragment;

	}



	function replaceElement(el, options) {

		var name = el.nodeName.toLowerCase();

		if (options.ignore[name]) return;

		if (options.onBeforeReplace) options.onBeforeReplace(el, options);

		var replace = !options.textless[name], simple = (options.trim === 'simple');

		var style = CSS.getStyle(attach(el, options)).extend(options);

		// may cause issues if the element contains other elements

		// with larger fontSize, however such cases are rare and can

		// be fixed by using a more specific selector

		if (parseFloat(style.get('fontSize')) === 0) return;

		var font = getFont(el, style), node, type, next, anchor, text, lastElement;

		var isShy = options.softHyphens, anyShy = false, pos, shy, reShy = /\u00ad/g;

		var modifyText = options.modifyText;

		if (!font) return;

		for (node = el.firstChild; node; node = next) {

			type = node.nodeType;

			next = node.nextSibling;

			if (replace && type == 3) {

				if (isShy && el.nodeName.toLowerCase() != TAG_SHY) {

					pos = node.data.indexOf('\u00ad');

					if (pos >= 0) {

						node.splitText(pos);

						next = node.nextSibling;

						next.deleteData(0, 1);

						shy = document.createElement(TAG_SHY);

						shy.appendChild(document.createTextNode('\u00ad'));

						el.insertBefore(shy, next);

						next = shy;

						anyShy = true;

					}

				}

				// Node.normalize() is broken in IE 6, 7, 8

				if (anchor) {

					anchor.appendData(node.data);

					el.removeChild(node);

				}

				else anchor = node;

				if (next) continue;

			}

			if (anchor) {

				text = anchor.data;

				if (!isShy) text = text.replace(reShy, '');

				text = CSS.whiteSpace(text, style, anchor, lastElement, simple);

				// modify text only on the first replace

				if (modifyText) text = modifyText(text, anchor, el, options);

				el.replaceChild(process(font, text, style, options, node, el), anchor);

				anchor = null;

			}

			if (type == 1) {

				if (node.firstChild) {

					if (node.nodeName.toLowerCase() == 'cufon') {

						engines[options.engine](font, null, style, options, node, el);

					}

					else arguments.callee(node, options);

				}

				lastElement = node;

			}

		}

		if (isShy && anyShy) {

			updateShy(el);

			if (!trackingShy) addEvent(window, 'resize', updateShyOnResize);

			trackingShy = true;

		}

		if (options.onAfterReplace) options.onAfterReplace(el, options);

	}



	function updateShy(context) {

		var shys, shy, parent, glue, newGlue, next, prev, i;

		shys = context.getElementsByTagName(TAG_SHY);

		// unfortunately there doesn't seem to be any easy

		// way to avoid having to loop through the shys twice.

		for (i = 0; shy = shys[i]; ++i) {

			shy.className = C_SHY_DISABLED;

			glue = parent = shy.parentNode;

			if (glue.nodeName.toLowerCase() != TAG_GLUE) {

				newGlue = document.createElement(TAG_GLUE);

				newGlue.appendChild(shy.previousSibling);

				parent.insertBefore(newGlue, shy);

				newGlue.appendChild(shy);

			}

			else {

				// get rid of double glue (edge case fix)

				glue = glue.parentNode;

				if (glue.nodeName.toLowerCase() == TAG_GLUE) {

					parent = glue.parentNode;

					while (glue.firstChild) {

						parent.insertBefore(glue.firstChild, glue);

					}

					parent.removeChild(glue);

				}

			}

		}

		for (i = 0; shy = shys[i]; ++i) {

			shy.className = '';

			glue = shy.parentNode;

			parent = glue.parentNode;

			next = glue.nextSibling || parent.nextSibling;

			// make sure we're comparing same types

			prev = (next.nodeName.toLowerCase() == TAG_GLUE) ? glue : shy.previousSibling;

			if (prev.offsetTop >= next.offsetTop) {

				shy.className = C_SHY_DISABLED;

				if (prev.offsetTop < next.offsetTop) {

					// we have an annoying edge case, double the glue

					newGlue = document.createElement(TAG_GLUE);

					parent.insertBefore(newGlue, glue);

					newGlue.appendChild(glue);

					newGlue.appendChild(next);

				}

			}

		}

	}



	function updateShyOnResize() {

		if (ignoreResize) return; // needed for IE

		CSS.addClass(DOM.root(), C_VIEWPORT_RESIZING);

		clearTimeout(shyTimer);

		shyTimer = setTimeout(function() {

			ignoreResize = true;

			CSS.removeClass(DOM.root(), C_VIEWPORT_RESIZING);

			updateShy(document);

			ignoreResize = false;

		}, 100);

	}



	var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0;

	var TAG_GLUE = 'cufonglue';

	var TAG_SHY = 'cufonshy';

	var C_SHY_DISABLED = 'cufon-shy-disabled';

	var C_VIEWPORT_RESIZING = 'cufon-viewport-resizing';



	var sharedStorage = new Storage();

	var hoverHandler = new HoverHandler();

	var replaceHistory = new ReplaceHistory();

	var initialized = false;

	var trackingShy = false;

	var shyTimer;

	var ignoreResize = false;



	var engines = {}, fonts = {}, defaultOptions = {

		autoDetect: false,

		engine: null,

		//fontScale: 1,

		//fontScaling: false,

		forceHitArea: false,

		hover: false,

		hoverables: {

			a: true

		},

		ignore: {

			applet: 1,

			canvas: 1,

			col: 1,

			colgroup: 1,

			head: 1,

			iframe: 1,

			map: 1,

			noscript: 1,

			optgroup: 1,

			option: 1,

			script: 1,

			select: 1,

			style: 1,

			textarea: 1,

			title: 1,

			pre: 1

		},

		modifyText: null,

		onAfterReplace: null,

		onBeforeReplace: null,

		printable: true,

		//rotation: 0,

		//selectable: false,

		selector: (

				window.Sizzle

			||	(window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues

			||	(window.dojo && dojo.query)

			||	(window.glow && glow.dom && glow.dom.get)

			||	(window.Ext && Ext.query)

			||	(window.YAHOO && YAHOO.util && YAHOO.util.Selector && YAHOO.util.Selector.query)

			||	(window.$$ && function(query) { return $$(query); })

			||	(window.$ && function(query) { return $(query); })

			||	(document.querySelectorAll && function(query) { return document.querySelectorAll(query); })

			||	elementsByTagName

		),

		separate: 'words', // 'none' and 'characters' are also accepted

		softHyphens: true,

		textless: {

			dl: 1,

			html: 1,

			ol: 1,

			table: 1,

			tbody: 1,

			thead: 1,

			tfoot: 1,

			tr: 1,

			ul: 1

		},

		textShadow: 'none',

		trim: 'advanced'

	};



	var separators = {

		// The first pattern may cause unicode characters above

		// code point 255 to be removed in Safari 3.0. Luckily enough

		// Safari 3.0 does not include non-breaking spaces in \s, so

		// we can just use a simple alternative pattern.

		words: /\s/.test('\u00a0') ? /[^\S\u00a0]+/ : /\s+/,

		characters: '',

		none: /^/

	};



	api.now = function() {

		DOM.ready();

		return api;

	};



	api.refresh = function() {

		replaceHistory.repeat.apply(replaceHistory, arguments);

		return api;

	};



	api.registerEngine = function(id, engine) {

		if (!engine) return api;

		engines[id] = engine;

		return api.set('engine', id);

	};



	api.registerFont = function(data) {

		if (!data) return api;

		var font = new Font(data), family = font.family;

		if (!fonts[family]) fonts[family] = new FontFamily();

		fonts[family].add(font);

		return api.set('fontFamily', '"' + family + '"');

	};



	api.replace = function(elements, options, ignoreHistory) {

		options = merge(defaultOptions, options);

		if (!options.engine) return api; // there's no browser support so we'll just stop here

		if (!initialized) {

			CSS.addClass(DOM.root(), 'cufon-active cufon-loading');

			CSS.ready(function() {

				// fires before any replace() calls, but it doesn't really matter

				CSS.addClass(CSS.removeClass(DOM.root(), 'cufon-loading'), 'cufon-ready');

			});

			initialized = true;

		}

		if (options.hover) options.forceHitArea = true;

		if (options.autoDetect) delete options.fontFamily;

		if (typeof options.textShadow == 'string') {

			options.textShadow = CSS.textShadow(options.textShadow);

		}

		if (typeof options.color == 'string' && /^-/.test(options.color)) {

			options.textGradient = CSS.gradient(options.color);

		}

		else delete options.textGradient;

		if (!ignoreHistory) replaceHistory.add(elements, arguments);

		if (elements.nodeType || typeof elements == 'string') elements = [ elements ];

		CSS.ready(function() {

			for (var i = 0, l = elements.length; i < l; ++i) {

				var el = elements[i];

				if (typeof el == 'string') api.replace(options.selector(el), options, true);

				else replaceElement(el, options);

			}

		});

		return api;

	};



	api.set = function(option, value) {

		defaultOptions[option] = value;

		return api;

	};



	return api;



})();



Cufon.registerEngine('vml', (function() {



	var ns = document.namespaces;

	if (!ns) return;

	ns.add('cvml', 'urn:schemas-microsoft-com:vml');

	ns = null;



	var check = document.createElement('cvml:shape');

	check.style.behavior = 'url(#default#VML)';

	if (!check.coordsize) return; // VML isn't supported

	check = null;



	var HAS_BROKEN_LINEHEIGHT = (document.documentMode || 0) < 8;



	document.write(('<style type="text/css">' +

		'cufoncanvas{text-indent:0;}' +

		'@media screen{' +

			'cvml\\:shape,cvml\\:rect,cvml\\:fill,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute;}' +

			'cufoncanvas{position:absolute;text-align:left;}' +

			'cufon{display:inline-block;position:relative;vertical-align:' +

			(HAS_BROKEN_LINEHEIGHT

				? 'middle'

				: 'text-bottom') +

			';}' +

			'cufon cufontext{position:absolute;left:-10000in;font-size:1px;text-align:left;}' +

			'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +

			'cufonglue{white-space:nowrap;display:inline-block;}' +

			'.cufon-viewport-resizing cufonglue{white-space:normal;}' +

			'a cufon{cursor:pointer}' + // ignore !important here

		'}' +

		'@media print{' +

			'cufon cufoncanvas{display:none;}' +

		'}' +

	'</style>').replace(/;/g, '!important;'));



	function getFontSizeInPixels(el, value) {

		return getSizeInPixels(el, /(?:em|ex|%)$|^[a-z-]+$/i.test(value) ? '1em' : value);

	}



	// Original by Dead Edwards.

	// Combined with getFontSizeInPixels it also works with relative units.

	function getSizeInPixels(el, value) {

		if (!isNaN(value) || /px$/i.test(value)) return parseFloat(value);

		var style = el.style.left, runtimeStyle = el.runtimeStyle.left;

		el.runtimeStyle.left = el.currentStyle.left;

		el.style.left = value.replace('%', 'em');

		var result = el.style.pixelLeft;

		el.style.left = style;

		el.runtimeStyle.left = runtimeStyle;

		return result;

	}



	function getSpacingValue(el, style, size, property) {

		var key = 'computed' + property, value = style[key];

		if (isNaN(value)) {

			value = style.get(property);

			style[key] = value = (value == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, value));

		}

		return value;

	}



	var fills = {};



	function gradientFill(gradient) {

		var id = gradient.id;

		if (!fills[id]) {

			var stops = gradient.stops, fill = document.createElement('cvml:fill'), colors = [];

			fill.type = 'gradient';

			fill.angle = 180;

			fill.focus = '0';

			fill.method = 'none';

			fill.color = stops[0][1];

			for (var j = 1, k = stops.length - 1; j < k; ++j) {

				colors.push(stops[j][0] * 100 + '% ' + stops[j][1]);

			}

			fill.colors = colors.join(',');

			fill.color2 = stops[k][1];

			fills[id] = fill;

		}

		return fills[id];

	}



	return function(font, text, style, options, node, el, hasNext) {



		var redraw = (text === null);



		if (redraw) text = node.alt;



		var viewBox = font.viewBox;



		var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));



		var wrapper, canvas;



		if (redraw) {

			wrapper = node;

			canvas = node.firstChild;

		}

		else {

			wrapper = document.createElement('cufon');

			wrapper.className = 'cufon cufon-vml';

			wrapper.alt = text;



			canvas = document.createElement('cufoncanvas');

			wrapper.appendChild(canvas);



			if (options.printable) {

				var print = document.createElement('cufontext');

				print.appendChild(document.createTextNode(text));

				wrapper.appendChild(print);

			}



			// ie6, for some reason, has trouble rendering the last VML element in the document.

			// we can work around this by injecting a dummy element where needed.

			// @todo find a better solution

			if (!hasNext) wrapper.appendChild(document.createElement('cvml:shape'));

		}



		var wStyle = wrapper.style;

		var cStyle = canvas.style;



		var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);

		var roundingFactor = roundedHeight / height;

		var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));

		var minX = viewBox.minX, minY = viewBox.minY;



		cStyle.height = roundedHeight;

		cStyle.top = Math.round(size.convert(minY - font.ascent));

		cStyle.left = Math.round(size.convert(minX));



		wStyle.height = size.convert(font.height) + 'px';



		var color = style.get('color');

		var chars = Cufon.CSS.textTransform(text, style).split('');



		var jumps = font.spacing(chars,

			getSpacingValue(el, style, size, 'letterSpacing'),

			getSpacingValue(el, style, size, 'wordSpacing')

		);



		if (!jumps.length) return null;



		var width = jumps.total;

		var fullWidth = -minX + width + (viewBox.width - jumps[jumps.length - 1]);



		var shapeWidth = size.convert(fullWidth * stretchFactor), roundedShapeWidth = Math.round(shapeWidth);



		var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;

		var stretch = 'r' + coordSize + 'ns';



		var fill = options.textGradient && gradientFill(options.textGradient);



		var glyphs = font.glyphs, offsetX = 0;

		var shadows = options.textShadow;

		var i = -1, j = 0, chr;



		while (chr = chars[++i]) {



			var glyph = glyphs[chars[i]] || font.missingGlyph, shape;

			if (!glyph) continue;



			if (redraw) {

				// some glyphs may be missing so we can't use i

				shape = canvas.childNodes[j];

				while (shape.firstChild) shape.removeChild(shape.firstChild); // shadow, fill

			}

			else {

				shape = document.createElement('cvml:shape');

				canvas.appendChild(shape);

			}



			shape.stroked = 'f';

			shape.coordsize = coordSize;

			shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;

			shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;

			shape.fillcolor = color;



			if (fill) shape.appendChild(fill.cloneNode(false));



			// it's important to not set top/left or IE8 will grind to a halt

			var sStyle = shape.style;

			sStyle.width = roundedShapeWidth;

			sStyle.height = roundedHeight;



			if (shadows) {

				// due to the limitations of the VML shadow element there

				// can only be two visible shadows. opacity is shared

				// for all shadows.

				var shadow1 = shadows[0], shadow2 = shadows[1];

				var color1 = Cufon.CSS.color(shadow1.color), color2;

				var shadow = document.createElement('cvml:shadow');

				shadow.on = 't';

				shadow.color = color1.color;

				shadow.offset = shadow1.offX + ',' + shadow1.offY;

				if (shadow2) {

					color2 = Cufon.CSS.color(shadow2.color);

					shadow.type = 'double';

					shadow.color2 = color2.color;

					shadow.offset2 = shadow2.offX + ',' + shadow2.offY;

				}

				shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;

				shape.appendChild(shadow);

			}



			offsetX += jumps[j++];

		}



		// addresses flickering issues on :hover



		var cover = shape.nextSibling, coverFill, vStyle;



		if (options.forceHitArea) {



			if (!cover) {

				cover = document.createElement('cvml:rect');

				cover.stroked = 'f';

				cover.className = 'cufon-vml-cover';

				coverFill = document.createElement('cvml:fill');

				coverFill.opacity = 0;

				cover.appendChild(coverFill);

				canvas.appendChild(cover);

			}



			vStyle = cover.style;



			vStyle.width = roundedShapeWidth;

			vStyle.height = roundedHeight;



		}

		else if (cover) canvas.removeChild(cover);



		wStyle.width = Math.max(Math.ceil(size.convert(width * stretchFactor)), 0);



		if (HAS_BROKEN_LINEHEIGHT) {



			var yAdjust = style.computedYAdjust;



			if (yAdjust === undefined) {

				var lineHeight = style.get('lineHeight');

				if (lineHeight == 'normal') lineHeight = '1em';

				else if (!isNaN(lineHeight)) lineHeight += 'em'; // no unit

				style.computedYAdjust = yAdjust = 0.5 * (getSizeInPixels(el, lineHeight) - parseFloat(wStyle.height));

			}



			if (yAdjust) {

				wStyle.marginTop = Math.ceil(yAdjust) + 'px';

				wStyle.marginBottom = yAdjust + 'px';

			}



		}



		return wrapper;



	};



})());



Cufon.registerEngine('canvas', (function() {



	// Safari 2 doesn't support .apply() on native methods



	var check = document.createElement('canvas');

	if (!check || !check.getContext || !check.getContext.apply) return;

	check = null;



	var HAS_INLINE_BLOCK = Cufon.CSS.supports('display', 'inline-block');



	// Firefox 2 w/ non-strict doctype (almost standards mode)

	var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (document.compatMode == 'BackCompat' || /frameset|transitional/i.test(document.doctype.publicId));



	var styleSheet = document.createElement('style');

	styleSheet.type = 'text/css';

	styleSheet.appendChild(document.createTextNode((

		'cufon{text-indent:0;}' +

		'@media screen,projection{' +

			'cufon{display:inline;display:inline-block;position:relative;vertical-align:middle;' +

			(HAS_BROKEN_LINEHEIGHT

				? ''

				: 'font-size:1px;line-height:1px;') +

			'}cufon cufontext{display:-moz-inline-box;display:inline-block;width:0;height:0;text-align:left;text-indent:-10000in;}' +

			(HAS_INLINE_BLOCK

				? 'cufon canvas{position:relative;}'

				: 'cufon canvas{position:absolute;}') +

			'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +

			'cufonglue{white-space:nowrap;display:inline-block;}' +

			'.cufon-viewport-resizing cufonglue{white-space:normal;}' +

		'}' +

		'@media print{' +

			'cufon{padding:0;}' + // Firefox 2

			'cufon canvas{display:none;}' +

		'}'

	).replace(/;/g, '!important;')));

	document.getElementsByTagName('head')[0].appendChild(styleSheet);



	function generateFromVML(path, context) {

		var atX = 0, atY = 0;

		var code = [], re = /([mrvxe])([^a-z]*)/g, match;

		generate: for (var i = 0; match = re.exec(path); ++i) {

			var c = match[2].split(',');

			switch (match[1]) {

				case 'v':

					code[i] = { m: 'bezierCurveTo', a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] };

					break;

				case 'r':

					code[i] = { m: 'lineTo', a: [ atX += ~~c[0], atY += ~~c[1] ] };

					break;

				case 'm':

					code[i] = { m: 'moveTo', a: [ atX = ~~c[0], atY = ~~c[1] ] };

					break;

				case 'x':

					code[i] = { m: 'closePath' };

					break;

				case 'e':

					break generate;

			}

			context[code[i].m].apply(context, code[i].a);

		}

		return code;

	}



	function interpret(code, context) {

		for (var i = 0, l = code.length; i < l; ++i) {

			var line = code[i];

			context[line.m].apply(context, line.a);

		}

	}



	return function(font, text, style, options, node, el) {



		var redraw = (text === null);



		if (redraw) text = node.getAttribute('alt');



		var viewBox = font.viewBox;



		var size = style.getSize('fontSize', font.baseSize);



		var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0;

		var shadows = options.textShadow, shadowOffsets = [];

		if (shadows) {

			for (var i = shadows.length; i--;) {

				var shadow = shadows[i];

				var x = size.convertFrom(parseFloat(shadow.offX));

				var y = size.convertFrom(parseFloat(shadow.offY));

				shadowOffsets[i] = [ x, y ];

				if (y < expandTop) expandTop = y;

				if (x > expandRight) expandRight = x;

				if (y > expandBottom) expandBottom = y;

				if (x < expandLeft) expandLeft = x;

			}

		}



		var chars = Cufon.CSS.textTransform(text, style).split('');



		var jumps = font.spacing(chars,

			~~size.convertFrom(parseFloat(style.get('letterSpacing')) || 0),

			~~size.convertFrom(parseFloat(style.get('wordSpacing')) || 0)

		);



		if (!jumps.length) return null; // there's nothing to render



		var width = jumps.total;



		expandRight += viewBox.width - jumps[jumps.length - 1];

		expandLeft += viewBox.minX;



		var wrapper, canvas;



		if (redraw) {

			wrapper = node;

			canvas = node.firstChild;

		}

		else {

			wrapper = document.createElement('cufon');

			wrapper.className = 'cufon cufon-canvas';

			wrapper.setAttribute('alt', text);



			canvas = document.createElement('canvas');

			wrapper.appendChild(canvas);



			if (options.printable) {

				var print = document.createElement('cufontext');

				print.appendChild(document.createTextNode(text));

				wrapper.appendChild(print);

			}

		}



		var wStyle = wrapper.style;

		var cStyle = canvas.style;



		var height = size.convert(viewBox.height);

		var roundedHeight = Math.ceil(height);

		var roundingFactor = roundedHeight / height;

		var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));

		var stretchedWidth = width * stretchFactor;



		var canvasWidth = Math.ceil(size.convert(stretchedWidth + expandRight - expandLeft));

		var canvasHeight = Math.ceil(size.convert(viewBox.height - expandTop + expandBottom));



		canvas.width = canvasWidth;

		canvas.height = canvasHeight;



		// needed for WebKit and full page zoom

		cStyle.width = canvasWidth + 'px';

		cStyle.height = canvasHeight + 'px';



		// minY has no part in canvas.height

		expandTop += viewBox.minY;



		cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + 'px';

		cStyle.left = Math.round(size.convert(expandLeft)) + 'px';



		var wrapperWidth = Math.max(Math.ceil(size.convert(stretchedWidth)), 0) + 'px';



		if (HAS_INLINE_BLOCK) {

			wStyle.width = wrapperWidth;

			wStyle.height = size.convert(font.height) + 'px';

		}

		else {

			wStyle.paddingLeft = wrapperWidth;

			wStyle.paddingBottom = (size.convert(font.height) - 1) + 'px';

		}



		var g = canvas.getContext('2d'), scale = height / viewBox.height;



		// proper horizontal scaling is performed later

		g.scale(scale, scale * roundingFactor);

		g.translate(-expandLeft, -expandTop);

		g.save();



		function renderText() {

			var glyphs = font.glyphs, glyph, i = -1, j = -1, chr;

			g.scale(stretchFactor, 1);

			while (chr = chars[++i]) {

				var glyph = glyphs[chars[i]] || font.missingGlyph;

				if (!glyph) continue;

				if (glyph.d) {

					g.beginPath();

					if (glyph.code) interpret(glyph.code, g);

					else glyph.code = generateFromVML('m' + glyph.d, g);

					g.fill();

				}

				g.translate(jumps[++j], 0);

			}

			g.restore();

		}



		if (shadows) {

			for (var i = shadows.length; i--;) {

				var shadow = shadows[i];

				g.save();

				g.fillStyle = shadow.color;

				g.translate.apply(g, shadowOffsets[i]);

				renderText();

			}

		}



		var gradient = options.textGradient;

		if (gradient) {

			var stops = gradient.stops, fill = g.createLinearGradient(0, viewBox.minY, 0, viewBox.maxY);

			for (var i = 0, l = stops.length; i < l; ++i) {

				fill.addColorStop.apply(fill, stops[i]);

			}

			g.fillStyle = fill;

		}

		else g.fillStyle = style.get('color');



		renderText();



		return wrapper;



	};



})());


